@ricsam/isolate-daemon 0.1.4 → 0.1.6
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 +27 -0
- package/dist/cjs/callback-fs-handler.cjs +68 -37
- package/dist/cjs/callback-fs-handler.cjs.map +3 -3
- package/dist/cjs/connection.cjs +255 -69
- package/dist/cjs/connection.cjs.map +3 -3
- package/dist/cjs/index.cjs +4 -2
- package/dist/cjs/index.cjs.map +3 -3
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/callback-fs-handler.mjs +68 -37
- package/dist/mjs/callback-fs-handler.mjs.map +3 -3
- package/dist/mjs/connection.mjs +255 -70
- package/dist/mjs/connection.mjs.map +3 -3
- package/dist/mjs/index.mjs +4 -2
- package/dist/mjs/index.mjs.map +3 -3
- package/dist/mjs/package.json +1 -1
- package/dist/types/callback-fs-handler.d.ts +3 -3
- package/dist/types/types.d.ts +37 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ npm add @ricsam/isolate-daemon
|
|
|
17
17
|
- Test environment support (enabled via `testEnvironment: true`)
|
|
18
18
|
- Playwright integration (client owns the browser, daemon invokes callbacks)
|
|
19
19
|
- Connection-scoped resource cleanup
|
|
20
|
+
- **Namespace-based runtime pooling** with LRU eviction for performance optimization
|
|
20
21
|
|
|
21
22
|
## Starting the Daemon
|
|
22
23
|
|
|
@@ -76,6 +77,32 @@ interface DaemonStats {
|
|
|
76
77
|
}
|
|
77
78
|
```
|
|
78
79
|
|
|
80
|
+
## Runtime Pooling with Namespaces
|
|
81
|
+
|
|
82
|
+
The daemon supports **namespace-based runtime pooling** for improved performance. When a client creates a runtime with a namespace ID, the runtime is cached on dispose (soft-delete) rather than destroyed. Future requests with the same namespace ID reuse the cached runtime, preserving:
|
|
83
|
+
|
|
84
|
+
- V8 Isolate instance
|
|
85
|
+
- V8 Context
|
|
86
|
+
- Compiled ES module cache
|
|
87
|
+
- Global state and imported modules
|
|
88
|
+
|
|
89
|
+
### How It Works
|
|
90
|
+
|
|
91
|
+
1. Client creates a namespace: `client.createNamespace("tenant-123")`
|
|
92
|
+
2. Client creates a runtime in that namespace: `namespace.createRuntime(options)`
|
|
93
|
+
3. On dispose, runtime is soft-deleted (cached in pool)
|
|
94
|
+
4. Any client can later request the same namespace and reuse the cached runtime
|
|
95
|
+
5. When `maxIsolates` limit is reached, oldest disposed runtimes are evicted (LRU)
|
|
96
|
+
|
|
97
|
+
### Pooling Behavior
|
|
98
|
+
|
|
99
|
+
- **Non-namespaced runtimes** (`client.createRuntime()`) work as before - true disposal on dispose
|
|
100
|
+
- **Namespaced runtimes** are cached and reusable across connections
|
|
101
|
+
- **LRU eviction** removes oldest disposed runtimes when at capacity
|
|
102
|
+
- **Connection close** soft-deletes namespaced runtimes (keeps them in pool)
|
|
103
|
+
|
|
104
|
+
The `maxIsolates` limit includes both active and pooled (disposed) runtimes. This ensures predictable memory usage while allowing runtime reuse.
|
|
105
|
+
|
|
79
106
|
## License
|
|
80
107
|
|
|
81
108
|
MIT
|
|
@@ -34,7 +34,7 @@ __export(exports_callback_fs_handler, {
|
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(exports_callback_fs_handler);
|
|
36
36
|
function createCallbackFileSystemHandler(options) {
|
|
37
|
-
const { connection,
|
|
37
|
+
const { connection, callbackContext, invokeClientCallback, basePath = "" } = options;
|
|
38
38
|
const resolvePath = (path) => {
|
|
39
39
|
const cleanPath = path.startsWith("/") ? path.slice(1) : path;
|
|
40
40
|
if (!basePath || basePath === "/") {
|
|
@@ -43,19 +43,28 @@ function createCallbackFileSystemHandler(options) {
|
|
|
43
43
|
const cleanBase = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
|
|
44
44
|
return `${cleanBase}/${cleanPath}`;
|
|
45
45
|
};
|
|
46
|
+
const getCallbackId = (name) => {
|
|
47
|
+
return callbackContext.fs[name];
|
|
48
|
+
};
|
|
49
|
+
const getConnection = () => {
|
|
50
|
+
return callbackContext.connection || connection;
|
|
51
|
+
};
|
|
46
52
|
return {
|
|
47
53
|
async getFileHandle(path, opts) {
|
|
48
54
|
const fullPath = resolvePath(path);
|
|
55
|
+
const conn = getConnection();
|
|
49
56
|
if (opts?.create) {
|
|
50
|
-
|
|
57
|
+
const writeFileId = getCallbackId("writeFile");
|
|
58
|
+
if (writeFileId !== undefined) {
|
|
51
59
|
try {
|
|
52
|
-
|
|
60
|
+
const statId2 = getCallbackId("stat");
|
|
61
|
+
if (statId2 !== undefined) {
|
|
53
62
|
try {
|
|
54
|
-
await invokeClientCallback(
|
|
63
|
+
await invokeClientCallback(conn, statId2, [fullPath]);
|
|
55
64
|
return;
|
|
56
65
|
} catch {}
|
|
57
66
|
}
|
|
58
|
-
await invokeClientCallback(
|
|
67
|
+
await invokeClientCallback(conn, writeFileId, [
|
|
59
68
|
fullPath,
|
|
60
69
|
new Uint8Array(0)
|
|
61
70
|
]);
|
|
@@ -66,9 +75,10 @@ function createCallbackFileSystemHandler(options) {
|
|
|
66
75
|
}
|
|
67
76
|
return;
|
|
68
77
|
}
|
|
69
|
-
|
|
78
|
+
const statId = getCallbackId("stat");
|
|
79
|
+
if (statId !== undefined) {
|
|
70
80
|
try {
|
|
71
|
-
const result = await invokeClientCallback(
|
|
81
|
+
const result = await invokeClientCallback(conn, statId, [
|
|
72
82
|
fullPath
|
|
73
83
|
]);
|
|
74
84
|
if (!result.isFile) {
|
|
@@ -84,10 +94,12 @@ function createCallbackFileSystemHandler(options) {
|
|
|
84
94
|
},
|
|
85
95
|
async getDirectoryHandle(path, opts) {
|
|
86
96
|
const fullPath = resolvePath(path);
|
|
97
|
+
const conn = getConnection();
|
|
87
98
|
if (opts?.create) {
|
|
88
|
-
|
|
99
|
+
const mkdirId = getCallbackId("mkdir");
|
|
100
|
+
if (mkdirId !== undefined) {
|
|
89
101
|
try {
|
|
90
|
-
await invokeClientCallback(
|
|
102
|
+
await invokeClientCallback(conn, mkdirId, [
|
|
91
103
|
fullPath,
|
|
92
104
|
{ recursive: true }
|
|
93
105
|
]);
|
|
@@ -95,9 +107,10 @@ function createCallbackFileSystemHandler(options) {
|
|
|
95
107
|
}
|
|
96
108
|
return;
|
|
97
109
|
}
|
|
98
|
-
|
|
110
|
+
const statId = getCallbackId("stat");
|
|
111
|
+
if (statId !== undefined) {
|
|
99
112
|
try {
|
|
100
|
-
const result = await invokeClientCallback(
|
|
113
|
+
const result = await invokeClientCallback(conn, statId, [
|
|
101
114
|
fullPath
|
|
102
115
|
]);
|
|
103
116
|
if (!result.isDirectory) {
|
|
@@ -113,10 +126,12 @@ function createCallbackFileSystemHandler(options) {
|
|
|
113
126
|
},
|
|
114
127
|
async removeEntry(path, opts) {
|
|
115
128
|
const fullPath = resolvePath(path);
|
|
129
|
+
const conn = getConnection();
|
|
116
130
|
let isFile = true;
|
|
117
|
-
|
|
131
|
+
const statId = getCallbackId("stat");
|
|
132
|
+
if (statId !== undefined) {
|
|
118
133
|
try {
|
|
119
|
-
const result = await invokeClientCallback(
|
|
134
|
+
const result = await invokeClientCallback(conn, statId, [
|
|
120
135
|
fullPath
|
|
121
136
|
]);
|
|
122
137
|
isFile = result.isFile;
|
|
@@ -125,32 +140,37 @@ function createCallbackFileSystemHandler(options) {
|
|
|
125
140
|
}
|
|
126
141
|
}
|
|
127
142
|
if (isFile) {
|
|
128
|
-
|
|
143
|
+
const unlinkId = getCallbackId("unlink");
|
|
144
|
+
if (unlinkId === undefined) {
|
|
129
145
|
throw new Error(`[NotAllowedError]File deletion not supported`);
|
|
130
146
|
}
|
|
131
|
-
await invokeClientCallback(
|
|
147
|
+
await invokeClientCallback(conn, unlinkId, [fullPath]);
|
|
132
148
|
} else {
|
|
133
|
-
|
|
149
|
+
const rmdirId = getCallbackId("rmdir");
|
|
150
|
+
if (rmdirId === undefined) {
|
|
134
151
|
throw new Error(`[NotAllowedError]Directory deletion not supported`);
|
|
135
152
|
}
|
|
136
|
-
await invokeClientCallback(
|
|
153
|
+
await invokeClientCallback(conn, rmdirId, [fullPath]);
|
|
137
154
|
}
|
|
138
155
|
},
|
|
139
156
|
async readDirectory(path) {
|
|
140
157
|
const fullPath = resolvePath(path);
|
|
141
|
-
|
|
158
|
+
const conn = getConnection();
|
|
159
|
+
const readdirId = getCallbackId("readdir");
|
|
160
|
+
if (readdirId === undefined) {
|
|
142
161
|
throw new Error(`[NotAllowedError]Directory reading not supported`);
|
|
143
162
|
}
|
|
144
|
-
const entries = await invokeClientCallback(
|
|
163
|
+
const entries = await invokeClientCallback(conn, readdirId, [
|
|
145
164
|
fullPath
|
|
146
165
|
]);
|
|
147
166
|
const result = [];
|
|
167
|
+
const statId = getCallbackId("stat");
|
|
148
168
|
for (const name of entries) {
|
|
149
169
|
const entryPath = fullPath ? `${fullPath}/${name}` : name;
|
|
150
170
|
let kind = "file";
|
|
151
|
-
if (
|
|
171
|
+
if (statId !== undefined) {
|
|
152
172
|
try {
|
|
153
|
-
const stat = await invokeClientCallback(
|
|
173
|
+
const stat = await invokeClientCallback(conn, statId, [
|
|
154
174
|
entryPath
|
|
155
175
|
]);
|
|
156
176
|
kind = stat.isDirectory ? "directory" : "file";
|
|
@@ -162,10 +182,12 @@ function createCallbackFileSystemHandler(options) {
|
|
|
162
182
|
},
|
|
163
183
|
async readFile(path) {
|
|
164
184
|
const fullPath = resolvePath(path);
|
|
165
|
-
|
|
185
|
+
const conn = getConnection();
|
|
186
|
+
const readFileId = getCallbackId("readFile");
|
|
187
|
+
if (readFileId === undefined) {
|
|
166
188
|
throw new Error(`[NotAllowedError]File reading not supported`);
|
|
167
189
|
}
|
|
168
|
-
const data = await invokeClientCallback(
|
|
190
|
+
const data = await invokeClientCallback(conn, readFileId, [
|
|
169
191
|
fullPath
|
|
170
192
|
]);
|
|
171
193
|
let bytes;
|
|
@@ -180,9 +202,10 @@ function createCallbackFileSystemHandler(options) {
|
|
|
180
202
|
}
|
|
181
203
|
let size = bytes.length;
|
|
182
204
|
let lastModified = Date.now();
|
|
183
|
-
|
|
205
|
+
const statId = getCallbackId("stat");
|
|
206
|
+
if (statId !== undefined) {
|
|
184
207
|
try {
|
|
185
|
-
const stat = await invokeClientCallback(
|
|
208
|
+
const stat = await invokeClientCallback(conn, statId, [
|
|
186
209
|
fullPath
|
|
187
210
|
]);
|
|
188
211
|
size = stat.size;
|
|
@@ -212,13 +235,16 @@ function createCallbackFileSystemHandler(options) {
|
|
|
212
235
|
},
|
|
213
236
|
async writeFile(path, data, position) {
|
|
214
237
|
const fullPath = resolvePath(path);
|
|
215
|
-
|
|
238
|
+
const conn = getConnection();
|
|
239
|
+
const writeFileId = getCallbackId("writeFile");
|
|
240
|
+
if (writeFileId === undefined) {
|
|
216
241
|
throw new Error(`[NotAllowedError]File writing not supported`);
|
|
217
242
|
}
|
|
218
243
|
if (position !== undefined && position > 0) {
|
|
219
|
-
|
|
244
|
+
const readFileId = getCallbackId("readFile");
|
|
245
|
+
if (readFileId !== undefined) {
|
|
220
246
|
try {
|
|
221
|
-
const existing = await invokeClientCallback(
|
|
247
|
+
const existing = await invokeClientCallback(conn, readFileId, [fullPath]);
|
|
222
248
|
let existingBytes;
|
|
223
249
|
if (existing instanceof Uint8Array) {
|
|
224
250
|
existingBytes = existing;
|
|
@@ -233,7 +259,7 @@ function createCallbackFileSystemHandler(options) {
|
|
|
233
259
|
const merged = new Uint8Array(newSize);
|
|
234
260
|
merged.set(existingBytes);
|
|
235
261
|
merged.set(data, position);
|
|
236
|
-
await invokeClientCallback(
|
|
262
|
+
await invokeClientCallback(conn, writeFileId, [
|
|
237
263
|
fullPath,
|
|
238
264
|
merged
|
|
239
265
|
]);
|
|
@@ -241,7 +267,7 @@ function createCallbackFileSystemHandler(options) {
|
|
|
241
267
|
} catch {
|
|
242
268
|
const newData = new Uint8Array(position + data.length);
|
|
243
269
|
newData.set(data, position);
|
|
244
|
-
await invokeClientCallback(
|
|
270
|
+
await invokeClientCallback(conn, writeFileId, [
|
|
245
271
|
fullPath,
|
|
246
272
|
newData
|
|
247
273
|
]);
|
|
@@ -249,14 +275,17 @@ function createCallbackFileSystemHandler(options) {
|
|
|
249
275
|
}
|
|
250
276
|
}
|
|
251
277
|
}
|
|
252
|
-
await invokeClientCallback(
|
|
278
|
+
await invokeClientCallback(conn, writeFileId, [fullPath, data]);
|
|
253
279
|
},
|
|
254
280
|
async truncateFile(path, size) {
|
|
255
281
|
const fullPath = resolvePath(path);
|
|
256
|
-
|
|
282
|
+
const conn = getConnection();
|
|
283
|
+
const readFileId = getCallbackId("readFile");
|
|
284
|
+
const writeFileId = getCallbackId("writeFile");
|
|
285
|
+
if (readFileId === undefined || writeFileId === undefined) {
|
|
257
286
|
throw new Error(`[NotAllowedError]File truncation not supported`);
|
|
258
287
|
}
|
|
259
|
-
const existing = await invokeClientCallback(
|
|
288
|
+
const existing = await invokeClientCallback(conn, readFileId, [
|
|
260
289
|
fullPath
|
|
261
290
|
]);
|
|
262
291
|
let existingBytes;
|
|
@@ -271,14 +300,16 @@ function createCallbackFileSystemHandler(options) {
|
|
|
271
300
|
}
|
|
272
301
|
const truncated = new Uint8Array(size);
|
|
273
302
|
truncated.set(existingBytes.slice(0, size));
|
|
274
|
-
await invokeClientCallback(
|
|
303
|
+
await invokeClientCallback(conn, writeFileId, [fullPath, truncated]);
|
|
275
304
|
},
|
|
276
305
|
async getFileMetadata(path) {
|
|
277
306
|
const fullPath = resolvePath(path);
|
|
278
|
-
|
|
307
|
+
const conn = getConnection();
|
|
308
|
+
const statId = getCallbackId("stat");
|
|
309
|
+
if (statId === undefined) {
|
|
279
310
|
throw new Error(`[NotAllowedError]File stat not supported`);
|
|
280
311
|
}
|
|
281
|
-
const stat = await invokeClientCallback(
|
|
312
|
+
const stat = await invokeClientCallback(conn, statId, [
|
|
282
313
|
fullPath
|
|
283
314
|
]);
|
|
284
315
|
if (!stat.isFile) {
|
|
@@ -311,4 +342,4 @@ function createCallbackFileSystemHandler(options) {
|
|
|
311
342
|
}
|
|
312
343
|
})
|
|
313
344
|
|
|
314
|
-
//# debugId=
|
|
345
|
+
//# debugId=311D5527AAA937A764756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/callback-fs-handler.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * Callback-based FileSystemHandler adapter.\n *\n * Adapts simple client callbacks (readFile, writeFile, etc.) to the\n * FileSystemHandler interface used by @ricsam/isolate-fs.\n */\n\nimport type { FileSystemHandler } from \"@ricsam/isolate-fs\";\nimport type { FsCallbackRegistrations, CallbackRegistration } from \"@ricsam/isolate-protocol\";\nimport type { ConnectionState } from \"./types.cjs\";\n\ninterface InvokeClientCallback {\n (connection: ConnectionState, callbackId: number, args: unknown[]): Promise<unknown>;\n}\n\ninterface CallbackFsHandlerOptions {\n connection: ConnectionState;\n callbacks: FsCallbackRegistrations;\n invokeClientCallback: InvokeClientCallback;\n basePath?: string;\n}\n\n/**\n * Create a FileSystemHandler that invokes client callbacks.\n *\n * Maps WHATWG FileSystem API operations to simple POSIX-like callbacks.\n */\nexport function createCallbackFileSystemHandler(\n options: CallbackFsHandlerOptions\n): FileSystemHandler {\n const { connection, callbacks, invokeClientCallback, basePath = \"\" } = options;\n\n const resolvePath = (path: string): string => {\n // Remove leading slash from the path\n const cleanPath = path.startsWith(\"/\") ? path.slice(1) : path;\n // Handle root case\n if (!basePath || basePath === \"/\") {\n return `/${cleanPath}`;\n }\n // Remove trailing slash from basePath\n const cleanBase = basePath.endsWith(\"/\") ? basePath.slice(0, -1) : basePath;\n return `${cleanBase}/${cleanPath}`;\n };\n\n return {\n async getFileHandle(path: string, opts?: { create?: boolean }): Promise<void> {\n const fullPath = resolvePath(path);\n\n if (opts?.create) {\n // Ensure file exists by writing empty content if it doesn't exist\n if (callbacks.writeFile) {\n try {\n // Check if file exists first\n if (callbacks.stat) {\n try {\n await invokeClientCallback(connection, callbacks.stat.callbackId, [fullPath]);\n // File exists, nothing to do\n return;\n } catch {\n // File doesn't exist, create it\n }\n }\n // Create empty file\n await invokeClientCallback(connection, callbacks.writeFile.callbackId, [\n fullPath,\n new Uint8Array(0),\n ]);\n } catch (err) {\n const error = err as Error;\n throw new Error(`[NotFoundError]${error.message}`);\n }\n }\n return;\n }\n\n // Check file exists\n if (callbacks.stat) {\n try {\n const result = (await invokeClientCallback(connection, callbacks.stat.callbackId, [\n fullPath,\n ])) as { isFile: boolean };\n if (!result.isFile) {\n throw new Error(`[TypeMismatchError]Not a file: ${fullPath}`);\n }\n } catch (err) {\n const error = err as Error;\n if (error.message.includes(\"TypeMismatchError\")) throw error;\n throw new Error(`[NotFoundError]File not found: ${fullPath}`);\n }\n }\n },\n\n async getDirectoryHandle(path: string, opts?: { create?: boolean }): Promise<void> {\n const fullPath = resolvePath(path);\n\n if (opts?.create) {\n if (callbacks.mkdir) {\n try {\n await invokeClientCallback(connection, callbacks.mkdir.callbackId, [\n fullPath,\n { recursive: true },\n ]);\n } catch {\n // Ignore error if directory already exists\n }\n }\n return;\n }\n\n // Check directory exists\n if (callbacks.stat) {\n try {\n const result = (await invokeClientCallback(connection, callbacks.stat.callbackId, [\n fullPath,\n ])) as { isDirectory: boolean };\n if (!result.isDirectory) {\n throw new Error(`[TypeMismatchError]Not a directory: ${fullPath}`);\n }\n } catch (err) {\n const error = err as Error;\n if (error.message.includes(\"TypeMismatchError\")) throw error;\n throw new Error(`[NotFoundError]Directory not found: ${fullPath}`);\n }\n }\n },\n\n async removeEntry(path: string, opts?: { recursive?: boolean }): Promise<void> {\n const fullPath = resolvePath(path);\n\n // Check if it's a file or directory\n let isFile = true;\n if (callbacks.stat) {\n try {\n const result = (await invokeClientCallback(connection, callbacks.stat.callbackId, [\n fullPath,\n ])) as { isFile: boolean; isDirectory: boolean };\n isFile = result.isFile;\n } catch {\n throw new Error(`[NotFoundError]Entry not found: ${fullPath}`);\n }\n }\n\n if (isFile) {\n if (!callbacks.unlink) {\n throw new Error(`[NotAllowedError]File deletion not supported`);\n }\n await invokeClientCallback(connection, callbacks.unlink.callbackId, [fullPath]);\n } else {\n if (!callbacks.rmdir) {\n throw new Error(`[NotAllowedError]Directory deletion not supported`);\n }\n // Note: recursive option may need special handling\n await invokeClientCallback(connection, callbacks.rmdir.callbackId, [fullPath]);\n }\n },\n\n async readDirectory(\n path: string\n ): Promise<Array<{ name: string; kind: \"file\" | \"directory\" }>> {\n const fullPath = resolvePath(path);\n\n if (!callbacks.readdir) {\n throw new Error(`[NotAllowedError]Directory reading not supported`);\n }\n\n const entries = (await invokeClientCallback(connection, callbacks.readdir.callbackId, [\n fullPath,\n ])) as string[];\n\n // We need to stat each entry to determine if it's a file or directory\n const result: Array<{ name: string; kind: \"file\" | \"directory\" }> = [];\n\n for (const name of entries) {\n const entryPath = fullPath ? `${fullPath}/${name}` : name;\n let kind: \"file\" | \"directory\" = \"file\";\n\n if (callbacks.stat) {\n try {\n const stat = (await invokeClientCallback(connection, callbacks.stat.callbackId, [\n entryPath,\n ])) as { isFile: boolean; isDirectory: boolean };\n kind = stat.isDirectory ? \"directory\" : \"file\";\n } catch {\n // Default to file if stat fails\n }\n }\n\n result.push({ name, kind });\n }\n\n return result;\n },\n\n async readFile(\n path: string\n ): Promise<{ data: Uint8Array; size: number; lastModified: number; type: string }> {\n const fullPath = resolvePath(path);\n\n if (!callbacks.readFile) {\n throw new Error(`[NotAllowedError]File reading not supported`);\n }\n\n const data = (await invokeClientCallback(connection, callbacks.readFile.callbackId, [\n fullPath,\n ])) as Uint8Array | ArrayBuffer | number[];\n\n // Convert to Uint8Array if needed\n let bytes: Uint8Array;\n if (data instanceof Uint8Array) {\n bytes = data;\n } else if (Array.isArray(data)) {\n bytes = new Uint8Array(data);\n } else if (data instanceof ArrayBuffer) {\n bytes = new Uint8Array(data);\n } else {\n bytes = new Uint8Array(0);\n }\n\n // Get metadata if stat is available\n let size = bytes.length;\n let lastModified = Date.now();\n\n if (callbacks.stat) {\n try {\n const stat = (await invokeClientCallback(connection, callbacks.stat.callbackId, [\n fullPath,\n ])) as { size: number; lastModified?: number };\n size = stat.size;\n if (stat.lastModified) {\n lastModified = stat.lastModified;\n }\n } catch {\n // Use byte length as fallback\n }\n }\n\n // Determine MIME type from extension\n const ext = path.split(\".\").pop()?.toLowerCase() || \"\";\n const mimeTypes: Record<string, string> = {\n txt: \"text/plain\",\n html: \"text/html\",\n htm: \"text/html\",\n css: \"text/css\",\n js: \"text/javascript\",\n json: \"application/json\",\n xml: \"application/xml\",\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n svg: \"image/svg+xml\",\n pdf: \"application/pdf\",\n };\n const type = mimeTypes[ext] || \"application/octet-stream\";\n\n return { data: bytes, size, lastModified, type };\n },\n\n async writeFile(path: string, data: Uint8Array, position?: number): Promise<void> {\n const fullPath = resolvePath(path);\n\n if (!callbacks.writeFile) {\n throw new Error(`[NotAllowedError]File writing not supported`);\n }\n\n // Note: position parameter for partial writes may need special handling\n // Simple implementation overwrites entire file\n if (position !== undefined && position > 0) {\n // For positional writes, we need to read existing content and merge\n if (callbacks.readFile) {\n try {\n const existing = (await invokeClientCallback(\n connection,\n callbacks.readFile.callbackId,\n [fullPath]\n )) as Uint8Array | ArrayBuffer | number[];\n\n let existingBytes: Uint8Array;\n if (existing instanceof Uint8Array) {\n existingBytes = existing;\n } else if (Array.isArray(existing)) {\n existingBytes = new Uint8Array(existing);\n } else if (existing instanceof ArrayBuffer) {\n existingBytes = new Uint8Array(existing);\n } else {\n existingBytes = new Uint8Array(0);\n }\n\n // Create merged buffer\n const newSize = Math.max(existingBytes.length, position + data.length);\n const merged = new Uint8Array(newSize);\n merged.set(existingBytes);\n merged.set(data, position);\n\n await invokeClientCallback(connection, callbacks.writeFile.callbackId, [\n fullPath,\n merged,\n ]);\n return;\n } catch {\n // File doesn't exist, create new one at position\n const newData = new Uint8Array(position + data.length);\n newData.set(data, position);\n await invokeClientCallback(connection, callbacks.writeFile.callbackId, [\n fullPath,\n newData,\n ]);\n return;\n }\n }\n }\n\n await invokeClientCallback(connection, callbacks.writeFile.callbackId, [fullPath, data]);\n },\n\n async truncateFile(path: string, size: number): Promise<void> {\n const fullPath = resolvePath(path);\n\n if (!callbacks.readFile || !callbacks.writeFile) {\n throw new Error(`[NotAllowedError]File truncation not supported`);\n }\n\n // Read existing content\n const existing = (await invokeClientCallback(connection, callbacks.readFile.callbackId, [\n fullPath,\n ])) as Uint8Array | ArrayBuffer | number[];\n\n let existingBytes: Uint8Array;\n if (existing instanceof Uint8Array) {\n existingBytes = existing;\n } else if (Array.isArray(existing)) {\n existingBytes = new Uint8Array(existing);\n } else if (existing instanceof ArrayBuffer) {\n existingBytes = new Uint8Array(existing);\n } else {\n existingBytes = new Uint8Array(0);\n }\n\n // Create truncated buffer\n const truncated = new Uint8Array(size);\n truncated.set(existingBytes.slice(0, size));\n\n await invokeClientCallback(connection, callbacks.writeFile.callbackId, [fullPath, truncated]);\n },\n\n async getFileMetadata(\n path: string\n ): Promise<{ size: number; lastModified: number; type: string }> {\n const fullPath = resolvePath(path);\n\n if (!callbacks.stat) {\n throw new Error(`[NotAllowedError]File stat not supported`);\n }\n\n const stat = (await invokeClientCallback(connection, callbacks.stat.callbackId, [\n fullPath,\n ])) as { size: number; lastModified?: number; isFile: boolean };\n\n if (!stat.isFile) {\n throw new Error(`[TypeMismatchError]Not a file: ${fullPath}`);\n }\n\n // Determine MIME type from extension\n const ext = path.split(\".\").pop()?.toLowerCase() || \"\";\n const mimeTypes: Record<string, string> = {\n txt: \"text/plain\",\n html: \"text/html\",\n htm: \"text/html\",\n css: \"text/css\",\n js: \"text/javascript\",\n json: \"application/json\",\n xml: \"application/xml\",\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n svg: \"image/svg+xml\",\n pdf: \"application/pdf\",\n };\n const type = mimeTypes[ext] || \"application/octet-stream\";\n\n return {\n size: stat.size,\n lastModified: stat.lastModified ?? Date.now(),\n type,\n };\n },\n };\n}\n"
|
|
5
|
+
"/**\n * Callback-based FileSystemHandler adapter.\n *\n * Adapts simple client callbacks (readFile, writeFile, etc.) to the\n * FileSystemHandler interface used by @ricsam/isolate-fs.\n */\n\nimport type { FileSystemHandler } from \"@ricsam/isolate-fs\";\nimport type { ConnectionState, CallbackContext } from \"./types.cjs\";\n\ninterface InvokeClientCallback {\n (connection: ConnectionState, callbackId: number, args: unknown[]): Promise<unknown>;\n}\n\ninterface CallbackFsHandlerOptions {\n connection: ConnectionState;\n callbackContext: CallbackContext;\n invokeClientCallback: InvokeClientCallback;\n basePath?: string;\n}\n\n/**\n * Create a FileSystemHandler that invokes client callbacks.\n *\n * Maps WHATWG FileSystem API operations to simple POSIX-like callbacks.\n * Uses callbackContext for dynamic callback ID lookup to support runtime reuse.\n */\nexport function createCallbackFileSystemHandler(\n options: CallbackFsHandlerOptions\n): FileSystemHandler {\n const { connection, callbackContext, invokeClientCallback, basePath = \"\" } = options;\n\n const resolvePath = (path: string): string => {\n // Remove leading slash from the path\n const cleanPath = path.startsWith(\"/\") ? path.slice(1) : path;\n // Handle root case\n if (!basePath || basePath === \"/\") {\n return `/${cleanPath}`;\n }\n // Remove trailing slash from basePath\n const cleanBase = basePath.endsWith(\"/\") ? basePath.slice(0, -1) : basePath;\n return `${cleanBase}/${cleanPath}`;\n };\n\n // Helper to get current callback ID (supports runtime reuse)\n const getCallbackId = (name: keyof CallbackContext[\"fs\"]): number | undefined => {\n return callbackContext.fs[name];\n };\n\n // Helper to get current connection (supports runtime reuse)\n const getConnection = (): ConnectionState => {\n return callbackContext.connection || connection;\n };\n\n return {\n async getFileHandle(path: string, opts?: { create?: boolean }): Promise<void> {\n const fullPath = resolvePath(path);\n const conn = getConnection();\n\n if (opts?.create) {\n // Ensure file exists by writing empty content if it doesn't exist\n const writeFileId = getCallbackId(\"writeFile\");\n if (writeFileId !== undefined) {\n try {\n // Check if file exists first\n const statId = getCallbackId(\"stat\");\n if (statId !== undefined) {\n try {\n await invokeClientCallback(conn, statId, [fullPath]);\n // File exists, nothing to do\n return;\n } catch {\n // File doesn't exist, create it\n }\n }\n // Create empty file\n await invokeClientCallback(conn, writeFileId, [\n fullPath,\n new Uint8Array(0),\n ]);\n } catch (err) {\n const error = err as Error;\n throw new Error(`[NotFoundError]${error.message}`);\n }\n }\n return;\n }\n\n // Check file exists\n const statId = getCallbackId(\"stat\");\n if (statId !== undefined) {\n try {\n const result = (await invokeClientCallback(conn, statId, [\n fullPath,\n ])) as { isFile: boolean };\n if (!result.isFile) {\n throw new Error(`[TypeMismatchError]Not a file: ${fullPath}`);\n }\n } catch (err) {\n const error = err as Error;\n if (error.message.includes(\"TypeMismatchError\")) throw error;\n throw new Error(`[NotFoundError]File not found: ${fullPath}`);\n }\n }\n },\n\n async getDirectoryHandle(path: string, opts?: { create?: boolean }): Promise<void> {\n const fullPath = resolvePath(path);\n const conn = getConnection();\n\n if (opts?.create) {\n const mkdirId = getCallbackId(\"mkdir\");\n if (mkdirId !== undefined) {\n try {\n await invokeClientCallback(conn, mkdirId, [\n fullPath,\n { recursive: true },\n ]);\n } catch {\n // Ignore error if directory already exists\n }\n }\n return;\n }\n\n // Check directory exists\n const statId = getCallbackId(\"stat\");\n if (statId !== undefined) {\n try {\n const result = (await invokeClientCallback(conn, statId, [\n fullPath,\n ])) as { isDirectory: boolean };\n if (!result.isDirectory) {\n throw new Error(`[TypeMismatchError]Not a directory: ${fullPath}`);\n }\n } catch (err) {\n const error = err as Error;\n if (error.message.includes(\"TypeMismatchError\")) throw error;\n throw new Error(`[NotFoundError]Directory not found: ${fullPath}`);\n }\n }\n },\n\n async removeEntry(path: string, opts?: { recursive?: boolean }): Promise<void> {\n const fullPath = resolvePath(path);\n const conn = getConnection();\n\n // Check if it's a file or directory\n let isFile = true;\n const statId = getCallbackId(\"stat\");\n if (statId !== undefined) {\n try {\n const result = (await invokeClientCallback(conn, statId, [\n fullPath,\n ])) as { isFile: boolean; isDirectory: boolean };\n isFile = result.isFile;\n } catch {\n throw new Error(`[NotFoundError]Entry not found: ${fullPath}`);\n }\n }\n\n if (isFile) {\n const unlinkId = getCallbackId(\"unlink\");\n if (unlinkId === undefined) {\n throw new Error(`[NotAllowedError]File deletion not supported`);\n }\n await invokeClientCallback(conn, unlinkId, [fullPath]);\n } else {\n const rmdirId = getCallbackId(\"rmdir\");\n if (rmdirId === undefined) {\n throw new Error(`[NotAllowedError]Directory deletion not supported`);\n }\n // Note: recursive option may need special handling\n await invokeClientCallback(conn, rmdirId, [fullPath]);\n }\n },\n\n async readDirectory(\n path: string\n ): Promise<Array<{ name: string; kind: \"file\" | \"directory\" }>> {\n const fullPath = resolvePath(path);\n const conn = getConnection();\n\n const readdirId = getCallbackId(\"readdir\");\n if (readdirId === undefined) {\n throw new Error(`[NotAllowedError]Directory reading not supported`);\n }\n\n const entries = (await invokeClientCallback(conn, readdirId, [\n fullPath,\n ])) as string[];\n\n // We need to stat each entry to determine if it's a file or directory\n const result: Array<{ name: string; kind: \"file\" | \"directory\" }> = [];\n\n const statId = getCallbackId(\"stat\");\n for (const name of entries) {\n const entryPath = fullPath ? `${fullPath}/${name}` : name;\n let kind: \"file\" | \"directory\" = \"file\";\n\n if (statId !== undefined) {\n try {\n const stat = (await invokeClientCallback(conn, statId, [\n entryPath,\n ])) as { isFile: boolean; isDirectory: boolean };\n kind = stat.isDirectory ? \"directory\" : \"file\";\n } catch {\n // Default to file if stat fails\n }\n }\n\n result.push({ name, kind });\n }\n\n return result;\n },\n\n async readFile(\n path: string\n ): Promise<{ data: Uint8Array; size: number; lastModified: number; type: string }> {\n const fullPath = resolvePath(path);\n const conn = getConnection();\n\n const readFileId = getCallbackId(\"readFile\");\n if (readFileId === undefined) {\n throw new Error(`[NotAllowedError]File reading not supported`);\n }\n\n const data = (await invokeClientCallback(conn, readFileId, [\n fullPath,\n ])) as Uint8Array | ArrayBuffer | number[];\n\n // Convert to Uint8Array if needed\n let bytes: Uint8Array;\n if (data instanceof Uint8Array) {\n bytes = data;\n } else if (Array.isArray(data)) {\n bytes = new Uint8Array(data);\n } else if (data instanceof ArrayBuffer) {\n bytes = new Uint8Array(data);\n } else {\n bytes = new Uint8Array(0);\n }\n\n // Get metadata if stat is available\n let size = bytes.length;\n let lastModified = Date.now();\n\n const statId = getCallbackId(\"stat\");\n if (statId !== undefined) {\n try {\n const stat = (await invokeClientCallback(conn, statId, [\n fullPath,\n ])) as { size: number; lastModified?: number };\n size = stat.size;\n if (stat.lastModified) {\n lastModified = stat.lastModified;\n }\n } catch {\n // Use byte length as fallback\n }\n }\n\n // Determine MIME type from extension\n const ext = path.split(\".\").pop()?.toLowerCase() || \"\";\n const mimeTypes: Record<string, string> = {\n txt: \"text/plain\",\n html: \"text/html\",\n htm: \"text/html\",\n css: \"text/css\",\n js: \"text/javascript\",\n json: \"application/json\",\n xml: \"application/xml\",\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n svg: \"image/svg+xml\",\n pdf: \"application/pdf\",\n };\n const type = mimeTypes[ext] || \"application/octet-stream\";\n\n return { data: bytes, size, lastModified, type };\n },\n\n async writeFile(path: string, data: Uint8Array, position?: number): Promise<void> {\n const fullPath = resolvePath(path);\n const conn = getConnection();\n\n const writeFileId = getCallbackId(\"writeFile\");\n if (writeFileId === undefined) {\n throw new Error(`[NotAllowedError]File writing not supported`);\n }\n\n // Note: position parameter for partial writes may need special handling\n // Simple implementation overwrites entire file\n if (position !== undefined && position > 0) {\n // For positional writes, we need to read existing content and merge\n const readFileId = getCallbackId(\"readFile\");\n if (readFileId !== undefined) {\n try {\n const existing = (await invokeClientCallback(\n conn,\n readFileId,\n [fullPath]\n )) as Uint8Array | ArrayBuffer | number[];\n\n let existingBytes: Uint8Array;\n if (existing instanceof Uint8Array) {\n existingBytes = existing;\n } else if (Array.isArray(existing)) {\n existingBytes = new Uint8Array(existing);\n } else if (existing instanceof ArrayBuffer) {\n existingBytes = new Uint8Array(existing);\n } else {\n existingBytes = new Uint8Array(0);\n }\n\n // Create merged buffer\n const newSize = Math.max(existingBytes.length, position + data.length);\n const merged = new Uint8Array(newSize);\n merged.set(existingBytes);\n merged.set(data, position);\n\n await invokeClientCallback(conn, writeFileId, [\n fullPath,\n merged,\n ]);\n return;\n } catch {\n // File doesn't exist, create new one at position\n const newData = new Uint8Array(position + data.length);\n newData.set(data, position);\n await invokeClientCallback(conn, writeFileId, [\n fullPath,\n newData,\n ]);\n return;\n }\n }\n }\n\n await invokeClientCallback(conn, writeFileId, [fullPath, data]);\n },\n\n async truncateFile(path: string, size: number): Promise<void> {\n const fullPath = resolvePath(path);\n const conn = getConnection();\n\n const readFileId = getCallbackId(\"readFile\");\n const writeFileId = getCallbackId(\"writeFile\");\n if (readFileId === undefined || writeFileId === undefined) {\n throw new Error(`[NotAllowedError]File truncation not supported`);\n }\n\n // Read existing content\n const existing = (await invokeClientCallback(conn, readFileId, [\n fullPath,\n ])) as Uint8Array | ArrayBuffer | number[];\n\n let existingBytes: Uint8Array;\n if (existing instanceof Uint8Array) {\n existingBytes = existing;\n } else if (Array.isArray(existing)) {\n existingBytes = new Uint8Array(existing);\n } else if (existing instanceof ArrayBuffer) {\n existingBytes = new Uint8Array(existing);\n } else {\n existingBytes = new Uint8Array(0);\n }\n\n // Create truncated buffer\n const truncated = new Uint8Array(size);\n truncated.set(existingBytes.slice(0, size));\n\n await invokeClientCallback(conn, writeFileId, [fullPath, truncated]);\n },\n\n async getFileMetadata(\n path: string\n ): Promise<{ size: number; lastModified: number; type: string }> {\n const fullPath = resolvePath(path);\n const conn = getConnection();\n\n const statId = getCallbackId(\"stat\");\n if (statId === undefined) {\n throw new Error(`[NotAllowedError]File stat not supported`);\n }\n\n const stat = (await invokeClientCallback(conn, statId, [\n fullPath,\n ])) as { size: number; lastModified?: number; isFile: boolean };\n\n if (!stat.isFile) {\n throw new Error(`[TypeMismatchError]Not a file: ${fullPath}`);\n }\n\n // Determine MIME type from extension\n const ext = path.split(\".\").pop()?.toLowerCase() || \"\";\n const mimeTypes: Record<string, string> = {\n txt: \"text/plain\",\n html: \"text/html\",\n htm: \"text/html\",\n css: \"text/css\",\n js: \"text/javascript\",\n json: \"application/json\",\n xml: \"application/xml\",\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n svg: \"image/svg+xml\",\n pdf: \"application/pdf\",\n };\n const type = mimeTypes[ext] || \"application/octet-stream\";\n\n return {\n size: stat.size,\n lastModified: stat.lastModified ?? Date.now(),\n type,\n };\n },\n };\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BO,SAAS,+BAA+B,CAC7C,SACmB;AAAA,EACnB,QAAQ,YAAY,
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "311D5527AAA937A764756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|