@ricsam/isolate-test-utils 0.1.10 → 0.1.11

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 (45) hide show
  1. package/dist/cjs/fetch-context.cjs +66 -0
  2. package/dist/cjs/fetch-context.cjs.map +10 -0
  3. package/dist/cjs/fs-context.cjs +73 -0
  4. package/dist/cjs/fs-context.cjs.map +10 -0
  5. package/dist/cjs/index.cjs +121 -0
  6. package/dist/cjs/index.cjs.map +10 -0
  7. package/{src/mock-fs.ts → dist/cjs/mock-fs.cjs} +63 -97
  8. package/dist/cjs/mock-fs.cjs.map +10 -0
  9. package/dist/cjs/native-input-test.cjs +347 -0
  10. package/dist/cjs/native-input-test.cjs.map +10 -0
  11. package/dist/cjs/package.json +5 -0
  12. package/dist/cjs/runtime-context.cjs +97 -0
  13. package/dist/cjs/runtime-context.cjs.map +10 -0
  14. package/dist/cjs/server.cjs +109 -0
  15. package/dist/cjs/server.cjs.map +10 -0
  16. package/dist/mjs/fetch-context.mjs +23 -0
  17. package/dist/mjs/fetch-context.mjs.map +10 -0
  18. package/dist/mjs/fs-context.mjs +30 -0
  19. package/dist/mjs/fs-context.mjs.map +10 -0
  20. package/dist/mjs/index.mjs +78 -0
  21. package/dist/mjs/index.mjs.map +10 -0
  22. package/dist/mjs/mock-fs.mjs +181 -0
  23. package/dist/mjs/mock-fs.mjs.map +10 -0
  24. package/{src/native-input-test.ts → dist/mjs/native-input-test.mjs} +60 -169
  25. package/dist/mjs/native-input-test.mjs.map +10 -0
  26. package/dist/mjs/package.json +5 -0
  27. package/dist/mjs/runtime-context.mjs +67 -0
  28. package/dist/mjs/runtime-context.mjs.map +10 -0
  29. package/dist/mjs/server.mjs +79 -0
  30. package/dist/mjs/server.mjs.map +10 -0
  31. package/dist/types/fetch-context.d.ts +7 -0
  32. package/dist/types/fs-context.d.ts +30 -0
  33. package/dist/types/index.d.ts +88 -0
  34. package/dist/types/mock-fs.d.ts +59 -0
  35. package/dist/types/native-input-test.d.ts +29 -0
  36. package/dist/types/runtime-context.d.ts +73 -0
  37. package/dist/types/server.d.ts +53 -0
  38. package/package.json +45 -18
  39. package/CHANGELOG.md +0 -119
  40. package/src/fetch-context.ts +0 -33
  41. package/src/fs-context.ts +0 -65
  42. package/src/index.test.ts +0 -472
  43. package/src/index.ts +0 -177
  44. package/src/runtime-context.ts +0 -148
  45. package/src/server.ts +0 -150
