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,777 @@
1
+ // IR → Rust source. A plain printer: every IR expression becomes a Rust
2
+ // expression of type `Val` (owned), statements become statements, and the
3
+ // crate skeleton wires routes into `zoo_host::dispatch`.
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ export const DEFAULT_RUNTIME_PATH = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', 'runtime', 'zoo-host');
8
+
9
+ // ---------------------------------------------------------------- literals
10
+
11
+ export function rustStr(s) {
12
+ let out = '"';
13
+ for (const ch of String(s)) {
14
+ const c = ch.codePointAt(0);
15
+ if (ch === '"') out += '\\"';
16
+ else if (ch === '\\') out += '\\\\';
17
+ else if (ch === '\n') out += '\\n';
18
+ else if (ch === '\r') out += '\\r';
19
+ else if (ch === '\t') out += '\\t';
20
+ else if (c < 0x20 || c === 0x7f) out += `\\u{${c.toString(16)}}`;
21
+ else out += ch;
22
+ }
23
+ return out + '"';
24
+ }
25
+
26
+ export function numLit(v) {
27
+ if (Number.isNaN(v)) return 'f64::NAN';
28
+ if (v === Infinity) return 'f64::INFINITY';
29
+ if (v === -Infinity) return 'f64::NEG_INFINITY';
30
+ if (Object.is(v, -0)) return '-0.0';
31
+ if (Number.isInteger(v) && Math.abs(v) < 1e15) return `${v}.0`;
32
+ let s = String(v);
33
+ if (!/[.e]/.test(s)) s += '.0';
34
+ return s;
35
+ }
36
+
37
+ /** Crate/package name; underscores only so the artifact is always `target/deploy/<name>.so`. */
38
+ export function sanitizeCrateName(name) {
39
+ let n = String(name || 'zoo_site').toLowerCase().replace(/[^a-z0-9_]+/g, '_').replace(/^_+|_+$/g, '');
40
+ if (!n || /^[0-9]/.test(n)) n = 'zoo_' + (n || 'site');
41
+ return n;
42
+ }
43
+
44
+ // ---------------------------------------------------------------- printer
45
+
46
+ const IND = ' ';
47
+
48
+ export class RustPrinter {
49
+ constructor() {
50
+ this.tmp = 0;
51
+ }
52
+ t(prefix = '__x') {
53
+ return `${prefix}${++this.tmp}`;
54
+ }
55
+
56
+ // ---- expressions
57
+
58
+ E(ir) {
59
+ switch (ir.k) {
60
+ case 'undef': return 'Val::Undef';
61
+ case 'null': return 'Val::Null';
62
+ case 'bool': return `Val::Bool(${ir.v ? 'true' : 'false'})`;
63
+ case 'num': return `Val::Num(${numLit(ir.v)})`;
64
+ case 'str': return `Val::str(${rustStr(ir.v)})`;
65
+ case 'raw': return ir.code;
66
+ case 'var': return `${ir.name}.clone()`;
67
+ case 'template': {
68
+ const s = this.t('__s');
69
+ const parts = ir.parts.map((p) => (p.s !== undefined ? `${s}.push_str(${rustStr(p.s)});` : `${s}.push_str(&(${this.E(p.e)}).to_js_string());`));
70
+ return `Val::Str({ let mut ${s} = String::new(); ${parts.join(' ')} ${s} })`;
71
+ }
72
+ case 'obj': {
73
+ const o = this.t('__o');
74
+ const parts = ir.props.map((p) => {
75
+ if (p.spread) { const kv = this.t('__kv'); return `if let Val::Obj(${kv}) = ${this.E(p.spread)} { for (__k, __v) in ${kv} { ${o}.set_str(&__k, __v); } }`; }
76
+ if (typeof p.key === 'string') return `${o}.set_str(${rustStr(p.key)}, ${this.E(p.value)});`;
77
+ const k = this.t('__k');
78
+ return `{ let ${k} = ${this.E(p.key.e)}; let __v = ${this.E(p.value)}; ${o}.set(&${k}, __v); }`;
79
+ });
80
+ return `{ let mut ${o} = Val::obj(); ${parts.join(' ')} ${o} }`;
81
+ }
82
+ case 'arr': {
83
+ if (!ir.items.some((i) => i.spread)) return `Val::Arr(alloc::vec![${ir.items.map((i) => (i.hole ? 'Val::Undef' : this.E(i.e))).join(', ')}])`;
84
+ const a = this.t('__a');
85
+ const parts = ir.items.map((i) => (i.spread ? `for __v in (${this.E(i.spread)}).iter_values() { ${a}.push(__v); }` : `${a}.push(${i.hole ? 'Val::Undef' : this.E(i.e)});`));
86
+ return `{ let mut ${a} = Val::arr(); ${parts.join(' ')} ${a} }`;
87
+ }
88
+ case 'get': return `(${this.E(ir.obj)}).get_str(${rustStr(ir.key)})`;
89
+ case 'getc': { const o = this.t('__o'); const k = this.t('__k'); return `{ let ${o} = ${this.E(ir.obj)}; let ${k} = ${this.E(ir.key)}; ${o}.get(&${k}) }`; }
90
+ case 'chain': return `${ir.label}: { ${this.E(ir.e)} }`;
91
+ case 'optcheck': { const t = this.t(); return `{ let ${t} = ${this.E(ir.e)}; if ${t}.is_nullish() { break ${ir.label} Val::Undef; } ${t} }`; }
92
+ case 'bin': return `(${this.E(ir.l)}).${ir.op}(&(${this.E(ir.r)}))`;
93
+ case 'cmp': return `Val::Bool(${this.cmp(ir.op, ir.l, ir.r)})`;
94
+ case 'logical': {
95
+ const t = this.t();
96
+ const test = ir.op === 'and' ? `${t}.truthy()` : ir.op === 'or' ? `!${t}.truthy()` : `${t}.is_nullish()`;
97
+ return `{ let ${t} = ${this.E(ir.l)}; if ${test} { ${this.E(ir.r)} } else { ${t} } }`;
98
+ }
99
+ case 'cond': return `if ${this.C(ir.test)} { ${this.E(ir.then)} } else { ${this.E(ir.else)} }`;
100
+ case 'unary':
101
+ switch (ir.op) {
102
+ case 'neg': return `(${this.E(ir.e)}).neg()`;
103
+ case 'plus': return `Val::Num((${this.E(ir.e)}).to_num())`;
104
+ case 'not': return `Val::Bool(!(${this.C(ir.e)}))`;
105
+ case 'bitnot': return `(${this.E(ir.e)}).bit_xor(&Val::Num(-1.0))`;
106
+ case 'typeof': return `Val::str((${this.E(ir.e)}).type_of())`;
107
+ case 'void': return `{ let _ = ${this.E(ir.e)}; Val::Undef }`;
108
+ default: throw new Error(`unknown unary ${ir.op}`);
109
+ }
110
+ case 'in': { const k = this.t('__k'); const o = this.t('__o'); return `{ let ${k} = ${this.E(ir.key)}; let ${o} = ${this.E(ir.obj)}; Val::Bool(${o}.has(&${k})) }`; }
111
+ case 'delete': {
112
+ if (ir.lv.k !== 'lv-member') return 'Val::Bool(true)';
113
+ const ctx = this.lvCtx(ir.lv);
114
+ const keyExpr = this.keyRef(ir.lv, ctx);
115
+ const stmts = this.rmw(ir.lv.obj, (base) => `${base}.delete(&${keyExpr});`, ctx);
116
+ return `{ ${ctx.pre.join(' ')} ${stmts} Val::Bool(true) }`;
117
+ }
118
+ case 'assign': return this.assign(ir);
119
+ case 'update': return this.update(ir);
120
+ case 'seq': return `{ ${ir.exprs.slice(0, -1).map((e) => `let _ = ${this.E(e)};`).join(' ')} ${this.E(ir.exprs[ir.exprs.length - 1])} }`;
121
+ case 'callm': return this.callm(ir);
122
+ case 'host': {
123
+ const q = ir.q ? '?' : '';
124
+ if (!ir.args.length) return ir.void ? `{ cx.${ir.fn}(); Val::Undef }` : `cx.${ir.fn}()${q}`;
125
+ const names = ir.args.map(() => this.t('__a'));
126
+ const lets = ir.args.map((a, i) => `let ${names[i]} = ${this.E(a)};`).join(' ');
127
+ const call = `cx.${ir.fn}(${names.map((n) => `&${n}`).join(', ')})${q}`;
128
+ return ir.void ? `{ ${lets} ${call}; Val::Undef }` : `{ ${lets} ${call} }`;
129
+ }
130
+ case 'pre': return `{ ${ir.stmts.map((s) => this.S(s, '').join(' ')).join(' ')} ${this.E(ir.e)} }`;
131
+ case 'math': return `{ ${this.argsVec(ir.args)} zv::math(${rustStr(ir.name)}, &__a)? }`;
132
+ case 'global': return `{ ${this.argsVec(ir.args)} zv::global_call(${rustStr(ir.name)}, &__a)? }`;
133
+ case 'jsonparse': return `{ let __s = ${this.E(ir.e)}; zjson::parse(&__s.to_js_string())? }`;
134
+ case 'jsonstringify': return `{ let __v = ${this.E(ir.e)}; Val::Str(zjson::stringify(&__v)) }`;
135
+ case 'keys': return `(${this.E(ir.e)}).keys()`;
136
+ case 'values': return `(${this.E(ir.e)}).values()`;
137
+ case 'entries': return `(${this.E(ir.e)}).entries()`;
138
+ case 'isarray': return `Val::Bool(matches!(${this.E(ir.e)}, Val::Arr(_)))`;
139
+ case 'newerror': {
140
+ const name = ir.name === 'Error' ? '' : ` __e.set_str("name", Val::str(${rustStr(ir.name)}));`;
141
+ return `{ ${this.argsVec(ir.args)} let mut __e = Ctx::new_error(&__a);${name} __e }`;
142
+ }
143
+ case 'log': return `{ ${this.argsVec(ir.args)} __zoo_log(cx, &__a); Val::Undef }`;
144
+ case 'helper': return `{ ${this.argsVec(ir.args)} __zoo_${ir.name}(cx, &__a) }`;
145
+ case 'resp': return `{ let __b = ${this.E(ir.body)}; let __i = ${this.E(ir.init)}; __zoo_resp(${rustStr(ir.kind)}, __b, __i) }`;
146
+ case 'params':
147
+ if (ir.fn === 'query') return `__zoo_query(cx, ${ir.paramsConst})`;
148
+ if (ir.fn === 'params') return `__zoo_params(cx, ${ir.paramsConst})`;
149
+ return `__zoo_param(cx, ${rustStr(ir.name)}, ${ir.catchAll ? 'true' : 'false'})`;
150
+ case 'callh': return `{ ${this.argsVec(ir.args)} ${ir.name}(cx, &__a)? }`;
151
+ case 'constref': return `${ir.name}(cx)?`;
152
+ case 'iife': return `{ ${this.argsVec(ir.args)} let __args: &[Val] = &__a; (|| -> Result<Val, Val> {\n${this.fnBody(ir.fn, IND)}\n})()? }`;
153
+ default: throw new Error(`unknown IR expression ${ir.k}`);
154
+ }
155
+ }
156
+
157
+ argsVec(args) {
158
+ return `let __a: Vec<Val> = alloc::vec![${args.map((a) => this.E(a)).join(', ')}];`;
159
+ }
160
+
161
+ cmp(op, l, r) {
162
+ const L = this.E(l);
163
+ const R = this.E(r);
164
+ switch (op) {
165
+ case 'strict_eq': return `(${L}).strict_eq(&(${R}))`;
166
+ case 'strict_ne': return `!(${L}).strict_eq(&(${R}))`;
167
+ case 'loose_eq': return `(${L}).loose_eq(&(${R}))`;
168
+ case 'loose_ne': return `!(${L}).loose_eq(&(${R}))`;
169
+ default: return `(${L}).${op}(&(${R}))`;
170
+ }
171
+ }
172
+
173
+ // ---- conditions
174
+
175
+ C(c) {
176
+ switch (c.k) {
177
+ case 'c-truthy': return `(${this.E(c.e)}).truthy()`;
178
+ case 'c-not': return `!(${this.C(c.c)})`;
179
+ case 'c-and': return `(${this.C(c.l)} && ${this.C(c.r)})`;
180
+ case 'c-or': return `(${this.C(c.l)} || ${this.C(c.r)})`;
181
+ case 'c-cmp': return this.cmp(c.op, c.l, c.r);
182
+ case 'c-nullish': return `(${this.E(c.e)}).is_nullish()`;
183
+ case 'c-undef': return `(${this.E(c.e)}).is_undef()`;
184
+ default: throw new Error(`unknown condition ${c.k}`);
185
+ }
186
+ }
187
+
188
+ // ---- lvalues
189
+
190
+ /**
191
+ * An lvalue "operation context": computed keys of the path are evaluated
192
+ * once, left to right, into temps (`pre`), so reads and writes of the same
193
+ * path agree. Fresh per assignment/update/call — never cached on the IR.
194
+ */
195
+ lvCtx(lv) {
196
+ const ctx = { pre: [], keys: new Map() };
197
+ const hoist = (x) => {
198
+ if (x.k !== 'lv-member') return;
199
+ hoist(x.obj);
200
+ if (typeof x.key !== 'string') { const t = this.t('__k'); ctx.keys.set(x, t); ctx.pre.push(`let ${t} = ${this.E(x.key)};`); }
201
+ };
202
+ hoist(lv);
203
+ return ctx;
204
+ }
205
+ keyRef(lv, ctx) {
206
+ if (typeof lv.key === 'string') return `Val::str(${rustStr(lv.key)})`;
207
+ return ctx.keys.get(lv);
208
+ }
209
+ readLv(lv, ctx) {
210
+ if (lv.k === 'lv-var') return `${lv.name}.clone()`;
211
+ if (lv.k === 'lv-expr') return this.E(lv.e);
212
+ const base = this.readLv(lv.obj, ctx);
213
+ if (typeof lv.key === 'string') return `(${base}).get_str(${rustStr(lv.key)})`;
214
+ return `(${base}).get(&${this.keyRef(lv, ctx)})`;
215
+ }
216
+ /** Read-modify-write: `mut(place)` receives the name of a mutable Val binding. */
217
+ rmw(lv, mut, ctx) {
218
+ if (lv.k === 'lv-var') return mut(lv.name);
219
+ if (lv.k === 'lv-expr') { const t = this.t('__m'); return `{ let mut ${t} = ${this.E(lv.e)}; ${mut(t)} }`; }
220
+ return this.rmw(lv.obj, (base) => {
221
+ const t = this.t('__m');
222
+ const key = this.keyRef(lv, ctx);
223
+ const getter = typeof lv.key === 'string' ? `get_str(${rustStr(lv.key)})` : `get(&${key})`;
224
+ const setter = typeof lv.key === 'string' ? `set_str(${rustStr(lv.key)}, ${t})` : `set(&${key}, ${t})`;
225
+ return `{ let mut ${t} = ${base}.${getter}; ${mut(t)} ${base}.${setter}; }`;
226
+ }, ctx);
227
+ }
228
+ writeLv(lv, valueExpr, ctx) {
229
+ if (lv.k === 'lv-var') return `${lv.name} = ${valueExpr};`;
230
+ if (lv.k === 'lv-expr') return `{ let _ = ${valueExpr}; }`;
231
+ return this.rmw(lv.obj, (base) => {
232
+ if (typeof lv.key === 'string') return `${base}.set_str(${rustStr(lv.key)}, ${valueExpr});`;
233
+ return `${base}.set(&${this.keyRef(lv, ctx)}, ${valueExpr});`;
234
+ }, ctx);
235
+ }
236
+ assign(ir) {
237
+ const ctx = this.lvCtx(ir.lv);
238
+ const v = this.t('__v');
239
+ let valueExpr = this.E(ir.value);
240
+ if (ir.op) valueExpr = `(${this.readLv(ir.lv, ctx)}).${ir.op}(&(${valueExpr}))`;
241
+ const write = this.writeLv(ir.lv, `${v}.clone()`, ctx);
242
+ return `{ ${ctx.pre.join(' ')} let ${v} = ${valueExpr}; ${write} ${v} }`;
243
+ }
244
+ update(ir) {
245
+ const ctx = this.lvCtx(ir.lv);
246
+ const cur = this.readLv(ir.lv, ctx);
247
+ const o = this.t('__old');
248
+ const n = this.t('__new');
249
+ const write = this.writeLv(ir.lv, `${n}.clone()`, ctx);
250
+ const delta = ir.delta > 0 ? '+ 1.0' : '- 1.0';
251
+ return `{ ${ctx.pre.join(' ')} let ${o} = Val::Num((${cur}).to_num()); let ${n} = Val::Num(${o}.to_num() ${delta}); ${write} ${ir.prefix ? n : o} }`;
252
+ }
253
+
254
+ // ---- method calls
255
+
256
+ callm(ir) {
257
+ const parts = [];
258
+ const r = this.t('__r');
259
+ if (!ir.lv) parts.push(`let mut ${r} = ${this.E(ir.recv)};`);
260
+ parts.push(this.argsVec(ir.args));
261
+ let cbArg = 'None';
262
+ let errCheck = '';
263
+ if (ir.cb) {
264
+ const f = this.t('__f');
265
+ const err = this.t('__err');
266
+ parts.push(`let mut ${err}: Option<Val> = None;`);
267
+ if (ir.cb.kind === 'fn') {
268
+ parts.push(`let mut ${f} = |__args: &[Val]| -> Val { let __res: Result<Val, Val> = (|| -> Result<Val, Val> {\n${this.fnBody(ir.cb.fn, IND)}\n})(); match __res { Ok(__v) => __v, Err(__e) => { ${err} = Some(__e); Val::Undef } } };`);
269
+ } else if (ir.cb.kind === 'ref') {
270
+ parts.push(`let mut ${f} = |__args: &[Val]| -> Val { match ${ir.cb.name}(cx, __args) { Ok(__v) => __v, Err(__e) => { ${err} = Some(__e); Val::Undef } } };`);
271
+ } else {
272
+ parts.push(`let mut ${f} = |__args: &[Val]| -> Val { let __x = __args.get(0).map(|v| v.to_js_string()).unwrap_or_default(); let __y = __args.get(1).map(|v| v.to_js_string()).unwrap_or_default(); Val::Num(if __x < __y { -1.0 } else if __x > __y { 1.0 } else { 0.0 }) };`);
273
+ }
274
+ cbArg = `Some(&mut ${f})`;
275
+ errCheck = ` if let Some(__e) = ${err} { return Err(__e); }`;
276
+ }
277
+ const out = this.t('__out');
278
+ if (ir.lv) {
279
+ const ctx = this.lvCtx(ir.lv);
280
+ const stmts = this.rmw(ir.lv, (place) => `${out} = ${place}.call(${rustStr(ir.name)}, &__a, ${cbArg});`, ctx);
281
+ parts.push(`${ctx.pre.join(' ')} let mut ${out}: Result<Val, Val> = Ok(Val::Undef); ${stmts}`);
282
+ } else {
283
+ parts.push(`let ${out} = ${r}.call(${rustStr(ir.name)}, &__a, ${cbArg});`);
284
+ }
285
+ return `{ ${parts.join(' ')}${errCheck} ${out}? }`;
286
+ }
287
+
288
+ // ---- statements
289
+
290
+ S(s, ind) {
291
+ const L = (x) => `${ind}${x}`;
292
+ switch (s.k) {
293
+ case 'let': return [L(`let mut ${s.name}: Val = ${s.init ? this.E(s.init) : 'Val::Undef'};`)];
294
+ case 'hoistvar': return [L(`let mut ${s.name}: Val = Val::Undef;`)];
295
+ case 'expr': return [L(`let _ = ${this.E(s.e)};`)];
296
+ case 'block': return [L('{'), ...this.body(s.body, ind + IND), L('}')];
297
+ case 'if': {
298
+ const out = [L(`if ${this.C(s.test)} {`), ...this.body(s.then, ind + IND)];
299
+ if (s.else) out.push(L('} else {'), ...this.body(s.else, ind + IND));
300
+ out.push(L('}'));
301
+ return out;
302
+ }
303
+ case 'while': return [L(`${s.label}: while ${this.C(s.test)} {`), ...this.body(s.body, ind + IND), L('}')];
304
+ case 'dowhile': {
305
+ const f = this.t('__first');
306
+ return [L('{'), L(`${IND}let mut ${f} = true;`), L(`${IND}${s.label}: while ${f} || ${this.C(s.test)} {`), L(`${IND}${IND}${f} = false;`), ...this.body(s.body, ind + IND + IND), L(`${IND}}`), L('}')];
307
+ }
308
+ case 'for': {
309
+ const f = this.t('__first');
310
+ const out = [L('{'), ...this.body(s.init, ind + IND), L(`${IND}let mut ${f} = true;`), L(`${IND}${s.label}: loop {`)];
311
+ if (s.update.length) out.push(L(`${IND}${IND}if !${f} {`), ...this.body(s.update, ind + IND + IND + IND), L(`${IND}${IND}}`));
312
+ out.push(L(`${IND}${IND}${f} = false;`));
313
+ if (s.test) out.push(L(`${IND}${IND}if !(${this.C(s.test)}) { break; }`));
314
+ out.push(...this.body(s.body, ind + IND + IND), L(`${IND}}`), L('}'));
315
+ return out;
316
+ }
317
+ case 'forof': return [L(`${s.label}: for ${s.tmp} in (${this.E(s.iter)}).iter_values() {`), ...this.body(s.body, ind + IND), L('}')];
318
+ case 'forin': return [L(`${s.label}: for ${s.tmp} in (${this.E(s.iter)}).keys().iter_values() {`), ...this.body(s.body, ind + IND), L('}')];
319
+ case 'break': return [L(`break ${s.label};`)];
320
+ case 'continue': return [L(`continue ${s.label};`)];
321
+ case 'return': return this.ret(s, ind);
322
+ case 'throw': return [L(`return Err(${this.E(s.e)});`)];
323
+ case 'try': return this.tryStmt(s, ind);
324
+ case 'switch': {
325
+ const d = this.t('__d');
326
+ const idx = this.t('__idx');
327
+ const out = [L('{'), L(`${IND}let ${d} = ${this.E(s.disc)};`)];
328
+ let defaultIdx = -1;
329
+ const chain = [];
330
+ s.cases.forEach((c, i) => {
331
+ if (c.test === null) { defaultIdx = i; return; }
332
+ chain.push(`if ${d}.strict_eq(&(${this.E(c.test)})) { ${i} }`);
333
+ });
334
+ out.push(L(`${IND}let ${idx}: i32 = ${chain.length ? chain.join(' else ') + ' else ' : ''}{ ${defaultIdx} };`));
335
+ out.push(L(`${IND}${s.label}: loop {`));
336
+ s.cases.forEach((c, i) => {
337
+ out.push(L(`${IND}${IND}if ${idx} >= 0 && ${idx} <= ${i} {`), ...this.body(c.body, ind + IND + IND + IND), L(`${IND}${IND}}`));
338
+ });
339
+ out.push(L(`${IND}${IND}break;`), L(`${IND}}`), L('}'));
340
+ return out;
341
+ }
342
+ case 'letfn': {
343
+ const caps = s.captures.map((c) => `let ${c} = ${c}.clone();`).join(' ');
344
+ return [L(`let ${s.name} = { ${caps} move |cx: &mut Ctx, __args: &[Val]| -> Result<Val, Val> {`), ...this.fnBodyLines(s.fn, ind + IND), L('} };')];
345
+ }
346
+ default: throw new Error(`unknown IR statement ${s.k}`);
347
+ }
348
+ }
349
+
350
+ body(stmts, ind) {
351
+ const out = [];
352
+ for (const s of stmts) out.push(...this.S(s, ind));
353
+ return out;
354
+ }
355
+
356
+ ret(s, ind) {
357
+ const L = (x) => `${ind}${x}`;
358
+ const val = s.value ? this.E(s.value) : 'Val::Undef';
359
+ switch (s.mode) {
360
+ case 'node': {
361
+ const out = [];
362
+ if (s.value) out.push(L(`{ let _ = ${val}; }`));
363
+ out.push(L(s.inTry ? 'return Ok(Some(Val::Undef));' : 'return Ok(());'));
364
+ return out;
365
+ }
366
+ case 'web':
367
+ if (s.inTry) return [L(`return Ok(Some(${val}));`)];
368
+ if (!s.value) return [L('return Ok(());')];
369
+ return [L(`{ let __rv = ${val}; __zoo_send(cx, &__rv)?; }`), L('return Ok(());')];
370
+ case 'value':
371
+ return [L(s.inTry ? `return Ok(Some(${val}));` : `return Ok(${val});`)];
372
+ default: throw new Error(`unknown return mode ${s.mode}`);
373
+ }
374
+ }
375
+
376
+ /** What to do after a try body returned `Some(rv)`. */
377
+ retFromTry(mode, inTry, ind) {
378
+ const L = (x) => `${ind}${x}`;
379
+ if (inTry) return [L('return Ok(Some(__rv));')];
380
+ if (mode === 'node') return [L('return Ok(());')];
381
+ if (mode === 'web') return [L('__zoo_send(cx, &__rv)?;'), L('return Ok(());')];
382
+ return [L('return Ok(__rv);')];
383
+ }
384
+
385
+ tryStmt(s, ind) {
386
+ const L = (x) => `${ind}${x}`;
387
+ const r = this.t('__tr');
388
+ const out = [L(`let ${r}: Result<Option<Val>, Val> = (|| -> Result<Option<Val>, Val> {`), ...this.body(s.body, ind + IND), L(`${IND}Ok(None)`), L('})();')];
389
+ if (s.handlerClosure) {
390
+ // try { } catch { } finally { }: the handler runs as a closure too, then the
391
+ // finalizer, then the outcome (return / rethrow / fall through) is applied.
392
+ const r2 = this.t('__tr');
393
+ if (s.handler) {
394
+ out.push(L(`let ${r2}: Result<Option<Val>, Val> = match ${r} {`));
395
+ out.push(L(`${IND}Err(__e) => (|| -> Result<Option<Val>, Val> {`));
396
+ if (s.param) out.push(L(`${IND}${IND}let mut ${s.param}: Val = __zoo_caught(__e);`));
397
+ out.push(...this.body(s.handler, ind + IND + IND), L(`${IND}${IND}Ok(None)`), L(`${IND}})(),`));
398
+ out.push(L(`${IND}__other => __other,`), L('};'));
399
+ } else {
400
+ out.push(L(`let ${r2}: Result<Option<Val>, Val> = ${r};`));
401
+ }
402
+ if (s.finalizer) out.push(...this.body(s.finalizer, ind));
403
+ out.push(L(`match ${r2} {`));
404
+ out.push(L(`${IND}Ok(None) => {}`));
405
+ out.push(L(`${IND}Ok(Some(__rv)) => {`), ...this.retFromTry(s.mode, s.inTry, ind + IND + IND), L(`${IND}}`));
406
+ out.push(L(`${IND}Err(__e) => { return Err(__e); }`));
407
+ out.push(L('}'));
408
+ return out;
409
+ }
410
+ out.push(L(`match ${r} {`));
411
+ out.push(L(`${IND}Ok(None) => {}`));
412
+ out.push(L(`${IND}Ok(Some(__rv)) => {`), ...(s.finalizer ? this.body(s.finalizer, ind + IND + IND) : []), ...this.retFromTry(s.mode, s.inTry, ind + IND + IND), L(`${IND}}`));
413
+ if (s.handler) {
414
+ out.push(L(`${IND}Err(__e) => {`));
415
+ if (s.param) out.push(L(`${IND}${IND}let mut ${s.param}: Val = __zoo_caught(__e);`));
416
+ out.push(...this.body(s.handler, ind + IND + IND), L(`${IND}}`));
417
+ } else {
418
+ out.push(L(`${IND}Err(__e) => {`), ...(s.finalizer ? this.body(s.finalizer, ind + IND + IND) : []), L(`${IND}${IND}return Err(__e);`), L(`${IND}}`));
419
+ }
420
+ out.push(L('}'));
421
+ if (s.finalizer) out.push(...this.body(s.finalizer, ind));
422
+ return out;
423
+ }
424
+
425
+ // ---- functions
426
+
427
+ fnBodyLines(fn, ind) {
428
+ return [...this.body(fn.params, ind), ...this.body(fn.body, ind), `${ind}Ok(Val::Undef)`];
429
+ }
430
+ fnBody(fn, ind) {
431
+ return this.fnBodyLines(fn, ind).join('\n');
432
+ }
433
+ }
434
+
435
+ // ---------------------------------------------------------------- route & crate emission
436
+
437
+ /** Rust for one route IR (route fn(s), params const, helpers, consts). */
438
+ export function emitRoute(route, { printer = new RustPrinter() } = {}) {
439
+ const P = printer;
440
+ const out = [];
441
+ const paramsConst = `const ${route.paramsConst}: &[(&str, bool)] = &[${route.params.map((p) => `(${rustStr(p.name)}, ${p.catchAll})`).join(', ')}];`;
442
+ out.push(`// ---- route ${route.index}: ${route.name} (${route.file})`);
443
+ out.push(paramsConst);
444
+ const routeFn = (name, fn) => {
445
+ out.push(`fn ${name}(cx: &mut Ctx) -> Result<(), Val> {`);
446
+ out.push(...P.body(fn.params, IND));
447
+ out.push(...P.body(fn.body, IND));
448
+ out.push(`${IND}Ok(())`);
449
+ out.push('}');
450
+ };
451
+ if (route.style === 'node') {
452
+ routeFn(`route_${route.index}`, route.node);
453
+ } else {
454
+ const methods = Object.keys(route.methods);
455
+ const allow = [...methods];
456
+ if (!allow.includes('HEAD') && allow.includes('GET')) allow.push('HEAD');
457
+ if (!allow.includes('OPTIONS')) allow.push('OPTIONS');
458
+ const allowStr = rustStr(allow.join(', '));
459
+ out.push(`fn route_${route.index}(cx: &mut Ctx) -> Result<(), Val> {`);
460
+ out.push(`${IND}let __m = cx.req_method();`);
461
+ out.push(`${IND}match __m.as_str() {`);
462
+ for (const m of methods) out.push(`${IND}${IND}Some(${rustStr(m)}) => route_${route.index}_${m.toLowerCase()}(cx),`);
463
+ if (!methods.includes('HEAD') && methods.includes('GET')) out.push(`${IND}${IND}Some("HEAD") => route_${route.index}_get(cx),`);
464
+ if (!methods.includes('OPTIONS')) out.push(`${IND}${IND}Some("OPTIONS") => { cx.res_status(&Val::Num(204.0)); cx.res_header(&Val::str("allow"), &Val::str(${allowStr})); cx.res_end(&Val::Undef); Ok(()) }`);
465
+ out.push(`${IND}${IND}_ => { cx.res_status(&Val::Num(405.0)); cx.res_header(&Val::str("allow"), &Val::str(${allowStr})); cx.res_end(&Val::Undef); Ok(()) }`);
466
+ out.push(`${IND}}`);
467
+ out.push('}');
468
+ for (const m of methods) routeFn(`route_${route.index}_${m.toLowerCase()}`, route.methods[m]);
469
+ }
470
+ for (const h of route.helpers) {
471
+ out.push(`fn ${h.name}(cx: &mut Ctx, __args: &[Val]) -> Result<Val, Val> {`);
472
+ out.push(...P.fnBodyLines(h.fn, IND));
473
+ out.push('}');
474
+ }
475
+ for (const c of route.consts) {
476
+ out.push(`fn ${c.name}(cx: &mut Ctx) -> Result<Val, Val> {`);
477
+ out.push(`${IND}Ok(${P.E(c.e)})`);
478
+ out.push('}');
479
+ }
480
+ return out.join('\n');
481
+ }
482
+
483
+ export const PRELUDE = `
484
+ // ---- prelude: bridge helpers used by generated routes ----
485
+
486
+ /// Response value for app-router handlers: a tagged object that \`__zoo_send\` turns into the bridge response.
487
+ fn __zoo_resp(kind: &str, a: Val, b: Val) -> Val {
488
+ let mut o = Val::obj();
489
+ o.set_str("__zoo", Val::str(kind));
490
+ o.set_str("a", a);
491
+ o.set_str("b", b);
492
+ o
493
+ }
494
+
495
+ fn __zoo_send(cx: &mut Ctx, v: &Val) -> Result<(), Val> {
496
+ let kind = v.get_str("__zoo");
497
+ match kind.as_str() {
498
+ Some("json") => { let a = v.get_str("a"); let b = v.get_str("b"); cx.respond_json(&a, &b); Ok(()) }
499
+ Some("raw") => { let a = v.get_str("a"); let b = v.get_str("b"); cx.respond(&a, &b); Ok(()) }
500
+ Some("redirect") => {
501
+ let url = v.get_str("a");
502
+ let mut st = v.get_str("b");
503
+ if let Val::Obj(_) = st { st = st.get_str("status"); }
504
+ cx.respond_redirect(&url, &st);
505
+ Ok(())
506
+ }
507
+ _ => {
508
+ if v.is_undef() { Ok(()) } else { Err(Val::str("TypeError: the handler returned a value that is not a Response")) }
509
+ }
510
+ }
511
+ }
512
+
513
+ /// Runtime throws are strings ("SyntaxError: ..."); in a catch they become
514
+ /// Error-like objects so \`e.message\` / \`e.name\` read as in Node.
515
+ fn __zoo_caught(v: Val) -> Val {
516
+ if let Val::Str(s) = &v {
517
+ if let Some(i) = s.find(": ") {
518
+ let name = &s[..i];
519
+ if name.ends_with("Error") && !name.contains(' ') {
520
+ let mut o = Val::obj();
521
+ o.set_str("name", Val::str(name));
522
+ o.set_str("message", Val::str(&s[i + 2..]));
523
+ return o;
524
+ }
525
+ }
526
+ }
527
+ v
528
+ }
529
+
530
+ fn __zoo_param_value(s: &str, catch_all: bool) -> Val {
531
+ if catch_all {
532
+ Val::Arr(s.split('/').filter(|p| !p.is_empty()).map(|p| Val::Str(zv::url_decode(p))).collect())
533
+ } else {
534
+ Val::Str(zv::url_decode(s))
535
+ }
536
+ }
537
+
538
+ /// A dynamic segment, passed by the gateway as the \`x-zoo-param-<name>\` header.
539
+ fn __zoo_param(cx: &mut Ctx, name: &str, catch_all: bool) -> Val {
540
+ match cx.req_header(&Val::Str(format!("x-zoo-param-{}", name))) {
541
+ Val::Str(s) => __zoo_param_value(&s, catch_all),
542
+ _ => Val::Undef,
543
+ }
544
+ }
545
+
546
+ fn __zoo_params(cx: &mut Ctx, params: &[(&str, bool)]) -> Val {
547
+ let mut o = Val::obj();
548
+ for (name, catch_all) in params.iter() {
549
+ let v = __zoo_param(cx, name, *catch_all);
550
+ if !v.is_undef() { o.set_str(name, v); }
551
+ }
552
+ o
553
+ }
554
+
555
+ /// \`req.query\`: the query string plus the route's dynamic segments (pages-router semantics).
556
+ fn __zoo_query(cx: &mut Ctx, params: &[(&str, bool)]) -> Val {
557
+ let mut q = cx.req_query();
558
+ for (name, catch_all) in params.iter() {
559
+ let v = __zoo_param(cx, name, *catch_all);
560
+ if !v.is_undef() { q.set_str(name, v); }
561
+ }
562
+ q
563
+ }
564
+
565
+ fn __zoo_cookies(cx: &mut Ctx, _a: &[Val]) -> Val {
566
+ let mut o = Val::obj();
567
+ if let Val::Str(h) = cx.req_header(&Val::str("cookie")) {
568
+ for part in h.split(';') {
569
+ let p = part.trim();
570
+ if let Some(i) = p.find('=') { o.set_str(&p[..i], Val::Str(zv::url_decode(&p[i + 1..]))); }
571
+ }
572
+ }
573
+ o
574
+ }
575
+
576
+ fn __zoo_cookie_get(cx: &mut Ctx, a: &[Val]) -> Val {
577
+ let name = a.get(0).cloned().unwrap_or(Val::Undef);
578
+ let v = __zoo_cookies(cx, &[]).get(&name);
579
+ if v.is_undef() { return Val::Undef; }
580
+ let mut o = Val::obj();
581
+ o.set_str("name", name);
582
+ o.set_str("value", v);
583
+ o
584
+ }
585
+
586
+ fn __zoo_cookie_has(cx: &mut Ctx, a: &[Val]) -> Val {
587
+ let name = a.get(0).cloned().unwrap_or(Val::Undef);
588
+ Val::Bool(__zoo_cookies(cx, &[]).has(&name))
589
+ }
590
+
591
+ fn __zoo_cookie_all(cx: &mut Ctx, _a: &[Val]) -> Val {
592
+ let mut out = Val::arr();
593
+ if let Val::Obj(kv) = __zoo_cookies(cx, &[]) {
594
+ for (k, v) in kv {
595
+ let mut o = Val::obj();
596
+ o.set_str("name", Val::Str(k));
597
+ o.set_str("value", v);
598
+ out.push(o);
599
+ }
600
+ }
601
+ out
602
+ }
603
+
604
+ fn __zoo_header_has(cx: &mut Ctx, a: &[Val]) -> Val {
605
+ let name = a.get(0).cloned().unwrap_or(Val::Undef);
606
+ Val::Bool(!cx.req_header(&name).is_nullish())
607
+ }
608
+
609
+ fn __zoo_query_has(cx: &mut Ctx, a: &[Val]) -> Val {
610
+ let name = a.get(0).cloned().unwrap_or(Val::Undef);
611
+ Val::Bool(cx.req_query().has(&name))
612
+ }
613
+
614
+ fn __zoo_query_get_all(cx: &mut Ctx, a: &[Val]) -> Val {
615
+ let name = a.get(0).cloned().unwrap_or(Val::Undef);
616
+ match cx.req_query_get(&name) {
617
+ Val::Null | Val::Undef => Val::arr(),
618
+ v => Val::Arr(alloc::vec![v]),
619
+ }
620
+ }
621
+
622
+ /// \`url.search\` (with '?') or \`searchParams.toString()\` (without).
623
+ fn __zoo_search(cx: &mut Ctx, a: &[Val]) -> Val {
624
+ let with_q = a.get(0).map(|v| v.truthy()).unwrap_or(false);
625
+ let u = cx.req_url().to_js_string();
626
+ match u.find('?') {
627
+ Some(i) => Val::Str(String::from(if with_q { &u[i..] } else { &u[i + 1..] })),
628
+ None => Val::str(""),
629
+ }
630
+ }
631
+
632
+ fn __zoo_assign(_cx: &mut Ctx, a: &[Val]) -> Val {
633
+ let mut t = a.get(0).cloned().unwrap_or(Val::obj());
634
+ for src in a.iter().skip(1) {
635
+ if let Val::Obj(kv) = src { for (k, v) in kv.iter() { t.set_str(k, v.clone()); } }
636
+ }
637
+ t
638
+ }
639
+
640
+ /// Object rest: the object without the listed keys.
641
+ fn __zoo_omit(_cx: &mut Ctx, a: &[Val]) -> Val {
642
+ let mut out = Val::obj();
643
+ let skip: Vec<String> = a.get(1).map(|v| v.iter_values().iter().map(|k| k.to_js_string()).collect()).unwrap_or_default();
644
+ if let Some(Val::Obj(kv)) = a.get(0) {
645
+ for (k, v) in kv.iter() { if !skip.iter().any(|s| s == k) { out.set_str(k, v.clone()); } }
646
+ }
647
+ out
648
+ }
649
+
650
+ fn __zoo_from_entries(_cx: &mut Ctx, a: &[Val]) -> Val {
651
+ let mut o = Val::obj();
652
+ for pair in a.get(0).map(|v| v.iter_values()).unwrap_or_default() {
653
+ let k = pair.get(&Val::Num(0.0));
654
+ let v = pair.get(&Val::Num(1.0));
655
+ o.set(&k, v);
656
+ }
657
+ o
658
+ }
659
+
660
+ fn __zoo_array_from(_cx: &mut Ctx, a: &[Val]) -> Val {
661
+ Val::Arr(a.get(0).map(|v| v.iter_values()).unwrap_or_default())
662
+ }
663
+
664
+ /// Array rest / rest parameters: elements from index \`a[1]\` on.
665
+ fn __zoo_slice_from(_cx: &mut Ctx, a: &[Val]) -> Val {
666
+ let items = a.get(0).map(|v| v.iter_values()).unwrap_or_default();
667
+ let from = a.get(1).map(|v| v.to_num()).unwrap_or(0.0).max(0.0) as usize;
668
+ Val::Arr(items.into_iter().skip(from).collect())
669
+ }
670
+
671
+ /// console.log: joined like Node prints it, into the program log.
672
+ fn __zoo_log(_cx: &mut Ctx, a: &[Val]) -> Val {
673
+ let mut s = String::new();
674
+ for (i, v) in a.iter().enumerate() {
675
+ if i > 0 { s.push(' '); }
676
+ match v {
677
+ Val::Str(x) => s.push_str(x),
678
+ Val::Obj(_) | Val::Arr(_) => s.push_str(&zjson::stringify(v)),
679
+ other => s.push_str(&other.to_js_string()),
680
+ }
681
+ }
682
+ zoo_host::wire::log_str(&s);
683
+ Val::Undef
684
+ }
685
+
686
+ /// \`new URL(input, base)\` as a string (enough for redirect targets).
687
+ fn __zoo_url(_cx: &mut Ctx, a: &[Val]) -> Val {
688
+ let input = a.get(0).map(|v| v.to_js_string()).unwrap_or_default();
689
+ let base = a.get(1).map(|v| if v.is_undef() { String::new() } else { v.to_js_string() }).unwrap_or_default();
690
+ if input.contains("://") || base.is_empty() { return Val::Str(input); }
691
+ let origin_end = match base.find("://") {
692
+ Some(i) => base[i + 3..].find('/').map(|j| i + 3 + j).unwrap_or(base.len()),
693
+ None => 0,
694
+ };
695
+ if input.starts_with('/') { return Val::Str(format!("{}{}", &base[..origin_end], input)); }
696
+ let path_end = base.rfind('/').map(|i| i + 1).unwrap_or(base.len()).max(origin_end).min(base.len());
697
+ Val::Str(format!("{}{}", &base[..path_end], input))
698
+ }
699
+
700
+ /// Number.isInteger / isNaN / isFinite / isSafeInteger
701
+ fn __zoo_num(_cx: &mut Ctx, a: &[Val]) -> Val {
702
+ let name = a.get(0).map(|v| v.to_js_string()).unwrap_or_default();
703
+ let n = match a.get(1) { Some(Val::Num(n)) => *n, _ => return Val::Bool(false) };
704
+ Val::Bool(match name.as_str() {
705
+ "isInteger" => n.is_finite() && n == libm::trunc(n),
706
+ "isNaN" => n.is_nan(),
707
+ "isFinite" => n.is_finite(),
708
+ "isSafeInteger" => n.is_finite() && n == libm::trunc(n) && libm::fabs(n) <= 9007199254740991.0,
709
+ _ => false,
710
+ })
711
+ }
712
+
713
+ /// res.writeHead(status, headers)
714
+ fn __zoo_set_headers(cx: &mut Ctx, a: &[Val]) -> Val {
715
+ if let Some(Val::Obj(kv)) = a.get(0) {
716
+ for (k, v) in kv.iter() { cx.res_header(&Val::str(k), v); }
717
+ }
718
+ Val::Undef
719
+ }
720
+ `;
721
+
722
+ /**
723
+ * Whole crate: { 'Cargo.toml', 'src/lib.rs' }.
724
+ * @param {{ name: string, runtimePath: string, env: [string,string][], routes: string[] (emitted route sources), routeCount: number, header?: string }} o
725
+ */
726
+ export function emitCrate({ name, runtimePath, env, routes, routeCount, header = '' }) {
727
+ const crate = sanitizeCrateName(name);
728
+ const cargo = `[package]
729
+ name = ${JSON.stringify(crate)}
730
+ version = "0.1.0"
731
+ edition = "2021"
732
+ description = "Generated by openzoo-transmute: Vercel Lambdas re-hosted as a Solana program"
733
+ publish = false
734
+
735
+ [lib]
736
+ crate-type = ["cdylib", "lib"]
737
+
738
+ [dependencies]
739
+ pinocchio = "0.11.2"
740
+ libm = "0.2"
741
+ zoo-host = { path = ${JSON.stringify(runtimePath)} }
742
+
743
+ [profile.release]
744
+ overflow-checks = false
745
+ lto = "fat"
746
+ codegen-units = 1
747
+ opt-level = 3
748
+ `;
749
+ const envTable = env.map(([k, v]) => `(${rustStr(k)}, ${rustStr(v)})`).join(', ');
750
+ const routesTable = Array.from({ length: routeCount }, (_, i) => `route_${i}`).join(', ');
751
+ const lib = `//! Generated by openzoo-transmute — do not edit.
752
+ ${header ? header.split('\n').map((l) => `//! ${l}`.trimEnd()).join('\n') + '\n' : ''}#![no_std]
753
+ #![allow(unused_mut, unused_variables, unused_assignments, dead_code, unused_parens, unreachable_code, unused_braces, unused_labels, unused_imports, non_snake_case, non_upper_case_globals, unused_unsafe, clippy::all)]
754
+ extern crate alloc;
755
+ use alloc::{format, string::String, vec::Vec};
756
+ use pinocchio::{AccountView, Address, ProgramResult};
757
+ use zoo_host::{Ctx, Route, Val};
758
+ use zoo_host::val as zv;
759
+ use zoo_host::json as zjson;
760
+
761
+ pinocchio::program_entrypoint!(process_instruction);
762
+ pinocchio::default_allocator!();
763
+ pinocchio::nostd_panic_handler!();
764
+
765
+ /// The Lambda environment table, baked at build time (public on chain).
766
+ const ENV: &[(&str, &str)] = &[${envTable}];
767
+ /// Route table: instruction byte 1 indexes it.
768
+ const ROUTES: &[Route] = &[${routesTable}];
769
+
770
+ pub fn process_instruction(program_id: &Address, accounts: &mut [AccountView], data: &[u8]) -> ProgramResult {
771
+ zoo_host::dispatch(program_id, accounts, data, ROUTES, ENV)
772
+ }
773
+ ${PRELUDE}
774
+ ${routes.join('\n\n')}
775
+ `;
776
+ return { 'Cargo.toml': cargo, 'src/lib.rs': lib, crateName: crate };
777
+ }