@uicast/core 0.0.1-beta.2 → 0.0.2

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.
Files changed (78) hide show
  1. package/CHANGELOG.md +3 -13
  2. package/README.md +126 -1
  3. package/dist/build-elements-by-key.d.ts +13 -0
  4. package/dist/build-elements-by-key.js +39 -0
  5. package/dist/constants.d.ts +2 -0
  6. package/dist/constants.js +4 -0
  7. package/dist/create-component-definition.d.ts +26 -0
  8. package/dist/create-component-definition.js +74 -0
  9. package/dist/entry-error.d.ts +41 -31
  10. package/dist/entry-error.js +49 -20
  11. package/dist/entry-shape.d.ts +7 -0
  12. package/dist/entry-shape.js +88 -0
  13. package/dist/expr/evaluate.d.ts +4 -6
  14. package/dist/expr/evaluate.js +36 -46
  15. package/dist/expr/extract-deps.d.ts +3 -1
  16. package/dist/expr/extract-deps.js +20 -20
  17. package/dist/expr/plan-step-waves.d.ts +8 -0
  18. package/dist/expr/plan-step-waves.js +45 -0
  19. package/dist/index.d.ts +7 -8
  20. package/dist/index.js +5 -9
  21. package/dist/internal.d.ts +12 -0
  22. package/dist/internal.js +13 -0
  23. package/dist/json-schema.d.ts +58 -0
  24. package/dist/json-schema.js +73 -0
  25. package/dist/prompt/format.d.ts +5 -0
  26. package/dist/prompt/format.js +22 -0
  27. package/dist/prompt/get-common-instructions-partial-prompt.d.ts +20 -10
  28. package/dist/prompt/get-common-instructions-partial-prompt.js +11 -9
  29. package/dist/prompt/get-components-partial-prompt.d.ts +20 -28
  30. package/dist/prompt/get-components-partial-prompt.js +122 -123
  31. package/dist/prompt/get-edit-request-prompt.d.ts +16 -16
  32. package/dist/prompt/get-edit-request-prompt.js +11 -18
  33. package/dist/prompt/get-error-recovery-prompt.d.ts +25 -14
  34. package/dist/prompt/get-error-recovery-prompt.js +17 -14
  35. package/dist/prompt/get-expressions-partial-prompt.d.ts +19 -20
  36. package/dist/prompt/get-expressions-partial-prompt.js +11 -21
  37. package/dist/prompt/get-functions-partial-prompt.d.ts +16 -16
  38. package/dist/prompt/get-functions-partial-prompt.js +52 -40
  39. package/dist/prompt/get-scope-partial-prompt.d.ts +17 -17
  40. package/dist/prompt/get-scope-partial-prompt.js +21 -29
  41. package/dist/prompt/index.js +0 -2
  42. package/dist/prompt/json-schema-to-ts.d.ts +6 -0
  43. package/dist/prompt/json-schema-to-ts.js +146 -0
  44. package/dist/prompt/md/EXPRESSIONS.json +1 -1
  45. package/dist/prompt/md/INSTRUCTIONS.json +1 -1
  46. package/dist/prompt/shared-types.d.ts +5 -0
  47. package/dist/prompt/shared-types.js +82 -0
  48. package/dist/scope/create-proxy-scope.d.ts +52 -17
  49. package/dist/scope/create-proxy-scope.js +280 -86
  50. package/dist/scope/parse-scope.d.ts +2 -20
  51. package/dist/scope/parse-scope.js +10 -35
  52. package/dist/scope/parse-set-address.d.ts +16 -0
  53. package/dist/scope/parse-set-address.js +52 -0
  54. package/dist/stream-json-lines.d.ts +14 -0
  55. package/dist/stream-json-lines.js +59 -0
  56. package/dist/types.d.ts +81 -46
  57. package/dist/types.js +6 -9
  58. package/dist/url-policy.d.ts +45 -0
  59. package/dist/url-policy.js +252 -0
  60. package/package.json +18 -16
  61. package/dist/def/create-component-definition.d.ts +0 -9
  62. package/dist/def/create-component-definition.js +0 -10
  63. package/dist/expr/analyze.d.ts +0 -5
  64. package/dist/expr/analyze.js +0 -189
  65. package/dist/expr/ast-utils.d.ts +0 -4
  66. package/dist/expr/ast-utils.js +0 -23
  67. package/dist/expr/globals.d.ts +0 -3
  68. package/dist/expr/globals.js +0 -111
  69. package/dist/expr/safer-eval.d.ts +0 -21
  70. package/dist/expr/safer-eval.js +0 -170
  71. package/dist/expr/validate.d.ts +0 -7
  72. package/dist/expr/validate.js +0 -109
  73. package/dist/prompt-utils/json-schema-to-ts.d.ts +0 -73
  74. package/dist/prompt-utils/json-schema-to-ts.js +0 -199
  75. package/dist/stream/stream-json-lines.d.ts +0 -1
  76. package/dist/stream/stream-json-lines.js +0 -94
  77. package/dist/utils/utils.d.ts +0 -13
  78. package/dist/utils/utils.js +0 -58