@@ -0,0 +1,23 @@
1
+ // packages/test-utils/src/fetch-context.ts
2
+ import ivmModule from "isolated-vm";
3
+ import { setupFetch, clearAllInstanceState } from "@ricsam/isolate-fetch";
4
+ async function createFetchTestContext() {
5
+ const isolate = new ivmModule.Isolate;
6
+ const context = await isolate.createContext();
7
+ clearAllInstanceState();
8
+ const fetchHandle = await setupFetch(context);
9
+ return {
10
+ isolate,
11
+ context,
12
+ dispose() {
13
+ fetchHandle.dispose();
14
+ context.release();
15
+ isolate.dispose();
16
+ }
17
+ };
18
+ }
19
+ export {
20
+ createFetchTestContext
21
+ };
22
+
23
+ //# debugId=DFD00F9EFAA140C964756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/fetch-context.ts"],
4
+ "sourcesContent": [
5
+ "import type ivm from \"isolated-vm\";\nimport type { TestContext } from \"./index.mjs\";\nimport ivmModule from \"isolated-vm\";\nimport { setupFetch, clearAllInstanceState } from \"@ricsam/isolate-fetch\";\n\nexport interface FetchTestContext extends TestContext {\n // Context with fetch APIs set up\n}\n\n/**\n * Create a test context with fetch APIs set up (Headers, Request, Response, FormData, fetch)\n */\nexport async function createFetchTestContext(): Promise<FetchTestContext> {\n const isolate = new ivmModule.Isolate();\n const context = await isolate.createContext();\n\n clearAllInstanceState();\n\n const fetchHandle = await setupFetch(context);\n\n return {\n isolate,\n context,\n dispose() {\n fetchHandle.dispose();\n context.release();\n isolate.dispose();\n },\n };\n}\n"
6
+ ],
7
+ "mappings": ";AAEA;AACA;AASA,eAAsB,sBAAsB,GAA8B;AAAA,EACxE,MAAM,UAAU,IAAI,UAAU;AAAA,EAC9B,MAAM,UAAU,MAAM,QAAQ,cAAc;AAAA,EAE5C,sBAAsB;AAAA,EAEtB,MAAM,cAAc,MAAM,WAAW,OAAO;AAAA,EAE5C,OAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAO,GAAG;AAAA,MACR,YAAY,QAAQ;AAAA,MACpB,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ;AAAA;AAAA,EAEpB;AAAA;",
8
+ "debugId": "DFD00F9EFAA140C964756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,30 @@
1
+ // packages/test-utils/src/fs-context.ts
2
+ import { MockFileSystem } from "./mock-fs.mjs";
3
+ import ivmModule from "isolated-vm";
4
+ import { setupFs } from "@ricsam/isolate-fs";
5
+ import { clearAllInstanceState } from "@ricsam/isolate-core";
6
+ import { setupCore } from "@ricsam/isolate-core";
7
+ async function createFsTestContext() {
8
+ const isolate = new ivmModule.Isolate;
9
+ const context = await isolate.createContext();
10
+ clearAllInstanceState();
11
+ const mockFs = new MockFileSystem;
12
+ const coreHandle = await setupCore(context);
13
+ const fsHandle = await setupFs(context, { getDirectory: async () => mockFs });
14
+ return {
15
+ isolate,
16
+ context,
17
+ mockFs,
18
+ dispose() {
19
+ fsHandle.dispose();
20
+ coreHandle.dispose();
21
+ context.release();
22
+ isolate.dispose();
23
+ }
24
+ };
25
+ }
26
+ export {
27
+ createFsTestContext
28
+ };
29
+
30
+ //# debugId=731949FF1E56804B64756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/fs-context.ts"],
4
+ "sourcesContent": [
5
+ "import type ivm from \"isolated-vm\";\nimport { MockFileSystem } from \"./mock-fs.mjs\";\nimport ivmModule from \"isolated-vm\";\nimport { setupFs } from \"@ricsam/isolate-fs\";\nimport { clearAllInstanceState } from \"@ricsam/isolate-core\";\nimport { setupCore } from \"@ricsam/isolate-core\";\n\nexport interface FsTestContext {\n isolate: ivm.Isolate;\n context: ivm.Context;\n mockFs: MockFileSystem;\n dispose(): void;\n}\n\n/**\n * Create a test context with file system APIs set up using a mock file system.\n *\n * @example\n * const ctx = await createFsTestContext();\n *\n * // Set up initial files\n * ctx.mockFs.setFile(\"/test.txt\", \"Hello, World!\");\n *\n * // Use file system APIs in the isolate\n * const result = await ctx.context.eval(`\n * (async () => {\n * const root = await navigator.storage.getDirectory();\n * const fileHandle = await root.getFileHandle(\"test.txt\");\n * const file = await fileHandle.getFile();\n * return await file.text();\n * })()\n * `, { promise: true });\n *\n * ctx.dispose();\n */\nexport async function createFsTestContext(): Promise<FsTestContext> {\n const isolate = new ivmModule.Isolate();\n const context = await isolate.createContext();\n\n // Clear any previous instance state\n clearAllInstanceState();\n\n // Create mock file system\n const mockFs = new MockFileSystem();\n\n // Setup core APIs (required for Blob, File, streams)\n const coreHandle = await setupCore(context);\n\n // Setup file system APIs with mock handler\n const fsHandle = await setupFs(context, { getDirectory: async () => mockFs });\n\n return {\n isolate,\n context,\n mockFs,\n dispose() {\n fsHandle.dispose();\n coreHandle.dispose();\n context.release();\n isolate.dispose();\n },\n };\n}\n"
6
+ ],
7
+ "mappings": ";AACA;AACA;AACA;AACA;AACA;AA8BA,eAAsB,mBAAmB,GAA2B;AAAA,EAClE,MAAM,UAAU,IAAI,UAAU;AAAA,EAC9B,MAAM,UAAU,MAAM,QAAQ,cAAc;AAAA,EAG5C,sBAAsB;AAAA,EAGtB,MAAM,SAAS,IAAI;AAAA,EAGnB,MAAM,aAAa,MAAM,UAAU,OAAO;AAAA,EAG1C,MAAM,WAAW,MAAM,QAAQ,SAAS,EAAE,cAAc,YAAY,OAAO,CAAC;AAAA,EAE5E,OAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO,GAAG;AAAA,MACR,SAAS,QAAQ;AAAA,MACjB,WAAW,QAAQ;AAAA,MACnB,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ;AAAA;AAAA,EAEpB;AAAA;",
8
+ "debugId": "731949FF1E56804B64756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,78 @@
1
+ // packages/test-utils/src/index.ts
2
+ import ivmModule from "isolated-vm";
3
+ import { setupCore } from "@ricsam/isolate-core";
4
+ import { MockFileSystem } from "./mock-fs.mjs";
5
+ import { createFsTestContext } from "./fs-context.mjs";
6
+ import { createRuntimeTestContext } from "./runtime-context.mjs";
7
+ import { startIntegrationServer } from "./server.mjs";
8
+ import { runTestCode } from "./native-input-test.mjs";
9
+ import { createFetchTestContext } from "./fetch-context.mjs";
10
+ async function createTestContext() {
11
+ const isolate = new ivmModule.Isolate;
12
+ const context = await isolate.createContext();
13
+ return {
14
+ isolate,
15
+ context,
16
+ dispose() {
17
+ context.release();
18
+ isolate.dispose();
19
+ }
20
+ };
21
+ }
22
+ async function createCoreTestContext() {
23
+ const isolate = new ivmModule.Isolate;
24
+ const context = await isolate.createContext();
25
+ const coreHandle = await setupCore(context);
26
+ return {
27
+ isolate,
28
+ context,
29
+ dispose() {
30
+ coreHandle.dispose();
31
+ context.release();
32
+ isolate.dispose();
33
+ }
34
+ };
35
+ }
36
+ function evalCode(context, code) {
37
+ return context.evalSync(code);
38
+ }
39
+ async function evalCodeAsync(context, code) {
40
+ return await context.eval(code, { promise: true });
41
+ }
42
+ function evalCodeJson(context, code) {
43
+ const jsonString = context.evalSync(code);
44
+ return JSON.parse(jsonString);
45
+ }
46
+ async function evalCodeJsonAsync(context, code) {
47
+ const jsonString = await context.eval(code, { promise: true });
48
+ return JSON.parse(jsonString);
49
+ }
50
+ async function injectGlobals(context, values) {
51
+ const global = context.global;
52
+ for (const [key, value] of Object.entries(values)) {
53
+ if (typeof value === "function") {
54
+ global.setSync(key, new ivmModule.Callback(value));
55
+ } else if (typeof value === "object" && value !== null) {
56
+ context.evalSync(`globalThis.${key} = ${JSON.stringify(value)}`);
57
+ } else {
58
+ global.setSync(key, value);
59
+ }
60
+ }
61
+ }
62
+ export {
63
+ startIntegrationServer,
64
+ runTestCode,
65
+ injectGlobals,
66
+ evalCodeJsonAsync,
67
+ evalCodeJson,
68
+ evalCodeAsync,
69
+ evalCode,
70
+ createTestContext,
71
+ createRuntimeTestContext,
72
+ createFsTestContext,
73
+ createFetchTestContext,
74
+ createCoreTestContext,
75
+ MockFileSystem
76
+ };
77
+
78
+ //# debugId=B594ED4B92E3950E64756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/index.ts"],
4
+ "sourcesContent": [
5
+ "import type ivm from \"isolated-vm\";\nimport ivmModule from \"isolated-vm\";\nimport { setupCore } from \"@ricsam/isolate-core\";\nimport { MockFileSystem } from \"./mock-fs.mjs\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface TestContext {\n isolate: ivm.Isolate;\n context: ivm.Context;\n dispose(): void;\n}\n\nexport interface TestResult<T> {\n result: T;\n logs: Array<{ level: string; args: unknown[] }>;\n}\n\n// ============================================================================\n// Context Creation\n// ============================================================================\n\n/**\n * Create a basic test context for isolated-vm tests.\n * This creates a bare context without any APIs set up.\n */\nexport async function createTestContext(): Promise<TestContext> {\n const isolate = new ivmModule.Isolate();\n const context = await isolate.createContext();\n\n return {\n isolate,\n context,\n dispose() {\n context.release();\n isolate.dispose();\n },\n };\n}\n\n/**\n * Create a test context with core APIs set up (Blob, File, URL, streams, etc.)\n */\nexport async function createCoreTestContext(): Promise<TestContext> {\n const isolate = new ivmModule.Isolate();\n const context = await isolate.createContext();\n const coreHandle = await setupCore(context);\n\n return {\n isolate,\n context,\n dispose() {\n coreHandle.dispose();\n context.release();\n isolate.dispose();\n },\n };\n}\n\n// ============================================================================\n// Code Evaluation Helpers\n// ============================================================================\n\n/**\n * Synchronously evaluate code and return typed result.\n * Use this for simple expressions that don't involve promises.\n *\n * @example\n * const result = evalCode<number>(ctx.context, \"1 + 1\");\n * // result === 2\n */\nexport function evalCode<T = unknown>(context: ivm.Context, code: string): T {\n return context.evalSync(code) as T;\n}\n\n/**\n * Asynchronously evaluate code that may return promises.\n * Automatically wraps code to handle promise resolution.\n *\n * @example\n * const result = await evalCodeAsync<string>(ctx.context, `\n * (async () => {\n * return \"hello\";\n * })()\n * `);\n */\nexport async function evalCodeAsync<T = unknown>(\n context: ivm.Context,\n code: string\n): Promise<T> {\n return (await context.eval(code, { promise: true })) as T;\n}\n\n/**\n * Evaluate code and return the result as JSON (for complex objects).\n * Useful when you need to extract structured data from the isolate.\n *\n * @example\n * const data = evalCodeJson<{ name: string }>(ctx.context, `\n * JSON.stringify({ name: \"test\" })\n * `);\n */\nexport function evalCodeJson<T = unknown>(context: ivm.Context, code: string): T {\n const jsonString = context.evalSync(code) as string;\n return JSON.parse(jsonString) as T;\n}\n\n/**\n * Evaluate async code and return the result as JSON (for complex objects).\n *\n * @example\n * const data = await evalCodeJsonAsync<{ status: number }>(ctx.context, `\n * (async () => {\n * const response = await fetch(\"...\");\n * return JSON.stringify({ status: response.status });\n * })()\n * `);\n */\nexport async function evalCodeJsonAsync<T = unknown>(\n context: ivm.Context,\n code: string\n): Promise<T> {\n const jsonString = (await context.eval(code, { promise: true })) as string;\n return JSON.parse(jsonString) as T;\n}\n\n/**\n * Inject values into the isolate's global scope before running code.\n *\n * @example\n * await injectGlobals(ctx.context, {\n * testInput: \"hello\",\n * testConfig: { debug: true }\n * });\n * const result = evalCode<string>(ctx.context, \"testInput\");\n */\nexport async function injectGlobals(\n context: ivm.Context,\n values: Record<string, unknown>\n): Promise<void> {\n const global = context.global;\n\n for (const [key, value] of Object.entries(values)) {\n if (typeof value === \"function\") {\n global.setSync(key, new ivmModule.Callback(value as (...args: unknown[]) => unknown));\n } else if (typeof value === \"object\" && value !== null) {\n // For objects, serialize as JSON and inject\n context.evalSync(`globalThis.${key} = ${JSON.stringify(value)}`);\n } else {\n // For primitives, set directly\n global.setSync(key, value);\n }\n }\n}\n\n// ============================================================================\n// Exports from other modules\n// ============================================================================\n\nexport { MockFileSystem } from \"./mock-fs.mjs\";\nexport { createFsTestContext } from \"./fs-context.mjs\";\nexport type { FsTestContext } from \"./fs-context.mjs\";\nexport { createRuntimeTestContext } from \"./runtime-context.mjs\";\nexport type { RuntimeTestContext } from \"./runtime-context.mjs\";\nexport { startIntegrationServer } from \"./server.mjs\";\nexport type { IntegrationServer } from \"./server.mjs\";\nexport { runTestCode } from \"./native-input-test.mjs\";\nexport type { TestRunner, TestRuntime } from \"./native-input-test.mjs\";\nexport { createFetchTestContext } from \"./fetch-context.mjs\";\nexport type { FetchTestContext } from \"./fetch-context.mjs\";\n\n// Re-export useful types\nexport type { FileSystemHandler } from \"@ricsam/isolate-fs\";\n"
6
+ ],
7
+ "mappings": ";AACA;AACA;AA+JA;AACA;AAEA;AAEA;AAEA;AAEA;AA9IA,eAAsB,iBAAiB,GAAyB;AAAA,EAC9D,MAAM,UAAU,IAAI,UAAU;AAAA,EAC9B,MAAM,UAAU,MAAM,QAAQ,cAAc;AAAA,EAE5C,OAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAO,GAAG;AAAA,MACR,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ;AAAA;AAAA,EAEpB;AAAA;AAMF,eAAsB,qBAAqB,GAAyB;AAAA,EAClE,MAAM,UAAU,IAAI,UAAU;AAAA,EAC9B,MAAM,UAAU,MAAM,QAAQ,cAAc;AAAA,EAC5C,MAAM,aAAa,MAAM,UAAU,OAAO;AAAA,EAE1C,OAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAO,GAAG;AAAA,MACR,WAAW,QAAQ;AAAA,MACnB,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ;AAAA;AAAA,EAEpB;AAAA;AAeK,SAAS,QAAqB,CAAC,SAAsB,MAAiB;AAAA,EAC3E,OAAO,QAAQ,SAAS,IAAI;AAAA;AAc9B,eAAsB,aAA0B,CAC9C,SACA,MACY;AAAA,EACZ,OAAQ,MAAM,QAAQ,KAAK,MAAM,EAAE,SAAS,KAAK,CAAC;AAAA;AAY7C,SAAS,YAAyB,CAAC,SAAsB,MAAiB;AAAA,EAC/E,MAAM,aAAa,QAAQ,SAAS,IAAI;AAAA,EACxC,OAAO,KAAK,MAAM,UAAU;AAAA;AAc9B,eAAsB,iBAA8B,CAClD,SACA,MACY;AAAA,EACZ,MAAM,aAAc,MAAM,QAAQ,KAAK,MAAM,EAAE,SAAS,KAAK,CAAC;AAAA,EAC9D,OAAO,KAAK,MAAM,UAAU;AAAA;AAa9B,eAAsB,aAAa,CACjC,SACA,QACe;AAAA,EACf,MAAM,SAAS,QAAQ;AAAA,EAEvB,YAAY,KAAK,UAAU,OAAO,QAAQ,MAAM,GAAG;AAAA,IACjD,IAAI,OAAO,UAAU,YAAY;AAAA,MAC7B,OAAO,QAAQ,KAAK,IAAI,UAAU,SAAS,KAAwC,CAAC;AAAA,IACxF,EAAO,SAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAAA,MAEtD,QAAQ,SAAS,cAAc,SAAS,KAAK,UAAU,KAAK,GAAG;AAAA,IACjE,EAAO;AAAA,MAEL,OAAO,QAAQ,KAAK,KAAK;AAAA;AAAA,EAE7B;AAAA;",
8
+ "debugId": "B594ED4B92E3950E64756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,181 @@
1
+ // packages/test-utils/src/mock-fs.ts
2
+ class MockFileSystem {
3
+ files = new Map;
4
+ directories = new Set(["/"]);
5
+ async getFileHandle(path, options) {
6
+ const exists = this.files.has(path);
7
+ if (!exists && !options?.create) {
8
+ throw new Error("[NotFoundError]File not found: " + path);
9
+ }
10
+ if (this.directories.has(path)) {
11
+ throw new Error("[TypeMismatchError]Path is a directory: " + path);
12
+ }
13
+ if (!exists && options?.create) {
14
+ this.files.set(path, {
15
+ data: new Uint8Array(0),
16
+ lastModified: Date.now(),
17
+ type: ""
18
+ });
19
+ }
20
+ }
21
+ async getDirectoryHandle(path, options) {
22
+ const exists = this.directories.has(path);
23
+ if (!exists && !options?.create) {
24
+ throw new Error("[NotFoundError]Directory not found: " + path);
25
+ }
26
+ if (this.files.has(path)) {
27
+ throw new Error("[TypeMismatchError]Path is a file: " + path);
28
+ }
29
+ if (!exists && options?.create) {
30
+ this.directories.add(path);
31
+ }
32
+ }
33
+ async removeEntry(path, options) {
34
+ if (this.files.has(path)) {
35
+ this.files.delete(path);
36
+ return;
37
+ }
38
+ if (this.directories.has(path)) {
39
+ const prefix = path === "/" ? "/" : path + "/";
40
+ const hasChildren = [...this.files.keys()].some((p) => p.startsWith(prefix)) || [...this.directories].some((p) => p !== path && p.startsWith(prefix));
41
+ if (hasChildren && !options?.recursive) {
42
+ throw new Error("[InvalidModificationError]Directory not empty: " + path);
43
+ }
44
+ for (const p of this.files.keys()) {
45
+ if (p.startsWith(prefix)) {
46
+ this.files.delete(p);
47
+ }
48
+ }
49
+ for (const p of this.directories) {
50
+ if (p.startsWith(prefix) || p === path) {
51
+ this.directories.delete(p);
52
+ }
53
+ }
54
+ return;
55
+ }
56
+ throw new Error("[NotFoundError]Entry not found: " + path);
57
+ }
58
+ async readDirectory(path) {
59
+ if (!this.directories.has(path)) {
60
+ throw new Error("[NotFoundError]Directory not found: " + path);
61
+ }
62
+ const prefix = path === "/" ? "/" : path + "/";
63
+ const entries = [];
64
+ const seen = new Set;
65
+ for (const p of this.files.keys()) {
66
+ if (p.startsWith(prefix)) {
67
+ const rest = p.slice(prefix.length);
68
+ const name = rest.split("/")[0];
69
+ if (name && !rest.includes("/") && !seen.has(name)) {
70
+ seen.add(name);
71
+ entries.push({ name, kind: "file" });
72
+ }
73
+ }
74
+ }
75
+ for (const p of this.directories) {
76
+ if (p !== path && p.startsWith(prefix)) {
77
+ const rest = p.slice(prefix.length);
78
+ const name = rest.split("/")[0];
79
+ if (name && !rest.includes("/") && !seen.has(name)) {
80
+ seen.add(name);
81
+ entries.push({ name, kind: "directory" });
82
+ }
83
+ }
84
+ }
85
+ return entries;
86
+ }
87
+ async readFile(path) {
88
+ const file = this.files.get(path);
89
+ if (!file) {
90
+ throw new Error("[NotFoundError]File not found: " + path);
91
+ }
92
+ return {
93
+ data: file.data,
94
+ size: file.data.length,
95
+ lastModified: file.lastModified,
96
+ type: file.type
97
+ };
98
+ }
99
+ async writeFile(path, data, position) {
100
+ const existing = this.files.get(path);
101
+ if (!existing) {
102
+ throw new Error("[NotFoundError]File not found: " + path);
103
+ }
104
+ if (position !== undefined && position > 0) {
105
+ const newSize = Math.max(existing.data.length, position + data.length);
106
+ const newData = new Uint8Array(newSize);
107
+ newData.set(existing.data);
108
+ newData.set(data, position);
109
+ existing.data = newData;
110
+ } else if (position === 0) {
111
+ const newSize = Math.max(existing.data.length, data.length);
112
+ const newData = new Uint8Array(newSize);
113
+ newData.set(existing.data);
114
+ newData.set(data, 0);
115
+ existing.data = newData;
116
+ } else {
117
+ existing.data = data;
118
+ }
119
+ existing.lastModified = Date.now();
120
+ }
121
+ async truncateFile(path, size) {
122
+ const file = this.files.get(path);
123
+ if (!file) {
124
+ throw new Error("[NotFoundError]File not found: " + path);
125
+ }
126
+ if (size < file.data.length) {
127
+ file.data = file.data.slice(0, size);
128
+ } else if (size > file.data.length) {
129
+ const newData = new Uint8Array(size);
130
+ newData.set(file.data);
131
+ file.data = newData;
132
+ }
133
+ file.lastModified = Date.now();
134
+ }
135
+ async getFileMetadata(path) {
136
+ const file = this.files.get(path);
137
+ if (!file) {
138
+ throw new Error("[NotFoundError]File not found: " + path);
139
+ }
140
+ return {
141
+ size: file.data.length,
142
+ lastModified: file.lastModified,
143
+ type: file.type
144
+ };
145
+ }
146
+ reset() {
147
+ this.files.clear();
148
+ this.directories.clear();
149
+ this.directories.add("/");
150
+ }
151
+ setFile(path, content, type) {
152
+ const data = typeof content === "string" ? new TextEncoder().encode(content) : content;
153
+ this.files.set(path, {
154
+ data,
155
+ lastModified: Date.now(),
156
+ type: type ?? ""
157
+ });
158
+ }
159
+ getFile(path) {
160
+ return this.files.get(path)?.data;
161
+ }
162
+ getFileAsString(path) {
163
+ const data = this.getFile(path);
164
+ if (!data)
165
+ return;
166
+ return new TextDecoder().decode(data);
167
+ }
168
+ createDirectory(path) {
169
+ const parts = path.split("/").filter(Boolean);
170
+ let current = "";
171
+ for (const part of parts) {
172
+ current += "/" + part;
173
+ this.directories.add(current);
174
+ }
175
+ }
176
+ }
177
+ export {
178
+ MockFileSystem
179
+ };
180
+
181
+ //# debugId=1C211EFA47640B4464756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/mock-fs.ts"],
4
+ "sourcesContent": [
5
+ "import type { FileSystemHandler } from \"@ricsam/isolate-fs\";\n\n/**\n * In-memory file system implementation for testing.\n * Implements the FileSystemHandler interface from @ricsam/isolate-fs.\n */\nexport class MockFileSystem implements FileSystemHandler {\n files = new Map<\n string,\n { data: Uint8Array; lastModified: number; type: string }\n >();\n directories = new Set<string>([\"/\"]);\n\n async getFileHandle(\n path: string,\n options?: { create?: boolean }\n ): Promise<void> {\n const exists = this.files.has(path);\n if (!exists && !options?.create) {\n throw new Error(\"[NotFoundError]File not found: \" + path);\n }\n if (this.directories.has(path)) {\n throw new Error(\"[TypeMismatchError]Path is a directory: \" + path);\n }\n if (!exists && options?.create) {\n this.files.set(path, {\n data: new Uint8Array(0),\n lastModified: Date.now(),\n type: \"\",\n });\n }\n }\n\n async getDirectoryHandle(\n path: string,\n options?: { create?: boolean }\n ): Promise<void> {\n const exists = this.directories.has(path);\n if (!exists && !options?.create) {\n throw new Error(\"[NotFoundError]Directory not found: \" + path);\n }\n if (this.files.has(path)) {\n throw new Error(\"[TypeMismatchError]Path is a file: \" + path);\n }\n if (!exists && options?.create) {\n this.directories.add(path);\n }\n }\n\n async removeEntry(\n path: string,\n options?: { recursive?: boolean }\n ): Promise<void> {\n if (this.files.has(path)) {\n this.files.delete(path);\n return;\n }\n\n if (this.directories.has(path)) {\n const prefix = path === \"/\" ? \"/\" : path + \"/\";\n const hasChildren =\n [...this.files.keys()].some((p) => p.startsWith(prefix)) ||\n [...this.directories].some((p) => p !== path && p.startsWith(prefix));\n\n if (hasChildren && !options?.recursive) {\n throw new Error(\"[InvalidModificationError]Directory not empty: \" + path);\n }\n\n for (const p of this.files.keys()) {\n if (p.startsWith(prefix)) {\n this.files.delete(p);\n }\n }\n for (const p of this.directories) {\n if (p.startsWith(prefix) || p === path) {\n this.directories.delete(p);\n }\n }\n return;\n }\n\n throw new Error(\"[NotFoundError]Entry not found: \" + path);\n }\n\n async readDirectory(\n path: string\n ): Promise<Array<{ name: string; kind: \"file\" | \"directory\" }>> {\n if (!this.directories.has(path)) {\n throw new Error(\"[NotFoundError]Directory not found: \" + path);\n }\n\n const prefix = path === \"/\" ? \"/\" : path + \"/\";\n const entries: Array<{ name: string; kind: \"file\" | \"directory\" }> = [];\n const seen = new Set<string>();\n\n for (const p of this.files.keys()) {\n if (p.startsWith(prefix)) {\n const rest = p.slice(prefix.length);\n const name = rest.split(\"/\")[0];\n if (name && !rest.includes(\"/\") && !seen.has(name)) {\n seen.add(name);\n entries.push({ name, kind: \"file\" });\n }\n }\n }\n\n for (const p of this.directories) {\n if (p !== path && p.startsWith(prefix)) {\n const rest = p.slice(prefix.length);\n const name = rest.split(\"/\")[0];\n if (name && !rest.includes(\"/\") && !seen.has(name)) {\n seen.add(name);\n entries.push({ name, kind: \"directory\" });\n }\n }\n }\n\n return entries;\n }\n\n async readFile(\n path: string\n ): Promise<{ data: Uint8Array; size: number; lastModified: number; type: string }> {\n const file = this.files.get(path);\n if (!file) {\n throw new Error(\"[NotFoundError]File not found: \" + path);\n }\n return {\n data: file.data,\n size: file.data.length,\n lastModified: file.lastModified,\n type: file.type,\n };\n }\n\n async writeFile(\n path: string,\n data: Uint8Array,\n position?: number\n ): Promise<void> {\n const existing = this.files.get(path);\n if (!existing) {\n throw new Error(\"[NotFoundError]File not found: \" + path);\n }\n\n if (position !== undefined && position > 0) {\n const newSize = Math.max(existing.data.length, position + data.length);\n const newData = new Uint8Array(newSize);\n newData.set(existing.data);\n newData.set(data, position);\n existing.data = newData;\n } else if (position === 0) {\n const newSize = Math.max(existing.data.length, data.length);\n const newData = new Uint8Array(newSize);\n newData.set(existing.data);\n newData.set(data, 0);\n existing.data = newData;\n } else {\n existing.data = data;\n }\n existing.lastModified = Date.now();\n }\n\n async truncateFile(path: string, size: number): Promise<void> {\n const file = this.files.get(path);\n if (!file) {\n throw new Error(\"[NotFoundError]File not found: \" + path);\n }\n if (size < file.data.length) {\n file.data = file.data.slice(0, size);\n } else if (size > file.data.length) {\n const newData = new Uint8Array(size);\n newData.set(file.data);\n file.data = newData;\n }\n file.lastModified = Date.now();\n }\n\n async getFileMetadata(\n path: string\n ): Promise<{ size: number; lastModified: number; type: string }> {\n const file = this.files.get(path);\n if (!file) {\n throw new Error(\"[NotFoundError]File not found: \" + path);\n }\n return {\n size: file.data.length,\n lastModified: file.lastModified,\n type: file.type,\n };\n }\n\n // Test helper methods\n\n /**\n * Reset the mock file system to its initial state (empty, with only root directory)\n */\n reset(): void {\n this.files.clear();\n this.directories.clear();\n this.directories.add(\"/\");\n }\n\n /**\n * Convenience method to set a file with string or binary content\n */\n setFile(path: string, content: string | Uint8Array, type?: string): void {\n const data =\n typeof content === \"string\"\n ? new TextEncoder().encode(content)\n : content;\n this.files.set(path, {\n data,\n lastModified: Date.now(),\n type: type ?? \"\",\n });\n }\n\n /**\n * Get file content as Uint8Array, or undefined if not found\n */\n getFile(path: string): Uint8Array | undefined {\n return this.files.get(path)?.data;\n }\n\n /**\n * Get file content as string, or undefined if not found\n */\n getFileAsString(path: string): string | undefined {\n const data = this.getFile(path);\n if (!data) return undefined;\n return new TextDecoder().decode(data);\n }\n\n /**\n * Create a directory (and any necessary parent directories)\n */\n createDirectory(path: string): void {\n const parts = path.split(\"/\").filter(Boolean);\n let current = \"\";\n for (const part of parts) {\n current += \"/\" + part;\n this.directories.add(current);\n }\n }\n}\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "1C211EFA47640B4464756E2164756E21",
9
+ "names": []
10
+ }