bonsai-js 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"program.mjs","names":[],"sources":["../src/program.ts"],"sourcesContent":["import type { Analysis } from './check/checker.js'\nimport { compileProgram, type CompiledProgram } from './compile/compiler.js'\nimport type { EvaluateOptions, ResidualOf, Settings } from './environment.js'\nimport { BonsaiLimitError, BonsaiRuntimeError, type BonsaiError } from './errors.js'\nimport { zoneParameter } from './functions/builtins.js'\nimport {\n conforms,\n describeMismatch,\n isRecord,\n type Out,\n type ValidationBudget,\n} from './functions/define.js'\nimport { escaping, isOwn, raised } from './runtime/attribution.js'\nimport { isStackOverflow, tooDeep } from './runtime/overflow.js'\nimport { State } from './runtime/state.js'\nimport { warmZone } from './runtime/time.js'\nimport { errorText, isMap } from './runtime/values.js'\nimport { formatType, type Type } from './types.js'\n\n/** What running a program needs from its analysis. */\nexport type ProgramAnalysis = Pick<Analysis, 'root' | 'calls' | 'async'>\n\nconst EMPTY_CONTEXT: Record<string, unknown> = Object.freeze({})\n\n/** A number option: TypeError when it is not a number, RangeError when out of range. */\nexport function numberOption(\n name: string,\n value: unknown,\n fallback: number,\n minimum: number,\n): number {\n if (value === undefined) return fallback\n if (typeof value !== 'number') throw new TypeError(`${name} must be a number`)\n if (!Number.isInteger(value) || value < minimum) {\n throw new RangeError(\n minimum === 0\n ? `${name} must be a non-negative integer (0 disables it)`\n : `${name} must be a positive integer`,\n )\n }\n return value\n}\n\n/**\n * A timeout in milliseconds: 0 turns it off; any other non-negative number,\n * fractions included, is a limit, so a budget computed down to its last\n * fraction of a millisecond stays a limit rather than becoming 0.\n */\nexport function timeoutOption(name: string, value: unknown, fallback: number): number {\n if (value === undefined) return fallback\n if (typeof value !== 'number') throw new TypeError(`${name} must be a number`)\n if (!Number.isFinite(value) || value < 0) {\n throw new RangeError(`${name} must be a non-negative number of milliseconds (0 disables it)`)\n }\n return value\n}\n\nexport function assertKeys(value: unknown, allowed: ReadonlySet<string>, what: string): void {\n if (!isRecord(value)) throw new TypeError(`${what} must be an object`)\n for (const key of Object.keys(value)) {\n if (!allowed.has(key)) {\n throw new TypeError(\n `Unknown ${what.toLowerCase()} key \"${key}\" (expected one of: ${[...allowed].join(', ')})`,\n )\n }\n }\n}\n\nexport const EVALUATE_OPTION_KEYS = new Set([\n 'timeout',\n 'maxSteps',\n 'signal',\n 'now',\n 'validateContext',\n])\n\nexport interface EvaluationLimits {\n readonly maxSteps: number\n readonly timeout: number\n readonly signal: AbortSignal | undefined\n /** The time now() returns, when the caller fixed it. */\n readonly now: Date | undefined\n /** Whether to validate the context, when the caller overrides the environment. */\n readonly validateContext: boolean | undefined\n}\n\n/** Whether a value is a valid Date (an object that only inherits from Date.prototype is not). */\nexport function isValidDate(value: unknown): value is Date {\n try {\n return value instanceof Date && !Number.isNaN(Date.prototype.getTime.call(value))\n } catch {\n return false\n }\n}\n\n/** Whether a value has an AbortSignal's members; a signal whose getters throw does not. */\nfunction looksLikeSignal(signal: object): boolean {\n try {\n const s = signal as Record<string, unknown>\n return (\n typeof s.aborted === 'boolean' &&\n typeof s.addEventListener === 'function' &&\n typeof s.removeEventListener === 'function'\n )\n } catch {\n return false\n }\n}\n\n/** Validates per-evaluation options the way `limits` are validated; nothing fails open. */\nexport function evaluationLimits(options: unknown, settings: Settings): EvaluationLimits {\n if (options === undefined) {\n return {\n maxSteps: settings.runtimeLimits.maxSteps,\n timeout: settings.timeout,\n signal: undefined,\n now: undefined,\n validateContext: undefined,\n }\n }\n // Options are the caller's own object; one whose getters or Proxy traps throw\n // is invalid options (a TypeError), never an error from inside evaluation.\n let o: Record<string, unknown>\n try {\n assertKeys(options, EVALUATE_OPTION_KEYS, 'Evaluate option')\n const read = options as Record<string, unknown>\n o = {\n maxSteps: read.maxSteps,\n timeout: read.timeout,\n signal: read.signal,\n now: read.now,\n validateContext: read.validateContext,\n }\n } catch (error) {\n if (error instanceof TypeError || error instanceof RangeError) throw error\n throw new TypeError(`Evaluate options could not be read: ${errorText(error)}`, { cause: error })\n }\n const signal = o.signal\n if (\n signal !== undefined &&\n (typeof signal !== 'object' || signal === null || !looksLikeSignal(signal))\n ) {\n throw new TypeError('signal must be an AbortSignal')\n }\n if (o.now !== undefined && !isValidDate(o.now)) throw new TypeError('now must be a valid Date')\n if (o.validateContext !== undefined && typeof o.validateContext !== 'boolean')\n throw new TypeError('validateContext must be a boolean')\n return {\n maxSteps: numberOption('maxSteps', o.maxSteps, settings.runtimeLimits.maxSteps, 0),\n timeout: timeoutOption('timeout', o.timeout, settings.timeout),\n // A structurally checked AbortSignalLike; evaluation only reads the members checked above.\n signal: signal as AbortSignal | undefined,\n now: o.now,\n validateContext: o.validateContext,\n }\n}\n\ninterface ValidationLimits {\n readonly maxDepth: number\n readonly maxSteps: number\n readonly timeout: number\n}\n\nexport function validationBudget(source: string, limits: ValidationLimits): ValidationBudget {\n return {\n maxDepth: limits.maxDepth,\n remaining: limits.maxSteps > 0 ? limits.maxSteps : Number.POSITIVE_INFINITY,\n deadline: limits.timeout > 0 ? performance.now() + limits.timeout : 0,\n onExhausted: (reason) => {\n if (reason === 'depth') {\n throw raised(\n new BonsaiLimitError(\n 'VALUE_DEPTH_LIMIT',\n `Context nests deeper than ${limits.maxDepth}`,\n {\n source,\n },\n ),\n )\n }\n if (reason === 'time')\n throw raised(new BonsaiLimitError('TIMEOUT', 'Context validation timed out', { source }))\n throw raised(\n new BonsaiLimitError('STEP_LIMIT', 'Context validation exceeded the step limit', {\n source,\n }),\n )\n },\n }\n}\n\nexport function invalidContext(problem: string | undefined, source: string): void {\n if (problem !== undefined) {\n throw raised(\n new BonsaiRuntimeError('INVALID_CONTEXT', `Invalid context: ${problem}`, { source }),\n )\n }\n}\n\nfunction validateContext(\n ctx: Record<string, unknown>,\n variables: Readonly<Record<string, Type>>,\n source: string,\n limits: ValidationLimits,\n): void {\n const budget = validationBudget(source, limits)\n for (const [name, type] of Object.entries(variables)) {\n const value = Object.hasOwn(ctx, name) ? ctx[name] : undefined\n invalidContext(describeMismatch(value, type, name, budget), source)\n }\n}\n\n/**\n * Why the value read along `segments` (from index `at`) does not conform, or\n * undefined. Each step is judged by its own declared type: an absent or null\n * value is fine only where that type is nullable; a declared field is followed\n * (a missing required one fails); an undeclared key of an object (objects are\n * open) or an absent record entry ends the check; past a type that is not a\n * map, and at the end of the path, the value is checked whole, as a context is.\n * Fields the path does not name are not checked.\n */\nfunction describeAlong(\n value: unknown,\n type: Type,\n segments: readonly string[],\n at: number,\n path: string,\n budget: ValidationBudget,\n): string | undefined {\n if (at === segments.length) return describeMismatch(value, type, path, budget)\n if (--budget.remaining < 0) budget.onExhausted('steps')\n if (type.kind === 'union') {\n let first: string | undefined\n for (const member of type.types) {\n const problem = describeAlong(value, member, segments, at, path, budget)\n if (problem === undefined) return undefined\n first ??= problem\n }\n return first\n }\n const actual = value === undefined ? null : value\n if (type.kind !== 'map' || !isMap(actual)) return describeMismatch(value, type, path, budget)\n const key = segments[at]\n const field = Object.hasOwn(actual, key) ? actual[key] : undefined\n if (Object.hasOwn(type.fields, key))\n return describeAlong(field, type.fields[key], segments, at + 1, `${path}.${key}`, budget)\n if (type.rest === undefined || field === undefined) return undefined\n return describeAlong(field, type.rest, segments, at + 1, `${path}.${key}`, budget)\n}\n\n/**\n * Validates the values a residual reads: each path in `dependsOn`, step by\n * step against the types declared along it (see describeAlong), so a context\n * holding exactly those paths passes and invalid data on them fails as it\n * would for the program.\n */\nfunction validatePaths(\n ctx: Record<string, unknown>,\n variables: Readonly<Record<string, Type>>,\n paths: readonly string[],\n source: string,\n limits: ValidationLimits,\n): void {\n const budget = validationBudget(source, limits)\n for (const path of paths) {\n const segments = path.split('.')\n const name = segments[0]\n if (!Object.hasOwn(variables, name)) continue\n const value = Object.hasOwn(ctx, name) ? ctx[name] : undefined\n invalidContext(describeAlong(value, variables[name], segments, 1, name, budget), source)\n }\n}\n\n/**\n * Reading host data (a getter, a Proxy trap, a `then` hook) can throw. Such a\n * failure is a HOST_ERROR, never a raw JavaScript error. Only the errors this\n * evaluation raised pass through: any other Bonsai error (from another\n * evaluation, from parsing, checking, or translating, or built by the host)\n * that host data threw is the host data's failure too.\n */\nexport function hostDataFailure(error: unknown, source: string): BonsaiError {\n if (isOwn(error)) return error\n if (isStackOverflow(error)) return raised(tooDeep(source, error))\n return raised(\n new BonsaiRuntimeError('HOST_ERROR', `Reading host data failed: ${errorText(error)}`, {\n source,\n cause: error,\n }),\n )\n}\n\n/** The most zones one program warms when it is compiled. */\nconst WARMED_ZONES = 16\n\n/**\n * Loads the time zone data a program's calls need, so its first evaluation\n * does not pay for it inside its deadline. Only built-in functions that take a\n * zone count: a zone written as text is loaded itself, and any other zone\n * argument loads the data every zone shares. Each zone is warmed once, and at\n * most WARMED_ZONES of them, so a source full of zone names compiles quickly.\n */\nexport function warmZones(analysis: Pick<Analysis, 'calls'>): void {\n const seen = new Set<string | undefined>()\n for (const [call, plan] of analysis.calls) {\n const index = zoneParameter(plan.def)\n const arg = index === undefined ? undefined : call.args[index]\n if (arg === undefined) continue\n const zone = arg.type === 'Literal' ? arg.value : undefined\n if ((zone !== undefined && typeof zone !== 'string') || seen.has(zone)) continue\n seen.add(zone)\n warmZone(zone)\n if (seen.size >= WARMED_ZONES) return\n }\n}\n\nexport function contextOf(value: unknown): Record<string, unknown> {\n if (value === undefined || value === null) return EMPTY_CONTEXT\n let map: boolean\n try {\n // A Proxy (even a revoked one) answers these through its traps.\n map = typeof value === 'object' && isMap(value)\n } catch (error) {\n throw new BonsaiRuntimeError('HOST_ERROR', 'Reading the evaluation context failed', {\n cause: error,\n })\n }\n // A caller's mistake, like invalid options, not an error in the expression's data.\n if (!map) throw new TypeError('The evaluation context must be a plain object or class instance')\n return value as Record<string, unknown>\n}\n\nexport interface RunnerInput {\n readonly source: string\n readonly analysis: ProgramAnalysis\n /** The type the result is checked against at run time, when the checker could not prove it. */\n readonly guard: Type | undefined\n readonly settings: Settings\n readonly constants?: ReadonlyMap<string, unknown> | undefined\n readonly residualOf?: ResidualOf | undefined\n}\n\n/**\n * How a compiled program runs: per-evaluation limits, context validation, the\n * pooled synchronous State, the result guard, and settling async results.\n * Shared by environments and by anything else that builds programs, so every\n * program evaluates the same way.\n */\nexport function programRunner<R>({\n source,\n analysis,\n guard,\n settings,\n constants,\n residualOf,\n}: RunnerInput) {\n const checked = (result: unknown, state: State): void => {\n if (guard !== undefined && !conforms(result, guard, state)) {\n throw raised(\n new BonsaiRuntimeError(\n 'TYPE_ERROR',\n `The result does not match the expected type ${formatType(guard)}`,\n { source },\n ),\n )\n }\n }\n // The limits of an evaluation without options: built once, not per run.\n const defaultLimits = evaluationLimits(undefined, settings)\n const limitsOf = (options: EvaluateOptions | undefined): EvaluationLimits =>\n options === undefined ? defaultLimits : evaluationLimits(options, settings)\n let syncCode: CompiledProgram | undefined\n let asyncCode: CompiledProgram | undefined\n let pooled: State | undefined\n let pooledInUse = false\n\n const compileOptions = constants === undefined ? {} : { constants, shapes: residualOf?.shapes }\n const syncProgram = (): CompiledProgram =>\n (syncCode ??= compileProgram(analysis, 'sync', compileOptions))\n const asyncProgram = (): CompiledProgram =>\n (asyncCode ??= analysis.async\n ? compileProgram(analysis, 'async', compileOptions)\n : syncProgram())\n\n // Options and the context's shape are the caller's own mistakes (TypeError,\n // RangeError, INVALID_ARGUMENT), so they are checked before anything reads\n // host data and are never reported as host failures.\n function prepare(\n state: State,\n ctx: Record<string, unknown>,\n limits: EvaluationLimits,\n locals: number,\n ): void {\n state.reset(ctx, source, locals, limits.maxSteps, limits.timeout, limits.signal)\n if (limits.now !== undefined) state.nowValue = limits.now\n const residual = residualOf\n if (residual !== undefined && residual.readsContext) {\n // call: true functions read the whole context, which must then hold\n // the variables partial() knew, as the program's context would.\n const roots = residual.knownRoots\n state.charge(roots.length)\n // In a strict environment, a known key that is not a declared variable is\n // not part of any context the program accepts, so it is not required.\n const declared = settings.strict ? settings.variables : undefined\n // Decided from the property itself, never running a getter: an accessor is\n // present, and so is a data property unless it holds undefined.\n const missing = roots.find((name) => {\n if (declared !== undefined && !Object.hasOwn(declared, name)) return false\n const descriptor = Object.getOwnPropertyDescriptor(ctx, name)\n return descriptor === undefined || ('value' in descriptor && descriptor.value === undefined)\n })\n if (missing !== undefined) {\n throw raised(\n new BonsaiRuntimeError(\n 'INVALID_CONTEXT',\n `Invalid context: ${missing} is missing; this residual calls a call: true host function, so pass the full context, as for the program`,\n { source },\n ),\n )\n }\n }\n const validation =\n (limits.validateContext ?? settings.validateContext) ? settings.variables : undefined\n if (validation !== undefined) {\n const validationLimits = {\n maxDepth: settings.runtimeLimits.maxValueDepth,\n maxSteps: limits.maxSteps,\n timeout: limits.timeout,\n }\n if (residual === undefined || residual.readsContext)\n validateContext(ctx, validation, source, validationLimits)\n else validatePaths(ctx, validation, residual.dependsOn, source, validationLimits)\n }\n }\n\n function runSync(context: unknown, options: EvaluateOptions | undefined): R {\n if (analysis.async) {\n throw escaping(\n new BonsaiRuntimeError(\n 'ASYNC_IN_SYNC',\n 'This expression calls an async function; use evaluate() instead of evaluateSync()',\n { source },\n ),\n )\n }\n const code = syncProgram()\n const limits = limitsOf(options)\n const ctx = contextOf(context)\n let state: State\n const reuse = !pooledInUse\n if (reuse) {\n pooled ??= new State(settings.runtimeLimits, settings.clock)\n state = pooled\n pooledInUse = true\n } else {\n state = new State(settings.runtimeLimits, settings.clock)\n }\n try {\n prepare(state, ctx, limits, code.localCount)\n const result = code.run(state)\n checked(result, state)\n state.checkTime()\n return result as R\n } catch (error) {\n throw escaping(hostDataFailure(error, source))\n } finally {\n state.release()\n if (reuse) pooledInUse = false\n }\n }\n\n async function runAsync(context: unknown, options: EvaluateOptions | undefined): Promise<R> {\n if (!analysis.async) {\n const result = runSync(context, options)\n try {\n return settle(result)\n } catch (error) {\n throw escaping(hostDataFailure(error, source))\n }\n }\n const code = asyncProgram()\n const limits = limitsOf(options)\n const ctx = contextOf(context)\n const state = new State(settings.runtimeLimits, settings.clock)\n try {\n prepare(state, ctx, limits, code.localCount)\n const result = ((await code.run(state)) as Out).v\n checked(result, state)\n state.checkTime()\n return settle(result as R)\n } catch (error) {\n throw escaping(hostDataFailure(error, source))\n } finally {\n state.release()\n }\n }\n\n // Returning a thenable from an async function would call its `then`\n // (an ORM query object would execute), so it is an error instead.\n function settle(result: R): R {\n if (\n result !== null &&\n (typeof result === 'object' || typeof result === 'function') &&\n typeof (result as { then?: unknown }).then === 'function'\n ) {\n throw raised(\n new BonsaiRuntimeError(\n 'TYPE_ERROR',\n 'evaluate() cannot return a promise-like value; use evaluateSync()',\n { source },\n ),\n )\n }\n return result\n }\n return { prepare, checked, settle, runSync, runAsync, compileOptions }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"program.mjs","names":[],"sources":["../src/program.ts"],"sourcesContent":["import type { Analysis } from './check/checker.js'\nimport { compileProgram, type CompiledProgram } from './compile/compiler.js'\nimport type { EvaluateOptions, ResidualOf, Settings } from './environment.js'\nimport { BonsaiLimitError, BonsaiRuntimeError, type BonsaiError } from './errors.js'\nimport {\n conforms,\n describeMismatch,\n isRecord,\n type Out,\n type ValidationBudget,\n} from './functions/define.js'\nimport { escaping, isOwn, raised } from './runtime/attribution.js'\nimport { isStackOverflow, tooDeep } from './runtime/overflow.js'\nimport { State } from './runtime/state.js'\nimport { errorText, isMap } from './runtime/values.js'\nimport { formatType, type Type } from './types.js'\n\n/** What running a program needs from its analysis. */\nexport type ProgramAnalysis = Pick<Analysis, 'root' | 'calls' | 'async'>\n\nconst EMPTY_CONTEXT: Record<string, unknown> = Object.freeze({})\n\n/** A number option: TypeError when it is not a number, RangeError when out of range. */\nexport function numberOption(\n name: string,\n value: unknown,\n fallback: number,\n minimum: number,\n): number {\n if (value === undefined) return fallback\n if (typeof value !== 'number') throw new TypeError(`${name} must be a number`)\n if (!Number.isInteger(value) || value < minimum) {\n throw new RangeError(\n minimum === 0\n ? `${name} must be a non-negative integer (0 disables it)`\n : `${name} must be a positive integer`,\n )\n }\n return value\n}\n\n/**\n * A timeout in milliseconds: 0 turns it off; any other non-negative number,\n * fractions included, is a limit, so a budget computed down to its last\n * fraction of a millisecond stays a limit rather than becoming 0.\n */\nexport function timeoutOption(name: string, value: unknown, fallback: number): number {\n if (value === undefined) return fallback\n if (typeof value !== 'number') throw new TypeError(`${name} must be a number`)\n if (!Number.isFinite(value) || value < 0) {\n throw new RangeError(`${name} must be a non-negative number of milliseconds (0 disables it)`)\n }\n return value\n}\n\nexport function assertKeys(value: unknown, allowed: ReadonlySet<string>, what: string): void {\n if (!isRecord(value)) throw new TypeError(`${what} must be an object`)\n for (const key of Object.keys(value)) {\n if (!allowed.has(key)) {\n throw new TypeError(\n `Unknown ${what.toLowerCase()} key \"${key}\" (expected one of: ${[...allowed].join(', ')})`,\n )\n }\n }\n}\n\nexport const EVALUATE_OPTION_KEYS = new Set([\n 'timeout',\n 'maxSteps',\n 'signal',\n 'now',\n 'validateContext',\n])\n\nexport interface EvaluationLimits {\n readonly maxSteps: number\n readonly timeout: number\n readonly signal: AbortSignal | undefined\n /** The time now() returns, when the caller fixed it. */\n readonly now: Date | undefined\n /** Whether to validate the context, when the caller overrides the environment. */\n readonly validateContext: boolean | undefined\n}\n\n/** Whether a value is a valid Date (an object that only inherits from Date.prototype is not). */\nexport function isValidDate(value: unknown): value is Date {\n try {\n return value instanceof Date && !Number.isNaN(Date.prototype.getTime.call(value))\n } catch {\n return false\n }\n}\n\n/** Whether a value has an AbortSignal's members; a signal whose getters throw does not. */\nfunction looksLikeSignal(signal: object): boolean {\n try {\n const s = signal as Record<string, unknown>\n return (\n typeof s.aborted === 'boolean' &&\n typeof s.addEventListener === 'function' &&\n typeof s.removeEventListener === 'function'\n )\n } catch {\n return false\n }\n}\n\n/** Validates per-evaluation options the way `limits` are validated; nothing fails open. */\nexport function evaluationLimits(options: unknown, settings: Settings): EvaluationLimits {\n if (options === undefined) {\n return {\n maxSteps: settings.runtimeLimits.maxSteps,\n timeout: settings.timeout,\n signal: undefined,\n now: undefined,\n validateContext: undefined,\n }\n }\n // Options are the caller's own object; one whose getters or Proxy traps throw\n // is invalid options (a TypeError), never an error from inside evaluation.\n let o: Record<string, unknown>\n try {\n assertKeys(options, EVALUATE_OPTION_KEYS, 'Evaluate option')\n const read = options as Record<string, unknown>\n o = {\n maxSteps: read.maxSteps,\n timeout: read.timeout,\n signal: read.signal,\n now: read.now,\n validateContext: read.validateContext,\n }\n } catch (error) {\n if (error instanceof TypeError || error instanceof RangeError) throw error\n throw new TypeError(`Evaluate options could not be read: ${errorText(error)}`, { cause: error })\n }\n const signal = o.signal\n if (\n signal !== undefined &&\n (typeof signal !== 'object' || signal === null || !looksLikeSignal(signal))\n ) {\n throw new TypeError('signal must be an AbortSignal')\n }\n if (o.now !== undefined && !isValidDate(o.now)) throw new TypeError('now must be a valid Date')\n if (o.validateContext !== undefined && typeof o.validateContext !== 'boolean')\n throw new TypeError('validateContext must be a boolean')\n return {\n maxSteps: numberOption('maxSteps', o.maxSteps, settings.runtimeLimits.maxSteps, 0),\n timeout: timeoutOption('timeout', o.timeout, settings.timeout),\n // A structurally checked AbortSignalLike; evaluation only reads the members checked above.\n signal: signal as AbortSignal | undefined,\n now: o.now,\n validateContext: o.validateContext,\n }\n}\n\ninterface ValidationLimits {\n readonly maxDepth: number\n readonly maxSteps: number\n readonly timeout: number\n}\n\nexport function validationBudget(source: string, limits: ValidationLimits): ValidationBudget {\n return {\n maxDepth: limits.maxDepth,\n remaining: limits.maxSteps > 0 ? limits.maxSteps : Number.POSITIVE_INFINITY,\n deadline: limits.timeout > 0 ? performance.now() + limits.timeout : 0,\n onExhausted: (reason) => {\n if (reason === 'depth') {\n throw raised(\n new BonsaiLimitError(\n 'VALUE_DEPTH_LIMIT',\n `Context nests deeper than ${limits.maxDepth}`,\n {\n source,\n },\n ),\n )\n }\n if (reason === 'time')\n throw raised(new BonsaiLimitError('TIMEOUT', 'Context validation timed out', { source }))\n throw raised(\n new BonsaiLimitError('STEP_LIMIT', 'Context validation exceeded the step limit', {\n source,\n }),\n )\n },\n }\n}\n\nexport function invalidContext(problem: string | undefined, source: string): void {\n if (problem !== undefined) {\n throw raised(\n new BonsaiRuntimeError('INVALID_CONTEXT', `Invalid context: ${problem}`, { source }),\n )\n }\n}\n\nfunction validateContext(\n ctx: Record<string, unknown>,\n variables: Readonly<Record<string, Type>>,\n source: string,\n limits: ValidationLimits,\n): void {\n const budget = validationBudget(source, limits)\n for (const [name, type] of Object.entries(variables)) {\n const value = Object.hasOwn(ctx, name) ? ctx[name] : undefined\n invalidContext(describeMismatch(value, type, name, budget), source)\n }\n}\n\n/**\n * Why the value read along `segments` (from index `at`) does not conform, or\n * undefined. Each step is judged by its own declared type: an absent or null\n * value is fine only where that type is nullable; a declared field is followed\n * (a missing required one fails); an undeclared key of an object (objects are\n * open) or an absent record entry ends the check; past a type that is not a\n * map, and at the end of the path, the value is checked whole, as a context is.\n * Fields the path does not name are not checked.\n */\nfunction describeAlong(\n value: unknown,\n type: Type,\n segments: readonly string[],\n at: number,\n path: string,\n budget: ValidationBudget,\n): string | undefined {\n if (at === segments.length) return describeMismatch(value, type, path, budget)\n if (--budget.remaining < 0) budget.onExhausted('steps')\n if (type.kind === 'union') {\n let first: string | undefined\n for (const member of type.types) {\n const problem = describeAlong(value, member, segments, at, path, budget)\n if (problem === undefined) return undefined\n first ??= problem\n }\n return first\n }\n const actual = value === undefined ? null : value\n if (type.kind !== 'map' || !isMap(actual)) return describeMismatch(value, type, path, budget)\n const key = segments[at]\n const field = Object.hasOwn(actual, key) ? actual[key] : undefined\n if (Object.hasOwn(type.fields, key))\n return describeAlong(field, type.fields[key], segments, at + 1, `${path}.${key}`, budget)\n if (type.rest === undefined || field === undefined) return undefined\n return describeAlong(field, type.rest, segments, at + 1, `${path}.${key}`, budget)\n}\n\n/**\n * Validates the values a residual reads: each path in `dependsOn`, step by\n * step against the types declared along it (see describeAlong), so a context\n * holding exactly those paths passes and invalid data on them fails as it\n * would for the program.\n */\nfunction validatePaths(\n ctx: Record<string, unknown>,\n variables: Readonly<Record<string, Type>>,\n paths: readonly string[],\n source: string,\n limits: ValidationLimits,\n): void {\n const budget = validationBudget(source, limits)\n for (const path of paths) {\n const segments = path.split('.')\n const name = segments[0]\n if (!Object.hasOwn(variables, name)) continue\n const value = Object.hasOwn(ctx, name) ? ctx[name] : undefined\n invalidContext(describeAlong(value, variables[name], segments, 1, name, budget), source)\n }\n}\n\n/**\n * Reading host data (a getter, a Proxy trap, a `then` hook) can throw. Such a\n * failure is a HOST_ERROR, never a raw JavaScript error. Only the errors this\n * evaluation raised pass through: any other Bonsai error (from another\n * evaluation, from parsing, checking, or translating, or built by the host)\n * that host data threw is the host data's failure too.\n */\nexport function hostDataFailure(error: unknown, source: string): BonsaiError {\n if (isOwn(error)) return error\n if (isStackOverflow(error)) return raised(tooDeep(source, error))\n return raised(\n new BonsaiRuntimeError('HOST_ERROR', `Reading host data failed: ${errorText(error)}`, {\n source,\n cause: error,\n }),\n )\n}\n\n/** The most zones one program warms when it is compiled. */\nconst WARMED_ZONES = 16\n\n/**\n * Loads the time zone data a program's calls need, so its first evaluation\n * does not pay for it inside its deadline. Only built-in functions that take a\n * zone count: a zone written as text is loaded itself, and any other zone\n * argument loads the data every zone shares. Each zone is warmed once, and at\n * most WARMED_ZONES of them, so a source full of zone names compiles quickly.\n */\nexport function warmZones(analysis: Pick<Analysis, 'calls'>): void {\n const seen = new Set<string | undefined>()\n for (const [call, plan] of analysis.calls) {\n const zone = plan.def.zone\n const arg = zone === undefined ? undefined : call.args[zone.at]\n if (arg === undefined) continue\n const name = arg.type === 'Literal' ? arg.value : undefined\n if ((name !== undefined && typeof name !== 'string') || seen.has(name)) continue\n seen.add(name)\n zone?.warm(name)\n if (seen.size >= WARMED_ZONES) return\n }\n}\n\nexport function contextOf(value: unknown): Record<string, unknown> {\n if (value === undefined || value === null) return EMPTY_CONTEXT\n let map: boolean\n try {\n // A Proxy (even a revoked one) answers these through its traps.\n map = typeof value === 'object' && isMap(value)\n } catch (error) {\n throw new BonsaiRuntimeError('HOST_ERROR', 'Reading the evaluation context failed', {\n cause: error,\n })\n }\n // A caller's mistake, like invalid options, not an error in the expression's data.\n if (!map) throw new TypeError('The evaluation context must be a plain object or class instance')\n return value as Record<string, unknown>\n}\n\nexport interface RunnerInput {\n readonly source: string\n readonly analysis: ProgramAnalysis\n /** The type the result is checked against at run time, when the checker could not prove it. */\n readonly guard: Type | undefined\n readonly settings: Settings\n readonly constants?: ReadonlyMap<string, unknown> | undefined\n readonly residualOf?: ResidualOf | undefined\n}\n\n/**\n * How a compiled program runs: per-evaluation limits, context validation, the\n * pooled synchronous State, the result guard, and settling async results.\n * Shared by environments and by anything else that builds programs, so every\n * program evaluates the same way.\n */\nexport function programRunner<R>({\n source,\n analysis,\n guard,\n settings,\n constants,\n residualOf,\n}: RunnerInput) {\n const checked = (result: unknown, state: State): void => {\n if (guard !== undefined && !conforms(result, guard, state)) {\n throw raised(\n new BonsaiRuntimeError(\n 'TYPE_ERROR',\n `The result does not match the expected type ${formatType(guard)}`,\n { source },\n ),\n )\n }\n }\n // The limits of an evaluation without options: built once, not per run.\n const defaultLimits = evaluationLimits(undefined, settings)\n const limitsOf = (options: EvaluateOptions | undefined): EvaluationLimits =>\n options === undefined ? defaultLimits : evaluationLimits(options, settings)\n let syncCode: CompiledProgram | undefined\n let asyncCode: CompiledProgram | undefined\n let pooled: State | undefined\n let pooledInUse = false\n\n const compileOptions = constants === undefined ? {} : { constants, shapes: residualOf?.shapes }\n const syncProgram = (): CompiledProgram =>\n (syncCode ??= compileProgram(analysis, 'sync', compileOptions))\n const asyncProgram = (): CompiledProgram =>\n (asyncCode ??= analysis.async\n ? compileProgram(analysis, 'async', compileOptions)\n : syncProgram())\n\n // Options and the context's shape are the caller's own mistakes (TypeError,\n // RangeError, INVALID_ARGUMENT), so they are checked before anything reads\n // host data and are never reported as host failures.\n function prepare(\n state: State,\n ctx: Record<string, unknown>,\n limits: EvaluationLimits,\n locals: number,\n ): void {\n state.reset(ctx, source, locals, limits.maxSteps, limits.timeout, limits.signal)\n if (limits.now !== undefined) state.nowValue = limits.now\n const residual = residualOf\n if (residual !== undefined && residual.readsContext) {\n // call: true functions read the whole context, which must then hold\n // the variables partial() knew, as the program's context would.\n const roots = residual.knownRoots\n state.charge(roots.length)\n // In a strict environment, a known key that is not a declared variable is\n // not part of any context the program accepts, so it is not required.\n const declared = settings.strict ? settings.variables : undefined\n // Decided from the property itself, never running a getter: an accessor is\n // present, and so is a data property unless it holds undefined.\n const missing = roots.find((name) => {\n if (declared !== undefined && !Object.hasOwn(declared, name)) return false\n const descriptor = Object.getOwnPropertyDescriptor(ctx, name)\n return descriptor === undefined || ('value' in descriptor && descriptor.value === undefined)\n })\n if (missing !== undefined) {\n throw raised(\n new BonsaiRuntimeError(\n 'INVALID_CONTEXT',\n `Invalid context: ${missing} is missing; this residual calls a call: true host function, so pass the full context, as for the program`,\n { source },\n ),\n )\n }\n }\n const validation =\n (limits.validateContext ?? settings.validateContext) ? settings.variables : undefined\n if (validation !== undefined) {\n const validationLimits = {\n maxDepth: settings.runtimeLimits.maxValueDepth,\n maxSteps: limits.maxSteps,\n timeout: limits.timeout,\n }\n if (residual === undefined || residual.readsContext)\n validateContext(ctx, validation, source, validationLimits)\n else validatePaths(ctx, validation, residual.dependsOn, source, validationLimits)\n }\n }\n\n function runSync(context: unknown, options: EvaluateOptions | undefined): R {\n if (analysis.async) {\n throw escaping(\n new BonsaiRuntimeError(\n 'ASYNC_IN_SYNC',\n 'This expression calls an async function; use evaluate() instead of evaluateSync()',\n { source },\n ),\n )\n }\n const code = syncProgram()\n const limits = limitsOf(options)\n const ctx = contextOf(context)\n let state: State\n const reuse = !pooledInUse\n if (reuse) {\n pooled ??= new State(settings.runtimeLimits, settings.clock)\n state = pooled\n pooledInUse = true\n } else {\n state = new State(settings.runtimeLimits, settings.clock)\n }\n try {\n prepare(state, ctx, limits, code.localCount)\n const result = code.run(state)\n checked(result, state)\n state.checkTime()\n return result as R\n } catch (error) {\n throw escaping(hostDataFailure(error, source))\n } finally {\n state.release()\n if (reuse) pooledInUse = false\n }\n }\n\n async function runAsync(context: unknown, options: EvaluateOptions | undefined): Promise<R> {\n if (!analysis.async) {\n const result = runSync(context, options)\n try {\n return settle(result)\n } catch (error) {\n throw escaping(hostDataFailure(error, source))\n }\n }\n const code = asyncProgram()\n const limits = limitsOf(options)\n const ctx = contextOf(context)\n const state = new State(settings.runtimeLimits, settings.clock)\n try {\n prepare(state, ctx, limits, code.localCount)\n const result = ((await code.run(state)) as Out).v\n checked(result, state)\n state.checkTime()\n return settle(result as R)\n } catch (error) {\n throw escaping(hostDataFailure(error, source))\n } finally {\n state.release()\n }\n }\n\n // Returning a thenable from an async function would call its `then`\n // (an ORM query object would execute), so it is an error instead.\n function settle(result: R): R {\n if (\n result !== null &&\n (typeof result === 'object' || typeof result === 'function') &&\n typeof (result as { then?: unknown }).then === 'function'\n ) {\n throw raised(\n new BonsaiRuntimeError(\n 'TYPE_ERROR',\n 'evaluate() cannot return a promise-like value; use evaluateSync()',\n { source },\n ),\n )\n }\n return result\n }\n return { prepare, checked, settle, runSync, runAsync, compileOptions }\n}\n"],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { ArtifactProblem, BonsaiArtifactError, BonsaiError, BonsaiErrorJSON, BonsaiErrorName, BonsaiLimitError, BonsaiRuntimeError, ErrorCode, Span, isBonsaiError } from "../errors.mjs";
2
+ import { Duration } from "./values.mjs";
3
+ import { Infer, InferVariables, Type, formatType, t } from "../types.mjs";
4
+ import { AbortSignalLike, Builtin, ContextFor, EvaluateOptions, FnSpec, HostCall, HostFunction, Library, Limits, NoVariables, fn, withContext } from "../environment.mjs";
5
+ import { Artifact, Plan } from "../artifact.mjs";
6
+ import { CoreRuntime, CoreRuntimeOptions, LoadOptions, RuntimeProgram } from "./create.mjs";
7
+ //#region src/runtime/core.d.ts
8
+ /**
9
+ * Creates a runtime with the built-ins in `builtins` (default none). An
10
+ * artifact that calls a function the runtime does not define is refused with
11
+ * reason `function`, so pass the same list to the bonsai() that checks the
12
+ * rules, which then reports such a call when the rule is saved.
13
+ */
14
+ export declare function runtime<const V extends Readonly<Record<string, Type>> = NoVariables, const L extends readonly Library[] = [], const S extends boolean | undefined = undefined>(options?: CoreRuntimeOptions<V, L> & {
15
+ readonly strict?: S;
16
+ }): CoreRuntime<ContextFor<V, L, S>>;
17
+ //#endregion
18
+ export { type AbortSignalLike, type Artifact, type ArtifactProblem, BonsaiArtifactError, BonsaiError, type BonsaiErrorJSON, type BonsaiErrorName, BonsaiLimitError, BonsaiRuntimeError, type Builtin, type CoreRuntime, type CoreRuntimeOptions, Duration, type ErrorCode, type EvaluateOptions, type FnSpec, type HostCall, type HostFunction, type Infer, type InferVariables, type Library, type Limits, type LoadOptions, type Plan, type RuntimeProgram, type Span, type Type, fn, formatType, isBonsaiError, t, withContext };
@@ -0,0 +1,31 @@
1
+ import { BonsaiArtifactError, BonsaiError, BonsaiLimitError, BonsaiRuntimeError, isBonsaiError } from "../errors.mjs";
2
+ import { Duration } from "./values.mjs";
3
+ import { formatType, t } from "../types.mjs";
4
+ import { fn, settingsFrom, withContext } from "../environment.mjs";
5
+ import { assertRuntimeOptions, createRuntime } from "./create.mjs";
6
+ //#region src/runtime/core.ts
7
+ /**
8
+ * `bonsai-js/runtime/core`: bonsai-js/runtime with only the built-ins passed
9
+ * in `builtins`, so a bundle carries just the functions its rules call.
10
+ *
11
+ * ```ts
12
+ * import { runtime } from 'bonsai-js/runtime/core'
13
+ * import * as b from 'bonsai-js/builtins'
14
+ *
15
+ * const rt = runtime({ variables, builtins: [b.includes, b.some] })
16
+ * ```
17
+ */
18
+ /**
19
+ * Creates a runtime with the built-ins in `builtins` (default none). An
20
+ * artifact that calls a function the runtime does not define is refused with
21
+ * reason `function`, so pass the same list to the bonsai() that checks the
22
+ * rules, which then reports such a call when the rule is saved.
23
+ */
24
+ function runtime(options = {}) {
25
+ assertRuntimeOptions(options, true);
26
+ return createRuntime(settingsFrom(void 0, options), true);
27
+ }
28
+ //#endregion
29
+ export { BonsaiArtifactError, BonsaiError, BonsaiLimitError, BonsaiRuntimeError, Duration, fn, formatType, isBonsaiError, runtime, t, withContext };
30
+
31
+ //# sourceMappingURL=core.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.mjs","names":[],"sources":["../../src/runtime/core.ts"],"sourcesContent":["/**\n * `bonsai-js/runtime/core`: bonsai-js/runtime with only the built-ins passed\n * in `builtins`, so a bundle carries just the functions its rules call.\n *\n * ```ts\n * import { runtime } from 'bonsai-js/runtime/core'\n * import * as b from 'bonsai-js/builtins'\n *\n * const rt = runtime({ variables, builtins: [b.includes, b.some] })\n * ```\n */\nimport { settingsFrom, type ContextFor, type Library, type NoVariables } from '../environment.js'\nimport type { Type } from '../types.js'\nimport {\n assertRuntimeOptions,\n createRuntime,\n type CoreRuntime,\n type CoreRuntimeOptions,\n} from './create.js'\n\n/**\n * Creates a runtime with the built-ins in `builtins` (default none). An\n * artifact that calls a function the runtime does not define is refused with\n * reason `function`, so pass the same list to the bonsai() that checks the\n * rules, which then reports such a call when the rule is saved.\n */\nexport function runtime<\n const V extends Readonly<Record<string, Type>> = NoVariables,\n const L extends readonly Library[] = [],\n const S extends boolean | undefined = undefined,\n>(\n options: CoreRuntimeOptions<V, L> & { readonly strict?: S } = {},\n): CoreRuntime<ContextFor<V, L, S>> {\n assertRuntimeOptions(options, true)\n // createRuntime builds both kinds; with `core`, its extend takes `builtins`.\n return createRuntime<ContextFor<V, L, S>>(\n settingsFrom(undefined, options),\n true,\n ) as unknown as CoreRuntime<ContextFor<V, L, S>>\n}\n\nexport type { CoreRuntime, CoreRuntimeOptions } from './create.js'\nexport type { Builtin } from '../environment.js'\nexport * from './exports.js'\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA0BA,SAAgB,QAKd,UAA8D,CAAC,GAC7B;CAClC,qBAAqB,SAAS,IAAI;CAElC,OAAO,cACL,aAAa,KAAA,GAAW,OAAO,GAC/B,IACF;AACF"}
@@ -0,0 +1,61 @@
1
+ import { AnyType, Infer, Type } from "../types.mjs";
2
+ import { Args, EnvironmentOptions, Extended, Library, NoVariables } from "../environment.mjs";
3
+ import { Artifact } from "../artifact.mjs";
4
+ //#region src/runtime/create.d.ts
5
+ /**
6
+ * The options of `runtime()`: those of `bonsai()` that affect evaluation.
7
+ * `strict` has bonsai()'s default and must match the producer's: a strict
8
+ * runtime refuses artifacts that read undeclared variables. Every built-in is
9
+ * available; bonsai-js/runtime/core takes `builtins` instead.
10
+ */
11
+ export type RuntimeOptions<V extends Readonly<Record<string, Type>> = Readonly<Record<string, Type>>, L extends readonly Library[] = readonly Library[]> = Omit<EnvironmentOptions<V, L>, 'builtins'> & {
12
+ /** Not an option here: bonsai-js/runtime has every built-in (see bonsai-js/runtime/core). */
13
+ readonly builtins?: never;
14
+ };
15
+ /**
16
+ * The options of bonsai-js/runtime/core's `runtime()`: those of
17
+ * bonsai-js/runtime, and `builtins`, the built-ins available (default none).
18
+ */
19
+ export type CoreRuntimeOptions<V extends Readonly<Record<string, Type>> = Readonly<Record<string, Type>>, L extends readonly Library[] = readonly Library[]> = EnvironmentOptions<V, L>;
20
+ /** A loaded artifact: evaluates as the program it was made from does. Immutable. */
21
+ export interface RuntimeProgram<Ctx = any, R = unknown> {
22
+ readonly source: string;
23
+ /** Whether the expression calls an async host function (evaluateSync rejects it). */
24
+ readonly async: boolean;
25
+ evaluate: (...args: Args<Ctx>) => Promise<R>;
26
+ evaluateSync: (...args: Args<Ctx>) => R;
27
+ }
28
+ export interface LoadOptions<E extends Type = Type> {
29
+ /**
30
+ * The type the host relies on the result having. The artifact must have been
31
+ * compiled for exactly this type, and every result is checked against it.
32
+ */
33
+ readonly expect?: E | undefined;
34
+ }
35
+ /** Loads and evaluates artifacts against one set of definitions. Immutable. */
36
+ export interface Runtime<Ctx = any> extends Loader<Ctx> {
37
+ /** A runtime with more or replaced definitions; see `bonsai().extend`. */
38
+ extend: <const V2 extends Readonly<Record<string, Type>> = NoVariables, const L2 extends readonly Library[] = [], const S2 extends boolean | undefined = undefined>(options: RuntimeOptions<V2, L2> & {
39
+ readonly strict?: S2;
40
+ }) => Runtime<Extended<Ctx, V2, L2, S2>>;
41
+ }
42
+ /** A runtime from bonsai-js/runtime/core. Immutable. */
43
+ export interface CoreRuntime<Ctx = any> extends Loader<Ctx> {
44
+ /** A runtime with more or replaced definitions; `builtins` are added to this one's. */
45
+ extend: <const V2 extends Readonly<Record<string, Type>> = NoVariables, const L2 extends readonly Library[] = [], const S2 extends boolean | undefined = undefined>(options: CoreRuntimeOptions<V2, L2> & {
46
+ readonly strict?: S2;
47
+ }) => CoreRuntime<Extended<Ctx, V2, L2, S2>>;
48
+ }
49
+ interface Loader<Ctx = any> {
50
+ /**
51
+ * Loads an artifact. Throws BonsaiArtifactError (code ARTIFACT, with a
52
+ * `reason`) when the artifact does not reproduce the checked program with
53
+ * this runtime's definitions, and TypeError when the value is not an artifact.
54
+ */
55
+ load: <E extends Type = AnyType>(artifact: Artifact, options?: LoadOptions<E>) => RuntimeProgram<Ctx, Infer<E>>;
56
+ /** Loads (cached) and evaluates. `R` is an unchecked type, as for `env.evaluate`. */
57
+ evaluate: <R = unknown>(artifact: Artifact, ...args: Args<Ctx>) => Promise<R>;
58
+ /** Loads (cached) and evaluates synchronously. */
59
+ evaluateSync: <R = unknown>(artifact: Artifact, ...args: Args<Ctx>) => R;
60
+ }
61
+ //#endregion
@@ -0,0 +1,76 @@
1
+ import { ProgramCache, expectOption, settingsFrom } from "../environment.mjs";
2
+ import { assertExpectSize } from "../artifact.mjs";
3
+ import { artifactKey, loadArtifact, loadedRunner, readArtifact } from "../load.mjs";
4
+ //#region src/runtime/create.ts
5
+ /**
6
+ * What bonsai-js/runtime and bonsai-js/runtime/core share: loading and
7
+ * evaluating artifacts made by `toArtifact()`. They differ only in their
8
+ * built-ins: every one, or those passed in `builtins`.
9
+ */
10
+ const RUNTIME_KEYS = [
11
+ "variables",
12
+ "functions",
13
+ "libraries",
14
+ "limits",
15
+ "cacheSize",
16
+ "clock",
17
+ "validateContext",
18
+ "strict"
19
+ ];
20
+ /** The runtime takes bonsai()'s options (`builtins` only in bonsai-js/runtime/core). */
21
+ function assertRuntimeOptions(options, core) {
22
+ if (typeof options !== "object" || options === null) return;
23
+ let keys;
24
+ try {
25
+ keys = Object.keys(options);
26
+ } catch (error) {
27
+ throw new TypeError("Runtime options could not be read", { cause: error });
28
+ }
29
+ const allowed = core ? [...RUNTIME_KEYS, "builtins"] : RUNTIME_KEYS;
30
+ for (const key of keys) if (!allowed.includes(key)) throw new TypeError(key === "builtins" ? "Unknown runtime option key \"builtins\" (bonsai-js/runtime has every built-in; import runtime from bonsai-js/runtime/core to choose them)" : `Unknown runtime option key "${key}" (expected one of: ${allowed.join(", ")})`);
31
+ }
32
+ function createRuntime(settings, core) {
33
+ const cache = new ProgramCache(settings.cacheSize);
34
+ function load(artifact, expect) {
35
+ const loaded = loadArtifact(artifact, settings, expect);
36
+ const { runSync, runAsync } = loadedRunner(loaded, settings);
37
+ return Object.freeze({
38
+ source: loaded.source,
39
+ async: loaded.analysis.async,
40
+ evaluate: (...args) => runAsync(args[0], args[1]),
41
+ evaluateSync: (...args) => runSync(args[0], args[1])
42
+ });
43
+ }
44
+ function cached(artifact) {
45
+ const read = readArtifact(artifact, settings);
46
+ const key = artifactKey(read);
47
+ const known = cache.get(key);
48
+ if (known !== void 0) return known;
49
+ const program = load(read, void 0);
50
+ cache.set(key, program);
51
+ return program;
52
+ }
53
+ return Object.freeze({
54
+ load: (artifact, options) => {
55
+ const expect = expectOption(options, "Load");
56
+ if (expect !== void 0) assertExpectSize(expect, "Load option \"expect\"");
57
+ return load(readArtifact(artifact, settings), expect);
58
+ },
59
+ evaluate(artifact, ...args) {
60
+ try {
61
+ return cached(artifact).evaluate(...args);
62
+ } catch (error) {
63
+ return Promise.reject(error instanceof Error ? error : new Error(String(error)));
64
+ }
65
+ },
66
+ evaluateSync: (artifact, ...args) => cached(artifact).evaluateSync(...args),
67
+ extend: (options) => {
68
+ assertRuntimeOptions(options, core);
69
+ return createRuntime(settingsFrom(settings, options), core);
70
+ }
71
+ });
72
+ }
73
+ //#endregion
74
+ export { assertRuntimeOptions, createRuntime };
75
+
76
+ //# sourceMappingURL=create.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create.mjs","names":[],"sources":["../../src/runtime/create.ts"],"sourcesContent":["/**\n * What bonsai-js/runtime and bonsai-js/runtime/core share: loading and\n * evaluating artifacts made by `toArtifact()`. They differ only in their\n * built-ins: every one, or those passed in `builtins`.\n */\nimport { assertExpectSize, type Artifact } from '../artifact.js'\nimport {\n ProgramCache,\n expectOption,\n settingsFrom,\n type Args,\n type EnvironmentOptions,\n type Extended,\n type Library,\n type NoVariables,\n type Settings,\n} from '../environment.js'\nimport { artifactKey, loadArtifact, loadedRunner, readArtifact } from '../load.js'\nimport type { AnyType, Infer, Type } from '../types.js'\n\n/**\n * The options of `runtime()`: those of `bonsai()` that affect evaluation.\n * `strict` has bonsai()'s default and must match the producer's: a strict\n * runtime refuses artifacts that read undeclared variables. Every built-in is\n * available; bonsai-js/runtime/core takes `builtins` instead.\n */\nexport type RuntimeOptions<\n V extends Readonly<Record<string, Type>> = Readonly<Record<string, Type>>,\n L extends readonly Library[] = readonly Library[],\n> = Omit<EnvironmentOptions<V, L>, 'builtins'> & {\n /** Not an option here: bonsai-js/runtime has every built-in (see bonsai-js/runtime/core). */\n readonly builtins?: never\n}\n\n/**\n * The options of bonsai-js/runtime/core's `runtime()`: those of\n * bonsai-js/runtime, and `builtins`, the built-ins available (default none).\n */\nexport type CoreRuntimeOptions<\n V extends Readonly<Record<string, Type>> = Readonly<Record<string, Type>>,\n L extends readonly Library[] = readonly Library[],\n> = EnvironmentOptions<V, L>\n\n/** A loaded artifact: evaluates as the program it was made from does. Immutable. */\n// oxlint-disable-next-line typescript/no-explicit-any -- the erased context, as for Program\nexport interface RuntimeProgram<Ctx = any, R = unknown> {\n readonly source: string\n /** Whether the expression calls an async host function (evaluateSync rejects it). */\n readonly async: boolean\n evaluate: (...args: Args<Ctx>) => Promise<R>\n evaluateSync: (...args: Args<Ctx>) => R\n}\n\nexport interface LoadOptions<E extends Type = Type> {\n /**\n * The type the host relies on the result having. The artifact must have been\n * compiled for exactly this type, and every result is checked against it.\n */\n readonly expect?: E | undefined\n}\n\n/** Loads and evaluates artifacts against one set of definitions. Immutable. */\n// oxlint-disable-next-line typescript/no-explicit-any -- the erased context, as for Environment\nexport interface Runtime<Ctx = any> extends Loader<Ctx> {\n /** A runtime with more or replaced definitions; see `bonsai().extend`. */\n extend: <\n const V2 extends Readonly<Record<string, Type>> = NoVariables,\n const L2 extends readonly Library[] = [],\n const S2 extends boolean | undefined = undefined,\n >(\n options: RuntimeOptions<V2, L2> & { readonly strict?: S2 },\n ) => Runtime<Extended<Ctx, V2, L2, S2>>\n}\n\n/** A runtime from bonsai-js/runtime/core. Immutable. */\n// oxlint-disable-next-line typescript/no-explicit-any -- the erased context, as for Environment\nexport interface CoreRuntime<Ctx = any> extends Loader<Ctx> {\n /** A runtime with more or replaced definitions; `builtins` are added to this one's. */\n extend: <\n const V2 extends Readonly<Record<string, Type>> = NoVariables,\n const L2 extends readonly Library[] = [],\n const S2 extends boolean | undefined = undefined,\n >(\n options: CoreRuntimeOptions<V2, L2> & { readonly strict?: S2 },\n ) => CoreRuntime<Extended<Ctx, V2, L2, S2>>\n}\n\n// oxlint-disable-next-line typescript/no-explicit-any -- the erased context, as for Environment\ninterface Loader<Ctx = any> {\n /**\n * Loads an artifact. Throws BonsaiArtifactError (code ARTIFACT, with a\n * `reason`) when the artifact does not reproduce the checked program with\n * this runtime's definitions, and TypeError when the value is not an artifact.\n */\n load: <E extends Type = AnyType>(\n artifact: Artifact,\n options?: LoadOptions<E>,\n ) => RuntimeProgram<Ctx, Infer<E>>\n /** Loads (cached) and evaluates. `R` is an unchecked type, as for `env.evaluate`. */\n evaluate: <R = unknown>(artifact: Artifact, ...args: Args<Ctx>) => Promise<R>\n /** Loads (cached) and evaluates synchronously. */\n evaluateSync: <R = unknown>(artifact: Artifact, ...args: Args<Ctx>) => R\n}\n\nconst RUNTIME_KEYS = [\n 'variables',\n 'functions',\n 'libraries',\n 'limits',\n 'cacheSize',\n 'clock',\n 'validateContext',\n 'strict',\n]\n\n/** The runtime takes bonsai()'s options (`builtins` only in bonsai-js/runtime/core). */\nexport function assertRuntimeOptions(options: unknown, core: boolean): void {\n if (typeof options !== 'object' || options === null) return\n let keys: string[]\n try {\n keys = Object.keys(options)\n } catch (error) {\n throw new TypeError('Runtime options could not be read', { cause: error })\n }\n const allowed = core ? [...RUNTIME_KEYS, 'builtins'] : RUNTIME_KEYS\n for (const key of keys) {\n if (!allowed.includes(key))\n throw new TypeError(\n key === 'builtins'\n ? 'Unknown runtime option key \"builtins\" (bonsai-js/runtime has every built-in; import runtime from bonsai-js/runtime/core to choose them)'\n : `Unknown runtime option key \"${key}\" (expected one of: ${allowed.join(', ')})`,\n )\n }\n}\n\nexport function createRuntime<Ctx>(settings: Settings, core: boolean): Runtime<Ctx> {\n const cache = new ProgramCache<RuntimeProgram<Ctx>>(settings.cacheSize)\n\n function load<R>(artifact: Artifact, expect: Type | undefined): RuntimeProgram<Ctx, R> {\n const loaded = loadArtifact(artifact, settings, expect)\n const { runSync, runAsync } = loadedRunner<R>(loaded, settings)\n return Object.freeze({\n source: loaded.source,\n async: loaded.analysis.async,\n evaluate: (...args: Args<Ctx>) => runAsync(args[0], args[1]),\n evaluateSync: (...args: Args<Ctx>) => runSync(args[0], args[1]),\n })\n }\n\n function cached(artifact: unknown): RuntimeProgram<Ctx> {\n const read = readArtifact(artifact, settings)\n const key = artifactKey(read)\n const known = cache.get(key)\n if (known !== undefined) return known\n const program = load(read, undefined)\n cache.set(key, program)\n return program\n }\n\n const rt: Runtime<Ctx> = {\n load: <E extends Type = AnyType>(artifact: Artifact, options?: LoadOptions<E>) => {\n const expect = expectOption(options, 'Load')\n if (expect !== undefined) assertExpectSize(expect, 'Load option \"expect\"')\n return load<Infer<E>>(readArtifact(artifact, settings), expect)\n },\n evaluate<R>(artifact: Artifact, ...args: Args<Ctx>): Promise<R> {\n try {\n return cached(artifact).evaluate(...args) as Promise<R>\n } catch (error) {\n return Promise.reject(error instanceof Error ? error : new Error(String(error)))\n }\n },\n evaluateSync: <R>(artifact: Artifact, ...args: Args<Ctx>) =>\n cached(artifact).evaluateSync(...args) as R,\n extend: (options) => {\n assertRuntimeOptions(options, core)\n return createRuntime(settingsFrom(settings, options), core)\n },\n }\n return Object.freeze(rt)\n}\n"],"mappings":";;;;;;;;;AAwGA,MAAM,eAAe;CACnB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAGA,SAAgB,qBAAqB,SAAkB,MAAqB;CAC1E,IAAI,OAAO,YAAY,YAAY,YAAY,MAAM;CACrD,IAAI;CACJ,IAAI;EACF,OAAO,OAAO,KAAK,OAAO;CAC5B,SAAS,OAAO;EACd,MAAM,IAAI,UAAU,qCAAqC,EAAE,OAAO,MAAM,CAAC;CAC3E;CACA,MAAM,UAAU,OAAO,CAAC,GAAG,cAAc,UAAU,IAAI;CACvD,KAAK,MAAM,OAAO,MAChB,IAAI,CAAC,QAAQ,SAAS,GAAG,GACvB,MAAM,IAAI,UACR,QAAQ,aACJ,8IACA,+BAA+B,IAAI,sBAAsB,QAAQ,KAAK,IAAI,EAAE,EAClF;AAEN;AAEA,SAAgB,cAAmB,UAAoB,MAA6B;CAClF,MAAM,QAAQ,IAAI,aAAkC,SAAS,SAAS;CAEtE,SAAS,KAAQ,UAAoB,QAAkD;EACrF,MAAM,SAAS,aAAa,UAAU,UAAU,MAAM;EACtD,MAAM,EAAE,SAAS,aAAa,aAAgB,QAAQ,QAAQ;EAC9D,OAAO,OAAO,OAAO;GACnB,QAAQ,OAAO;GACf,OAAO,OAAO,SAAS;GACvB,WAAW,GAAG,SAAoB,SAAS,KAAK,IAAI,KAAK,EAAE;GAC3D,eAAe,GAAG,SAAoB,QAAQ,KAAK,IAAI,KAAK,EAAE;EAChE,CAAC;CACH;CAEA,SAAS,OAAO,UAAwC;EACtD,MAAM,OAAO,aAAa,UAAU,QAAQ;EAC5C,MAAM,MAAM,YAAY,IAAI;EAC5B,MAAM,QAAQ,MAAM,IAAI,GAAG;EAC3B,IAAI,UAAU,KAAA,GAAW,OAAO;EAChC,MAAM,UAAU,KAAK,MAAM,KAAA,CAAS;EACpC,MAAM,IAAI,KAAK,OAAO;EACtB,OAAO;CACT;CAsBA,OAAO,OAAO,OAAO;EAnBnB,OAAiC,UAAoB,YAA6B;GAChF,MAAM,SAAS,aAAa,SAAS,MAAM;GAC3C,IAAI,WAAW,KAAA,GAAW,iBAAiB,QAAQ,wBAAsB;GACzE,OAAO,KAAe,aAAa,UAAU,QAAQ,GAAG,MAAM;EAChE;EACA,SAAY,UAAoB,GAAG,MAA6B;GAC9D,IAAI;IACF,OAAO,OAAO,QAAQ,CAAC,CAAC,SAAS,GAAG,IAAI;GAC1C,SAAS,OAAO;IACd,OAAO,QAAQ,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;GACjF;EACF;EACA,eAAkB,UAAoB,GAAG,SACvC,OAAO,QAAQ,CAAC,CAAC,aAAa,GAAG,IAAI;EACvC,SAAS,YAAY;GACnB,qBAAqB,SAAS,IAAI;GAClC,OAAO,cAAc,aAAa,UAAU,OAAO,GAAG,IAAI;EAC5D;CAEoB,CAAC;AACzB"}
@@ -1,47 +1,10 @@
1
1
  import { ArtifactProblem, BonsaiArtifactError, BonsaiError, BonsaiErrorJSON, BonsaiErrorName, BonsaiLimitError, BonsaiRuntimeError, ErrorCode, Span, isBonsaiError } from "../errors.mjs";
2
2
  import { Duration } from "./values.mjs";
3
- import { AnyType, Infer, InferVariables, Type, formatType, t } from "../types.mjs";
4
- import { AbortSignalLike, Args, ContextFor, EnvironmentOptions, EvaluateOptions, Extended, FnSpec, HostCall, HostFunction, Library, Limits, NoVariables, fn, withContext } from "../environment.mjs";
3
+ import { Infer, InferVariables, Type, formatType, t } from "../types.mjs";
4
+ import { AbortSignalLike, ContextFor, EvaluateOptions, FnSpec, HostCall, HostFunction, Library, Limits, NoVariables, fn, withContext } from "../environment.mjs";
5
5
  import { Artifact, Plan } from "../artifact.mjs";
6
+ import { LoadOptions, Runtime, RuntimeOptions, RuntimeProgram } from "./create.mjs";
6
7
  //#region src/runtime/index.d.ts
7
- /**
8
- * The options of `runtime()`: those of `bonsai()` that affect evaluation.
9
- * `strict` has bonsai()'s default and must match the producer's: a strict
10
- * runtime refuses artifacts that read undeclared variables.
11
- */
12
- export type RuntimeOptions<V extends Readonly<Record<string, Type>> = Readonly<Record<string, Type>>, L extends readonly Library[] = readonly Library[]> = EnvironmentOptions<V, L>;
13
- /** A loaded artifact: evaluates as the program it was made from does. Immutable. */
14
- export interface RuntimeProgram<Ctx = any, R = unknown> {
15
- readonly source: string;
16
- /** Whether the expression calls an async host function (evaluateSync rejects it). */
17
- readonly async: boolean;
18
- evaluate: (...args: Args<Ctx>) => Promise<R>;
19
- evaluateSync: (...args: Args<Ctx>) => R;
20
- }
21
- export interface LoadOptions<E extends Type = Type> {
22
- /**
23
- * The type the host relies on the result having. The artifact must have been
24
- * compiled for exactly this type, and every result is checked against it.
25
- */
26
- readonly expect?: E | undefined;
27
- }
28
- /** Loads and evaluates artifacts against one set of definitions. Immutable. */
29
- export interface Runtime<Ctx = any> {
30
- /**
31
- * Loads an artifact. Throws BonsaiArtifactError (code ARTIFACT, with a
32
- * `reason`) when the artifact does not reproduce the checked program with
33
- * this runtime's definitions, and TypeError when the value is not an artifact.
34
- */
35
- load: <E extends Type = AnyType>(artifact: Artifact, options?: LoadOptions<E>) => RuntimeProgram<Ctx, Infer<E>>;
36
- /** Loads (cached) and evaluates. `R` is an unchecked type, as for `env.evaluate`. */
37
- evaluate: <R = unknown>(artifact: Artifact, ...args: Args<Ctx>) => Promise<R>;
38
- /** Loads (cached) and evaluates synchronously. */
39
- evaluateSync: <R = unknown>(artifact: Artifact, ...args: Args<Ctx>) => R;
40
- /** A runtime with more or replaced definitions; see `bonsai().extend`. */
41
- extend: <const V2 extends Readonly<Record<string, Type>> = NoVariables, const L2 extends readonly Library[] = [], const S2 extends boolean | undefined = undefined>(options: RuntimeOptions<V2, L2> & {
42
- readonly strict?: S2;
43
- }) => Runtime<Extended<Ctx, V2, L2, S2>>;
44
- }
45
8
  /**
46
9
  * Creates a runtime: the host functions, variables, and limits artifacts are
47
10
  * loaded and evaluated against. Declare them in a module the producing
@@ -58,4 +21,4 @@ export declare function runtime<const V extends Readonly<Record<string, Type>> =
58
21
  readonly strict?: S;
59
22
  }): Runtime<ContextFor<V, L, S>>;
60
23
  //#endregion
61
- export { type AbortSignalLike, type Artifact, type ArtifactProblem, BonsaiArtifactError, BonsaiError, type BonsaiErrorJSON, type BonsaiErrorName, BonsaiLimitError, BonsaiRuntimeError, Duration, type ErrorCode, type EvaluateOptions, type FnSpec, type HostCall, type HostFunction, type Infer, type InferVariables, type Library, type Limits, type Plan, type Span, type Type, fn, formatType, isBonsaiError, t, withContext };
24
+ export { type AbortSignalLike, type Artifact, type ArtifactProblem, BonsaiArtifactError, BonsaiError, type BonsaiErrorJSON, type BonsaiErrorName, BonsaiLimitError, BonsaiRuntimeError, Duration, type ErrorCode, type EvaluateOptions, type FnSpec, type HostCall, type HostFunction, type Infer, type InferVariables, type Library, type Limits, type LoadOptions, type Plan, type Runtime, type RuntimeOptions, type RuntimeProgram, type Span, type Type, fn, formatType, isBonsaiError, t, withContext };
@@ -1,78 +1,17 @@
1
1
  import { BonsaiArtifactError, BonsaiError, BonsaiLimitError, BonsaiRuntimeError, isBonsaiError } from "../errors.mjs";
2
2
  import { Duration } from "./values.mjs";
3
3
  import { formatType, t } from "../types.mjs";
4
- import { ProgramCache, expectOption, fn, settingsFrom, withContext } from "../environment.mjs";
5
- import { assertExpectSize } from "../artifact.mjs";
6
- import { artifactKey, loadArtifact, loadedRunner, readArtifact } from "../load.mjs";
4
+ import { BUILTINS } from "../functions/builtins.mjs";
5
+ import { fn, settingsFrom, withContext } from "../environment.mjs";
6
+ import { assertRuntimeOptions, createRuntime } from "./create.mjs";
7
7
  //#region src/runtime/index.ts
8
8
  /**
9
9
  * `bonsai-js/runtime`: loads and evaluates artifacts made by `toArtifact()`
10
10
  * from `bonsai-js`, without the parser's checker. Check and compile rules where
11
11
  * the full toolchain runs (a server, CI, an admin backend), ship the artifacts,
12
- * and evaluate them here with the same results.
12
+ * and evaluate them here with the same results. Every built-in is included;
13
+ * bonsai-js/runtime/core includes only those passed in `builtins`.
13
14
  */
14
- const RUNTIME_KEYS = [
15
- "variables",
16
- "functions",
17
- "libraries",
18
- "limits",
19
- "cacheSize",
20
- "clock",
21
- "validateContext",
22
- "strict"
23
- ];
24
- /** The runtime takes bonsai()'s options. */
25
- function assertRuntimeOptions(options) {
26
- if (typeof options !== "object" || options === null) return;
27
- let keys;
28
- try {
29
- keys = Object.keys(options);
30
- } catch (error) {
31
- throw new TypeError("Runtime options could not be read", { cause: error });
32
- }
33
- for (const key of keys) if (!RUNTIME_KEYS.includes(key)) throw new TypeError(`Unknown runtime option key "${key}" (expected one of: ${RUNTIME_KEYS.join(", ")})`);
34
- }
35
- function createRuntime(settings) {
36
- const cache = new ProgramCache(settings.cacheSize);
37
- function load(artifact, expect) {
38
- const loaded = loadArtifact(artifact, settings, expect);
39
- const { runSync, runAsync } = loadedRunner(loaded, settings);
40
- return Object.freeze({
41
- source: loaded.source,
42
- async: loaded.analysis.async,
43
- evaluate: (...args) => runAsync(args[0], args[1]),
44
- evaluateSync: (...args) => runSync(args[0], args[1])
45
- });
46
- }
47
- function cached(artifact) {
48
- const read = readArtifact(artifact, settings);
49
- const key = artifactKey(read);
50
- const known = cache.get(key);
51
- if (known !== void 0) return known;
52
- const program = load(read, void 0);
53
- cache.set(key, program);
54
- return program;
55
- }
56
- return Object.freeze({
57
- load: (artifact, options) => {
58
- const expect = expectOption(options, "Load");
59
- if (expect !== void 0) assertExpectSize(expect, "Load option \"expect\"");
60
- return load(readArtifact(artifact, settings), expect);
61
- },
62
- evaluate(artifact, ...args) {
63
- try {
64
- return cached(artifact).evaluate(...args);
65
- } catch (error) {
66
- return Promise.reject(error instanceof Error ? error : new Error(String(error)));
67
- }
68
- },
69
- evaluateSync: (artifact, ...args) => cached(artifact).evaluateSync(...args),
70
- extend: (options) => {
71
- assertRuntimeOptions(options);
72
- return createRuntime(settingsFrom(settings, options));
73
- }
74
- });
75
- }
76
15
  /**
77
16
  * Creates a runtime: the host functions, variables, and limits artifacts are
78
17
  * loaded and evaluated against. Declare them in a module the producing
@@ -86,8 +25,8 @@ function createRuntime(settings) {
86
25
  * ```
87
26
  */
88
27
  function runtime(options = {}) {
89
- assertRuntimeOptions(options);
90
- return createRuntime(settingsFrom(void 0, options));
28
+ assertRuntimeOptions(options, false);
29
+ return createRuntime(settingsFrom(void 0, options, BUILTINS), false);
91
30
  }
92
31
  //#endregion
93
32
  export { BonsaiArtifactError, BonsaiError, BonsaiLimitError, BonsaiRuntimeError, Duration, fn, formatType, isBonsaiError, runtime, t, withContext };
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/runtime/index.ts"],"sourcesContent":["/**\n * `bonsai-js/runtime`: loads and evaluates artifacts made by `toArtifact()`\n * from `bonsai-js`, without the parser's checker. Check and compile rules where\n * the full toolchain runs (a server, CI, an admin backend), ship the artifacts,\n * and evaluate them here with the same results.\n */\nimport { assertExpectSize, type Artifact } from '../artifact.js'\nimport {\n ProgramCache,\n expectOption,\n settingsFrom,\n type Args,\n type ContextFor,\n type EnvironmentOptions,\n type Extended,\n type Library,\n type NoVariables,\n type Settings,\n} from '../environment.js'\nimport { artifactKey, loadArtifact, loadedRunner, readArtifact } from '../load.js'\nimport type { AnyType, Infer, Type } from '../types.js'\n\n/**\n * The options of `runtime()`: those of `bonsai()` that affect evaluation.\n * `strict` has bonsai()'s default and must match the producer's: a strict\n * runtime refuses artifacts that read undeclared variables.\n */\nexport type RuntimeOptions<\n V extends Readonly<Record<string, Type>> = Readonly<Record<string, Type>>,\n L extends readonly Library[] = readonly Library[],\n> = EnvironmentOptions<V, L>\n\n/** A loaded artifact: evaluates as the program it was made from does. Immutable. */\n// oxlint-disable-next-line typescript/no-explicit-any -- the erased context, as for Program\nexport interface RuntimeProgram<Ctx = any, R = unknown> {\n readonly source: string\n /** Whether the expression calls an async host function (evaluateSync rejects it). */\n readonly async: boolean\n evaluate: (...args: Args<Ctx>) => Promise<R>\n evaluateSync: (...args: Args<Ctx>) => R\n}\n\nexport interface LoadOptions<E extends Type = Type> {\n /**\n * The type the host relies on the result having. The artifact must have been\n * compiled for exactly this type, and every result is checked against it.\n */\n readonly expect?: E | undefined\n}\n\n/** Loads and evaluates artifacts against one set of definitions. Immutable. */\n// oxlint-disable-next-line typescript/no-explicit-any -- the erased context, as for Environment\nexport interface Runtime<Ctx = any> {\n /**\n * Loads an artifact. Throws BonsaiArtifactError (code ARTIFACT, with a\n * `reason`) when the artifact does not reproduce the checked program with\n * this runtime's definitions, and TypeError when the value is not an artifact.\n */\n load: <E extends Type = AnyType>(\n artifact: Artifact,\n options?: LoadOptions<E>,\n ) => RuntimeProgram<Ctx, Infer<E>>\n /** Loads (cached) and evaluates. `R` is an unchecked type, as for `env.evaluate`. */\n evaluate: <R = unknown>(artifact: Artifact, ...args: Args<Ctx>) => Promise<R>\n /** Loads (cached) and evaluates synchronously. */\n evaluateSync: <R = unknown>(artifact: Artifact, ...args: Args<Ctx>) => R\n /** A runtime with more or replaced definitions; see `bonsai().extend`. */\n extend: <\n const V2 extends Readonly<Record<string, Type>> = NoVariables,\n const L2 extends readonly Library[] = [],\n const S2 extends boolean | undefined = undefined,\n >(\n options: RuntimeOptions<V2, L2> & { readonly strict?: S2 },\n ) => Runtime<Extended<Ctx, V2, L2, S2>>\n}\n\nconst RUNTIME_KEYS = [\n 'variables',\n 'functions',\n 'libraries',\n 'limits',\n 'cacheSize',\n 'clock',\n 'validateContext',\n 'strict',\n]\n\n/** The runtime takes bonsai()'s options. */\nfunction assertRuntimeOptions(options: unknown): void {\n if (typeof options !== 'object' || options === null) return\n let keys: string[]\n try {\n keys = Object.keys(options)\n } catch (error) {\n throw new TypeError('Runtime options could not be read', { cause: error })\n }\n for (const key of keys) {\n if (!RUNTIME_KEYS.includes(key))\n throw new TypeError(\n `Unknown runtime option key \"${key}\" (expected one of: ${RUNTIME_KEYS.join(', ')})`,\n )\n }\n}\n\nfunction createRuntime<Ctx>(settings: Settings): Runtime<Ctx> {\n const cache = new ProgramCache<RuntimeProgram<Ctx>>(settings.cacheSize)\n\n function load<R>(artifact: Artifact, expect: Type | undefined): RuntimeProgram<Ctx, R> {\n const loaded = loadArtifact(artifact, settings, expect)\n const { runSync, runAsync } = loadedRunner<R>(loaded, settings)\n return Object.freeze({\n source: loaded.source,\n async: loaded.analysis.async,\n evaluate: (...args: Args<Ctx>) => runAsync(args[0], args[1]),\n evaluateSync: (...args: Args<Ctx>) => runSync(args[0], args[1]),\n })\n }\n\n function cached(artifact: unknown): RuntimeProgram<Ctx> {\n const read = readArtifact(artifact, settings)\n const key = artifactKey(read)\n const known = cache.get(key)\n if (known !== undefined) return known\n const program = load(read, undefined)\n cache.set(key, program)\n return program\n }\n\n const rt: Runtime<Ctx> = {\n load: <E extends Type = AnyType>(artifact: Artifact, options?: LoadOptions<E>) => {\n const expect = expectOption(options, 'Load')\n if (expect !== undefined) assertExpectSize(expect, 'Load option \"expect\"')\n return load<Infer<E>>(readArtifact(artifact, settings), expect)\n },\n evaluate<R>(artifact: Artifact, ...args: Args<Ctx>): Promise<R> {\n try {\n return cached(artifact).evaluate(...args) as Promise<R>\n } catch (error) {\n return Promise.reject(error instanceof Error ? error : new Error(String(error)))\n }\n },\n evaluateSync: <R>(artifact: Artifact, ...args: Args<Ctx>) =>\n cached(artifact).evaluateSync(...args) as R,\n extend: (options) => {\n assertRuntimeOptions(options)\n return createRuntime(settingsFrom(settings, options))\n },\n }\n return Object.freeze(rt)\n}\n\n/**\n * Creates a runtime: the host functions, variables, and limits artifacts are\n * loaded and evaluated against. Declare them in a module the producing\n * environment shares, since an artifact loads only where its called functions\n * and read variables are declared exactly as where it was made.\n *\n * ```ts\n * const rt = runtime({ variables, functions })\n * const rule = rt.load(artifact, { expect: t.boolean() })\n * rule.evaluateSync({ user: { age: 30 } }) // true\n * ```\n */\nexport function runtime<\n const V extends Readonly<Record<string, Type>> = NoVariables,\n const L extends readonly Library[] = [],\n const S extends boolean | undefined = undefined,\n>(options: RuntimeOptions<V, L> & { readonly strict?: S } = {}): Runtime<ContextFor<V, L, S>> {\n assertRuntimeOptions(options)\n return createRuntime<ContextFor<V, L, S>>(settingsFrom(undefined, options))\n}\n\nexport { fn, withContext } from '../environment.js'\nexport type {\n AbortSignalLike,\n EvaluateOptions,\n FnSpec,\n HostCall,\n HostFunction,\n Library,\n Limits,\n} from '../environment.js'\nexport type { Artifact, Plan } from '../artifact.js'\nexport { t, formatType } from '../types.js'\nexport type { Infer, InferVariables, Type } from '../types.js'\nexport { Duration } from './values.js'\nexport {\n BonsaiError,\n BonsaiArtifactError,\n BonsaiLimitError,\n BonsaiRuntimeError,\n isBonsaiError,\n} from '../errors.js'\nexport type {\n ArtifactProblem,\n BonsaiErrorJSON,\n BonsaiErrorName,\n ErrorCode,\n Span,\n} from '../errors.js'\n"],"mappings":";;;;;;;;;;;;;AA4EA,MAAM,eAAe;CACnB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAGA,SAAS,qBAAqB,SAAwB;CACpD,IAAI,OAAO,YAAY,YAAY,YAAY,MAAM;CACrD,IAAI;CACJ,IAAI;EACF,OAAO,OAAO,KAAK,OAAO;CAC5B,SAAS,OAAO;EACd,MAAM,IAAI,UAAU,qCAAqC,EAAE,OAAO,MAAM,CAAC;CAC3E;CACA,KAAK,MAAM,OAAO,MAChB,IAAI,CAAC,aAAa,SAAS,GAAG,GAC5B,MAAM,IAAI,UACR,+BAA+B,IAAI,sBAAsB,aAAa,KAAK,IAAI,EAAE,EACnF;AAEN;AAEA,SAAS,cAAmB,UAAkC;CAC5D,MAAM,QAAQ,IAAI,aAAkC,SAAS,SAAS;CAEtE,SAAS,KAAQ,UAAoB,QAAkD;EACrF,MAAM,SAAS,aAAa,UAAU,UAAU,MAAM;EACtD,MAAM,EAAE,SAAS,aAAa,aAAgB,QAAQ,QAAQ;EAC9D,OAAO,OAAO,OAAO;GACnB,QAAQ,OAAO;GACf,OAAO,OAAO,SAAS;GACvB,WAAW,GAAG,SAAoB,SAAS,KAAK,IAAI,KAAK,EAAE;GAC3D,eAAe,GAAG,SAAoB,QAAQ,KAAK,IAAI,KAAK,EAAE;EAChE,CAAC;CACH;CAEA,SAAS,OAAO,UAAwC;EACtD,MAAM,OAAO,aAAa,UAAU,QAAQ;EAC5C,MAAM,MAAM,YAAY,IAAI;EAC5B,MAAM,QAAQ,MAAM,IAAI,GAAG;EAC3B,IAAI,UAAU,KAAA,GAAW,OAAO;EAChC,MAAM,UAAU,KAAK,MAAM,KAAA,CAAS;EACpC,MAAM,IAAI,KAAK,OAAO;EACtB,OAAO;CACT;CAsBA,OAAO,OAAO,OAAO;EAnBnB,OAAiC,UAAoB,YAA6B;GAChF,MAAM,SAAS,aAAa,SAAS,MAAM;GAC3C,IAAI,WAAW,KAAA,GAAW,iBAAiB,QAAQ,wBAAsB;GACzE,OAAO,KAAe,aAAa,UAAU,QAAQ,GAAG,MAAM;EAChE;EACA,SAAY,UAAoB,GAAG,MAA6B;GAC9D,IAAI;IACF,OAAO,OAAO,QAAQ,CAAC,CAAC,SAAS,GAAG,IAAI;GAC1C,SAAS,OAAO;IACd,OAAO,QAAQ,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;GACjF;EACF;EACA,eAAkB,UAAoB,GAAG,SACvC,OAAO,QAAQ,CAAC,CAAC,aAAa,GAAG,IAAI;EACvC,SAAS,YAAY;GACnB,qBAAqB,OAAO;GAC5B,OAAO,cAAc,aAAa,UAAU,OAAO,CAAC;EACtD;CAEoB,CAAC;AACzB;;;;;;;;;;;;;AAcA,SAAgB,QAId,UAA0D,CAAC,GAAiC;CAC5F,qBAAqB,OAAO;CAC5B,OAAO,cAAmC,aAAa,KAAA,GAAW,OAAO,CAAC;AAC5E"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/runtime/index.ts"],"sourcesContent":["/**\n * `bonsai-js/runtime`: loads and evaluates artifacts made by `toArtifact()`\n * from `bonsai-js`, without the parser's checker. Check and compile rules where\n * the full toolchain runs (a server, CI, an admin backend), ship the artifacts,\n * and evaluate them here with the same results. Every built-in is included;\n * bonsai-js/runtime/core includes only those passed in `builtins`.\n */\nimport { settingsFrom, type ContextFor, type Library, type NoVariables } from '../environment.js'\nimport { BUILTINS } from '../functions/builtins.js'\nimport type { Type } from '../types.js'\nimport { assertRuntimeOptions, createRuntime, type Runtime, type RuntimeOptions } from './create.js'\n\n/**\n * Creates a runtime: the host functions, variables, and limits artifacts are\n * loaded and evaluated against. Declare them in a module the producing\n * environment shares, since an artifact loads only where its called functions\n * and read variables are declared exactly as where it was made.\n *\n * ```ts\n * const rt = runtime({ variables, functions })\n * const rule = rt.load(artifact, { expect: t.boolean() })\n * rule.evaluateSync({ user: { age: 30 } }) // true\n * ```\n */\nexport function runtime<\n const V extends Readonly<Record<string, Type>> = NoVariables,\n const L extends readonly Library[] = [],\n const S extends boolean | undefined = undefined,\n>(options: RuntimeOptions<V, L> & { readonly strict?: S } = {}): Runtime<ContextFor<V, L, S>> {\n assertRuntimeOptions(options, false)\n return createRuntime<ContextFor<V, L, S>>(settingsFrom(undefined, options, BUILTINS), false)\n}\n\nexport type { Runtime, RuntimeOptions } from './create.js'\nexport * from './exports.js'\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,QAId,UAA0D,CAAC,GAAiC;CAC5F,qBAAqB,SAAS,KAAK;CACnC,OAAO,cAAmC,aAAa,KAAA,GAAW,SAAS,QAAQ,GAAG,KAAK;AAC7F"}
@@ -4,6 +4,7 @@ import { assertType, signatureText } from "../functions/define.mjs";
4
4
  import { forEachChild } from "../syntax/ast.mjs";
5
5
  import { parse } from "../syntax/parser.mjs";
6
6
  import { acceptsArgument, analyze } from "../check/checker.mjs";
7
+ import { descriptionOf } from "../functions/descriptions.mjs";
7
8
  import { internalsOf } from "../environment.mjs";
8
9
  //#region src/service/index.ts
9
10
  const PROBE = "__bonsai_probe__";
@@ -289,7 +290,7 @@ function createLanguageService(env, options) {
289
290
  label: name,
290
291
  kind: "method",
291
292
  detail: applicable.map((o) => signatureText(name, o)).join("\n"),
292
- documentation: def.description,
293
+ documentation: descriptionOf(def),
293
294
  insertText: `${name}(${takesMore(def) ? "" : ")"}`
294
295
  });
295
296
  }
@@ -323,7 +324,7 @@ function createLanguageService(env, options) {
323
324
  label: name,
324
325
  kind: "function",
325
326
  detail: def.overloads.map((o) => signatureText(name, o)).join("\n"),
326
- documentation: def.description,
327
+ documentation: descriptionOf(def),
327
328
  insertText: `${name}(`
328
329
  });
329
330
  }
@@ -355,7 +356,7 @@ function createLanguageService(env, options) {
355
356
  start: node.nameStart,
356
357
  end: node.nameEnd,
357
358
  detail: def.overloads.map((o) => signatureText(def.name, o)).join("\n"),
358
- documentation: def.description
359
+ documentation: descriptionOf(def)
359
360
  };
360
361
  }
361
362
  const type = analysis.types.get(node);