@ricsam/isolate-runtime 0.1.1 → 0.1.3

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/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # @ricsam/isolate-runtime
2
+
3
+ Umbrella package that combines all APIs.
4
+
5
+ ```typescript
6
+ import { createRuntime } from "@ricsam/isolate-runtime";
7
+
8
+ const runtime = await createRuntime({
9
+ // Memory limit in MB
10
+ memoryLimit: 128,
11
+ // Console API
12
+ console: {
13
+ onLog: (level, ...args) => console.log(`[${level}]`, ...args),
14
+ },
15
+ // Fetch API
16
+ fetch: {
17
+ onFetch: async (req) => fetch(req),
18
+ },
19
+ // File System API (optional)
20
+ fs: {
21
+ getDirectory: async (path) => createNodeFileSystemHandler(`./data${path}`),
22
+ },
23
+ });
24
+
25
+ // The runtime includes:
26
+ // - runtime.isolate: The V8 isolate
27
+ // - runtime.context: The execution context
28
+ // - runtime.tick(): Process pending timers
29
+ // - runtime.dispose(): Clean up all resources
30
+
31
+ // Run code
32
+ await runtime.context.eval(`
33
+ console.log("Hello from sandbox!");
34
+ `, { promise: true });
35
+
36
+ // Process timers
37
+ await runtime.tick(100);
38
+
39
+ // Cleanup
40
+ runtime.dispose();
41
+ ```
42
+
43
+ **What's Included:**
44
+ - Core (Blob, File, streams, URL, TextEncoder/Decoder)
45
+ - Console
46
+ - Encoding (atob/btoa)
47
+ - Timers (setTimeout, setInterval)
48
+ - Path utilities
49
+ - Crypto (randomUUID, getRandomValues, subtle)
50
+ - Fetch API
51
+ - File System (if handler provided)
@@ -0,0 +1,115 @@
1
+ // @bun @bun-cjs
2
+ (function(exports, require, module, __filename, __dirname) {var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __toESM = (mod, isNodeMode, target) => {
9
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
10
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
+ for (let key of __getOwnPropNames(mod))
12
+ if (!__hasOwnProp.call(to, key))
13
+ __defProp(to, key, {
14
+ get: () => mod[key],
15
+ enumerable: true
16
+ });
17
+ return to;
18
+ };
19
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
20
+ var __toCommonJS = (from) => {
21
+ var entry = __moduleCache.get(from), desc;
22
+ if (entry)
23
+ return entry;
24
+ entry = __defProp({}, "__esModule", { value: true });
25
+ if (from && typeof from === "object" || typeof from === "function")
26
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
27
+ get: () => from[key],
28
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
29
+ }));
30
+ __moduleCache.set(from, entry);
31
+ return entry;
32
+ };
33
+ var __export = (target, all) => {
34
+ for (var name in all)
35
+ __defProp(target, name, {
36
+ get: all[name],
37
+ enumerable: true,
38
+ configurable: true,
39
+ set: (newValue) => all[name] = () => newValue
40
+ });
41
+ };
42
+
43
+ // packages/runtime/src/index.ts
44
+ var exports_src = {};
45
+ __export(exports_src, {
46
+ setupTimers: () => import_isolate_timers2.setupTimers,
47
+ setupPath: () => import_isolate_path2.setupPath,
48
+ setupFs: () => import_isolate_fs2.setupFs,
49
+ setupFetch: () => import_isolate_fetch2.setupFetch,
50
+ setupEncoding: () => import_isolate_encoding2.setupEncoding,
51
+ setupCrypto: () => import_isolate_crypto2.setupCrypto,
52
+ setupCore: () => import_isolate_core2.setupCore,
53
+ setupConsole: () => import_isolate_console2.setupConsole,
54
+ createRuntime: () => createRuntime,
55
+ createNodeFileSystemHandler: () => import_isolate_fs2.createNodeFileSystemHandler
56
+ });
57
+ module.exports = __toCommonJS(exports_src);
58
+ var import_isolated_vm = __toESM(require("isolated-vm"));
59
+ var import_isolate_core = require("@ricsam/isolate-core");
60
+ var import_isolate_console = require("@ricsam/isolate-console");
61
+ var import_isolate_encoding = require("@ricsam/isolate-encoding");
62
+ var import_isolate_timers = require("@ricsam/isolate-timers");
63
+ var import_isolate_path = require("@ricsam/isolate-path");
64
+ var import_isolate_crypto = require("@ricsam/isolate-crypto");
65
+ var import_isolate_fetch = require("@ricsam/isolate-fetch");
66
+ var import_isolate_fs = require("@ricsam/isolate-fs");
67
+ var import_isolate_core2 = require("@ricsam/isolate-core");
68
+ var import_isolate_console2 = require("@ricsam/isolate-console");
69
+ var import_isolate_crypto2 = require("@ricsam/isolate-crypto");
70
+ var import_isolate_encoding2 = require("@ricsam/isolate-encoding");
71
+ var import_isolate_fetch2 = require("@ricsam/isolate-fetch");
72
+ var import_isolate_fs2 = require("@ricsam/isolate-fs");
73
+ var import_isolate_path2 = require("@ricsam/isolate-path");
74
+ var import_isolate_timers2 = require("@ricsam/isolate-timers");
75
+ async function createRuntime(options) {
76
+ const opts = options ?? {};
77
+ const isolate = new import_isolated_vm.default.Isolate({
78
+ memoryLimit: opts.memoryLimit
79
+ });
80
+ const context = await isolate.createContext();
81
+ const handles = {};
82
+ handles.core = await import_isolate_core.setupCore(context);
83
+ handles.console = await import_isolate_console.setupConsole(context, opts.console);
84
+ handles.encoding = await import_isolate_encoding.setupEncoding(context);
85
+ handles.timers = await import_isolate_timers.setupTimers(context);
86
+ handles.path = await import_isolate_path.setupPath(context);
87
+ handles.crypto = await import_isolate_crypto.setupCrypto(context);
88
+ handles.fetch = await import_isolate_fetch.setupFetch(context, opts.fetch);
89
+ if (opts.fs) {
90
+ handles.fs = await import_isolate_fs.setupFs(context, opts.fs);
91
+ }
92
+ return {
93
+ isolate,
94
+ context,
95
+ fetch: handles.fetch,
96
+ async tick(ms) {
97
+ await handles.timers.tick(ms);
98
+ },
99
+ dispose() {
100
+ handles.fs?.dispose();
101
+ handles.fetch?.dispose();
102
+ handles.crypto?.dispose();
103
+ handles.path?.dispose();
104
+ handles.timers?.dispose();
105
+ handles.encoding?.dispose();
106
+ handles.console?.dispose();
107
+ handles.core?.dispose();
108
+ context.release();
109
+ isolate.dispose();
110
+ }
111
+ };
112
+ }
113
+ })
114
+
115
+ //# debugId=4A635399139F1BA664756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/index.ts"],
4
+ "sourcesContent": [
5
+ "import ivm from \"isolated-vm\";\nimport { setupCore } from \"@ricsam/isolate-core\";\nimport { setupConsole } from \"@ricsam/isolate-console\";\nimport { setupEncoding } from \"@ricsam/isolate-encoding\";\nimport { setupTimers } from \"@ricsam/isolate-timers\";\nimport { setupPath } from \"@ricsam/isolate-path\";\nimport { setupCrypto } from \"@ricsam/isolate-crypto\";\nimport { setupFetch } from \"@ricsam/isolate-fetch\";\nimport { setupFs } from \"@ricsam/isolate-fs\";\n\nimport type { ConsoleOptions, ConsoleHandle } from \"@ricsam/isolate-console\";\nimport type { FetchOptions, FetchHandle } from \"@ricsam/isolate-fetch\";\nimport type { FsOptions, FsHandle } from \"@ricsam/isolate-fs\";\nimport type { CoreHandle } from \"@ricsam/isolate-core\";\nimport type { EncodingHandle } from \"@ricsam/isolate-encoding\";\nimport type { TimersHandle } from \"@ricsam/isolate-timers\";\nimport type { PathHandle } from \"@ricsam/isolate-path\";\nimport type { CryptoHandle } from \"@ricsam/isolate-crypto\";\n\nexport interface RuntimeOptions {\n /** Isolate memory limit in MB */\n memoryLimit?: number;\n /** Console options */\n console?: ConsoleOptions;\n /** Fetch options */\n fetch?: FetchOptions;\n /** File system options (optional - fs only set up if provided) */\n fs?: FsOptions;\n}\n\nexport interface RuntimeHandle {\n /** The isolate instance */\n readonly isolate: ivm.Isolate;\n /** The context instance */\n readonly context: ivm.Context;\n /** The fetch handle for serve() and WebSocket dispatching */\n readonly fetch: FetchHandle;\n /** Process pending timers */\n tick(ms?: number): Promise<void>;\n /** Dispose all resources */\n dispose(): void;\n}\n\n/**\n * Create a fully configured isolated-vm runtime\n *\n * Sets up all WHATWG APIs: fetch, fs, console, crypto, encoding, timers\n *\n * @example\n * const runtime = await createRuntime({\n * console: {\n * onLog: (level, ...args) => console.log(`[${level}]`, ...args)\n * },\n * fetch: {\n * onFetch: async (request) => fetch(request)\n * }\n * });\n *\n * await runtime.context.eval(`\n * console.log(\"Hello from sandbox!\");\n * const response = await fetch(\"https://example.com\");\n * `);\n *\n * runtime.dispose();\n */\nexport async function createRuntime(\n options?: RuntimeOptions\n): Promise<RuntimeHandle> {\n const opts = options ?? {};\n\n // Create isolate with optional memory limit\n const isolate = new ivm.Isolate({\n memoryLimit: opts.memoryLimit,\n });\n const context = await isolate.createContext();\n\n // Store all handles for disposal\n const handles: {\n core?: CoreHandle;\n console?: ConsoleHandle;\n encoding?: EncodingHandle;\n timers?: TimersHandle;\n path?: PathHandle;\n crypto?: CryptoHandle;\n fetch?: FetchHandle;\n fs?: FsHandle;\n } = {};\n\n // Setup all APIs in order\n // Core must be first as it provides Blob, File, streams, URL, etc.\n handles.core = await setupCore(context);\n\n // Console\n handles.console = await setupConsole(context, opts.console);\n\n // Encoding (btoa/atob)\n handles.encoding = await setupEncoding(context);\n\n // Timers (setTimeout, setInterval)\n handles.timers = await setupTimers(context);\n\n // Path module\n handles.path = await setupPath(context);\n\n // Crypto (randomUUID, getRandomValues)\n handles.crypto = await setupCrypto(context);\n\n // Fetch API\n handles.fetch = await setupFetch(context, opts.fetch);\n\n // File system (only if handler provided)\n if (opts.fs) {\n handles.fs = await setupFs(context, opts.fs);\n }\n\n return {\n isolate,\n context,\n fetch: handles.fetch!,\n async tick(ms?: number) {\n await handles.timers!.tick(ms);\n },\n dispose() {\n // Dispose all handles\n handles.fs?.dispose();\n handles.fetch?.dispose();\n handles.crypto?.dispose();\n handles.path?.dispose();\n handles.timers?.dispose();\n handles.encoding?.dispose();\n handles.console?.dispose();\n handles.core?.dispose();\n\n // Release context and dispose isolate\n context.release();\n isolate.dispose();\n },\n };\n}\n\n// Re-export all package types and functions\nexport { setupCore } from \"@ricsam/isolate-core\";\nexport type { CoreHandle, SetupCoreOptions } from \"@ricsam/isolate-core\";\n\nexport { setupConsole } from \"@ricsam/isolate-console\";\nexport type { ConsoleHandle, ConsoleOptions } from \"@ricsam/isolate-console\";\n\nexport { setupCrypto } from \"@ricsam/isolate-crypto\";\nexport type { CryptoHandle } from \"@ricsam/isolate-crypto\";\n\nexport { setupEncoding } from \"@ricsam/isolate-encoding\";\nexport type { EncodingHandle } from \"@ricsam/isolate-encoding\";\n\nexport { setupFetch } from \"@ricsam/isolate-fetch\";\nexport type { FetchHandle, FetchOptions, WebSocketCommand, UpgradeRequest } from \"@ricsam/isolate-fetch\";\n\nexport { setupFs, createNodeFileSystemHandler } from \"@ricsam/isolate-fs\";\nexport type { FsHandle, FsOptions, FileSystemHandler, NodeFileSystemHandlerOptions } from \"@ricsam/isolate-fs\";\n\nexport { setupPath } from \"@ricsam/isolate-path\";\nexport type { PathHandle } from \"@ricsam/isolate-path\";\n\nexport { setupTimers } from \"@ricsam/isolate-timers\";\nexport type { TimersHandle } from \"@ricsam/isolate-timers\";\n"
6
+ ],
7
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAgB,IAAhB;AAC0B,IAA1B;AAC6B,IAA7B;AAC8B,IAA9B;AAC4B,IAA5B;AAC0B,IAA1B;AAC4B,IAA5B;AAC2B,IAA3B;AACwB,IAAxB;AAqI0B,IAA1B;AAG6B,IAA7B;AAG4B,IAA5B;AAG8B,IAA9B;AAG2B,IAA3B;AAGqD,IAArD;AAG0B,IAA1B;AAG4B,IAA5B;AAjGA,eAAsB,aAAa,CACjC,SACwB;AAAA,EACxB,MAAM,OAAO,WAAW,CAAC;AAAA,EAGzB,MAAM,UAAU,IAAI,2BAAI,QAAQ;AAAA,IAC9B,aAAa,KAAK;AAAA,EACpB,CAAC;AAAA,EACD,MAAM,UAAU,MAAM,QAAQ,cAAc;AAAA,EAG5C,MAAM,UASF,CAAC;AAAA,EAIL,QAAQ,OAAO,MAAM,8BAAU,OAAO;AAAA,EAGtC,QAAQ,UAAU,MAAM,oCAAa,SAAS,KAAK,OAAO;AAAA,EAG1D,QAAQ,WAAW,MAAM,sCAAc,OAAO;AAAA,EAG9C,QAAQ,SAAS,MAAM,kCAAY,OAAO;AAAA,EAG1C,QAAQ,OAAO,MAAM,8BAAU,OAAO;AAAA,EAGtC,QAAQ,SAAS,MAAM,kCAAY,OAAO;AAAA,EAG1C,QAAQ,QAAQ,MAAM,gCAAW,SAAS,KAAK,KAAK;AAAA,EAGpD,IAAI,KAAK,IAAI;AAAA,IACX,QAAQ,KAAK,MAAM,0BAAQ,SAAS,KAAK,EAAE;AAAA,EAC7C;AAAA,EAEA,OAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAO,QAAQ;AAAA,SACT,KAAI,CAAC,IAAa;AAAA,MACtB,MAAM,QAAQ,OAAQ,KAAK,EAAE;AAAA;AAAA,IAE/B,OAAO,GAAG;AAAA,MAER,QAAQ,IAAI,QAAQ;AAAA,MACpB,QAAQ,OAAO,QAAQ;AAAA,MACvB,QAAQ,QAAQ,QAAQ;AAAA,MACxB,QAAQ,MAAM,QAAQ;AAAA,MACtB,QAAQ,QAAQ,QAAQ;AAAA,MACxB,QAAQ,UAAU,QAAQ;AAAA,MAC1B,QAAQ,SAAS,QAAQ;AAAA,MACzB,QAAQ,MAAM,QAAQ;AAAA,MAGtB,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ;AAAA;AAAA,EAEpB;AAAA;",
8
+ "debugId": "4A635399139F1BA664756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "@ricsam/isolate-runtime",
3
+ "version": "0.1.3",
4
+ "type": "commonjs"
5
+ }
@@ -0,0 +1,71 @@
1
+ // @bun
2
+ // packages/runtime/src/index.ts
3
+ import ivm from "isolated-vm";
4
+ import { setupCore } from "@ricsam/isolate-core";
5
+ import { setupConsole } from "@ricsam/isolate-console";
6
+ import { setupEncoding } from "@ricsam/isolate-encoding";
7
+ import { setupTimers } from "@ricsam/isolate-timers";
8
+ import { setupPath } from "@ricsam/isolate-path";
9
+ import { setupCrypto } from "@ricsam/isolate-crypto";
10
+ import { setupFetch } from "@ricsam/isolate-fetch";
11
+ import { setupFs } from "@ricsam/isolate-fs";
12
+ import { setupCore as setupCore2 } from "@ricsam/isolate-core";
13
+ import { setupConsole as setupConsole2 } from "@ricsam/isolate-console";
14
+ import { setupCrypto as setupCrypto2 } from "@ricsam/isolate-crypto";
15
+ import { setupEncoding as setupEncoding2 } from "@ricsam/isolate-encoding";
16
+ import { setupFetch as setupFetch2 } from "@ricsam/isolate-fetch";
17
+ import { setupFs as setupFs2, createNodeFileSystemHandler } from "@ricsam/isolate-fs";
18
+ import { setupPath as setupPath2 } from "@ricsam/isolate-path";
19
+ import { setupTimers as setupTimers2 } from "@ricsam/isolate-timers";
20
+ async function createRuntime(options) {
21
+ const opts = options ?? {};
22
+ const isolate = new ivm.Isolate({
23
+ memoryLimit: opts.memoryLimit
24
+ });
25
+ const context = await isolate.createContext();
26
+ const handles = {};
27
+ handles.core = await setupCore(context);
28
+ handles.console = await setupConsole(context, opts.console);
29
+ handles.encoding = await setupEncoding(context);
30
+ handles.timers = await setupTimers(context);
31
+ handles.path = await setupPath(context);
32
+ handles.crypto = await setupCrypto(context);
33
+ handles.fetch = await setupFetch(context, opts.fetch);
34
+ if (opts.fs) {
35
+ handles.fs = await setupFs(context, opts.fs);
36
+ }
37
+ return {
38
+ isolate,
39
+ context,
40
+ fetch: handles.fetch,
41
+ async tick(ms) {
42
+ await handles.timers.tick(ms);
43
+ },
44
+ dispose() {
45
+ handles.fs?.dispose();
46
+ handles.fetch?.dispose();
47
+ handles.crypto?.dispose();
48
+ handles.path?.dispose();
49
+ handles.timers?.dispose();
50
+ handles.encoding?.dispose();
51
+ handles.console?.dispose();
52
+ handles.core?.dispose();
53
+ context.release();
54
+ isolate.dispose();
55
+ }
56
+ };
57
+ }
58
+ export {
59
+ setupTimers2 as setupTimers,
60
+ setupPath2 as setupPath,
61
+ setupFs2 as setupFs,
62
+ setupFetch2 as setupFetch,
63
+ setupEncoding2 as setupEncoding,
64
+ setupCrypto2 as setupCrypto,
65
+ setupCore2 as setupCore,
66
+ setupConsole2 as setupConsole,
67
+ createRuntime,
68
+ createNodeFileSystemHandler
69
+ };
70
+
71
+ //# debugId=FDBFD94222E0044364756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/index.ts"],
4
+ "sourcesContent": [
5
+ "import ivm from \"isolated-vm\";\nimport { setupCore } from \"@ricsam/isolate-core\";\nimport { setupConsole } from \"@ricsam/isolate-console\";\nimport { setupEncoding } from \"@ricsam/isolate-encoding\";\nimport { setupTimers } from \"@ricsam/isolate-timers\";\nimport { setupPath } from \"@ricsam/isolate-path\";\nimport { setupCrypto } from \"@ricsam/isolate-crypto\";\nimport { setupFetch } from \"@ricsam/isolate-fetch\";\nimport { setupFs } from \"@ricsam/isolate-fs\";\n\nimport type { ConsoleOptions, ConsoleHandle } from \"@ricsam/isolate-console\";\nimport type { FetchOptions, FetchHandle } from \"@ricsam/isolate-fetch\";\nimport type { FsOptions, FsHandle } from \"@ricsam/isolate-fs\";\nimport type { CoreHandle } from \"@ricsam/isolate-core\";\nimport type { EncodingHandle } from \"@ricsam/isolate-encoding\";\nimport type { TimersHandle } from \"@ricsam/isolate-timers\";\nimport type { PathHandle } from \"@ricsam/isolate-path\";\nimport type { CryptoHandle } from \"@ricsam/isolate-crypto\";\n\nexport interface RuntimeOptions {\n /** Isolate memory limit in MB */\n memoryLimit?: number;\n /** Console options */\n console?: ConsoleOptions;\n /** Fetch options */\n fetch?: FetchOptions;\n /** File system options (optional - fs only set up if provided) */\n fs?: FsOptions;\n}\n\nexport interface RuntimeHandle {\n /** The isolate instance */\n readonly isolate: ivm.Isolate;\n /** The context instance */\n readonly context: ivm.Context;\n /** The fetch handle for serve() and WebSocket dispatching */\n readonly fetch: FetchHandle;\n /** Process pending timers */\n tick(ms?: number): Promise<void>;\n /** Dispose all resources */\n dispose(): void;\n}\n\n/**\n * Create a fully configured isolated-vm runtime\n *\n * Sets up all WHATWG APIs: fetch, fs, console, crypto, encoding, timers\n *\n * @example\n * const runtime = await createRuntime({\n * console: {\n * onLog: (level, ...args) => console.log(`[${level}]`, ...args)\n * },\n * fetch: {\n * onFetch: async (request) => fetch(request)\n * }\n * });\n *\n * await runtime.context.eval(`\n * console.log(\"Hello from sandbox!\");\n * const response = await fetch(\"https://example.com\");\n * `);\n *\n * runtime.dispose();\n */\nexport async function createRuntime(\n options?: RuntimeOptions\n): Promise<RuntimeHandle> {\n const opts = options ?? {};\n\n // Create isolate with optional memory limit\n const isolate = new ivm.Isolate({\n memoryLimit: opts.memoryLimit,\n });\n const context = await isolate.createContext();\n\n // Store all handles for disposal\n const handles: {\n core?: CoreHandle;\n console?: ConsoleHandle;\n encoding?: EncodingHandle;\n timers?: TimersHandle;\n path?: PathHandle;\n crypto?: CryptoHandle;\n fetch?: FetchHandle;\n fs?: FsHandle;\n } = {};\n\n // Setup all APIs in order\n // Core must be first as it provides Blob, File, streams, URL, etc.\n handles.core = await setupCore(context);\n\n // Console\n handles.console = await setupConsole(context, opts.console);\n\n // Encoding (btoa/atob)\n handles.encoding = await setupEncoding(context);\n\n // Timers (setTimeout, setInterval)\n handles.timers = await setupTimers(context);\n\n // Path module\n handles.path = await setupPath(context);\n\n // Crypto (randomUUID, getRandomValues)\n handles.crypto = await setupCrypto(context);\n\n // Fetch API\n handles.fetch = await setupFetch(context, opts.fetch);\n\n // File system (only if handler provided)\n if (opts.fs) {\n handles.fs = await setupFs(context, opts.fs);\n }\n\n return {\n isolate,\n context,\n fetch: handles.fetch!,\n async tick(ms?: number) {\n await handles.timers!.tick(ms);\n },\n dispose() {\n // Dispose all handles\n handles.fs?.dispose();\n handles.fetch?.dispose();\n handles.crypto?.dispose();\n handles.path?.dispose();\n handles.timers?.dispose();\n handles.encoding?.dispose();\n handles.console?.dispose();\n handles.core?.dispose();\n\n // Release context and dispose isolate\n context.release();\n isolate.dispose();\n },\n };\n}\n\n// Re-export all package types and functions\nexport { setupCore } from \"@ricsam/isolate-core\";\nexport type { CoreHandle, SetupCoreOptions } from \"@ricsam/isolate-core\";\n\nexport { setupConsole } from \"@ricsam/isolate-console\";\nexport type { ConsoleHandle, ConsoleOptions } from \"@ricsam/isolate-console\";\n\nexport { setupCrypto } from \"@ricsam/isolate-crypto\";\nexport type { CryptoHandle } from \"@ricsam/isolate-crypto\";\n\nexport { setupEncoding } from \"@ricsam/isolate-encoding\";\nexport type { EncodingHandle } from \"@ricsam/isolate-encoding\";\n\nexport { setupFetch } from \"@ricsam/isolate-fetch\";\nexport type { FetchHandle, FetchOptions, WebSocketCommand, UpgradeRequest } from \"@ricsam/isolate-fetch\";\n\nexport { setupFs, createNodeFileSystemHandler } from \"@ricsam/isolate-fs\";\nexport type { FsHandle, FsOptions, FileSystemHandler, NodeFileSystemHandlerOptions } from \"@ricsam/isolate-fs\";\n\nexport { setupPath } from \"@ricsam/isolate-path\";\nexport type { PathHandle } from \"@ricsam/isolate-path\";\n\nexport { setupTimers } from \"@ricsam/isolate-timers\";\nexport type { TimersHandle } from \"@ricsam/isolate-timers\";\n"
6
+ ],
7
+ "mappings": ";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAqIA,sBAAS;AAGT,yBAAS;AAGT,wBAAS;AAGT,0BAAS;AAGT,uBAAS;AAGT,oBAAS;AAGT,sBAAS;AAGT,wBAAS;AAjGT,eAAsB,aAAa,CACjC,SACwB;AAAA,EACxB,MAAM,OAAO,WAAW,CAAC;AAAA,EAGzB,MAAM,UAAU,IAAI,IAAI,QAAQ;AAAA,IAC9B,aAAa,KAAK;AAAA,EACpB,CAAC;AAAA,EACD,MAAM,UAAU,MAAM,QAAQ,cAAc;AAAA,EAG5C,MAAM,UASF,CAAC;AAAA,EAIL,QAAQ,OAAO,MAAM,UAAU,OAAO;AAAA,EAGtC,QAAQ,UAAU,MAAM,aAAa,SAAS,KAAK,OAAO;AAAA,EAG1D,QAAQ,WAAW,MAAM,cAAc,OAAO;AAAA,EAG9C,QAAQ,SAAS,MAAM,YAAY,OAAO;AAAA,EAG1C,QAAQ,OAAO,MAAM,UAAU,OAAO;AAAA,EAGtC,QAAQ,SAAS,MAAM,YAAY,OAAO;AAAA,EAG1C,QAAQ,QAAQ,MAAM,WAAW,SAAS,KAAK,KAAK;AAAA,EAGpD,IAAI,KAAK,IAAI;AAAA,IACX,QAAQ,KAAK,MAAM,QAAQ,SAAS,KAAK,EAAE;AAAA,EAC7C;AAAA,EAEA,OAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAO,QAAQ;AAAA,SACT,KAAI,CAAC,IAAa;AAAA,MACtB,MAAM,QAAQ,OAAQ,KAAK,EAAE;AAAA;AAAA,IAE/B,OAAO,GAAG;AAAA,MAER,QAAQ,IAAI,QAAQ;AAAA,MACpB,QAAQ,OAAO,QAAQ;AAAA,MACvB,QAAQ,QAAQ,QAAQ;AAAA,MACxB,QAAQ,MAAM,QAAQ;AAAA,MACtB,QAAQ,QAAQ,QAAQ;AAAA,MACxB,QAAQ,UAAU,QAAQ;AAAA,MAC1B,QAAQ,SAAS,QAAQ;AAAA,MACzB,QAAQ,MAAM,QAAQ;AAAA,MAGtB,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ;AAAA;AAAA,EAEpB;AAAA;",
8
+ "debugId": "FDBFD94222E0044364756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "@ricsam/isolate-runtime",
3
+ "version": "0.1.3",
4
+ "type": "module"
5
+ }
@@ -0,0 +1,65 @@
1
+ import ivm from "isolated-vm";
2
+ import type { ConsoleOptions } from "@ricsam/isolate-console";
3
+ import type { FetchOptions, FetchHandle } from "@ricsam/isolate-fetch";
4
+ import type { FsOptions } from "@ricsam/isolate-fs";
5
+ export interface RuntimeOptions {
6
+ /** Isolate memory limit in MB */
7
+ memoryLimit?: number;
8
+ /** Console options */
9
+ console?: ConsoleOptions;
10
+ /** Fetch options */
11
+ fetch?: FetchOptions;
12
+ /** File system options (optional - fs only set up if provided) */
13
+ fs?: FsOptions;
14
+ }
15
+ export interface RuntimeHandle {
16
+ /** The isolate instance */
17
+ readonly isolate: ivm.Isolate;
18
+ /** The context instance */
19
+ readonly context: ivm.Context;
20
+ /** The fetch handle for serve() and WebSocket dispatching */
21
+ readonly fetch: FetchHandle;
22
+ /** Process pending timers */
23
+ tick(ms?: number): Promise<void>;
24
+ /** Dispose all resources */
25
+ dispose(): void;
26
+ }
27
+ /**
28
+ * Create a fully configured isolated-vm runtime
29
+ *
30
+ * Sets up all WHATWG APIs: fetch, fs, console, crypto, encoding, timers
31
+ *
32
+ * @example
33
+ * const runtime = await createRuntime({
34
+ * console: {
35
+ * onLog: (level, ...args) => console.log(`[${level}]`, ...args)
36
+ * },
37
+ * fetch: {
38
+ * onFetch: async (request) => fetch(request)
39
+ * }
40
+ * });
41
+ *
42
+ * await runtime.context.eval(`
43
+ * console.log("Hello from sandbox!");
44
+ * const response = await fetch("https://example.com");
45
+ * `);
46
+ *
47
+ * runtime.dispose();
48
+ */
49
+ export declare function createRuntime(options?: RuntimeOptions): Promise<RuntimeHandle>;
50
+ export { setupCore } from "@ricsam/isolate-core";
51
+ export type { CoreHandle, SetupCoreOptions } from "@ricsam/isolate-core";
52
+ export { setupConsole } from "@ricsam/isolate-console";
53
+ export type { ConsoleHandle, ConsoleOptions } from "@ricsam/isolate-console";
54
+ export { setupCrypto } from "@ricsam/isolate-crypto";
55
+ export type { CryptoHandle } from "@ricsam/isolate-crypto";
56
+ export { setupEncoding } from "@ricsam/isolate-encoding";
57
+ export type { EncodingHandle } from "@ricsam/isolate-encoding";
58
+ export { setupFetch } from "@ricsam/isolate-fetch";
59
+ export type { FetchHandle, FetchOptions, WebSocketCommand, UpgradeRequest } from "@ricsam/isolate-fetch";
60
+ export { setupFs, createNodeFileSystemHandler } from "@ricsam/isolate-fs";
61
+ export type { FsHandle, FsOptions, FileSystemHandler, NodeFileSystemHandlerOptions } from "@ricsam/isolate-fs";
62
+ export { setupPath } from "@ricsam/isolate-path";
63
+ export type { PathHandle } from "@ricsam/isolate-path";
64
+ export { setupTimers } from "@ricsam/isolate-timers";
65
+ export type { TimersHandle } from "@ricsam/isolate-timers";
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@ricsam/isolate-runtime",
3
- "version": "0.1.1",
4
- "type": "module",
5
- "main": "./src/index.ts",
6
- "types": "./src/index.ts",
3
+ "version": "0.1.3",
4
+ "main": "./dist/cjs/index.cjs",
5
+ "types": "./dist/types/index.d.ts",
7
6
  "exports": {
8
7
  ".": {
9
- "import": "./src/index.ts",
10
- "types": "./src/index.ts"
8
+ "types": "./dist/types/index.d.ts",
9
+ "require": "./dist/cjs/index.cjs",
10
+ "import": "./dist/mjs/index.mjs"
11
11
  }
12
12
  },
