@bastani/atomic 0.9.26 → 0.9.27-alpha.1
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.
- package/CHANGELOG.md +18 -0
- package/dist/builtin/intercom/CHANGELOG.md +6 -0
- package/dist/builtin/intercom/index.bundle.mjs +21 -6
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +8 -0
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/src/extension/index.bundle.mjs +693 -642
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +8 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +71 -20
- package/dist/builtin/workflows/src/index.js +31 -10
- package/dist/codemode-worker.js +3309 -0
- package/dist/config.d.ts +7 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +38 -12
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-custom-message-commit.d.ts.map +1 -1
- package/dist/core/agent-session-custom-message-commit.js +6 -0
- package/dist/core/agent-session-custom-message-commit.js.map +1 -1
- package/dist/core/agent-session-extension-bindings.d.ts.map +1 -1
- package/dist/core/agent-session-extension-bindings.js +22 -6
- package/dist/core/agent-session-extension-bindings.js.map +1 -1
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +3 -1
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session-tasks.js +9 -4
- package/dist/core/agent-session-tasks.js.map +1 -1
- package/dist/core/auth-storage-backends.d.ts +3 -0
- package/dist/core/auth-storage-backends.d.ts.map +1 -1
- package/dist/core/auth-storage-backends.js +45 -1
- package/dist/core/auth-storage-backends.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +14 -0
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/extensions/provider-types.d.ts +3 -2
- package/dist/core/extensions/provider-types.d.ts.map +1 -1
- package/dist/core/extensions/provider-types.js.map +1 -1
- package/dist/core/extensions/runner-events.d.ts.map +1 -1
- package/dist/core/extensions/runner-events.js +10 -1
- package/dist/core/extensions/runner-events.js.map +1 -1
- package/dist/extensions/codemode/execute.d.ts.map +1 -1
- package/dist/extensions/codemode/execute.js +40 -6
- package/dist/extensions/codemode/execute.js.map +1 -1
- package/dist/extensions/codemode/tool.d.ts.map +1 -1
- package/dist/extensions/codemode/tool.js +1 -0
- package/dist/extensions/codemode/tool.js.map +1 -1
- package/dist/extensions/mcp/tools.d.ts.map +1 -1
- package/dist/extensions/mcp/tools.js +4 -9
- package/dist/extensions/mcp/tools.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/interactive-agent-events.js +21 -0
- package/dist/modes/interactive/interactive-agent-events.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-base.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-base.js +1 -0
- package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-helpers.js +1 -1
- package/dist/modes/interactive/interactive-mode-helpers.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
- package/dist/modes/interactive/interactive-process-lifecycle.js +7 -4
- package/dist/modes/interactive/interactive-process-lifecycle.js.map +1 -1
- package/dist/utils/output-files.d.ts +7 -0
- package/dist/utils/output-files.d.ts.map +1 -0
- package/dist/utils/output-files.js +20 -0
- package/dist/utils/output-files.js.map +1 -0
- package/docs/codemode.md +3 -3
- package/docs/extensions/api-reference.md +3 -1
- package/docs/extensions.md +2 -0
- package/docs/sdk.md +2 -0
- package/docs/settings.md +2 -0
- package/docs/subagents.md +4 -0
- package/docs/workflows/api-reference.md +6 -2
- package/docs/workflows/operations.md +10 -10
- package/npm-shrinkwrap.json +32 -32
- package/package.json +6 -6
|
@@ -0,0 +1,3309 @@
|
|
|
1
|
+
// ../../node_modules/@earendil-works/pi-codemode/dist/runtime/worker.js
|
|
2
|
+
import { parentPort, workerData } from "node:worker_threads";
|
|
3
|
+
|
|
4
|
+
// ../../node_modules/quickjs-wasi/dist/wasi-shim.js
|
|
5
|
+
function createWasiShim(memoryAccessor) {
|
|
6
|
+
const ERRNO_SUCCESS = 0;
|
|
7
|
+
const ERRNO_BADF = 8;
|
|
8
|
+
const ERRNO_NOSYS = 52;
|
|
9
|
+
const CLOCK_REALTIME = 0;
|
|
10
|
+
const CLOCK_MONOTONIC = 1;
|
|
11
|
+
return {
|
|
12
|
+
clock_time_get(clockId, _precision, resultPtr) {
|
|
13
|
+
const mem = memoryAccessor();
|
|
14
|
+
const view = new DataView(mem.buffer);
|
|
15
|
+
if (clockId === CLOCK_REALTIME || clockId === CLOCK_MONOTONIC) {
|
|
16
|
+
const timeNs = BigInt(Date.now()) * 1000000n;
|
|
17
|
+
view.setBigUint64(resultPtr, timeNs, true);
|
|
18
|
+
return ERRNO_SUCCESS;
|
|
19
|
+
}
|
|
20
|
+
return ERRNO_NOSYS;
|
|
21
|
+
},
|
|
22
|
+
fd_write(fd, iovsPtr, iovsLen, nwrittenPtr) {
|
|
23
|
+
const mem = memoryAccessor();
|
|
24
|
+
const view = new DataView(mem.buffer);
|
|
25
|
+
const bytes = new Uint8Array(mem.buffer);
|
|
26
|
+
let totalWritten = 0;
|
|
27
|
+
if (fd !== 1 && fd !== 2) {
|
|
28
|
+
return ERRNO_BADF;
|
|
29
|
+
}
|
|
30
|
+
for (let i = 0; i < iovsLen; i++) {
|
|
31
|
+
const bufPtr = view.getUint32(iovsPtr + i * 8, true);
|
|
32
|
+
const bufLen = view.getUint32(iovsPtr + i * 8 + 4, true);
|
|
33
|
+
const chunk = bytes.slice(bufPtr, bufPtr + bufLen);
|
|
34
|
+
const text = new TextDecoder().decode(chunk);
|
|
35
|
+
if (fd === 1) {
|
|
36
|
+
if (typeof process !== "undefined" && process.stdout) {
|
|
37
|
+
process.stdout.write(text);
|
|
38
|
+
} else {
|
|
39
|
+
console.log(text);
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
if (typeof process !== "undefined" && process.stderr) {
|
|
43
|
+
process.stderr.write(text);
|
|
44
|
+
} else {
|
|
45
|
+
console.error(text);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
totalWritten += bufLen;
|
|
49
|
+
}
|
|
50
|
+
view.setUint32(nwrittenPtr, totalWritten, true);
|
|
51
|
+
return ERRNO_SUCCESS;
|
|
52
|
+
},
|
|
53
|
+
fd_close(_fd) {
|
|
54
|
+
return ERRNO_NOSYS;
|
|
55
|
+
},
|
|
56
|
+
fd_fdstat_get(fd, statPtr) {
|
|
57
|
+
const mem = memoryAccessor();
|
|
58
|
+
const view = new DataView(mem.buffer);
|
|
59
|
+
if (fd === 1 || fd === 2) {
|
|
60
|
+
view.setUint8(statPtr, 2);
|
|
61
|
+
view.setUint16(statPtr + 2, 0, true);
|
|
62
|
+
view.setBigUint64(statPtr + 8, 0n, true);
|
|
63
|
+
view.setBigUint64(statPtr + 16, 0n, true);
|
|
64
|
+
return ERRNO_SUCCESS;
|
|
65
|
+
}
|
|
66
|
+
return ERRNO_BADF;
|
|
67
|
+
},
|
|
68
|
+
fd_seek(_fd, _offset, _whence, _resultPtr) {
|
|
69
|
+
return ERRNO_NOSYS;
|
|
70
|
+
},
|
|
71
|
+
random_get(bufPtr, bufLen) {
|
|
72
|
+
const mem = memoryAccessor();
|
|
73
|
+
const bytes = new Uint8Array(mem.buffer, bufPtr, bufLen);
|
|
74
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
75
|
+
crypto.getRandomValues(bytes);
|
|
76
|
+
} else {
|
|
77
|
+
for (let i = 0; i < bufLen; i++) {
|
|
78
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return ERRNO_SUCCESS;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ../../node_modules/quickjs-wasi/dist/extensions.js
|
|
87
|
+
var decoder = new TextDecoder();
|
|
88
|
+
function parseVersionString(memory, ptr) {
|
|
89
|
+
const mem = new Uint8Array(memory.buffer);
|
|
90
|
+
let end = ptr;
|
|
91
|
+
while (mem[end] !== 0)
|
|
92
|
+
end++;
|
|
93
|
+
if (end === ptr)
|
|
94
|
+
return {};
|
|
95
|
+
const str = decoder.decode(mem.slice(ptr, end));
|
|
96
|
+
const result = {};
|
|
97
|
+
for (const line of str.split("\n")) {
|
|
98
|
+
const eq = line.indexOf("=");
|
|
99
|
+
if (eq > 0) {
|
|
100
|
+
result[line.slice(0, eq)] = line.slice(eq + 1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
function extractExtensionVersions(ext, memory) {
|
|
106
|
+
const versionsFnName = `qjs_ext_${ext.name.replace(/-/g, "_")}_versions`;
|
|
107
|
+
const versionsFn = ext.instance.exports[versionsFnName];
|
|
108
|
+
if (typeof versionsFn !== "function")
|
|
109
|
+
return void 0;
|
|
110
|
+
const ptr = versionsFn();
|
|
111
|
+
if (ptr === 0)
|
|
112
|
+
return void 0;
|
|
113
|
+
return parseVersionString(memory, ptr);
|
|
114
|
+
}
|
|
115
|
+
function readULEB128(bytes, offset) {
|
|
116
|
+
let result = 0;
|
|
117
|
+
let shift = 0;
|
|
118
|
+
while (offset.value < bytes.length) {
|
|
119
|
+
const byte = bytes[offset.value++];
|
|
120
|
+
result |= (byte & 127) << shift;
|
|
121
|
+
if ((byte & 128) === 0)
|
|
122
|
+
break;
|
|
123
|
+
shift += 7;
|
|
124
|
+
}
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
function parseDylink(module) {
|
|
128
|
+
const sections = WebAssembly.Module.customSections(module, "dylink.0");
|
|
129
|
+
if (sections.length === 0)
|
|
130
|
+
return null;
|
|
131
|
+
const bytes = new Uint8Array(sections[0]);
|
|
132
|
+
const offset = { value: 0 };
|
|
133
|
+
const info = {
|
|
134
|
+
memorySize: 0,
|
|
135
|
+
memoryAlignment: 0,
|
|
136
|
+
tableSize: 0,
|
|
137
|
+
tableAlignment: 0,
|
|
138
|
+
needed: []
|
|
139
|
+
};
|
|
140
|
+
while (offset.value < bytes.length) {
|
|
141
|
+
const subsectionType = readULEB128(bytes, offset);
|
|
142
|
+
const subsectionSize = readULEB128(bytes, offset);
|
|
143
|
+
const subsectionEnd = offset.value + subsectionSize;
|
|
144
|
+
if (subsectionType === 1) {
|
|
145
|
+
info.memorySize = readULEB128(bytes, offset);
|
|
146
|
+
info.memoryAlignment = readULEB128(bytes, offset);
|
|
147
|
+
info.tableSize = readULEB128(bytes, offset);
|
|
148
|
+
info.tableAlignment = readULEB128(bytes, offset);
|
|
149
|
+
} else if (subsectionType === 2) {
|
|
150
|
+
const count = readULEB128(bytes, offset);
|
|
151
|
+
for (let i = 0; i < count; i++) {
|
|
152
|
+
const len = readULEB128(bytes, offset);
|
|
153
|
+
const name = new TextDecoder().decode(bytes.slice(offset.value, offset.value + len));
|
|
154
|
+
offset.value += len;
|
|
155
|
+
info.needed.push(name);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
offset.value = subsectionEnd;
|
|
159
|
+
}
|
|
160
|
+
return info;
|
|
161
|
+
}
|
|
162
|
+
async function loadExtension(descriptor, mainExports, wasiBuiltins, wasiUserOverrides, memoryProxy, allocBase) {
|
|
163
|
+
let module;
|
|
164
|
+
if (descriptor.wasm instanceof WebAssembly.Module) {
|
|
165
|
+
module = descriptor.wasm;
|
|
166
|
+
} else {
|
|
167
|
+
module = await WebAssembly.compile(descriptor.wasm);
|
|
168
|
+
}
|
|
169
|
+
const dylink = parseDylink(module);
|
|
170
|
+
if (!dylink) {
|
|
171
|
+
throw new Error(`Extension "${descriptor.name}" is not a WASM shared library (missing dylink.0 section)`);
|
|
172
|
+
}
|
|
173
|
+
const memory = mainExports.memory;
|
|
174
|
+
const table = mainExports.__indirect_function_table;
|
|
175
|
+
const stackPointer = mainExports.__stack_pointer;
|
|
176
|
+
const mallocFn = mainExports.malloc;
|
|
177
|
+
if (!memory)
|
|
178
|
+
throw new Error("Main module does not export memory");
|
|
179
|
+
if (!table)
|
|
180
|
+
throw new Error("Main module does not export __indirect_function_table");
|
|
181
|
+
if (!mallocFn)
|
|
182
|
+
throw new Error("Main module does not export malloc");
|
|
183
|
+
let memoryBase;
|
|
184
|
+
let tableBase;
|
|
185
|
+
if (allocBase) {
|
|
186
|
+
memoryBase = allocBase.memoryBase;
|
|
187
|
+
tableBase = allocBase.tableBase;
|
|
188
|
+
} else {
|
|
189
|
+
if (dylink.memorySize > 0) {
|
|
190
|
+
memoryBase = mallocFn(dylink.memorySize);
|
|
191
|
+
if (memoryBase === 0) {
|
|
192
|
+
throw new Error(`Failed to allocate ${dylink.memorySize} bytes for extension "${descriptor.name}"`);
|
|
193
|
+
}
|
|
194
|
+
new Uint8Array(memory.buffer, memoryBase, dylink.memorySize).fill(0);
|
|
195
|
+
} else {
|
|
196
|
+
memoryBase = 0;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (allocBase) {
|
|
200
|
+
tableBase = allocBase.tableBase;
|
|
201
|
+
if (tableBase + dylink.tableSize > table.length) {
|
|
202
|
+
table.grow(tableBase + dylink.tableSize - table.length);
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
tableBase = table.length;
|
|
206
|
+
if (dylink.tableSize > 0) {
|
|
207
|
+
table.grow(dylink.tableSize);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const extImports = WebAssembly.Module.imports(module);
|
|
211
|
+
const importObj = {
|
|
212
|
+
env: {},
|
|
213
|
+
"GOT.mem": {},
|
|
214
|
+
"GOT.func": {}
|
|
215
|
+
};
|
|
216
|
+
const needsWasi = extImports.some((imp) => imp.module === "wasi_snapshot_preview1");
|
|
217
|
+
if (needsWasi) {
|
|
218
|
+
const extWasi = descriptor.wasi && memoryProxy ? descriptor.wasi(memoryProxy) : void 0;
|
|
219
|
+
importObj["wasi_snapshot_preview1"] = {
|
|
220
|
+
...wasiBuiltins,
|
|
221
|
+
// 1. Built-in defaults (lowest priority)
|
|
222
|
+
...extWasi,
|
|
223
|
+
// 2. Extension-provided
|
|
224
|
+
...wasiUserOverrides
|
|
225
|
+
// 3. User overrides (highest priority)
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
const extExportNames = new Set(WebAssembly.Module.exports(module).filter((e) => e.kind === "function").map((e) => e.name));
|
|
229
|
+
const unresolvedFuncs = /* @__PURE__ */ new Set();
|
|
230
|
+
for (const imp of extImports) {
|
|
231
|
+
if (imp.module === "env") {
|
|
232
|
+
if (imp.name === "memory" && imp.kind === "memory") {
|
|
233
|
+
importObj.env.memory = memory;
|
|
234
|
+
} else if (imp.name === "__indirect_function_table" && imp.kind === "table") {
|
|
235
|
+
importObj.env.__indirect_function_table = table;
|
|
236
|
+
} else if (imp.name === "__memory_base" && imp.kind === "global") {
|
|
237
|
+
importObj.env.__memory_base = new WebAssembly.Global({ value: "i32", mutable: false }, memoryBase);
|
|
238
|
+
} else if (imp.name === "__table_base" && imp.kind === "global") {
|
|
239
|
+
importObj.env.__table_base = new WebAssembly.Global({ value: "i32", mutable: false }, tableBase);
|
|
240
|
+
} else if (imp.name === "__stack_pointer" && imp.kind === "global") {
|
|
241
|
+
importObj.env.__stack_pointer = stackPointer;
|
|
242
|
+
} else if (imp.kind === "function") {
|
|
243
|
+
const resolved = mainExports[imp.name];
|
|
244
|
+
if (resolved && typeof resolved === "function") {
|
|
245
|
+
importObj.env[imp.name] = resolved;
|
|
246
|
+
} else {
|
|
247
|
+
unresolvedFuncs.add(imp.name);
|
|
248
|
+
importObj.env[imp.name] = () => {
|
|
249
|
+
throw new Error(`Extension "${descriptor.name}" called unresolved symbol: env.${imp.name}`);
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
} else if (imp.kind === "global") {
|
|
253
|
+
const resolved = mainExports[imp.name];
|
|
254
|
+
if (resolved instanceof WebAssembly.Global) {
|
|
255
|
+
importObj.env[imp.name] = resolved;
|
|
256
|
+
} else {
|
|
257
|
+
importObj.env[imp.name] = new WebAssembly.Global({ value: "i32", mutable: true }, 0);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
} else if (imp.module === "GOT.mem" && imp.kind === "global") {
|
|
261
|
+
importObj["GOT.mem"][imp.name] = new WebAssembly.Global({ value: "i32", mutable: true }, 0);
|
|
262
|
+
} else if (imp.module === "GOT.func" && imp.kind === "global") {
|
|
263
|
+
const resolved = mainExports[imp.name];
|
|
264
|
+
if (resolved && typeof resolved === "function") {
|
|
265
|
+
const idx = table.length;
|
|
266
|
+
table.grow(1);
|
|
267
|
+
table.set(idx, resolved);
|
|
268
|
+
importObj["GOT.func"][imp.name] = new WebAssembly.Global({ value: "i32", mutable: true }, idx);
|
|
269
|
+
} else {
|
|
270
|
+
importObj["GOT.func"][imp.name] = new WebAssembly.Global({ value: "i32", mutable: true }, 0);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const selfResolvable = [...unresolvedFuncs].filter((name) => extExportNames.has(name));
|
|
275
|
+
const wrappers = {};
|
|
276
|
+
for (const name of selfResolvable) {
|
|
277
|
+
const wrapper = { target: null };
|
|
278
|
+
wrappers[name] = wrapper;
|
|
279
|
+
importObj.env[name] = (...args) => {
|
|
280
|
+
if (!wrapper.target) {
|
|
281
|
+
throw new Error(`Extension "${descriptor.name}" called unresolved symbol during init: env.${name}`);
|
|
282
|
+
}
|
|
283
|
+
return wrapper.target(...args);
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
const instance = await WebAssembly.instantiate(module, importObj);
|
|
287
|
+
const extExports = instance.exports;
|
|
288
|
+
for (const name of selfResolvable) {
|
|
289
|
+
const selfExport = extExports[name];
|
|
290
|
+
if (typeof selfExport === "function") {
|
|
291
|
+
wrappers[name].target = selfExport;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (typeof extExports.__wasm_apply_data_relocs === "function") {
|
|
295
|
+
extExports.__wasm_apply_data_relocs();
|
|
296
|
+
}
|
|
297
|
+
if (typeof extExports.__wasm_call_ctors === "function") {
|
|
298
|
+
extExports.__wasm_call_ctors();
|
|
299
|
+
}
|
|
300
|
+
const initFn = descriptor.initFn ?? `qjs_ext_${descriptor.name.replace(/-/g, "_")}_init`;
|
|
301
|
+
const ext = {
|
|
302
|
+
name: descriptor.name,
|
|
303
|
+
module,
|
|
304
|
+
instance,
|
|
305
|
+
dylink,
|
|
306
|
+
memoryBase,
|
|
307
|
+
tableBase,
|
|
308
|
+
initFn
|
|
309
|
+
};
|
|
310
|
+
ext.versions = extractExtensionVersions(ext, memory);
|
|
311
|
+
return ext;
|
|
312
|
+
}
|
|
313
|
+
function initExtension(ext, mainExports) {
|
|
314
|
+
const initFunc = ext.instance.exports[ext.initFn];
|
|
315
|
+
if (typeof initFunc !== "function") {
|
|
316
|
+
throw new Error(`Extension "${ext.name}" does not export init function "${ext.initFn}"`);
|
|
317
|
+
}
|
|
318
|
+
const ctxPtr = mainExports.qjs_get_context_ptr();
|
|
319
|
+
const rtPtr = mainExports.qjs_get_runtime_ptr();
|
|
320
|
+
const result = initFunc(ctxPtr, rtPtr);
|
|
321
|
+
if (result !== 0) {
|
|
322
|
+
throw new Error(`Extension "${ext.name}" init function returned error code ${result}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
async function restoreExtensions(descriptors, extensionMeta, mainExports, wasiBuiltins, wasiUserOverrides, memoryProxy) {
|
|
326
|
+
const loaded = [];
|
|
327
|
+
for (const meta of extensionMeta) {
|
|
328
|
+
const descriptor = descriptors.find((d) => d.name === meta.name);
|
|
329
|
+
if (!descriptor) {
|
|
330
|
+
throw new Error(`Extension "${meta.name}" required by snapshot but not provided`);
|
|
331
|
+
}
|
|
332
|
+
const ext = await loadExtension(descriptor, mainExports, wasiBuiltins, wasiUserOverrides, memoryProxy, {
|
|
333
|
+
memoryBase: meta.memoryBase,
|
|
334
|
+
tableBase: meta.tableBase
|
|
335
|
+
});
|
|
336
|
+
ext.initFn = meta.initFn;
|
|
337
|
+
loaded.push(ext);
|
|
338
|
+
}
|
|
339
|
+
return loaded;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ../../node_modules/quickjs-wasi/dist/version.js
|
|
343
|
+
var VERSION = "3.6.2";
|
|
344
|
+
|
|
345
|
+
// ../../node_modules/quickjs-wasi/dist/index.js
|
|
346
|
+
var __addDisposableResource = function(env, value, async) {
|
|
347
|
+
if (value !== null && value !== void 0) {
|
|
348
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
349
|
+
var dispose, inner;
|
|
350
|
+
if (async) {
|
|
351
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
352
|
+
dispose = value[Symbol.asyncDispose];
|
|
353
|
+
}
|
|
354
|
+
if (dispose === void 0) {
|
|
355
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
356
|
+
dispose = value[Symbol.dispose];
|
|
357
|
+
if (async) inner = dispose;
|
|
358
|
+
}
|
|
359
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
360
|
+
if (inner) dispose = function() {
|
|
361
|
+
try {
|
|
362
|
+
inner.call(this);
|
|
363
|
+
} catch (e) {
|
|
364
|
+
return Promise.reject(e);
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
env.stack.push({ value, dispose, async });
|
|
368
|
+
} else if (async) {
|
|
369
|
+
env.stack.push({ async: true });
|
|
370
|
+
}
|
|
371
|
+
return value;
|
|
372
|
+
};
|
|
373
|
+
var __disposeResources = /* @__PURE__ */ (function(SuppressedError2) {
|
|
374
|
+
return function(env) {
|
|
375
|
+
function fail(e) {
|
|
376
|
+
env.error = env.hasError ? new SuppressedError2(e, env.error, "An error was suppressed during disposal.") : e;
|
|
377
|
+
env.hasError = true;
|
|
378
|
+
}
|
|
379
|
+
var r, s = 0;
|
|
380
|
+
function next() {
|
|
381
|
+
while (r = env.stack.pop()) {
|
|
382
|
+
try {
|
|
383
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
384
|
+
if (r.dispose) {
|
|
385
|
+
var result = r.dispose.call(r.value);
|
|
386
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) {
|
|
387
|
+
fail(e);
|
|
388
|
+
return next();
|
|
389
|
+
});
|
|
390
|
+
} else s |= 1;
|
|
391
|
+
} catch (e) {
|
|
392
|
+
fail(e);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
396
|
+
if (env.hasError) throw env.error;
|
|
397
|
+
}
|
|
398
|
+
return next();
|
|
399
|
+
};
|
|
400
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
|
|
401
|
+
var e = new Error(message);
|
|
402
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
403
|
+
});
|
|
404
|
+
var MAX_STACK_SIZE = 512 * 1024;
|
|
405
|
+
var EvalFlags = {
|
|
406
|
+
/** Global script mode (default). */
|
|
407
|
+
TYPE_GLOBAL: 0,
|
|
408
|
+
/**
|
|
409
|
+
* Module mode. `evalCode()` returns a handle to a Promise that resolves
|
|
410
|
+
* to the module's namespace object (its exports), or rejects if module
|
|
411
|
+
* evaluation throws. Use together with `executePendingJobs()` and
|
|
412
|
+
* `resolvePromise()`.
|
|
413
|
+
*/
|
|
414
|
+
TYPE_MODULE: 1 << 0,
|
|
415
|
+
/** Force strict mode. */
|
|
416
|
+
STRICT: 1 << 3,
|
|
417
|
+
/** Compile only; do not execute. */
|
|
418
|
+
COMPILE_ONLY: 1 << 5,
|
|
419
|
+
/** Omit stack frames before this eval from Error backtraces. */
|
|
420
|
+
BACKTRACE_BARRIER: 1 << 6,
|
|
421
|
+
/**
|
|
422
|
+
* Allow top-level `await` in global scripts. When used, `evalCode()`
|
|
423
|
+
* returns a handle to a Promise that resolves to the completion value.
|
|
424
|
+
* Use together with `executePendingJobs()` and `resolvePromise()`.
|
|
425
|
+
*/
|
|
426
|
+
ASYNC: 1 << 7
|
|
427
|
+
};
|
|
428
|
+
var CompileFlags = {
|
|
429
|
+
/** Strip source code from the bytecode (smaller output, no source in errors). */
|
|
430
|
+
STRIP_SOURCE: 1 << 4,
|
|
431
|
+
/** Strip debug information (line numbers, etc.) from the bytecode. */
|
|
432
|
+
STRIP_DEBUG: 1 << 5
|
|
433
|
+
};
|
|
434
|
+
var Intrinsics = {
|
|
435
|
+
/** `Date` constructor and prototype methods. */
|
|
436
|
+
DATE: 1 << 0,
|
|
437
|
+
/** `eval()` and `Function()` constructor. */
|
|
438
|
+
EVAL: 1 << 1,
|
|
439
|
+
/** `RegExp` constructor, prototype methods, and regex literals. */
|
|
440
|
+
REGEXP: 1 << 2,
|
|
441
|
+
/** `JSON.parse()` and `JSON.stringify()`. */
|
|
442
|
+
JSON: 1 << 3,
|
|
443
|
+
/** `Proxy` and `Reflect`. */
|
|
444
|
+
PROXY: 1 << 4,
|
|
445
|
+
/** `Map`, `Set`, `WeakMap`, `WeakSet`. */
|
|
446
|
+
MAP_SET: 1 << 5,
|
|
447
|
+
/** `ArrayBuffer`, `TypedArray` variants, `DataView`. */
|
|
448
|
+
TYPED_ARRAYS: 1 << 6,
|
|
449
|
+
/** `Promise`, `async`/`await`. */
|
|
450
|
+
PROMISE: 1 << 7,
|
|
451
|
+
/** `BigInt`. Note: BigInt is part of BaseObjects in quickjs-ng and cannot be fully removed. */
|
|
452
|
+
BIG_INT: 1 << 8,
|
|
453
|
+
/** `WeakRef` and `FinalizationRegistry`. */
|
|
454
|
+
WEAK_REF: 1 << 9,
|
|
455
|
+
/** `performance.now()`. */
|
|
456
|
+
PERFORMANCE: 1 << 10,
|
|
457
|
+
/** `DOMException` class. */
|
|
458
|
+
DOM_EXCEPTION: 1 << 11,
|
|
459
|
+
/**
|
|
460
|
+
* `atob()` and `btoa()` global functions. Also pulls in `DOMException` as
|
|
461
|
+
* a dependency (errors thrown by these functions are `DOMException`s).
|
|
462
|
+
*/
|
|
463
|
+
ATOB_BTOA: 1 << 12,
|
|
464
|
+
/** All intrinsics enabled (default). */
|
|
465
|
+
ALL: 4294967295
|
|
466
|
+
};
|
|
467
|
+
var SNAPSHOT_MAGIC = 1363825491;
|
|
468
|
+
var SNAPSHOT_VERSION = 2;
|
|
469
|
+
var SNAPSHOT_HEADER_SIZE = 24;
|
|
470
|
+
var QuickJS = class _QuickJS {
|
|
471
|
+
exports;
|
|
472
|
+
module;
|
|
473
|
+
instance;
|
|
474
|
+
encoder = new TextEncoder();
|
|
475
|
+
decoder = new TextDecoder();
|
|
476
|
+
disposed = false;
|
|
477
|
+
/** Registry of host callbacks, keyed by function name */
|
|
478
|
+
hostCallbacks = /* @__PURE__ */ new Map();
|
|
479
|
+
/** Counter for internal-only callbacks (e.g. promise settle handlers) */
|
|
480
|
+
nextInternalId = 1;
|
|
481
|
+
interruptHandler = null;
|
|
482
|
+
unhandledRejectionHandler = null;
|
|
483
|
+
moduleNormalizeHandler = null;
|
|
484
|
+
moduleLoadHandler = null;
|
|
485
|
+
timezoneOffsetHandler = null;
|
|
486
|
+
// Cached singleton handles
|
|
487
|
+
_global = null;
|
|
488
|
+
_versions = null;
|
|
489
|
+
_undefined = null;
|
|
490
|
+
_null = null;
|
|
491
|
+
_true = null;
|
|
492
|
+
_false = null;
|
|
493
|
+
// Handles that must be freed on dispose (e.g. unresolved promise resolve/reject functions)
|
|
494
|
+
_ownedHandles = /* @__PURE__ */ new Set();
|
|
495
|
+
/**
|
|
496
|
+
* The innermost active `withScope()` batch, if any. New non-singleton
|
|
497
|
+
* handles register themselves here so they can be freed together.
|
|
498
|
+
* @internal
|
|
499
|
+
*/
|
|
500
|
+
_activeScope = null;
|
|
501
|
+
/** Loaded extensions in deterministic order */
|
|
502
|
+
loadedExtensions = [];
|
|
503
|
+
constructor(module) {
|
|
504
|
+
this.module = module;
|
|
505
|
+
this.instance = null;
|
|
506
|
+
this.exports = null;
|
|
507
|
+
}
|
|
508
|
+
setInstance(instance) {
|
|
509
|
+
this.instance = instance;
|
|
510
|
+
this.exports = instance.exports;
|
|
511
|
+
}
|
|
512
|
+
// ---- Cached property accessors ----
|
|
513
|
+
/**
|
|
514
|
+
* Version information for the runtime and loaded native libraries.
|
|
515
|
+
* Always includes `"quickjs-wasi"` (the npm package version) and
|
|
516
|
+
* `"quickjs"` (the QuickJS engine version). Extensions may contribute
|
|
517
|
+
* additional entries for their native dependencies (e.g. `"ada"`, `"mbedtls"`).
|
|
518
|
+
*/
|
|
519
|
+
get versions() {
|
|
520
|
+
this.assertNotDisposed();
|
|
521
|
+
if (!this._versions) {
|
|
522
|
+
const result = {
|
|
523
|
+
"quickjs-wasi": VERSION,
|
|
524
|
+
quickjs: this.readCString(this.exports.qjs_get_quickjs_version())
|
|
525
|
+
};
|
|
526
|
+
for (const ext of this.loadedExtensions) {
|
|
527
|
+
if (ext.versions) {
|
|
528
|
+
Object.assign(result, ext.versions);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
this._versions = result;
|
|
532
|
+
}
|
|
533
|
+
return this._versions;
|
|
534
|
+
}
|
|
535
|
+
/** The global object. Cached; do not dispose. */
|
|
536
|
+
get global() {
|
|
537
|
+
if (!this._global) {
|
|
538
|
+
this._global = new JSValueHandle(this, this.exports.qjs_get_global(), true);
|
|
539
|
+
}
|
|
540
|
+
return this._global;
|
|
541
|
+
}
|
|
542
|
+
/** The undefined value. Cached; do not dispose. */
|
|
543
|
+
get undefined() {
|
|
544
|
+
if (!this._undefined) {
|
|
545
|
+
this._undefined = new JSValueHandle(this, this.exports.qjs_get_undefined(), true);
|
|
546
|
+
}
|
|
547
|
+
return this._undefined;
|
|
548
|
+
}
|
|
549
|
+
/** The null value. Cached; do not dispose. */
|
|
550
|
+
get null() {
|
|
551
|
+
if (!this._null) {
|
|
552
|
+
this._null = new JSValueHandle(this, this.exports.qjs_get_null(), true);
|
|
553
|
+
}
|
|
554
|
+
return this._null;
|
|
555
|
+
}
|
|
556
|
+
/** The true value. Cached; do not dispose. */
|
|
557
|
+
get true() {
|
|
558
|
+
if (!this._true) {
|
|
559
|
+
this._true = new JSValueHandle(this, this.exports.qjs_get_true(), true);
|
|
560
|
+
}
|
|
561
|
+
return this._true;
|
|
562
|
+
}
|
|
563
|
+
/** The false value. Cached; do not dispose. */
|
|
564
|
+
get false() {
|
|
565
|
+
if (!this._false) {
|
|
566
|
+
this._false = new JSValueHandle(this, this.exports.qjs_get_false(), true);
|
|
567
|
+
}
|
|
568
|
+
return this._false;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Create a fresh QuickJS VM instance.
|
|
572
|
+
*
|
|
573
|
+
* @param options - Optional configuration. Can also pass raw WASM bytes
|
|
574
|
+
* directly for backwards compatibility.
|
|
575
|
+
*/
|
|
576
|
+
static async create(options) {
|
|
577
|
+
const opts = _QuickJS.normalizeOptions(options);
|
|
578
|
+
const module = await _QuickJS.resolveModule(opts.wasm);
|
|
579
|
+
const vm = new _QuickJS(module);
|
|
580
|
+
const { instance, wasiBuiltins, wasiUserOverrides, memoryProxy } = await _QuickJS.instantiate(module, vm, opts.wasi);
|
|
581
|
+
vm.setInstance(instance);
|
|
582
|
+
vm.exports._initialize();
|
|
583
|
+
const result = opts.intrinsics !== void 0 ? vm.exports.qjs_init2(opts.intrinsics) : vm.exports.qjs_init();
|
|
584
|
+
if (result !== 0) {
|
|
585
|
+
throw new Error("Failed to initialize QuickJS runtime");
|
|
586
|
+
}
|
|
587
|
+
if (opts.extensions) {
|
|
588
|
+
const mainExports = instance.exports;
|
|
589
|
+
for (const desc of opts.extensions) {
|
|
590
|
+
const ext = await loadExtension(desc, mainExports, wasiBuiltins, wasiUserOverrides, memoryProxy);
|
|
591
|
+
vm.loadedExtensions.push(ext);
|
|
592
|
+
initExtension(ext, mainExports);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
_QuickJS.applyLimits(vm, opts);
|
|
596
|
+
return vm;
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Restore a QuickJS VM from a snapshot.
|
|
600
|
+
*
|
|
601
|
+
* @param snapshot - The snapshot to restore from.
|
|
602
|
+
* @param options - Optional configuration. Can also pass raw WASM bytes
|
|
603
|
+
* directly for backwards compatibility.
|
|
604
|
+
*/
|
|
605
|
+
static async restore(snapshot, options) {
|
|
606
|
+
const opts = _QuickJS.normalizeOptions(options);
|
|
607
|
+
const module = await _QuickJS.resolveModule(opts.wasm);
|
|
608
|
+
const vm = new _QuickJS(module);
|
|
609
|
+
const { instance, wasiBuiltins, wasiUserOverrides, memoryProxy } = await _QuickJS.instantiate(module, vm, opts.wasi);
|
|
610
|
+
vm.setInstance(instance);
|
|
611
|
+
const mainExports = instance.exports;
|
|
612
|
+
const exportedMemory = vm.exports.memory;
|
|
613
|
+
const currentPages = exportedMemory.buffer.byteLength / 65536;
|
|
614
|
+
const neededPages = Math.ceil(snapshot.memory.byteLength / 65536);
|
|
615
|
+
if (neededPages > currentPages) {
|
|
616
|
+
exportedMemory.grow(neededPages - currentPages);
|
|
617
|
+
}
|
|
618
|
+
if (snapshot.extensions.length > 0) {
|
|
619
|
+
const descriptors = opts.extensions ?? [];
|
|
620
|
+
vm.loadedExtensions = await restoreExtensions(descriptors, snapshot.extensions, mainExports, wasiBuiltins, wasiUserOverrides, memoryProxy);
|
|
621
|
+
}
|
|
622
|
+
const dst = new Uint8Array(exportedMemory.buffer);
|
|
623
|
+
dst.set(snapshot.memory);
|
|
624
|
+
vm.exports.qjs_set_runtime_and_context(snapshot.runtimePtr, snapshot.contextPtr);
|
|
625
|
+
vm.exports.__stack_pointer.value = snapshot.stackPointer;
|
|
626
|
+
_QuickJS.applyLimits(vm, opts);
|
|
627
|
+
return vm;
|
|
628
|
+
}
|
|
629
|
+
// ---- Snapshot serialization ----
|
|
630
|
+
/**
|
|
631
|
+
* Serialize a snapshot to a binary buffer for persistent storage.
|
|
632
|
+
*
|
|
633
|
+
* The format includes a versioned header followed by the raw memory.
|
|
634
|
+
* Apply your own compression (gzip, zstd, etc.) on top for smaller
|
|
635
|
+
* storage. The memory compresses well due to its large zero regions.
|
|
636
|
+
*
|
|
637
|
+
* Format (version 1):
|
|
638
|
+
* ```
|
|
639
|
+
* Offset Size Field
|
|
640
|
+
* 0 4 Magic: "QJSS" (0x514A5353, big-endian)
|
|
641
|
+
* 4 1 Version: 1
|
|
642
|
+
* 5 3 Reserved (zero)
|
|
643
|
+
* 8 4 Memory size in bytes (u32 little-endian)
|
|
644
|
+
* 12 4 Stack pointer (u32 little-endian)
|
|
645
|
+
* 16 4 Runtime pointer (u32 little-endian)
|
|
646
|
+
* 20 4 Context pointer (u32 little-endian)
|
|
647
|
+
* 24 N Memory data (N = memory size from offset 8)
|
|
648
|
+
* ```
|
|
649
|
+
*/
|
|
650
|
+
static serializeSnapshot(snapshot) {
|
|
651
|
+
const textEncoder = new TextEncoder();
|
|
652
|
+
let extMetaSize = 4;
|
|
653
|
+
const extEncodedNames = [];
|
|
654
|
+
const extEncodedInitFns = [];
|
|
655
|
+
for (const ext of snapshot.extensions) {
|
|
656
|
+
const nameBytes = textEncoder.encode(ext.name);
|
|
657
|
+
const initFnBytes = textEncoder.encode(ext.initFn);
|
|
658
|
+
extEncodedNames.push(nameBytes);
|
|
659
|
+
extEncodedInitFns.push(initFnBytes);
|
|
660
|
+
extMetaSize += 4 + nameBytes.length + 4 + 4 + 4 + initFnBytes.length;
|
|
661
|
+
}
|
|
662
|
+
const totalSize = SNAPSHOT_HEADER_SIZE + extMetaSize + snapshot.memory.byteLength;
|
|
663
|
+
const buffer = new ArrayBuffer(totalSize);
|
|
664
|
+
const view = new DataView(buffer);
|
|
665
|
+
const bytes = new Uint8Array(buffer);
|
|
666
|
+
view.setUint32(0, SNAPSHOT_MAGIC, false);
|
|
667
|
+
view.setUint8(4, SNAPSHOT_VERSION);
|
|
668
|
+
view.setUint32(8, snapshot.memory.byteLength, true);
|
|
669
|
+
view.setUint32(12, snapshot.stackPointer, true);
|
|
670
|
+
view.setUint32(16, snapshot.runtimePtr, true);
|
|
671
|
+
view.setUint32(20, snapshot.contextPtr, true);
|
|
672
|
+
let offset = SNAPSHOT_HEADER_SIZE;
|
|
673
|
+
view.setUint32(offset, snapshot.extensions.length, true);
|
|
674
|
+
offset += 4;
|
|
675
|
+
for (let i = 0; i < snapshot.extensions.length; i++) {
|
|
676
|
+
const ext = snapshot.extensions[i];
|
|
677
|
+
const nameBytes = extEncodedNames[i];
|
|
678
|
+
const initFnBytes = extEncodedInitFns[i];
|
|
679
|
+
view.setUint32(offset, nameBytes.length, true);
|
|
680
|
+
offset += 4;
|
|
681
|
+
bytes.set(nameBytes, offset);
|
|
682
|
+
offset += nameBytes.length;
|
|
683
|
+
view.setUint32(offset, ext.memoryBase, true);
|
|
684
|
+
offset += 4;
|
|
685
|
+
view.setUint32(offset, ext.tableBase, true);
|
|
686
|
+
offset += 4;
|
|
687
|
+
view.setUint32(offset, initFnBytes.length, true);
|
|
688
|
+
offset += 4;
|
|
689
|
+
bytes.set(initFnBytes, offset);
|
|
690
|
+
offset += initFnBytes.length;
|
|
691
|
+
}
|
|
692
|
+
bytes.set(snapshot.memory, offset);
|
|
693
|
+
return bytes;
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Deserialize a snapshot from a binary buffer produced by `serializeSnapshot()`.
|
|
697
|
+
*/
|
|
698
|
+
static deserializeSnapshot(data) {
|
|
699
|
+
if (data.length < SNAPSHOT_HEADER_SIZE) {
|
|
700
|
+
throw new Error("Invalid snapshot: too small");
|
|
701
|
+
}
|
|
702
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
703
|
+
const magic = view.getUint32(0, false);
|
|
704
|
+
if (magic !== SNAPSHOT_MAGIC) {
|
|
705
|
+
throw new Error(`Invalid snapshot: bad magic (expected 0x${SNAPSHOT_MAGIC.toString(16)}, got 0x${magic.toString(16)})`);
|
|
706
|
+
}
|
|
707
|
+
const version = view.getUint8(4);
|
|
708
|
+
if (version !== SNAPSHOT_VERSION && version !== 1) {
|
|
709
|
+
throw new Error(`Unsupported snapshot version: ${version} (expected ${SNAPSHOT_VERSION})`);
|
|
710
|
+
}
|
|
711
|
+
const memorySize = view.getUint32(8, true);
|
|
712
|
+
const stackPointer = view.getUint32(12, true);
|
|
713
|
+
const runtimePtr = view.getUint32(16, true);
|
|
714
|
+
const contextPtr = view.getUint32(20, true);
|
|
715
|
+
let extensions = [];
|
|
716
|
+
let memoryOffset = SNAPSHOT_HEADER_SIZE;
|
|
717
|
+
if (version >= 2) {
|
|
718
|
+
const extCount = view.getUint32(24, true);
|
|
719
|
+
let offset = 28;
|
|
720
|
+
const textDecoder = new TextDecoder();
|
|
721
|
+
for (let i = 0; i < extCount; i++) {
|
|
722
|
+
const nameLen = view.getUint32(offset, true);
|
|
723
|
+
offset += 4;
|
|
724
|
+
const name = textDecoder.decode(data.slice(offset, offset + nameLen));
|
|
725
|
+
offset += nameLen;
|
|
726
|
+
const memBase = view.getUint32(offset, true);
|
|
727
|
+
offset += 4;
|
|
728
|
+
const tblBase = view.getUint32(offset, true);
|
|
729
|
+
offset += 4;
|
|
730
|
+
const initFnLen = view.getUint32(offset, true);
|
|
731
|
+
offset += 4;
|
|
732
|
+
const initFn = textDecoder.decode(data.slice(offset, offset + initFnLen));
|
|
733
|
+
offset += initFnLen;
|
|
734
|
+
extensions.push({ name, memoryBase: memBase, tableBase: tblBase, initFn });
|
|
735
|
+
}
|
|
736
|
+
memoryOffset = offset;
|
|
737
|
+
}
|
|
738
|
+
const expectedSize = memoryOffset + memorySize;
|
|
739
|
+
if (data.length < expectedSize) {
|
|
740
|
+
throw new Error(`Invalid snapshot: expected ${expectedSize} bytes, got ${data.length}`);
|
|
741
|
+
}
|
|
742
|
+
const memory = data.slice(memoryOffset, memoryOffset + memorySize);
|
|
743
|
+
return { memory, stackPointer, runtimePtr, contextPtr, extensions };
|
|
744
|
+
}
|
|
745
|
+
// ---- Internal instantiation helpers ----
|
|
746
|
+
static normalizeOptions(options) {
|
|
747
|
+
if (!options) {
|
|
748
|
+
return { wasm: void 0 };
|
|
749
|
+
}
|
|
750
|
+
if (options instanceof WebAssembly.Module)
|
|
751
|
+
return { wasm: options };
|
|
752
|
+
if (typeof options === "object" && ("wasm" in options || "wasi" in options || "memoryLimit" in options || "maxStackSize" in options || "interruptHandler" in options || "onUnhandledRejection" in options || "moduleLoader" in options || "intrinsics" in options || "extensions" in options || "timezoneOffset" in options)) {
|
|
753
|
+
const opts = options;
|
|
754
|
+
if (opts.maxStackSize !== void 0 && (!Number.isInteger(opts.maxStackSize) || opts.maxStackSize < 0 || opts.maxStackSize > MAX_STACK_SIZE)) {
|
|
755
|
+
throw new RangeError(`maxStackSize must be an integer between 0 and ${MAX_STACK_SIZE}`);
|
|
756
|
+
}
|
|
757
|
+
return opts;
|
|
758
|
+
}
|
|
759
|
+
return { wasm: options };
|
|
760
|
+
}
|
|
761
|
+
static applyLimits(vm, opts) {
|
|
762
|
+
if (opts.memoryLimit !== void 0) {
|
|
763
|
+
vm.exports.qjs_set_memory_limit(opts.memoryLimit);
|
|
764
|
+
}
|
|
765
|
+
if (opts.maxStackSize !== void 0) {
|
|
766
|
+
vm.exports.qjs_set_max_stack_size(opts.maxStackSize);
|
|
767
|
+
}
|
|
768
|
+
if (opts.interruptHandler) {
|
|
769
|
+
vm.interruptHandler = opts.interruptHandler;
|
|
770
|
+
vm.exports.qjs_set_interrupt_handler(1);
|
|
771
|
+
}
|
|
772
|
+
if (opts.onUnhandledRejection) {
|
|
773
|
+
vm.unhandledRejectionHandler = opts.onUnhandledRejection;
|
|
774
|
+
vm.exports.qjs_set_promise_rejection_handler(1);
|
|
775
|
+
}
|
|
776
|
+
if (opts.moduleLoader) {
|
|
777
|
+
vm.moduleLoadHandler = opts.moduleLoader.load;
|
|
778
|
+
vm.moduleNormalizeHandler = opts.moduleLoader.normalize ?? null;
|
|
779
|
+
vm.exports.qjs_set_module_loader(1);
|
|
780
|
+
}
|
|
781
|
+
const tz = opts.timezoneOffset;
|
|
782
|
+
if (typeof tz === "function") {
|
|
783
|
+
vm.timezoneOffsetHandler = (timeSecs) => -tz(timeSecs) * 60;
|
|
784
|
+
} else if (typeof tz === "number") {
|
|
785
|
+
const offsetSecs = -tz * 60;
|
|
786
|
+
vm.timezoneOffsetHandler = () => offsetSecs;
|
|
787
|
+
} else {
|
|
788
|
+
vm.timezoneOffsetHandler = (timeSecs) => {
|
|
789
|
+
return -new Date(timeSecs * 1e3).getTimezoneOffset() * 60;
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
static async resolveModule(wasmInput) {
|
|
794
|
+
if (wasmInput instanceof WebAssembly.Module) {
|
|
795
|
+
return wasmInput;
|
|
796
|
+
}
|
|
797
|
+
if (wasmInput) {
|
|
798
|
+
return WebAssembly.compile(wasmInput);
|
|
799
|
+
}
|
|
800
|
+
throw new TypeError("QuickJS: `wasm` option is required. Provide WASM bytes or a compiled `WebAssembly.Module`. The binary is shipped at `quickjs-wasi/quickjs.wasm` and can be loaded via your environment's preferred mechanism (e.g. `fetch()`, `node:fs/promises`, or a bundler import like Vite's `?url`).");
|
|
801
|
+
}
|
|
802
|
+
static async instantiate(module, vm, wasiOptions) {
|
|
803
|
+
let memory = null;
|
|
804
|
+
const memoryProxy = new Proxy({}, {
|
|
805
|
+
get(_target, prop) {
|
|
806
|
+
return memory[prop];
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
const wasiBuiltins = createWasiShim(() => memory);
|
|
810
|
+
const wasiUserOverrides = wasiOptions ? wasiOptions(memoryProxy) : void 0;
|
|
811
|
+
const wasiShim = { ...wasiBuiltins, ...wasiUserOverrides };
|
|
812
|
+
const hostCall = (namePtr, nameLen, thisPtr, argc, argvPtr) => {
|
|
813
|
+
return vm.handleHostCall(namePtr, nameLen, thisPtr, argc, argvPtr);
|
|
814
|
+
};
|
|
815
|
+
const hostInterrupt = () => {
|
|
816
|
+
return vm.interruptHandler ? vm.interruptHandler() ? 1 : 0 : 0;
|
|
817
|
+
};
|
|
818
|
+
const hostPromiseRejection = (promisePtr, reasonPtr, isHandled) => {
|
|
819
|
+
if (!vm.unhandledRejectionHandler) {
|
|
820
|
+
vm.exports.qjs_free_value(promisePtr);
|
|
821
|
+
vm.exports.qjs_free_value(reasonPtr);
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
const promise = new JSValueHandle(vm, promisePtr);
|
|
825
|
+
const reason = new JSValueHandle(vm, reasonPtr);
|
|
826
|
+
try {
|
|
827
|
+
vm.unhandledRejectionHandler(promise, reason, isHandled !== 0);
|
|
828
|
+
} finally {
|
|
829
|
+
promise.dispose();
|
|
830
|
+
reason.dispose();
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
const throwIntoContext = (err) => {
|
|
834
|
+
const errHandle = vm.newError(err instanceof Error ? err : String(err));
|
|
835
|
+
vm.exports.qjs_throw(errHandle.ptr);
|
|
836
|
+
errHandle.dispose();
|
|
837
|
+
};
|
|
838
|
+
const assertSyncString = (value, callbackName) => {
|
|
839
|
+
if (typeof value === "string")
|
|
840
|
+
return value;
|
|
841
|
+
const got = value !== null && typeof value === "object" && typeof value.then === "function" ? "a Promise" : `type ${typeof value}`;
|
|
842
|
+
throw new TypeError(`moduleLoader.${callbackName} must synchronously return a string (got ${got}). Async module loading is not supported. Pre-fetch module sources instead (see the "ES Modules" section of the quickjs-wasi README).`);
|
|
843
|
+
};
|
|
844
|
+
const hostModuleNormalize = (baseNamePtr, namePtr) => {
|
|
845
|
+
if (!vm.moduleNormalizeHandler) {
|
|
846
|
+
const name = vm.readCString(namePtr);
|
|
847
|
+
return vm.writeString(name).ptr;
|
|
848
|
+
}
|
|
849
|
+
const baseName = vm.readCString(baseNamePtr);
|
|
850
|
+
const specifier = vm.readCString(namePtr);
|
|
851
|
+
try {
|
|
852
|
+
const normalized = assertSyncString(vm.moduleNormalizeHandler(baseName, specifier), "normalize");
|
|
853
|
+
return vm.writeString(normalized).ptr;
|
|
854
|
+
} catch (err) {
|
|
855
|
+
throwIntoContext(err);
|
|
856
|
+
return 0;
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
const hostModuleLoad = (namePtr, outLenPtr) => {
|
|
860
|
+
if (!vm.moduleLoadHandler)
|
|
861
|
+
return 0;
|
|
862
|
+
const name = vm.readCString(namePtr);
|
|
863
|
+
try {
|
|
864
|
+
const source = assertSyncString(vm.moduleLoadHandler(name), "load");
|
|
865
|
+
const { ptr, len } = vm.writeString(source);
|
|
866
|
+
new Uint32Array(vm.exports.memory.buffer, outLenPtr, 1)[0] = len;
|
|
867
|
+
return ptr;
|
|
868
|
+
} catch (err) {
|
|
869
|
+
throwIntoContext(err);
|
|
870
|
+
return 0;
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
const hostGetTimezoneOffset = (hi, lo) => {
|
|
874
|
+
const timeSecs = Number(BigInt(hi) << 32n | BigInt(lo >>> 0));
|
|
875
|
+
return vm.timezoneOffsetHandler ? vm.timezoneOffsetHandler(timeSecs) : 0;
|
|
876
|
+
};
|
|
877
|
+
const instance = await WebAssembly.instantiate(module, {
|
|
878
|
+
env: {
|
|
879
|
+
host_call: hostCall,
|
|
880
|
+
host_interrupt: hostInterrupt,
|
|
881
|
+
host_promise_rejection: hostPromiseRejection,
|
|
882
|
+
host_module_normalize: hostModuleNormalize,
|
|
883
|
+
host_module_load: hostModuleLoad,
|
|
884
|
+
host_get_timezone_offset: hostGetTimezoneOffset
|
|
885
|
+
},
|
|
886
|
+
wasi_snapshot_preview1: wasiShim
|
|
887
|
+
});
|
|
888
|
+
memory = instance.exports.memory;
|
|
889
|
+
return { instance, wasiBuiltins, wasiUserOverrides, memoryProxy };
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Called from WASM when a host function is invoked from QuickJS code.
|
|
893
|
+
*/
|
|
894
|
+
handleHostCall(namePtr, nameLen, thisPtr, argc, argvPtr) {
|
|
895
|
+
const name = this.decoder.decode(new Uint8Array(this.exports.memory.buffer, namePtr, nameLen));
|
|
896
|
+
const callback = this.hostCallbacks.get(name);
|
|
897
|
+
if (!callback) {
|
|
898
|
+
const errHandle = this.newError(`Host callback "${name}" is not registered: it was unregistered, its ephemeral function handle was disposed, or it was never re-registered after a snapshot restore.`);
|
|
899
|
+
this.exports.qjs_throw(errHandle.ptr);
|
|
900
|
+
errHandle.dispose();
|
|
901
|
+
return 0;
|
|
902
|
+
}
|
|
903
|
+
const thisHandle = new JSValueHandle(this, thisPtr, false, true);
|
|
904
|
+
const args = [];
|
|
905
|
+
if (argc > 0 && argvPtr !== 0) {
|
|
906
|
+
const view = new DataView(this.exports.memory.buffer);
|
|
907
|
+
for (let i = 0; i < argc; i++) {
|
|
908
|
+
const argPtr = view.getUint32(argvPtr + i * 4, true);
|
|
909
|
+
args.push(new JSValueHandle(this, argPtr, false, true));
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
try {
|
|
913
|
+
const result = callback.call(thisHandle, ...args);
|
|
914
|
+
return this.exports.qjs_dup_value(result.ptr);
|
|
915
|
+
} catch (err) {
|
|
916
|
+
const errHandle = this.newError(err instanceof Error ? err : String(err));
|
|
917
|
+
this.exports.qjs_throw(errHandle.ptr);
|
|
918
|
+
errHandle.dispose();
|
|
919
|
+
return 0;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
// ---- String helpers ----
|
|
923
|
+
/** Write a JS string into WASM memory, returning the pointer. Caller must free. */
|
|
924
|
+
writeString(str) {
|
|
925
|
+
const encoded = encodeWtf8(str);
|
|
926
|
+
const ptr = this.exports.wasm_malloc(encoded.length + 1);
|
|
927
|
+
if (ptr === 0)
|
|
928
|
+
throw new Error("wasm_malloc failed");
|
|
929
|
+
const mem = new Uint8Array(this.exports.memory.buffer);
|
|
930
|
+
mem.set(encoded, ptr);
|
|
931
|
+
mem[ptr + encoded.length] = 0;
|
|
932
|
+
return { ptr, len: encoded.length };
|
|
933
|
+
}
|
|
934
|
+
/** Read a null-terminated C string from WASM memory */
|
|
935
|
+
readCString(ptr) {
|
|
936
|
+
const mem = new Uint8Array(this.exports.memory.buffer);
|
|
937
|
+
let end = ptr;
|
|
938
|
+
while (mem[end] !== 0)
|
|
939
|
+
end++;
|
|
940
|
+
return this.decoder.decode(mem.slice(ptr, end));
|
|
941
|
+
}
|
|
942
|
+
// ---- Public API ----
|
|
943
|
+
/**
|
|
944
|
+
* Check if a result handle is an exception and throw a JSException if so.
|
|
945
|
+
* Used internally by evalCode and callFunction.
|
|
946
|
+
*/
|
|
947
|
+
throwIfException(result) {
|
|
948
|
+
if (this.exports.qjs_is_exception(result.ptr) !== 0) {
|
|
949
|
+
const exc = this.getException();
|
|
950
|
+
result.dispose();
|
|
951
|
+
this._ownedHandles.add(exc);
|
|
952
|
+
throw new JSException(exc);
|
|
953
|
+
}
|
|
954
|
+
return result;
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Evaluate JavaScript code and return the result as a handle.
|
|
958
|
+
* If the code throws, a `JSException` (which extends `Error`) is thrown
|
|
959
|
+
* on the host side, matching standard JavaScript semantics.
|
|
960
|
+
*
|
|
961
|
+
* @param code - The JavaScript source code to evaluate.
|
|
962
|
+
* @param filename - Optional filename for error stack traces (default `'<eval>'`).
|
|
963
|
+
* @param flags - Optional bitwise OR of `EvalFlags.*` constants.
|
|
964
|
+
* For example, pass `EvalFlags.ASYNC` to allow top-level `await`; the
|
|
965
|
+
* returned handle will be a Promise that resolves to the completion value.
|
|
966
|
+
* With `EvalFlags.TYPE_MODULE` the returned handle is a Promise that
|
|
967
|
+
* resolves to the module's namespace object (its exports).
|
|
968
|
+
*/
|
|
969
|
+
evalCode(code, filename = "<eval>", flags = 0) {
|
|
970
|
+
this.assertNotDisposed();
|
|
971
|
+
const codeStr = this.writeString(code);
|
|
972
|
+
const fnStr = this.writeString(filename);
|
|
973
|
+
const resultPtr = this.exports.qjs_eval(codeStr.ptr, codeStr.len, fnStr.ptr, flags);
|
|
974
|
+
this.exports.wasm_free(codeStr.ptr);
|
|
975
|
+
this.exports.wasm_free(fnStr.ptr);
|
|
976
|
+
return this.throwIfException(new JSValueHandle(this, resultPtr));
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* Compile JavaScript source code to bytecode without executing it.
|
|
980
|
+
* The returned `Uint8Array` can be stored, transferred, or later executed
|
|
981
|
+
* with `evalBytecode()`.
|
|
982
|
+
*
|
|
983
|
+
* @param code - The JavaScript source code to compile.
|
|
984
|
+
* @param filename - Optional filename for error stack traces (default `'<compile>'`).
|
|
985
|
+
* @param evalFlags - Optional bitwise OR of `EvalFlags.*` constants.
|
|
986
|
+
* Use `EvalFlags.TYPE_MODULE` to compile as a module.
|
|
987
|
+
* @param compileFlags - Optional bitwise OR of `CompileFlags.*` constants.
|
|
988
|
+
* Use `CompileFlags.STRIP_SOURCE` and/or `CompileFlags.STRIP_DEBUG` to
|
|
989
|
+
* reduce bytecode size.
|
|
990
|
+
*/
|
|
991
|
+
compile(code, filename = "<compile>", evalFlags = 0, compileFlags = 0) {
|
|
992
|
+
this.assertNotDisposed();
|
|
993
|
+
const codeStr = this.writeString(code);
|
|
994
|
+
const fnStr = this.writeString(filename);
|
|
995
|
+
const outLenPtr = this.exports.wasm_malloc(4);
|
|
996
|
+
const bufPtr = this.exports.qjs_compile(codeStr.ptr, codeStr.len, fnStr.ptr, evalFlags, compileFlags, outLenPtr);
|
|
997
|
+
this.exports.wasm_free(codeStr.ptr);
|
|
998
|
+
this.exports.wasm_free(fnStr.ptr);
|
|
999
|
+
if (bufPtr === 0) {
|
|
1000
|
+
this.exports.wasm_free(outLenPtr);
|
|
1001
|
+
const exc = this.getException();
|
|
1002
|
+
throw new Error(`Compilation error: ${exc.toString()}`);
|
|
1003
|
+
}
|
|
1004
|
+
const outLen = new Uint32Array(this.exports.memory.buffer, outLenPtr, 1)[0];
|
|
1005
|
+
this.exports.wasm_free(outLenPtr);
|
|
1006
|
+
const bytecode = new Uint8Array(this.exports.memory.buffer, bufPtr, outLen).slice();
|
|
1007
|
+
this.exports.wasm_free(bufPtr);
|
|
1008
|
+
return bytecode;
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Execute previously compiled bytecode (from `compile()`).
|
|
1012
|
+
* Returns the evaluation result as a handle.
|
|
1013
|
+
*
|
|
1014
|
+
* For module bytecode (compiled with `EvalFlags.TYPE_MODULE`), the
|
|
1015
|
+
* returned handle is a Promise that resolves to the module's namespace
|
|
1016
|
+
* object (its exports).
|
|
1017
|
+
*
|
|
1018
|
+
* @param bytecode - The bytecode `Uint8Array` from `compile()`.
|
|
1019
|
+
*/
|
|
1020
|
+
evalBytecode(bytecode) {
|
|
1021
|
+
this.assertNotDisposed();
|
|
1022
|
+
const bufPtr = this.exports.wasm_malloc(bytecode.byteLength);
|
|
1023
|
+
new Uint8Array(this.exports.memory.buffer, bufPtr, bytecode.byteLength).set(bytecode);
|
|
1024
|
+
const resultPtr = this.exports.qjs_eval_bytecode(bufPtr, bytecode.byteLength);
|
|
1025
|
+
this.exports.wasm_free(bufPtr);
|
|
1026
|
+
return this.throwIfException(new JSValueHandle(this, resultPtr));
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Execute all pending microtask jobs (promise reactions, etc.)
|
|
1030
|
+
* Returns the number of jobs executed.
|
|
1031
|
+
*/
|
|
1032
|
+
executePendingJobs() {
|
|
1033
|
+
this.assertNotDisposed();
|
|
1034
|
+
let count = 0;
|
|
1035
|
+
while (this.exports.qjs_is_job_pending()) {
|
|
1036
|
+
const result = this.exports.qjs_execute_pending_job();
|
|
1037
|
+
if (result < 0) {
|
|
1038
|
+
const exc = this.getException();
|
|
1039
|
+
throw new Error(`Job execution error: ${exc.toString()}`);
|
|
1040
|
+
}
|
|
1041
|
+
count++;
|
|
1042
|
+
}
|
|
1043
|
+
return count;
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Explicitly trigger garbage collection. QuickJS runs GC automatically,
|
|
1047
|
+
* but this can be useful to reclaim memory at a known point or before
|
|
1048
|
+
* taking a snapshot.
|
|
1049
|
+
*/
|
|
1050
|
+
runGC() {
|
|
1051
|
+
this.assertNotDisposed();
|
|
1052
|
+
this.exports.qjs_run_gc();
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* The GC threshold in bytes. When allocated memory exceeds this value,
|
|
1056
|
+
* garbage collection is triggered automatically. Set to 0 to disable
|
|
1057
|
+
* automatic GC.
|
|
1058
|
+
*/
|
|
1059
|
+
get gcThreshold() {
|
|
1060
|
+
this.assertNotDisposed();
|
|
1061
|
+
return this.exports.qjs_get_gc_threshold();
|
|
1062
|
+
}
|
|
1063
|
+
set gcThreshold(threshold) {
|
|
1064
|
+
this.assertNotDisposed();
|
|
1065
|
+
this.exports.qjs_set_gc_threshold(threshold);
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Get detailed memory usage statistics from the QuickJS runtime.
|
|
1069
|
+
* Returns counts and sizes for atoms, strings, objects, functions, etc.
|
|
1070
|
+
*/
|
|
1071
|
+
getMemoryUsage() {
|
|
1072
|
+
this.assertNotDisposed();
|
|
1073
|
+
const bufPtr = this.exports.wasm_malloc(26 * 8);
|
|
1074
|
+
this.exports.qjs_compute_memory_usage(bufPtr);
|
|
1075
|
+
const view = new BigInt64Array(this.exports.memory.buffer, bufPtr, 26);
|
|
1076
|
+
const result = {
|
|
1077
|
+
mallocSize: Number(view[0]),
|
|
1078
|
+
mallocLimit: Number(view[1]),
|
|
1079
|
+
memoryUsedSize: Number(view[2]),
|
|
1080
|
+
mallocCount: Number(view[3]),
|
|
1081
|
+
memoryUsedCount: Number(view[4]),
|
|
1082
|
+
atomCount: Number(view[5]),
|
|
1083
|
+
atomSize: Number(view[6]),
|
|
1084
|
+
strCount: Number(view[7]),
|
|
1085
|
+
strSize: Number(view[8]),
|
|
1086
|
+
objCount: Number(view[9]),
|
|
1087
|
+
objSize: Number(view[10]),
|
|
1088
|
+
propCount: Number(view[11]),
|
|
1089
|
+
propSize: Number(view[12]),
|
|
1090
|
+
shapeCount: Number(view[13]),
|
|
1091
|
+
shapeSize: Number(view[14]),
|
|
1092
|
+
jsFuncCount: Number(view[15]),
|
|
1093
|
+
jsFuncSize: Number(view[16]),
|
|
1094
|
+
jsFuncCodeSize: Number(view[17]),
|
|
1095
|
+
jsFuncPc2lineCount: Number(view[18]),
|
|
1096
|
+
jsFuncPc2lineSize: Number(view[19]),
|
|
1097
|
+
cFuncCount: Number(view[20]),
|
|
1098
|
+
arrayCount: Number(view[21]),
|
|
1099
|
+
fastArrayCount: Number(view[22]),
|
|
1100
|
+
fastArrayElements: Number(view[23]),
|
|
1101
|
+
binaryObjectCount: Number(view[24]),
|
|
1102
|
+
binaryObjectSize: Number(view[25])
|
|
1103
|
+
};
|
|
1104
|
+
this.exports.wasm_free(bufPtr);
|
|
1105
|
+
return result;
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Get the global object. Prefer the cached `vm.global` property.
|
|
1109
|
+
*/
|
|
1110
|
+
getGlobal() {
|
|
1111
|
+
this.assertNotDisposed();
|
|
1112
|
+
return new JSValueHandle(this, this.exports.qjs_get_global());
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Create a new QuickJS string value.
|
|
1116
|
+
*/
|
|
1117
|
+
newString(str) {
|
|
1118
|
+
this.assertNotDisposed();
|
|
1119
|
+
const { ptr, len } = this.writeString(str);
|
|
1120
|
+
const resultPtr = this.exports.qjs_new_string(ptr, len);
|
|
1121
|
+
this.exports.wasm_free(ptr);
|
|
1122
|
+
return new JSValueHandle(this, resultPtr);
|
|
1123
|
+
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Create a new QuickJS number value.
|
|
1126
|
+
*/
|
|
1127
|
+
newNumber(num) {
|
|
1128
|
+
this.assertNotDisposed();
|
|
1129
|
+
return new JSValueHandle(this, this.exports.qjs_new_number(num));
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Create a new QuickJS BigInt value.
|
|
1133
|
+
*/
|
|
1134
|
+
newBigInt(val) {
|
|
1135
|
+
this.assertNotDisposed();
|
|
1136
|
+
const lo = Number(val & 0xffffffffn);
|
|
1137
|
+
const hi = Number(val >> 32n & 0xffffffffn);
|
|
1138
|
+
return new JSValueHandle(this, this.exports.qjs_new_big_int64(lo, hi));
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Create a new QuickJS object value.
|
|
1142
|
+
*/
|
|
1143
|
+
newObject() {
|
|
1144
|
+
this.assertNotDisposed();
|
|
1145
|
+
return new JSValueHandle(this, this.exports.qjs_new_object());
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Create a new QuickJS array value.
|
|
1149
|
+
*/
|
|
1150
|
+
newArray() {
|
|
1151
|
+
this.assertNotDisposed();
|
|
1152
|
+
return new JSValueHandle(this, this.exports.qjs_new_array());
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Create a global symbol (`Symbol.for(description)`).
|
|
1156
|
+
* Global symbols with the same description are always the same symbol,
|
|
1157
|
+
* even across snapshot/restore.
|
|
1158
|
+
*/
|
|
1159
|
+
newSymbolFor(description) {
|
|
1160
|
+
this.assertNotDisposed();
|
|
1161
|
+
const { ptr, len } = this.writeString(description);
|
|
1162
|
+
const result = new JSValueHandle(this, this.exports.qjs_new_symbol(ptr, len, 1));
|
|
1163
|
+
this.exports.wasm_free(ptr);
|
|
1164
|
+
return result;
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Create a new QuickJS ArrayBuffer by copying data from a host buffer.
|
|
1168
|
+
*/
|
|
1169
|
+
newArrayBuffer(data) {
|
|
1170
|
+
this.assertNotDisposed();
|
|
1171
|
+
const bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
1172
|
+
const ptr = this.exports.wasm_malloc(bytes.length);
|
|
1173
|
+
if (ptr === 0)
|
|
1174
|
+
throw new Error("wasm_malloc failed");
|
|
1175
|
+
new Uint8Array(this.exports.memory.buffer).set(bytes, ptr);
|
|
1176
|
+
const result = new JSValueHandle(this, this.exports.qjs_new_array_buffer(ptr, bytes.length));
|
|
1177
|
+
this.exports.wasm_free(ptr);
|
|
1178
|
+
return result;
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Create a new QuickJS Uint8Array by copying data from a host buffer.
|
|
1182
|
+
*/
|
|
1183
|
+
newUint8Array(data) {
|
|
1184
|
+
this.assertNotDisposed();
|
|
1185
|
+
const ptr = this.exports.wasm_malloc(data.length);
|
|
1186
|
+
if (ptr === 0)
|
|
1187
|
+
throw new Error("wasm_malloc failed");
|
|
1188
|
+
new Uint8Array(this.exports.memory.buffer).set(data, ptr);
|
|
1189
|
+
const result = new JSValueHandle(this, this.exports.qjs_new_uint8_array(ptr, data.length));
|
|
1190
|
+
this.exports.wasm_free(ptr);
|
|
1191
|
+
return result;
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* Get undefined. Prefer the cached `vm.undefined` property.
|
|
1195
|
+
*/
|
|
1196
|
+
getUndefined() {
|
|
1197
|
+
this.assertNotDisposed();
|
|
1198
|
+
return new JSValueHandle(this, this.exports.qjs_get_undefined());
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Get null. Prefer the cached `vm.null` property.
|
|
1202
|
+
*/
|
|
1203
|
+
getNull() {
|
|
1204
|
+
this.assertNotDisposed();
|
|
1205
|
+
return new JSValueHandle(this, this.exports.qjs_get_null());
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Get true. Prefer the cached `vm.true` property.
|
|
1209
|
+
*/
|
|
1210
|
+
getTrue() {
|
|
1211
|
+
this.assertNotDisposed();
|
|
1212
|
+
return new JSValueHandle(this, this.exports.qjs_get_true());
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Get false. Prefer the cached `vm.false` property.
|
|
1216
|
+
*/
|
|
1217
|
+
getFalse() {
|
|
1218
|
+
this.assertNotDisposed();
|
|
1219
|
+
return new JSValueHandle(this, this.exports.qjs_get_false());
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Create a new QuickJS function backed by a host callback.
|
|
1223
|
+
*
|
|
1224
|
+
* When the function is called inside QuickJS, the host callback is invoked
|
|
1225
|
+
* with the `this` value and arguments as JSValueHandles.
|
|
1226
|
+
*/
|
|
1227
|
+
newFunction(name, fn) {
|
|
1228
|
+
this.assertNotDisposed();
|
|
1229
|
+
if (this.hostCallbacks.has(name)) {
|
|
1230
|
+
throw new Error(`Host callback with name "${name}" is already registered`);
|
|
1231
|
+
}
|
|
1232
|
+
this.hostCallbacks.set(name, fn);
|
|
1233
|
+
const { ptr: namePtr, len: nameLen } = this.writeString(name);
|
|
1234
|
+
const resultPtr = this.exports.qjs_new_host_function(namePtr, nameLen, 0);
|
|
1235
|
+
this.exports.wasm_free(namePtr);
|
|
1236
|
+
return new JSValueHandle(this, resultPtr);
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Run `fn` with a handle scope: every handle created during the call is
|
|
1240
|
+
* disposed when it returns, except those passed to `scope.escape()`.
|
|
1241
|
+
*
|
|
1242
|
+
* This is the bulk alternative to disposing handles individually, for code
|
|
1243
|
+
* that creates many intermediates, such as walking a large value:
|
|
1244
|
+
*
|
|
1245
|
+
* ```ts
|
|
1246
|
+
* const name = vm.withScope((scope) => {
|
|
1247
|
+
* const user = root.getProp('user'); // freed automatically
|
|
1248
|
+
* const profile = user.getProp('profile'); // freed automatically
|
|
1249
|
+
* return scope.escape(profile.getProp('name'));
|
|
1250
|
+
* });
|
|
1251
|
+
* ```
|
|
1252
|
+
*
|
|
1253
|
+
* Scopes nest: `escape()` transfers the handle to the enclosing scope when
|
|
1254
|
+
* there is one, so it is still cleaned up at the outer boundary.
|
|
1255
|
+
*
|
|
1256
|
+
* `fn` must be synchronous. Handles created after an `await` are outside
|
|
1257
|
+
* the scope, because it closes as soon as `fn` returns.
|
|
1258
|
+
*
|
|
1259
|
+
* Host callbacks are safe to trigger inside a scope: the `this`/argument
|
|
1260
|
+
* handles the trampoline passes to a callback wrap C-owned pointers and
|
|
1261
|
+
* are exempt from scope tracking (see `handleHostCall`), so the scope
|
|
1262
|
+
* frees only handles the host actually owns. Handles a callback CREATES
|
|
1263
|
+
* (including `dup()`s of its arguments) are tracked normally.
|
|
1264
|
+
*/
|
|
1265
|
+
withScope(fn) {
|
|
1266
|
+
this.assertNotDisposed();
|
|
1267
|
+
const enclosing = this._activeScope;
|
|
1268
|
+
const tracked = /* @__PURE__ */ new Set();
|
|
1269
|
+
this._activeScope = tracked;
|
|
1270
|
+
const scope = {
|
|
1271
|
+
escape: (handle) => {
|
|
1272
|
+
tracked.delete(handle);
|
|
1273
|
+
enclosing?.add(handle);
|
|
1274
|
+
return handle;
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
try {
|
|
1278
|
+
return fn(scope);
|
|
1279
|
+
} finally {
|
|
1280
|
+
this._activeScope = enclosing;
|
|
1281
|
+
for (const handle of tracked)
|
|
1282
|
+
handle.dispose();
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Export a handle as a snapshot-portable token.
|
|
1287
|
+
*
|
|
1288
|
+
* A handle's heap box lives in the VM's linear memory, so a
|
|
1289
|
+
* `snapshot()` taken while the handle is alive carries it, and a VM
|
|
1290
|
+
* restored from that snapshot has the identical box at the identical
|
|
1291
|
+
* offset. `importHandle(token)` on the restored VM (or on this VM)
|
|
1292
|
+
* re-materializes an owned handle for the same guest value without
|
|
1293
|
+
* evaluating any guest code.
|
|
1294
|
+
*
|
|
1295
|
+
* Contract:
|
|
1296
|
+
* - the handle must stay undisposed until after `snapshot()`; its
|
|
1297
|
+
* box (and the reference it holds) must be part of the memory image;
|
|
1298
|
+
* - the token is only meaningful to THIS VM and VMs restored from a
|
|
1299
|
+
* snapshot of it taken while the handle was alive;
|
|
1300
|
+
* - `importHandle` duplicates the underlying value (fresh reference,
|
|
1301
|
+
* fresh box), so it can be called any number of times and each
|
|
1302
|
+
* returned handle is independently owned and disposable. The
|
|
1303
|
+
* exported box's own reference is intentionally never released on
|
|
1304
|
+
* restored VMs (one retained reference per VM image, reclaimed
|
|
1305
|
+
* with the VM).
|
|
1306
|
+
*
|
|
1307
|
+
* The intended use is boot-time capture: snapshot a VM after capturing
|
|
1308
|
+
* references to pristine intrinsics but BEFORE evaluating untrusted or
|
|
1309
|
+
* user code, then restore per task and import the captured handles,
|
|
1310
|
+
* guaranteeing the references predate anything user code patched,
|
|
1311
|
+
* without re-running capture code in the restored VM (where user-
|
|
1312
|
+
* patched globals could observe it). See vercel/workflow's host-side
|
|
1313
|
+
* serde for a worked example.
|
|
1314
|
+
*/
|
|
1315
|
+
exportHandle(handle) {
|
|
1316
|
+
this.assertNotDisposed();
|
|
1317
|
+
if (handle.vm !== this) {
|
|
1318
|
+
throw new Error("exportHandle: handle belongs to a different VM");
|
|
1319
|
+
}
|
|
1320
|
+
if (handle.disposed) {
|
|
1321
|
+
throw new Error("exportHandle: handle is disposed");
|
|
1322
|
+
}
|
|
1323
|
+
if (handle._isBorrowed) {
|
|
1324
|
+
throw new Error("exportHandle: cannot export a borrowed handle (host-callback this/argument); its box is freed when the callback returns. dup() it and export the duplicate.");
|
|
1325
|
+
}
|
|
1326
|
+
return handle.ptr;
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* Re-materialize a handle from a token produced by `exportHandle`,
|
|
1330
|
+
* on this VM, or on a VM restored from a snapshot taken while the
|
|
1331
|
+
* exported handle was alive. Returns a NEW owned handle (the
|
|
1332
|
+
* underlying value's refcount is incremented); dispose it like any
|
|
1333
|
+
* other handle. See `exportHandle` for the full contract.
|
|
1334
|
+
*/
|
|
1335
|
+
importHandle(token) {
|
|
1336
|
+
this.assertNotDisposed();
|
|
1337
|
+
if (!Number.isInteger(token) || token <= 0 || token >= this.exports.memory.buffer.byteLength) {
|
|
1338
|
+
throw new Error(`importHandle: invalid token ${token}`);
|
|
1339
|
+
}
|
|
1340
|
+
return new JSValueHandle(this, this.exports.qjs_dup_value(token));
|
|
1341
|
+
}
|
|
1342
|
+
/**
|
|
1343
|
+
* Create a QuickJS function backed by a host callback whose registration is
|
|
1344
|
+
* tied to the returned handle: disposing the handle unregisters the
|
|
1345
|
+
* callback.
|
|
1346
|
+
*
|
|
1347
|
+
* Use this for short-lived callbacks (e.g. a visitor passed to
|
|
1348
|
+
* `Map.prototype.forEach`) where the name is an implementation detail.
|
|
1349
|
+
* `newFunction()` keeps its callback registered for the lifetime of the VM
|
|
1350
|
+
* (by design, so that names can be re-registered after a snapshot is
|
|
1351
|
+
* restored), which makes it unsuitable for callbacks created in a loop.
|
|
1352
|
+
*
|
|
1353
|
+
* The guest must not retain the function past disposal: calling it after
|
|
1354
|
+
* the handle is disposed throws, because the callback is gone. Ephemeral
|
|
1355
|
+
* functions do not survive snapshot/restore.
|
|
1356
|
+
*/
|
|
1357
|
+
newEphemeralFunction(fn) {
|
|
1358
|
+
this.assertNotDisposed();
|
|
1359
|
+
const name = `__ephemeral:${this.nextInternalId++}`;
|
|
1360
|
+
this.hostCallbacks.set(name, fn);
|
|
1361
|
+
const { ptr: namePtr, len: nameLen } = this.writeString(name);
|
|
1362
|
+
const resultPtr = this.exports.qjs_new_host_function(namePtr, nameLen, 0);
|
|
1363
|
+
this.exports.wasm_free(namePtr);
|
|
1364
|
+
const handle = new JSValueHandle(this, resultPtr);
|
|
1365
|
+
handle._onDispose = () => {
|
|
1366
|
+
this.hostCallbacks.delete(name);
|
|
1367
|
+
};
|
|
1368
|
+
return handle;
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Remove a host callback registered with `newFunction()` or
|
|
1372
|
+
* `registerHostCallback()`. Returns true if a callback was removed.
|
|
1373
|
+
*
|
|
1374
|
+
* Any QuickJS function still referencing the name will throw when called,
|
|
1375
|
+
* so only unregister once the guest can no longer reach it.
|
|
1376
|
+
*/
|
|
1377
|
+
unregisterHostCallback(name) {
|
|
1378
|
+
return this.hostCallbacks.delete(name);
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* Create an internal host function that bypasses the duplicate-name check.
|
|
1382
|
+
* Used for ephemeral callbacks (promise settle handlers, resolvePromise, etc.)
|
|
1383
|
+
* that are not intended to survive snapshot/restore.
|
|
1384
|
+
*/
|
|
1385
|
+
newInternalFunction(name, fn) {
|
|
1386
|
+
this.hostCallbacks.set(name, fn);
|
|
1387
|
+
const { ptr: namePtr, len: nameLen } = this.writeString(name);
|
|
1388
|
+
const resultPtr = this.exports.qjs_new_host_function(namePtr, nameLen, 0);
|
|
1389
|
+
this.exports.wasm_free(namePtr);
|
|
1390
|
+
return new JSValueHandle(this, resultPtr);
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* Create a new promise.
|
|
1394
|
+
*
|
|
1395
|
+
* Returns a Deferred with:
|
|
1396
|
+
* - `handle` - the QuickJS promise object
|
|
1397
|
+
* - `settled` - a host Promise that resolves when the QuickJS promise settles
|
|
1398
|
+
* - `resolve(value)` - resolve the promise with a QuickJS value
|
|
1399
|
+
* - `reject(value)` - reject the promise with a QuickJS value
|
|
1400
|
+
*/
|
|
1401
|
+
newPromise() {
|
|
1402
|
+
this.assertNotDisposed();
|
|
1403
|
+
const resolveOutPtr = this.exports.wasm_malloc(4);
|
|
1404
|
+
const rejectOutPtr = this.exports.wasm_malloc(4);
|
|
1405
|
+
const promisePtr = this.exports.qjs_new_promise(resolveOutPtr, rejectOutPtr);
|
|
1406
|
+
const view = new DataView(this.exports.memory.buffer);
|
|
1407
|
+
const resolvePtr = view.getUint32(resolveOutPtr, true);
|
|
1408
|
+
const rejectPtr = view.getUint32(rejectOutPtr, true);
|
|
1409
|
+
this.exports.wasm_free(resolveOutPtr);
|
|
1410
|
+
this.exports.wasm_free(rejectOutPtr);
|
|
1411
|
+
const promiseHandle = new JSValueHandle(this, promisePtr);
|
|
1412
|
+
const resolveHandle = new JSValueHandle(this, resolvePtr);
|
|
1413
|
+
const rejectHandle = new JSValueHandle(this, rejectPtr);
|
|
1414
|
+
const vm = this;
|
|
1415
|
+
vm._ownedHandles.add(resolveHandle);
|
|
1416
|
+
vm._ownedHandles.add(rejectHandle);
|
|
1417
|
+
let _settled = null;
|
|
1418
|
+
return {
|
|
1419
|
+
handle: promiseHandle,
|
|
1420
|
+
get settled() {
|
|
1421
|
+
if (!_settled) {
|
|
1422
|
+
let settledResolve;
|
|
1423
|
+
_settled = new Promise((res) => {
|
|
1424
|
+
settledResolve = res;
|
|
1425
|
+
});
|
|
1426
|
+
const settleName = `__settle:${vm.nextInternalId++}`;
|
|
1427
|
+
const onSettleFn = vm.newInternalFunction(settleName, () => {
|
|
1428
|
+
settledResolve();
|
|
1429
|
+
vm.hostCallbacks.delete(settleName);
|
|
1430
|
+
return vm.undefined;
|
|
1431
|
+
});
|
|
1432
|
+
vm.promiseThenRaw(promiseHandle, onSettleFn, onSettleFn).dispose();
|
|
1433
|
+
onSettleFn.dispose();
|
|
1434
|
+
}
|
|
1435
|
+
return _settled;
|
|
1436
|
+
},
|
|
1437
|
+
resolve(value) {
|
|
1438
|
+
vm.callFunctionRaw(resolveHandle, vm.undefined, value).dispose();
|
|
1439
|
+
vm._ownedHandles.delete(resolveHandle);
|
|
1440
|
+
resolveHandle.dispose();
|
|
1441
|
+
},
|
|
1442
|
+
reject(value) {
|
|
1443
|
+
vm.callFunctionRaw(rejectHandle, vm.undefined, value).dispose();
|
|
1444
|
+
vm._ownedHandles.delete(rejectHandle);
|
|
1445
|
+
rejectHandle.dispose();
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
/**
|
|
1450
|
+
* Resolve a promise handle. Returns a host-side Promise that resolves
|
|
1451
|
+
* with the settled value/error of the QuickJS promise.
|
|
1452
|
+
*
|
|
1453
|
+
* If the handle is not a promise, it is treated as an already-fulfilled value.
|
|
1454
|
+
*
|
|
1455
|
+
* The returned host Promise resolves to `{ value: JSValueHandle }` on
|
|
1456
|
+
* fulfillment or `{ error: JSValueHandle }` on rejection.
|
|
1457
|
+
*/
|
|
1458
|
+
resolvePromise(promiseHandle) {
|
|
1459
|
+
this.assertNotDisposed();
|
|
1460
|
+
if (!this.exports.qjs_is_promise(promiseHandle.ptr)) {
|
|
1461
|
+
return Promise.resolve({ value: promiseHandle.dup() });
|
|
1462
|
+
}
|
|
1463
|
+
const state = this.exports.qjs_promise_state(promiseHandle.ptr);
|
|
1464
|
+
if (state === 1) {
|
|
1465
|
+
return Promise.resolve({ value: new JSValueHandle(this, this.exports.qjs_promise_result(promiseHandle.ptr)) });
|
|
1466
|
+
} else if (state === 2) {
|
|
1467
|
+
return Promise.resolve({ error: new JSValueHandle(this, this.exports.qjs_promise_result(promiseHandle.ptr)) });
|
|
1468
|
+
}
|
|
1469
|
+
return new Promise((hostResolve) => {
|
|
1470
|
+
const id = this.nextInternalId++;
|
|
1471
|
+
const fulfilledName = `__onFulfilled:${id}`;
|
|
1472
|
+
const rejectedName = `__onRejected:${id}`;
|
|
1473
|
+
const onFulfilled = this.newInternalFunction(fulfilledName, (...args) => {
|
|
1474
|
+
const val = args[0]?.dup() ?? this.undefined;
|
|
1475
|
+
this.hostCallbacks.delete(fulfilledName);
|
|
1476
|
+
this.hostCallbacks.delete(rejectedName);
|
|
1477
|
+
hostResolve({ value: val });
|
|
1478
|
+
return this.undefined;
|
|
1479
|
+
});
|
|
1480
|
+
const onRejected = this.newInternalFunction(rejectedName, (...args) => {
|
|
1481
|
+
const val = args[0]?.dup() ?? this.undefined;
|
|
1482
|
+
this.hostCallbacks.delete(fulfilledName);
|
|
1483
|
+
this.hostCallbacks.delete(rejectedName);
|
|
1484
|
+
hostResolve({ error: val });
|
|
1485
|
+
return this.undefined;
|
|
1486
|
+
});
|
|
1487
|
+
this.promiseThenRaw(promiseHandle, onFulfilled, onRejected).dispose();
|
|
1488
|
+
onFulfilled.dispose();
|
|
1489
|
+
onRejected.dispose();
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Subscribe to a promise without executing guest code, via quickjs-ng's
|
|
1494
|
+
* JS_PromiseThen: no Promise.prototype.then lookup, no Symbol.species.
|
|
1495
|
+
* Returns the chained promise. Handler handles are borrowed (caller
|
|
1496
|
+
* still owns and disposes them).
|
|
1497
|
+
* @internal
|
|
1498
|
+
*/
|
|
1499
|
+
promiseThenRaw(promise, onFulfilled, onRejected) {
|
|
1500
|
+
return new JSValueHandle(this, this.exports.qjs_promise_then(promise.ptr, onFulfilled.ptr, onRejected.ptr));
|
|
1501
|
+
}
|
|
1502
|
+
/**
|
|
1503
|
+
* Mark a promise as handled: an eventual (or already-recorded) rejection
|
|
1504
|
+
* will not be reported to `onUnhandledRejection`. Useful when the host
|
|
1505
|
+
* observes a rejection through other means (e.g. `resolvePromise()`) and
|
|
1506
|
+
* wants to suppress the unhandled-rejection callback for it.
|
|
1507
|
+
*
|
|
1508
|
+
* No-op if the handle is not a promise.
|
|
1509
|
+
*/
|
|
1510
|
+
markPromiseHandled(promise) {
|
|
1511
|
+
this.assertNotDisposed();
|
|
1512
|
+
this.exports.qjs_promise_mark_as_handled(promise.ptr);
|
|
1513
|
+
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Call a QuickJS function. If the function throws, a `JSException`
|
|
1516
|
+
* is thrown on the host side.
|
|
1517
|
+
*/
|
|
1518
|
+
callFunction(func, thisVal, ...args) {
|
|
1519
|
+
return this.throwIfException(this.callFunctionRaw(func, thisVal, ...args));
|
|
1520
|
+
}
|
|
1521
|
+
/**
|
|
1522
|
+
* Invoke a QuickJS constructor with `new`, i.e. `new ctor(...args)`.
|
|
1523
|
+
* If the constructor throws (including when `ctor` is not a constructor),
|
|
1524
|
+
* a `JSException` is thrown on the host side.
|
|
1525
|
+
*
|
|
1526
|
+
* This is the counterpart to `callFunction` for building values inside
|
|
1527
|
+
* the VM from the host, e.g. `new Date(iso)` on a constructor captured
|
|
1528
|
+
* before any user code ran.
|
|
1529
|
+
*/
|
|
1530
|
+
construct(ctor, ...args) {
|
|
1531
|
+
this.assertNotDisposed();
|
|
1532
|
+
const argc = args.length;
|
|
1533
|
+
let argvPtr = 0;
|
|
1534
|
+
if (argc > 0) {
|
|
1535
|
+
argvPtr = this.exports.wasm_malloc(argc * 4);
|
|
1536
|
+
const view = new DataView(this.exports.memory.buffer);
|
|
1537
|
+
for (let i = 0; i < argc; i++) {
|
|
1538
|
+
view.setUint32(argvPtr + i * 4, args[i].ptr, true);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
const resultPtr = this.exports.qjs_call_constructor(ctor.ptr, argc, argvPtr);
|
|
1542
|
+
if (argvPtr)
|
|
1543
|
+
this.exports.wasm_free(argvPtr);
|
|
1544
|
+
return this.throwIfException(new JSValueHandle(this, resultPtr));
|
|
1545
|
+
}
|
|
1546
|
+
/**
|
|
1547
|
+
* Internal: call a QuickJS function without throwing on exception.
|
|
1548
|
+
* Used by promise plumbing where exceptions are handled differently.
|
|
1549
|
+
*/
|
|
1550
|
+
callFunctionRaw(func, thisVal, ...args) {
|
|
1551
|
+
this.assertNotDisposed();
|
|
1552
|
+
const argc = args.length;
|
|
1553
|
+
let argvPtr = 0;
|
|
1554
|
+
if (argc > 0) {
|
|
1555
|
+
argvPtr = this.exports.wasm_malloc(argc * 4);
|
|
1556
|
+
const view = new DataView(this.exports.memory.buffer);
|
|
1557
|
+
for (let i = 0; i < argc; i++) {
|
|
1558
|
+
view.setUint32(argvPtr + i * 4, args[i].ptr, true);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
const resultPtr = this.exports.qjs_call(func.ptr, thisVal.ptr, argc, argvPtr);
|
|
1562
|
+
if (argvPtr)
|
|
1563
|
+
this.exports.wasm_free(argvPtr);
|
|
1564
|
+
return new JSValueHandle(this, resultPtr);
|
|
1565
|
+
}
|
|
1566
|
+
/**
|
|
1567
|
+
* Set a property on an object. Accepts string or JSValueHandle as key.
|
|
1568
|
+
* JSValueHandle keys support symbols (including `Symbol.for()`).
|
|
1569
|
+
*/
|
|
1570
|
+
setProp(obj, key, value) {
|
|
1571
|
+
this.assertNotDisposed();
|
|
1572
|
+
if (typeof key === "string") {
|
|
1573
|
+
const { ptr: namePtr } = this.writeString(key);
|
|
1574
|
+
this.exports.qjs_set_prop_string(obj.ptr, namePtr, value.ptr);
|
|
1575
|
+
this.exports.wasm_free(namePtr);
|
|
1576
|
+
} else {
|
|
1577
|
+
this.exports.qjs_set_prop_value(obj.ptr, key.ptr, value.ptr);
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
/**
|
|
1581
|
+
* Define a property on an object with explicit property descriptor flags.
|
|
1582
|
+
* Unlike `setProp`, this allows controlling `writable`, `enumerable`, and
|
|
1583
|
+
* `configurable` attributes, matching `Object.defineProperty()` semantics.
|
|
1584
|
+
* Accepts string or JSValueHandle as key (JSValueHandle keys support symbols).
|
|
1585
|
+
*
|
|
1586
|
+
* All flags default to `false` when not specified.
|
|
1587
|
+
*/
|
|
1588
|
+
defineProp(obj, key, value, descriptor) {
|
|
1589
|
+
this.assertNotDisposed();
|
|
1590
|
+
let flags = 0;
|
|
1591
|
+
if (descriptor?.configurable)
|
|
1592
|
+
flags |= 1;
|
|
1593
|
+
if (descriptor?.writable)
|
|
1594
|
+
flags |= 2;
|
|
1595
|
+
if (descriptor?.enumerable)
|
|
1596
|
+
flags |= 4;
|
|
1597
|
+
if (typeof key === "string") {
|
|
1598
|
+
const { ptr: namePtr } = this.writeString(key);
|
|
1599
|
+
this.exports.qjs_define_prop_string(obj.ptr, namePtr, value.ptr, flags);
|
|
1600
|
+
this.exports.wasm_free(namePtr);
|
|
1601
|
+
} else {
|
|
1602
|
+
this.exports.qjs_define_prop_value(obj.ptr, key.ptr, value.ptr, flags);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
/**
|
|
1606
|
+
* Get a property from an object using a JSValueHandle key.
|
|
1607
|
+
* Supports symbol keys (including `Symbol.for()`).
|
|
1608
|
+
*/
|
|
1609
|
+
getProp(obj, key) {
|
|
1610
|
+
this.assertNotDisposed();
|
|
1611
|
+
return new JSValueHandle(this, this.exports.qjs_get_prop_value(obj.ptr, key.ptr));
|
|
1612
|
+
}
|
|
1613
|
+
/**
|
|
1614
|
+
* Get the current exception, if any.
|
|
1615
|
+
*/
|
|
1616
|
+
getException() {
|
|
1617
|
+
this.assertNotDisposed();
|
|
1618
|
+
return new JSValueHandle(this, this.exports.qjs_get_exception());
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* Create a new QuickJS Error object.
|
|
1622
|
+
* Accepts a string message or a native Error object.
|
|
1623
|
+
*/
|
|
1624
|
+
newError(messageOrError) {
|
|
1625
|
+
this.assertNotDisposed();
|
|
1626
|
+
const errPtr = this.exports.qjs_new_error();
|
|
1627
|
+
const errHandle = new JSValueHandle(this, errPtr);
|
|
1628
|
+
if (typeof messageOrError === "string") {
|
|
1629
|
+
const msgHandle = this.newString(messageOrError);
|
|
1630
|
+
errHandle.setProp("message", msgHandle);
|
|
1631
|
+
msgHandle.dispose();
|
|
1632
|
+
} else {
|
|
1633
|
+
const msgHandle = this.newString(messageOrError.message);
|
|
1634
|
+
errHandle.setProp("message", msgHandle);
|
|
1635
|
+
msgHandle.dispose();
|
|
1636
|
+
if (messageOrError.name) {
|
|
1637
|
+
const nameHandle = this.newString(messageOrError.name);
|
|
1638
|
+
errHandle.setProp("name", nameHandle);
|
|
1639
|
+
nameHandle.dispose();
|
|
1640
|
+
}
|
|
1641
|
+
if (messageOrError.stack) {
|
|
1642
|
+
const stackHandle = this.newString(messageOrError.stack);
|
|
1643
|
+
errHandle.setProp("stack", stackHandle);
|
|
1644
|
+
stackHandle.dispose();
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
return errHandle;
|
|
1648
|
+
}
|
|
1649
|
+
/**
|
|
1650
|
+
* Get the typeof a handle as a string.
|
|
1651
|
+
*/
|
|
1652
|
+
typeof(handle) {
|
|
1653
|
+
this.assertNotDisposed();
|
|
1654
|
+
const e = this.exports;
|
|
1655
|
+
if (e.qjs_is_undefined(handle.ptr))
|
|
1656
|
+
return "undefined";
|
|
1657
|
+
if (e.qjs_is_null(handle.ptr))
|
|
1658
|
+
return "object";
|
|
1659
|
+
if (e.qjs_is_bool(handle.ptr))
|
|
1660
|
+
return "boolean";
|
|
1661
|
+
if (e.qjs_is_number(handle.ptr))
|
|
1662
|
+
return "number";
|
|
1663
|
+
if (e.qjs_is_big_int(handle.ptr))
|
|
1664
|
+
return "bigint";
|
|
1665
|
+
if (e.qjs_is_string(handle.ptr))
|
|
1666
|
+
return "string";
|
|
1667
|
+
if (e.qjs_is_symbol(handle.ptr))
|
|
1668
|
+
return "symbol";
|
|
1669
|
+
if (e.qjs_is_function(handle.ptr))
|
|
1670
|
+
return "function";
|
|
1671
|
+
if (e.qjs_is_object(handle.ptr))
|
|
1672
|
+
return "object";
|
|
1673
|
+
return "unknown";
|
|
1674
|
+
}
|
|
1675
|
+
/**
|
|
1676
|
+
* Convert a QuickJS handle to a host JavaScript value.
|
|
1677
|
+
* Handles strings, numbers, booleans, null, undefined, bigint, arrays,
|
|
1678
|
+
* errors, functions, and plain objects. Circular references in objects
|
|
1679
|
+
* are returned as `undefined`.
|
|
1680
|
+
*/
|
|
1681
|
+
dump(handle) {
|
|
1682
|
+
this.assertNotDisposed();
|
|
1683
|
+
return this._dump(handle, /* @__PURE__ */ new Map());
|
|
1684
|
+
}
|
|
1685
|
+
_dump(handle, visited) {
|
|
1686
|
+
const e = this.exports;
|
|
1687
|
+
if (e.qjs_is_undefined(handle.ptr))
|
|
1688
|
+
return void 0;
|
|
1689
|
+
if (e.qjs_is_null(handle.ptr))
|
|
1690
|
+
return null;
|
|
1691
|
+
if (e.qjs_is_bool(handle.ptr))
|
|
1692
|
+
return e.qjs_get_bool(handle.ptr) !== 0;
|
|
1693
|
+
if (e.qjs_is_number(handle.ptr))
|
|
1694
|
+
return e.qjs_get_float64(handle.ptr);
|
|
1695
|
+
if (e.qjs_is_string(handle.ptr))
|
|
1696
|
+
return handle.toString();
|
|
1697
|
+
if (e.qjs_is_big_int(handle.ptr))
|
|
1698
|
+
return handle.toBigInt();
|
|
1699
|
+
if (e.qjs_is_symbol(handle.ptr)) {
|
|
1700
|
+
const descOutPtr = e.wasm_malloc(4);
|
|
1701
|
+
const kind = e.qjs_get_symbol_description(handle.ptr, descOutPtr);
|
|
1702
|
+
const view = new DataView(e.memory.buffer);
|
|
1703
|
+
const descPtr = view.getUint32(descOutPtr, true);
|
|
1704
|
+
e.wasm_free(descOutPtr);
|
|
1705
|
+
if (kind === 1) {
|
|
1706
|
+
const descHandle = new JSValueHandle(this, descPtr);
|
|
1707
|
+
const description = descHandle.toString();
|
|
1708
|
+
descHandle.dispose();
|
|
1709
|
+
return Symbol.for(description);
|
|
1710
|
+
} else if (kind === 2) {
|
|
1711
|
+
const descHandle = new JSValueHandle(this, descPtr);
|
|
1712
|
+
descHandle.dispose();
|
|
1713
|
+
return void 0;
|
|
1714
|
+
}
|
|
1715
|
+
return void 0;
|
|
1716
|
+
}
|
|
1717
|
+
if (e.qjs_is_array_buffer(handle.ptr))
|
|
1718
|
+
return handle.toArrayBuffer();
|
|
1719
|
+
if (e.qjs_is_exception(handle.ptr)) {
|
|
1720
|
+
const exc = this.getException();
|
|
1721
|
+
const msg = exc.toString();
|
|
1722
|
+
exc.dispose();
|
|
1723
|
+
return new Error(msg);
|
|
1724
|
+
}
|
|
1725
|
+
if (e.qjs_is_function(handle.ptr))
|
|
1726
|
+
return void 0;
|
|
1727
|
+
if (e.qjs_is_object(handle.ptr)) {
|
|
1728
|
+
const objPtr = e.qjs_get_value_ptr(handle.ptr);
|
|
1729
|
+
if (objPtr) {
|
|
1730
|
+
const existing = visited.get(objPtr);
|
|
1731
|
+
if (existing !== void 0)
|
|
1732
|
+
return existing;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
if (e.qjs_is_object(handle.ptr)) {
|
|
1736
|
+
const byteOffsetPtr = e.wasm_malloc(4);
|
|
1737
|
+
const byteLengthPtr = e.wasm_malloc(4);
|
|
1738
|
+
const bytesPerElemPtr = e.wasm_malloc(4);
|
|
1739
|
+
const abPtr = e.qjs_get_typed_array_buffer(handle.ptr, byteOffsetPtr, byteLengthPtr, bytesPerElemPtr);
|
|
1740
|
+
const abHandle = new JSValueHandle(this, abPtr);
|
|
1741
|
+
if (e.qjs_is_exception(abHandle.ptr) === 0) {
|
|
1742
|
+
const view = new DataView(e.memory.buffer);
|
|
1743
|
+
const byteOffset = view.getUint32(byteOffsetPtr, true);
|
|
1744
|
+
const byteLength = view.getUint32(byteLengthPtr, true);
|
|
1745
|
+
const bytesPerElement = view.getUint32(bytesPerElemPtr, true);
|
|
1746
|
+
e.wasm_free(byteOffsetPtr);
|
|
1747
|
+
e.wasm_free(byteLengthPtr);
|
|
1748
|
+
e.wasm_free(bytesPerElemPtr);
|
|
1749
|
+
const abLenPtr = e.wasm_malloc(4);
|
|
1750
|
+
const abDataPtr = e.qjs_get_array_buffer(abHandle.ptr, abLenPtr);
|
|
1751
|
+
e.wasm_free(abLenPtr);
|
|
1752
|
+
abHandle.dispose();
|
|
1753
|
+
if (abDataPtr !== 0) {
|
|
1754
|
+
const rawBytes = new Uint8Array(e.memory.buffer, abDataPtr + byteOffset, byteLength).slice();
|
|
1755
|
+
switch (bytesPerElement) {
|
|
1756
|
+
case 1:
|
|
1757
|
+
return rawBytes;
|
|
1758
|
+
case 2:
|
|
1759
|
+
return new Uint16Array(rawBytes.buffer);
|
|
1760
|
+
case 4:
|
|
1761
|
+
return new Uint32Array(rawBytes.buffer);
|
|
1762
|
+
case 8:
|
|
1763
|
+
return new Float64Array(rawBytes.buffer);
|
|
1764
|
+
default:
|
|
1765
|
+
return rawBytes;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
} else {
|
|
1769
|
+
abHandle.dispose();
|
|
1770
|
+
e.wasm_free(byteOffsetPtr);
|
|
1771
|
+
e.wasm_free(byteLengthPtr);
|
|
1772
|
+
e.wasm_free(bytesPerElemPtr);
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
if (e.qjs_is_array(handle.ptr)) {
|
|
1776
|
+
const lenHandle = handle.getProp("length");
|
|
1777
|
+
const len = e.qjs_get_float64(lenHandle.ptr);
|
|
1778
|
+
lenHandle.dispose();
|
|
1779
|
+
const arr = [];
|
|
1780
|
+
const objPtr = e.qjs_get_value_ptr(handle.ptr);
|
|
1781
|
+
if (objPtr)
|
|
1782
|
+
visited.set(objPtr, arr);
|
|
1783
|
+
for (let i = 0; i < len; i++) {
|
|
1784
|
+
const elemPtr = e.qjs_get_prop_uint32(handle.ptr, i);
|
|
1785
|
+
const elemHandle = new JSValueHandle(this, elemPtr);
|
|
1786
|
+
arr.push(this._dump(elemHandle, visited));
|
|
1787
|
+
elemHandle.dispose();
|
|
1788
|
+
}
|
|
1789
|
+
return arr;
|
|
1790
|
+
}
|
|
1791
|
+
if (e.qjs_is_error(handle.ptr)) {
|
|
1792
|
+
const nameHandle = handle.getProp("name");
|
|
1793
|
+
const msgHandle = handle.getProp("message");
|
|
1794
|
+
const stackHandle = handle.getProp("stack");
|
|
1795
|
+
const name = nameHandle.isUndefined ? "Error" : nameHandle.toString();
|
|
1796
|
+
const message = msgHandle.isUndefined ? "" : msgHandle.toString();
|
|
1797
|
+
const stack = stackHandle.isUndefined ? void 0 : stackHandle.toString();
|
|
1798
|
+
nameHandle.dispose();
|
|
1799
|
+
msgHandle.dispose();
|
|
1800
|
+
stackHandle.dispose();
|
|
1801
|
+
const err = new Error(message);
|
|
1802
|
+
err.name = name;
|
|
1803
|
+
if (stack !== void 0) {
|
|
1804
|
+
err.stack = stack;
|
|
1805
|
+
}
|
|
1806
|
+
return err;
|
|
1807
|
+
}
|
|
1808
|
+
if (e.qjs_is_object(handle.ptr)) {
|
|
1809
|
+
const keysPtr = e.qjs_get_own_property_names(handle.ptr);
|
|
1810
|
+
const keysHandle = new JSValueHandle(this, keysPtr);
|
|
1811
|
+
if (e.qjs_is_exception(keysHandle.ptr) !== 0) {
|
|
1812
|
+
keysHandle.dispose();
|
|
1813
|
+
return {};
|
|
1814
|
+
}
|
|
1815
|
+
const lenHandle = keysHandle.getProp("length");
|
|
1816
|
+
const len = e.qjs_get_float64(lenHandle.ptr);
|
|
1817
|
+
lenHandle.dispose();
|
|
1818
|
+
const obj = {};
|
|
1819
|
+
const objPtr = e.qjs_get_value_ptr(handle.ptr);
|
|
1820
|
+
if (objPtr)
|
|
1821
|
+
visited.set(objPtr, obj);
|
|
1822
|
+
for (let i = 0; i < len; i++) {
|
|
1823
|
+
const keyPtr = e.qjs_get_prop_uint32(keysHandle.ptr, i);
|
|
1824
|
+
const keyHandle = new JSValueHandle(this, keyPtr);
|
|
1825
|
+
const key = keyHandle.toString();
|
|
1826
|
+
keyHandle.dispose();
|
|
1827
|
+
const valHandle = handle.getProp(key);
|
|
1828
|
+
obj[key] = this._dump(valHandle, visited);
|
|
1829
|
+
valHandle.dispose();
|
|
1830
|
+
}
|
|
1831
|
+
keysHandle.dispose();
|
|
1832
|
+
return obj;
|
|
1833
|
+
}
|
|
1834
|
+
return void 0;
|
|
1835
|
+
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Convert a host JavaScript value to a QuickJS handle.
|
|
1838
|
+
*/
|
|
1839
|
+
hostToHandle(value) {
|
|
1840
|
+
this.assertNotDisposed();
|
|
1841
|
+
if (value === void 0)
|
|
1842
|
+
return this.undefined;
|
|
1843
|
+
if (value === null)
|
|
1844
|
+
return this.null;
|
|
1845
|
+
if (value === true)
|
|
1846
|
+
return this.true;
|
|
1847
|
+
if (value === false)
|
|
1848
|
+
return this.false;
|
|
1849
|
+
if (typeof value === "number")
|
|
1850
|
+
return this.newNumber(value);
|
|
1851
|
+
if (typeof value === "string")
|
|
1852
|
+
return this.newString(value);
|
|
1853
|
+
if (typeof value === "bigint")
|
|
1854
|
+
return this.newBigInt(value);
|
|
1855
|
+
if (typeof value === "symbol") {
|
|
1856
|
+
const key = Symbol.keyFor(value);
|
|
1857
|
+
if (key !== void 0) {
|
|
1858
|
+
return this.newSymbolFor(key);
|
|
1859
|
+
}
|
|
1860
|
+
throw new Error(`Cannot convert local symbol to QuickJS handle. Use Symbol.for() for cross-boundary symbols.`);
|
|
1861
|
+
}
|
|
1862
|
+
if (value instanceof Promise) {
|
|
1863
|
+
const deferred = this.newPromise();
|
|
1864
|
+
value.then((r) => {
|
|
1865
|
+
deferred.resolve(this.hostToHandle(r));
|
|
1866
|
+
this.executePendingJobs();
|
|
1867
|
+
}, (err) => {
|
|
1868
|
+
deferred.reject(this.hostToHandle(err));
|
|
1869
|
+
this.executePendingJobs();
|
|
1870
|
+
});
|
|
1871
|
+
return deferred.handle;
|
|
1872
|
+
}
|
|
1873
|
+
if (value instanceof Error) {
|
|
1874
|
+
return this.newError(value);
|
|
1875
|
+
}
|
|
1876
|
+
if (value instanceof ArrayBuffer) {
|
|
1877
|
+
return this.newArrayBuffer(value);
|
|
1878
|
+
}
|
|
1879
|
+
if (value instanceof Uint8Array) {
|
|
1880
|
+
return this.newUint8Array(value);
|
|
1881
|
+
}
|
|
1882
|
+
if (ArrayBuffer.isView(value)) {
|
|
1883
|
+
return this.newArrayBuffer(new Uint8Array(value.buffer, value.byteOffset, value.byteLength));
|
|
1884
|
+
}
|
|
1885
|
+
if (Array.isArray(value)) {
|
|
1886
|
+
const arr = this.newArray();
|
|
1887
|
+
for (let i = 0; i < value.length; i++) {
|
|
1888
|
+
const elemHandle = this.hostToHandle(value[i]);
|
|
1889
|
+
this.exports.qjs_set_prop_uint32(arr.ptr, i, elemHandle.ptr);
|
|
1890
|
+
elemHandle.dispose();
|
|
1891
|
+
}
|
|
1892
|
+
return arr;
|
|
1893
|
+
}
|
|
1894
|
+
if (typeof value === "object" && value !== null) {
|
|
1895
|
+
const obj = this.newObject();
|
|
1896
|
+
for (const [key, val] of Object.entries(value)) {
|
|
1897
|
+
const valHandle = this.hostToHandle(val);
|
|
1898
|
+
obj.setProp(key, valHandle);
|
|
1899
|
+
valHandle.dispose();
|
|
1900
|
+
}
|
|
1901
|
+
return obj;
|
|
1902
|
+
}
|
|
1903
|
+
return this.undefined;
|
|
1904
|
+
}
|
|
1905
|
+
// ---- Snapshot / Restore ----
|
|
1906
|
+
/**
|
|
1907
|
+
* Snapshot the entire VM state.
|
|
1908
|
+
*
|
|
1909
|
+
* Returns a snapshot containing the full WASM linear memory. Use
|
|
1910
|
+
* `QuickJS.serializeSnapshot()` to convert to a versioned binary
|
|
1911
|
+
* buffer for persistent storage.
|
|
1912
|
+
*/
|
|
1913
|
+
snapshot() {
|
|
1914
|
+
this.assertNotDisposed();
|
|
1915
|
+
return {
|
|
1916
|
+
memory: new Uint8Array(this.exports.memory.buffer).slice(),
|
|
1917
|
+
stackPointer: this.exports.__stack_pointer.value,
|
|
1918
|
+
runtimePtr: this.exports.qjs_get_runtime_ptr(),
|
|
1919
|
+
contextPtr: this.exports.qjs_get_context_ptr(),
|
|
1920
|
+
extensions: this.loadedExtensions.map((ext) => ({
|
|
1921
|
+
name: ext.name,
|
|
1922
|
+
memoryBase: ext.memoryBase,
|
|
1923
|
+
tableBase: ext.tableBase,
|
|
1924
|
+
initFn: ext.initFn
|
|
1925
|
+
}))
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
/**
|
|
1929
|
+
* Re-register a host callback after restoring from a snapshot.
|
|
1930
|
+
* The name must match the name passed to `newFunction()` before the snapshot.
|
|
1931
|
+
*/
|
|
1932
|
+
registerHostCallback(name, fn) {
|
|
1933
|
+
this.hostCallbacks.set(name, fn);
|
|
1934
|
+
}
|
|
1935
|
+
/**
|
|
1936
|
+
* Dispose the VM, releasing all references to the WASM instance
|
|
1937
|
+
* so it can be garbage collected by the host JS engine.
|
|
1938
|
+
*/
|
|
1939
|
+
dispose() {
|
|
1940
|
+
if (!this.disposed) {
|
|
1941
|
+
this.disposed = true;
|
|
1942
|
+
this._global = null;
|
|
1943
|
+
this._undefined = null;
|
|
1944
|
+
this._null = null;
|
|
1945
|
+
this._true = null;
|
|
1946
|
+
this._false = null;
|
|
1947
|
+
this._ownedHandles.clear();
|
|
1948
|
+
this.hostCallbacks.clear();
|
|
1949
|
+
this._activeScope = null;
|
|
1950
|
+
this.exports = null;
|
|
1951
|
+
this.instance = null;
|
|
1952
|
+
this.module = null;
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
/**
|
|
1956
|
+
* Support for `using` declarations (Explicit Resource Management).
|
|
1957
|
+
* Automatically disposes the VM when it goes out of scope.
|
|
1958
|
+
*
|
|
1959
|
+
* ```typescript
|
|
1960
|
+
* using vm = await QuickJS.create(wasmBytes);
|
|
1961
|
+
* vm.evalCode('1 + 2');
|
|
1962
|
+
* // vm is automatically disposed here
|
|
1963
|
+
* ```
|
|
1964
|
+
*/
|
|
1965
|
+
[Symbol.dispose]() {
|
|
1966
|
+
this.dispose();
|
|
1967
|
+
}
|
|
1968
|
+
assertNotDisposed() {
|
|
1969
|
+
if (this.disposed) {
|
|
1970
|
+
throw new Error("QuickJS instance has been disposed");
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
// ---- Internal accessors for JSValueHandle ----
|
|
1974
|
+
/** @internal */
|
|
1975
|
+
_getExports() {
|
|
1976
|
+
return this.exports;
|
|
1977
|
+
}
|
|
1978
|
+
/** @internal */
|
|
1979
|
+
_getMemory() {
|
|
1980
|
+
return this.exports.memory;
|
|
1981
|
+
}
|
|
1982
|
+
/** @internal */
|
|
1983
|
+
_writeString(str) {
|
|
1984
|
+
return this.writeString(str);
|
|
1985
|
+
}
|
|
1986
|
+
/** @internal */
|
|
1987
|
+
_readCString(ptr) {
|
|
1988
|
+
return this.readCString(ptr);
|
|
1989
|
+
}
|
|
1990
|
+
};
|
|
1991
|
+
function stringKeyNeedsValuePath(key) {
|
|
1992
|
+
return key.includes("\0") || LONE_SURROGATE_RE.test(key);
|
|
1993
|
+
}
|
|
1994
|
+
var wtf8Decoder = new TextDecoder();
|
|
1995
|
+
var wtf8Encoder = new TextEncoder();
|
|
1996
|
+
var LONE_SURROGATE_RE = /(?:[\uD800-\uDBFF](?![\uDC00-\uDFFF]))|(?:(?<![\uD800-\uDBFF])[\uDC00-\uDFFF])/;
|
|
1997
|
+
function encodeWtf8(str) {
|
|
1998
|
+
if (!LONE_SURROGATE_RE.test(str))
|
|
1999
|
+
return wtf8Encoder.encode(str);
|
|
2000
|
+
const bytes = [];
|
|
2001
|
+
for (let i = 0; i < str.length; i++) {
|
|
2002
|
+
const code = str.charCodeAt(i);
|
|
2003
|
+
if (code < 128) {
|
|
2004
|
+
bytes.push(code);
|
|
2005
|
+
} else if (code < 2048) {
|
|
2006
|
+
bytes.push(192 | code >> 6, 128 | code & 63);
|
|
2007
|
+
} else if (code >= 55296 && code <= 56319 && i + 1 < str.length) {
|
|
2008
|
+
const next = str.charCodeAt(i + 1);
|
|
2009
|
+
if (next >= 56320 && next <= 57343) {
|
|
2010
|
+
const cp = 65536 + (code - 55296 << 10) + (next - 56320);
|
|
2011
|
+
bytes.push(240 | cp >> 18, 128 | cp >> 12 & 63, 128 | cp >> 6 & 63, 128 | cp & 63);
|
|
2012
|
+
i++;
|
|
2013
|
+
continue;
|
|
2014
|
+
}
|
|
2015
|
+
bytes.push(224 | code >> 12, 128 | code >> 6 & 63, 128 | code & 63);
|
|
2016
|
+
} else {
|
|
2017
|
+
bytes.push(224 | code >> 12, 128 | code >> 6 & 63, 128 | code & 63);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
return new Uint8Array(bytes);
|
|
2021
|
+
}
|
|
2022
|
+
function decodeWtf8(bytes) {
|
|
2023
|
+
let hasSurrogateSequence = false;
|
|
2024
|
+
for (let i2 = 0; i2 < bytes.length - 1; i2++) {
|
|
2025
|
+
if (bytes[i2] === 237 && bytes[i2 + 1] >= 160 && bytes[i2 + 1] <= 191) {
|
|
2026
|
+
hasSurrogateSequence = true;
|
|
2027
|
+
break;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
if (!hasSurrogateSequence)
|
|
2031
|
+
return wtf8Decoder.decode(bytes);
|
|
2032
|
+
let out = "";
|
|
2033
|
+
let i = 0;
|
|
2034
|
+
while (i < bytes.length) {
|
|
2035
|
+
const b0 = bytes[i];
|
|
2036
|
+
if (b0 < 128) {
|
|
2037
|
+
out += String.fromCharCode(b0);
|
|
2038
|
+
i += 1;
|
|
2039
|
+
} else if (b0 < 224) {
|
|
2040
|
+
out += String.fromCharCode((b0 & 31) << 6 | bytes[i + 1] & 63);
|
|
2041
|
+
i += 2;
|
|
2042
|
+
} else if (b0 < 240) {
|
|
2043
|
+
out += String.fromCharCode((b0 & 15) << 12 | (bytes[i + 1] & 63) << 6 | bytes[i + 2] & 63);
|
|
2044
|
+
i += 3;
|
|
2045
|
+
} else {
|
|
2046
|
+
const cp = (b0 & 7) << 18 | (bytes[i + 1] & 63) << 12 | (bytes[i + 2] & 63) << 6 | bytes[i + 3] & 63;
|
|
2047
|
+
out += String.fromCodePoint(cp);
|
|
2048
|
+
i += 4;
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
return out;
|
|
2052
|
+
}
|
|
2053
|
+
var JSException = class extends Error {
|
|
2054
|
+
/**
|
|
2055
|
+
* A live handle to the QuickJS exception value. You can read custom
|
|
2056
|
+
* properties, call methods, etc. Must be disposed when done.
|
|
2057
|
+
*/
|
|
2058
|
+
handle;
|
|
2059
|
+
// Cached values so they survive handle disposal / VM teardown.
|
|
2060
|
+
// Using # fields keeps them out of console.log / Object.keys output.
|
|
2061
|
+
#name;
|
|
2062
|
+
#message;
|
|
2063
|
+
#stack;
|
|
2064
|
+
/** @internal */
|
|
2065
|
+
constructor(handle) {
|
|
2066
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
2067
|
+
try {
|
|
2068
|
+
super();
|
|
2069
|
+
this.handle = handle;
|
|
2070
|
+
delete this.stack;
|
|
2071
|
+
const msgHandle = __addDisposableResource(env_1, handle.getProp("message"), false);
|
|
2072
|
+
this.#name = handle.getProp("name").consume((h) => h.isUndefined ? "Error" : h.toString());
|
|
2073
|
+
this.#message = msgHandle.isUndefined ? handle.toString() : msgHandle.toString();
|
|
2074
|
+
this.#stack = handle.getProp("stack").consume((h) => h.isUndefined ? void 0 : h.toString());
|
|
2075
|
+
} catch (e_1) {
|
|
2076
|
+
env_1.error = e_1;
|
|
2077
|
+
env_1.hasError = true;
|
|
2078
|
+
} finally {
|
|
2079
|
+
__disposeResources(env_1);
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
get name() {
|
|
2083
|
+
return this.#name;
|
|
2084
|
+
}
|
|
2085
|
+
set name(v) {
|
|
2086
|
+
this.#name = v;
|
|
2087
|
+
}
|
|
2088
|
+
get message() {
|
|
2089
|
+
return this.#message;
|
|
2090
|
+
}
|
|
2091
|
+
set message(v) {
|
|
2092
|
+
this.#message = v;
|
|
2093
|
+
}
|
|
2094
|
+
get stack() {
|
|
2095
|
+
return this.#stack;
|
|
2096
|
+
}
|
|
2097
|
+
set stack(v) {
|
|
2098
|
+
this.#stack = v;
|
|
2099
|
+
}
|
|
2100
|
+
dispose() {
|
|
2101
|
+
this.handle.dispose();
|
|
2102
|
+
}
|
|
2103
|
+
[Symbol.dispose]() {
|
|
2104
|
+
this.handle.dispose();
|
|
2105
|
+
}
|
|
2106
|
+
};
|
|
2107
|
+
var JSValueHandle = class _JSValueHandle {
|
|
2108
|
+
/** The QuickJS VM instance this handle belongs to. */
|
|
2109
|
+
vm;
|
|
2110
|
+
/** @internal */
|
|
2111
|
+
ptr;
|
|
2112
|
+
disposed_ = false;
|
|
2113
|
+
/**
|
|
2114
|
+
* When true, this handle is a cached singleton (e.g. `undefined`, `null`,
|
|
2115
|
+
* `true`, `false`, the global object) and `dispose()` is a no-op. This
|
|
2116
|
+
* prevents code that routinely disposes handles (such as the object/array
|
|
2117
|
+
* branches of `hostToHandle`) from freeing the shared heap `JSValue*` that
|
|
2118
|
+
* the cached singleton still references, which would corrupt later reads.
|
|
2119
|
+
* @internal
|
|
2120
|
+
*/
|
|
2121
|
+
singleton;
|
|
2122
|
+
/**
|
|
2123
|
+
* When true, this handle wraps a `JSValue*` OWNED BY THE C CALLER: the
|
|
2124
|
+
* `this`/argument handles the host-call trampoline passes to a host
|
|
2125
|
+
* callback (`handleHostCall`). The C side frees those values after the
|
|
2126
|
+
* call returns, so `dispose()` is a no-op and the handle is never
|
|
2127
|
+
* registered with an active `withScope()` (either would double-free
|
|
2128
|
+
* the guest value and corrupt the heap). A callback that needs to
|
|
2129
|
+
* retain an argument past its own invocation must `dup()` it; the
|
|
2130
|
+
* duplicate takes a fresh reference and behaves like any owned handle.
|
|
2131
|
+
* @internal
|
|
2132
|
+
*/
|
|
2133
|
+
borrowed;
|
|
2134
|
+
/**
|
|
2135
|
+
* Extra cleanup to run when this handle is disposed. Used by
|
|
2136
|
+
* `newEphemeralFunction()` to unregister its host callback.
|
|
2137
|
+
* @internal
|
|
2138
|
+
*/
|
|
2139
|
+
_onDispose;
|
|
2140
|
+
constructor(vm, ptr, singleton = false, borrowed = false) {
|
|
2141
|
+
this.vm = vm;
|
|
2142
|
+
this.ptr = ptr;
|
|
2143
|
+
this.singleton = singleton;
|
|
2144
|
+
this.borrowed = borrowed;
|
|
2145
|
+
if (!singleton && !borrowed)
|
|
2146
|
+
vm._activeScope?.add(this);
|
|
2147
|
+
}
|
|
2148
|
+
/**
|
|
2149
|
+
* Whether this handle wraps a C-owned pointer (host-callback
|
|
2150
|
+
* `this`/arguments). Borrowed handles must never be exported as
|
|
2151
|
+
* snapshot tokens: the trampoline frees their boxes after the
|
|
2152
|
+
* callback returns. @internal
|
|
2153
|
+
*/
|
|
2154
|
+
get _isBorrowed() {
|
|
2155
|
+
return this.borrowed;
|
|
2156
|
+
}
|
|
2157
|
+
/**
|
|
2158
|
+
* Whether `dispose()` has been called on this handle.
|
|
2159
|
+
*
|
|
2160
|
+
* Note that handle methods do not currently guard against use after
|
|
2161
|
+
* disposal: reading from a disposed handle reads freed memory. Check this
|
|
2162
|
+
* when a handle's lifetime is managed elsewhere (e.g. by `withScope()`).
|
|
2163
|
+
*/
|
|
2164
|
+
get disposed() {
|
|
2165
|
+
return this.disposed_;
|
|
2166
|
+
}
|
|
2167
|
+
get isUndefined() {
|
|
2168
|
+
return this.vm._getExports().qjs_is_undefined(this.ptr) !== 0;
|
|
2169
|
+
}
|
|
2170
|
+
get isNull() {
|
|
2171
|
+
return this.vm._getExports().qjs_is_null(this.ptr) !== 0;
|
|
2172
|
+
}
|
|
2173
|
+
/**
|
|
2174
|
+
* Get the promise state: 0 = pending, 1 = fulfilled, 2 = rejected
|
|
2175
|
+
*/
|
|
2176
|
+
get isBool() {
|
|
2177
|
+
return this.vm._getExports().qjs_is_bool(this.ptr) !== 0;
|
|
2178
|
+
}
|
|
2179
|
+
get isNumber() {
|
|
2180
|
+
return this.vm._getExports().qjs_is_number(this.ptr) !== 0;
|
|
2181
|
+
}
|
|
2182
|
+
get isString() {
|
|
2183
|
+
return this.vm._getExports().qjs_is_string(this.ptr) !== 0;
|
|
2184
|
+
}
|
|
2185
|
+
get isSymbol() {
|
|
2186
|
+
return this.vm._getExports().qjs_is_symbol(this.ptr) !== 0;
|
|
2187
|
+
}
|
|
2188
|
+
get isBigInt() {
|
|
2189
|
+
return this.vm._getExports().qjs_is_big_int(this.ptr) !== 0;
|
|
2190
|
+
}
|
|
2191
|
+
get isObject() {
|
|
2192
|
+
return this.vm._getExports().qjs_is_object(this.ptr) !== 0;
|
|
2193
|
+
}
|
|
2194
|
+
get isArray() {
|
|
2195
|
+
return this.vm._getExports().qjs_is_array(this.ptr) !== 0;
|
|
2196
|
+
}
|
|
2197
|
+
get isFunction() {
|
|
2198
|
+
return this.vm._getExports().qjs_is_function(this.ptr) !== 0;
|
|
2199
|
+
}
|
|
2200
|
+
get isError() {
|
|
2201
|
+
return this.vm._getExports().qjs_is_error(this.ptr) !== 0;
|
|
2202
|
+
}
|
|
2203
|
+
get isPromise() {
|
|
2204
|
+
return this.vm._getExports().qjs_is_promise(this.ptr) !== 0;
|
|
2205
|
+
}
|
|
2206
|
+
get isArrayBuffer() {
|
|
2207
|
+
return this.vm._getExports().qjs_is_array_buffer(this.ptr) !== 0;
|
|
2208
|
+
}
|
|
2209
|
+
/**
|
|
2210
|
+
* Whether this value is a Proxy exotic object.
|
|
2211
|
+
*
|
|
2212
|
+
* This is an engine-level check: it never fires proxy traps and cannot
|
|
2213
|
+
* be determined (or spoofed) from within guest JavaScript. Use
|
|
2214
|
+
* {@link getProxyTarget} / {@link getProxyHandler} to introspect a
|
|
2215
|
+
* detected proxy without executing guest code.
|
|
2216
|
+
*/
|
|
2217
|
+
get isProxy() {
|
|
2218
|
+
return this.vm._getExports().qjs_is_proxy(this.ptr) !== 0;
|
|
2219
|
+
}
|
|
2220
|
+
/**
|
|
2221
|
+
* Whether this value is a Map (engine brand check: trap-free,
|
|
2222
|
+
* spoof-proof, and unaffected by prototype/constructor mutation).
|
|
2223
|
+
* A Proxy wrapping a Map returns false.
|
|
2224
|
+
*/
|
|
2225
|
+
get isMap() {
|
|
2226
|
+
return this.vm._getExports().qjs_is_map(this.ptr) !== 0;
|
|
2227
|
+
}
|
|
2228
|
+
/**
|
|
2229
|
+
* Whether this value is a Set (engine brand check: trap-free,
|
|
2230
|
+
* spoof-proof, and unaffected by prototype/constructor mutation).
|
|
2231
|
+
* A Proxy wrapping a Set returns false.
|
|
2232
|
+
*/
|
|
2233
|
+
get isSet() {
|
|
2234
|
+
return this.vm._getExports().qjs_is_set(this.ptr) !== 0;
|
|
2235
|
+
}
|
|
2236
|
+
/**
|
|
2237
|
+
* Whether this value is a Date (engine brand check: trap-free,
|
|
2238
|
+
* spoof-proof, and unaffected by prototype/constructor mutation).
|
|
2239
|
+
* A Proxy wrapping a Date returns false.
|
|
2240
|
+
*/
|
|
2241
|
+
get isDate() {
|
|
2242
|
+
return this.vm._getExports().qjs_is_date(this.ptr) !== 0;
|
|
2243
|
+
}
|
|
2244
|
+
/**
|
|
2245
|
+
* Whether this value is a RegExp (engine brand check: trap-free,
|
|
2246
|
+
* spoof-proof, and unaffected by prototype/constructor mutation).
|
|
2247
|
+
* A Proxy wrapping a RegExp returns false.
|
|
2248
|
+
*/
|
|
2249
|
+
get isRegExp() {
|
|
2250
|
+
return this.vm._getExports().qjs_is_regexp(this.ptr) !== 0;
|
|
2251
|
+
}
|
|
2252
|
+
/** Whether this value is a WeakRef (engine brand check). */
|
|
2253
|
+
get isWeakRef() {
|
|
2254
|
+
return this.vm._getExports().qjs_is_weak_ref(this.ptr) !== 0;
|
|
2255
|
+
}
|
|
2256
|
+
/** Whether this value is a WeakMap (engine brand check). */
|
|
2257
|
+
get isWeakMap() {
|
|
2258
|
+
return this.vm._getExports().qjs_is_weak_map(this.ptr) !== 0;
|
|
2259
|
+
}
|
|
2260
|
+
/** Whether this value is a WeakSet (engine brand check). */
|
|
2261
|
+
get isWeakSet() {
|
|
2262
|
+
return this.vm._getExports().qjs_is_weak_set(this.ptr) !== 0;
|
|
2263
|
+
}
|
|
2264
|
+
/** Whether this value is a DataView (engine brand check). */
|
|
2265
|
+
get isDataView() {
|
|
2266
|
+
return this.vm._getExports().qjs_is_data_view(this.ptr) !== 0;
|
|
2267
|
+
}
|
|
2268
|
+
/**
|
|
2269
|
+
* A numeric identity for the underlying heap value, or 0 for values that
|
|
2270
|
+
* are not heap-allocated (numbers, booleans, `null`, `undefined`).
|
|
2271
|
+
*
|
|
2272
|
+
* Two handles to the same underlying object always report the same
|
|
2273
|
+
* identity, and two live handles to different objects always report
|
|
2274
|
+
* different identities, so this is the value to key a `Map` on when
|
|
2275
|
+
* deduplicating or detecting cycles across handles (`dump()` uses it for
|
|
2276
|
+
* exactly that).
|
|
2277
|
+
*
|
|
2278
|
+
* The identity is only meaningful while the value is alive; it is an
|
|
2279
|
+
* address, so it may be reused after every handle to the value has been
|
|
2280
|
+
* disposed. Do not persist it, and do not treat it as unforgeable: a
|
|
2281
|
+
* number read out of the guest can trivially collide with one.
|
|
2282
|
+
*/
|
|
2283
|
+
get identity() {
|
|
2284
|
+
return this.vm._getExports().qjs_get_value_ptr(this.ptr);
|
|
2285
|
+
}
|
|
2286
|
+
/**
|
|
2287
|
+
* Extract the value as a boolean, applying JavaScript truthiness
|
|
2288
|
+
* (equivalent to `!!value` inside the VM).
|
|
2289
|
+
*/
|
|
2290
|
+
toBoolean() {
|
|
2291
|
+
return this.vm._getExports().qjs_get_bool(this.ptr) !== 0;
|
|
2292
|
+
}
|
|
2293
|
+
/**
|
|
2294
|
+
* The internal QuickJS class ID of this value, or 0 for non-objects.
|
|
2295
|
+
* Useful as a generic engine-level brand when no dedicated `is*`
|
|
2296
|
+
* getter exists. Class IDs are stable within a VM instance but are an
|
|
2297
|
+
* engine implementation detail, so prefer the dedicated getters.
|
|
2298
|
+
*/
|
|
2299
|
+
get classId() {
|
|
2300
|
+
return this.vm._getExports().qjs_get_class_id(this.ptr);
|
|
2301
|
+
}
|
|
2302
|
+
/**
|
|
2303
|
+
* The engine-level class name of this value, e.g. `"Object"`, `"Map"`,
|
|
2304
|
+
* `"Date"`, `"RegExp"`, or the registered name of an extension-defined
|
|
2305
|
+
* class like `"URL"`, or `undefined` for non-objects and unnamed
|
|
2306
|
+
* internal classes.
|
|
2307
|
+
*
|
|
2308
|
+
* Unlike `constructorName` (which reads the `constructor` and `name`
|
|
2309
|
+
* properties and can therefore fire getters/proxy traps and be spoofed),
|
|
2310
|
+
* this is trap-free: it reads the engine's class table directly, never
|
|
2311
|
+
* executes guest code, and cannot be forged by reassigning prototypes or
|
|
2312
|
+
* constructors. Note that the engine registers the Proxy class under the
|
|
2313
|
+
* name `"Object"` (mirroring `Object.prototype.toString`), so use `isProxy`
|
|
2314
|
+
* to detect proxies and `getProxyTarget()` to read the target's brand.
|
|
2315
|
+
*/
|
|
2316
|
+
get className() {
|
|
2317
|
+
const h = new _JSValueHandle(this.vm, this.vm._getExports().qjs_get_class_name(this.ptr));
|
|
2318
|
+
if (this.vm._getExports().qjs_is_exception(h.ptr) !== 0) {
|
|
2319
|
+
h.dispose();
|
|
2320
|
+
throw new JSException(this.vm.getException());
|
|
2321
|
+
}
|
|
2322
|
+
try {
|
|
2323
|
+
return h.isUndefined ? void 0 : h.toString();
|
|
2324
|
+
} finally {
|
|
2325
|
+
h.dispose();
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
get promiseState() {
|
|
2329
|
+
return this.vm._getExports().qjs_promise_state(this.ptr);
|
|
2330
|
+
}
|
|
2331
|
+
/**
|
|
2332
|
+
* Get the typeof this value as a string.
|
|
2333
|
+
* Returns the same values as the native `typeof` operator.
|
|
2334
|
+
*/
|
|
2335
|
+
get typeof() {
|
|
2336
|
+
return this.vm.typeof(this);
|
|
2337
|
+
}
|
|
2338
|
+
/**
|
|
2339
|
+
* Get the length property of this value (for arrays, strings, etc.).
|
|
2340
|
+
*/
|
|
2341
|
+
get length() {
|
|
2342
|
+
const h = this.getProp("length");
|
|
2343
|
+
const n = h.toNumber();
|
|
2344
|
+
h.dispose();
|
|
2345
|
+
return n;
|
|
2346
|
+
}
|
|
2347
|
+
/**
|
|
2348
|
+
* Get the constructor name of this object, or undefined if unavailable.
|
|
2349
|
+
*/
|
|
2350
|
+
get constructorName() {
|
|
2351
|
+
const ctor = this.getProp("constructor");
|
|
2352
|
+
if (ctor.isUndefined || ctor.isNull) {
|
|
2353
|
+
ctor.dispose();
|
|
2354
|
+
return void 0;
|
|
2355
|
+
}
|
|
2356
|
+
const name = ctor.getProp("name");
|
|
2357
|
+
ctor.dispose();
|
|
2358
|
+
if (name.isUndefined || name.isNull) {
|
|
2359
|
+
name.dispose();
|
|
2360
|
+
return void 0;
|
|
2361
|
+
}
|
|
2362
|
+
const result = name.toString();
|
|
2363
|
+
name.dispose();
|
|
2364
|
+
return result;
|
|
2365
|
+
}
|
|
2366
|
+
/**
|
|
2367
|
+
* Get the own enumerable string property names (equivalent to Object.keys()).
|
|
2368
|
+
*/
|
|
2369
|
+
keys() {
|
|
2370
|
+
const e = this.vm._getExports();
|
|
2371
|
+
const keysPtr = e.qjs_get_own_property_names(this.ptr);
|
|
2372
|
+
const keysHandle = new _JSValueHandle(this.vm, keysPtr);
|
|
2373
|
+
if (e.qjs_is_exception(keysHandle.ptr) !== 0) {
|
|
2374
|
+
keysHandle.dispose();
|
|
2375
|
+
return [];
|
|
2376
|
+
}
|
|
2377
|
+
const lenHandle = keysHandle.getProp("length");
|
|
2378
|
+
const len = e.qjs_get_float64(lenHandle.ptr);
|
|
2379
|
+
lenHandle.dispose();
|
|
2380
|
+
const result = [];
|
|
2381
|
+
for (let i = 0; i < len; i++) {
|
|
2382
|
+
const keyPtr = e.qjs_get_prop_uint32(keysHandle.ptr, i);
|
|
2383
|
+
const keyHandle = new _JSValueHandle(this.vm, keyPtr);
|
|
2384
|
+
result.push(keyHandle.toString());
|
|
2385
|
+
keyHandle.dispose();
|
|
2386
|
+
}
|
|
2387
|
+
keysHandle.dispose();
|
|
2388
|
+
return result;
|
|
2389
|
+
}
|
|
2390
|
+
/**
|
|
2391
|
+
* Get all own property names including non-enumerable ones
|
|
2392
|
+
* (equivalent to Object.getOwnPropertyNames()).
|
|
2393
|
+
*/
|
|
2394
|
+
getOwnPropertyNames() {
|
|
2395
|
+
const e = this.vm._getExports();
|
|
2396
|
+
const keysPtr = e.qjs_get_own_property_names_all(this.ptr);
|
|
2397
|
+
const keysHandle = new _JSValueHandle(this.vm, keysPtr);
|
|
2398
|
+
if (e.qjs_is_exception(keysHandle.ptr) !== 0) {
|
|
2399
|
+
keysHandle.dispose();
|
|
2400
|
+
return [];
|
|
2401
|
+
}
|
|
2402
|
+
const lenHandle = keysHandle.getProp("length");
|
|
2403
|
+
const len = e.qjs_get_float64(lenHandle.ptr);
|
|
2404
|
+
lenHandle.dispose();
|
|
2405
|
+
const result = [];
|
|
2406
|
+
for (let i = 0; i < len; i++) {
|
|
2407
|
+
const keyPtr = e.qjs_get_prop_uint32(keysHandle.ptr, i);
|
|
2408
|
+
const keyHandle = new _JSValueHandle(this.vm, keyPtr);
|
|
2409
|
+
result.push(keyHandle.toString());
|
|
2410
|
+
keyHandle.dispose();
|
|
2411
|
+
}
|
|
2412
|
+
keysHandle.dispose();
|
|
2413
|
+
return result;
|
|
2414
|
+
}
|
|
2415
|
+
/**
|
|
2416
|
+
* Get ALL own property keys (strings and symbols), including
|
|
2417
|
+
* non-enumerable (equivalent to Reflect.ownKeys()).
|
|
2418
|
+
*
|
|
2419
|
+
* String keys are returned as strings; symbol keys are returned as
|
|
2420
|
+
* JSValueHandles which the caller must dispose.
|
|
2421
|
+
*
|
|
2422
|
+
* Trap-free for ordinary objects; fires the `ownKeys` trap for a
|
|
2423
|
+
* Proxy (check {@link isProxy} first if that matters).
|
|
2424
|
+
*/
|
|
2425
|
+
getOwnPropertyKeys() {
|
|
2426
|
+
const e = this.vm._getExports();
|
|
2427
|
+
const keysPtr = e.qjs_get_own_property_keys(this.ptr);
|
|
2428
|
+
const keysHandle = new _JSValueHandle(this.vm, keysPtr);
|
|
2429
|
+
if (e.qjs_is_exception(keysHandle.ptr) !== 0) {
|
|
2430
|
+
keysHandle.dispose();
|
|
2431
|
+
return [];
|
|
2432
|
+
}
|
|
2433
|
+
const lenHandle = keysHandle.getProp("length");
|
|
2434
|
+
const len = e.qjs_get_float64(lenHandle.ptr);
|
|
2435
|
+
lenHandle.dispose();
|
|
2436
|
+
const result = [];
|
|
2437
|
+
for (let i = 0; i < len; i++) {
|
|
2438
|
+
const keyPtr = e.qjs_get_prop_uint32(keysHandle.ptr, i);
|
|
2439
|
+
const keyHandle = new _JSValueHandle(this.vm, keyPtr);
|
|
2440
|
+
if (keyHandle.isSymbol) {
|
|
2441
|
+
result.push(keyHandle);
|
|
2442
|
+
} else {
|
|
2443
|
+
result.push(keyHandle.toString());
|
|
2444
|
+
keyHandle.dispose();
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
keysHandle.dispose();
|
|
2448
|
+
return result;
|
|
2449
|
+
}
|
|
2450
|
+
/**
|
|
2451
|
+
* Get the own property descriptor for a key WITHOUT invoking getters
|
|
2452
|
+
* (equivalent to Object.getOwnPropertyDescriptor()).
|
|
2453
|
+
*
|
|
2454
|
+
* This is the safe way to inspect a property that may be an accessor:
|
|
2455
|
+
* a data property yields `{ value, writable, enumerable, configurable }`,
|
|
2456
|
+
* an accessor property yields `{ get, set, enumerable, configurable }`
|
|
2457
|
+
* where `get`/`set` are handles to the accessor functions themselves
|
|
2458
|
+
* (never invoked). Returns undefined if there is no such own property.
|
|
2459
|
+
*
|
|
2460
|
+
* The `value`/`get`/`set` handles are owned by the caller and must be
|
|
2461
|
+
* disposed.
|
|
2462
|
+
*
|
|
2463
|
+
* Trap-free for ordinary objects; fires the `getOwnPropertyDescriptor`
|
|
2464
|
+
* trap for a Proxy (check {@link isProxy} first if that matters).
|
|
2465
|
+
*/
|
|
2466
|
+
getOwnPropertyDescriptor(key) {
|
|
2467
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
2468
|
+
try {
|
|
2469
|
+
const e = this.vm._getExports();
|
|
2470
|
+
let keyHandle;
|
|
2471
|
+
let keyPtr;
|
|
2472
|
+
if (typeof key === "string") {
|
|
2473
|
+
keyHandle = this.vm.newString(key);
|
|
2474
|
+
keyPtr = keyHandle.ptr;
|
|
2475
|
+
} else {
|
|
2476
|
+
keyPtr = key.ptr;
|
|
2477
|
+
}
|
|
2478
|
+
const descPtr = e.qjs_get_own_property_descriptor(this.ptr, keyPtr);
|
|
2479
|
+
keyHandle?.dispose();
|
|
2480
|
+
if (descPtr === 0)
|
|
2481
|
+
return void 0;
|
|
2482
|
+
const descHandle = __addDisposableResource(env_2, new _JSValueHandle(this.vm, descPtr), false);
|
|
2483
|
+
if (e.qjs_is_exception(descHandle.ptr) !== 0) {
|
|
2484
|
+
throw new JSException(this.vm.getException());
|
|
2485
|
+
}
|
|
2486
|
+
const enumerable = descHandle.getProp("enumerable").consume((h) => e.qjs_get_bool(h.ptr) !== 0);
|
|
2487
|
+
const configurable = descHandle.getProp("configurable").consume((h) => e.qjs_get_bool(h.ptr) !== 0);
|
|
2488
|
+
if (descHandle.hasOwnProperty("value")) {
|
|
2489
|
+
return {
|
|
2490
|
+
value: descHandle.getProp("value"),
|
|
2491
|
+
writable: descHandle.getProp("writable").consume((h) => e.qjs_get_bool(h.ptr) !== 0),
|
|
2492
|
+
enumerable,
|
|
2493
|
+
configurable
|
|
2494
|
+
};
|
|
2495
|
+
}
|
|
2496
|
+
return {
|
|
2497
|
+
get: descHandle.getProp("get"),
|
|
2498
|
+
set: descHandle.getProp("set"),
|
|
2499
|
+
enumerable,
|
|
2500
|
+
configurable
|
|
2501
|
+
};
|
|
2502
|
+
} catch (e_2) {
|
|
2503
|
+
env_2.error = e_2;
|
|
2504
|
+
env_2.hasError = true;
|
|
2505
|
+
} finally {
|
|
2506
|
+
__disposeResources(env_2);
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
/**
|
|
2510
|
+
* Check if a property is an own property (equivalent to Object.prototype.hasOwnProperty).
|
|
2511
|
+
*/
|
|
2512
|
+
hasOwnProperty(name) {
|
|
2513
|
+
if (stringKeyNeedsValuePath(name)) {
|
|
2514
|
+
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
2515
|
+
try {
|
|
2516
|
+
const keyHandle = __addDisposableResource(env_3, this.vm.newString(name), false);
|
|
2517
|
+
return this.vm._getExports().qjs_has_own_property_value(this.ptr, keyHandle.ptr) === 1;
|
|
2518
|
+
} catch (e_3) {
|
|
2519
|
+
env_3.error = e_3;
|
|
2520
|
+
env_3.hasError = true;
|
|
2521
|
+
} finally {
|
|
2522
|
+
__disposeResources(env_3);
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
const { ptr: namePtr } = this.vm._writeString(name);
|
|
2526
|
+
const result = this.vm._getExports().qjs_has_own_property(this.ptr, namePtr);
|
|
2527
|
+
this.vm._getExports().wasm_free(namePtr);
|
|
2528
|
+
return result === 1;
|
|
2529
|
+
}
|
|
2530
|
+
/**
|
|
2531
|
+
* Check if a property is enumerable (equivalent to Object.prototype.propertyIsEnumerable).
|
|
2532
|
+
*/
|
|
2533
|
+
propertyIsEnumerable(name) {
|
|
2534
|
+
if (stringKeyNeedsValuePath(name)) {
|
|
2535
|
+
const env_4 = { stack: [], error: void 0, hasError: false };
|
|
2536
|
+
try {
|
|
2537
|
+
const keyHandle = __addDisposableResource(env_4, this.vm.newString(name), false);
|
|
2538
|
+
return this.vm._getExports().qjs_property_is_enumerable_value(this.ptr, keyHandle.ptr) === 1;
|
|
2539
|
+
} catch (e_4) {
|
|
2540
|
+
env_4.error = e_4;
|
|
2541
|
+
env_4.hasError = true;
|
|
2542
|
+
} finally {
|
|
2543
|
+
__disposeResources(env_4);
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
const { ptr: namePtr } = this.vm._writeString(name);
|
|
2547
|
+
const result = this.vm._getExports().qjs_property_is_enumerable(this.ptr, namePtr);
|
|
2548
|
+
this.vm._getExports().wasm_free(namePtr);
|
|
2549
|
+
return result === 1;
|
|
2550
|
+
}
|
|
2551
|
+
/**
|
|
2552
|
+
* Get the prototype of this object (equivalent to Object.getPrototypeOf()).
|
|
2553
|
+
*/
|
|
2554
|
+
getPrototypeOf() {
|
|
2555
|
+
const protoPtr = this.vm._getExports().qjs_get_prototype_of(this.ptr);
|
|
2556
|
+
return new _JSValueHandle(this.vm, protoPtr);
|
|
2557
|
+
}
|
|
2558
|
+
/**
|
|
2559
|
+
* Get the `[[ProxyTarget]]` of this Proxy without firing any traps.
|
|
2560
|
+
* Throws {@link JSException} if this value is not a Proxy; check
|
|
2561
|
+
* {@link isProxy} first. Note the target may itself be a Proxy.
|
|
2562
|
+
*/
|
|
2563
|
+
getProxyTarget() {
|
|
2564
|
+
const ptr = this.vm._getExports().qjs_get_proxy_target(this.ptr);
|
|
2565
|
+
const handle = new _JSValueHandle(this.vm, ptr);
|
|
2566
|
+
if (this.vm._getExports().qjs_is_exception(handle.ptr) !== 0) {
|
|
2567
|
+
handle.dispose();
|
|
2568
|
+
throw new JSException(this.vm.getException());
|
|
2569
|
+
}
|
|
2570
|
+
return handle;
|
|
2571
|
+
}
|
|
2572
|
+
/**
|
|
2573
|
+
* Get the `[[ProxyHandler]]` of this Proxy without firing any traps.
|
|
2574
|
+
* Throws {@link JSException} if this value is not a Proxy; check
|
|
2575
|
+
* {@link isProxy} first.
|
|
2576
|
+
*/
|
|
2577
|
+
getProxyHandler() {
|
|
2578
|
+
const ptr = this.vm._getExports().qjs_get_proxy_handler(this.ptr);
|
|
2579
|
+
const handle = new _JSValueHandle(this.vm, ptr);
|
|
2580
|
+
if (this.vm._getExports().qjs_is_exception(handle.ptr) !== 0) {
|
|
2581
|
+
handle.dispose();
|
|
2582
|
+
throw new JSException(this.vm.getException());
|
|
2583
|
+
}
|
|
2584
|
+
return handle;
|
|
2585
|
+
}
|
|
2586
|
+
/**
|
|
2587
|
+
* Get a property by name.
|
|
2588
|
+
*/
|
|
2589
|
+
getProp(name) {
|
|
2590
|
+
if (stringKeyNeedsValuePath(name)) {
|
|
2591
|
+
const env_5 = { stack: [], error: void 0, hasError: false };
|
|
2592
|
+
try {
|
|
2593
|
+
const keyHandle = __addDisposableResource(env_5, this.vm.newString(name), false);
|
|
2594
|
+
return this.vm.getProp(this, keyHandle);
|
|
2595
|
+
} catch (e_5) {
|
|
2596
|
+
env_5.error = e_5;
|
|
2597
|
+
env_5.hasError = true;
|
|
2598
|
+
} finally {
|
|
2599
|
+
__disposeResources(env_5);
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
const { ptr: namePtr } = this.vm._writeString(name);
|
|
2603
|
+
const resultPtr = this.vm._getExports().qjs_get_prop_string(this.ptr, namePtr);
|
|
2604
|
+
this.vm._getExports().wasm_free(namePtr);
|
|
2605
|
+
return new _JSValueHandle(this.vm, resultPtr);
|
|
2606
|
+
}
|
|
2607
|
+
/**
|
|
2608
|
+
* Set a property by name.
|
|
2609
|
+
*/
|
|
2610
|
+
setProp(name, value) {
|
|
2611
|
+
if (stringKeyNeedsValuePath(name)) {
|
|
2612
|
+
const env_6 = { stack: [], error: void 0, hasError: false };
|
|
2613
|
+
try {
|
|
2614
|
+
const keyHandle = __addDisposableResource(env_6, this.vm.newString(name), false);
|
|
2615
|
+
this.vm.setProp(this, keyHandle, value);
|
|
2616
|
+
return;
|
|
2617
|
+
} catch (e_6) {
|
|
2618
|
+
env_6.error = e_6;
|
|
2619
|
+
env_6.hasError = true;
|
|
2620
|
+
} finally {
|
|
2621
|
+
__disposeResources(env_6);
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
const { ptr: namePtr } = this.vm._writeString(name);
|
|
2625
|
+
this.vm._getExports().qjs_set_prop_string(this.ptr, namePtr, value.ptr);
|
|
2626
|
+
this.vm._getExports().wasm_free(namePtr);
|
|
2627
|
+
}
|
|
2628
|
+
/**
|
|
2629
|
+
* Define a property with explicit property descriptor flags.
|
|
2630
|
+
* Unlike `setProp`, this allows controlling `writable`, `enumerable`, and
|
|
2631
|
+
* `configurable` attributes, matching `Object.defineProperty()` semantics.
|
|
2632
|
+
* Accepts string or JSValueHandle as key (JSValueHandle keys support symbols).
|
|
2633
|
+
*
|
|
2634
|
+
* All flags default to `false` when not specified.
|
|
2635
|
+
*/
|
|
2636
|
+
defineProp(key, value, descriptor) {
|
|
2637
|
+
let flags = 0;
|
|
2638
|
+
if (descriptor?.configurable)
|
|
2639
|
+
flags |= 1;
|
|
2640
|
+
if (descriptor?.writable)
|
|
2641
|
+
flags |= 2;
|
|
2642
|
+
if (descriptor?.enumerable)
|
|
2643
|
+
flags |= 4;
|
|
2644
|
+
if (typeof key === "string") {
|
|
2645
|
+
if (stringKeyNeedsValuePath(key)) {
|
|
2646
|
+
const env_7 = { stack: [], error: void 0, hasError: false };
|
|
2647
|
+
try {
|
|
2648
|
+
const keyHandle = __addDisposableResource(env_7, this.vm.newString(key), false);
|
|
2649
|
+
this.vm._getExports().qjs_define_prop_value(this.ptr, keyHandle.ptr, value.ptr, flags);
|
|
2650
|
+
return;
|
|
2651
|
+
} catch (e_7) {
|
|
2652
|
+
env_7.error = e_7;
|
|
2653
|
+
env_7.hasError = true;
|
|
2654
|
+
} finally {
|
|
2655
|
+
__disposeResources(env_7);
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
const { ptr: namePtr } = this.vm._writeString(key);
|
|
2659
|
+
this.vm._getExports().qjs_define_prop_string(this.ptr, namePtr, value.ptr, flags);
|
|
2660
|
+
this.vm._getExports().wasm_free(namePtr);
|
|
2661
|
+
} else {
|
|
2662
|
+
this.vm._getExports().qjs_define_prop_value(this.ptr, key.ptr, value.ptr, flags);
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
/**
|
|
2666
|
+
* Extract the value as a number.
|
|
2667
|
+
*/
|
|
2668
|
+
toNumber() {
|
|
2669
|
+
return this.vm._getExports().qjs_get_float64(this.ptr);
|
|
2670
|
+
}
|
|
2671
|
+
/**
|
|
2672
|
+
* Extract the value as a BigInt.
|
|
2673
|
+
*/
|
|
2674
|
+
toBigInt() {
|
|
2675
|
+
const e = this.vm._getExports();
|
|
2676
|
+
const loPtr = e.wasm_malloc(4);
|
|
2677
|
+
const hiPtr = e.wasm_malloc(4);
|
|
2678
|
+
const ret = e.qjs_get_big_int64(this.ptr, loPtr, hiPtr);
|
|
2679
|
+
if (ret !== 0) {
|
|
2680
|
+
e.wasm_free(loPtr);
|
|
2681
|
+
e.wasm_free(hiPtr);
|
|
2682
|
+
throw new Error("Failed to convert value to BigInt");
|
|
2683
|
+
}
|
|
2684
|
+
const view = new DataView(e.memory.buffer);
|
|
2685
|
+
const lo = view.getUint32(loPtr, true);
|
|
2686
|
+
const hi = view.getInt32(hiPtr, true);
|
|
2687
|
+
e.wasm_free(loPtr);
|
|
2688
|
+
e.wasm_free(hiPtr);
|
|
2689
|
+
return BigInt(hi) << 32n | BigInt(lo);
|
|
2690
|
+
}
|
|
2691
|
+
/**
|
|
2692
|
+
* Extract the value as an ArrayBuffer (copies from WASM memory).
|
|
2693
|
+
* Works on ArrayBuffer values. For typed arrays, gets the underlying buffer.
|
|
2694
|
+
*/
|
|
2695
|
+
toArrayBuffer() {
|
|
2696
|
+
const e = this.vm._getExports();
|
|
2697
|
+
const lenOutPtr = e.wasm_malloc(4);
|
|
2698
|
+
if (e.qjs_is_array_buffer(this.ptr)) {
|
|
2699
|
+
const dataPtr = e.qjs_get_array_buffer(this.ptr, lenOutPtr);
|
|
2700
|
+
if (dataPtr === 0) {
|
|
2701
|
+
e.wasm_free(lenOutPtr);
|
|
2702
|
+
throw new Error("Failed to get ArrayBuffer data");
|
|
2703
|
+
}
|
|
2704
|
+
const view2 = new DataView(e.memory.buffer);
|
|
2705
|
+
const len = view2.getUint32(lenOutPtr, true);
|
|
2706
|
+
e.wasm_free(lenOutPtr);
|
|
2707
|
+
return new Uint8Array(e.memory.buffer, dataPtr, len).slice().buffer;
|
|
2708
|
+
}
|
|
2709
|
+
e.wasm_free(lenOutPtr);
|
|
2710
|
+
const byteOffsetPtr = e.wasm_malloc(4);
|
|
2711
|
+
const byteLengthPtr = e.wasm_malloc(4);
|
|
2712
|
+
const bytesPerElemPtr = e.wasm_malloc(4);
|
|
2713
|
+
const abPtr = e.qjs_get_typed_array_buffer(this.ptr, byteOffsetPtr, byteLengthPtr, bytesPerElemPtr);
|
|
2714
|
+
const abHandle = new _JSValueHandle(this.vm, abPtr);
|
|
2715
|
+
if (this.vm._getExports().qjs_is_exception(abHandle.ptr) !== 0) {
|
|
2716
|
+
abHandle.dispose();
|
|
2717
|
+
e.wasm_free(byteOffsetPtr);
|
|
2718
|
+
e.wasm_free(byteLengthPtr);
|
|
2719
|
+
e.wasm_free(bytesPerElemPtr);
|
|
2720
|
+
throw new Error("Value is not an ArrayBuffer or typed array");
|
|
2721
|
+
}
|
|
2722
|
+
const view = new DataView(e.memory.buffer);
|
|
2723
|
+
const byteOffset = view.getUint32(byteOffsetPtr, true);
|
|
2724
|
+
const byteLength = view.getUint32(byteLengthPtr, true);
|
|
2725
|
+
e.wasm_free(byteOffsetPtr);
|
|
2726
|
+
e.wasm_free(byteLengthPtr);
|
|
2727
|
+
e.wasm_free(bytesPerElemPtr);
|
|
2728
|
+
const abLenPtr = e.wasm_malloc(4);
|
|
2729
|
+
const abDataPtr = e.qjs_get_array_buffer(abHandle.ptr, abLenPtr);
|
|
2730
|
+
e.wasm_free(abLenPtr);
|
|
2731
|
+
abHandle.dispose();
|
|
2732
|
+
if (abDataPtr === 0) {
|
|
2733
|
+
throw new Error("Failed to get ArrayBuffer data from typed array");
|
|
2734
|
+
}
|
|
2735
|
+
return new Uint8Array(e.memory.buffer, abDataPtr + byteOffset, byteLength).slice().buffer;
|
|
2736
|
+
}
|
|
2737
|
+
/**
|
|
2738
|
+
* Extract the value as a Uint8Array (copies from WASM memory).
|
|
2739
|
+
* Works on Uint8Array, ArrayBuffer, and other typed array values.
|
|
2740
|
+
*/
|
|
2741
|
+
toUint8Array() {
|
|
2742
|
+
return new Uint8Array(this.toArrayBuffer());
|
|
2743
|
+
}
|
|
2744
|
+
/**
|
|
2745
|
+
* Extract the value as a string. Works on any value.
|
|
2746
|
+
*
|
|
2747
|
+
* For values that are not already strings this performs a JavaScript
|
|
2748
|
+
* string conversion, which **executes guest code**: `toString()` /
|
|
2749
|
+
* `valueOf()` / `Symbol.toPrimitive` on the value or its prototype chain,
|
|
2750
|
+
* and proxy traps. Guard with `isString` when the caller must not run guest
|
|
2751
|
+
* code, and call a captured intrinsic (e.g. `URL.prototype.toString` via
|
|
2752
|
+
* `vm.callFunction`) when a specific conversion is wanted.
|
|
2753
|
+
*/
|
|
2754
|
+
toString() {
|
|
2755
|
+
const e = this.vm._getExports();
|
|
2756
|
+
const lenPtr = e.wasm_malloc(4);
|
|
2757
|
+
if (lenPtr === 0)
|
|
2758
|
+
throw new Error("wasm_malloc failed");
|
|
2759
|
+
try {
|
|
2760
|
+
const cstrPtr = e.qjs_get_string_len(this.ptr, lenPtr);
|
|
2761
|
+
if (cstrPtr === 0)
|
|
2762
|
+
return "<null>";
|
|
2763
|
+
const len = new DataView(e.memory.buffer).getUint32(lenPtr, true);
|
|
2764
|
+
const bytes = new Uint8Array(e.memory.buffer, cstrPtr, len);
|
|
2765
|
+
const str = decodeWtf8(bytes);
|
|
2766
|
+
e.qjs_free_cstring(cstrPtr);
|
|
2767
|
+
return str;
|
|
2768
|
+
} finally {
|
|
2769
|
+
e.wasm_free(lenPtr);
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
/**
|
|
2773
|
+
* Use this handle, then dispose it. Returns the callback's return value.
|
|
2774
|
+
*/
|
|
2775
|
+
consume(fn) {
|
|
2776
|
+
try {
|
|
2777
|
+
return fn(this);
|
|
2778
|
+
} finally {
|
|
2779
|
+
this.dispose();
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
/**
|
|
2783
|
+
* Duplicate this handle (increment refcount).
|
|
2784
|
+
*/
|
|
2785
|
+
dup() {
|
|
2786
|
+
return new _JSValueHandle(this.vm, this.vm._getExports().qjs_dup_value(this.ptr));
|
|
2787
|
+
}
|
|
2788
|
+
/**
|
|
2789
|
+
* Dispose this handle, freeing the heap-allocated JSValue.
|
|
2790
|
+
* Safe to call after the VM has been disposed (becomes a no-op).
|
|
2791
|
+
*/
|
|
2792
|
+
dispose() {
|
|
2793
|
+
if (this.singleton || this.borrowed)
|
|
2794
|
+
return;
|
|
2795
|
+
if (!this.disposed_) {
|
|
2796
|
+
this.disposed_ = true;
|
|
2797
|
+
this._onDispose?.();
|
|
2798
|
+
this._onDispose = void 0;
|
|
2799
|
+
const exports = this.vm._getExports();
|
|
2800
|
+
if (exports) {
|
|
2801
|
+
exports.qjs_free_value(this.ptr);
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
/**
|
|
2806
|
+
* Support for `using` declarations (Explicit Resource Management).
|
|
2807
|
+
* Automatically disposes the handle when it goes out of scope.
|
|
2808
|
+
*
|
|
2809
|
+
* ```typescript
|
|
2810
|
+
* using result = vm.evalCode('1 + 2');
|
|
2811
|
+
* console.log(result.toNumber()); // 3
|
|
2812
|
+
* // result is automatically disposed here
|
|
2813
|
+
* ```
|
|
2814
|
+
*/
|
|
2815
|
+
[Symbol.dispose]() {
|
|
2816
|
+
this.dispose();
|
|
2817
|
+
}
|
|
2818
|
+
};
|
|
2819
|
+
|
|
2820
|
+
// ../../node_modules/@earendil-works/pi-codemode/dist/runtime/prelude-source.js
|
|
2821
|
+
var MAX_STORE_VALUE_CHARS = 256 * 1024;
|
|
2822
|
+
var MAX_STORE_TOTAL_CHARS = 1024 * 1024;
|
|
2823
|
+
var MAX_OUTPUT_CHARS = 16 * 1024 * 1024;
|
|
2824
|
+
var MAX_OUTPUT_ITEMS = 1e5;
|
|
2825
|
+
var IMAGE_HELPER_EXPECTS = "image expects a non-empty image URL string, an object with image_url, or a raw MCP image block";
|
|
2826
|
+
var PRELUDE_SOURCE = `(function (bridge, toolsJson, globalsJson, storeJson) {
|
|
2827
|
+
"use strict";
|
|
2828
|
+
const stringify = JSON.stringify;
|
|
2829
|
+
const parse = JSON.parse;
|
|
2830
|
+
const promiseThen = Promise.prototype.then;
|
|
2831
|
+
const ErrorCtor = Error;
|
|
2832
|
+
const TypeErrorCtor = TypeError;
|
|
2833
|
+
const RangeErrorCtor = RangeError;
|
|
2834
|
+
const pending = new Map();
|
|
2835
|
+
let nextId = 1;
|
|
2836
|
+
let finished = false;
|
|
2837
|
+
// Thrown by exit() to unwind the script after it already reported success.
|
|
2838
|
+
const EXIT = Object.freeze({});
|
|
2839
|
+
|
|
2840
|
+
function done(ok, payload, writes) {
|
|
2841
|
+
if (finished) return;
|
|
2842
|
+
finished = true;
|
|
2843
|
+
bridge("done", ok, payload, writes);
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
function serialize(value) {
|
|
2847
|
+
return value === undefined ? undefined : stringify(value);
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
// QuickJS stacks list frames only. Prefix "Name: message" like V8 so the
|
|
2851
|
+
// text reads the same as a Node error, and drop this prelude's frames.
|
|
2852
|
+
function errorText(error) {
|
|
2853
|
+
const head = error.message ? error.name + ": " + error.message : String(error.name);
|
|
2854
|
+
const frames =
|
|
2855
|
+
typeof error.stack === "string"
|
|
2856
|
+
? error.stack.split("\\n").filter((line) => line.trim() && !line.includes("codemode-prelude.js"))
|
|
2857
|
+
: [];
|
|
2858
|
+
return [head, ...frames].join("\\n");
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
function format(value) {
|
|
2862
|
+
if (typeof value === "string") return value;
|
|
2863
|
+
if (value instanceof ErrorCtor) return errorText(value);
|
|
2864
|
+
try {
|
|
2865
|
+
const json = stringify(value);
|
|
2866
|
+
return json === undefined ? String(value) : json;
|
|
2867
|
+
} catch {
|
|
2868
|
+
return String(value);
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
function describeError(error) {
|
|
2873
|
+
if (error instanceof ErrorCtor) {
|
|
2874
|
+
return stringify({ name: error.name, message: error.message, stack: errorText(error) });
|
|
2875
|
+
}
|
|
2876
|
+
return stringify({ message: format(error) });
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2879
|
+
function caller(kind, name, spread) {
|
|
2880
|
+
return (...args) =>
|
|
2881
|
+
new Promise((resolve, reject) => {
|
|
2882
|
+
let json;
|
|
2883
|
+
try {
|
|
2884
|
+
json = serialize(spread ? args : args[0]);
|
|
2885
|
+
} catch (error) {
|
|
2886
|
+
reject(error);
|
|
2887
|
+
return;
|
|
2888
|
+
}
|
|
2889
|
+
const id = nextId++;
|
|
2890
|
+
pending.set(id, { resolve, reject });
|
|
2891
|
+
bridge(kind, id, name, json);
|
|
2892
|
+
});
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
const tools = Object.create(null);
|
|
2896
|
+
const allTools = [];
|
|
2897
|
+
for (const { name, jsName, description } of parse(toolsJson)) {
|
|
2898
|
+
const fn = caller("call", name);
|
|
2899
|
+
// The first tool wins when two names normalize to the same identifier.
|
|
2900
|
+
if (!(jsName in tools)) {
|
|
2901
|
+
tools[jsName] = fn;
|
|
2902
|
+
allTools.push(Object.freeze({ name: jsName, description }));
|
|
2903
|
+
}
|
|
2904
|
+
if (!(name in tools)) tools[name] = fn;
|
|
2905
|
+
}
|
|
2906
|
+
Object.freeze(tools);
|
|
2907
|
+
Object.freeze(allTools);
|
|
2908
|
+
|
|
2909
|
+
// Reading a member that does not exist throws an error that names the close matches, instead of
|
|
2910
|
+
// a later "not a function". \`in\` checks still work.
|
|
2911
|
+
const comparable = (name) => name.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2912
|
+
function guard(target, label, names, hint) {
|
|
2913
|
+
return new Proxy(target, {
|
|
2914
|
+
get(object, property, receiver) {
|
|
2915
|
+
if (typeof property !== "string" || property in object || property in Object.prototype || property === "then" || property === "toJSON") {
|
|
2916
|
+
return Reflect.get(object, property, receiver);
|
|
2917
|
+
}
|
|
2918
|
+
const wanted = comparable(property);
|
|
2919
|
+
const exact = names.filter((name) => comparable(name) === wanted);
|
|
2920
|
+
const close = exact.length > 0 ? exact : names.filter((name) => wanted && (comparable(name).includes(wanted) || wanted.includes(comparable(name))));
|
|
2921
|
+
let message = label + "." + property + " does not exist.";
|
|
2922
|
+
if (close.length > 0) message += " Did you mean " + close.slice(0, 5).map((name) => label + "." + name).join(", ") + "?";
|
|
2923
|
+
else if (names.length <= 20) message += " Available: " + names.join(", ") + ".";
|
|
2924
|
+
if (hint) message += " " + hint;
|
|
2925
|
+
message += ' Check for a member with "' + property + '" in ' + label + ".";
|
|
2926
|
+
throw new TypeErrorCtor(message);
|
|
2927
|
+
},
|
|
2928
|
+
});
|
|
2929
|
+
}
|
|
2930
|
+
const toolsProxy = guard(
|
|
2931
|
+
tools,
|
|
2932
|
+
"tools",
|
|
2933
|
+
allTools.map((tool) => tool.name),
|
|
2934
|
+
"ALL_TOOLS lists every tool; searchTools(query) finds tools by topic.",
|
|
2935
|
+
);
|
|
2936
|
+
|
|
2937
|
+
const namespaces = new Map();
|
|
2938
|
+
for (const { name, spread } of parse(globalsJson)) {
|
|
2939
|
+
const fn = caller("global", name, spread);
|
|
2940
|
+
const dot = name.indexOf(".");
|
|
2941
|
+
if (dot === -1) {
|
|
2942
|
+
Object.defineProperty(globalThis, name, { value: fn, enumerable: true });
|
|
2943
|
+
continue;
|
|
2944
|
+
}
|
|
2945
|
+
const namespace = name.slice(0, dot);
|
|
2946
|
+
if (!namespaces.has(namespace)) namespaces.set(namespace, Object.create(null));
|
|
2947
|
+
namespaces.get(namespace)[name.slice(dot + 1)] = fn;
|
|
2948
|
+
}
|
|
2949
|
+
for (const [namespace, members] of namespaces) {
|
|
2950
|
+
Object.freeze(members);
|
|
2951
|
+
const value = guard(members, namespace, Object.keys(members));
|
|
2952
|
+
Object.defineProperty(globalThis, namespace, { value, enumerable: true });
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2955
|
+
// key -> JSON text. Sizes count key and JSON characters.
|
|
2956
|
+
const stored = new Map(Object.entries(parse(storeJson)));
|
|
2957
|
+
const writes = new Map();
|
|
2958
|
+
let storedChars = 0;
|
|
2959
|
+
for (const [key, json] of stored) storedChars += key.length + json.length;
|
|
2960
|
+
|
|
2961
|
+
const STORE_HINT =
|
|
2962
|
+
"store() is for small state such as IDs or summaries. Show images with image(), keep large data in variables, or write it to a file with a tool.";
|
|
2963
|
+
|
|
2964
|
+
function checkKey(name, key) {
|
|
2965
|
+
if (typeof key !== "string") throw new TypeError(name + "() key must be a string");
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
function store(key, value) {
|
|
2969
|
+
checkKey("store", key);
|
|
2970
|
+
const previous = stored.has(key) ? key.length + stored.get(key).length : 0;
|
|
2971
|
+
if (value === undefined) {
|
|
2972
|
+
stored.delete(key);
|
|
2973
|
+
storedChars -= previous;
|
|
2974
|
+
writes.set(key, undefined);
|
|
2975
|
+
return;
|
|
2976
|
+
}
|
|
2977
|
+
let json;
|
|
2978
|
+
try {
|
|
2979
|
+
json = stringify(value);
|
|
2980
|
+
} catch (error) {
|
|
2981
|
+
throw new TypeError("store(" + stringify(key) + ") value is not JSON-serializable: " + format(error));
|
|
2982
|
+
}
|
|
2983
|
+
if (json === undefined) {
|
|
2984
|
+
throw new TypeError("store(" + stringify(key) + ") value is not JSON-serializable");
|
|
2985
|
+
}
|
|
2986
|
+
if (json.length > ${MAX_STORE_VALUE_CHARS}) {
|
|
2987
|
+
throw new RangeError(
|
|
2988
|
+
"store(" + stringify(key) + ") value has " + json.length + " characters of JSON, more than the limit of ${MAX_STORE_VALUE_CHARS}. " +
|
|
2989
|
+
STORE_HINT,
|
|
2990
|
+
);
|
|
2991
|
+
}
|
|
2992
|
+
const next = storedChars - previous + key.length + json.length;
|
|
2993
|
+
if (next > ${MAX_STORE_TOTAL_CHARS}) {
|
|
2994
|
+
throw new RangeError(
|
|
2995
|
+
"store is full: stored values would exceed ${MAX_STORE_TOTAL_CHARS} characters of JSON. Delete keys with store(key, undefined). " +
|
|
2996
|
+
STORE_HINT,
|
|
2997
|
+
);
|
|
2998
|
+
}
|
|
2999
|
+
stored.set(key, json);
|
|
3000
|
+
storedChars = next;
|
|
3001
|
+
writes.set(key, json);
|
|
3002
|
+
}
|
|
3003
|
+
|
|
3004
|
+
function load(key) {
|
|
3005
|
+
checkKey("load", key);
|
|
3006
|
+
const json = stored.get(key);
|
|
3007
|
+
return json === undefined ? undefined : parse(json);
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
function serializeWrites() {
|
|
3011
|
+
const entries = [];
|
|
3012
|
+
for (const [key, json] of writes) entries.push(json === undefined ? [key] : [key, json]);
|
|
3013
|
+
return stringify(entries);
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
Object.defineProperty(globalThis, "store", { value: store, enumerable: true });
|
|
3017
|
+
Object.defineProperty(globalThis, "load", { value: load, enumerable: true });
|
|
3018
|
+
|
|
3019
|
+
let outputChars = 0;
|
|
3020
|
+
let outputItems = 0;
|
|
3021
|
+
|
|
3022
|
+
// Past the output limits the script fails: done() reports the error, so catching it does not
|
|
3023
|
+
// resume output, and the host ends the script.
|
|
3024
|
+
function output(kind, data, mimeType) {
|
|
3025
|
+
if (finished) return;
|
|
3026
|
+
outputChars += data.length;
|
|
3027
|
+
outputItems++;
|
|
3028
|
+
if (outputChars > ${MAX_OUTPUT_CHARS} || outputItems > ${MAX_OUTPUT_ITEMS}) {
|
|
3029
|
+
const error = new RangeErrorCtor(
|
|
3030
|
+
"script output exceeded the limit of ${MAX_OUTPUT_CHARS} characters or ${MAX_OUTPUT_ITEMS} text(), image(), and console calls. " +
|
|
3031
|
+
"Print a summary instead, or write large data to a file with a tool.",
|
|
3032
|
+
);
|
|
3033
|
+
done(false, describeError(error));
|
|
3034
|
+
throw error;
|
|
3035
|
+
}
|
|
3036
|
+
bridge("output", kind, data, mimeType);
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3039
|
+
// Primitives become their string form, everything else JSON.
|
|
3040
|
+
function outputText(value) {
|
|
3041
|
+
if (value === undefined || value === null || typeof value !== "object" && typeof value !== "function") {
|
|
3042
|
+
return String(value);
|
|
3043
|
+
}
|
|
3044
|
+
const json = stringify(value);
|
|
3045
|
+
return json === undefined ? String(value) : json;
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
function text(value) {
|
|
3049
|
+
let rendered;
|
|
3050
|
+
try {
|
|
3051
|
+
rendered = outputText(value);
|
|
3052
|
+
} catch (error) {
|
|
3053
|
+
throw new TypeErrorCtor(error instanceof ErrorCtor ? error.message : String(error));
|
|
3054
|
+
}
|
|
3055
|
+
output("text", rendered);
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
function imageUrl(value) {
|
|
3059
|
+
if (typeof value === "string") return value;
|
|
3060
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
3061
|
+
throw new TypeErrorCtor(${JSON.stringify(IMAGE_HELPER_EXPECTS)});
|
|
3062
|
+
}
|
|
3063
|
+
if (value.image_url !== undefined) {
|
|
3064
|
+
if (typeof value.image_url !== "string") throw new TypeErrorCtor(${JSON.stringify(IMAGE_HELPER_EXPECTS)});
|
|
3065
|
+
return value.image_url;
|
|
3066
|
+
}
|
|
3067
|
+
if (typeof value.type !== "string") throw new TypeErrorCtor(${JSON.stringify(IMAGE_HELPER_EXPECTS)});
|
|
3068
|
+
if (value.type !== "image") {
|
|
3069
|
+
throw new TypeErrorCtor('image only accepts MCP image blocks, got "' + value.type + '"');
|
|
3070
|
+
}
|
|
3071
|
+
if (typeof value.data !== "string" || value.data === "") throw new TypeErrorCtor("image expected MCP image data");
|
|
3072
|
+
if (value.data.toLowerCase().startsWith("data:")) return value.data;
|
|
3073
|
+
return "data:;base64," + value.data;
|
|
3074
|
+
}
|
|
3075
|
+
|
|
3076
|
+
// Base64 of the signatures of the formats providers accept inline (PNG, JPEG except
|
|
3077
|
+
// JPEG-LS, GIF, "RIFF....WEBP"). Signatures start at byte 0, so their encodings are prefixes.
|
|
3078
|
+
const IMAGE_SIGNATURES = [
|
|
3079
|
+
["image/png", /^iVBORw0KGg/],
|
|
3080
|
+
["image/jpeg", /^[/]9j[/](?!9)/],
|
|
3081
|
+
["image/gif", /^R0lGOD[dl]h/],
|
|
3082
|
+
["image/webp", /^UklG.{8}RUJQ/],
|
|
3083
|
+
];
|
|
3084
|
+
|
|
3085
|
+
function image(value) {
|
|
3086
|
+
const url = imageUrl(value);
|
|
3087
|
+
if (url === "") throw new TypeErrorCtor(${JSON.stringify(IMAGE_HELPER_EXPECTS)});
|
|
3088
|
+
const colon = url.indexOf(":");
|
|
3089
|
+
const scheme = colon === -1 ? "" : url.slice(0, colon).toLowerCase();
|
|
3090
|
+
if (scheme === "http" || scheme === "https") {
|
|
3091
|
+
throw new TypeErrorCtor("remote image URLs are not supported in tool outputs. Pass a base64 data URI instead");
|
|
3092
|
+
}
|
|
3093
|
+
const comma = url.indexOf(",");
|
|
3094
|
+
const header = comma === -1 ? [] : url.slice(colon + 1, comma).split(";");
|
|
3095
|
+
if (scheme !== "data" || comma === -1 || header.slice(1).every((part) => part.toLowerCase() !== "base64")) {
|
|
3096
|
+
throw new TypeErrorCtor("invalid image output. Pass a base64 data URI instead");
|
|
3097
|
+
}
|
|
3098
|
+
// Providers reject the whole request on a bad image, and a persisted image block would be
|
|
3099
|
+
// resent on every later turn. Line breaks from wrapped base64 are dropped. The declared type
|
|
3100
|
+
// is ignored in favor of the detected one, as providers also reject mismatches.
|
|
3101
|
+
const data = url.slice(comma + 1).replace(/\\s+/g, "");
|
|
3102
|
+
if (data.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(data)) {
|
|
3103
|
+
throw new TypeErrorCtor("invalid image output. The image data is not valid base64 (truncated or corrupted?)");
|
|
3104
|
+
}
|
|
3105
|
+
const head = data.slice(0, 16);
|
|
3106
|
+
const signature = IMAGE_SIGNATURES.find(([, pattern]) => pattern.test(head));
|
|
3107
|
+
if (!signature) {
|
|
3108
|
+
throw new TypeErrorCtor("invalid image output. The image data is not a PNG, JPEG, GIF, or WebP image");
|
|
3109
|
+
}
|
|
3110
|
+
output("image", data, signature[0]);
|
|
3111
|
+
}
|
|
3112
|
+
|
|
3113
|
+
function exit() {
|
|
3114
|
+
let writesJson;
|
|
3115
|
+
try {
|
|
3116
|
+
writesJson = serializeWrites();
|
|
3117
|
+
} catch (error) {
|
|
3118
|
+
done(false, describeError(error));
|
|
3119
|
+
throw EXIT;
|
|
3120
|
+
}
|
|
3121
|
+
done(true, undefined, writesJson);
|
|
3122
|
+
throw EXIT;
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3125
|
+
const console = {};
|
|
3126
|
+
for (const level of ["log", "info", "warn", "error", "debug"]) {
|
|
3127
|
+
console[level] = (...args) => {
|
|
3128
|
+
output("text", args.map(format).join(" "));
|
|
3129
|
+
};
|
|
3130
|
+
}
|
|
3131
|
+
Object.freeze(console);
|
|
3132
|
+
|
|
3133
|
+
Object.defineProperty(globalThis, "tools", { value: toolsProxy, enumerable: true });
|
|
3134
|
+
Object.defineProperty(globalThis, "ALL_TOOLS", { value: allTools, enumerable: true });
|
|
3135
|
+
Object.defineProperty(globalThis, "console", { value: console, enumerable: true });
|
|
3136
|
+
Object.defineProperty(globalThis, "text", { value: text, enumerable: true });
|
|
3137
|
+
Object.defineProperty(globalThis, "image", { value: image, enumerable: true });
|
|
3138
|
+
Object.defineProperty(globalThis, "exit", { value: exit, enumerable: true });
|
|
3139
|
+
|
|
3140
|
+
return {
|
|
3141
|
+
settle(id, ok, payload) {
|
|
3142
|
+
const entry = pending.get(id);
|
|
3143
|
+
if (!entry) return;
|
|
3144
|
+
pending.delete(id);
|
|
3145
|
+
if (!ok) {
|
|
3146
|
+
entry.reject(new ErrorCtor(payload));
|
|
3147
|
+
return;
|
|
3148
|
+
}
|
|
3149
|
+
let value;
|
|
3150
|
+
try {
|
|
3151
|
+
value = payload === undefined ? undefined : parse(payload);
|
|
3152
|
+
} catch (error) {
|
|
3153
|
+
entry.reject(error);
|
|
3154
|
+
return;
|
|
3155
|
+
}
|
|
3156
|
+
entry.resolve(value);
|
|
3157
|
+
},
|
|
3158
|
+
run(fn) {
|
|
3159
|
+
let promise;
|
|
3160
|
+
try {
|
|
3161
|
+
promise = fn(toolsProxy, console);
|
|
3162
|
+
} catch (error) {
|
|
3163
|
+
done(false, describeError(error));
|
|
3164
|
+
return;
|
|
3165
|
+
}
|
|
3166
|
+
promiseThen.call(
|
|
3167
|
+
promise,
|
|
3168
|
+
(value) => {
|
|
3169
|
+
let json;
|
|
3170
|
+
try {
|
|
3171
|
+
json = serialize(value);
|
|
3172
|
+
} catch (error) {
|
|
3173
|
+
done(false, describeError(error));
|
|
3174
|
+
return;
|
|
3175
|
+
}
|
|
3176
|
+
done(true, json, serializeWrites());
|
|
3177
|
+
},
|
|
3178
|
+
(error) => {
|
|
3179
|
+
done(false, describeError(error));
|
|
3180
|
+
},
|
|
3181
|
+
);
|
|
3182
|
+
},
|
|
3183
|
+
stalled() {
|
|
3184
|
+
if (finished || pending.size > 0) return false;
|
|
3185
|
+
done(
|
|
3186
|
+
false,
|
|
3187
|
+
stringify({
|
|
3188
|
+
name: "Error",
|
|
3189
|
+
message:
|
|
3190
|
+
"The script is waiting on a promise that can never settle: no tool call is pending, and timers do not exist here.",
|
|
3191
|
+
}),
|
|
3192
|
+
);
|
|
3193
|
+
return true;
|
|
3194
|
+
},
|
|
3195
|
+
};
|
|
3196
|
+
})`;
|
|
3197
|
+
|
|
3198
|
+
// ../../node_modules/@earendil-works/pi-codemode/dist/runtime/protocol.js
|
|
3199
|
+
function isHostToWorkerMessage(value) {
|
|
3200
|
+
return typeof value === "object" && value !== null && value.type === "result";
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
// ../../node_modules/@earendil-works/pi-codemode/dist/runtime/worker.js
|
|
3204
|
+
function post(message) {
|
|
3205
|
+
parentPort?.postMessage(message);
|
|
3206
|
+
}
|
|
3207
|
+
function crash(error) {
|
|
3208
|
+
post({ type: "crash", message: error instanceof Error ? `${error.name}: ${error.message}` : String(error) });
|
|
3209
|
+
}
|
|
3210
|
+
function discardOutput(memory) {
|
|
3211
|
+
return {
|
|
3212
|
+
fd_write(_fd, iovsPtr, iovsLen, nwrittenPtr) {
|
|
3213
|
+
const view = new DataView(memory.buffer);
|
|
3214
|
+
let written = 0;
|
|
3215
|
+
for (let i = 0; i < iovsLen; i++) {
|
|
3216
|
+
written += view.getUint32(iovsPtr + i * 8 + 4, true);
|
|
3217
|
+
}
|
|
3218
|
+
view.setUint32(nwrittenPtr, written, true);
|
|
3219
|
+
return 0;
|
|
3220
|
+
}
|
|
3221
|
+
};
|
|
3222
|
+
}
|
|
3223
|
+
function describeException(error) {
|
|
3224
|
+
const head = error.message ? `${error.name}: ${error.message}` : error.name;
|
|
3225
|
+
const stack = error.stack?.trimEnd();
|
|
3226
|
+
return JSON.stringify({ name: error.name, message: error.message, stack: stack ? `${head}
|
|
3227
|
+
${stack}` : head });
|
|
3228
|
+
}
|
|
3229
|
+
async function main(data) {
|
|
3230
|
+
const interrupt = new Int32Array(data.interrupt);
|
|
3231
|
+
const vm = await QuickJS.create({
|
|
3232
|
+
wasm: data.wasm,
|
|
3233
|
+
memoryLimit: data.memoryLimitBytes,
|
|
3234
|
+
// Without a guard, deep recursion overflows the wasm stack and traps instead of throwing a
|
|
3235
|
+
// catchable RangeError.
|
|
3236
|
+
maxStackSize: MAX_STACK_SIZE,
|
|
3237
|
+
interruptHandler: () => Atomics.load(interrupt, 0) !== 0,
|
|
3238
|
+
wasi: discardOutput
|
|
3239
|
+
});
|
|
3240
|
+
const bridge = vm.newFunction("bridge", (kind, a, b, c) => {
|
|
3241
|
+
switch (kind.toString()) {
|
|
3242
|
+
case "call":
|
|
3243
|
+
case "global":
|
|
3244
|
+
post({
|
|
3245
|
+
type: "call",
|
|
3246
|
+
id: a.toNumber(),
|
|
3247
|
+
target: kind.toString() === "call" ? "tool" : "global",
|
|
3248
|
+
name: b.toString(),
|
|
3249
|
+
args: c === void 0 || c.isUndefined ? void 0 : c.toString()
|
|
3250
|
+
});
|
|
3251
|
+
break;
|
|
3252
|
+
case "output":
|
|
3253
|
+
post({
|
|
3254
|
+
type: "output",
|
|
3255
|
+
item: a.toString() === "image" ? { type: "image", data: b.toString(), mimeType: c.toString() } : { type: "text", text: b.toString() }
|
|
3256
|
+
});
|
|
3257
|
+
break;
|
|
3258
|
+
case "done":
|
|
3259
|
+
if (a.toBoolean()) {
|
|
3260
|
+
post({
|
|
3261
|
+
type: "done",
|
|
3262
|
+
ok: true,
|
|
3263
|
+
value: b === void 0 || b.isUndefined ? void 0 : b.toString(),
|
|
3264
|
+
writes: c.toString()
|
|
3265
|
+
});
|
|
3266
|
+
} else {
|
|
3267
|
+
post({ type: "done", ok: false, error: b.toString() });
|
|
3268
|
+
}
|
|
3269
|
+
break;
|
|
3270
|
+
}
|
|
3271
|
+
return vm.undefined;
|
|
3272
|
+
});
|
|
3273
|
+
const api = vm.withScope((scope) => scope.escape(vm.callFunction(vm.evalCode(PRELUDE_SOURCE, "codemode-prelude.js"), vm.undefined, bridge, vm.newString(JSON.stringify(data.tools)), vm.newString(JSON.stringify(data.globals)), vm.newString(JSON.stringify(data.store)))));
|
|
3274
|
+
const settle = api.getProp("settle");
|
|
3275
|
+
const run = api.getProp("run");
|
|
3276
|
+
const stalled = api.getProp("stalled");
|
|
3277
|
+
const drain = () => {
|
|
3278
|
+
vm.executePendingJobs();
|
|
3279
|
+
vm.callFunction(stalled, api).dispose();
|
|
3280
|
+
};
|
|
3281
|
+
parentPort?.on("message", (message) => {
|
|
3282
|
+
if (!isHostToWorkerMessage(message))
|
|
3283
|
+
return;
|
|
3284
|
+
try {
|
|
3285
|
+
vm.withScope(() => {
|
|
3286
|
+
vm.callFunction(settle, api, vm.newNumber(message.id), message.ok ? vm.true : vm.false, message.payload === void 0 ? vm.undefined : vm.newString(message.payload));
|
|
3287
|
+
});
|
|
3288
|
+
drain();
|
|
3289
|
+
} catch (error) {
|
|
3290
|
+
crash(error);
|
|
3291
|
+
}
|
|
3292
|
+
});
|
|
3293
|
+
let fn;
|
|
3294
|
+
try {
|
|
3295
|
+
fn = vm.evalCode(`(async (tools, console) => {${data.code}
|
|
3296
|
+
})`, "codemode.js");
|
|
3297
|
+
} catch (error) {
|
|
3298
|
+
if (!(error instanceof JSException))
|
|
3299
|
+
throw error;
|
|
3300
|
+
post({ type: "done", ok: false, error: describeException(error) });
|
|
3301
|
+
return;
|
|
3302
|
+
}
|
|
3303
|
+
vm.callFunction(run, api, fn).dispose();
|
|
3304
|
+
fn.dispose();
|
|
3305
|
+
drain();
|
|
3306
|
+
}
|
|
3307
|
+
if (parentPort) {
|
|
3308
|
+
main(workerData).catch(crash);
|
|
3309
|
+
}
|