openzoo 0.50.99 → 0.51.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.
Files changed (39) hide show
  1. package/bin/openzoo.js +33 -0
  2. package/lib/cursorbackend.js +6 -1
  3. package/lib/cursorbackend.js.bak-hiddenbots +4072 -0
  4. package/lib/models.js +6 -1
  5. package/package.json +7 -4
  6. package/transmute/README.md +352 -0
  7. package/transmute/bin/openzoo-transmute.js +7 -0
  8. package/transmute/docs/OPENZOO_CLI_PATCH.md +139 -0
  9. package/transmute/docs/VERCEL_TO_SOLANA.md +381 -0
  10. package/transmute/lib/build.js +311 -0
  11. package/transmute/lib/cli.js +231 -0
  12. package/transmute/lib/compile/index.js +146 -0
  13. package/transmute/lib/compile/ir.js +1634 -0
  14. package/transmute/lib/compile/parse.js +753 -0
  15. package/transmute/lib/compile/rust.js +777 -0
  16. package/transmute/lib/deploy.js +209 -0
  17. package/transmute/lib/eligibility.js +211 -0
  18. package/transmute/lib/gateway.js +704 -0
  19. package/transmute/lib/hub.js +249 -0
  20. package/transmute/lib/solana.js +339 -0
  21. package/transmute/lib/vercel.js +296 -0
  22. package/transmute/lib/wallet.js +41 -0
  23. package/transmute/lib/wire.js +167 -0
  24. package/transmute/runtime/zoo-host/Cargo.lock +588 -0
  25. package/transmute/runtime/zoo-host/Cargo.toml +28 -0
  26. package/transmute/runtime/zoo-host/src/assets.rs +170 -0
  27. package/transmute/runtime/zoo-host/src/ctx.rs +322 -0
  28. package/transmute/runtime/zoo-host/src/json.rs +257 -0
  29. package/transmute/runtime/zoo-host/src/kv.rs +182 -0
  30. package/transmute/runtime/zoo-host/src/lib.rs +123 -0
  31. package/transmute/runtime/zoo-host/src/val.rs +1044 -0
  32. package/transmute/runtime/zoo-host/src/wire.rs +239 -0
  33. package/transmute/site/.env.production +2 -0
  34. package/transmute/site/api/echo.js +10 -0
  35. package/transmute/site/api/hello.js +12 -0
  36. package/transmute/site/api/hits.js +13 -0
  37. package/transmute/site/api/time.js +5 -0
  38. package/transmute/site/public/app.js +16 -0
  39. package/transmute/site/public/index.html +62 -0