@@ -1,189 +0,0 @@
1
- import { childNodes } from "./ast-utils.js";
2
- // Static analysis of a parsed expression (not the security check — that's
3
- // validate.ts). Two things the renderer needs: whether the expr is async
4
- // (containsAwait), and which scopes.X.Y paths it reads (extractScopeReads), so it
5
- // can subscribe without the LLM writing a deps array.
6
- /** Does the expression `await` anything? */
7
- export function containsAwait(node) {
8
- if (!node || typeof node !== "object")
9
- return false;
10
- if (node.type === "AwaitExpression")
11
- return true;
12
- for (const child of childNodes(node)) {
13
- if (containsAwait(child))
14
- return true;
15
- }
16
- return false;
17
- }
18
- // Collect every static scopes.X.Y chain the expression reads, stopping at the
19
- // first dynamic segment (computed key, call, non-identifier property). A method
20
- // call drops its last segment (rows.filter(...) depends on rows, not .filter).
21
- // The test file pins the trickier cases.
22
- // Read a member chain into its identifier segments (`a.b.c` -> ["a","b","c"]), or
23
- // null if it hits a computed / non-identifier segment.
24
- function collectChain(node) {
25
- const parts = [];
26
- let cur = node;
27
- while (cur) {
28
- if (cur.type === "MemberExpression") {
29
- if (cur.computed)
30
- return null;
31
- const prop = cur.property;
32
- if (prop.type !== "Identifier")
33
- return null;
34
- parts.unshift(prop.name);
35
- cur = cur.object;
36
- }
37
- else if (cur.type === "Identifier") {
38
- parts.unshift(cur.name);
39
- return parts;
40
- }
41
- else {
42
- return null;
43
- }
44
- }
45
- return null;
46
- }
47
- // A computed key can read scopes itself (`scopes.x[scopes.y]`), so walk the
48
- // computed parts of a chain too.
49
- function walkComputedKeysWithin(memberExpr, out) {
50
- let cur = memberExpr;
51
- while (cur.type === "MemberExpression") {
52
- if (cur.computed) {
53
- walkScopeReads(cur.property, out);
54
- }
55
- cur = cur.object;
56
- }
57
- }
58
- // The recursive walker behind extractScopeReads().
59
- function walkScopeReads(node, out) {
60
- if (!node || typeof node !== "object")
61
- return;
62
- // A method call depends on the chain, not the method name — record the chain
63
- // without the method, then recurse args.
64
- if (node.type === "CallExpression") {
65
- const callee = node.callee;
66
- if (callee.type === "MemberExpression" && !callee.computed) {
67
- const chain = collectChain(callee.object);
68
- if (chain && chain[0] === "scopes" && chain.length > 1) {
69
- out.add(chain.join("."));
70
- walkComputedKeysWithin(callee.object, out);
71
- }
72
- else {
73
- walkScopeReads(callee.object, out);
74
- }
75
- }
76
- else {
77
- walkScopeReads(callee, out);
78
- }
79
- for (const arg of node.arguments) {
80
- walkScopeReads(arg, out);
81
- }
82
- return;
83
- }
84
- // A plain member read: take the chain if it's rooted at `scopes`.
85
- if (node.type === "MemberExpression") {
86
- const chain = collectChain(node);
87
- if (chain && chain[0] === "scopes" && chain.length > 1) {
88
- out.add(chain.join("."));
89
- walkComputedKeysWithin(node, out);
90
- return;
91
- }
92
- // Not a scopes chain (or it hit a computed segment) — recurse for nested reads.
93
- walkScopeReads(node.object, out);
94
- if (node.computed) {
95
- walkScopeReads(node.property, out);
96
- }
97
- return;
98
- }
99
- for (const child of childNodes(node)) {
100
- walkScopeReads(child, out);
101
- }
102
- }
103
- export function extractScopeReads(ast) {
104
- const out = new Set();
105
- walkScopeReads(ast, out);
106
- return [...out];
107
- }
108
- // Free identifiers — names read from the outer scope (globals + injected
109
- // context). Anything bound by a param, destructuring, or a local declaration
110
- // stays internal and is not reported.
111
- function bindPattern(node, scope) {
112
- if (!node)
113
- return;
114
- if (node.type === "Identifier") {
115
- scope.add(node.name);
116
- }
117
- else if (node.type === "ObjectPattern") {
118
- for (const p of node.properties)
119
- bindPattern(p.type === "RestElement" ? p.argument : p.value, scope);
120
- }
121
- else if (node.type === "ArrayPattern") {
122
- for (const el of node.elements)
123
- if (el)
124
- bindPattern(el, scope);
125
- }
126
- else if (node.type === "AssignmentPattern") {
127
- bindPattern(node.left, scope);
128
- }
129
- else if (node.type === "RestElement") {
130
- bindPattern(node.argument, scope);
131
- }
132
- }
133
- function isBound(name, stack) {
134
- for (let i = stack.length - 1; i >= 0; i--)
135
- if (stack[i].has(name))
136
- return true;
137
- return false;
138
- }
139
- function walkFree(node, stack, out) {
140
- if (!node || typeof node !== "object")
141
- return;
142
- switch (node.type) {
143
- case "Identifier":
144
- if (!isBound(node.name, stack))
145
- out.add(node.name);
146
- return;
147
- case "MemberExpression":
148
- walkFree(node.object, stack, out);
149
- if (node.computed)
150
- walkFree(node.property, stack, out);
151
- return;
152
- case "Property":
153
- if (node.computed)
154
- walkFree(node.key, stack, out);
155
- walkFree(node.value, stack, out);
156
- return;
157
- case "VariableDeclarator":
158
- bindPattern(node.id, stack[stack.length - 1]);
159
- walkFree(node.init, stack, out);
160
- return;
161
- case "ArrowFunctionExpression":
162
- case "FunctionExpression": {
163
- const scope = new Set();
164
- if (node.type === "FunctionExpression" && node.id)
165
- scope.add(node.id.name);
166
- for (const p of node.params)
167
- bindPattern(p, scope);
168
- const inner = [...stack, scope];
169
- for (const p of node.params)
170
- walkFree(p, inner, out);
171
- walkFree(node.body, inner, out);
172
- return;
173
- }
174
- case "CatchClause": {
175
- const scope = new Set();
176
- if (node.param)
177
- bindPattern(node.param, scope);
178
- walkFree(node.body, [...stack, scope], out);
179
- return;
180
- }
181
- }
182
- for (const child of childNodes(node))
183
- walkFree(child, stack, out);
184
- }
185
- export function extractFreeIdentifiers(ast) {
186
- const out = new Set();
187
- walkFree(ast, [new Set()], out);
188
- return [...out];
189
- }
@@ -1,4 +0,0 @@
1
- import type * as acorn from "acorn";
2
- /** Runtime guard: does `x` look like an AST node (object with a string `type`)? */
3
- export declare function isNode(x: unknown): x is acorn.AnyNode;
4
- export declare function childNodes(node: acorn.AnyNode): acorn.AnyNode[];
@@ -1,23 +0,0 @@
1
- // Low-level acorn-AST helpers shared by validate.ts and analyze.ts. NOT a parser
2
- // — acorn parses; these are a node type-guard and a child-traversal helper.
3
- /** Runtime guard: does `x` look like an AST node (object with a string `type`)? */
4
- export function isNode(x) {
5
- return (typeof x === "object" &&
6
- x !== null &&
7
- typeof x.type === "string");
8
- }
9
- // Direct child nodes, flattening array-valued fields — for key-agnostic walks.
10
- export function childNodes(node) {
11
- const out = [];
12
- for (const value of Object.values(node)) {
13
- if (Array.isArray(value)) {
14
- for (const child of value)
15
- if (isNode(child))
16
- out.push(child);
17
- }
18
- else if (isNode(value)) {
19
- out.push(value);
20
- }
21
- }
22
- return out;
23
- }
@@ -1,3 +0,0 @@
1
- /** Globals an expression may reference. Every entry is also usable with `new`. */
2
- export declare const ALLOWED_GLOBALS: string[];
3
- export declare const GLOBALS_TO_SHADOW: string[];
@@ -1,111 +0,0 @@
1
- // Which JavaScript globals an expression can see. The engine enforces an
2
- // allowlist (safer-eval.ts): a free identifier that is neither an allowed global
3
- // nor an injected context name is rejected before the expression runs.
4
- // ALLOWED_GLOBALS is that list — also handed to SaferEval as allowGlobals and
5
- // shown to the LLM in the prompt. GLOBALS_TO_SHADOW are bound to undefined as a
6
- // second line of defence, so an allowed-by-mistake reference still stays inert.
7
- /** Globals an expression may reference. Every entry is also usable with `new`. */
8
- export const ALLOWED_GLOBALS = [
9
- // Namespaces / utilities
10
- "Math",
11
- "JSON",
12
- "Intl",
13
- "Object",
14
- "Array",
15
- // Coercion (use as calls — `new Number(x)` etc. boxes the value)
16
- "Number",
17
- "String",
18
- "Boolean",
19
- "parseInt",
20
- "parseFloat",
21
- // Predicates / constants
22
- "isNaN",
23
- "isFinite",
24
- "undefined",
25
- "NaN",
26
- "Infinity",
27
- // URI helpers
28
- "encodeURIComponent",
29
- "decodeURIComponent",
30
- "encodeURI",
31
- "decodeURI",
32
- // Constructors & async
33
- "Date",
34
- "Map",
35
- "Set",
36
- "RegExp",
37
- "URL",
38
- "URLSearchParams",
39
- "Promise",
40
- "BigInt",
41
- // Error constructors
42
- "Error",
43
- "TypeError",
44
- "RangeError",
45
- "SyntaxError",
46
- "ReferenceError",
47
- "EvalError",
48
- "URIError",
49
- "AggregateError",
50
- ];
51
- // Capability globals shadowed to undefined. (`eval` / `arguments` can't be param
52
- // names in strict mode — blocked in validate.ts instead.)
53
- export const GLOBALS_TO_SHADOW = [
54
- // Global objects
55
- "globalThis",
56
- "self",
57
- "window",
58
- "global",
59
- "document",
60
- "navigator",
61
- "location",
62
- "history",
63
- "localStorage",
64
- "sessionStorage",
65
- "indexedDB",
66
- // Network / IO
67
- "fetch",
68
- "XMLHttpRequest",
69
- "WebSocket",
70
- "EventSource",
71
- "Worker",
72
- "SharedWorker",
73
- "ServiceWorker",
74
- "importScripts",
75
- // Fire a network request via `.src` — an exfiltration channel even with fetch
76
- // shadowed.
77
- "Image",
78
- "Audio",
79
- // Code execution
80
- "Function",
81
- "WebAssembly", // runs arbitrary code
82
- "setTimeout",
83
- "setInterval",
84
- "setImmediate",
85
- "requestAnimationFrame",
86
- "requestIdleCallback",
87
- "queueMicrotask",
88
- // Process / non-browser runtimes
89
- "process",
90
- "require",
91
- "module",
92
- "exports",
93
- "__dirname",
94
- "__filename",
95
- "Buffer",
96
- "Deno", // runtime god-object (fs/net/env) outside a browser
97
- "Bun",
98
- // DOM
99
- "alert",
100
- "confirm",
101
- "prompt",
102
- "close",
103
- "open",
104
- "print",
105
- "postMessage",
106
- // Constructors that can escape
107
- "Proxy",
108
- "Reflect",
109
- "SharedArrayBuffer",
110
- "Atomics",
111
- ];
@@ -1,21 +0,0 @@
1
- import { SaferEvalError } from "./validate.js";
2
- export { SaferEvalError };
3
- export declare class SaferEval {
4
- #private;
5
- constructor(options?: {
6
- maxCacheSize?: number;
7
- extraGlobalsToShadow?: string[];
8
- allowGlobals?: string[];
9
- enforceAllowlist?: boolean;
10
- });
11
- validate(expression: string): {
12
- isAsync: boolean;
13
- scopeReads: string[];
14
- freeIds: string[];
15
- };
16
- compile(expression: string): (context: Record<string, unknown>) => unknown;
17
- isAsync(expression: string): boolean;
18
- scopeReads(expression: string): string[];
19
- eval(expression: string, context?: Record<string, unknown>, allowedGlobals?: string[]): unknown;
20
- clearCache(): void;
21
- }
@@ -1,170 +0,0 @@
1
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
- if (kind === "m") throw new TypeError("Private method is not writable");
3
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
- };
7
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
- };
12
- var _SaferEval_instances, _SaferEval_cache, _SaferEval_maxCacheSize, _SaferEval_shadowParams, _SaferEval_allowGlobals, _SaferEval_enforceAllowlist, _SaferEval_checkAllowlist;
13
- import * as acorn from "acorn";
14
- import { containsAwait, extractFreeIdentifiers, extractScopeReads, } from "./analyze.js";
15
- import { GLOBALS_TO_SHADOW } from "./globals.js";
16
- import { SaferEvalError, validateNode } from "./validate.js";
17
- // Compiles and runs the expressions validate.ts approves: parse + analyse once
18
- // (cached), compile to a Function (AsyncFunction when the expr uses `await`),
19
- // shadow ambient globals as undefined params, run in-realm so Proxy state works.
20
- // Guardrail, not a sandbox — see the Expressions docs page, § Safety.
21
- // Re-exported from validate.ts for callers of this module.
22
- export { SaferEvalError };
23
- // The AsyncFunction constructor, for expressions that use `await`.
24
- const AsyncFunction = Object.getPrototypeOf(async () => { })
25
- .constructor;
26
- export class SaferEval {
27
- constructor(options = {}) {
28
- _SaferEval_instances.add(this);
29
- _SaferEval_cache.set(this, new Map());
30
- _SaferEval_maxCacheSize.set(this, void 0);
31
- _SaferEval_shadowParams.set(this, void 0);
32
- _SaferEval_allowGlobals.set(this, void 0);
33
- _SaferEval_enforceAllowlist.set(this, void 0);
34
- const { maxCacheSize = 500, extraGlobalsToShadow = [], allowGlobals = [], enforceAllowlist = false, } = options;
35
- __classPrivateFieldSet(this, _SaferEval_maxCacheSize, maxCacheSize, "f");
36
- __classPrivateFieldSet(this, _SaferEval_enforceAllowlist, enforceAllowlist, "f");
37
- const allowSet = new Set(allowGlobals);
38
- __classPrivateFieldSet(this, _SaferEval_allowGlobals, allowSet, "f");
39
- __classPrivateFieldSet(this, _SaferEval_shadowParams, [...GLOBALS_TO_SHADOW, ...extraGlobalsToShadow].filter((g) => !allowSet.has(g)), "f");
40
- }
41
- // Parse + validate without running. Throws SaferEvalError if invalid; returns
42
- // isAsync + scopeReads and caches the analysis (compile() builds the fn later).
43
- validate(expression) {
44
- if (typeof expression !== "string") {
45
- throw new SaferEvalError("Expression must be a string", "expression-syntax");
46
- }
47
- const trimmed = expression.trim();
48
- if (!trimmed) {
49
- throw new SaferEvalError("Expression cannot be empty", "expression-syntax");
50
- }
51
- const cached = __classPrivateFieldGet(this, _SaferEval_cache, "f").get(expression);
52
- if (cached) {
53
- return {
54
- isAsync: cached.isAsync,
55
- scopeReads: cached.scopeReads,
56
- freeIds: cached.freeIds,
57
- };
58
- }
59
- // Parse as `"use strict"; void (expr)`: void(...) forces expression context
60
- // (so `{...}` is an object literal, not a block), and strict mode rejects
61
- // sloppy-only constructs (octal, `with`, dup params) here, not at compile.
62
- const wrapper = `"use strict"; void (${trimmed})`;
63
- let ast;
64
- try {
65
- ast = acorn.parse(wrapper, {
66
- ecmaVersion: 2022,
67
- sourceType: "script",
68
- allowAwaitOutsideFunction: true,
69
- });
70
- }
71
- catch (e) {
72
- throw new SaferEvalError(`Syntax error: ${e instanceof Error ? e.message : String(e)}. Expression: ${expression}`, "expression-syntax");
73
- }
74
- validateNode(ast);
75
- const analysis = {
76
- isAsync: containsAwait(ast),
77
- scopeReads: extractScopeReads(ast),
78
- freeIds: extractFreeIdentifiers(ast),
79
- };
80
- // Cache the analysis (no fn yet); evict oldest when full.
81
- if (__classPrivateFieldGet(this, _SaferEval_cache, "f").size >= __classPrivateFieldGet(this, _SaferEval_maxCacheSize, "f")) {
82
- const firstKey = __classPrivateFieldGet(this, _SaferEval_cache, "f").keys().next().value;
83
- if (firstKey !== undefined)
84
- __classPrivateFieldGet(this, _SaferEval_cache, "f").delete(firstKey);
85
- }
86
- __classPrivateFieldGet(this, _SaferEval_cache, "f").set(expression, Object.assign({}, analysis));
87
- return analysis;
88
- }
89
- // Compile to a reusable fn (returns a Promise if the expr uses `await`).
90
- compile(expression) {
91
- const { isAsync } = this.validate(expression);
92
- const entry = __classPrivateFieldGet(this, _SaferEval_cache, "f").get(expression);
93
- if (!entry) {
94
- // Unreachable — validate() just inserted this entry.
95
- throw new SaferEvalError(`Cache entry missing for expression: ${expression}`);
96
- }
97
- if (entry.fn)
98
- return entry.fn;
99
- const expr = expression.trim();
100
- const Ctor = isAsync ? AsyncFunction : Function;
101
- // Single-slot cache: new Ctor bakes in the param names (context keys ∪ shadow
102
- // params), so it recompiles only if the context shape changes — which it
103
- // doesn't in practice (a given expr always sees the same keys).
104
- let cachedSig;
105
- let cachedFn;
106
- const evaluator = (context) => {
107
- const contextKeys = Object.keys(context);
108
- const sig = contextKeys.join("\u0000");
109
- if (sig !== cachedSig) {
110
- // Context keys become params; shadow params follow, bound to undefined.
111
- const allParams = [...contextKeys, ...__classPrivateFieldGet(this, _SaferEval_shadowParams, "f")];
112
- try {
113
- cachedFn = new Ctor(...allParams, `"use strict"; return (${expr})`);
114
- }
115
- catch (e) {
116
- // Parsed but won't compile — surface as SaferEvalError, not a raw one.
117
- throw new SaferEvalError(`Failed to compile expression: ${e instanceof Error ? e.message : String(e)}. Expression: ${expression}`, "expression-syntax");
118
- }
119
- cachedSig = sig;
120
- }
121
- // Args match allParams: context values (Object.keys order) then shadow fills.
122
- const allArgs = [
123
- ...Object.values(context),
124
- ...new Array(__classPrivateFieldGet(this, _SaferEval_shadowParams, "f").length).fill(undefined),
125
- ];
126
- return cachedFn(...allArgs);
127
- };
128
- entry.fn = evaluator;
129
- return evaluator;
130
- }
131
- isAsync(expression) {
132
- const cached = __classPrivateFieldGet(this, _SaferEval_cache, "f").get(expression);
133
- if (cached)
134
- return cached.isAsync;
135
- return this.validate(expression).isAsync;
136
- }
137
- // Every `scopes.X.Y` path the expression reads — the renderer auto-subscribes
138
- // to these instead of the LLM writing a deps array.
139
- scopeReads(expression) {
140
- const cached = __classPrivateFieldGet(this, _SaferEval_cache, "f").get(expression);
141
- if (cached)
142
- return cached.scopeReads;
143
- return this.validate(expression).scopeReads;
144
- }
145
- eval(expression, context = {}, allowedGlobals = []) {
146
- if (__classPrivateFieldGet(this, _SaferEval_enforceAllowlist, "f")) {
147
- const { freeIds } = this.validate(expression);
148
- __classPrivateFieldGet(this, _SaferEval_instances, "m", _SaferEval_checkAllowlist).call(this, freeIds, Object.keys(context), allowedGlobals);
149
- }
150
- const evaluator = this.compile(expression);
151
- return evaluator(context);
152
- }
153
- clearCache() {
154
- __classPrivateFieldGet(this, _SaferEval_cache, "f").clear();
155
- }
156
- }
157
- _SaferEval_cache = new WeakMap(), _SaferEval_maxCacheSize = new WeakMap(), _SaferEval_shadowParams = new WeakMap(), _SaferEval_allowGlobals = new WeakMap(), _SaferEval_enforceAllowlist = new WeakMap(), _SaferEval_instances = new WeakSet(), _SaferEval_checkAllowlist = function _SaferEval_checkAllowlist(freeIds, contextKeys, allowedGlobals) {
158
- const allowed = new Set([
159
- ...contextKeys,
160
- ...__classPrivateFieldGet(this, _SaferEval_allowGlobals, "f"),
161
- ...allowedGlobals,
162
- ]);
163
- for (const id of freeIds) {
164
- if (!allowed.has(id)) {
165
- // The expression named something that doesn't exist here — an
166
- // unregistered function or a global outside the allowlist.
167
- throw new SaferEvalError(`"${id}" is not available in expressions`, "unknown-reference");
168
- }
169
- }
170
- };
@@ -1,7 +0,0 @@
1
- import type * as acorn from "acorn";
2
- export type SaferEvalErrorReason = "expression-syntax" | "sandbox-violation" | "unknown-reference";
3
- export declare class SaferEvalError extends Error {
4
- readonly reason: SaferEvalErrorReason;
5
- constructor(message: string, reason?: SaferEvalErrorReason);
6
- }
7
- export declare function validateNode(node: acorn.AnyNode | null | undefined, insideFunctionBody?: boolean): void;
@@ -1,109 +0,0 @@
1
- import { isNode } from "./ast-utils.js";
2
- export class SaferEvalError extends Error {
3
- constructor(message, reason = "sandbox-violation") {
4
- super(message);
5
- this.name = "SaferEvalError";
6
- this.reason = reason;
7
- }
8
- }
9
- // `eval` / `arguments` can't be shadowed as strict-mode params, so reject them as
10
- // references here instead (the rest are shadowed — see GLOBALS_TO_SHADOW).
11
- const FORBIDDEN_IDENTIFIERS = new Set(["eval", "arguments"]);
12
- // Most dangerous syntax never reaches this walk: every expression is parsed as
13
- // `"use strict"; void (EXPR)`, so a top-level statement / declaration / `with` /
14
- // octal / import is a syntax error. What survives, to reject by hand, is below.
15
- // Forbidden everywhere, even in a function body: dynamic `import()` loads code,
16
- // and `import.meta` / `new.target` (MetaProperty) reach host / constructor
17
- // internals. Keywords, so shadowing can't touch them — block structurally.
18
- const FORBIDDEN_EXPRESSIONS = new Set(["ImportExpression", "MetaProperty"]);
19
- // Forbidden as the whole expression (which must be one pure value); fine inside a
20
- // callback body where imperative code is allowed.
21
- const BODY_ONLY_EXPRESSIONS = new Set([
22
- "AssignmentExpression", // x = 1, x += 1
23
- "UpdateExpression", // x++, --x
24
- "SequenceExpression", // a, b — comma operator
25
- ]);
26
- // Escape-hatch property names: `.constructor` reaches the Function constructor,
27
- // `.__proto__` the prototype chain. Only static keys are caught; a computed key
28
- // escapes (the guardrail gap — banning it would ban all `arr[i]` access).
29
- const FORBIDDEN_PROPERTIES = new Set([
30
- "constructor",
31
- "__proto__",
32
- "prototype",
33
- "__defineGetter__",
34
- "__defineSetter__",
35
- "__lookupGetter__",
36
- "__lookupSetter__",
37
- ]);
38
- // Recursively walk the parsed expression, throwing on anything disallowed.
39
- // insideFunctionBody flips once inside an arrow / function body, where the
40
- // BODY_ONLY_EXPRESSIONS become legal. Entry point is acorn's Program node.
41
- export function validateNode(node, insideFunctionBody = false) {
42
- if (!node || typeof node !== "object")
43
- return;
44
- // The wrapped program must hold exactly one expression (the filter drops the
45
- // "use strict" directive). More than one means the expr smuggled a `)` to break
46
- // out of `void (...)` — e.g. `1); evil(); (2`.
47
- if (node.type === "Program") {
48
- const statements = node.body.filter((s) => !(s.type === "ExpressionStatement" && s.directive));
49
- if (statements.length !== 1) {
50
- throw new SaferEvalError(`Expression must be a single expression, got ${statements.length} statements`);
51
- }
52
- validateNode(statements[0]);
53
- return;
54
- }
55
- // Dynamic import / meta-properties: never allowed, not even in a body.
56
- if (FORBIDDEN_EXPRESSIONS.has(node.type)) {
57
- throw new SaferEvalError(`"${node.type}" is not allowed in expressions`);
58
- }
59
- // Assignment / update / comma: allowed in a callback body, not as the whole expr.
60
- if (BODY_ONLY_EXPRESSIONS.has(node.type) && !insideFunctionBody) {
61
- throw new SaferEvalError(`"${node.type}" is only allowed inside a function body, not as the whole expression`);
62
- }
63
- // Tagged templates invoke an arbitrary tag function.
64
- if (node.type === "TaggedTemplateExpression") {
65
- throw new SaferEvalError("Tagged template literals are not allowed");
66
- }
67
- // Static access to escape-hatch properties; dynamic keys slip past.
68
- if (node.type === "MemberExpression") {
69
- const { property, computed } = node;
70
- const staticKey = !computed && property.type === "Identifier"
71
- ? property.name
72
- : computed &&
73
- property.type === "Literal" &&
74
- typeof property.value === "string"
75
- ? property.value
76
- : null;
77
- if (staticKey !== null && FORBIDDEN_PROPERTIES.has(staticKey)) {
78
- throw new SaferEvalError(`Access to "${staticKey}" is not allowed`);
79
- }
80
- }
81
- // `eval` / `arguments` in reference position (the recursion below skips a
82
- // non-computed member's `.property`, so a harmless `row.arguments` isn't hit).
83
- if (node.type === "Identifier" && FORBIDDEN_IDENTIFIERS.has(node.name)) {
84
- throw new SaferEvalError(`Access to "${node.name}" is not allowed`);
85
- }
86
- // Recurse. Two carries: descending into a function `body` sets
87
- // insideFunctionBody for the subtree; a non-computed member's `.property` is a
88
- // name already checked, so skip it (a computed `obj[eval]` is not skipped).
89
- const fields = node;
90
- const isFunction = node.type === "ArrowFunctionExpression" ||
91
- node.type === "FunctionExpression";
92
- for (const key of Object.keys(node)) {
93
- if (key === "type" || key === "start" || key === "end")
94
- continue;
95
- if (node.type === "MemberExpression" && !node.computed && key === "property")
96
- continue;
97
- const child = fields[key];
98
- const childInside = insideFunctionBody || (isFunction && key === "body");
99
- if (Array.isArray(child)) {
100
- for (const c of child) {
101
- if (isNode(c))
102
- validateNode(c, childInside);
103
- }
104
- }
105
- else if (isNode(child)) {
106
- validateNode(child, childInside);
107
- }
108
- }
109
- }