13
13
  "scripts": {
@@ -25,12 +25,37 @@
25
25
  "@ricsam/isolate-timers": "*",
26
26
  "isolated-vm": "^6"
27
27
  },
28
- "devDependencies": {
29
- "@ricsam/isolate-test-utils": "*",
30
- "@types/node": "^24",
31
- "typescript": "^5"
32
- },
33
28
  "peerDependencies": {
34
29
  "isolated-vm": "^6"
35
- }
36
- }
30
+ },
31
+ "author": "Richard Samuelsson",
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/ricsam/isolate.git"
36
+ },
37
+ "bugs": {
38
+ "url": "https://github.com/ricsam/isolate/issues"
39
+ },
40
+ "homepage": "https://github.com/ricsam/isolate#readme",
41
+ "keywords": [
42
+ "isolated-vm",
43
+ "sandbox",
44
+ "javascript",
45
+ "runtime",
46
+ "fetch",
47
+ "filesystem",
48
+ "streams",
49
+ "v8",
50
+ "isolate"
51
+ ],
52
+ "description": "Complete isolated-vm V8 sandbox runtime with fetch, fs, and core bindings",
53
+ "module": "./dist/mjs/index.mjs",
54
+ "publishConfig": {
55
+ "access": "public"
56
+ },
57
+ "files": [
58
+ "dist",
59
+ "README.md"
60
+ ]
61
+ }
package/CHANGELOG.md DELETED
@@ -1,15 +0,0 @@
1
- # @ricsam/isolate-runtime
2
-
3
- ## 0.1.1
4
-
5
- ### Patch Changes
6
-
7
- - initial release
8
- - Updated dependencies
9
- - @ricsam/isolate-console@0.1.1
10
- - @ricsam/isolate-core@0.1.1
11
- - @ricsam/isolate-crypto@0.1.1
12
- - @ricsam/isolate-encoding@0.1.1
13
- - @ricsam/isolate-fetch@0.1.1
14
- - @ricsam/isolate-fs@0.1.1
15
- - @ricsam/isolate-timers@0.1.1
package/src/index.test.ts DELETED
@@ -1,503 +0,0 @@
1
- import { test, describe } from "node:test";
2
- import assert from "node:assert";
3
- import { createRuntime, type RuntimeHandle } from "./index.ts";
4
-
5
- describe("@ricsam/isolate-runtime", () => {
6
- describe("createRuntime", () => {
7
- test("creates runtime with default options", async () => {
8
- const runtime = await createRuntime();
9
- try {
10
- assert(runtime.isolate, "isolate should be defined");
11
- assert(runtime.context, "context should be defined");
12
- assert(typeof runtime.tick === "function", "tick should be a function");
13
- assert(
14
- typeof runtime.dispose === "function",
15
- "dispose should be a function"
16
- );
17
- } finally {
18
- runtime.dispose();
19
- }
20
- });
21
-
22
- test("runtime has all globals defined", async () => {
23
- const runtime = await createRuntime();
24
- try {
25
- const result = await runtime.context.eval(`
26
- JSON.stringify({
27
- hasFetch: typeof fetch === 'function',
28
- hasConsole: typeof console === 'object',
29
- hasCrypto: typeof crypto === 'object',
30
- hasSetTimeout: typeof setTimeout === 'function',
31
- hasSetInterval: typeof setInterval === 'function',
32
- hasClearTimeout: typeof clearTimeout === 'function',
33
- hasClearInterval: typeof clearInterval === 'function',
34
- hasPath: typeof path === 'object',
35
- hasTextEncoder: typeof TextEncoder === 'function',
36
- hasTextDecoder: typeof TextDecoder === 'function',
37
- hasBlob: typeof Blob === 'function',
38
- hasFile: typeof File === 'function',
39
- hasURL: typeof URL === 'function',
40
- hasURLSearchParams: typeof URLSearchParams === 'function',
41
- hasHeaders: typeof Headers === 'function',
42
- hasRequest: typeof Request === 'function',
43
- hasResponse: typeof Response === 'function',
44
- hasFormData: typeof FormData === 'function',
45
- hasAbortController: typeof AbortController === 'function',
46
- hasAbortSignal: typeof AbortSignal === 'function',
47
- hasReadableStream: typeof ReadableStream === 'function',
48
- hasBtoa: typeof btoa === 'function',
49
- hasAtob: typeof atob === 'function',
50
- })
51
- `);
52
- const globals = JSON.parse(result as string);
53
-
54
- assert.strictEqual(globals.hasFetch, true, "fetch should be defined");
55
- assert.strictEqual(
56
- globals.hasConsole,
57
- true,
58
- "console should be defined"
59
- );
60
- assert.strictEqual(globals.hasCrypto, true, "crypto should be defined");
61
- assert.strictEqual(
62
- globals.hasSetTimeout,
63
- true,
64
- "setTimeout should be defined"
65
- );
66
- assert.strictEqual(
67
- globals.hasSetInterval,
68
- true,
69
- "setInterval should be defined"
70
- );
71
- assert.strictEqual(
72
- globals.hasClearTimeout,
73
- true,
74
- "clearTimeout should be defined"
75
- );
76
- assert.strictEqual(
77
- globals.hasClearInterval,
78
- true,
79
- "clearInterval should be defined"
80
- );
81
- assert.strictEqual(globals.hasPath, true, "path should be defined");
82
- assert.strictEqual(
83
- globals.hasTextEncoder,
84
- true,
85
- "TextEncoder should be defined"
86
- );
87
- assert.strictEqual(
88
- globals.hasTextDecoder,
89
- true,
90
- "TextDecoder should be defined"
91
- );
92
- assert.strictEqual(globals.hasBlob, true, "Blob should be defined");
93
- assert.strictEqual(globals.hasFile, true, "File should be defined");
94
- assert.strictEqual(globals.hasURL, true, "URL should be defined");
95
- assert.strictEqual(
96
- globals.hasURLSearchParams,
97
- true,
98
- "URLSearchParams should be defined"
99
- );
100
- assert.strictEqual(
101
- globals.hasHeaders,
102
- true,
103
- "Headers should be defined"
104
- );
105
- assert.strictEqual(
106
- globals.hasRequest,
107
- true,
108
- "Request should be defined"
109
- );
110
- assert.strictEqual(
111
- globals.hasResponse,
112
- true,
113
- "Response should be defined"
114
- );
115
- assert.strictEqual(
116
- globals.hasFormData,
117
- true,
118
- "FormData should be defined"
119
- );
120
- assert.strictEqual(
121
- globals.hasAbortController,
122
- true,
123
- "AbortController should be defined"
124
- );
125
- assert.strictEqual(
126
- globals.hasAbortSignal,
127
- true,
128
- "AbortSignal should be defined"
129
- );
130
- assert.strictEqual(
131
- globals.hasReadableStream,
132
- true,
133
- "ReadableStream should be defined"
134
- );
135
- assert.strictEqual(globals.hasBtoa, true, "btoa should be defined");
136
- assert.strictEqual(globals.hasAtob, true, "atob should be defined");
137
- } finally {
138
- runtime.dispose();
139
- }
140
- });
141
-
142
- test("dispose cleans up resources", async () => {
143
- const runtime = await createRuntime();
144
- runtime.dispose();
145
-
146
- // After dispose, the isolate should be disposed
147
- // Attempting to use it should throw
148
- assert.throws(
149
- () => {
150
- runtime.isolate.createContextSync();
151
- },
152
- /disposed/i,
153
- "isolate should be disposed"
154
- );
155
- });
156
-
157
- test("accepts memory limit option", async () => {
158
- const runtime = await createRuntime({
159
- memoryLimit: 128,
160
- });
161
- try {
162
- assert(runtime.isolate, "isolate should be created with memory limit");
163
- } finally {
164
- runtime.dispose();
165
- }
166
- });
167
- });
168
-
169
- describe("console integration", () => {
170
- test("console.log is captured", async () => {
171
- const logs: Array<{ level: string; args: unknown[] }> = [];
172
-
173
- const runtime = await createRuntime({
174
- console: {
175
- onLog: (level, ...args) => {
176
- logs.push({ level, args });
177
- },
178
- },
179
- });
180
-
181
- try {
182
- await runtime.context.eval(`
183
- console.log("hello", "world");
184
- console.warn("warning message");
185
- console.error("error message");
186
- `);
187
-
188
- assert.strictEqual(logs.length, 3, "should have captured 3 logs");
189
- assert.strictEqual(logs[0].level, "log");
190
- assert.deepStrictEqual(logs[0].args, ["hello", "world"]);
191
- assert.strictEqual(logs[1].level, "warn");
192
- assert.deepStrictEqual(logs[1].args, ["warning message"]);
193
- assert.strictEqual(logs[2].level, "error");
194
- assert.deepStrictEqual(logs[2].args, ["error message"]);
195
- } finally {
196
- runtime.dispose();
197
- }
198
- });
199
- });
200
-
201
- describe("fetch integration", () => {
202
- test("fetch calls onFetch handler", async () => {
203
- let capturedRequest: Request | null = null;
204
-
205
- const runtime = await createRuntime({
206
- fetch: {
207
- onFetch: async (request) => {
208
- capturedRequest = request;
209
- return new Response(JSON.stringify({ message: "mocked" }), {
210
- status: 200,
211
- headers: { "Content-Type": "application/json" },
212
- });
213
- },
214
- },
215
- });
216
-
217
- try {
218
- const result = await runtime.context.eval(
219
- `
220
- (async () => {
221
- const response = await fetch("https://example.com/api", {
222
- method: "POST",
223
- headers: { "X-Custom": "header" },
224
- body: "test body"
225
- });
226
- return JSON.stringify({
227
- status: response.status,
228
- body: await response.json()
229
- });
230
- })()
231
- `,
232
- { promise: true }
233
- );
234
-
235
- const data = JSON.parse(result as string);
236
- assert.strictEqual(data.status, 200);
237
- assert.deepStrictEqual(data.body, { message: "mocked" });
238
-
239
- // Verify the request was captured correctly
240
- assert(capturedRequest, "request should be captured");
241
- assert.strictEqual(capturedRequest!.url, "https://example.com/api");
242
- assert.strictEqual(capturedRequest!.method, "POST");
243
- assert.strictEqual(capturedRequest!.headers.get("X-Custom"), "header");
244
- } finally {
245
- runtime.dispose();
246
- }
247
- });
248
- });
249
-
250
- describe("timers integration", () => {
251
- test("setTimeout works with tick()", async () => {
252
- const runtime = await createRuntime();
253
-
254
- try {
255
- // Set up a timeout that modifies a global variable
256
- await runtime.context.eval(`
257
- globalThis.timerFired = false;
258
- globalThis.timerValue = 0;
259
- setTimeout(() => {
260
- globalThis.timerFired = true;
261
- globalThis.timerValue = 42;
262
- }, 100);
263
- `);
264
-
265
- // Before tick, timer should not have fired
266
- let result = await runtime.context.eval(`globalThis.timerFired`);
267
- assert.strictEqual(result, false, "timer should not fire before tick");
268
-
269
- // Tick forward 50ms - still not enough
270
- await runtime.tick(50);
271
- result = await runtime.context.eval(`globalThis.timerFired`);
272
- assert.strictEqual(result, false, "timer should not fire at 50ms");
273
-
274
- // Tick forward another 50ms (total 100ms) - now it should fire
275
- await runtime.tick(50);
276
- result = await runtime.context.eval(`globalThis.timerFired`);
277
- assert.strictEqual(result, true, "timer should fire at 100ms");
278
-
279
- result = await runtime.context.eval(`globalThis.timerValue`);
280
- assert.strictEqual(result, 42, "timer should have set value");
281
- } finally {
282
- runtime.dispose();
283
- }
284
- });
285
-
286
- test("setInterval works with tick()", async () => {
287
- const runtime = await createRuntime();
288
-
289
- try {
290
- await runtime.context.eval(`
291
- globalThis.intervalCount = 0;
292
- setInterval(() => {
293
- globalThis.intervalCount++;
294
- }, 100);
295
- `);
296
-
297
- // Tick incrementally - interval fires at each 100ms boundary
298
- await runtime.tick(100); // t=100ms, first fire
299
- let count = await runtime.context.eval(`globalThis.intervalCount`);
300
- assert.strictEqual(count, 1, "interval should fire once at 100ms");
301
-
302
- await runtime.tick(100); // t=200ms, second fire
303
- count = await runtime.context.eval(`globalThis.intervalCount`);
304
- assert.strictEqual(count, 2, "interval should fire twice at 200ms");
305
- } finally {
306
- runtime.dispose();
307
- }
308
- });
309
- });
310
-
311
- describe("crypto integration", () => {
312
- test("crypto.randomUUID generates valid UUIDs", async () => {
313
- const runtime = await createRuntime();
314
-
315
- try {
316
- const uuid = (await runtime.context.eval(
317
- `crypto.randomUUID()`
318
- )) as string;
319
- assert.match(
320
- uuid,
321
- /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
322
- "should generate valid UUID v4"
323
- );
324
- } finally {
325
- runtime.dispose();
326
- }
327
- });
328
- });
329
-
330
- describe("path integration", () => {
331
- test("path.join works correctly", async () => {
332
- const runtime = await createRuntime();
333
-
334
- try {
335
- const result = await runtime.context.eval(`path.join('a', 'b', 'c')`);
336
- assert.strictEqual(result, "a/b/c");
337
- } finally {
338
- runtime.dispose();
339
- }
340
- });
341
- });
342
-
343
- describe("encoding integration", () => {
344
- test("btoa and atob work correctly", async () => {
345
- const runtime = await createRuntime();
346
-
347
- try {
348
- const encoded = await runtime.context.eval(`btoa('hello')`);
349
- assert.strictEqual(encoded, "aGVsbG8=");
350
-
351
- const decoded = await runtime.context.eval(`atob('aGVsbG8=')`);
352
- assert.strictEqual(decoded, "hello");
353
- } finally {
354
- runtime.dispose();
355
- }
356
- });
357
- });
358
-
359
- describe("GC disposal", () => {
360
- test("resources are cleaned up on dispose", async () => {
361
- const runtime = await createRuntime();
362
-
363
- // Create some resources
364
- await runtime.context.eval(`
365
- const blob = new Blob(["test"]);
366
- const url = new URL("https://example.com");
367
- setTimeout(() => {}, 1000);
368
- `);
369
-
370
- // Dispose should not throw
371
- assert.doesNotThrow(() => {
372
- runtime.dispose();
373
- }, "dispose should not throw");
374
-
375
- // After dispose, attempting to use the context should fail
376
- await assert.rejects(
377
- async () => {
378
- await runtime.context.eval(`1 + 1`);
379
- },
380
- /released|disposed/i,
381
- "context should be released after dispose"
382
- );
383
- });
384
- });
385
-
386
- describe("fs integration", () => {
387
- test("getDirectory works when handler provided", async () => {
388
- const files = new Map<
389
- string,
390
- { data: Uint8Array; lastModified: number; type: string }
391
- >();
392
- const directories = new Set<string>(["/"]); // Root directory exists
393
-
394
- const createHandler = () => ({
395
- async getFileHandle(path: string, options?: { create?: boolean }) {
396
- if (!files.has(path) && !options?.create) {
397
- throw new Error("[NotFoundError]File not found");
398
- }
399
- if (!files.has(path) && options?.create) {
400
- files.set(path, {
401
- data: new Uint8Array(0),
402
- lastModified: Date.now(),
403
- type: "",
404
- });
405
- }
406
- },
407
- async getDirectoryHandle(path: string, options?: { create?: boolean }) {
408
- if (!directories.has(path) && !options?.create) {
409
- throw new Error("[NotFoundError]Directory not found");
410
- }
411
- if (options?.create) {
412
- directories.add(path);
413
- }
414
- },
415
- async removeEntry(path: string) {
416
- files.delete(path);
417
- directories.delete(path);
418
- },
419
- async readDirectory(path: string) {
420
- const entries: Array<{ name: string; kind: "file" | "directory" }> =
421
- [];
422
- for (const filePath of files.keys()) {
423
- const dir = filePath.substring(0, filePath.lastIndexOf("/")) || "/";
424
- if (dir === path) {
425
- entries.push({
426
- name: filePath.substring(filePath.lastIndexOf("/") + 1),
427
- kind: "file",
428
- });
429
- }
430
- }
431
- for (const dirPath of directories) {
432
- if (dirPath !== path && dirPath.startsWith(path)) {
433
- const relativePath = dirPath.substring(path.length);
434
- const parts = relativePath.split("/").filter(Boolean);
435
- if (parts.length === 1) {
436
- entries.push({ name: parts[0], kind: "directory" });
437
- }
438
- }
439
- }
440
- return entries;
441
- },
442
- async readFile(path: string) {
443
- const file = files.get(path);
444
- if (!file) {
445
- throw new Error("[NotFoundError]File not found");
446
- }
447
- return {
448
- data: file.data,
449
- size: file.data.length,
450
- lastModified: file.lastModified,
451
- type: file.type,
452
- };
453
- },
454
- async writeFile(path: string, data: Uint8Array) {
455
- const existing = files.get(path);
456
- files.set(path, {
457
- data,
458
- lastModified: Date.now(),
459
- type: existing?.type ?? "",
460
- });
461
- },
462
- async truncateFile(path: string, size: number) {
463
- const file = files.get(path);
464
- if (file) {
465
- file.data = file.data.slice(0, size);
466
- }
467
- },
468
- async getFileMetadata(path: string) {
469
- const file = files.get(path);
470
- if (!file) {
471
- throw new Error("[NotFoundError]File not found");
472
- }
473
- return {
474
- size: file.data.length,
475
- lastModified: file.lastModified,
476
- type: file.type,
477
- };
478
- },
479
- });
480
-
481
- const runtime = await createRuntime({
482
- fs: {
483
- getDirectory: async () => createHandler(),
484
- },
485
- });
486
-
487
- try {
488
- const result = await runtime.context.eval(
489
- `
490
- (async () => {
491
- const root = await getDirectory("/");
492
- return root.kind;
493
- })()
494
- `,
495
- { promise: true }
496
- );
497
- assert.strictEqual(result, "directory");
498
- } finally {
499
- runtime.dispose();
500
- }
501
- });
502
- });
503
- });
package/src/index.ts DELETED
@@ -1,164 +0,0 @@
1
- import ivm from "isolated-vm";
2
- import { setupCore } from "@ricsam/isolate-core";
3
- import { setupConsole } from "@ricsam/isolate-console";
4
- import { setupEncoding } from "@ricsam/isolate-encoding";
5
- import { setupTimers } from "@ricsam/isolate-timers";
6
- import { setupPath } from "@ricsam/isolate-path";
7
- import { setupCrypto } from "@ricsam/isolate-crypto";
8
- import { setupFetch } from "@ricsam/isolate-fetch";
9
- import { setupFs } from "@ricsam/isolate-fs";
10
-
11
- import type { ConsoleOptions, ConsoleHandle } from "@ricsam/isolate-console";
12
- import type { FetchOptions, FetchHandle } from "@ricsam/isolate-fetch";
13
- import type { FsOptions, FsHandle } from "@ricsam/isolate-fs";
14
- import type { CoreHandle } from "@ricsam/isolate-core";
15
- import type { EncodingHandle } from "@ricsam/isolate-encoding";
16
- import type { TimersHandle } from "@ricsam/isolate-timers";
17
- import type { PathHandle } from "@ricsam/isolate-path";
18
- import type { CryptoHandle } from "@ricsam/isolate-crypto";
19
-
20
- export interface RuntimeOptions {
21
- /** Isolate memory limit in MB */
22
- memoryLimit?: number;
23
- /** Console options */
24
- console?: ConsoleOptions;
25
- /** Fetch options */
26
- fetch?: FetchOptions;
27
- /** File system options (optional - fs only set up if provided) */
28
- fs?: FsOptions;
29
- }
30
-
31
- export interface RuntimeHandle {
32
- /** The isolate instance */
33
- readonly isolate: ivm.Isolate;
34
- /** The context instance */
35
- readonly context: ivm.Context;
36
- /** The fetch handle for serve() and WebSocket dispatching */
37
- readonly fetch: FetchHandle;
38
- /** Process pending timers */
39
- tick(ms?: number): Promise<void>;
40
- /** Dispose all resources */
41
- dispose(): void;
42
- }
43
-
44
- /**
45
- * Create a fully configured isolated-vm runtime
46
- *
47
- * Sets up all WHATWG APIs: fetch, fs, console, crypto, encoding, timers
48
- *
49
- * @example
50
- * const runtime = await createRuntime({
51
- * console: {
52
- * onLog: (level, ...args) => console.log(`[${level}]`, ...args)
53
- * },
54
- * fetch: {
55
- * onFetch: async (request) => fetch(request)
56
- * }
57
- * });
58
- *
59
- * await runtime.context.eval(`
60
- * console.log("Hello from sandbox!");
61
- * const response = await fetch("https://example.com");
62
- * `);
63
- *
64
- * runtime.dispose();
65
- */
66
- export async function createRuntime(
67
- options?: RuntimeOptions
68
- ): Promise<RuntimeHandle> {
69
- const opts = options ?? {};
70
-
71
- // Create isolate with optional memory limit
72
- const isolate = new ivm.Isolate({
73
- memoryLimit: opts.memoryLimit,
74
- });
75
- const context = await isolate.createContext();
76
-
77
- // Store all handles for disposal
78
- const handles: {
79
- core?: CoreHandle;
80
- console?: ConsoleHandle;
81
- encoding?: EncodingHandle;
82
- timers?: TimersHandle;
83
- path?: PathHandle;
84
- crypto?: CryptoHandle;
85
- fetch?: FetchHandle;
86
- fs?: FsHandle;
87
- } = {};
88
-
89
- // Setup all APIs in order
90
- // Core must be first as it provides Blob, File, streams, URL, etc.
91
- handles.core = await setupCore(context);
92
-
93
- // Console
94
- handles.console = await setupConsole(context, opts.console);
95
-
96
- // Encoding (btoa/atob)
97
- handles.encoding = await setupEncoding(context);
98
-
99
- // Timers (setTimeout, setInterval)
100
- handles.timers = await setupTimers(context);
101
-
102
- // Path module
103
- handles.path = await setupPath(context);
104
-
105
- // Crypto (randomUUID, getRandomValues)
106
- handles.crypto = await setupCrypto(context);
107
-
108
- // Fetch API
109
- handles.fetch = await setupFetch(context, opts.fetch);
110
-
111
- // File system (only if handler provided)
112
- if (opts.fs) {
113
- handles.fs = await setupFs(context, opts.fs);
114
- }
115
-
116
- return {
117
- isolate,
118
- context,
119
- fetch: handles.fetch!,
120
- async tick(ms?: number) {
121
- await handles.timers!.tick(ms);
122
- },
123
- dispose() {
124
- // Dispose all handles
125
- handles.fs?.dispose();
126
- handles.fetch?.dispose();
127
- handles.crypto?.dispose();
128
- handles.path?.dispose();
129
- handles.timers?.dispose();
130
- handles.encoding?.dispose();
131
- handles.console?.dispose();
132
- handles.core?.dispose();
133
-
134
- // Release context and dispose isolate
135
- context.release();
136
- isolate.dispose();
137
- },
138
- };
139
- }
140
-
141
- // Re-export all package types and functions
142
- export { setupCore } from "@ricsam/isolate-core";
143
- export type { CoreHandle, SetupCoreOptions } from "@ricsam/isolate-core";
144
-
145
- export { setupConsole } from "@ricsam/isolate-console";
146
- export type { ConsoleHandle, ConsoleOptions } from "@ricsam/isolate-console";
147
-
148
- export { setupCrypto } from "@ricsam/isolate-crypto";
149
- export type { CryptoHandle } from "@ricsam/isolate-crypto";
150
-
151
- export { setupEncoding } from "@ricsam/isolate-encoding";
152
- export type { EncodingHandle } from "@ricsam/isolate-encoding";
153
-
154
- export { setupFetch } from "@ricsam/isolate-fetch";
155
- export type { FetchHandle, FetchOptions, WebSocketCommand, UpgradeRequest } from "@ricsam/isolate-fetch";
156
-
157
- export { setupFs, createNodeFileSystemHandler } from "@ricsam/isolate-fs";
158
- export type { FsHandle, FsOptions, FileSystemHandler, NodeFileSystemHandlerOptions } from "@ricsam/isolate-fs";
159
-
160
- export { setupPath } from "@ricsam/isolate-path";
161
- export type { PathHandle } from "@ricsam/isolate-path";
162
-
163
- export { setupTimers } from "@ricsam/isolate-timers";
164
- export type { TimersHandle } from "@ricsam/isolate-timers";
package/tsconfig.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "rootDir": "./src"
5
- },
6
- "include": ["src/**/*"],
7
- "exclude": ["node_modules", "dist", "**/*.test.ts"]
8
- }