@@ -0,0 +1,1634 @@
1
+ // JS AST → IR.
2
+ //
3
+ // The IR is a small tree of Rust-shaped nodes: every expression yields an
4
+ // owned `Val`, statements are Rust statements, and the "specials" of the
5
+ // bridge (`req`, `res`, `request`, `params`, `kv`, `process.env`, ...) are
6
+ // resolved here into host calls so `rust.js` is a plain printer.
7
+ //
8
+ // Lowering is where eligibility is decided: anything outside the subset
9
+ // throws `Ineligible` with the offending `file:line`.
10
+ import {
11
+ Ineligible, BANNED_GLOBALS, KNOWN_GLOBALS, RUNTIME_METHODS, CALLBACK_METHODS, MUTATING_METHODS, MATH_FUNCS, MATH_CONSTS,
12
+ GLOBAL_FUNCS, KV_METHODS, bindImport,
13
+ } from '../eligibility.js';
14
+ import { HTTP_METHODS, walk, patternNames, freeVars, isFunctionNode, resolveHandlerNode } from './parse.js';
15
+
16
+ // ---------------------------------------------------------------- IR constructors
17
+
18
+ export const IR = {
19
+ undef: () => ({ k: 'undef' }),
20
+ null: () => ({ k: 'null' }),
21
+ bool: (v) => ({ k: 'bool', v }),
22
+ num: (v) => ({ k: 'num', v }),
23
+ str: (v) => ({ k: 'str', v }),
24
+ raw: (code) => ({ k: 'raw', code }),
25
+ var: (name) => ({ k: 'var', name }),
26
+ template: (parts) => ({ k: 'template', parts }),
27
+ obj: (props) => ({ k: 'obj', props }),
28
+ arr: (items) => ({ k: 'arr', items }),
29
+ get: (obj, key) => ({ k: 'get', obj, key }),
30
+ getc: (obj, key) => ({ k: 'getc', obj, key }),
31
+ chain: (label, e) => ({ k: 'chain', label, e }),
32
+ optcheck: (label, e) => ({ k: 'optcheck', label, e }),
33
+ bin: (op, l, r) => ({ k: 'bin', op, l, r }),
34
+ cmp: (op, l, r) => ({ k: 'cmp', op, l, r }),
35
+ logical: (op, l, r) => ({ k: 'logical', op, l, r }),
36
+ cond: (test, then, els) => ({ k: 'cond', test, then, else: els }),
37
+ unary: (op, e) => ({ k: 'unary', op, e }),
38
+ in: (key, obj) => ({ k: 'in', key, obj }),
39
+ delete: (lv) => ({ k: 'delete', lv }),
40
+ assign: (lv, value, op = null) => ({ k: 'assign', lv, value, op }),
41
+ update: (lv, delta, prefix) => ({ k: 'update', lv, delta, prefix }),
42
+ seq: (exprs) => ({ k: 'seq', exprs }),
43
+ callm: (recv, lv, name, args, cb = null) => ({ k: 'callm', recv, lv, name, args, cb }),
44
+ host: (fn, args = [], { q = false, void: v = false } = {}) => ({ k: 'host', fn, args, q, void: v }),
45
+ pre: (stmts, e) => (stmts.length ? { k: 'pre', stmts, e } : e),
46
+ math: (name, args) => ({ k: 'math', name, args }),
47
+ global: (name, args) => ({ k: 'global', name, args }),
48
+ jsonparse: (e) => ({ k: 'jsonparse', e }),
49
+ jsonstringify: (e) => ({ k: 'jsonstringify', e }),
50
+ keys: (e) => ({ k: 'keys', e }),
51
+ values: (e) => ({ k: 'values', e }),
52
+ entries: (e) => ({ k: 'entries', e }),
53
+ isarray: (e) => ({ k: 'isarray', e }),
54
+ newerror: (name, args) => ({ k: 'newerror', name, args }),
55
+ log: (args) => ({ k: 'log', args }),
56
+ helper: (name, args) => ({ k: 'helper', name, args }),
57
+ resp: (kind, body, init) => ({ k: 'resp', kind, body, init }),
58
+ params: (fn, paramsConst, name = null, catchAll = false) => ({ k: 'params', fn, paramsConst, name, catchAll }),
59
+ callh: (name, args) => ({ k: 'callh', name, args }),
60
+ constref: (name) => ({ k: 'constref', name }),
61
+ iife: (fn, args) => ({ k: 'iife', fn, args }),
62
+ // conditions
63
+ truthy: (e) => (e.k === 'cmp' ? { k: 'c-cmp', op: e.op, l: e.l, r: e.r } : { k: 'c-truthy', e }),
64
+ cnot: (c) => ({ k: 'c-not', c }),
65
+ cand: (l, r) => ({ k: 'c-and', l, r }),
66
+ cor: (l, r) => ({ k: 'c-or', l, r }),
67
+ cnullish: (e) => ({ k: 'c-nullish', e }),
68
+ // statements
69
+ let: (name, init = null) => ({ k: 'let', name, init }),
70
+ hoistvar: (name) => ({ k: 'hoistvar', name }),
71
+ expr: (e) => ({ k: 'expr', e }),
72
+ block: (body) => ({ k: 'block', body }),
73
+ if: (test, then, els = null) => ({ k: 'if', test, then, else: els }),
74
+ while: (label, test, body) => ({ k: 'while', label, test, body }),
75
+ dowhile: (label, test, body) => ({ k: 'dowhile', label, test, body }),
76
+ for: (label, init, test, update, body) => ({ k: 'for', label, init, test, update, body }),
77
+ forof: (label, tmp, iter, body) => ({ k: 'forof', label, tmp, iter, body }),
78
+ forin: (label, tmp, iter, body) => ({ k: 'forin', label, tmp, iter, body }),
79
+ break: (label) => ({ k: 'break', label }),
80
+ continue: (label) => ({ k: 'continue', label }),
81
+ return: (mode, inTry, value = null) => ({ k: 'return', mode, inTry, value }),
82
+ throw: (e) => ({ k: 'throw', e }),
83
+ try: (body, param, handler, finalizer, mode, inTry) => ({ k: 'try', body, param, handler, finalizer, mode, inTry }),
84
+ switch: (label, disc, cases) => ({ k: 'switch', label, disc, cases }),
85
+ letfn: (name, fn, captures) => ({ k: 'letfn', name, fn, captures }),
86
+ fn: (params, body) => ({ params, body }),
87
+ };
88
+
89
+ const SP = (t, extra = {}) => ({ t, ...extra });
90
+
91
+ // ---------------------------------------------------------------- naming
92
+
93
+ export function sanitizeIdent(name) {
94
+ return String(name).replace(/[^A-Za-z0-9_]/g, (c) => (c === '$' ? '_S_' : '_u' + c.codePointAt(0).toString(16) + '_'));
95
+ }
96
+
97
+ // ---------------------------------------------------------------- scopes
98
+
99
+ class Scope {
100
+ constructor(parent, { boundary = null, fn = null } = {}) {
101
+ this.parent = parent;
102
+ this.vars = new Map();
103
+ this.boundary = boundary; // null | 'callback' | 'closure' | 'try' | 'fn'
104
+ this.fn = fn || parent?.fn || null;
105
+ }
106
+ lookup(name) {
107
+ const boundaries = [];
108
+ for (let s = this; s; s = s.parent) {
109
+ if (s.vars.has(name)) return { binding: s.vars.get(name), scope: s, boundaries };
110
+ if (s.boundary) boundaries.push(s.boundary);
111
+ }
112
+ return null;
113
+ }
114
+ declare(name, binding) {
115
+ this.vars.set(name, binding);
116
+ return binding;
117
+ }
118
+ }
119
+
120
+ /** Per-function lowering state. */
121
+ class FnCtx {
122
+ constructor(retMode) {
123
+ this.retMode = retMode; // 'node' | 'web' | 'value'
124
+ this.tryDepth = 0;
125
+ this.loops = []; // { label, isLoop, jsLabel } — 'marker' entries block break/continue across closures
126
+ this.hoisted = new Set();
127
+ }
128
+ }
129
+
130
+ // ---------------------------------------------------------------- the lowerer
131
+
132
+ export class Lowerer {
133
+ constructor(mod, fn, opts = {}) {
134
+ this.mod = mod;
135
+ this.file = opts.file || mod.file;
136
+ this.routeIndex = opts.index ?? 0;
137
+ this.fnMeta = fn;
138
+ this.params = (fn.params || []).map((p) => ({ name: p, catchAll: routeParamIsCatchAll(fn.routePath, p) }));
139
+ this.paramsConst = `PARAMS_${this.routeIndex}`;
140
+ this.tmp = 0;
141
+ this.labelN = 0;
142
+ this.chainN = 0;
143
+ this.usesKv = false;
144
+ this.env = new Set();
145
+ this.envDynamic = false;
146
+ this.warnings = [];
147
+ this.helpers = []; // { name, fn }
148
+ this.consts = []; // { name, e }
149
+ this.moduleScope = new Scope(null, { boundary: 'fn' });
150
+ this.moduleScope.fn = new FnCtx('value');
151
+ this.nameCounts = new Map();
152
+ this.pendingHelpers = [];
153
+ this.setupModuleScope();
154
+ }
155
+
156
+ // ---- errors
157
+
158
+ fail(reason, node) {
159
+ throw new Ineligible(reason, { file: this.file, node });
160
+ }
161
+ warn(msg, node) {
162
+ this.warnings.push(`${msg} (${this.file}${node?.loc ? ':' + node.loc.start.line : ''})`);
163
+ }
164
+ newTmp(prefix = '__t') {
165
+ return `${prefix}${++this.tmp}`;
166
+ }
167
+ uniqueName(prefix, base) {
168
+ const key = `${prefix}${sanitizeIdent(base)}`;
169
+ const n = (this.nameCounts.get(key) || 0) + 1;
170
+ this.nameCounts.set(key, n);
171
+ return n === 1 ? key : `${key}_${n}`;
172
+ }
173
+
174
+ // ---- module scope
175
+
176
+ setupModuleScope() {
177
+ const ms = this.moduleScope;
178
+ for (const imp of this.mod.imports) {
179
+ const bound = bindImport(imp.source, imp.specifiers, { file: this.file, line: imp.line });
180
+ for (const b of bound) {
181
+ if (b.special) ms.declare(b.local, { kind: 'special', sp: b.special });
182
+ else ms.declare(b.local, { kind: 'type-only', source: imp.source });
183
+ }
184
+ }
185
+ for (const [name, d] of this.mod.decls) {
186
+ if (ms.vars.has(name)) continue;
187
+ if (d.kind === 'class') { ms.declare(name, { kind: 'class', node: d.node }); continue; }
188
+ if (d.kind === 'function' || (d.init && isFunctionNode(d.init))) {
189
+ ms.declare(name, { kind: 'helper', rust: this.uniqueName(`h_${this.routeIndex}_`, name), node: d.kind === 'function' ? d.node : d.init, lowered: false, name });
190
+ continue;
191
+ }
192
+ ms.declare(name, { kind: 'modconst', decl: d, name, state: 'pending' });
193
+ }
194
+ }
195
+
196
+ /** Resolve a module-scope binding on first use (lazy: only referenced helpers are emitted). */
197
+ resolveModule(binding, node) {
198
+ if (binding.kind === 'helper') {
199
+ if (!binding.lowered) {
200
+ binding.lowered = true;
201
+ const fnIr = this.lowerFunctionBody(binding.node, this.moduleScope, 'value', { name: binding.name });
202
+ this.helpers.push({ name: binding.rust, fn: fnIr });
203
+ }
204
+ return binding;
205
+ }
206
+ if (binding.kind === 'modconst') {
207
+ const d = binding.decl;
208
+ if (binding.state === 'lowering') this.fail(`module-level \`${binding.name}\` depends on itself`, node);
209
+ if (binding.state === 'done') return binding.resolved;
210
+ if (d.reassigned || d.kind !== 'const') {
211
+ if (d.reassigned) this.fail(`module-level \`${binding.name}\` is reassigned: module state does not persist between invocations on chain; use @vercel/kv`, d.node);
212
+ }
213
+ if (d.pattern) this.fail(`destructuring at module scope (\`${binding.name}\`) is not supported; destructure inside the handler`, d.node);
214
+ if (!d.init) this.fail(`module-level \`${binding.name}\` has no initializer`, d.node);
215
+ binding.state = 'lowering';
216
+ const saved = this.cur;
217
+ this.cur = { scope: this.moduleScope, fn: this.moduleScope.fn, stmts: [] };
218
+ let r;
219
+ try {
220
+ r = this.lowerExpr(d.init);
221
+ } finally {
222
+ this.cur = saved;
223
+ }
224
+ let resolved;
225
+ if (r.special) {
226
+ if (r.pre?.length) this.fail(`module-level \`${binding.name}\`: response calls at module scope are not allowed`, d.node);
227
+ resolved = { kind: 'special', sp: r.special };
228
+ } else {
229
+ const rust = this.uniqueName(`c_${this.routeIndex}_`, binding.name);
230
+ this.consts.push({ name: rust, e: r.ir });
231
+ resolved = { kind: 'const', rust };
232
+ }
233
+ binding.state = 'done';
234
+ binding.resolved = resolved;
235
+ return resolved;
236
+ }
237
+ return binding;
238
+ }
239
+
240
+ // ---- entry points
241
+
242
+ /** Lower a handler function node into an IR function. */
243
+ lowerHandler(fnNode, style) {
244
+ const retMode = style === 'node' ? 'node' : 'web';
245
+ const specials = style === 'node' ? [SP('Req'), SP('Res')] : [SP('WebReq'), SP('ParamsCtx')];
246
+ return this.lowerFunctionBody(fnNode, this.moduleScope, retMode, { paramSpecials: specials, name: 'handler' });
247
+ }
248
+
249
+ /**
250
+ * Lower a function (handler, module helper, callback, local closure).
251
+ * `paramSpecials` binds leading params to bridge specials instead of `__args`.
252
+ */
253
+ lowerFunctionBody(fnNode, parentScope, retMode, { paramSpecials = null, boundary = 'fn', name = null, captures = null } = {}) {
254
+ if (fnNode.generator) this.fail('generator functions are not supported', fnNode);
255
+ const fctx = new FnCtx(retMode);
256
+ const scope = new Scope(parentScope, { boundary, fn: fctx });
257
+ const saved = this.cur;
258
+ const paramStmts = [];
259
+ const bodyStmts = [];
260
+ this.cur = { scope, fn: fctx, stmts: paramStmts };
261
+ try {
262
+ if (captures) for (const [jsName, b] of captures) scope.declare(jsName, b);
263
+ fnNode.params.forEach((p, i) => {
264
+ let pattern = p;
265
+ let def = null;
266
+ if (pattern.type === 'AssignmentPattern') { def = pattern.right; pattern = pattern.left; }
267
+ if (pattern.type === 'RestElement') {
268
+ if (paramSpecials) this.fail('rest parameters are not supported on handlers', p);
269
+ this.bindPattern(pattern.argument, { ir: IR.helper('slice_from', [IR.raw('Val::Arr(__args.to_vec())'), IR.num(i)]) }, true, p);
270
+ return;
271
+ }
272
+ if (paramSpecials && i < paramSpecials.length) {
273
+ this.bindPattern(pattern, { special: paramSpecials[i] }, true, p);
274
+ return;
275
+ }
276
+ if (paramSpecials) {
277
+ // extra handler params (e.g. `next`) are undefined
278
+ this.bindPattern(pattern, { ir: IR.undef() }, true, p);
279
+ return;
280
+ }
281
+ let src = IR.raw(`__args.get(${i}).cloned().unwrap_or(Val::Undef)`);
282
+ if (def) src = this.withDefault(src, def);
283
+ this.bindPattern(pattern, { ir: src }, true, p);
284
+ });
285
+ this.cur.stmts = bodyStmts;
286
+ if (fnNode.body.type === 'BlockStatement') {
287
+ this.hoistVars(fnNode.body);
288
+ this.lowerStatements(fnNode.body.body);
289
+ } else {
290
+ // expression-bodied arrow
291
+ const r = this.lowerExpr(fnNode.body);
292
+ this.emitReturn(r, fnNode.body);
293
+ }
294
+ } finally {
295
+ this.cur = saved;
296
+ }
297
+ return { params: paramStmts, body: bodyStmts, name };
298
+ }
299
+
300
+ // ---- var hoisting
301
+
302
+ hoistVars(block) {
303
+ const names = [];
304
+ const visit = (n) => {
305
+ if (isFunctionNode(n)) return false;
306
+ if (n.type === 'VariableDeclaration' && n.kind === 'var') for (const d of n.declarations) names.push(...patternNames(d.id));
307
+ return true;
308
+ };
309
+ walk(block, visit);
310
+ for (const nm of names) {
311
+ if (this.cur.scope.vars.has(nm)) continue;
312
+ const rust = `v_${sanitizeIdent(nm)}`;
313
+ this.cur.scope.declare(nm, { kind: 'local', rust, hoisted: true });
314
+ this.cur.fn.hoisted.add(nm);
315
+ this.cur.stmts.push(IR.hoistvar(rust));
316
+ }
317
+ }
318
+
319
+ // ---- statements
320
+
321
+ emit(stmt) {
322
+ this.cur.stmts.push(stmt);
323
+ }
324
+ withScope(fnBody, { boundary = null } = {}) {
325
+ const saved = this.cur;
326
+ const stmts = [];
327
+ this.cur = { scope: new Scope(saved.scope, { boundary }), fn: saved.fn, stmts };
328
+ try { fnBody(); } finally { this.cur = saved; }
329
+ return stmts;
330
+ }
331
+ lowerStatements(list) {
332
+ for (const s of list) this.lowerStmt(s);
333
+ }
334
+ lowerBlock(node, opts) {
335
+ const body = node.type === 'BlockStatement' ? node.body : [node];
336
+ return this.withScope(() => this.lowerStatements(body), opts);
337
+ }
338
+
339
+ lowerStmt(node, jsLabel = null) {
340
+ switch (node.type) {
341
+ case 'VariableDeclaration': return this.lowerVarDecl(node);
342
+ case 'FunctionDeclaration': return this.lowerLocalFunction(node.id.name, node, node);
343
+ case 'ExpressionStatement': {
344
+ const r = this.lowerExpr(node.expression);
345
+ if (r.special) { for (const s of r.pre || []) this.emit(s); return; }
346
+ this.emit(IR.expr(r.ir));
347
+ return;
348
+ }
349
+ case 'ReturnStatement': {
350
+ const r = node.argument ? this.lowerExpr(node.argument) : null;
351
+ this.emitReturn(r, node);
352
+ return;
353
+ }
354
+ case 'IfStatement': {
355
+ const test = this.lowerCond(node.test);
356
+ const then = this.lowerBlock(node.consequent);
357
+ const els = node.alternate ? this.lowerBlock(node.alternate) : null;
358
+ this.emit(IR.if(test, then, els));
359
+ return;
360
+ }
361
+ case 'BlockStatement': this.emit(IR.block(this.lowerBlock(node))); return;
362
+ case 'EmptyStatement': return;
363
+ case 'DebuggerStatement': return;
364
+ case 'LabeledStatement': {
365
+ const b = node.body;
366
+ if (['ForStatement', 'ForOfStatement', 'ForInStatement', 'WhileStatement', 'DoWhileStatement', 'SwitchStatement', 'BlockStatement'].includes(b.type)) return this.lowerStmt(b, node.label.name);
367
+ this.fail('labeled statements are only supported on loops', node);
368
+ }
369
+ // eslint-disable-next-line no-fallthrough
370
+ case 'WhileStatement': {
371
+ const label = this.pushLoop(true, jsLabel);
372
+ const test = this.lowerCond(node.test);
373
+ const body = this.lowerBlock(node.body);
374
+ this.popLoop();
375
+ this.emit(IR.while(label, test, body));
376
+ return;
377
+ }
378
+ case 'DoWhileStatement': {
379
+ const label = this.pushLoop(true, jsLabel);
380
+ const body = this.lowerBlock(node.body);
381
+ const test = this.lowerCond(node.test);
382
+ this.popLoop();
383
+ this.emit(IR.dowhile(label, test, body));
384
+ return;
385
+ }
386
+ case 'ForStatement': {
387
+ const outer = this.withScope(() => {
388
+ const init = [];
389
+ if (node.init) {
390
+ const saved = this.cur.stmts;
391
+ this.cur.stmts = init;
392
+ if (node.init.type === 'VariableDeclaration') this.lowerVarDecl(node.init);
393
+ else { const r = this.lowerExpr(node.init); if (!r.special) this.emit(IR.expr(r.ir)); }
394
+ this.cur.stmts = saved;
395
+ }
396
+ const label = this.pushLoop(true, jsLabel);
397
+ const test = node.test ? this.lowerCond(node.test) : null;
398
+ const body = this.lowerBlock(node.body);
399
+ const update = [];
400
+ if (node.update) {
401
+ const saved = this.cur.stmts;
402
+ this.cur.stmts = update;
403
+ const r = this.lowerExpr(node.update);
404
+ if (!r.special) this.emit(IR.expr(r.ir));
405
+ this.cur.stmts = saved;
406
+ }
407
+ this.popLoop();
408
+ this.emit(IR.for(label, init, test, update, body));
409
+ });
410
+ this.emit(IR.block(outer));
411
+ return;
412
+ }
413
+ case 'ForOfStatement':
414
+ case 'ForInStatement': {
415
+ if (node.await) this.warn('for await: the await is dropped', node);
416
+ const iter = this.val(this.lowerExpr(node.right), node.right);
417
+ const label = this.pushLoop(true, jsLabel);
418
+ const tmp = this.newTmp('__it');
419
+ const body = this.withScope(() => {
420
+ const left = node.left;
421
+ if (left.type === 'VariableDeclaration') this.bindPattern(left.declarations[0].id, { ir: IR.var(tmp) }, true, left);
422
+ else this.assignPattern(left, IR.var(tmp), left);
423
+ const inner = node.body.type === 'BlockStatement' ? node.body.body : [node.body];
424
+ this.lowerStatements(inner);
425
+ });
426
+ this.popLoop();
427
+ this.emit(node.type === 'ForOfStatement' ? IR.forof(label, tmp, iter, body) : IR.forin(label, tmp, iter, body));
428
+ return;
429
+ }
430
+ case 'BreakStatement': {
431
+ const target = this.findLoop(node.label?.name ?? null, false, node);
432
+ this.emit(IR.break(target.label));
433
+ return;
434
+ }
435
+ case 'ContinueStatement': {
436
+ const target = this.findLoop(node.label?.name ?? null, true, node);
437
+ this.emit(IR.continue(target.label));
438
+ return;
439
+ }
440
+ case 'ThrowStatement': this.emit(IR.throw(this.val(this.lowerExpr(node.argument), node.argument))); return;
441
+ case 'TryStatement': return this.lowerTry(node);
442
+ case 'SwitchStatement': return this.lowerSwitch(node, jsLabel);
443
+ case 'ClassDeclaration': this.fail('classes are not supported on chain; use plain objects and functions', node); break;
444
+ case 'WithStatement': this.fail('`with` is not supported', node); break;
445
+ case 'ImportDeclaration': case 'ExportNamedDeclaration': case 'ExportDefaultDeclaration': this.fail('import/export inside a function body', node); break;
446
+ default: this.fail(`unsupported statement: ${node.type}`, node);
447
+ }
448
+ }
449
+
450
+ emitReturn(r, node) {
451
+ const fn = this.cur.fn;
452
+ const inTry = fn.tryDepth > 0;
453
+ if (r && r.special) {
454
+ for (const s of r.pre || []) this.emit(s);
455
+ if (fn.retMode === 'value') {
456
+ const m = this.materialize(r.special, node, true);
457
+ if (!m) this.fail(`cannot return \`${describeSpecial(r.special)}\` from a helper`, node);
458
+ this.emit(IR.return('value', inTry, m));
459
+ return;
460
+ }
461
+ this.emit(IR.return(fn.retMode, inTry, null));
462
+ return;
463
+ }
464
+ this.emit(IR.return(fn.retMode, inTry, r ? r.ir : null));
465
+ }
466
+
467
+ pushLoop(isLoop, jsLabel) {
468
+ const label = `'l${this.labelN++}`;
469
+ this.cur.fn.loops.push({ label, isLoop, jsLabel });
470
+ return label;
471
+ }
472
+ popLoop() {
473
+ this.cur.fn.loops.pop();
474
+ }
475
+ findLoop(jsLabel, needLoop, node) {
476
+ const loops = this.cur.fn.loops;
477
+ for (let i = loops.length - 1; i >= 0; i--) {
478
+ const l = loops[i];
479
+ if (l.marker) this.fail(`${needLoop ? 'continue' : 'break'} cannot cross a ${l.marker} boundary (move the loop inside, or the ${l.marker} outside)`, node);
480
+ if (jsLabel ? l.jsLabel === jsLabel : (needLoop ? l.isLoop : true)) return l;
481
+ }
482
+ this.fail(`${needLoop ? 'continue' : 'break'} outside of a loop`, node);
483
+ }
484
+ /** Run `f` with loops blocked (inside a closure body). */
485
+ withLoopMarker(marker, f) {
486
+ const loops = this.cur.fn.loops;
487
+ loops.push({ marker });
488
+ try { return f(); } finally { loops.pop(); }
489
+ }
490
+
491
+ lowerVarDecl(node) {
492
+ for (const d of node.declarations) {
493
+ const id = d.id;
494
+ // local function values: const f = (x) => ...
495
+ if (id.type === 'Identifier' && d.init && isFunctionNode(d.init)) { this.lowerLocalFunction(id.name, d.init, d); continue; }
496
+ const hoisted = node.kind === 'var';
497
+ if (!d.init) {
498
+ if (id.type === 'Identifier') {
499
+ if (hoisted && this.cur.fn.hoisted.has(id.name) && this.cur.scope.lookup(id.name)?.binding.hoisted) continue;
500
+ this.declareLocal(id.name, IR.undef(), d);
501
+ } else this.fail('destructuring declaration without an initializer', d);
502
+ continue;
503
+ }
504
+ const r = this.lowerExpr(d.init);
505
+ this.bindPattern(id, r, !hoisted, d);
506
+ }
507
+ }
508
+
509
+ declareLocal(jsName, initIr, node) {
510
+ const rust = `v_${sanitizeIdent(jsName)}`;
511
+ const existing = this.cur.scope.vars.get(jsName);
512
+ if (existing && existing.hoisted) {
513
+ this.emit(IR.expr(IR.assign({ k: 'lv-var', name: rust }, initIr)));
514
+ return existing;
515
+ }
516
+ this.cur.scope.declare(jsName, { kind: 'local', rust });
517
+ this.emit(IR.let(rust, initIr));
518
+ return this.cur.scope.vars.get(jsName);
519
+ }
520
+
521
+ /**
522
+ * Bind a declaration pattern to a lowering result (ir or special).
523
+ * `declare` = true declares new locals; false assigns to existing ones.
524
+ */
525
+ bindPattern(pattern, r, declare, node) {
526
+ if (r.special && r.pre?.length) { for (const s of r.pre) this.emit(s); r = { special: r.special }; }
527
+ switch (pattern.type) {
528
+ case 'Identifier': {
529
+ if (r.special) {
530
+ const sp = r.special;
531
+ if (!declare) this.fail(`cannot assign \`${describeSpecial(sp)}\` to \`${pattern.name}\``, node);
532
+ // plain constants become real values; bridge objects keep their methods
533
+ if (sp.t === 'MathConst' || sp.t === 'ValueLike') { this.declareLocal(pattern.name, this.materialize(sp, node, true), node); return; }
534
+ if (sp.t === 'Void') this.fail('this expression has no value (a response call)', node);
535
+ this.cur.scope.declare(pattern.name, { kind: 'special', sp });
536
+ return;
537
+ }
538
+ if (declare) this.declareLocal(pattern.name, r.ir, node);
539
+ else this.emit(IR.expr(IR.assign(this.lvalueOfIdent(pattern.name, pattern), r.ir)));
540
+ return;
541
+ }
542
+ case 'AssignmentPattern': {
543
+ if (r.special) return this.bindPattern(pattern.left, r, declare, node);
544
+ return this.bindPattern(pattern.left, { ir: this.withDefault(r.ir, pattern.right) }, declare, node);
545
+ }
546
+ case 'ObjectPattern': {
547
+ if (r.special) {
548
+ for (const p of pattern.properties) {
549
+ if (p.type === 'RestElement') this.fail(`rest in destructuring of \`${describeSpecial(r.special)}\` is not supported`, p);
550
+ const key = this.propKeyName(p);
551
+ if (key === null) this.fail('computed keys in destructuring are not supported', p);
552
+ const sub = this.memberOf(r.special, key, p, null);
553
+ this.bindPattern(p.value, sub, declare, p);
554
+ }
555
+ return;
556
+ }
557
+ const tmp = this.newTmp('__d');
558
+ this.emit(IR.let(tmp, r.ir));
559
+ const used = [];
560
+ for (const p of pattern.properties) {
561
+ if (p.type === 'RestElement') {
562
+ this.bindPattern(p.argument, { ir: IR.helper('omit', [IR.var(tmp), IR.arr(used.map((k) => ({ e: IR.str(k) })))]) }, declare, p);
563
+ continue;
564
+ }
565
+ const key = this.propKeyName(p);
566
+ if (key !== null) {
567
+ used.push(key);
568
+ this.bindPattern(p.value, { ir: IR.get(IR.var(tmp), key) }, declare, p);
569
+ } else {
570
+ const k = this.val(this.lowerExpr(p.key), p.key);
571
+ this.bindPattern(p.value, { ir: IR.getc(IR.var(tmp), k) }, declare, p);
572
+ }
573
+ }
574
+ return;
575
+ }
576
+ case 'ArrayPattern': {
577
+ if (r.special) this.fail(`\`${describeSpecial(r.special)}\` is not iterable`, node);
578
+ const tmp = this.newTmp('__d');
579
+ this.emit(IR.let(tmp, r.ir));
580
+ pattern.elements.forEach((el, i) => {
581
+ if (!el) return;
582
+ if (el.type === 'RestElement') { this.bindPattern(el.argument, { ir: IR.helper('slice_from', [IR.var(tmp), IR.num(i)]) }, declare, el); return; }
583
+ this.bindPattern(el, { ir: IR.getc(IR.var(tmp), IR.num(i)) }, declare, el);
584
+ });
585
+ return;
586
+ }
587
+ case 'MemberExpression': {
588
+ if (declare) this.fail('invalid declaration target', node);
589
+ const lv = this.lowerLvalue(pattern);
590
+ if (lv.k === 'lv-special') { for (const s of lv.set(r.ir)) this.emit(s); return; }
591
+ this.emit(IR.expr(IR.assign(lv, this.val(r, node))));
592
+ return;
593
+ }
594
+ default: this.fail(`unsupported binding pattern ${pattern.type}`, pattern);
595
+ }
596
+ }
597
+ assignPattern(target, ir, node) {
598
+ return this.bindPattern(target, { ir }, false, node);
599
+ }
600
+ propKeyName(p) {
601
+ if (p.computed) return p.key.type === 'Literal' ? String(p.key.value) : null;
602
+ return p.key.type === 'Identifier' ? p.key.name : String(p.key.value);
603
+ }
604
+ withDefault(ir, defNode) {
605
+ const def = this.val(this.lowerExpr(defNode), defNode);
606
+ const t = this.newTmp();
607
+ return IR.pre([IR.let(t, ir)], IR.cond({ k: 'c-undef', e: IR.var(t) }, def, IR.var(t)));
608
+ }
609
+
610
+ // ---- local functions
611
+
612
+ lowerLocalFunction(jsName, fnNode, declNode) {
613
+ // What does it capture from enclosing *local* scopes?
614
+ const free = freeVars(fnNode);
615
+ const captures = new Map();
616
+ for (const [name, refNode] of free) {
617
+ const found = this.cur.scope.lookup(name);
618
+ if (!found) continue;
619
+ if (found.scope === this.moduleScope) continue;
620
+ const b = found.binding;
621
+ if (b.kind === 'local') captures.set(name, b);
622
+ else if (b.kind === 'localfn') captures.set(name, b);
623
+ else if (b.kind === 'special') captures.set(name, b); // specials compile to cx calls: no capture needed, but keep visible
624
+ else this.fail(`local function \`${jsName}\` references \`${name}\` which cannot be captured`, refNode);
625
+ }
626
+ const valueCaptures = [...captures].filter(([, b]) => b.kind !== 'special');
627
+ if (valueCaptures.length === 0) {
628
+ // No captures: a plain Rust fn (can recurse, can be used before definition is not supported).
629
+ const rust = this.uniqueName(`h_${this.routeIndex}_`, jsName);
630
+ const binding = { kind: 'helper', rust, node: fnNode, lowered: false, name: jsName };
631
+ this.cur.scope.declare(jsName, binding);
632
+ // helpers at module scope could reference this by name only if declared there; local ones lower now
633
+ binding.lowered = true;
634
+ // capture specials into the helper scope so `req`/`res` remain usable inside
635
+ const specialCaptures = [...captures].filter(([, b]) => b.kind === 'special');
636
+ const fnIr = this.lowerFunctionBody(fnNode, this.moduleScope, 'value', { name: jsName, captures: specialCaptures });
637
+ this.helpers.push({ name: rust, fn: fnIr });
638
+ return;
639
+ }
640
+ // Captures: a move closure over clones of the captured locals.
641
+ const mutated = findMutation(fnNode, new Set(valueCaptures.map(([n]) => n)));
642
+ if (mutated) this.fail(`local function \`${jsName}\` mutates captured variable \`${mutated}\`; captured variables are copied into closures on chain`, fnNode);
643
+ const rust = this.uniqueName('f_', jsName);
644
+ const binding = { kind: 'localfn', rust, name: jsName };
645
+ const closureCaptures = new Map([...captures].map(([n, b]) => [n, b.kind === 'localfn' ? b : b.kind === 'local' ? { kind: 'local', rust: b.rust } : b]));
646
+ const savedName = this.currentClosureName;
647
+ this.currentClosureName = jsName;
648
+ let fnIr;
649
+ try {
650
+ fnIr = this.lowerFunctionBody(fnNode, this.moduleScope, 'value', { name: jsName, boundary: 'closure', captures: closureCaptures });
651
+ } finally {
652
+ this.currentClosureName = savedName;
653
+ }
654
+ this.cur.scope.declare(jsName, binding);
655
+ this.emit(IR.letfn(rust, fnIr, valueCaptures.map(([, b]) => b.rust)));
656
+ }
657
+
658
+ // ---- try / switch
659
+
660
+ lowerTry(node) {
661
+ const fn = this.cur.fn;
662
+ const mode = fn.retMode;
663
+ const inTry = fn.tryDepth > 0;
664
+ fn.tryDepth++;
665
+ let body;
666
+ try {
667
+ body = this.withLoopMarker('try', () => this.lowerBlock(node.block, { boundary: 'try' }));
668
+ } finally {
669
+ fn.tryDepth--;
670
+ }
671
+ let param = null;
672
+ let handler = null;
673
+ // With a `finally`, the catch handler is also a closure body so the
674
+ // finalizer can run before any `return` it makes.
675
+ const handlerClosure = !!node.finalizer;
676
+ if (node.handler) {
677
+ const lowerHandler = () => this.withScope(() => {
678
+ if (node.handler.param) {
679
+ if (node.handler.param.type === 'Identifier') {
680
+ param = `v_${sanitizeIdent(node.handler.param.name)}`;
681
+ this.cur.scope.declare(node.handler.param.name, { kind: 'local', rust: param });
682
+ } else {
683
+ param = this.newTmp('__e');
684
+ this.bindPattern(node.handler.param, { ir: IR.var(param) }, true, node.handler);
685
+ }
686
+ }
687
+ this.lowerStatements(node.handler.body.body);
688
+ }, { boundary: handlerClosure ? 'try' : null });
689
+ if (handlerClosure) {
690
+ fn.tryDepth++;
691
+ try { handler = this.withLoopMarker('try', lowerHandler); } finally { fn.tryDepth--; }
692
+ } else handler = lowerHandler();
693
+ }
694
+ const finalizer = node.finalizer ? this.lowerBlock(node.finalizer) : null;
695
+ this.emit({ ...IR.try(body, param, handler, finalizer, mode, inTry), handlerClosure });
696
+ }
697
+
698
+ lowerSwitch(node, jsLabel) {
699
+ const disc = this.val(this.lowerExpr(node.discriminant), node.discriminant);
700
+ const label = this.pushLoop(false, jsLabel);
701
+ const cases = [];
702
+ const outer = this.withScope(() => {
703
+ for (const c of node.cases) {
704
+ const test = c.test ? this.val(this.lowerExpr(c.test), c.test) : null;
705
+ const body = this.withScope(() => this.lowerStatements(c.consequent));
706
+ cases.push({ test, body });
707
+ }
708
+ });
709
+ this.popLoop();
710
+ this.emit(IR.block([...outer, IR.switch(label, disc, cases)]));
711
+ }
712
+
713
+ // ---- conditions
714
+
715
+ lowerCond(node) {
716
+ switch (node.type) {
717
+ case 'UnaryExpression':
718
+ if (node.operator === '!') return IR.cnot(this.lowerCond(node.argument));
719
+ break;
720
+ case 'LogicalExpression':
721
+ if (node.operator === '&&') return IR.cand(this.lowerCond(node.left), this.lowerCond(node.right));
722
+ if (node.operator === '||') return IR.cor(this.lowerCond(node.left), this.lowerCond(node.right));
723
+ break;
724
+ case 'BinaryExpression': {
725
+ const op = CMP_OPS[node.operator];
726
+ if (op) {
727
+ const l = this.val(this.lowerExpr(node.left), node.left);
728
+ const r = this.val(this.lowerExpr(node.right), node.right);
729
+ return { k: 'c-cmp', op, l, r };
730
+ }
731
+ break;
732
+ }
733
+ default: break;
734
+ }
735
+ return IR.truthy(this.val(this.lowerExpr(node), node));
736
+ }
737
+
738
+ // ---- expressions
739
+
740
+ /** Force a lowering result into a Val IR. */
741
+ val(r, node) {
742
+ if (!r.special) return r.ir;
743
+ const m = this.materialize(r.special, node, false);
744
+ if (!m) this.fail(`\`${describeSpecial(r.special)}\` cannot be used as a value here`, node);
745
+ return IR.pre(r.pre || [], m);
746
+ }
747
+
748
+ materialize(sp, node, quiet) {
749
+ switch (sp.t) {
750
+ case 'ReqQuery': return IR.params('query', this.paramsConst);
751
+ case 'ReqHeaders': case 'WebHeaders': return IR.host('req_headers');
752
+ case 'Params': return IR.params('params', this.paramsConst);
753
+ // as a value, URLSearchParams is an iterable of [key, value] pairs (Object.fromEntries / Array.from / for-of)
754
+ case 'SearchParams': return IR.entries(IR.host('req_query'));
755
+ case 'UrlObj': return IR.host('req_full_url');
756
+ case 'DateNow': return IR.host('now_ms');
757
+ case 'ProcessEnv': this.envDynamic = true; return IR.host('env_obj');
758
+ case 'Cookies': return IR.helper('cookies', []);
759
+ case 'MathConst': return IR.num(sp.v);
760
+ case 'ValueLike': return sp.ir;
761
+ default: return null;
762
+ }
763
+ }
764
+
765
+ lowerExpr(node) {
766
+ switch (node.type) {
767
+ case 'Literal': return { ir: this.lowerLiteral(node) };
768
+ case 'TemplateLiteral': {
769
+ const parts = [];
770
+ node.quasis.forEach((q, i) => {
771
+ if (q.value.cooked) parts.push({ s: q.value.cooked });
772
+ if (i < node.expressions.length) parts.push({ e: this.val(this.lowerExpr(node.expressions[i]), node.expressions[i]) });
773
+ });
774
+ return { ir: IR.template(parts) };
775
+ }
776
+ case 'TaggedTemplateExpression': this.fail('tagged templates are not supported', node); break;
777
+ case 'Identifier': return this.lowerIdentifier(node);
778
+ case 'ThisExpression': this.fail('`this` is not supported (no classes on chain)', node); break;
779
+ case 'ArrayExpression': {
780
+ const items = node.elements.map((el) => {
781
+ if (!el) return { hole: true };
782
+ if (el.type === 'SpreadElement') return { spread: this.val(this.lowerExpr(el.argument), el.argument) };
783
+ return { e: this.val(this.lowerExpr(el), el) };
784
+ });
785
+ return { ir: IR.arr(items) };
786
+ }
787
+ case 'ObjectExpression': {
788
+ const props = [];
789
+ for (const p of node.properties) {
790
+ if (p.type === 'SpreadElement') { props.push({ spread: this.val(this.lowerExpr(p.argument), p.argument) }); continue; }
791
+ if (p.kind !== 'init') this.fail('getters/setters in object literals are not supported', p);
792
+ if (p.method || isFunctionNode(p.value)) this.fail('functions as object property values are not supported (functions are not first-class values on chain)', p);
793
+ const key = this.propKeyName(p);
794
+ const value = this.val(this.lowerExpr(p.value), p.value);
795
+ if (key !== null) props.push({ key, value });
796
+ else props.push({ key: { e: this.val(this.lowerExpr(p.key), p.key) }, value });
797
+ }
798
+ return { ir: IR.obj(props) };
799
+ }
800
+ case 'MemberExpression': return this.lowerMember(node, null);
801
+ case 'ChainExpression': {
802
+ const label = `'c${this.chainN++}`;
803
+ const saved = this.chainLabel;
804
+ this.chainLabel = { label, used: false };
805
+ let r;
806
+ try { r = this.lowerExpr(node.expression); } finally { this.chainLabel = saved; }
807
+ if (r.special) return r;
808
+ return { ir: IR.chain(label, r.ir) };
809
+ }
810
+ case 'CallExpression': return this.lowerCall(node);
811
+ case 'NewExpression': return this.lowerNew(node);
812
+ case 'AwaitExpression': return this.lowerExpr(node.argument);
813
+ case 'UnaryExpression': return this.lowerUnary(node);
814
+ case 'BinaryExpression': return this.lowerBinary(node);
815
+ case 'LogicalExpression': {
816
+ const l = this.val(this.lowerExpr(node.left), node.left);
817
+ const r = this.val(this.lowerExpr(node.right), node.right);
818
+ return { ir: IR.logical(node.operator === '&&' ? 'and' : node.operator === '||' ? 'or' : 'nullish', l, r) };
819
+ }
820
+ case 'ConditionalExpression': {
821
+ const test = this.lowerCond(node.test);
822
+ const a = this.lowerExpr(node.consequent);
823
+ const b = this.lowerExpr(node.alternate);
824
+ if (a.special || b.special) {
825
+ // e.g. `cond ? res.status(200) : res.status(400)` — statements in both arms
826
+ const sa = a.special ? (a.pre || []) : [IR.expr(a.ir)];
827
+ const sb = b.special ? (b.pre || []) : [IR.expr(b.ir)];
828
+ return { special: SP('Void'), pre: [IR.if(test, sa, sb)] };
829
+ }
830
+ return { ir: IR.cond(test, a.ir, b.ir) };
831
+ }
832
+ case 'AssignmentExpression': return this.lowerAssign(node);
833
+ case 'UpdateExpression': {
834
+ const lv = this.lowerLvalue(node.argument);
835
+ if (lv.k === 'lv-special') this.fail('++/-- on a request/response property', node);
836
+ return { ir: IR.update(lv, node.operator === '++' ? 1 : -1, node.prefix) };
837
+ }
838
+ case 'SequenceExpression': {
839
+ const exprs = [];
840
+ for (const e of node.expressions) exprs.push(this.val(this.lowerExpr(e), e));
841
+ return { ir: IR.seq(exprs) };
842
+ }
843
+ case 'ArrowFunctionExpression': case 'FunctionExpression':
844
+ this.fail('a function is used as a value; on chain functions can only be declared (`function f() {}` / `const f = () => {}`) and called or passed to array methods', node);
845
+ break;
846
+ case 'SpreadElement': this.fail('spread is only supported inside array and object literals', node); break;
847
+ case 'YieldExpression': this.fail('generators are not supported', node); break;
848
+ case 'ClassExpression': this.fail('classes are not supported on chain', node); break;
849
+ case 'ImportExpression': this.fail('dynamic import() is not supported', node); break;
850
+ case 'MetaProperty': this.fail('import.meta / new.target are not supported', node); break;
851
+ case 'ParenthesizedExpression': return this.lowerExpr(node.expression);
852
+ default: this.fail(`unsupported expression: ${node.type}`, node);
853
+ }
854
+ return null;
855
+ }
856
+
857
+ lowerLiteral(node) {
858
+ if (node.regex) this.fail('regular expressions are not supported on chain (no RegExp engine in the runtime)', node);
859
+ if (node.bigint !== undefined) this.fail('BigInt literals are not supported (numbers are f64)', node);
860
+ const v = node.value;
861
+ if (v === null) return IR.null();
862
+ if (typeof v === 'string') return IR.str(v);
863
+ if (typeof v === 'number') return IR.num(v);
864
+ if (typeof v === 'boolean') return IR.bool(v);
865
+ this.fail(`unsupported literal ${String(v)}`, node);
866
+ return null;
867
+ }
868
+
869
+ lowerIdentifier(node) {
870
+ const name = node.name;
871
+ if (name === 'undefined') return { ir: IR.undef() };
872
+ if (name === 'NaN') return { ir: IR.raw('Val::Num(f64::NAN)') };
873
+ if (name === 'Infinity') return { ir: IR.raw('Val::Num(f64::INFINITY)') };
874
+ if (name === 'arguments') this.fail('`arguments` is not supported; use rest parameters', node);
875
+ const found = this.cur.scope.lookup(name);
876
+ if (found) {
877
+ let b = found.binding;
878
+ if (found.scope === this.moduleScope) b = this.resolveModule(b, node);
879
+ switch (b.kind) {
880
+ case 'local': return { ir: IR.var(b.rust) };
881
+ case 'special': return { special: b.sp };
882
+ case 'const': return { ir: IR.constref(b.rust) };
883
+ case 'helper': return { special: SP('HelperRef', { rust: b.rust, name }) };
884
+ case 'localfn': return { special: SP('LocalFnRef', { rust: b.rust, name }) };
885
+ case 'class': this.fail(`class \`${name}\`: classes are not supported on chain`, node); break;
886
+ case 'type-only': this.fail(`\`${name}\` is a type import and has no runtime value`, node); break;
887
+ default: this.fail(`cannot use \`${name}\` here`, node);
888
+ }
889
+ }
890
+ if (name in BANNED_GLOBALS) this.fail(BANNED_GLOBALS[name], node);
891
+ if (KNOWN_GLOBALS.has(name)) return { special: SP('Global', { name }) };
892
+ if (this.currentClosureName === name) this.fail(`\`${name}\` is a recursive local function; move it to module scope`, node);
893
+ this.fail(`unknown identifier \`${name}\` (not declared in this file, not imported, and not a supported global)`, node);
894
+ return null;
895
+ }
896
+
897
+ lowerUnary(node) {
898
+ const op = node.operator;
899
+ if (op === 'typeof') {
900
+ if (node.argument.type === 'Identifier') {
901
+ const found = this.cur.scope.lookup(node.argument.name);
902
+ if (!found && !KNOWN_GLOBALS.has(node.argument.name)) return { ir: IR.str(node.argument.name in BANNED_GLOBALS ? 'undefined' : 'undefined') };
903
+ if (found && (found.binding.kind === 'helper' || found.binding.kind === 'localfn')) return { ir: IR.str('function') };
904
+ }
905
+ return { ir: IR.unary('typeof', this.val(this.lowerExpr(node.argument), node.argument)) };
906
+ }
907
+ if (op === 'delete') {
908
+ if (node.argument.type !== 'MemberExpression') return { ir: IR.bool(true) };
909
+ const lv = this.lowerLvalue(node.argument);
910
+ if (lv.k === 'lv-special') this.fail('delete on a request/response property', node);
911
+ return { ir: IR.delete(lv) };
912
+ }
913
+ if (op === 'void') return { ir: IR.unary('void', this.val(this.lowerExpr(node.argument), node.argument)) };
914
+ if (op === '!') return { ir: IR.unary('not', this.lowerCond(node.argument)) };
915
+ const e = this.val(this.lowerExpr(node.argument), node.argument);
916
+ if (op === '-') { if (e.k === 'num') return { ir: IR.num(-e.v) }; return { ir: IR.unary('neg', e) }; }
917
+ if (op === '+') return { ir: IR.unary('plus', e) };
918
+ if (op === '~') return { ir: IR.unary('bitnot', e) };
919
+ this.fail(`unsupported unary operator ${op}`, node);
920
+ return null;
921
+ }
922
+
923
+ lowerBinary(node) {
924
+ const op = node.operator;
925
+ if (op === 'instanceof') this.fail('`instanceof` is not supported (no classes on chain)', node);
926
+ if (op === 'in') {
927
+ const key = this.val(this.lowerExpr(node.left), node.left);
928
+ const obj = this.val(this.lowerExpr(node.right), node.right);
929
+ return { ir: IR.in(key, obj) };
930
+ }
931
+ const l = this.val(this.lowerExpr(node.left), node.left);
932
+ const r = this.val(this.lowerExpr(node.right), node.right);
933
+ const cmp = CMP_OPS[op];
934
+ if (cmp) return { ir: IR.cmp(cmp, l, r) };
935
+ const bin = BIN_OPS[op];
936
+ if (!bin) this.fail(`unsupported operator ${op}`, node);
937
+ return { ir: IR.bin(bin, l, r) };
938
+ }
939
+
940
+ lowerAssign(node) {
941
+ const op = node.operator;
942
+ if (node.left.type === 'ObjectPattern' || node.left.type === 'ArrayPattern') {
943
+ if (op !== '=') this.fail('compound assignment to a pattern', node);
944
+ const tmp = this.newTmp('__d');
945
+ const value = this.val(this.lowerExpr(node.right), node.right);
946
+ const stmts = this.withScope(() => {
947
+ this.emit(IR.let(tmp, value));
948
+ this.assignPattern(node.left, IR.var(tmp), node);
949
+ });
950
+ return { ir: IR.pre(stmts, IR.var(tmp)) };
951
+ }
952
+ const lv = this.lowerLvalue(node.left);
953
+ const value = this.val(this.lowerExpr(node.right), node.right);
954
+ if (lv.k === 'lv-special') {
955
+ if (op !== '=') this.fail('compound assignment to a response property', node);
956
+ return { special: SP('Void'), pre: lv.set(value) };
957
+ }
958
+ if (op === '=') return { ir: IR.assign(lv, value) };
959
+ if (op === '&&=' || op === '||=' || op === '??=') {
960
+ const cur = this.readLvalue(lv);
961
+ const logical = IR.logical(op === '&&=' ? 'and' : op === '||=' ? 'or' : 'nullish', cur, value);
962
+ return { ir: IR.assign(lv, logical) };
963
+ }
964
+ const bin = BIN_OPS[op.slice(0, -1)];
965
+ if (!bin) this.fail(`unsupported assignment operator ${op}`, node);
966
+ return { ir: IR.assign(lv, value, bin) };
967
+ }
968
+
969
+ readLvalue(lv) {
970
+ if (lv.k === 'lv-var') return IR.var(lv.name);
971
+ if (lv.k === 'lv-member') {
972
+ const base = lv.obj.k === 'lv-expr' ? lv.obj.e : this.readLvalue(lv.obj);
973
+ return typeof lv.key === 'string' ? IR.get(base, lv.key) : IR.getc(base, lv.key);
974
+ }
975
+ return IR.undef();
976
+ }
977
+
978
+ lvalueOfIdent(name, node) {
979
+ const found = this.cur.scope.lookup(name);
980
+ if (!found) {
981
+ if (name in BANNED_GLOBALS) this.fail(BANNED_GLOBALS[name], node);
982
+ this.fail(`assignment to undeclared variable \`${name}\``, node);
983
+ }
984
+ if (found.scope === this.moduleScope) this.fail(`assignment to module-level \`${name}\`: module state does not persist between invocations on chain; use @vercel/kv`, node);
985
+ const b = found.binding;
986
+ if (b.kind !== 'local') this.fail(`cannot assign to \`${name}\` (${b.kind === 'special' ? describeSpecial(b.sp) : b.kind})`, node);
987
+ if (found.boundaries.includes('closure')) this.fail(`\`${name}\` is captured by a local function and mutated inside it; captured variables are copied into closures on chain`, node);
988
+ return { k: 'lv-var', name: b.rust };
989
+ }
990
+
991
+ lowerLvalue(node) {
992
+ if (node.type === 'Identifier') return this.lvalueOfIdent(node.name, node);
993
+ if (node.type === 'MemberExpression') {
994
+ if (node.optional) this.fail('optional chaining on an assignment target', node);
995
+ // res.statusCode = n
996
+ const objR = node.object.type === 'Identifier' || node.object.type === 'MemberExpression' ? this.peekSpecial(node.object) : null;
997
+ if (objR) {
998
+ const key = node.computed ? (node.property.type === 'Literal' ? String(node.property.value) : null) : node.property.name;
999
+ if (objR.t === 'Res' && key === 'statusCode') return { k: 'lv-special', set: (v) => [IR.expr(IR.host('res_status', [v], { void: true }))] };
1000
+ this.fail(`assignment to \`${describeSpecial(objR)}.${key ?? '[...]'}\` is not supported`, node);
1001
+ }
1002
+ let obj;
1003
+ if (node.object.type === 'Identifier' || node.object.type === 'MemberExpression') obj = this.lowerLvalue(node.object);
1004
+ else obj = { k: 'lv-expr', e: this.val(this.lowerExpr(node.object), node.object) };
1005
+ const key = node.computed ? (node.property.type === 'Literal' && typeof node.property.value === 'string' ? node.property.value : this.val(this.lowerExpr(node.property), node.property)) : node.property.name;
1006
+ return { k: 'lv-member', obj, key };
1007
+ }
1008
+ if (node.type === 'ChainExpression') this.fail('optional chaining on an assignment target', node);
1009
+ this.fail(`unsupported assignment target ${node.type}`, node);
1010
+ return null;
1011
+ }
1012
+
1013
+ /** If `node` resolves to a special without side effects, return it (no emission). */
1014
+ peekSpecial(node) {
1015
+ if (node.type === 'Identifier') {
1016
+ const found = this.cur.scope.lookup(node.name);
1017
+ if (found) {
1018
+ let b = found.binding;
1019
+ if (found.scope === this.moduleScope && b.kind === 'modconst') b = this.resolveModule(b, node);
1020
+ return b.kind === 'special' ? b.sp : null;
1021
+ }
1022
+ if (KNOWN_GLOBALS.has(node.name)) return SP('Global', { name: node.name });
1023
+ return null;
1024
+ }
1025
+ if (node.type === 'MemberExpression' && !node.computed) {
1026
+ const base = this.peekSpecial(node.object);
1027
+ if (!base) return null;
1028
+ const r = this.memberOf(base, node.property.name, node, null, true);
1029
+ return r?.special || null;
1030
+ }
1031
+ return null;
1032
+ }
1033
+
1034
+ // ---- members
1035
+
1036
+ lowerMember(node, callArgs) {
1037
+ const objR = this.lowerExpr(node.object);
1038
+ if (node.optional && !objR.special) {
1039
+ // a?.b — short-circuit the chain when the object is nullish
1040
+ const label = this.chainLabel;
1041
+ if (label) { label.used = true; objR.ir = IR.optcheck(label.label, objR.ir); }
1042
+ }
1043
+ let key;
1044
+ if (node.computed) {
1045
+ if (node.property.type === 'Literal' && typeof node.property.value === 'string') key = node.property.value;
1046
+ else if (node.property.type === 'Literal' && typeof node.property.value === 'number' && objR.special) key = String(node.property.value);
1047
+ else key = { e: this.val(this.lowerExpr(node.property), node.property) };
1048
+ } else key = node.property.name;
1049
+ if (objR.special) {
1050
+ if (typeof key === 'string') return this.memberOf(objR.special, key, node, callArgs, false, objR.pre);
1051
+ return this.computedMemberOf(objR.special, key.e, node, objR.pre);
1052
+ }
1053
+ return { ir: typeof key === 'string' ? IR.get(objR.ir, key) : IR.getc(objR.ir, key.e) };
1054
+ }
1055
+
1056
+ computedMemberOf(sp, keyIr, node, pre = []) {
1057
+ switch (sp.t) {
1058
+ case 'ReqQuery': return { ir: IR.pre(pre, IR.getc(IR.params('query', this.paramsConst), keyIr)) };
1059
+ case 'ReqHeaders': return { ir: IR.pre(pre, IR.host('req_header', [keyIr])) };
1060
+ case 'ProcessEnv': this.envDynamic = true; return { ir: IR.pre(pre, IR.host('env', [keyIr])) };
1061
+ case 'Cookies': return { ir: IR.pre(pre, IR.getc(IR.helper('cookies', []), keyIr)) };
1062
+ case 'Params': return { ir: IR.pre(pre, IR.getc(IR.params('params', this.paramsConst), keyIr)) };
1063
+ default: {
1064
+ const m = this.materialize(sp, node, true);
1065
+ if (m) return { ir: IR.pre(pre, IR.getc(m, keyIr)) };
1066
+ this.fail(`computed property access on \`${describeSpecial(sp)}\` is not supported`, node);
1067
+ }
1068
+ }
1069
+ return null;
1070
+ }
1071
+
1072
+ /**
1073
+ * `special.key` (and `special.key(...)` when callArgs is given). Returns a
1074
+ * lowering result. `quiet` = only resolve to specials, never emit/fail (for peeking).
1075
+ */
1076
+ memberOf(sp, key, node, callArgs, quiet = false, pre = []) {
1077
+ const isCall = callArgs !== null && callArgs !== undefined;
1078
+ const args = () => callArgs.map((a) => this.lowerArg(a));
1079
+ const arg = (i) => (callArgs[i] ? this.lowerArg(callArgs[i]) : IR.undef());
1080
+ const R = (ir) => ({ ir: IR.pre(pre, ir) });
1081
+ const S = (t, extra) => ({ special: SP(t, extra), pre });
1082
+ const notCall = () => { if (isCall) this.fail(`\`${describeSpecial(sp)}.${key}\` is not a function`, node); };
1083
+ const onlyCall = () => { if (!isCall) this.fail(`\`${describeSpecial(sp)}.${key}\` must be called`, node); };
1084
+ switch (sp.t) {
1085
+ case 'Req':
1086
+ switch (key) {
1087
+ case 'method': notCall(); return R(IR.host('req_method'));
1088
+ case 'url': notCall(); return R(IR.host('req_url'));
1089
+ case 'query': return S('ReqQuery');
1090
+ case 'body': notCall(); return R(IR.host('req_body'));
1091
+ case 'headers': return S('ReqHeaders');
1092
+ case 'cookies': return S('Cookies');
1093
+ case 'params': return S('Params');
1094
+ default: if (quiet) return null; this.fail(`req.${key} is not available on chain (supported: method, url, query, body, headers, cookies)`, node);
1095
+ }
1096
+ break;
1097
+ case 'ReqQuery': {
1098
+ if (quiet) return null;
1099
+ if (isCall) this.fail(`req.query.${key}(): req.query is a plain object`, node);
1100
+ return R(IR.get(IR.params('query', this.paramsConst), key));
1101
+ }
1102
+ case 'ReqHeaders': {
1103
+ if (quiet) return null;
1104
+ if (isCall) this.fail(`req.headers.${key}(): req.headers is a plain object`, node);
1105
+ return R(IR.host('req_header', [IR.str(key.toLowerCase())]));
1106
+ }
1107
+ case 'Cookies': {
1108
+ if (quiet) return null;
1109
+ return R(IR.get(IR.helper('cookies', []), key));
1110
+ }
1111
+ case 'Res': {
1112
+ if (quiet) return null;
1113
+ if (!isCall) {
1114
+ if (key === 'headersSent') return R(IR.bool(false));
1115
+ this.fail(`res.${key} is not supported as a value`, node);
1116
+ }
1117
+ const st = (ir) => ({ special: SP('Res'), pre: [...pre, IR.expr(ir)] });
1118
+ switch (key) {
1119
+ case 'status': return st(IR.host('res_status', [arg(0)], { void: true }));
1120
+ case 'json': return st(IR.host('res_json', [arg(0)], { void: true }));
1121
+ case 'send': return st(IR.host('res_send', [arg(0)], { void: true }));
1122
+ case 'end': return st(IR.host('res_end', [arg(0)], { void: true }));
1123
+ case 'setHeader': return st(IR.host('res_header', [arg(0), arg(1)], { void: true }));
1124
+ case 'redirect': return st(IR.host('res_redirect', [arg(0), arg(1)], { void: true }));
1125
+ case 'sendStatus': return { special: SP('Res'), pre: [...pre, IR.expr(IR.host('res_status', [arg(0)], { void: true })), IR.expr(IR.host('res_end', [IR.undef()], { void: true }))] };
1126
+ case 'writeHead': return { special: SP('Res'), pre: [...pre, IR.expr(IR.host('res_status', [arg(0)], { void: true })), ...(callArgs[1] ? [IR.expr(IR.helper('set_headers', [arg(1)]))] : [])] };
1127
+ case 'write': this.fail('res.write(): streaming responses are not supported; build the body and call res.send()/res.json() once', node); break;
1128
+ case 'getHeader': case 'removeHeader': case 'appendHeader': this.fail(`res.${key}() is not supported yet`, node); break;
1129
+ case 'cookie': case 'clearCookie': this.fail(`res.${key}() (Express helper) is not available; set a Set-Cookie header with res.setHeader`, node); break;
1130
+ default: this.fail(`res.${key}() is not a Vercel response helper the runtime supports (status, json, send, end, setHeader, redirect)`, node);
1131
+ }
1132
+ break;
1133
+ }
1134
+ case 'WebReq':
1135
+ switch (key) {
1136
+ case 'method': notCall(); return R(IR.host('req_method'));
1137
+ case 'url': notCall(); return R(IR.host('req_full_url'));
1138
+ case 'nextUrl': return S('UrlObj');
1139
+ case 'headers': return S('WebHeaders');
1140
+ case 'cookies': return S('WebCookies');
1141
+ case 'json': onlyCall(); return R(IR.host('req_json', [], { q: true }));
1142
+ case 'text': onlyCall(); return R(IR.host('req_text'));
1143
+ case 'clone': onlyCall(); return S('WebReq');
1144
+ case 'body': if (quiet) return null; this.fail('request.body is a stream; use `await request.json()` or `await request.text()`', node); break;
1145
+ case 'formData': if (quiet) return null; this.fail('request.formData() is not supported; send JSON', node); break;
1146
+ case 'arrayBuffer': case 'blob': case 'bytes': if (quiet) return null; this.fail(`request.${key}() is not supported; use request.text()`, node); break;
1147
+ case 'ip': case 'geo': if (quiet) return null; this.fail(`request.${key} is not available on chain`, node); break;
1148
+ case 'signal': if (quiet) return null; this.fail('request.signal is not available on chain', node); break;
1149
+ default: if (quiet) return null; this.fail(`request.${key} is not supported`, node);
1150
+ }
1151
+ break;
1152
+ case 'WebHeaders':
1153
+ if (quiet) return null;
1154
+ switch (key) {
1155
+ case 'get': onlyCall(); return R(IR.host('req_header', [arg(0)]));
1156
+ case 'has': onlyCall(); return R(IR.helper('header_has', [arg(0)]));
1157
+ default: this.fail(`request.headers.${key} is not supported (use .get(name) / .has(name))`, node);
1158
+ }
1159
+ break;
1160
+ case 'WebCookies':
1161
+ if (quiet) return null;
1162
+ switch (key) {
1163
+ case 'get': onlyCall(); return R(IR.helper('cookie_get', [arg(0)]));
1164
+ case 'has': onlyCall(); return R(IR.helper('cookie_has', [arg(0)]));
1165
+ case 'getAll': onlyCall(); return R(IR.helper('cookie_all', []));
1166
+ default: this.fail(`request.cookies.${key} is not supported (use .get(name) / .has(name) / .getAll())`, node);
1167
+ }
1168
+ break;
1169
+ case 'UrlObj':
1170
+ switch (key) {
1171
+ case 'searchParams': return S('SearchParams');
1172
+ case 'pathname': notCall(); return R(IR.host('req_path'));
1173
+ case 'search': notCall(); return R(IR.helper('search', [IR.bool(true)]));
1174
+ case 'href': notCall(); return R(IR.host('req_full_url'));
1175
+ case 'toString': onlyCall(); return R(IR.host('req_full_url'));
1176
+ case 'origin': notCall(); return R(IR.str('https://zoo.sol'));
1177
+ case 'host': case 'hostname': notCall(); return R(IR.str('zoo.sol'));
1178
+ case 'protocol': notCall(); return R(IR.str('https:'));
1179
+ case 'hash': notCall(); return R(IR.str(''));
1180
+ case 'basePath': notCall(); return R(IR.str(''));
1181
+ case 'clone': onlyCall(); return S('UrlObj');
1182
+ default: if (quiet) return null; this.fail(`URL.${key} is not supported`, node);
1183
+ }
1184
+ break;
1185
+ case 'SearchParams':
1186
+ if (quiet) return null;
1187
+ switch (key) {
1188
+ case 'get': onlyCall(); return R(IR.host('req_query_get', [arg(0)]));
1189
+ case 'has': onlyCall(); return R(IR.helper('query_has', [arg(0)]));
1190
+ case 'getAll': onlyCall(); return R(IR.helper('query_get_all', [arg(0)]));
1191
+ case 'toString': onlyCall(); return R(IR.helper('search', [IR.bool(false)]));
1192
+ case 'entries': onlyCall(); return R(IR.entries(IR.host('req_query')));
1193
+ case 'keys': onlyCall(); return R(IR.keys(IR.host('req_query')));
1194
+ case 'values': onlyCall(); return R(IR.values(IR.host('req_query')));
1195
+ case 'size': notCall(); return R(IR.get(IR.keys(IR.host('req_query')), 'length'));
1196
+ default: this.fail(`searchParams.${key} is not supported (use get/has/getAll/entries/keys/values/toString)`, node);
1197
+ }
1198
+ break;
1199
+ case 'ParamsCtx':
1200
+ if (key === 'params') return S('Params');
1201
+ if (quiet) return null;
1202
+ this.fail(`context.${key} is not supported (only context.params)`, node);
1203
+ break;
1204
+ case 'Params': {
1205
+ if (quiet) return null;
1206
+ if (isCall) this.fail('params is a plain object', node);
1207
+ const p = this.params.find((x) => x.name === key);
1208
+ if (!p) this.warn(`params.${key} is not a dynamic segment of ${this.fnMeta.routePath}; it will be undefined`, node);
1209
+ return R(IR.params('param', this.paramsConst, key, p ? p.catchAll : false));
1210
+ }
1211
+ case 'Kv': {
1212
+ if (quiet) return null;
1213
+ onlyCall();
1214
+ this.usesKv = true;
1215
+ const rt = KV_METHODS[key];
1216
+ if (!rt) this.fail(`kv.${key}() is not supported on chain (supported: ${Object.keys(KV_METHODS).join(', ')})`, node);
1217
+ const k = arg(0);
1218
+ switch (rt) {
1219
+ case 'kv_get': return R(IR.host('kv_get', [k], { q: true }));
1220
+ case 'kv_exists': return R(IR.host('kv_exists', [k], { q: true }));
1221
+ case 'kv_del': return R(IR.host('kv_del', [k], { q: true }));
1222
+ case 'kv_set': {
1223
+ if (callArgs.length > 2) this.warn('kv.set() options (ex/px/nx) are ignored: keys do not expire on chain', node);
1224
+ return R(IR.host('kv_set', [k, arg(1)], { q: true }));
1225
+ }
1226
+ case 'kv_incr': return R(IR.host('kv_incrby', [k, IR.num(1)], { q: true }));
1227
+ case 'kv_decr': return R(IR.host('kv_incrby', [k, IR.num(-1)], { q: true }));
1228
+ case 'kv_incrby': return R(IR.host('kv_incrby', [k, arg(1)], { q: true }));
1229
+ case 'kv_decrby': return R(IR.host('kv_incrby', [k, IR.unary('neg', arg(1))], { q: true }));
1230
+ default: break;
1231
+ }
1232
+ break;
1233
+ }
1234
+ case 'KvFactory': this.fail('createClient must be called: `const kv = createClient({...})`', node); break;
1235
+ case 'Namespace': {
1236
+ const bound = bindImport(sp.source, [{ local: key, imported: key }], { file: this.file, line: node.loc?.start.line });
1237
+ const b = bound[0];
1238
+ if (!b.special) { if (quiet) return null; this.fail(`\`${key}\` from ${sp.source} has no runtime value`, node); }
1239
+ if (isCall) return this.callSpecial(b.special, callArgs, node, pre);
1240
+ return { special: b.special, pre };
1241
+ }
1242
+ case 'NextResponse':
1243
+ case 'ResponseCls':
1244
+ if (quiet) return null;
1245
+ onlyCall();
1246
+ switch (key) {
1247
+ case 'json': return R(IR.resp('json', arg(0), arg(1)));
1248
+ case 'redirect': return R(IR.resp('redirect', arg(0), arg(1)));
1249
+ case 'rewrite': this.fail('NextResponse.rewrite() is a middleware/edge feature; not supported', node); break;
1250
+ case 'next': this.fail('NextResponse.next() is only meaningful in middleware', node); break;
1251
+ case 'error': return R(IR.resp('raw', IR.null(), IR.obj([{ key: 'status', value: IR.num(500) }])));
1252
+ default: this.fail(`${sp.t === 'NextResponse' ? 'NextResponse' : 'Response'}.${key}() is not supported (use json / redirect / new Response)`, node);
1253
+ }
1254
+ break;
1255
+ case 'DateNow':
1256
+ if (quiet) return null;
1257
+ onlyCall();
1258
+ switch (key) {
1259
+ case 'toISOString': case 'toJSON': return R(IR.host('now_iso'));
1260
+ case 'getTime': case 'valueOf': return R(IR.host('now_ms'));
1261
+ default: this.fail(`Date.${key}() is not supported on chain; only Date.now(), new Date().toISOString() and .getTime() are (there is no timezone database on chain)`, node);
1262
+ }
1263
+ break;
1264
+ case 'Global': return this.globalMember(sp.name, key, node, callArgs, quiet, pre);
1265
+ case 'Process':
1266
+ if (key === 'env') return S('ProcessEnv');
1267
+ if (quiet) return null;
1268
+ this.fail(`process.${key} is not available on chain`, node);
1269
+ break;
1270
+ case 'ProcessEnv': {
1271
+ if (quiet) return null;
1272
+ if (isCall) this.fail('process.env is a plain object', node);
1273
+ this.env.add(key);
1274
+ return R(IR.host('env', [IR.str(key)]));
1275
+ }
1276
+ case 'HelperRef': case 'LocalFnRef':
1277
+ if (quiet) return null;
1278
+ this.fail(`\`${sp.name}.${key}\`: functions have no properties on chain`, node);
1279
+ break;
1280
+ case 'MathConst': case 'ValueLike': {
1281
+ if (quiet) return null;
1282
+ const m = this.materialize(sp, node, true);
1283
+ if (isCall) return this.valMethodCall({ ir: m }, key, callArgs, node, null);
1284
+ return R(IR.get(m, key));
1285
+ }
1286
+ case 'Void':
1287
+ if (quiet) return null;
1288
+ this.fail('this expression has no value (a response call)', node);
1289
+ break;
1290
+ default:
1291
+ if (quiet) return null;
1292
+ this.fail(`\`${describeSpecial(sp)}.${key}\` is not supported`, node);
1293
+ }
1294
+ return null;
1295
+ }
1296
+
1297
+ globalMember(g, key, node, callArgs, quiet, pre) {
1298
+ const isCall = callArgs !== null && callArgs !== undefined;
1299
+ const args = () => callArgs.map((a) => this.lowerArg(a));
1300
+ const arg = (i) => (callArgs[i] ? this.lowerArg(callArgs[i]) : IR.undef());
1301
+ const R = (ir) => ({ ir: IR.pre(pre, ir) });
1302
+ const S = (t, extra) => ({ special: SP(t, extra), pre });
1303
+ switch (g) {
1304
+ case 'Math':
1305
+ if (key === 'random') { if (quiet) return null; this.fail('Math.random() is nondeterministic: every validator must compute the same result, so randomness is not available on chain (derive it from the slot or a hash of the request instead)', node); }
1306
+ if (key in MATH_CONSTS) return S('MathConst', { v: MATH_CONSTS[key] });
1307
+ if (quiet) return null;
1308
+ if (!isCall) this.fail(`Math.${key} is not supported`, node);
1309
+ if (!MATH_FUNCS.has(key)) this.fail(`Math.${key}() is not supported by the on-chain runtime (supported: ${[...MATH_FUNCS].join(', ')})`, node);
1310
+ return R(IR.math(key, args()));
1311
+ case 'JSON':
1312
+ if (quiet) return null;
1313
+ if (!isCall) this.fail(`JSON.${key} must be called`, node);
1314
+ if (key === 'parse') return R(IR.jsonparse(arg(0)));
1315
+ if (key === 'stringify') { if (callArgs.length > 1) this.warn('JSON.stringify replacer/indent arguments are ignored', node); return R(IR.jsonstringify(arg(0))); }
1316
+ this.fail(`JSON.${key} is not supported`, node);
1317
+ break;
1318
+ case 'Object':
1319
+ if (quiet) return null;
1320
+ if (!isCall) this.fail(`Object.${key} must be called`, node);
1321
+ switch (key) {
1322
+ case 'keys': return R(IR.keys(arg(0)));
1323
+ case 'values': return R(IR.values(arg(0)));
1324
+ case 'entries': return R(IR.entries(arg(0)));
1325
+ case 'assign': return R(IR.helper('assign', args()));
1326
+ case 'fromEntries': return R(IR.helper('from_entries', [arg(0)]));
1327
+ case 'freeze': case 'seal': return R(arg(0));
1328
+ case 'hasOwn': return R(IR.in(arg(1), arg(0)));
1329
+ default: this.fail(`Object.${key}() is not supported (keys, values, entries, assign, fromEntries, hasOwn)`, node);
1330
+ }
1331
+ break;
1332
+ case 'Array':
1333
+ if (quiet) return null;
1334
+ if (!isCall) this.fail(`Array.${key} must be called`, node);
1335
+ if (key === 'isArray') return R(IR.isarray(arg(0)));
1336
+ if (key === 'from') { if (callArgs.length > 1) this.fail('Array.from(x, mapFn) is not supported; call .map() on the result', node); return R(IR.helper('array_from', [arg(0)])); }
1337
+ if (key === 'of') return R(IR.arr(args().map((e) => ({ e }))));
1338
+ this.fail(`Array.${key}() is not supported`, node);
1339
+ break;
1340
+ case 'Number':
1341
+ if (key === 'MAX_SAFE_INTEGER') return S('MathConst', { v: Number.MAX_SAFE_INTEGER });
1342
+ if (key === 'MIN_SAFE_INTEGER') return S('MathConst', { v: Number.MIN_SAFE_INTEGER });
1343
+ if (key === 'EPSILON') return S('MathConst', { v: Number.EPSILON });
1344
+ if (key === 'MAX_VALUE') return S('MathConst', { v: Number.MAX_VALUE });
1345
+ if (key === 'POSITIVE_INFINITY') return { ir: IR.raw('Val::Num(f64::INFINITY)'), pre };
1346
+ if (key === 'NEGATIVE_INFINITY') return { ir: IR.raw('Val::Num(f64::NEG_INFINITY)'), pre };
1347
+ if (key === 'NaN') return { ir: IR.raw('Val::Num(f64::NAN)'), pre };
1348
+ if (quiet) return null;
1349
+ if (!isCall) this.fail(`Number.${key} is not supported`, node);
1350
+ if (key === 'parseInt' || key === 'parseFloat') return R(IR.global(key, args()));
1351
+ if (['isInteger', 'isNaN', 'isFinite', 'isSafeInteger'].includes(key)) return R(IR.helper('num', [IR.str(key), arg(0)]));
1352
+ this.fail(`Number.${key}() is not supported`, node);
1353
+ break;
1354
+ case 'String':
1355
+ if (quiet) return null;
1356
+ this.fail(`String.${key}() is not supported`, node);
1357
+ break;
1358
+ case 'Promise':
1359
+ if (quiet) return null;
1360
+ if (!isCall) this.fail(`Promise.${key} is not supported`, node);
1361
+ if (key === 'all' || key === 'allSettled' || key === 'resolve') return R(arg(0));
1362
+ this.fail(`Promise.${key}() is not supported (everything is synchronous on chain)`, node);
1363
+ break;
1364
+ case 'Date':
1365
+ if (quiet) return null;
1366
+ if (!isCall) this.fail(`Date.${key} is not supported`, node);
1367
+ if (key === 'now') return R(IR.host('now_ms'));
1368
+ this.fail(`Date.${key}() is not supported on chain (only Date.now() and new Date().toISOString()/getTime())`, node);
1369
+ break;
1370
+ case 'console':
1371
+ if (quiet) return null;
1372
+ if (!isCall) this.fail('console must be called', node);
1373
+ if (['log', 'error', 'warn', 'info', 'debug', 'trace'].includes(key)) return R(IR.log(args()));
1374
+ return R(IR.undef());
1375
+ case 'process':
1376
+ if (key === 'env') return S('ProcessEnv');
1377
+ if (quiet) return null;
1378
+ this.fail(`process.${key} is not available on chain (only process.env)`, node);
1379
+ break;
1380
+ case 'Response': return this.memberOf(SP('ResponseCls'), key, node, callArgs, quiet, pre);
1381
+ case 'globalThis':
1382
+ if (quiet) return null;
1383
+ this.fail('globalThis is not supported', node);
1384
+ break;
1385
+ case 'Error': case 'TypeError': case 'RangeError': case 'SyntaxError':
1386
+ if (quiet) return null;
1387
+ this.fail(`${g}.${key} is not supported`, node);
1388
+ break;
1389
+ default:
1390
+ if (quiet) return null;
1391
+ this.fail(`\`${g}.${key}\` is not supported`, node);
1392
+ }
1393
+ return null;
1394
+ }
1395
+
1396
+ // ---- calls
1397
+
1398
+ lowerArg(node) {
1399
+ if (node.type === 'SpreadElement') this.fail('spread arguments are not supported', node);
1400
+ return this.val(this.lowerExpr(node), node);
1401
+ }
1402
+
1403
+ lowerCall(node) {
1404
+ if (node.optional) this.fail('optional call `?.()` is not supported', node);
1405
+ const callee = node.callee;
1406
+ if (callee.type === 'MemberExpression') {
1407
+ // method call: special.method(...) or value.method(...)
1408
+ const objR = this.lowerExpr(callee.object);
1409
+ if (callee.optional && !objR.special) {
1410
+ const label = this.chainLabel;
1411
+ if (label) { label.used = true; objR.ir = IR.optcheck(label.label, objR.ir); }
1412
+ }
1413
+ let key;
1414
+ if (callee.computed) {
1415
+ if (callee.property.type === 'Literal' && typeof callee.property.value === 'string') key = callee.property.value;
1416
+ else this.fail('computed method calls `obj[name]()` are not supported', node);
1417
+ } else key = callee.property.name;
1418
+ if (objR.special) return this.memberOf(objR.special, key, node, node.arguments, false, objR.pre || []);
1419
+ const lv = this.lvalueIfPath(callee.object);
1420
+ return this.valMethodCall(objR, key, node.arguments, node, lv);
1421
+ }
1422
+ if (callee.type === 'Identifier') {
1423
+ const name = callee.name;
1424
+ const found = this.cur.scope.lookup(name);
1425
+ if (found) {
1426
+ let b = found.binding;
1427
+ if (found.scope === this.moduleScope) b = this.resolveModule(b, callee);
1428
+ if (b.kind === 'helper' || b.kind === 'localfn') return { ir: IR.callh(b.rust, node.arguments.map((a) => this.lowerArg(a))) };
1429
+ if (b.kind === 'special') return this.callSpecial(b.sp, node.arguments, node, []);
1430
+ if (b.kind === 'local') this.fail(`\`${name}\` is a value, not a function: functions are not first-class values on chain`, node);
1431
+ this.fail(`cannot call \`${name}\``, node);
1432
+ }
1433
+ if (name in BANNED_GLOBALS) this.fail(BANNED_GLOBALS[name], node);
1434
+ if (GLOBAL_FUNCS.has(name)) return { ir: IR.global(name, node.arguments.map((a) => this.lowerArg(a))) };
1435
+ if (name === 'Error' || name === 'TypeError' || name === 'RangeError' || name === 'SyntaxError') return { ir: IR.newerror(name, node.arguments.map((a) => this.lowerArg(a))) };
1436
+ if (name === 'Symbol') this.fail(BANNED_GLOBALS.Symbol, node);
1437
+ if (KNOWN_GLOBALS.has(name)) this.fail(`\`${name}()\` cannot be called directly`, node);
1438
+ this.fail(`call to unknown function \`${name}\` (not declared in this file, not imported)`, node);
1439
+ }
1440
+ if (isFunctionNode(callee)) {
1441
+ // IIFE
1442
+ const args = node.arguments.map((a) => this.lowerArg(a));
1443
+ const fnIr = this.withLoopMarker('closure', () => this.lowerFunctionBody(callee, this.cur.scope, 'value', { boundary: 'callback', name: 'iife' }));
1444
+ return { ir: IR.iife(fnIr, args) };
1445
+ }
1446
+ if (callee.type === 'ChainExpression') this.fail('optional call `?.()` is not supported', node);
1447
+ this.fail(`unsupported call target ${callee.type}`, node);
1448
+ return null;
1449
+ }
1450
+
1451
+ callSpecial(sp, argNodes, node, pre) {
1452
+ switch (sp.t) {
1453
+ case 'KvFactory': this.usesKv = true; return { special: SP('Kv'), pre };
1454
+ case 'HelperRef': case 'LocalFnRef': return { ir: IR.pre(pre, IR.callh(sp.rust, argNodes.map((a) => this.lowerArg(a)))) };
1455
+ case 'Global': {
1456
+ const name = sp.name;
1457
+ if (GLOBAL_FUNCS.has(name)) return { ir: IR.pre(pre, IR.global(name, argNodes.map((a) => this.lowerArg(a)))) };
1458
+ if (['Error', 'TypeError', 'RangeError', 'SyntaxError'].includes(name)) return { ir: IR.pre(pre, IR.newerror(name, argNodes.map((a) => this.lowerArg(a)))) };
1459
+ this.fail(`\`${name}()\` cannot be called directly`, node);
1460
+ break;
1461
+ }
1462
+ default: this.fail(`\`${describeSpecial(sp)}\` is not callable`, node);
1463
+ }
1464
+ return null;
1465
+ }
1466
+
1467
+ /** Is `node` an lvalue path rooted at a local variable? Returns the lv or null. */
1468
+ lvalueIfPath(node) {
1469
+ let n = node;
1470
+ while (n.type === 'MemberExpression') { if (n.optional) return null; n = n.object; }
1471
+ if (n.type !== 'Identifier') return null;
1472
+ const found = this.cur.scope.lookup(n.name);
1473
+ if (!found || found.binding.kind !== 'local' || found.scope === this.moduleScope) return null;
1474
+ if (found.boundaries.includes('closure')) return null;
1475
+ // build without side effects (member keys may have expressions — those are lowered again below, fine for pure keys)
1476
+ try {
1477
+ return this.lowerLvalue(node);
1478
+ } catch (e) {
1479
+ if (e instanceof Ineligible) return null;
1480
+ throw e;
1481
+ }
1482
+ }
1483
+
1484
+ valMethodCall(objR, name, argNodes, node, lv) {
1485
+ const recv = objR.ir;
1486
+ if (CALLBACK_METHODS.has(name)) {
1487
+ const cbNode = argNodes[0];
1488
+ let cb = null;
1489
+ if (cbNode && isFunctionNode(cbNode)) {
1490
+ const fnIr = this.withLoopMarker('callback', () => this.lowerFunctionBody(cbNode, this.cur.scope, 'value', { boundary: 'callback', name }));
1491
+ cb = { kind: 'fn', fn: fnIr };
1492
+ } else if (cbNode && cbNode.type === 'Identifier') {
1493
+ const r = this.lowerExpr(cbNode);
1494
+ if (r.special && (r.special.t === 'HelperRef' || r.special.t === 'LocalFnRef')) cb = { kind: 'ref', name: r.special.rust };
1495
+ else this.fail(`.${name}() needs an inline function or a declared helper as its callback`, cbNode);
1496
+ } else if (cbNode || name !== 'sort') {
1497
+ this.fail(`.${name}() needs an inline function or a declared helper as its callback`, cbNode || node);
1498
+ }
1499
+ const rest = argNodes.slice(1).map((a) => this.lowerArg(a));
1500
+ if (name === 'sort' && !cb) cb = { kind: 'default-sort' };
1501
+ return { ir: IR.callm(recv, MUTATING_METHODS.has(name) ? lv : null, name, rest, cb) };
1502
+ }
1503
+ if (!RUNTIME_METHODS.has(name)) this.fail(`.${name}() is not implemented by the on-chain runtime`, node);
1504
+ for (const a of argNodes) if (isFunctionNode(a)) this.fail(`.${name}() with a function argument is not supported`, a);
1505
+ const args = argNodes.map((a) => this.lowerArg(a));
1506
+ return { ir: IR.callm(recv, MUTATING_METHODS.has(name) ? lv : null, name, args, null) };
1507
+ }
1508
+
1509
+ lowerNew(node) {
1510
+ const callee = node.callee;
1511
+ const args = node.arguments;
1512
+ let sp = null;
1513
+ if (callee.type === 'Identifier') {
1514
+ const found = this.cur.scope.lookup(callee.name);
1515
+ if (found) {
1516
+ let b = found.binding;
1517
+ if (found.scope === this.moduleScope) b = this.resolveModule(b, callee);
1518
+ if (b.kind === 'special') sp = b.sp;
1519
+ else if (b.kind === 'class') this.fail('classes are not supported on chain', node);
1520
+ else this.fail(`\`new ${callee.name}\`: not a constructor`, node);
1521
+ } else if (callee.name in BANNED_GLOBALS) this.fail(BANNED_GLOBALS[callee.name], node);
1522
+ else if (KNOWN_GLOBALS.has(callee.name)) sp = SP('Global', { name: callee.name });
1523
+ else this.fail(`\`new ${callee.name}\`: unknown constructor`, node);
1524
+ } else if (callee.type === 'MemberExpression') {
1525
+ const r = this.peekSpecial(callee);
1526
+ if (r) sp = r;
1527
+ else this.fail('`new` on a computed constructor is not supported', node);
1528
+ } else this.fail('unsupported `new` target', node);
1529
+ const name = sp.t === 'Global' ? sp.name : sp.t;
1530
+ const arg = (i) => (args[i] ? this.lowerArg(args[i]) : IR.undef());
1531
+ switch (name) {
1532
+ case 'Response': case 'NextResponse': return { ir: IR.resp('raw', arg(0), arg(1)) };
1533
+ case 'URL': {
1534
+ // new URL(request.url [, base]) / new URL(req.url, base) → the request URL object
1535
+ const a0 = args[0];
1536
+ if (a0 && a0.type === 'MemberExpression') {
1537
+ const base = this.peekSpecial(a0.object);
1538
+ if (base && (base.t === 'WebReq' || base.t === 'Req') && !a0.computed && a0.property.name === 'url') return { special: SP('UrlObj') };
1539
+ if (base && base.t === 'WebReq' && !a0.computed && a0.property.name === 'nextUrl') return { special: SP('UrlObj') };
1540
+ }
1541
+ if (a0 && this.peekSpecial(a0)?.t === 'UrlObj') return { special: SP('UrlObj') };
1542
+ return { ir: IR.helper('url', [arg(0), arg(1)]) };
1543
+ }
1544
+ case 'Date':
1545
+ if (args.length === 0) return { special: SP('DateNow') };
1546
+ this.fail('new Date(value) is not supported on chain (no calendar/timezone library); only new Date() with toISOString()/getTime()', node);
1547
+ break;
1548
+ case 'Error': case 'TypeError': case 'RangeError': case 'SyntaxError':
1549
+ return { ir: IR.newerror(name, args.map((a) => this.lowerArg(a))) };
1550
+ case 'Promise': this.fail('new Promise() is not supported (everything is synchronous on chain)', node); break;
1551
+ case 'Object': return { ir: IR.obj([]) };
1552
+ case 'Array': if (args.length === 0) return { ir: IR.arr([]) }; this.fail('new Array(n) is not supported; use a literal', node); break;
1553
+ default: this.fail(`\`new ${name}\` is not supported`, node);
1554
+ }
1555
+ return null;
1556
+ }
1557
+ }
1558
+
1559
+ // ---------------------------------------------------------------- tables & utils
1560
+
1561
+ const CMP_OPS = { '===': 'strict_eq', '!==': 'strict_ne', '==': 'loose_eq', '!=': 'loose_ne', '<': 'lt', '<=': 'le', '>': 'gt', '>=': 'ge' };
1562
+ const BIN_OPS = { '+': 'add', '-': 'sub', '*': 'mul', '/': 'div', '%': 'rem', '**': 'pow', '&': 'bit_and', '|': 'bit_or', '^': 'bit_xor', '<<': 'shl', '>>': 'shr', '>>>': 'ushr' };
1563
+
1564
+ function describeSpecial(sp) {
1565
+ switch (sp.t) {
1566
+ case 'Req': return 'req'; case 'Res': return 'res'; case 'WebReq': return 'request'; case 'ParamsCtx': return 'context';
1567
+ case 'Params': return 'params'; case 'SearchParams': return 'searchParams'; case 'UrlObj': return 'URL'; case 'Kv': return 'kv';
1568
+ case 'Global': return sp.name; case 'HelperRef': case 'LocalFnRef': return `function ${sp.name}`;
1569
+ default: return sp.t;
1570
+ }
1571
+ }
1572
+
1573
+ function routeParamIsCatchAll(routePath, name) {
1574
+ if (!routePath) return false;
1575
+ return routePath.includes(`[...${name}]`) || routePath.includes(`[[...${name}]]`);
1576
+ }
1577
+
1578
+ /** First captured name that the function body assigns to (any depth), or null. */
1579
+ function findMutation(fnNode, names) {
1580
+ let found = null;
1581
+ walk(fnNode.body, (n) => {
1582
+ if (found) return false;
1583
+ if (n.type === 'AssignmentExpression' || n.type === 'UpdateExpression') {
1584
+ let t = n.type === 'AssignmentExpression' ? n.left : n.argument;
1585
+ while (t.type === 'MemberExpression') t = t.object;
1586
+ if (t.type === 'Identifier' && names.has(t.name)) found = t.name;
1587
+ if (t.type === 'ObjectPattern' || t.type === 'ArrayPattern') for (const nm of patternNames(t)) if (names.has(nm)) found = nm;
1588
+ }
1589
+ return true;
1590
+ });
1591
+ return found;
1592
+ }
1593
+
1594
+ // ---------------------------------------------------------------- route lowering
1595
+
1596
+ /**
1597
+ * Lower one Vercel function (a module) into a route IR.
1598
+ * @returns {{ ir: object, kv: boolean, env: string[], envDynamic: boolean, methods: string[]|null, style: 'node'|'web', warnings: string[] }}
1599
+ */
1600
+ export function lowerRoute(mod, fn, { index = 0, file = mod.file } = {}) {
1601
+ const L = new Lowerer(mod, fn, { index, file });
1602
+ for (const s of mod.sideEffects) {
1603
+ L.fail(`${s.what} at module scope has side effects that cannot run on chain (module code runs once per Lambda instance; on chain there is no instance)`, s.node);
1604
+ }
1605
+ const route = { index, name: fn.name, file, params: L.params, paramsConst: L.paramsConst, style: null, node: null, methods: {}, helpers: L.helpers, consts: L.consts };
1606
+ const isNode = fn.style !== 'app' && !!mod.handlers.default;
1607
+ if (isNode) {
1608
+ const fnNode = resolveHandlerNode(mod.handlers.default, mod);
1609
+ route.style = 'node';
1610
+ route.node = L.lowerHandler(fnNode, 'node');
1611
+ } else {
1612
+ const names = Object.keys(mod.handlers.methods).filter((m) => HTTP_METHODS.includes(m));
1613
+ if (names.length === 0) {
1614
+ if (mod.handlers.default && fn.style === 'app') {
1615
+ L.fail('app-router route files export HTTP methods (GET, POST, ...); a default export is not a route handler', mod.handlers.default.node);
1616
+ }
1617
+ L.fail('no handler export found: expected `export default function (req, res)` or `export function GET/POST/...(request)`', mod.ast);
1618
+ }
1619
+ route.style = 'web';
1620
+ for (const m of names) {
1621
+ const fnNode = resolveHandlerNode(mod.handlers.methods[m], mod);
1622
+ route.methods[m] = L.lowerHandler(fnNode, 'web');
1623
+ }
1624
+ }
1625
+ return {
1626
+ ir: route,
1627
+ kv: L.usesKv,
1628
+ env: [...L.env],
1629
+ envDynamic: L.envDynamic,
1630
+ methods: route.style === 'web' ? Object.keys(route.methods) : null,
1631
+ style: route.style,
1632
+ warnings: L.warnings,
1633
+ };
1634
+ }