@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,499 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime Primitive
|
|
3
|
+
*
|
|
4
|
+
* Executes compiled code (WASM) with resource limits and permissions.
|
|
5
|
+
* Composable: different runtimes for different isolation levels.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
ExecutionResult,
|
|
10
|
+
ExecutionMetrics,
|
|
11
|
+
Permissions,
|
|
12
|
+
Limits,
|
|
13
|
+
DisplayOutput,
|
|
14
|
+
} from "./types.js";
|
|
15
|
+
import type { CompileResult } from "./compiler.js";
|
|
16
|
+
|
|
17
|
+
/** Runtime options */
|
|
18
|
+
export interface RuntimeOptions {
|
|
19
|
+
permissions: Permissions;
|
|
20
|
+
limits: Limits;
|
|
21
|
+
/** Environment variables */
|
|
22
|
+
env?: Record<string, string>;
|
|
23
|
+
/** Working directory */
|
|
24
|
+
cwd?: string;
|
|
25
|
+
/** Abort signal */
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Execution request */
|
|
30
|
+
export interface ExecutionRequest {
|
|
31
|
+
/** Compiled WASM */
|
|
32
|
+
wasm: CompileResult;
|
|
33
|
+
/** Entrypoint function */
|
|
34
|
+
entrypoint?: string;
|
|
35
|
+
/** Arguments to pass */
|
|
36
|
+
args?: unknown[];
|
|
37
|
+
/** Input data */
|
|
38
|
+
input?: unknown;
|
|
39
|
+
/** Previous state */
|
|
40
|
+
state?: Map<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Base runtime interface */
|
|
44
|
+
export interface IRuntime {
|
|
45
|
+
/** Runtime name */
|
|
46
|
+
readonly name: string;
|
|
47
|
+
|
|
48
|
+
/** Runtime capabilities */
|
|
49
|
+
readonly capabilities: RuntimeCapabilities;
|
|
50
|
+
|
|
51
|
+
/** Initialize runtime */
|
|
52
|
+
init(): Promise<void>;
|
|
53
|
+
|
|
54
|
+
/** Check if runtime is available */
|
|
55
|
+
isAvailable(): Promise<boolean>;
|
|
56
|
+
|
|
57
|
+
/** Execute compiled code */
|
|
58
|
+
execute(request: ExecutionRequest, options: RuntimeOptions): Promise<ExecutionResult>;
|
|
59
|
+
|
|
60
|
+
/** Terminate runtime and cleanup resources */
|
|
61
|
+
terminate(): Promise<void>;
|
|
62
|
+
|
|
63
|
+
/** Check if currently executing */
|
|
64
|
+
isExecuting(): boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Runtime capabilities */
|
|
68
|
+
export interface RuntimeCapabilities {
|
|
69
|
+
/** Isolation level */
|
|
70
|
+
isolation: "none" | "process" | "vm" | "wasm" | "container";
|
|
71
|
+
/** Supports state persistence */
|
|
72
|
+
stateful: boolean;
|
|
73
|
+
/** Supports async execution */
|
|
74
|
+
async: boolean;
|
|
75
|
+
/** Can access filesystem */
|
|
76
|
+
filesystem: boolean;
|
|
77
|
+
/** Can access network */
|
|
78
|
+
network: boolean;
|
|
79
|
+
/** Max memory (bytes) */
|
|
80
|
+
maxMemory: number;
|
|
81
|
+
/** Supports WASI */
|
|
82
|
+
wasi: boolean;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* WASM-in-WASM Runtime
|
|
87
|
+
*
|
|
88
|
+
* Uses @ebowwa/wasm2wasm for maximum isolation.
|
|
89
|
+
* Runs WASM bytecode inside a WASM interpreter.
|
|
90
|
+
*/
|
|
91
|
+
export class Wasm2WasmRuntime implements IRuntime {
|
|
92
|
+
readonly name = "wasm2wasm";
|
|
93
|
+
readonly capabilities: RuntimeCapabilities = {
|
|
94
|
+
isolation: "wasm",
|
|
95
|
+
stateful: false,
|
|
96
|
+
async: true,
|
|
97
|
+
filesystem: false,
|
|
98
|
+
network: false,
|
|
99
|
+
maxMemory: 256 * 1024 * 1024, // 256MB
|
|
100
|
+
wasi: false,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
private executing = false;
|
|
104
|
+
|
|
105
|
+
async init(): Promise<void> {
|
|
106
|
+
// Will initialize wasm2wasm when needed
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async isAvailable(): Promise<boolean> {
|
|
110
|
+
try {
|
|
111
|
+
// Check if @ebowwa/wasm2wasm is available
|
|
112
|
+
const module = await import("@ebowwa/wasm2wasm");
|
|
113
|
+
return typeof module.execute === "function";
|
|
114
|
+
} catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async execute(
|
|
120
|
+
request: ExecutionRequest,
|
|
121
|
+
options: RuntimeOptions
|
|
122
|
+
): Promise<ExecutionResult> {
|
|
123
|
+
const startTime = Date.now();
|
|
124
|
+
this.executing = true;
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
// Dynamic import to avoid bundling issues
|
|
128
|
+
const { execute, validate } = await import("@ebowwa/wasm2wasm");
|
|
129
|
+
|
|
130
|
+
// Validate WASM first
|
|
131
|
+
const validation = validate(request.wasm.wasmBytes);
|
|
132
|
+
if (!validation.valid) {
|
|
133
|
+
return this.createErrorResult(
|
|
134
|
+
`Invalid WASM: ${validation.error}`,
|
|
135
|
+
"compile",
|
|
136
|
+
startTime
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Configure runtime with limits
|
|
141
|
+
const maxMemory = this.parseMemory(options.limits.memory);
|
|
142
|
+
const timeout = this.parseTimeout(options.limits.timeout);
|
|
143
|
+
|
|
144
|
+
// Set up timeout
|
|
145
|
+
const controller = new AbortController();
|
|
146
|
+
const timeoutId = setTimeout(
|
|
147
|
+
() => controller.abort(),
|
|
148
|
+
timeout
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Combine signals
|
|
152
|
+
const combinedSignal = options.signal
|
|
153
|
+
? this.combineSignals(options.signal, controller.signal)
|
|
154
|
+
: controller.signal;
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
// Execute using the main execute function
|
|
158
|
+
const result = await execute(request.wasm.wasmBytes, {
|
|
159
|
+
maxMemoryPages: Math.floor(maxMemory / 65536),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
clearTimeout(timeoutId);
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
success: result.success,
|
|
166
|
+
value: result.output,
|
|
167
|
+
metrics: {
|
|
168
|
+
duration: Date.now() - startTime,
|
|
169
|
+
memoryUsed: result.memoryUsed ?? 0,
|
|
170
|
+
},
|
|
171
|
+
output: {
|
|
172
|
+
stdout: [],
|
|
173
|
+
stderr: result.error ? [result.error] : [],
|
|
174
|
+
displays: [],
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
} catch (error) {
|
|
178
|
+
clearTimeout(timeoutId);
|
|
179
|
+
|
|
180
|
+
if (combinedSignal.aborted) {
|
|
181
|
+
return this.createErrorResult(
|
|
182
|
+
"Execution timed out",
|
|
183
|
+
"timeout",
|
|
184
|
+
startTime
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
} catch (error) {
|
|
191
|
+
return this.createErrorResult(
|
|
192
|
+
error instanceof Error ? error.message : String(error),
|
|
193
|
+
"runtime",
|
|
194
|
+
startTime
|
|
195
|
+
);
|
|
196
|
+
} finally {
|
|
197
|
+
this.executing = false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async terminate(): Promise<void> {
|
|
202
|
+
this.executing = false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
isExecuting(): boolean {
|
|
206
|
+
return this.executing;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private createErrorResult(
|
|
210
|
+
message: string,
|
|
211
|
+
type: "compile" | "runtime" | "permission" | "limit" | "timeout" | "cancel",
|
|
212
|
+
startTime: number
|
|
213
|
+
): ExecutionResult {
|
|
214
|
+
return {
|
|
215
|
+
success: false,
|
|
216
|
+
error: { message, type },
|
|
217
|
+
metrics: {
|
|
218
|
+
duration: Date.now() - startTime,
|
|
219
|
+
memoryUsed: 0,
|
|
220
|
+
},
|
|
221
|
+
output: { stdout: [], stderr: [], displays: [] },
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private parseMemory(memory: string | number | undefined): number {
|
|
226
|
+
if (!memory) return 16 * 1024 * 1024; // 16MB default
|
|
227
|
+
if (typeof memory === "number") return memory;
|
|
228
|
+
const match = memory.match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/i);
|
|
229
|
+
if (!match) return 16 * 1024 * 1024;
|
|
230
|
+
const [, num, unit] = match;
|
|
231
|
+
const multipliers: Record<string, number> = {
|
|
232
|
+
b: 1, kb: 1024, mb: 1024 ** 2, gb: 1024 ** 3,
|
|
233
|
+
};
|
|
234
|
+
return Math.floor(parseFloat(num) * (multipliers[unit?.toLowerCase() ?? "b"] ?? 1));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private parseTimeout(timeout: string | number | undefined): number {
|
|
238
|
+
if (!timeout) return 30000; // 30s default
|
|
239
|
+
if (typeof timeout === "number") return timeout;
|
|
240
|
+
const match = timeout.match(/^(\d+(?:\.\d+)?)\s*(ms|s|m)?$/i);
|
|
241
|
+
if (!match) return 30000;
|
|
242
|
+
const [, num, unit] = match;
|
|
243
|
+
const multipliers: Record<string, number> = {
|
|
244
|
+
ms: 1, s: 1000, m: 60000,
|
|
245
|
+
};
|
|
246
|
+
return Math.floor(parseFloat(num) * (multipliers[unit?.toLowerCase() ?? "ms"] ?? 1));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private combineSignals(sig1: AbortSignal, sig2: AbortSignal): AbortSignal {
|
|
250
|
+
const controller = new AbortController();
|
|
251
|
+
|
|
252
|
+
const handler = () => {
|
|
253
|
+
controller.abort();
|
|
254
|
+
sig1.removeEventListener("abort", handler);
|
|
255
|
+
sig2.removeEventListener("abort", handler);
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
sig1.addEventListener("abort", handler);
|
|
259
|
+
sig2.addEventListener("abort", handler);
|
|
260
|
+
|
|
261
|
+
return controller.signal;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Native WASM Runtime
|
|
267
|
+
*
|
|
268
|
+
* Uses browser/Node WebAssembly API directly.
|
|
269
|
+
* Less isolated but faster.
|
|
270
|
+
*/
|
|
271
|
+
export class NativeWasmRuntime implements IRuntime {
|
|
272
|
+
readonly name = "native-wasm";
|
|
273
|
+
readonly capabilities: RuntimeCapabilities = {
|
|
274
|
+
isolation: "process",
|
|
275
|
+
stateful: true,
|
|
276
|
+
async: true,
|
|
277
|
+
filesystem: true,
|
|
278
|
+
network: true,
|
|
279
|
+
maxMemory: 4 * 1024 * 1024 * 1024, // 4GB
|
|
280
|
+
wasi: true,
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
private executing = false;
|
|
284
|
+
|
|
285
|
+
async init(): Promise<void> {
|
|
286
|
+
// No init needed
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async isAvailable(): Promise<boolean> {
|
|
290
|
+
return typeof WebAssembly === "object";
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async execute(
|
|
294
|
+
request: ExecutionRequest,
|
|
295
|
+
options: RuntimeOptions
|
|
296
|
+
): Promise<ExecutionResult> {
|
|
297
|
+
const startTime = Date.now();
|
|
298
|
+
this.executing = true;
|
|
299
|
+
|
|
300
|
+
const stdout: string[] = [];
|
|
301
|
+
const stderr: string[] = [];
|
|
302
|
+
const displays: DisplayOutput[] = [];
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
// Compile WASM
|
|
306
|
+
const module = await WebAssembly.compile(request.wasm.wasmBytes);
|
|
307
|
+
|
|
308
|
+
// Build imports based on permissions
|
|
309
|
+
const imports = this.buildImports(options.permissions, stdout, stderr, displays);
|
|
310
|
+
|
|
311
|
+
// Instantiate
|
|
312
|
+
const instance = await WebAssembly.instantiate(module, imports);
|
|
313
|
+
|
|
314
|
+
// Get entrypoint
|
|
315
|
+
const entrypoint = request.entrypoint || "_start";
|
|
316
|
+
const exports = instance.exports as Record<string, unknown>;
|
|
317
|
+
|
|
318
|
+
if (!(entrypoint in exports)) {
|
|
319
|
+
return this.createErrorResult(
|
|
320
|
+
`Entrypoint '${entrypoint}' not found in exports`,
|
|
321
|
+
"runtime",
|
|
322
|
+
startTime,
|
|
323
|
+
stdout,
|
|
324
|
+
stderr
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const fn = exports[entrypoint];
|
|
329
|
+
if (typeof fn !== "function") {
|
|
330
|
+
return this.createErrorResult(
|
|
331
|
+
`Export '${entrypoint}' is not a function`,
|
|
332
|
+
"runtime",
|
|
333
|
+
startTime,
|
|
334
|
+
stdout,
|
|
335
|
+
stderr
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Execute with timeout
|
|
340
|
+
const timeout = this.parseTimeout(options.limits?.timeout);
|
|
341
|
+
const result = await Promise.race([
|
|
342
|
+
(fn as (...args: unknown[]) => unknown)(...(request.args ?? [])),
|
|
343
|
+
new Promise((_, reject) =>
|
|
344
|
+
setTimeout(() => reject(new Error("Timeout")), timeout)
|
|
345
|
+
),
|
|
346
|
+
]);
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
success: true,
|
|
350
|
+
value: result,
|
|
351
|
+
metrics: {
|
|
352
|
+
duration: Date.now() - startTime,
|
|
353
|
+
memoryUsed: 0, // Not available in native
|
|
354
|
+
},
|
|
355
|
+
output: { stdout, stderr, displays },
|
|
356
|
+
};
|
|
357
|
+
} catch (error) {
|
|
358
|
+
return this.createErrorResult(
|
|
359
|
+
error instanceof Error ? error.message : String(error),
|
|
360
|
+
"runtime",
|
|
361
|
+
startTime,
|
|
362
|
+
stdout,
|
|
363
|
+
stderr
|
|
364
|
+
);
|
|
365
|
+
} finally {
|
|
366
|
+
this.executing = false;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async terminate(): Promise<void> {
|
|
371
|
+
this.executing = false;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
isExecuting(): boolean {
|
|
375
|
+
return this.executing;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
private buildImports(
|
|
379
|
+
permissions: Permissions,
|
|
380
|
+
stdout: string[],
|
|
381
|
+
stderr: string[],
|
|
382
|
+
displays: DisplayOutput[]
|
|
383
|
+
): WebAssembly.Imports {
|
|
384
|
+
const imports: WebAssembly.Imports = {};
|
|
385
|
+
|
|
386
|
+
// Console import (always available)
|
|
387
|
+
imports.env = {
|
|
388
|
+
...imports.env,
|
|
389
|
+
console_log: (msg: number, len: number) => {
|
|
390
|
+
// Simplified - would need memory access
|
|
391
|
+
stdout.push(`[log] message at offset ${msg}`);
|
|
392
|
+
},
|
|
393
|
+
console_error: (msg: number, len: number) => {
|
|
394
|
+
stderr.push(`[error] message at offset ${msg}`);
|
|
395
|
+
},
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
// Add memory if needed
|
|
399
|
+
if (permissions.fs?.read || permissions.fs?.write) {
|
|
400
|
+
imports.env = {
|
|
401
|
+
...imports.env,
|
|
402
|
+
fs_read: () => { /* TODO: implement */ },
|
|
403
|
+
fs_write: () => { /* TODO: implement */ },
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return imports;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private createErrorResult(
|
|
411
|
+
message: string,
|
|
412
|
+
type: "compile" | "runtime" | "permission" | "limit" | "timeout" | "cancel",
|
|
413
|
+
startTime: number,
|
|
414
|
+
stdout: string[] = [],
|
|
415
|
+
stderr: string[] = []
|
|
416
|
+
): ExecutionResult {
|
|
417
|
+
return {
|
|
418
|
+
success: false,
|
|
419
|
+
error: { message, type },
|
|
420
|
+
metrics: {
|
|
421
|
+
duration: Date.now() - startTime,
|
|
422
|
+
memoryUsed: 0,
|
|
423
|
+
},
|
|
424
|
+
output: { stdout, stderr, displays: [] },
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
private parseTimeout(timeout: string | number | undefined): number {
|
|
429
|
+
if (!timeout) return 30000;
|
|
430
|
+
if (typeof timeout === "number") return timeout;
|
|
431
|
+
const match = timeout.match(/^(\d+(?:\.\d+)?)\s*(ms|s|m)?$/i);
|
|
432
|
+
if (!match) return 30000;
|
|
433
|
+
const [, num, unit] = match;
|
|
434
|
+
const multipliers: Record<string, number> = {
|
|
435
|
+
ms: 1, s: 1000, m: 60000,
|
|
436
|
+
};
|
|
437
|
+
return Math.floor(parseFloat(num) * (multipliers[unit?.toLowerCase() ?? "ms"] ?? 1));
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Runtime Registry
|
|
443
|
+
*
|
|
444
|
+
* Manages available runtimes and selects appropriate one.
|
|
445
|
+
*/
|
|
446
|
+
export class RuntimeRegistry {
|
|
447
|
+
private runtimes = new Map<string, IRuntime>();
|
|
448
|
+
|
|
449
|
+
register(runtime: IRuntime): void {
|
|
450
|
+
this.runtimes.set(runtime.name, runtime);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
get(name: string): IRuntime | undefined {
|
|
454
|
+
return this.runtimes.get(name);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
has(name: string): boolean {
|
|
458
|
+
return this.runtimes.has(name);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async getAvailable(): Promise<string[]> {
|
|
462
|
+
const available: string[] = [];
|
|
463
|
+
for (const [name, runtime] of this.runtimes) {
|
|
464
|
+
if (await runtime.isAvailable()) {
|
|
465
|
+
available.push(name);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return available;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Select best runtime for given requirements
|
|
473
|
+
*/
|
|
474
|
+
selectBest(requirements: {
|
|
475
|
+
isolation?: RuntimeCapabilities["isolation"];
|
|
476
|
+
filesystem?: boolean;
|
|
477
|
+
network?: boolean;
|
|
478
|
+
}): IRuntime | undefined {
|
|
479
|
+
for (const runtime of this.runtimes.values()) {
|
|
480
|
+
const caps = runtime.capabilities;
|
|
481
|
+
if (requirements.isolation && caps.isolation !== requirements.isolation) {
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
if (requirements.filesystem && !caps.filesystem) {
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
if (requirements.network && !caps.network) {
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
return runtime;
|
|
491
|
+
}
|
|
492
|
+
return undefined;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Default registry with built-in runtimes
|
|
497
|
+
export const defaultRuntimeRegistry = new RuntimeRegistry();
|
|
498
|
+
defaultRuntimeRegistry.register(new Wasm2WasmRuntime());
|
|
499
|
+
defaultRuntimeRegistry.register(new NativeWasmRuntime());
|