/** * `$expr` — the COMPUTED binding value. The `{...}` gaps of a screen document * are REAL JavaScript expressions, stored verbatim and evaluated in a sealed * interpreter: * `{ $expr: "invoices.data.reduce((t, r) => t + r.amount_cents, 0) / 100" }`. * * A computed value is evaluated LIVE at bind resolution in the renderer — the * same place `$path` resolves — and re-evaluated whenever the query data * changes. Nothing is ever computed at generation time: a headline total that * was frozen into the document the moment a model wrote it would be a lie by * the next refresh. * * Four surfaces: * - {@link parseExpr} source → an ESTree expression. Total, size-capped. * - {@link evaluateExpr} source + resolved query data → a value. Total: an * evaluation problem yields the issue, never a throw. * - {@link checkExpr} source + the declared query names → the fact * findings before it ships. The apps fact check * (checking/facts.ts) speaks these. TYPES are not * checked here: the screen is a strict TSX subset and * `tsc` reads it against the query result types * (checking/screen-typings.ts) — one compiler, not a * bespoke shape walker. * - {@link warmExprRuntime} boots the interpreter. Evaluation is * SYNCHRONOUS; only this one-time boot is not. * * Grammar: a JavaScript expression, parsed by acorn. There is no closed call * vocabulary and no reshape dialect — arrays, objects, strings and numbers all * carry their own methods, so `rows.reduce(...)`, `rows.map(...)`, * `rows.filter(...)`, arrow functions, spread, template literals, optional * chaining and ternaries are simply available. * * Data that has not arrived is not a problem: a query the data map does not * carry resolves to `undefined` and flows through the expression as * `undefined` — the same discipline as `$reshape` (loading is never a * mismatch). * * THE SEAL. Evaluation runs inside a QuickJS WebAssembly VM * (`quickjs-emscripten`), not in this realm: the expression cannot reach the * DOM, the network, `process`, `require`, or any host object, because none of * them exist inside the VM. Two intrinsics are DELETED from the VM at boot — * `Date` and `Math.random` — so the same source over the same data is always * the same answer, which is what the smoke checks rest on. Execution is * bounded by an interrupt budget and the runtime's memory limit, so no * expression can hang the surface it renders on. */ import type { Expression, Node } from "acorn"; import type { Json } from "@vendoai/core"; /** A computed binding value; the string is the expression source. */ export interface ExprBinding { $expr: string; } /** The `$path`/`$state` guards' sibling (tree-node.ts). */ export declare function isExprBinding(value: unknown): value is ExprBinding; /** The one size gate on an expression source. Longer than any real computed * value and short enough that parsing and evaluation stay cheap — it is what * replaces the old dialect's nesting-depth bound. */ export declare const EXPR_MAX_CHARS = 1000; export type ExprParse = { ok: true; node: Expression; } | { ok: false; issue: string; }; /** * Parse one expression source. Pure, deterministic, total: any input either * yields an ESTree expression or one issue written as a sentence. `node.end` * is where the expression stopped, which is how the wire compiler finds * trailing content. */ export declare function parseExpr(source: string): ExprParse; /** * The names an expression reads from OUTSIDE itself — the compiler's * unknown-reference gate and the fact check's. A function's parameters and the * locals its body declares are bound, not free, so * `rows.reduce((total, row) => total + row.n, 0)` and * `rows.map((r) => { const cents = r.amount; return cents / 100; })` both read * exactly `rows`. * * Non-computed member properties (`.data`) and non-computed object keys * (`{ label: … }`) are names of fields, not of values, so they are skipped. */ export declare function exprFreeIdentifiers(node: Node): string[]; /** The total evaluation result. `ok: false` is the renderer's contained * data-shape-notice path (the `$reshape` convention), never a throw. */ export type ExprResult = { ok: true; value: Json | undefined; } | { ok: false; issue: string; }; /** The intrinsics that survive the SEAL, and therefore the names an expression * may read that are NOT query data. The scope gates below need this list * because a free name they refuse reads as loading forever and a fact finding * says it names no query — both of which would be lies about a name the VM * really carries. `Date` is absent on purpose: the SEAL deletes it, so an * expression that reads it IS a finding. Add a name here only when the SEAL * leaves it deterministic. */ export declare const SEALED_GLOBALS: ReadonlySet; /** * Boot the interpreter. Evaluation is synchronous, but the WebAssembly module * behind it loads once, asynchronously — so a caller that must not miss the * first render (a server-side render, a test) awaits this first. A renderer * does not have to: an expression evaluated before the boot lands reads as * `undefined`, which is exactly how it reads while its query data is still in * flight, and the boot races a network round-trip it reliably wins. * * The variant is the engine's own — ./variant.ts, one build and one set of * WebAssembly bytes for the screen engine and for this. */ export declare function warmExprRuntime(): Promise; /** * Evaluate one expression against the renderer's resolved query data (keyed by * query name). Total: an evaluation problem yields the issue the renderer shows * as its contained data-shape notice, never a throw. Deterministic — same * source, same data, same answer. * * The scope is EXACTLY the query data. There is no `fmt`, no `tools`, no * ambient host object and no clock: a value is formatted by the component that * displays it, and an island is where code that needs more than this belongs. */ export declare function evaluateExpr(source: string, data: Record): ExprResult; export interface ExprCheckContext { /** The declared query names; a free name matching none is a fact finding. */ queryNames: readonly string[]; } /** * The FACT check behind an `$expr`: it parses as a JavaScript expression, it is * within the size cap, and every name it reads is a query the screen declared or * an intrinsic the sealed VM carries ({@link SEALED_GLOBALS}). * * FIELD existence and TYPES are deliberately not here. The screen prints back * as a strict TSX subset and the checks floor runs the real compiler over it * against the queries' declared result types (checking/screen-typings.ts), so * `invoices.data.reduce((t, r) => t + r.amont, 0)` is a tsc error naming the * real fields — a better finding than a bespoke walker could write, from the * one type checker instead of a second one that can disagree with it. */ export declare function checkExpr(source: string, context: ExprCheckContext): string[]; //# sourceMappingURL=expr.d.ts.map