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,753 @@
1
+ // Source → AST → module shape.
2
+ //
3
+ // * `stripTypes` is a conservative, position-preserving TypeScript stripper
4
+ // (removed text becomes spaces, newlines stay) so acorn can parse `.ts`
5
+ // route files and every reported line number still points at the source.
6
+ // * `parseModule` runs acorn (module first, script second for CommonJS).
7
+ // * `readModule` finds the Lambda handler(s): `export default (req, res)`,
8
+ // app-router `export function GET/POST/...`, `module.exports = ...`, and
9
+ // collects imports, module-scope declarations and config exports.
10
+ import path from 'node:path';
11
+ import * as acorn from 'acorn';
12
+ import { Ineligible } from '../eligibility.js';
13
+
14
+ export const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'];
15
+ const TS_EXTS = new Set(['.ts', '.tsx', '.mts', '.cts']);
16
+
17
+ // ---------------------------------------------------------------- code mask
18
+
19
+ const isWord = (c) => /[A-Za-z0-9_$]/.test(c);
20
+ const isSpace = (c) => c === ' ' || c === '\t' || c === '\n' || c === '\r';
21
+ const REGEX_PREV_WORDS = new Set(['return', 'typeof', 'instanceof', 'in', 'of', 'new', 'delete', 'void', 'throw', 'case', 'do', 'else', 'yield', 'await']);
22
+
23
+ /**
24
+ * 1 for characters that are code, 0 for characters inside strings, template
25
+ * literal text, comments and regex literals. Template `${...}` holes are code.
26
+ */
27
+ export function codeMask(src) {
28
+ const n = src.length;
29
+ const mask = new Uint8Array(n).fill(1);
30
+ const blank = (a, b) => { for (let k = a; k < b && k < n; k++) mask[k] = 0; };
31
+ function scanString(i) {
32
+ const q = src[i];
33
+ let j = i + 1;
34
+ while (j < n) {
35
+ const c = src[j];
36
+ if (c === '\\') { j += 2; continue; }
37
+ if (c === q || c === '\n') { j++; break; }
38
+ j++;
39
+ }
40
+ blank(i, j);
41
+ return j;
42
+ }
43
+ function scanRegex(i) {
44
+ let j = i + 1;
45
+ let inClass = false;
46
+ while (j < n) {
47
+ const c = src[j];
48
+ if (c === '\\') { j += 2; continue; }
49
+ if (c === '\n') break;
50
+ if (inClass) { if (c === ']') inClass = false; j++; continue; }
51
+ if (c === '[') { inClass = true; j++; continue; }
52
+ if (c === '/') { j++; break; }
53
+ j++;
54
+ }
55
+ while (j < n && /[a-z]/.test(src[j])) j++;
56
+ blank(i, j);
57
+ return j;
58
+ }
59
+ function scanTemplate(i) {
60
+ mask[i] = 0;
61
+ let j = i + 1;
62
+ while (j < n) {
63
+ const c = src[j];
64
+ if (c === '\\') { mask[j] = 0; if (j + 1 < n) mask[j + 1] = 0; j += 2; continue; }
65
+ if (c === '`') { mask[j] = 0; return j + 1; }
66
+ if (c === '$' && src[j + 1] === '{') {
67
+ mask[j] = 0; mask[j + 1] = 0;
68
+ const k = scanCode(j + 2, true);
69
+ if (k < n) mask[k] = 0;
70
+ j = k + 1;
71
+ continue;
72
+ }
73
+ mask[j] = 0;
74
+ j++;
75
+ }
76
+ return j;
77
+ }
78
+ function scanCode(i, stopAtBrace) {
79
+ let depth = 0;
80
+ let lastSig = '';
81
+ let lastWord = '';
82
+ while (i < n) {
83
+ const c = src[i];
84
+ const d = src[i + 1];
85
+ if (c === '/' && d === '/') { const e = src.indexOf('\n', i); const end = e < 0 ? n : e; blank(i, end); i = end; continue; }
86
+ if (c === '/' && d === '*') { const e = src.indexOf('*/', i + 2); const end = e < 0 ? n : e + 2; blank(i, end); i = end; continue; }
87
+ if (c === '"' || c === "'") { i = scanString(i); lastSig = '"'; lastWord = ''; continue; }
88
+ if (c === '`') { i = scanTemplate(i); lastSig = '"'; lastWord = ''; continue; }
89
+ if (c === '/') {
90
+ const prevIsOperand = lastSig === ')' || lastSig === ']' || lastSig === '"' || (isWord(lastSig) && !REGEX_PREV_WORDS.has(lastWord));
91
+ if (!prevIsOperand) { i = scanRegex(i); lastSig = ')'; lastWord = ''; continue; }
92
+ }
93
+ if (c === '{') depth++;
94
+ if (c === '}') { if (depth === 0 && stopAtBrace) return i; depth--; }
95
+ if (!isSpace(c)) { lastSig = c; lastWord = isWord(c) ? lastWord + c : ''; }
96
+ i++;
97
+ }
98
+ return i;
99
+ }
100
+ scanCode(0, false);
101
+ return mask;
102
+ }
103
+
104
+ // ---------------------------------------------------------------- TypeScript stripping
105
+
106
+ /**
107
+ * Best-effort removal of TypeScript syntax: type annotations on params /
108
+ * returns / variables, `as` / `satisfies` casts, generics, `import type`,
109
+ * `interface` / `type` declarations, non-null `!`, optional-param `?`.
110
+ * Positions are preserved (removed text becomes spaces).
111
+ */
112
+ export function stripTypes(src) {
113
+ const mask = codeMask(src);
114
+ const out = src.split('');
115
+ const n = out.length;
116
+ const code = (i) => i >= 0 && i < n && mask[i] === 1;
117
+ const ch = (i) => (i >= 0 && i < n ? out[i] : '');
118
+ const blank = (a, b) => { for (let k = a; k < b && k < n; k++) if (out[k] !== '\n') out[k] = ' '; };
119
+ const skipWs = (i) => { while (i < n && (!code(i) || isSpace(out[i]))) i++; return i; };
120
+ const skipWsNoNl = (i) => { while (i < n && (!code(i) || out[i] === ' ' || out[i] === '\t' || out[i] === '\r')) i++; return i; };
121
+ const wordAt = (i) => { let j = i; while (j < n && code(j) && isWord(out[j])) j++; return out.slice(i, j).join(''); };
122
+ const prevSig = (i) => { let j = i - 1; while (j >= 0 && (isSpace(out[j]) || (!code(j) && out[j] !== '"' && out[j] !== "'" && out[j] !== '`' && mask[j] === 0 && isBlankComment(j)))) j--; return j; };
123
+ // A masked char that belongs to a comment (not a string) — treat like whitespace when looking backwards.
124
+ function isBlankComment(j) {
125
+ // walk back to the start of the masked run and see whether it begins with // or /*
126
+ let k = j;
127
+ while (k > 0 && mask[k - 1] === 0) k--;
128
+ return out[k] === '/' && (out[k + 1] === '/' || out[k + 1] === '*');
129
+ }
130
+ const OPEN = { '(': ')', '[': ']', '{': '}', '<': '>' };
131
+ /** Index of the bracket closing the one at i (only code chars count). `<` groups skip `=>`. */
132
+ function matchClose(i) {
133
+ const o = out[i];
134
+ const c = OPEN[o];
135
+ let depth = 0;
136
+ for (let j = i; j < n; j++) {
137
+ if (!code(j)) continue;
138
+ const x = out[j];
139
+ if (o === '<' && x === '=' && out[j + 1] === '>') { j++; continue; }
140
+ if (x === o) depth++;
141
+ else if (x === c) { depth--; if (depth === 0) return j; }
142
+ else if (o === '<' && (x === ';' || x === '\n')) return -1;
143
+ }
144
+ return -1;
145
+ }
146
+ /**
147
+ * Scan a type starting at i (first char after `:`/`as`). Returns the index just
148
+ * past the type. `stops` are chars that end it at depth 0; `=>` ends it unless
149
+ * the type started with a parenthesized group (function type).
150
+ */
151
+ function scanType(i, stops) {
152
+ i = skipWs(i);
153
+ let j = i;
154
+ let first = true;
155
+ let startedWithParen = false;
156
+ let sawArrow = false;
157
+ while (j < n) {
158
+ if (!code(j)) { j++; continue; }
159
+ const x = out[j];
160
+ if (x === '\n') {
161
+ // a type continues on the next line only with a continuation token
162
+ const k = skipWs(j);
163
+ if (k < n && (out[k] === '|' || out[k] === '&' || out[k] === '.' || (out[k] === '<' && !first))) { j = k; continue; }
164
+ return j;
165
+ }
166
+ if (x === ' ' || x === '\t' || x === '\r') { j++; continue; }
167
+ if (x === '=' && out[j + 1] === '>') {
168
+ if (startedWithParen && !sawArrow) { sawArrow = true; j += 2; first = false; continue; }
169
+ return j;
170
+ }
171
+ if (stops.includes(x)) return j;
172
+ if (x === '{' && !first) return j;
173
+ if ((x === '|' && out[j + 1] === '|') || (x === '&' && out[j + 1] === '&')) return j;
174
+ if (x in OPEN) {
175
+ if (first && x === '(') startedWithParen = true;
176
+ const m = matchClose(j);
177
+ if (m < 0) return j;
178
+ j = m + 1;
179
+ first = false;
180
+ continue;
181
+ }
182
+ if (x === ')' || x === ']' || x === '}' || x === '>') return j;
183
+ if (isWord(x) || x === '|' || x === '&' || x === '.' || x === '?' || x === ':' || x === '-' || x === '+') {
184
+ // keywords that continue a type
185
+ j++;
186
+ first = false;
187
+ continue;
188
+ }
189
+ return j;
190
+ }
191
+ return j;
192
+ }
193
+ // -- 0. protect import/export { ... } groups from `as` stripping, remove `type` specifiers
194
+ const protectedRanges = [];
195
+ for (let i = 0; i < n; i++) {
196
+ if (!code(i)) continue;
197
+ if (!(out[i] === 'i' || out[i] === 'e') || (i > 0 && isWord(ch(i - 1)))) continue;
198
+ const w = wordAt(i);
199
+ if (w !== 'import' && w !== 'export') continue;
200
+ let j = skipWs(i + w.length);
201
+ const w2 = wordAt(j);
202
+ if (w2 === 'type') {
203
+ // `import type X from`, `export type { X }`, `export type X = ...`
204
+ const k = skipWs(j + 4);
205
+ if (w === 'import' || out[k] === '{') {
206
+ let end = k;
207
+ if (out[k] === '{') { const m = matchClose(k); end = m < 0 ? k : m + 1; }
208
+ // to end of statement: the module string or `;`/newline
209
+ while (end < n && out[end] !== ';' && out[end] !== '\n') end++;
210
+ blank(i, end + (out[end] === ';' ? 1 : 0));
211
+ continue;
212
+ }
213
+ // export type X = T;
214
+ let e = k;
215
+ while (e < n && code(e) && out[e] !== '=' && out[e] !== '\n') e++;
216
+ if (out[e] === '=') e = scanType(e + 1, [';']);
217
+ blank(i, e + (out[e] === ';' ? 1 : 0));
218
+ continue;
219
+ }
220
+ if (w === 'export' && (w2 === 'interface' || w2 === 'declare' || w2 === 'abstract')) continue; // handled below
221
+ if (w === 'export' && w2 === 'default') { j = skipWs(j + 7); }
222
+ if (out[j] === '*') { j = skipWs(j + 1); if (wordAt(j) === 'as') { j = skipWs(j + 2); j += wordAt(j).length; } }
223
+ if (out[j] === '{') {
224
+ const m = matchClose(j);
225
+ if (m > 0) {
226
+ protectedRanges.push([j, m]);
227
+ // `type X,` / `type X as Y,` inside the braces
228
+ let k = j + 1;
229
+ while (k < m) {
230
+ k = skipWs(k);
231
+ if (k >= m) break;
232
+ if (wordAt(k) === 'type' && isSpace(ch(k + 4))) {
233
+ let e = skipWs(k + 4);
234
+ e += wordAt(e).length;
235
+ let e2 = skipWs(e);
236
+ if (wordAt(e2) === 'as') { e2 = skipWs(e2 + 2); e2 += wordAt(e2).length; e = e2; }
237
+ e2 = skipWs(e);
238
+ if (out[e2] === ',') e = e2 + 1;
239
+ blank(k, e);
240
+ k = e;
241
+ continue;
242
+ }
243
+ while (k < m && out[k] !== ',') k++;
244
+ k++;
245
+ }
246
+ }
247
+ }
248
+ }
249
+ const isProtected = (i) => protectedRanges.some(([a, b]) => i > a && i < b);
250
+
251
+ // -- 1. interface / type / declare statements
252
+ for (let i = 0; i < n; i++) {
253
+ if (!code(i) || !isWord(out[i]) || (i > 0 && isWord(ch(i - 1)))) continue;
254
+ const w = wordAt(i);
255
+ if (w !== 'interface' && w !== 'type' && w !== 'declare' && w !== 'abstract') continue;
256
+ // statement start: preceded by newline / ; / } / start or `export`
257
+ const p = prevSig(i);
258
+ let stmtStart = i;
259
+ const pw = p >= 0 ? wordEndingAt(p) : '';
260
+ if (pw === 'export') stmtStart = p - 5;
261
+ else if (!(p < 0 || out[p] === ';' || out[p] === '}' || out[p] === '{' || out[p] === '\n' || lineBreakBetween(p, i))) continue;
262
+ const next = skipWs(i + w.length);
263
+ if (w === 'type') {
264
+ if (!isWord(ch(next))) continue; // `type` used as an identifier
265
+ let e = next;
266
+ while (e < n && (code(e) ? out[e] !== '=' && out[e] !== '\n' : true)) e++;
267
+ if (out[e] !== '=') continue;
268
+ e = scanType(e + 1, [';']);
269
+ blank(stmtStart, e + (out[e] === ';' ? 1 : 0));
270
+ i = e;
271
+ continue;
272
+ }
273
+ if (w === 'interface') {
274
+ let k = next;
275
+ while (k < n && out[k] !== '{') k++;
276
+ const m = matchClose(k);
277
+ if (m < 0) continue;
278
+ blank(stmtStart, m + 1);
279
+ i = m;
280
+ continue;
281
+ }
282
+ if (w === 'declare' || w === 'abstract') {
283
+ if (!isWord(ch(next))) continue;
284
+ let k = next;
285
+ let m = -1;
286
+ while (k < n && out[k] !== ';' && out[k] !== '\n') { if (code(k) && out[k] === '{') { m = matchClose(k); break; } k++; }
287
+ const end = m >= 0 ? m + 1 : k + (out[k] === ';' ? 1 : 0);
288
+ blank(stmtStart, end);
289
+ i = end;
290
+ }
291
+ }
292
+ function wordEndingAt(p) {
293
+ let s = p;
294
+ while (s >= 0 && isWord(ch(s))) s--;
295
+ return out.slice(s + 1, p + 1).join('');
296
+ }
297
+ function lineBreakBetween(a, b) {
298
+ for (let k = a; k < b; k++) if (out[k] === '\n') return true;
299
+ return false;
300
+ }
301
+
302
+ // -- 2. function generics, parameter lists, return types
303
+ function stripParamList(open) {
304
+ const close = matchClose(open);
305
+ if (close < 0) return open;
306
+ let i = open + 1;
307
+ while (i < close) {
308
+ if (!code(i)) { i++; continue; }
309
+ const x = out[i];
310
+ if (x === '(' || x === '[' || x === '{') { const m = matchClose(i); if (m < 0) break; i = m + 1; continue; }
311
+ if (x === '?') {
312
+ const k = skipWs(i + 1);
313
+ if (out[k] === ':' || out[k] === ',' || out[k] === ')') { blank(i, i + 1); i++; continue; }
314
+ }
315
+ if (x === ':') {
316
+ const e = scanType(i + 1, [',', ')', '=']);
317
+ blank(i, e);
318
+ i = e;
319
+ continue;
320
+ }
321
+ if (x === '=' && out[i + 1] !== '>') {
322
+ // default value: skip to the next top-level , or )
323
+ let k = i + 1;
324
+ while (k < close) {
325
+ if (!code(k)) { k++; continue; }
326
+ if (out[k] === '(' || out[k] === '[' || out[k] === '{') { const m = matchClose(k); if (m < 0) break; k = m + 1; continue; }
327
+ if (out[k] === ',') break;
328
+ k++;
329
+ }
330
+ i = k;
331
+ continue;
332
+ }
333
+ i++;
334
+ }
335
+ return close;
336
+ }
337
+ function stripReturnType(afterClose, needArrow) {
338
+ let k = skipWsNoNl(afterClose);
339
+ if (out[k] !== ':') return needArrow ? -1 : k;
340
+ const e = scanType(k + 1, ['{', ';', ',', ')', '=']);
341
+ const after = skipWs(e);
342
+ if (needArrow && !(out[after] === '=' && out[after + 1] === '>')) return -1;
343
+ blank(k, e);
344
+ return e;
345
+ }
346
+ for (let i = 0; i < n; i++) {
347
+ if (!code(i)) continue;
348
+ const x = out[i];
349
+ if (isWord(x) && !(i > 0 && isWord(ch(i - 1)))) {
350
+ const w = wordAt(i);
351
+ if (w === 'function' || w === 'catch') {
352
+ let j = skipWs(i + w.length);
353
+ if (out[j] === '*') j = skipWs(j + 1);
354
+ if (isWord(ch(j))) j = skipWs(j + wordAt(j).length);
355
+ if (out[j] === '<') { const m = matchClose(j); if (m > 0) { blank(j, m + 1); j = skipWs(m + 1); } }
356
+ if (out[j] === '(') {
357
+ const close = stripParamList(j);
358
+ if (w === 'function') stripReturnType(close + 1, false);
359
+ i = j; // continue scanning inside the params (arrow defaults etc.)
360
+ }
361
+ continue;
362
+ }
363
+ i += w.length - 1;
364
+ continue;
365
+ }
366
+ if (x === '(') {
367
+ const close = matchClose(i);
368
+ if (close < 0) continue;
369
+ // arrow function? ( ... ) [: T] =>
370
+ let k = skipWsNoNl(close + 1);
371
+ let isArrow = out[k] === '=' && out[k + 1] === '>';
372
+ if (!isArrow && out[k] === ':') isArrow = stripReturnType(close + 1, true) >= 0;
373
+ if (isArrow) stripParamList(i);
374
+ continue;
375
+ }
376
+ if (x === '<') {
377
+ // generic arrow `<T>(x: T) => ...` or `<T,>(...)`: previous is not an operand
378
+ const p = prevSig(i);
379
+ const pc = ch(p);
380
+ if (p < 0 || '=(,:?[{;'.includes(pc) || wordEndingAt(p) === 'return') {
381
+ const m = matchClose(i);
382
+ if (m > 0) {
383
+ const k = skipWs(m + 1);
384
+ if (out[k] === '(') {
385
+ const close = matchClose(k);
386
+ const a = close > 0 ? skipWs(close + 1) : -1;
387
+ const arrow = a >= 0 && ((out[a] === '=' && out[a + 1] === '>') || out[a] === ':');
388
+ if (arrow) blank(i, m + 1);
389
+ }
390
+ }
391
+ }
392
+ }
393
+ }
394
+
395
+ // -- 3. variable annotations: const x: T = ...
396
+ for (let i = 0; i < n; i++) {
397
+ if (!code(i) || !isWord(out[i]) || (i > 0 && isWord(ch(i - 1)))) continue;
398
+ const w = wordAt(i);
399
+ if (w !== 'const' && w !== 'let' && w !== 'var') { i += w.length - 1; continue; }
400
+ let j = skipWs(i + w.length);
401
+ if (out[j] === '{' || out[j] === '[') { const m = matchClose(j); if (m < 0) continue; j = m + 1; }
402
+ else if (isWord(ch(j))) j += wordAt(j).length;
403
+ else continue;
404
+ let k = skipWsNoNl(j);
405
+ if (out[k] === '!') { blank(k, k + 1); k = skipWsNoNl(k + 1); }
406
+ if (out[k] === ':') {
407
+ const e = scanType(k + 1, ['=', ';', ',']);
408
+ blank(k, e);
409
+ }
410
+ }
411
+
412
+ // -- 4. `as T` / `satisfies T`
413
+ for (let i = 0; i < n; i++) {
414
+ if (!code(i) || (out[i] !== 'a' && out[i] !== 's') || (i > 0 && isWord(ch(i - 1))) || isProtected(i)) continue;
415
+ const w = wordAt(i);
416
+ if (w !== 'as' && w !== 'satisfies') { i += Math.max(0, w.length - 1); continue; }
417
+ const p = prevSig(i);
418
+ if (p < 0) continue;
419
+ const pc = out[p];
420
+ const operand = isWord(pc) || pc === ')' || pc === ']' || pc === '}' || mask[p] === 0 || pc === '>';
421
+ if (!operand) continue;
422
+ if (isWord(pc)) {
423
+ const pw = wordEndingAt(p);
424
+ if (['return', 'typeof', 'case', 'in', 'of', 'import', 'export', 'new', 'await', 'yield', 'throw', 'delete', 'void'].includes(pw)) continue;
425
+ }
426
+ const after = skipWsNoNl(i + w.length);
427
+ if (!(isWord(ch(after)) || out[after] === '{' || out[after] === '(' || out[after] === '[' || mask[after] === 0)) continue;
428
+ const e = scanType(i + w.length, [',', ')', ']', '}', ';', '=', '?', ':', '!', '+', '-', '*', '/', '%', '^', '~', '<']);
429
+ blank(i, e);
430
+ i = e;
431
+ }
432
+
433
+ // -- 5. call / new generics: `name<T>(`
434
+ for (let i = 0; i < n; i++) {
435
+ if (!code(i) || out[i] !== '<') continue;
436
+ const p = i - 1;
437
+ if (!(p >= 0 && code(p) && isWord(out[p]))) continue;
438
+ const m = matchClose(i);
439
+ if (m < 0) continue;
440
+ const inner = out.slice(i + 1, m).join('');
441
+ if (/[;\n]/.test(inner) || /&&|\|\|/.test(inner) || inner.trim() === '') continue;
442
+ const k = skipWsNoNl(m + 1);
443
+ if (out[k] === '(' || out[k] === '`') blank(i, m + 1);
444
+ }
445
+
446
+ // -- 6. non-null assertions `x!`
447
+ for (let i = 1; i < n; i++) {
448
+ if (!code(i) || out[i] !== '!' || out[i + 1] === '=') continue;
449
+ const pc = out[i - 1];
450
+ if (code(i - 1) && (isWord(pc) || pc === ')' || pc === ']')) {
451
+ const k = skipWsNoNl(i + 1);
452
+ if (out[k] === '.' || out[k] === ')' || out[k] === ',' || out[k] === ';' || out[k] === ']' || out[k] === '[' || out[k] === '\n' || out[k] === '}' || out[k] === '?' || out[k] === ':' || k >= n) blank(i, i + 1);
453
+ }
454
+ }
455
+ return out.join('');
456
+ }
457
+
458
+ // ---------------------------------------------------------------- acorn
459
+
460
+ /** Parse a handler source file. Returns { ast, kind: 'esm'|'cjs', source, stripped }. Throws Ineligible. */
461
+ export function parseModule(source, { file = '' } = {}) {
462
+ const ext = path.extname(file || '').toLowerCase();
463
+ let text = source;
464
+ let stripped = false;
465
+ if (TS_EXTS.has(ext)) { text = stripTypes(source); stripped = true; }
466
+ const opts = { ecmaVersion: 'latest', locations: true, allowHashBang: true, allowAwaitOutsideFunction: true, allowReturnOutsideFunction: false };
467
+ let ast;
468
+ let esmErr;
469
+ try {
470
+ ast = acorn.parse(text, { ...opts, sourceType: 'module' });
471
+ return { ast, kind: hasEsmSyntax(ast) ? 'esm' : 'cjs', source, text, stripped };
472
+ } catch (e) {
473
+ esmErr = e;
474
+ }
475
+ try {
476
+ ast = acorn.parse(text, { ...opts, sourceType: 'script' });
477
+ return { ast, kind: 'cjs', source, text, stripped };
478
+ } catch {
479
+ const line = esmErr?.loc?.line ?? null;
480
+ let hint = '';
481
+ if (stripped) hint = ' (after TypeScript type stripping)';
482
+ else if (ext === '.jsx' || ext === '.tsx') hint = ' (JSX is not supported in route handlers)';
483
+ if (/\benum\b/.test(source)) hint += '; TypeScript enums are not supported, use a const object';
484
+ throw new Ineligible(`parse error${hint}: ${esmErr.message.replace(/\s*\(\d+:\d+\)$/, '')}`, { file, line });
485
+ }
486
+ }
487
+
488
+ function hasEsmSyntax(ast) {
489
+ return ast.body.some((s) => s.type === 'ImportDeclaration' || s.type === 'ExportNamedDeclaration' || s.type === 'ExportDefaultDeclaration' || s.type === 'ExportAllDeclaration');
490
+ }
491
+
492
+ // ---------------------------------------------------------------- AST helpers
493
+
494
+ /** Visit every node (depth-first). `visit(node, parent)` returning false skips children. */
495
+ export function walk(node, visit, parent = null) {
496
+ if (!node || typeof node.type !== 'string') return;
497
+ if (visit(node, parent) === false) return;
498
+ for (const key of Object.keys(node)) {
499
+ if (key === 'loc' || key === 'type' || key === 'start' || key === 'end') continue;
500
+ const v = node[key];
501
+ if (Array.isArray(v)) { for (const c of v) if (c && typeof c.type === 'string') walk(c, visit, node); }
502
+ else if (v && typeof v.type === 'string') walk(v, visit, node);
503
+ }
504
+ }
505
+
506
+ /** Names bound by a declaration pattern. */
507
+ export function patternNames(p, out = []) {
508
+ if (!p) return out;
509
+ switch (p.type) {
510
+ case 'Identifier': out.push(p.name); break;
511
+ case 'ObjectPattern': for (const pr of p.properties) patternNames(pr.type === 'RestElement' ? pr.argument : pr.value, out); break;
512
+ case 'ArrayPattern': for (const el of p.elements) if (el) patternNames(el, out); break;
513
+ case 'AssignmentPattern': patternNames(p.left, out); break;
514
+ case 'RestElement': patternNames(p.argument, out); break;
515
+ default: break;
516
+ }
517
+ return out;
518
+ }
519
+
520
+ const FN_TYPES = new Set(['FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression']);
521
+ export const isFunctionNode = (n) => !!n && FN_TYPES.has(n.type);
522
+
523
+ /**
524
+ * Free identifiers of a function (referenced but not declared inside it),
525
+ * with the AST node of the first reference. Property keys and member
526
+ * property names are not references.
527
+ */
528
+ export function freeVars(fnNode) {
529
+ const declared = new Set();
530
+ // params
531
+ for (const p of fnNode.params) for (const nm of patternNames(p)) declared.add(nm);
532
+ if (fnNode.id) declared.add(fnNode.id.name);
533
+ // declarations inside (all scopes flattened: conservative — a name declared anywhere inside is "local")
534
+ walk(fnNode.body, (n) => {
535
+ if (n.type === 'VariableDeclaration') for (const d of n.declarations) for (const nm of patternNames(d.id)) declared.add(nm);
536
+ else if (n.type === 'FunctionDeclaration' || n.type === 'ClassDeclaration') { if (n.id) declared.add(n.id.name); }
537
+ else if (n.type === 'FunctionExpression' || n.type === 'ArrowFunctionExpression') { for (const p of n.params) for (const nm of patternNames(p)) declared.add(nm); if (n.id) declared.add(n.id.name); }
538
+ else if (n.type === 'CatchClause' && n.param) for (const nm of patternNames(n.param)) declared.add(nm);
539
+ });
540
+ const free = new Map();
541
+ const visit = (n, parent) => {
542
+ if (n.type === 'Identifier') {
543
+ if (parent) {
544
+ if (parent.type === 'MemberExpression' && parent.property === n && !parent.computed) return;
545
+ if (parent.type === 'Property' && parent.key === n && !parent.computed && !parent.shorthand) return;
546
+ if (parent.type === 'Property' && parent.key === n && parent.shorthand && parent.value !== n) return;
547
+ if (parent.type === 'MethodDefinition' && parent.key === n) return;
548
+ if (parent.type === 'LabeledStatement' || parent.type === 'BreakStatement' || parent.type === 'ContinueStatement') return;
549
+ }
550
+ if (!declared.has(n.name) && !free.has(n.name)) free.set(n.name, n);
551
+ }
552
+ };
553
+ for (const p of fnNode.params) walk(p, visit);
554
+ walk(fnNode.body, visit);
555
+ return free;
556
+ }
557
+
558
+ // ---------------------------------------------------------------- module shape
559
+
560
+ /**
561
+ * Discover handlers, imports and module-scope declarations.
562
+ * Returns { kind, ast, source, text, imports, handlers, decls, meta, sideEffects }.
563
+ */
564
+ export function readModule(source, { file = '' } = {}) {
565
+ const parsed = parseModule(source, { file });
566
+ const { ast, kind } = parsed;
567
+ const imports = [];
568
+ const decls = new Map(); // name → { kind, node, init, line, exported }
569
+ const handlers = { default: null, methods: {} };
570
+ const meta = {};
571
+ const sideEffects = [];
572
+ const setDecl = (name, entry) => { if (!decls.has(name)) decls.set(name, entry); };
573
+ const setMethod = (name, node, line) => { if (node) handlers.methods[name] = { node, line, name }; };
574
+ const line = (n) => n?.loc?.start?.line ?? null;
575
+
576
+ const registerVarDecl = (decl, exported = false) => {
577
+ for (const d of decl.declarations) {
578
+ // CommonJS require
579
+ const req = requireSource(d.init);
580
+ if (req) {
581
+ const specs = [];
582
+ if (d.id.type === 'Identifier') specs.push({ local: d.id.name, imported: req.member ?? '*' });
583
+ else if (d.id.type === 'ObjectPattern') {
584
+ for (const p of d.id.properties) {
585
+ if (p.type === 'RestElement') continue;
586
+ const imported = p.key.type === 'Identifier' ? p.key.name : String(p.key.value);
587
+ const local = p.value.type === 'Identifier' ? p.value.name : p.value.type === 'AssignmentPattern' && p.value.left.type === 'Identifier' ? p.value.left.name : null;
588
+ if (local) specs.push({ local, imported });
589
+ }
590
+ }
591
+ imports.push({ source: req.source, specifiers: specs, line: line(d) });
592
+ continue;
593
+ }
594
+ if (d.id.type === 'Identifier') {
595
+ setDecl(d.id.name, { kind: decl.kind, node: d, init: d.init, line: line(d), exported });
596
+ } else {
597
+ for (const nm of patternNames(d.id)) setDecl(nm, { kind: decl.kind, node: d, init: null, pattern: d.id, line: line(d), exported });
598
+ }
599
+ }
600
+ };
601
+
602
+ for (const s of ast.body) {
603
+ switch (s.type) {
604
+ case 'ImportDeclaration': {
605
+ if (s.importKind === 'type') break;
606
+ const specs = s.specifiers.map((sp) => ({
607
+ local: sp.local.name,
608
+ imported: sp.type === 'ImportDefaultSpecifier' ? 'default' : sp.type === 'ImportNamespaceSpecifier' ? '*' : (sp.imported.name ?? sp.imported.value),
609
+ }));
610
+ imports.push({ source: s.source.value, specifiers: specs, line: line(s) });
611
+ break;
612
+ }
613
+ case 'ExportDefaultDeclaration': {
614
+ handlers.default = { node: s.declaration, line: line(s), name: 'default' };
615
+ if (s.declaration.type === 'FunctionDeclaration' && s.declaration.id) setDecl(s.declaration.id.name, { kind: 'function', node: s.declaration, line: line(s), exported: true });
616
+ break;
617
+ }
618
+ case 'ExportNamedDeclaration': {
619
+ if (s.declaration) {
620
+ const d = s.declaration;
621
+ if (d.type === 'FunctionDeclaration') {
622
+ setDecl(d.id.name, { kind: 'function', node: d, line: line(d), exported: true });
623
+ if (HTTP_METHODS.includes(d.id.name)) setMethod(d.id.name, d, line(d));
624
+ } else if (d.type === 'VariableDeclaration') {
625
+ registerVarDecl(d, true);
626
+ for (const v of d.declarations) {
627
+ if (v.id.type !== 'Identifier') continue;
628
+ if (HTTP_METHODS.includes(v.id.name)) setMethod(v.id.name, v.init, line(v));
629
+ else if (['config', 'runtime', 'dynamic', 'revalidate', 'maxDuration', 'preferredRegion', 'fetchCache', 'dynamicParams'].includes(v.id.name)) meta[v.id.name] = literalValue(v.init);
630
+ }
631
+ } else if (d.type === 'ClassDeclaration') {
632
+ setDecl(d.id.name, { kind: 'class', node: d, line: line(d), exported: true });
633
+ }
634
+ } else {
635
+ for (const sp of s.specifiers) {
636
+ const exportedName = sp.exported.name ?? sp.exported.value;
637
+ const localName = sp.local.name ?? sp.local.value;
638
+ if (exportedName === 'default') handlers.default = { node: { type: 'Identifier', name: localName, loc: sp.loc }, line: line(sp), name: 'default' };
639
+ else if (HTTP_METHODS.includes(exportedName)) setMethod(exportedName, { type: 'Identifier', name: localName, loc: sp.loc }, line(sp));
640
+ }
641
+ }
642
+ break;
643
+ }
644
+ case 'ExportAllDeclaration': sideEffects.push({ node: s, what: 'export * from' }); break;
645
+ case 'FunctionDeclaration': setDecl(s.id.name, { kind: 'function', node: s, line: line(s) }); break;
646
+ case 'ClassDeclaration': setDecl(s.id.name, { kind: 'class', node: s, line: line(s) }); break;
647
+ case 'VariableDeclaration': registerVarDecl(s); break;
648
+ case 'ExpressionStatement': {
649
+ const e = s.expression;
650
+ // module.exports = X ; exports.X = Y ; module.exports.X = Y
651
+ if (e.type === 'AssignmentExpression' && e.operator === '=') {
652
+ const target = memberPath(e.left);
653
+ if (target && (target[0] === 'module' && target[1] === 'exports' || target[0] === 'exports')) {
654
+ const rest = target[0] === 'module' ? target.slice(2) : target.slice(1);
655
+ if (rest.length === 0) {
656
+ // module.exports = handler | { GET, POST }
657
+ if (e.right.type === 'ObjectExpression') {
658
+ for (const p of e.right.properties) {
659
+ if (p.type !== 'Property') continue;
660
+ const k = p.key.name ?? p.key.value;
661
+ if (k === 'default') handlers.default = { node: p.value, line: line(p), name: 'default' };
662
+ else if (HTTP_METHODS.includes(k)) setMethod(k, p.value, line(p));
663
+ }
664
+ } else handlers.default = { node: e.right, line: line(s), name: 'default' };
665
+ } else if (rest.length === 1) {
666
+ if (rest[0] === 'default') handlers.default = { node: e.right, line: line(s), name: 'default' };
667
+ else if (HTTP_METHODS.includes(rest[0])) setMethod(rest[0], e.right, line(s));
668
+ else if (rest[0] === 'config') meta.config = literalValue(e.right);
669
+ }
670
+ break;
671
+ }
672
+ }
673
+ if (e.type === 'Literal' && typeof e.value === 'string') break; // 'use strict'
674
+ sideEffects.push({ node: s, what: 'top-level statement' });
675
+ break;
676
+ }
677
+ case 'EmptyStatement': break;
678
+ default: sideEffects.push({ node: s, what: `top-level ${s.type}` });
679
+ }
680
+ }
681
+ // record whether module-scope bindings are reassigned anywhere
682
+ const reassigned = new Set();
683
+ walk(ast, (n) => {
684
+ if (n.type === 'AssignmentExpression' || n.type === 'UpdateExpression') {
685
+ const t = n.type === 'AssignmentExpression' ? n.left : n.argument;
686
+ if (t.type === 'Identifier') reassigned.add(t.name);
687
+ for (const nm of (t.type === 'ObjectPattern' || t.type === 'ArrayPattern') ? patternNames(t) : []) reassigned.add(nm);
688
+ }
689
+ });
690
+ for (const [name, d] of decls) d.reassigned = reassigned.has(name);
691
+ return { ...parsed, file, imports, handlers, decls, meta, sideEffects };
692
+ }
693
+
694
+ function memberPath(n) {
695
+ const out = [];
696
+ while (n) {
697
+ if (n.type === 'Identifier') { out.unshift(n.name); return out; }
698
+ if (n.type === 'MemberExpression' && !n.computed && n.property.type === 'Identifier') { out.unshift(n.property.name); n = n.object; continue; }
699
+ if (n.type === 'MemberExpression' && n.computed && n.property.type === 'Literal') { out.unshift(String(n.property.value)); n = n.object; continue; }
700
+ return null;
701
+ }
702
+ return null;
703
+ }
704
+
705
+ function requireSource(init) {
706
+ if (!init) return null;
707
+ if (init.type === 'CallExpression' && init.callee.type === 'Identifier' && init.callee.name === 'require' && init.arguments[0]?.type === 'Literal') {
708
+ return { source: String(init.arguments[0].value), member: null };
709
+ }
710
+ if (init.type === 'MemberExpression' && !init.computed && init.object.type === 'CallExpression') {
711
+ const inner = requireSource(init.object);
712
+ if (inner) return { source: inner.source, member: init.property.name };
713
+ }
714
+ return null;
715
+ }
716
+
717
+ /** Literal / plain-object value of a config export, or undefined. */
718
+ export function literalValue(n) {
719
+ if (!n) return undefined;
720
+ switch (n.type) {
721
+ case 'Literal': return n.value;
722
+ case 'TemplateLiteral': return n.expressions.length ? undefined : n.quasis.map((q) => q.value.cooked).join('');
723
+ case 'ObjectExpression': {
724
+ const o = {};
725
+ for (const p of n.properties) { if (p.type !== 'Property') continue; o[p.key.name ?? p.key.value] = literalValue(p.value); }
726
+ return o;
727
+ }
728
+ case 'ArrayExpression': return n.elements.map(literalValue);
729
+ case 'UnaryExpression': return n.operator === '-' && n.argument.type === 'Literal' ? -n.argument.value : undefined;
730
+ default: return undefined;
731
+ }
732
+ }
733
+
734
+ /** Resolve a handler reference (identifier → its declaration's function node). */
735
+ export function resolveHandlerNode(ref, mod) {
736
+ let node = ref?.node;
737
+ if (!node) return null;
738
+ if (node.type === 'Identifier') {
739
+ const d = mod.decls.get(node.name);
740
+ if (!d) throw new Ineligible(`handler \`${node.name}\` is exported but never declared in this file`, { file: mod.file, node });
741
+ if (d.kind === 'function') return d.node;
742
+ if (d.init && isFunctionNode(d.init)) return d.init;
743
+ if (d.init?.type === 'CallExpression') throw new Ineligible(`handler \`${node.name}\` is wrapped in a call (${describeCallee(d.init)}); higher-order wrappers are not transmuted, export the plain handler`, { file: mod.file, node: d.init });
744
+ throw new Ineligible(`handler \`${node.name}\` is not a function`, { file: mod.file, node: d.node });
745
+ }
746
+ if (isFunctionNode(node)) return node;
747
+ if (node.type === 'CallExpression') throw new Ineligible(`the exported handler is wrapped in a call (${describeCallee(node)}); higher-order wrappers are not transmuted, export the plain handler`, { file: mod.file, node });
748
+ throw new Ineligible(`the exported handler is a ${node.type}, not a function`, { file: mod.file, node });
749
+ }
750
+ function describeCallee(call) {
751
+ const p = memberPath(call.callee);
752
+ return p ? p.join('.') + '(...)' : 'call expression';
753
+ }