@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.
Files changed (108) hide show
  1. package/dist/compilers/index.d.ts +24 -0
  2. package/dist/compilers/index.d.ts.map +1 -0
  3. package/dist/compilers/index.js +42 -0
  4. package/dist/compilers/index.js.map +1 -0
  5. package/dist/compilers/javascript.d.ts +117 -0
  6. package/dist/compilers/javascript.d.ts.map +1 -0
  7. package/dist/compilers/javascript.js +462 -0
  8. package/dist/compilers/javascript.js.map +1 -0
  9. package/dist/compilers/python.d.ts +140 -0
  10. package/dist/compilers/python.d.ts.map +1 -0
  11. package/dist/compilers/python.js +650 -0
  12. package/dist/compilers/python.js.map +1 -0
  13. package/dist/compilers/typescript.d.ts +99 -0
  14. package/dist/compilers/typescript.d.ts.map +1 -0
  15. package/dist/compilers/typescript.js +323 -0
  16. package/dist/compilers/typescript.js.map +1 -0
  17. package/dist/core/cell.d.ts +160 -0
  18. package/dist/core/cell.d.ts.map +1 -0
  19. package/dist/core/cell.js +319 -0
  20. package/dist/core/cell.js.map +1 -0
  21. package/dist/core/compiler.d.ts +126 -0
  22. package/dist/core/compiler.d.ts.map +1 -0
  23. package/dist/core/compiler.js +123 -0
  24. package/dist/core/compiler.js.map +1 -0
  25. package/dist/core/index.d.ts +19 -0
  26. package/dist/core/index.d.ts.map +1 -0
  27. package/dist/core/index.js +14 -0
  28. package/dist/core/index.js.map +1 -0
  29. package/dist/core/limits.d.ts +173 -0
  30. package/dist/core/limits.d.ts.map +1 -0
  31. package/dist/core/limits.js +440 -0
  32. package/dist/core/limits.js.map +1 -0
  33. package/dist/core/permissions.d.ts +103 -0
  34. package/dist/core/permissions.d.ts.map +1 -0
  35. package/dist/core/permissions.js +341 -0
  36. package/dist/core/permissions.js.map +1 -0
  37. package/dist/core/runtime.d.ts +127 -0
  38. package/dist/core/runtime.d.ts.map +1 -0
  39. package/dist/core/runtime.js +325 -0
  40. package/dist/core/runtime.js.map +1 -0
  41. package/dist/core/types.d.ts +380 -0
  42. package/dist/core/types.d.ts.map +1 -0
  43. package/dist/core/types.js +67 -0
  44. package/dist/core/types.js.map +1 -0
  45. package/dist/index.d.ts +145 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +279 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/multi/index.d.ts +9 -0
  50. package/dist/multi/index.d.ts.map +1 -0
  51. package/dist/multi/index.js +7 -0
  52. package/dist/multi/index.js.map +1 -0
  53. package/dist/multi/polyglot.d.ts +179 -0
  54. package/dist/multi/polyglot.d.ts.map +1 -0
  55. package/dist/multi/polyglot.js +319 -0
  56. package/dist/multi/polyglot.js.map +1 -0
  57. package/dist/runtimes/docker.d.ts +97 -0
  58. package/dist/runtimes/docker.d.ts.map +1 -0
  59. package/dist/runtimes/docker.js +368 -0
  60. package/dist/runtimes/docker.js.map +1 -0
  61. package/dist/runtimes/index.d.ts +11 -0
  62. package/dist/runtimes/index.d.ts.map +1 -0
  63. package/dist/runtimes/index.js +9 -0
  64. package/dist/runtimes/index.js.map +1 -0
  65. package/dist/runtimes/process.d.ts +47 -0
  66. package/dist/runtimes/process.d.ts.map +1 -0
  67. package/dist/runtimes/process.js +230 -0
  68. package/dist/runtimes/process.js.map +1 -0
  69. package/dist/session/index.d.ts +12 -0
  70. package/dist/session/index.d.ts.map +1 -0
  71. package/dist/session/index.js +9 -0
  72. package/dist/session/index.js.map +1 -0
  73. package/dist/session/kernel.d.ts +199 -0
  74. package/dist/session/kernel.d.ts.map +1 -0
  75. package/dist/session/kernel.js +400 -0
  76. package/dist/session/kernel.js.map +1 -0
  77. package/dist/session/notebook.d.ts +168 -0
  78. package/dist/session/notebook.d.ts.map +1 -0
  79. package/dist/session/notebook.js +499 -0
  80. package/dist/session/notebook.js.map +1 -0
  81. package/dist/session/repl.d.ts +159 -0
  82. package/dist/session/repl.d.ts.map +1 -0
  83. package/dist/session/repl.js +409 -0
  84. package/dist/session/repl.js.map +1 -0
  85. package/package.json +142 -0
  86. package/src/compilers/index.ts +80 -0
  87. package/src/compilers/javascript.ts +571 -0
  88. package/src/compilers/python.ts +785 -0
  89. package/src/compilers/typescript.ts +442 -0
  90. package/src/core/cell.ts +439 -0
  91. package/src/core/compiler.ts +250 -0
  92. package/src/core/index.ts +123 -0
  93. package/src/core/limits.ts +508 -0
  94. package/src/core/permissions.ts +409 -0
  95. package/src/core/runtime.ts +499 -0
  96. package/src/core/types.ts +528 -0
  97. package/src/global.d.ts +59 -0
  98. package/src/index.ts +515 -0
  99. package/src/multi/index.ts +22 -0
  100. package/src/multi/polyglot.ts +461 -0
  101. package/src/runtimes/docker.ts +501 -0
  102. package/src/runtimes/index.ts +21 -0
  103. package/src/runtimes/process.ts +316 -0
  104. package/src/session/index.ts +41 -0
  105. package/src/session/kernel.ts +553 -0
  106. package/src/session/notebook.ts +635 -0
  107. package/src/session/repl.ts +521 -0
  108. 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());