@getlang/get
Advanced tools
Comparing version 0.0.1 to 0.0.2
@@ -155,3 +155,3 @@ import { doc } from 'prettier'; | ||
const quot = value.includes('`') ? '```' : '`'; | ||
const lines = value.replace(/`/g, '\\`').split('\n'); | ||
const lines = value.split('\n'); | ||
return group([ | ||
@@ -158,0 +158,0 @@ quot, |
@@ -0,1 +1,2 @@ | ||
import type { MaybePromise } from '../utils'; | ||
import type { Node, Stmt, Expr } from './ast'; | ||
@@ -12,3 +13,2 @@ export declare const SKIP: { | ||
}; | ||
type MaybePromise<T> = T | Promise<T>; | ||
type NodeConfig<N extends Node, S, E, A extends boolean = false, TN = TransformNode<N, S, E>, XN = Transform<N, S, E>, Visit = <C extends Node>(child: C) => Transform<C, S, E>, EntryVisitor = (node: N, visit: Visit) => A extends true ? MaybePromise<TN | void | Control> : TN | void | Control, ExitVisitor = (node: TN, originalNode: N) => A extends true ? MaybePromise<XN> : XN, EntryExitVisitor = (node: N, visit: Visit) => A extends true ? MaybePromise<XN | Control> : XN | Control> = ExitVisitor | { | ||
@@ -15,0 +15,0 @@ enter?: EntryVisitor; |
@@ -0,1 +1,2 @@ | ||
import { wait, waitMap } from '../utils'; | ||
import { NodeKind } from './ast'; | ||
@@ -35,9 +36,1 @@ export const SKIP = { __skip: true }; | ||
} | ||
function wait(value, then) { | ||
return value instanceof Promise ? value.then(then) : then(value); | ||
} | ||
function waitMap(values, mapper) { | ||
return values.reduce((acc, value) => acc instanceof Promise | ||
? acc.then(async (acc) => [...acc, await mapper(value)]) | ||
: wait(mapper(value), value => [...acc, value]), []); | ||
} |
@@ -47,2 +47,5 @@ export declare abstract class RuntimeError extends Error { | ||
} | ||
export declare class ImportError extends RuntimeError { | ||
name: string; | ||
} | ||
export declare function invariant(condition: unknown, err: string | RuntimeError): asserts condition; |
@@ -69,2 +69,5 @@ export class RuntimeError extends Error { | ||
} | ||
export class ImportError extends RuntimeError { | ||
name = 'ImportError'; | ||
} | ||
export function invariant(condition, err) { | ||
@@ -71,0 +74,0 @@ if (!condition) { |
import type { Program } from '../ast/ast'; | ||
import * as http from './net/http'; | ||
export declare function execute(program: Program, inputs: Record<string, unknown>, requestFn?: http.RequestFn): Promise<unknown>; | ||
export type InternalHooks = { | ||
request: http.RequestHook; | ||
import: (module: string) => Program | Promise<Program>; | ||
}; | ||
declare class Modules { | ||
private importHook; | ||
private cache; | ||
constructor(importHook: InternalHooks['import']); | ||
import(module: string): Program | Promise<Program>; | ||
get(module: string): Program | Promise<Program> | undefined; | ||
} | ||
export declare function execute(program: Program, inputs: Record<string, unknown>, hooks: InternalHooks, modules?: Modules): Promise<unknown>; | ||
export {}; |
@@ -6,3 +6,3 @@ import { visit, SKIP } from '../ast/visitor'; | ||
import * as type from './value'; | ||
import { invariant, NullSelectionError, ReferenceError, SyntaxError, } from '../errors'; | ||
import { invariant, NullSelectionError, ReferenceError, SyntaxError, ImportError, } from '../errors'; | ||
import * as http from './net/http'; | ||
@@ -15,2 +15,16 @@ import * as html from './values/html'; | ||
import { select } from './select'; | ||
class Modules { | ||
importHook; | ||
cache = {}; | ||
constructor(importHook) { | ||
this.importHook = importHook; | ||
} | ||
import(module) { | ||
return (this.cache[module] ??= this.importHook(module)); | ||
} | ||
get(module) { | ||
return this.cache[module]; | ||
} | ||
} | ||
Headers; | ||
function toValue(value) { | ||
@@ -23,2 +37,5 @@ if (value instanceof type.Html) { | ||
} | ||
else if (value instanceof type.Headers) { | ||
return Object.fromEntries(value.raw); | ||
} | ||
else if (value instanceof type.CookieSet) { | ||
@@ -34,3 +51,3 @@ return Object.fromEntries(Object.entries(value.raw).map(e => [e[0], e[1].value])); | ||
} | ||
export async function execute(program, inputs, requestFn) { | ||
export async function execute(program, inputs, hooks, modules = new Modules(hooks.import)) { | ||
const scope = new RootScope(); | ||
@@ -40,6 +57,18 @@ let optional = [false]; | ||
const visitor = { | ||
DeclImportStmt() { }, | ||
ModuleCallExpr() { | ||
return new type.Value('TODO', null); | ||
async DeclImportStmt(node) { | ||
const module = node.id.value; | ||
try { | ||
await modules.import(module); | ||
} | ||
catch (e) { | ||
throw new ImportError(`Failed to import module: ${module}`); | ||
} | ||
}, | ||
async ModuleCallExpr(node) { | ||
const module = node.name.value; | ||
const external = await modules.get(module); | ||
invariant(external, new ReferenceError(module)); | ||
const output = await execute(external, node.args.raw, hooks, modules); | ||
return new type.Value(output, null); | ||
}, | ||
/** | ||
@@ -58,7 +87,14 @@ * Expression nodes | ||
invariant(value, new ReferenceError(node.value.value)); | ||
if (value instanceof type.Null) { | ||
invariant(allowNull(), new NullSelectionError(value.selector)); | ||
} | ||
return value; | ||
}, | ||
async SliceExpr(node) { | ||
const value = await lang.runSlice(node.slice.value, scope.context?.raw); | ||
return value === null ? new type.Null() : new type.Value(value, null); | ||
const { slice } = node; | ||
const fauxSelector = `slice@${slice.line}:${slice.col}`; | ||
const value = await lang.runSlice(slice.value, scope.context?.raw); | ||
return value === null | ||
? new type.Null(fauxSelector) | ||
: new type.Value(value, null); | ||
}, | ||
@@ -71,4 +107,4 @@ DrillExpr: { | ||
if (context instanceof type.Null) { | ||
invariant(allowNull(), new NullSelectionError('...')); | ||
return new type.Null(); | ||
invariant(allowNull(), new NullSelectionError(context.selector)); | ||
return context; | ||
} | ||
@@ -118,3 +154,3 @@ const isListContext = context instanceof type.List; | ||
return new type.CookieSet(cookies.parse(doc), context.base); | ||
case 'resolve': { | ||
case 'link': { | ||
const resolved = http.constructUrl(context.raw, context.base ?? undefined); | ||
@@ -124,4 +160,4 @@ if (resolved) { | ||
} | ||
invariant(allowNull(), new NullSelectionError('@resolve')); | ||
return new type.Null(); | ||
invariant(allowNull(), new NullSelectionError('@link')); | ||
return new type.Null('@link'); | ||
} | ||
@@ -183,23 +219,29 @@ default: | ||
exit(node) { | ||
optional.pop(); | ||
scope.vars[node.name.value] = node.value; | ||
}, | ||
}, | ||
RequestStmt: { | ||
enter() { | ||
optional.push(true); | ||
}, | ||
async exit(node) { | ||
optional.pop(); | ||
const method = node.method.value; | ||
const url = node.url.raw; | ||
const body = node.body?.raw || ''; | ||
invariant(typeof url === 'string', new TypeError('Request URL expected string')); | ||
invariant(typeof body === 'string', new TypeError('Request body expected string')); | ||
const obj = (entries) => { | ||
const filteredEntries = entries.flatMap(e => e.key.hasUndefined || e.value.hasUndefined | ||
? [] | ||
: [[e.key.raw, e.value.raw]]); | ||
return Object.fromEntries(filteredEntries); | ||
}; | ||
const headers = obj(node.headers); | ||
const blocks = Object.fromEntries(Object.entries(node.blocks).map(e => [e[0], obj(e[1])])); | ||
const res = await http.request(method, url, headers, blocks, body, hooks.request); | ||
scope.pushContext(new type.Value({ ...res, headers: new type.Headers(res.headers, url) }, url)); | ||
}, | ||
}, | ||
async RequestStmt(node) { | ||
const method = node.method.value; | ||
const url = node.url.raw; | ||
const body = node.body?.raw || ''; | ||
invariant(typeof url === 'string', new TypeError('Request URL expected string')); | ||
invariant(typeof body === 'string', new TypeError('Request body expected string')); | ||
const obj = (entries) => { | ||
const filteredEntries = entries.flatMap(e => e.key.hasUndefined || e.value.hasUndefined | ||
? [] | ||
: [[e.key.raw, e.value.raw]]); | ||
return Object.fromEntries(filteredEntries); | ||
}; | ||
const headers = obj(node.headers); | ||
const blocks = Object.fromEntries(Object.entries(node.blocks).map(e => [e[0], obj(e[1])])); | ||
const res = await http.request(method, url, headers, blocks, body, requestFn); | ||
scope.pushContext(new type.Value({ ...res, headers: new type.Headers(res.headers, url) }, url)); | ||
}, | ||
ExtractStmt(node) { | ||
@@ -206,0 +248,0 @@ scope.extracted = node.value; |
@@ -0,4 +1,5 @@ | ||
/// <reference types="node" /> | ||
import type { Element } from 'domhandler'; | ||
type StringMap = Record<string, string>; | ||
export type RequestFn = (url: string, opts: RequestOpts) => Promise<Response>; | ||
export type RequestHook = (url: string, opts: RequestOpts) => Promise<Response>; | ||
type RequestOpts = { | ||
@@ -21,3 +22,4 @@ method?: string; | ||
export declare const constructUrl: (elementOrString: string | Element, base: string | undefined) => string | null; | ||
export declare const request: (method: string, url: string, _headers: StringMap, blocks: Blocks, bodyRaw: string, fetch?: RequestFn) => Promise<Response>; | ||
export declare const requestHook: RequestHook; | ||
export declare const request: (method: string, url: string, _headers: StringMap, blocks: Blocks, bodyRaw: string, hook: RequestHook) => Promise<Response>; | ||
export {}; |
@@ -28,3 +28,3 @@ import { RequestError } from '../../errors'; | ||
}; | ||
const requestFn = async (url, opts) => { | ||
export const requestHook = async (url, opts) => { | ||
const res = await fetch(url, opts); | ||
@@ -37,3 +37,3 @@ return { | ||
}; | ||
export const request = async (method, url, _headers, blocks, bodyRaw, fetch = requestFn) => { | ||
export const request = async (method, url, _headers, blocks, bodyRaw, hook) => { | ||
// construct url | ||
@@ -64,3 +64,3 @@ const finalUrl = new URL(url); | ||
try { | ||
return await fetch(urlString, { method, headers, body }); | ||
return await hook(urlString, { method, headers, body }); | ||
} | ||
@@ -67,0 +67,0 @@ catch (e) { |
@@ -18,3 +18,3 @@ import * as html from './values/html'; | ||
} | ||
return new type.Null(); | ||
return new type.Null(args[0]); | ||
} | ||
@@ -21,0 +21,0 @@ export function select(context, ...args) { |
@@ -0,1 +1,2 @@ | ||
/// <reference types="node" /> | ||
import type { AnyHtmlNode } from 'domhandler'; | ||
@@ -32,4 +33,5 @@ import type { AnyNode as AnyJsNode } from 'acorn'; | ||
export declare class Null extends Value { | ||
selector: string; | ||
raw: null; | ||
constructor(); | ||
constructor(selector: string); | ||
} | ||
@@ -36,0 +38,0 @@ export declare class List<T extends Value> extends Value { |
@@ -50,5 +50,7 @@ export class Value { | ||
export class Null extends Value { | ||
selector; | ||
raw = null; | ||
constructor() { | ||
constructor(selector) { | ||
super(null, null); | ||
this.selector = selector; | ||
} | ||
@@ -55,0 +57,0 @@ } |
@@ -0,2 +1,3 @@ | ||
/// <reference types="node" /> | ||
import type { SelectFn } from './types'; | ||
export declare const select: SelectFn<Headers, string>; |
import { desugar } from './ast/simplified'; | ||
import { print } from './ast/print'; | ||
import type { Program } from './ast/ast'; | ||
import type { RequestFn } from './execute/net/http'; | ||
export type { RequestFn }; | ||
declare function parse(source: string): any; | ||
declare function execute(source: string, inputs?: Record<string, unknown>, requestFn?: RequestFn): Promise<unknown>; | ||
declare function executeAST(ast: Program, inputs?: Record<string, unknown>, requestFn?: RequestFn): Promise<unknown>; | ||
import type { InternalHooks } from './execute/execute'; | ||
export type { Program }; | ||
export { NodeKind } from './ast/ast'; | ||
export { RuntimeError } from './errors'; | ||
declare function parse(source: string): Program; | ||
export type RequestHook = InternalHooks['request']; | ||
export type ImportHook = (module: string) => string | Promise<string>; | ||
export type Hooks = Partial<{ | ||
request: RequestHook; | ||
import: ImportHook; | ||
}>; | ||
declare function execute(source: string, inputs?: Record<string, unknown>, hooks?: Hooks): Promise<unknown>; | ||
declare function executeAST(ast: Program, inputs?: Record<string, unknown>, hooks?: Hooks): Promise<unknown>; | ||
export { parse, desugar, execute, print, executeAST }; |
@@ -7,3 +7,7 @@ import nearley from 'nearley'; | ||
import { execute as exec } from './execute/execute'; | ||
import { SyntaxError, invariant } from './errors'; | ||
import * as http from './execute/net/http'; | ||
import { SyntaxError, ImportError, invariant } from './errors'; | ||
import { wait } from './utils'; | ||
export { NodeKind } from './ast/ast'; | ||
export { RuntimeError } from './errors'; | ||
function parse(source) { | ||
@@ -22,13 +26,24 @@ const parser = new nearley.Parser(nearley.Grammar.fromCompiled(grammar)); | ||
invariant(results.length !== 0, new SyntaxError('Unexpected end of input')); | ||
invariant(results.length === 1, new SyntaxError('Ambiguous source')); | ||
invariant(results.length === 1, new SyntaxError('Unexpected parsing error')); | ||
return results[0]; | ||
} | ||
function execute(source, inputs = {}, requestFn) { | ||
function buildHooks(hooks = {}) { | ||
return { | ||
request: hooks.request ?? http.requestHook, | ||
import: (module) => { | ||
if (!hooks.import) { | ||
throw new ImportError('Imports are not supported by the current runtime'); | ||
} | ||
return wait(hooks.import(module), src => desugar(parse(src))); | ||
}, | ||
}; | ||
} | ||
function execute(source, inputs = {}, hooks) { | ||
const ast = parse(source); | ||
const simplified = desugar(ast); | ||
return exec(simplified, inputs, requestFn); | ||
return exec(simplified, inputs, buildHooks(hooks)); | ||
} | ||
function executeAST(ast, inputs = {}, requestFn) { | ||
return exec(ast, inputs, requestFn); | ||
function executeAST(ast, inputs = {}, hooks) { | ||
return exec(ast, inputs, buildHooks(hooks)); | ||
} | ||
export { parse, desugar, execute, print, executeAST }; |
@@ -13,3 +13,3 @@ import moo from 'moo'; | ||
'cookies', | ||
'resolve', | ||
'link', | ||
'headers', | ||
@@ -16,0 +16,0 @@ 'cookies', |
{ | ||
"name": "@getlang/get", | ||
"version": "0.0.1", | ||
"version": "0.0.2", | ||
"license": "Apache-2.0", | ||
@@ -8,3 +8,4 @@ "type": "module", | ||
"files": [ | ||
"dist" | ||
"dist", | ||
"assets" | ||
], | ||
@@ -11,0 +12,0 @@ "dependencies": { |
@@ -1,3 +0,6 @@ | ||
<p align="center"><img src="assets/hero.png"></p> | ||
<picture> | ||
<source media="(prefers-color-scheme: dark)" srcset="./assets/hero.dark.png"> | ||
<img alt="GETlang hero banner" src="./assets/hero.png"> | ||
</picture> | ||
Visit [getlang.dev](https://getlang.dev) to view examples, documentation, and a space to try your first Get query! | ||
Visit [getlang.dev](https://getlang.dev) to view examples, take a guided tour, or try your first GET query! |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
396517
117
4712
7
4