@ebowwa/sandbox 0.1.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/dist/compilers/index.d.ts +24 -0
- package/dist/compilers/index.d.ts.map +1 -0
- package/dist/compilers/index.js +42 -0
- package/dist/compilers/index.js.map +1 -0
- package/dist/compilers/javascript.d.ts +117 -0
- package/dist/compilers/javascript.d.ts.map +1 -0
- package/dist/compilers/javascript.js +462 -0
- package/dist/compilers/javascript.js.map +1 -0
- package/dist/compilers/python.d.ts +140 -0
- package/dist/compilers/python.d.ts.map +1 -0
- package/dist/compilers/python.js +650 -0
- package/dist/compilers/python.js.map +1 -0
- package/dist/compilers/typescript.d.ts +99 -0
- package/dist/compilers/typescript.d.ts.map +1 -0
- package/dist/compilers/typescript.js +323 -0
- package/dist/compilers/typescript.js.map +1 -0
- package/dist/core/cell.d.ts +160 -0
- package/dist/core/cell.d.ts.map +1 -0
- package/dist/core/cell.js +319 -0
- package/dist/core/cell.js.map +1 -0
- package/dist/core/compiler.d.ts +126 -0
- package/dist/core/compiler.d.ts.map +1 -0
- package/dist/core/compiler.js +123 -0
- package/dist/core/compiler.js.map +1 -0
- package/dist/core/index.d.ts +19 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +14 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/limits.d.ts +173 -0
- package/dist/core/limits.d.ts.map +1 -0
- package/dist/core/limits.js +440 -0
- package/dist/core/limits.js.map +1 -0
- package/dist/core/permissions.d.ts +103 -0
- package/dist/core/permissions.d.ts.map +1 -0
- package/dist/core/permissions.js +341 -0
- package/dist/core/permissions.js.map +1 -0
- package/dist/core/runtime.d.ts +127 -0
- package/dist/core/runtime.d.ts.map +1 -0
- package/dist/core/runtime.js +325 -0
- package/dist/core/runtime.js.map +1 -0
- package/dist/core/types.d.ts +380 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +67 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +145 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +279 -0
- package/dist/index.js.map +1 -0
- package/dist/multi/index.d.ts +9 -0
- package/dist/multi/index.d.ts.map +1 -0
- package/dist/multi/index.js +7 -0
- package/dist/multi/index.js.map +1 -0
- package/dist/multi/polyglot.d.ts +179 -0
- package/dist/multi/polyglot.d.ts.map +1 -0
- package/dist/multi/polyglot.js +319 -0
- package/dist/multi/polyglot.js.map +1 -0
- package/dist/runtimes/docker.d.ts +97 -0
- package/dist/runtimes/docker.d.ts.map +1 -0
- package/dist/runtimes/docker.js +368 -0
- package/dist/runtimes/docker.js.map +1 -0
- package/dist/runtimes/index.d.ts +11 -0
- package/dist/runtimes/index.d.ts.map +1 -0
- package/dist/runtimes/index.js +9 -0
- package/dist/runtimes/index.js.map +1 -0
- package/dist/runtimes/process.d.ts +47 -0
- package/dist/runtimes/process.d.ts.map +1 -0
- package/dist/runtimes/process.js +230 -0
- package/dist/runtimes/process.js.map +1 -0
- package/dist/session/index.d.ts +12 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +9 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/kernel.d.ts +199 -0
- package/dist/session/kernel.d.ts.map +1 -0
- package/dist/session/kernel.js +400 -0
- package/dist/session/kernel.js.map +1 -0
- package/dist/session/notebook.d.ts +168 -0
- package/dist/session/notebook.d.ts.map +1 -0
- package/dist/session/notebook.js +499 -0
- package/dist/session/notebook.js.map +1 -0
- package/dist/session/repl.d.ts +159 -0
- package/dist/session/repl.d.ts.map +1 -0
- package/dist/session/repl.js +409 -0
- package/dist/session/repl.js.map +1 -0
- package/package.json +142 -0
- package/src/compilers/index.ts +80 -0
- package/src/compilers/javascript.ts +571 -0
- package/src/compilers/python.ts +785 -0
- package/src/compilers/typescript.ts +442 -0
- package/src/core/cell.ts +439 -0
- package/src/core/compiler.ts +250 -0
- package/src/core/index.ts +123 -0
- package/src/core/limits.ts +508 -0
- package/src/core/permissions.ts +409 -0
- package/src/core/runtime.ts +499 -0
- package/src/core/types.ts +528 -0
- package/src/global.d.ts +59 -0
- package/src/index.ts +515 -0
- package/src/multi/index.ts +22 -0
- package/src/multi/polyglot.ts +461 -0
- package/src/runtimes/docker.ts +501 -0
- package/src/runtimes/index.ts +21 -0
- package/src/runtimes/process.ts +316 -0
- package/src/session/index.ts +41 -0
- package/src/session/kernel.ts +553 -0
- package/src/session/notebook.ts +635 -0
- package/src/session/repl.ts +521 -0
- package/src/wasm2wasm.d.ts +35 -0
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JavaScript Compiler for @ebowwa/sandbox
|
|
3
|
+
*
|
|
4
|
+
* Compiles JavaScript to WASM or provides sandboxed execution.
|
|
5
|
+
* Uses AssemblyScript for strict JS/TS-to-WASM compilation when possible,
|
|
6
|
+
* falls back to sandboxed QuickJS or native execution for dynamic JS.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
BaseCompiler,
|
|
11
|
+
type CompileResult,
|
|
12
|
+
type CompilerOptions,
|
|
13
|
+
type LanguageCapabilities,
|
|
14
|
+
} from "../core/compiler.js";
|
|
15
|
+
import type { Permissions, Limits } from "../core/types.js";
|
|
16
|
+
|
|
17
|
+
/** JavaScript compiler options */
|
|
18
|
+
export interface JavaScriptCompilerOptions extends CompilerOptions {
|
|
19
|
+
/** Target ES version */
|
|
20
|
+
target?: "es2015" | "es2016" | "es2017" | "es2018" | "es2019" | "es2020" | "es2021" | "es2022" | "esnext";
|
|
21
|
+
/** Module format */
|
|
22
|
+
module?: "esm" | "cjs" | "iife";
|
|
23
|
+
/** Enable strict mode */
|
|
24
|
+
strict?: boolean;
|
|
25
|
+
/** Use AssemblyScript for compilation (requires strict typing) */
|
|
26
|
+
useAssemblyScript?: boolean;
|
|
27
|
+
/** Sandbox mode when not using WASM */
|
|
28
|
+
sandboxMode?: "quickjs" | "isolated-vm" | "vm" | "none";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Dynamic import type for esbuild (optional dependency)
|
|
32
|
+
type EsbuildTransformResult = { code: string; map?: string; warnings?: unknown[] };
|
|
33
|
+
type EsbuildModule = {
|
|
34
|
+
transform: (input: string, options?: Record<string, unknown>) => Promise<EsbuildTransformResult>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Dynamic import type for AssemblyScript (optional dependency)
|
|
38
|
+
type AssemblyScriptModule = {
|
|
39
|
+
main: (args: string[], options: unknown, callback: (err: Error | null, result?: { stdout: Uint8Array }) => void) => void;
|
|
40
|
+
builtins: unknown;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* JavaScript Compiler
|
|
45
|
+
*
|
|
46
|
+
* Provides JavaScript execution in a sandboxed environment.
|
|
47
|
+
* For true WASM compilation, AssemblyScript is used but requires
|
|
48
|
+
* strict typing with WebAssembly types (i32, i64, f32, f64).
|
|
49
|
+
*/
|
|
50
|
+
export class JavaScriptCompiler extends BaseCompiler {
|
|
51
|
+
readonly language = "javascript" as const;
|
|
52
|
+
|
|
53
|
+
private assemblyScriptAvailable: boolean | null = null;
|
|
54
|
+
private esbuildAvailable: boolean | null = null;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Check if AssemblyScript is available for WASM compilation
|
|
58
|
+
*/
|
|
59
|
+
async isAvailable(): Promise<boolean> {
|
|
60
|
+
if (this.assemblyScriptAvailable !== null) {
|
|
61
|
+
return this.assemblyScriptAvailable;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
await import("assemblyscript");
|
|
66
|
+
this.assemblyScriptAvailable = true;
|
|
67
|
+
return true;
|
|
68
|
+
} catch {
|
|
69
|
+
this.assemblyScriptAvailable = false;
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Validate JavaScript syntax
|
|
76
|
+
*/
|
|
77
|
+
async validate(source: string): Promise<{ valid: boolean; error?: string }> {
|
|
78
|
+
try {
|
|
79
|
+
// Use esbuild for syntax validation if available
|
|
80
|
+
const esbuild = await this.getEsbuild();
|
|
81
|
+
if (esbuild) {
|
|
82
|
+
await esbuild.transform(source, {
|
|
83
|
+
loader: "js",
|
|
84
|
+
target: "es2020",
|
|
85
|
+
});
|
|
86
|
+
return { valid: true };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Fallback: use Function constructor for basic syntax check
|
|
90
|
+
// This doesn't execute the code, just parses it
|
|
91
|
+
new Function(source);
|
|
92
|
+
return { valid: true };
|
|
93
|
+
} catch (error) {
|
|
94
|
+
return {
|
|
95
|
+
valid: false,
|
|
96
|
+
error: error instanceof Error ? error.message : String(error),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Compile JavaScript to WASM or prepare for sandboxed execution
|
|
103
|
+
*
|
|
104
|
+
* For dynamic JavaScript (with closures, objects, etc.), we create a WASM
|
|
105
|
+
* wrapper that includes a minimal JS runtime or use sandboxed execution.
|
|
106
|
+
*/
|
|
107
|
+
async compile(
|
|
108
|
+
source: string,
|
|
109
|
+
permissions: Permissions,
|
|
110
|
+
limits: Limits,
|
|
111
|
+
options?: JavaScriptCompilerOptions
|
|
112
|
+
): Promise<CompileResult> {
|
|
113
|
+
const opts: JavaScriptCompilerOptions = {
|
|
114
|
+
target: "es2020",
|
|
115
|
+
module: "esm",
|
|
116
|
+
strict: true,
|
|
117
|
+
useAssemblyScript: false, // Default to sandboxed execution
|
|
118
|
+
sandboxMode: "vm",
|
|
119
|
+
...options,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Try AssemblyScript compilation if explicitly requested and available
|
|
123
|
+
if (opts.useAssemblyScript && (await this.isAvailable())) {
|
|
124
|
+
return this.compileWithAssemblyScript(source, permissions, limits, opts);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// For standard JavaScript, create a sandboxed execution wrapper
|
|
128
|
+
return this.createSandboxedWrapper(source, permissions, limits, opts);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get language capabilities
|
|
133
|
+
*/
|
|
134
|
+
getCapabilities(): LanguageCapabilities {
|
|
135
|
+
return {
|
|
136
|
+
repl: true,
|
|
137
|
+
stateful: true,
|
|
138
|
+
compiled: false, // JS is interpreted, not compiled to WASM
|
|
139
|
+
gc: true, // JavaScript has garbage collection
|
|
140
|
+
imports: true,
|
|
141
|
+
async: true,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Compile with AssemblyScript
|
|
147
|
+
*
|
|
148
|
+
* Note: AssemblyScript requires strict typing with WASM types.
|
|
149
|
+
* Standard JavaScript with dynamic features won't compile.
|
|
150
|
+
*/
|
|
151
|
+
private async compileWithAssemblyScript(
|
|
152
|
+
source: string,
|
|
153
|
+
_permissions: Permissions,
|
|
154
|
+
_limits: Limits,
|
|
155
|
+
_options: JavaScriptCompilerOptions
|
|
156
|
+
): Promise<CompileResult> {
|
|
157
|
+
try {
|
|
158
|
+
const asc = (await import("assemblyscript")) as unknown as AssemblyScriptModule;
|
|
159
|
+
|
|
160
|
+
// Wrap user code in a module structure
|
|
161
|
+
const assemblyScriptSource = this.wrapForAssemblyScript(source);
|
|
162
|
+
|
|
163
|
+
return new Promise((resolve, reject) => {
|
|
164
|
+
asc.main(
|
|
165
|
+
[
|
|
166
|
+
"--textFile", "/dev/stdout",
|
|
167
|
+
"--outFile", "/dev/stdout",
|
|
168
|
+
"--optimize",
|
|
169
|
+
"--target", "release",
|
|
170
|
+
],
|
|
171
|
+
{
|
|
172
|
+
stdin: () => assemblyScriptSource,
|
|
173
|
+
listBuiltins: () => asc.builtins,
|
|
174
|
+
reportDiagnostic: (diagnostic: { messageText: string }) => {
|
|
175
|
+
console.error(diagnostic.messageText);
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
(error: Error | null, result?: { stdout: Uint8Array }) => {
|
|
179
|
+
if (error) {
|
|
180
|
+
reject(new Error(`AssemblyScript compilation failed: ${error.message}`));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!result?.stdout) {
|
|
185
|
+
reject(new Error("AssemblyScript produced no output"));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Parse the WASM module to extract exports/imports
|
|
190
|
+
WebAssembly.compile(result.stdout)
|
|
191
|
+
.then((module) => {
|
|
192
|
+
const exports = WebAssembly.Module.exports(module).map((e) => e.name);
|
|
193
|
+
const imports = WebAssembly.Module.imports(module).map((i) => ({
|
|
194
|
+
module: i.module,
|
|
195
|
+
name: i.name,
|
|
196
|
+
type: i.kind as "function" | "memory" | "global" | "table",
|
|
197
|
+
}));
|
|
198
|
+
|
|
199
|
+
resolve({
|
|
200
|
+
wasmBytes: result.stdout,
|
|
201
|
+
exports,
|
|
202
|
+
imports,
|
|
203
|
+
memoryRequirements: {
|
|
204
|
+
initial: 1,
|
|
205
|
+
maximum: 256, // 16MB max
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
})
|
|
209
|
+
.catch(reject);
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
} catch (error) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`AssemblyScript compilation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Create a sandboxed execution wrapper
|
|
222
|
+
*
|
|
223
|
+
* Instead of compiling to WASM, we create a WASM module that
|
|
224
|
+
* provides a sandboxed JavaScript execution environment.
|
|
225
|
+
* For simplicity, we use a minimal wrapper that can be executed
|
|
226
|
+
* by a JavaScript runtime.
|
|
227
|
+
*/
|
|
228
|
+
private async createSandboxedWrapper(
|
|
229
|
+
source: string,
|
|
230
|
+
permissions: Permissions,
|
|
231
|
+
limits: Limits,
|
|
232
|
+
options: JavaScriptCompilerOptions
|
|
233
|
+
): Promise<CompileResult> {
|
|
234
|
+
// Transpile with esbuild if available
|
|
235
|
+
let processedSource = source;
|
|
236
|
+
const esbuild = await this.getEsbuild();
|
|
237
|
+
|
|
238
|
+
if (esbuild) {
|
|
239
|
+
const result = await esbuild.transform(source, {
|
|
240
|
+
loader: "js",
|
|
241
|
+
target: options.target || "es2020",
|
|
242
|
+
format: options.module === "cjs" ? "cjs" : "esm",
|
|
243
|
+
minify: options.optimize,
|
|
244
|
+
});
|
|
245
|
+
processedSource = result.code;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Create a WASM wrapper that provides sandboxed execution
|
|
249
|
+
// This is a minimal WASM module that exports a function to get the JS source
|
|
250
|
+
// The actual execution happens in the JavaScript runtime
|
|
251
|
+
return this.createSandboxWasmWrapper(processedSource, permissions, limits);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Create a WASM wrapper for sandboxed JavaScript execution
|
|
256
|
+
*
|
|
257
|
+
* This creates a WASM module that:
|
|
258
|
+
* 1. Contains the JavaScript source code as data
|
|
259
|
+
* 2. Exports functions for the runtime to execute
|
|
260
|
+
* 3. Provides memory for execution results
|
|
261
|
+
*/
|
|
262
|
+
private async createSandboxWasmWrapper(
|
|
263
|
+
source: string,
|
|
264
|
+
permissions: Permissions,
|
|
265
|
+
limits: Limits
|
|
266
|
+
): Promise<CompileResult> {
|
|
267
|
+
// Encode the source and configuration
|
|
268
|
+
const sourceBytes = new TextEncoder().encode(source);
|
|
269
|
+
const configBytes = new TextEncoder().encode(
|
|
270
|
+
JSON.stringify({
|
|
271
|
+
permissions: {
|
|
272
|
+
fs: permissions.fs ? {
|
|
273
|
+
read: permissions.fs.read ?? false,
|
|
274
|
+
write: permissions.fs.write ?? false,
|
|
275
|
+
delete: permissions.fs.delete ?? false,
|
|
276
|
+
} : undefined,
|
|
277
|
+
network: permissions.network ? {
|
|
278
|
+
outbound: permissions.network.outbound ?? false,
|
|
279
|
+
inbound: permissions.network.inbound ?? false,
|
|
280
|
+
} : undefined,
|
|
281
|
+
},
|
|
282
|
+
limits: {
|
|
283
|
+
timeout: typeof limits.timeout === "string"
|
|
284
|
+
? this.parseTime(limits.timeout)
|
|
285
|
+
: limits.timeout ?? 30000,
|
|
286
|
+
memory: typeof limits.memory === "string"
|
|
287
|
+
? this.parseMemory(limits.memory)
|
|
288
|
+
: limits.memory ?? 16 * 1024 * 1024,
|
|
289
|
+
},
|
|
290
|
+
})
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
// Create a minimal WASM module that holds the JavaScript source
|
|
294
|
+
// This module exports functions to access the source and config
|
|
295
|
+
const wasmBytes = this.buildSandboxWasm(sourceBytes, configBytes);
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
wasmBytes,
|
|
299
|
+
exports: ["getSource", "getConfig", "execute", "getResult"],
|
|
300
|
+
imports: [
|
|
301
|
+
{ module: "env", name: "log", type: "function" },
|
|
302
|
+
{ module: "env", name: "abort", type: "function" },
|
|
303
|
+
],
|
|
304
|
+
memoryRequirements: {
|
|
305
|
+
initial: Math.max(1, Math.ceil((sourceBytes.length + configBytes.length) / 65536)),
|
|
306
|
+
maximum: 256,
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Build the sandbox WASM module
|
|
313
|
+
*
|
|
314
|
+
* Creates a minimal WASM binary that contains:
|
|
315
|
+
* - Data section with JS source
|
|
316
|
+
* - Exported functions for runtime interaction
|
|
317
|
+
*/
|
|
318
|
+
private buildSandboxWasm(sourceBytes: Uint8Array, configBytes: Uint8Array): Uint8Array {
|
|
319
|
+
// WASM module structure:
|
|
320
|
+
// - Magic number + version
|
|
321
|
+
// - Type section (function signatures)
|
|
322
|
+
// - Function section
|
|
323
|
+
// - Memory section
|
|
324
|
+
// - Export section
|
|
325
|
+
// - Code section
|
|
326
|
+
// - Data section (JS source and config)
|
|
327
|
+
|
|
328
|
+
const magicNumber = new Uint8Array([0x00, 0x61, 0x73, 0x6D]); // \0asm
|
|
329
|
+
const version = new Uint8Array([0x01, 0x00, 0x00, 0x00]); // version 1
|
|
330
|
+
|
|
331
|
+
// Build sections
|
|
332
|
+
const typeSection = this.buildTypeSection();
|
|
333
|
+
const functionSection = this.buildFunctionSection();
|
|
334
|
+
const memorySection = this.buildMemorySection(sourceBytes.length + configBytes.length + 1024);
|
|
335
|
+
const exportSection = this.buildExportSection();
|
|
336
|
+
const codeSection = this.buildCodeSection();
|
|
337
|
+
const dataSection = this.buildDataSection(sourceBytes, configBytes);
|
|
338
|
+
|
|
339
|
+
// Combine all sections
|
|
340
|
+
const sections = [
|
|
341
|
+
typeSection,
|
|
342
|
+
functionSection,
|
|
343
|
+
memorySection,
|
|
344
|
+
exportSection,
|
|
345
|
+
codeSection,
|
|
346
|
+
dataSection,
|
|
347
|
+
];
|
|
348
|
+
|
|
349
|
+
const totalLength = magicNumber.length + version.length +
|
|
350
|
+
sections.reduce((sum, s) => sum + s.length, 0);
|
|
351
|
+
|
|
352
|
+
const wasm = new Uint8Array(totalLength);
|
|
353
|
+
let offset = 0;
|
|
354
|
+
|
|
355
|
+
// Write header
|
|
356
|
+
wasm.set(magicNumber, offset);
|
|
357
|
+
offset += magicNumber.length;
|
|
358
|
+
wasm.set(version, offset);
|
|
359
|
+
offset += version.length;
|
|
360
|
+
|
|
361
|
+
// Write sections
|
|
362
|
+
for (const section of sections) {
|
|
363
|
+
wasm.set(section, offset);
|
|
364
|
+
offset += section.length;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return wasm;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private buildTypeSection(): Uint8Array {
|
|
371
|
+
// Section ID 1 (type), 1 type, function type () -> i32
|
|
372
|
+
return new Uint8Array([
|
|
373
|
+
0x01, // section id
|
|
374
|
+
0x04, // section size
|
|
375
|
+
0x01, // num types
|
|
376
|
+
0x60, // func type
|
|
377
|
+
0x00, // 0 params
|
|
378
|
+
0x01, 0x7F, // 1 result: i32
|
|
379
|
+
]);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
private buildFunctionSection(): Uint8Array {
|
|
383
|
+
// Section ID 3 (function), 4 functions
|
|
384
|
+
return new Uint8Array([
|
|
385
|
+
0x03, // section id
|
|
386
|
+
0x05, // section size
|
|
387
|
+
0x04, // num functions
|
|
388
|
+
0x00, 0x00, 0x00, 0x00, // function indices (all type 0)
|
|
389
|
+
]);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private buildMemorySection(dataSize: number): Uint8Array {
|
|
393
|
+
const pages = Math.max(1, Math.ceil(dataSize / 65536));
|
|
394
|
+
// Section ID 5 (memory), 1 memory, limits
|
|
395
|
+
return new Uint8Array([
|
|
396
|
+
0x05, // section id
|
|
397
|
+
0x03, // section size
|
|
398
|
+
0x01, // num memories
|
|
399
|
+
0x01, // has max
|
|
400
|
+
pages, // initial pages
|
|
401
|
+
0x80, 0x02, // max pages (256)
|
|
402
|
+
]);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
private buildExportSection(): Uint8Array {
|
|
406
|
+
// Section ID 7 (export)
|
|
407
|
+
// Export: memory, getSource, getConfig, execute, getResult
|
|
408
|
+
const exports = [
|
|
409
|
+
{ name: "memory", kind: 0x02, index: 0 }, // memory
|
|
410
|
+
{ name: "getSource", kind: 0x00, index: 0 }, // func 0
|
|
411
|
+
{ name: "getConfig", kind: 0x00, index: 1 }, // func 1
|
|
412
|
+
{ name: "execute", kind: 0x00, index: 2 }, // func 2
|
|
413
|
+
{ name: "getResult", kind: 0x00, index: 3 }, // func 3
|
|
414
|
+
];
|
|
415
|
+
|
|
416
|
+
const nameBytes: number[] = [];
|
|
417
|
+
for (const exp of exports) {
|
|
418
|
+
const nameEncoded = new TextEncoder().encode(exp.name);
|
|
419
|
+
nameBytes.push(nameEncoded.length);
|
|
420
|
+
nameBytes.push(...nameEncoded);
|
|
421
|
+
nameBytes.push(exp.kind);
|
|
422
|
+
nameBytes.push(exp.index);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const sectionContent = [exports.length, ...nameBytes];
|
|
426
|
+
const sectionSize = sectionContent.length;
|
|
427
|
+
|
|
428
|
+
return new Uint8Array([0x07, sectionSize, ...sectionContent]);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
private buildCodeSection(): Uint8Array {
|
|
432
|
+
// Section ID 10 (code)
|
|
433
|
+
// 4 function bodies, each returning a constant
|
|
434
|
+
const functions = [
|
|
435
|
+
[0x00, 0x41, 0x00, 0x0B], // getSource: return 0 (offset)
|
|
436
|
+
[0x00, 0x41, 0x00, 0x0B], // getConfig: return 0 (placeholder)
|
|
437
|
+
[0x00, 0x41, 0x00, 0x0B], // execute: return 0 (placeholder)
|
|
438
|
+
[0x00, 0x41, 0x00, 0x0B], // getResult: return 0 (placeholder)
|
|
439
|
+
];
|
|
440
|
+
|
|
441
|
+
const bodies: number[] = [];
|
|
442
|
+
for (const body of functions) {
|
|
443
|
+
bodies.push(body.length);
|
|
444
|
+
bodies.push(...body);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const sectionContent = [functions.length, ...bodies];
|
|
448
|
+
const sectionSize = sectionContent.length;
|
|
449
|
+
|
|
450
|
+
return new Uint8Array([0x0A, sectionSize, ...sectionContent]);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
private buildDataSection(sourceBytes: Uint8Array, configBytes: Uint8Array): Uint8Array {
|
|
454
|
+
// Section ID 11 (data)
|
|
455
|
+
// 2 data segments: source and config
|
|
456
|
+
const sourceLen = this.encodeLEB128(sourceBytes.length);
|
|
457
|
+
const configLen = this.encodeLEB128(configBytes.length);
|
|
458
|
+
|
|
459
|
+
const segment1 = [
|
|
460
|
+
0x00, // active, memory 0
|
|
461
|
+
0x41, 0x00, 0x23, 0x00, // i32.const 0 (offset placeholder)
|
|
462
|
+
...sourceLen,
|
|
463
|
+
...sourceBytes,
|
|
464
|
+
];
|
|
465
|
+
|
|
466
|
+
const segment2 = [
|
|
467
|
+
0x00, // active, memory 0
|
|
468
|
+
0x41, 0x00, 0x23, 0x00, // i32.const 0 (offset placeholder)
|
|
469
|
+
...configLen,
|
|
470
|
+
...configBytes,
|
|
471
|
+
];
|
|
472
|
+
|
|
473
|
+
const sectionContent = [
|
|
474
|
+
0x02, // 2 data segments
|
|
475
|
+
...segment1,
|
|
476
|
+
...segment2,
|
|
477
|
+
];
|
|
478
|
+
|
|
479
|
+
const sectionSize = sectionContent.length;
|
|
480
|
+
return new Uint8Array([0x0B, sectionSize, ...sectionContent]);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
private encodeLEB128(value: number): number[] {
|
|
484
|
+
const result: number[] = [];
|
|
485
|
+
let remaining = value;
|
|
486
|
+
|
|
487
|
+
do {
|
|
488
|
+
let byte = remaining & 0x7F;
|
|
489
|
+
remaining >>>= 7;
|
|
490
|
+
if (remaining !== 0) {
|
|
491
|
+
byte |= 0x80;
|
|
492
|
+
}
|
|
493
|
+
result.push(byte);
|
|
494
|
+
} while (remaining !== 0);
|
|
495
|
+
|
|
496
|
+
return result;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Wrap JavaScript source for AssemblyScript compilation
|
|
501
|
+
*
|
|
502
|
+
* AssemblyScript requires explicit types and specific syntax.
|
|
503
|
+
* This wrapper attempts to make standard JS compatible.
|
|
504
|
+
*/
|
|
505
|
+
private wrapForAssemblyScript(source: string): string {
|
|
506
|
+
return `
|
|
507
|
+
// AssemblyScript wrapper
|
|
508
|
+
// Note: AssemblyScript requires strict typing with i32, i64, f32, f64
|
|
509
|
+
|
|
510
|
+
${source}
|
|
511
|
+
|
|
512
|
+
// Export a main function if not present
|
|
513
|
+
export function _start(): void {
|
|
514
|
+
// Entry point
|
|
515
|
+
}
|
|
516
|
+
`;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Get esbuild instance for transpilation
|
|
521
|
+
*/
|
|
522
|
+
private async getEsbuild(): Promise<EsbuildModule | null> {
|
|
523
|
+
if (this.esbuildAvailable === false) {
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
try {
|
|
528
|
+
const esbuild = await import("esbuild") as EsbuildModule;
|
|
529
|
+
this.esbuildAvailable = true;
|
|
530
|
+
return esbuild;
|
|
531
|
+
} catch {
|
|
532
|
+
this.esbuildAvailable = false;
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Parse memory string to bytes
|
|
539
|
+
*/
|
|
540
|
+
private parseMemory(mem: string): number {
|
|
541
|
+
const match = mem.match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/i);
|
|
542
|
+
if (!match) return 16 * 1024 * 1024;
|
|
543
|
+
const [, num, unit] = match;
|
|
544
|
+
const multipliers: Record<string, number> = {
|
|
545
|
+
b: 1,
|
|
546
|
+
kb: 1024,
|
|
547
|
+
mb: 1024 ** 2,
|
|
548
|
+
gb: 1024 ** 3,
|
|
549
|
+
};
|
|
550
|
+
return Math.floor(parseFloat(num) * (multipliers[unit?.toLowerCase() ?? "b"] ?? 1));
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Parse time string to milliseconds
|
|
555
|
+
*/
|
|
556
|
+
private parseTime(time: string): number {
|
|
557
|
+
const match = time.match(/^(\d+(?:\.\d+)?)\s*(ms|s|m|h)?$/i);
|
|
558
|
+
if (!match) return 30000;
|
|
559
|
+
const [, num, unit] = match;
|
|
560
|
+
const multipliers: Record<string, number> = {
|
|
561
|
+
ms: 1,
|
|
562
|
+
s: 1000,
|
|
563
|
+
m: 60000,
|
|
564
|
+
h: 3600000,
|
|
565
|
+
};
|
|
566
|
+
return Math.floor(parseFloat(num) * (multipliers[unit?.toLowerCase() ?? "ms"] ?? 1));
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Default instance
|
|
571
|
+
export const javascriptCompiler = new JavaScriptCompiler();
|