@ricsam/isolate-daemon 0.0.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,45 +1,50 @@
1
1
  # @ricsam/isolate-daemon
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
3
+ Node.js daemon server that manages isolated-vm runtimes via Unix socket or TCP. Allows non-Node.js runtimes (Bun, Deno, etc.) to use isolated-vm through IPC.
4
4
 
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
5
+ ```bash
6
+ npm add @ricsam/isolate-daemon
7
+ ```
6
8
 
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
9
+ **Features:**
10
+ - Unix domain socket and TCP transport
11
+ - Multiple concurrent connections
12
+ - Runtime lifecycle management (create, dispose)
13
+ - Bidirectional callback bridging (console, fetch, fs)
14
+ - Test environment and Playwright integration
15
+ - Connection-scoped resource cleanup
8
16
 
9
- ## Purpose
17
+ **Starting the Daemon:**
10
18
 
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@ricsam/isolate-daemon`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
19
+ ```typescript
20
+ import { startDaemon } from "@ricsam/isolate-daemon";
15
21
 
16
- ## What is OIDC Trusted Publishing?
22
+ const daemon = await startDaemon({
23
+ socketPath: "/tmp/isolate-daemon.sock", // Unix socket
24
+ // Or TCP: host: "127.0.0.1", port: 47891
25
+ maxIsolates: 100,
26
+ defaultMemoryLimit: 128,
27
+ });
17
28
 
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
29
+ console.log(`Daemon listening on ${daemon.socketPath}`);
19
30
 
20
- ## Setup Instructions
31
+ // Get stats
32
+ const stats = daemon.getStats();
33
+ console.log(`Active isolates: ${stats.activeIsolates}`);
21
34
 
22
- To properly configure OIDC trusted publishing for this package:
35
+ // Graceful shutdown
36
+ await daemon.close();
37
+ ```
23
38
 
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
39
+ **CLI Usage:**
28
40
 
29
- ## DO NOT USE THIS PACKAGE
41
+ ```bash
42
+ # Start daemon on default socket
43
+ npx isolate-daemon
30
44
 
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
45
+ # Custom socket path
46
+ npx isolate-daemon --socket /var/run/isolate.sock
36
47
 
37
- ## More Information
38
-
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
42
-
43
- ---
44
-
45
- **Maintained for OIDC setup purposes only**
48
+ # TCP mode
49
+ npx isolate-daemon --host 127.0.0.1 --port 47891
50
+ ```
package/bin/daemon.js ADDED
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * CLI entry point for the isolate daemon.
5
+ *
6
+ * Usage:
7
+ * isolate-daemon [options]
8
+ *
9
+ * Options:
10
+ * --socket <path> Unix socket path (default: /tmp/isolate-daemon.sock)
11
+ * --host <host> TCP host (default: 127.0.0.1)
12
+ * --port <port> TCP port (default: 47891)
13
+ * --max-isolates <n> Maximum isolates (default: 100)
14
+ * --memory-limit <mb> Default memory limit (default: 128)
15
+ */
16
+
17
+ import { startDaemon } from "../src/index.ts";
18
+
19
+ function parseArgs(args) {
20
+ const options = {};
21
+
22
+ for (let i = 0; i < args.length; i++) {
23
+ const arg = args[i];
24
+
25
+ switch (arg) {
26
+ case "--socket":
27
+ options.socketPath = args[++i];
28
+ break;
29
+ case "--host":
30
+ options.host = args[++i];
31
+ options.socketPath = undefined; // Use TCP instead
32
+ break;
33
+ case "--port":
34
+ options.port = parseInt(args[++i], 10);
35
+ options.socketPath = undefined; // Use TCP instead
36
+ break;
37
+ case "--max-isolates":
38
+ options.maxIsolates = parseInt(args[++i], 10);
39
+ break;
40
+ case "--memory-limit":
41
+ options.defaultMemoryLimit = parseInt(args[++i], 10);
42
+ break;
43
+ case "--help":
44
+ case "-h":
45
+ console.log(`
46
+ Isolate Daemon - Run isolated-vm runtimes accessible via IPC
47
+
48
+ Usage:
49
+ isolate-daemon [options]
50
+
51
+ Options:
52
+ --socket <path> Unix socket path (default: /tmp/isolate-daemon.sock)
53
+ --host <host> TCP host (default: 127.0.0.1, disables Unix socket)
54
+ --port <port> TCP port (default: 47891, disables Unix socket)
55
+ --max-isolates <n> Maximum isolates (default: 100)
56
+ --memory-limit <mb> Default memory limit in MB (default: 128)
57
+ --help, -h Show this help message
58
+ `);
59
+ process.exit(0);
60
+ default:
61
+ if (arg.startsWith("-")) {
62
+ console.error(`Unknown option: ${arg}`);
63
+ process.exit(1);
64
+ }
65
+ }
66
+ }
67
+
68
+ return options;
69
+ }
70
+
71
+ async function main() {
72
+ const options = parseArgs(process.argv.slice(2));
73
+
74
+ const daemon = await startDaemon(options);
75
+
76
+ // Handle shutdown signals
77
+ const shutdown = async () => {
78
+ console.log("\nShutting down...");
79
+ await daemon.close();
80
+ process.exit(0);
81
+ };
82
+
83
+ process.on("SIGINT", shutdown);
84
+ process.on("SIGTERM", shutdown);
85
+
86
+ // Log stats periodically
87
+ setInterval(() => {
88
+ const stats = daemon.getStats();
89
+ console.log(
90
+ `[stats] connections: ${stats.activeConnections}, isolates: ${stats.activeIsolates}, total requests: ${stats.totalRequestsProcessed}`
91
+ );
92
+ }, 60000);
93
+ }
94
+
95
+ main().catch((err) => {
96
+ console.error("Failed to start daemon:", err);
97
+ process.exit(1);
98
+ });
@@ -0,0 +1,314 @@
1
+ // @bun @bun-cjs
2
+ (function(exports, require, module, __filename, __dirname) {var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
7
+ var __toCommonJS = (from) => {
8
+ var entry = __moduleCache.get(from), desc;
9
+ if (entry)
10
+ return entry;
11
+ entry = __defProp({}, "__esModule", { value: true });
12
+ if (from && typeof from === "object" || typeof from === "function")
13
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
14
+ get: () => from[key],
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ }));
17
+ __moduleCache.set(from, entry);
18
+ return entry;
19
+ };
20
+ var __export = (target, all) => {
21
+ for (var name in all)
22
+ __defProp(target, name, {
23
+ get: all[name],
24
+ enumerable: true,
25
+ configurable: true,
26
+ set: (newValue) => all[name] = () => newValue
27
+ });
28
+ };
29
+
30
+ // packages/isolate-daemon/src/callback-fs-handler.ts
31
+ var exports_callback_fs_handler = {};
32
+ __export(exports_callback_fs_handler, {
33
+ createCallbackFileSystemHandler: () => createCallbackFileSystemHandler
34
+ });
35
+ module.exports = __toCommonJS(exports_callback_fs_handler);
36
+ function createCallbackFileSystemHandler(options) {
37
+ const { connection, callbacks, invokeClientCallback, basePath = "" } = options;
38
+ const resolvePath = (path) => {
39
+ const cleanPath = path.startsWith("/") ? path.slice(1) : path;
40
+ if (!basePath || basePath === "/") {
41
+ return `/${cleanPath}`;
42
+ }
43
+ const cleanBase = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
44
+ return `${cleanBase}/${cleanPath}`;
45
+ };
46
+ return {
47
+ async getFileHandle(path, opts) {
48
+ const fullPath = resolvePath(path);
49
+ if (opts?.create) {
50
+ if (callbacks.writeFile) {
51
+ try {
52
+ if (callbacks.stat) {
53
+ try {
54
+ await invokeClientCallback(connection, callbacks.stat.callbackId, [fullPath]);
55
+ return;
56
+ } catch {}
57
+ }
58
+ await invokeClientCallback(connection, callbacks.writeFile.callbackId, [
59
+ fullPath,
60
+ new Uint8Array(0)
61
+ ]);
62
+ } catch (err) {
63
+ const error = err;
64
+ throw new Error(`[NotFoundError]${error.message}`);
65
+ }
66
+ }
67
+ return;
68
+ }
69
+ if (callbacks.stat) {
70
+ try {
71
+ const result = await invokeClientCallback(connection, callbacks.stat.callbackId, [
72
+ fullPath
73
+ ]);
74
+ if (!result.isFile) {
75
+ throw new Error(`[TypeMismatchError]Not a file: ${fullPath}`);
76
+ }
77
+ } catch (err) {
78
+ const error = err;
79
+ if (error.message.includes("TypeMismatchError"))
80
+ throw error;
81
+ throw new Error(`[NotFoundError]File not found: ${fullPath}`);
82
+ }
83
+ }
84
+ },
85
+ async getDirectoryHandle(path, opts) {
86
+ const fullPath = resolvePath(path);
87
+ if (opts?.create) {
88
+ if (callbacks.mkdir) {
89
+ try {
90
+ await invokeClientCallback(connection, callbacks.mkdir.callbackId, [
91
+ fullPath,
92
+ { recursive: true }
93
+ ]);
94
+ } catch {}
95
+ }
96
+ return;
97
+ }
98
+ if (callbacks.stat) {
99
+ try {
100
+ const result = await invokeClientCallback(connection, callbacks.stat.callbackId, [
101
+ fullPath
102
+ ]);
103
+ if (!result.isDirectory) {
104
+ throw new Error(`[TypeMismatchError]Not a directory: ${fullPath}`);
105
+ }
106
+ } catch (err) {
107
+ const error = err;
108
+ if (error.message.includes("TypeMismatchError"))
109
+ throw error;
110
+ throw new Error(`[NotFoundError]Directory not found: ${fullPath}`);
111
+ }
112
+ }
113
+ },
114
+ async removeEntry(path, opts) {
115
+ const fullPath = resolvePath(path);
116
+ let isFile = true;
117
+ if (callbacks.stat) {
118
+ try {
119
+ const result = await invokeClientCallback(connection, callbacks.stat.callbackId, [
120
+ fullPath
121
+ ]);
122
+ isFile = result.isFile;
123
+ } catch {
124
+ throw new Error(`[NotFoundError]Entry not found: ${fullPath}`);
125
+ }
126
+ }
127
+ if (isFile) {
128
+ if (!callbacks.unlink) {
129
+ throw new Error(`[NotAllowedError]File deletion not supported`);
130
+ }
131
+ await invokeClientCallback(connection, callbacks.unlink.callbackId, [fullPath]);
132
+ } else {
133
+ if (!callbacks.rmdir) {
134
+ throw new Error(`[NotAllowedError]Directory deletion not supported`);
135
+ }
136
+ await invokeClientCallback(connection, callbacks.rmdir.callbackId, [fullPath]);
137
+ }
138
+ },
139
+ async readDirectory(path) {
140
+ const fullPath = resolvePath(path);
141
+ if (!callbacks.readdir) {
142
+ throw new Error(`[NotAllowedError]Directory reading not supported`);
143
+ }
144
+ const entries = await invokeClientCallback(connection, callbacks.readdir.callbackId, [
145
+ fullPath
146
+ ]);
147
+ const result = [];
148
+ for (const name of entries) {
149
+ const entryPath = fullPath ? `${fullPath}/${name}` : name;
150
+ let kind = "file";
151
+ if (callbacks.stat) {
152
+ try {
153
+ const stat = await invokeClientCallback(connection, callbacks.stat.callbackId, [
154
+ entryPath
155
+ ]);
156
+ kind = stat.isDirectory ? "directory" : "file";
157
+ } catch {}
158
+ }
159
+ result.push({ name, kind });
160
+ }
161
+ return result;
162
+ },
163
+ async readFile(path) {
164
+ const fullPath = resolvePath(path);
165
+ if (!callbacks.readFile) {
166
+ throw new Error(`[NotAllowedError]File reading not supported`);
167
+ }
168
+ const data = await invokeClientCallback(connection, callbacks.readFile.callbackId, [
169
+ fullPath
170
+ ]);
171
+ let bytes;
172
+ if (data instanceof Uint8Array) {
173
+ bytes = data;
174
+ } else if (Array.isArray(data)) {
175
+ bytes = new Uint8Array(data);
176
+ } else if (data instanceof ArrayBuffer) {
177
+ bytes = new Uint8Array(data);
178
+ } else {
179
+ bytes = new Uint8Array(0);
180
+ }
181
+ let size = bytes.length;
182
+ let lastModified = Date.now();
183
+ if (callbacks.stat) {
184
+ try {
185
+ const stat = await invokeClientCallback(connection, callbacks.stat.callbackId, [
186
+ fullPath
187
+ ]);
188
+ size = stat.size;
189
+ if (stat.lastModified) {
190
+ lastModified = stat.lastModified;
191
+ }
192
+ } catch {}
193
+ }
194
+ const ext = path.split(".").pop()?.toLowerCase() || "";
195
+ const mimeTypes = {
196
+ txt: "text/plain",
197
+ html: "text/html",
198
+ htm: "text/html",
199
+ css: "text/css",
200
+ js: "text/javascript",
201
+ json: "application/json",
202
+ xml: "application/xml",
203
+ png: "image/png",
204
+ jpg: "image/jpeg",
205
+ jpeg: "image/jpeg",
206
+ gif: "image/gif",
207
+ svg: "image/svg+xml",
208
+ pdf: "application/pdf"
209
+ };
210
+ const type = mimeTypes[ext] || "application/octet-stream";
211
+ return { data: bytes, size, lastModified, type };
212
+ },
213
+ async writeFile(path, data, position) {
214
+ const fullPath = resolvePath(path);
215
+ if (!callbacks.writeFile) {
216
+ throw new Error(`[NotAllowedError]File writing not supported`);
217
+ }
218
+ if (position !== undefined && position > 0) {
219
+ if (callbacks.readFile) {
220
+ try {
221
+ const existing = await invokeClientCallback(connection, callbacks.readFile.callbackId, [fullPath]);
222
+ let existingBytes;
223
+ if (existing instanceof Uint8Array) {
224
+ existingBytes = existing;
225
+ } else if (Array.isArray(existing)) {
226
+ existingBytes = new Uint8Array(existing);
227
+ } else if (existing instanceof ArrayBuffer) {
228
+ existingBytes = new Uint8Array(existing);
229
+ } else {
230
+ existingBytes = new Uint8Array(0);
231
+ }
232
+ const newSize = Math.max(existingBytes.length, position + data.length);
233
+ const merged = new Uint8Array(newSize);
234
+ merged.set(existingBytes);
235
+ merged.set(data, position);
236
+ await invokeClientCallback(connection, callbacks.writeFile.callbackId, [
237
+ fullPath,
238
+ merged
239
+ ]);
240
+ return;
241
+ } catch {
242
+ const newData = new Uint8Array(position + data.length);
243
+ newData.set(data, position);
244
+ await invokeClientCallback(connection, callbacks.writeFile.callbackId, [
245
+ fullPath,
246
+ newData
247
+ ]);
248
+ return;
249
+ }
250
+ }
251
+ }
252
+ await invokeClientCallback(connection, callbacks.writeFile.callbackId, [fullPath, data]);
253
+ },
254
+ async truncateFile(path, size) {
255
+ const fullPath = resolvePath(path);
256
+ if (!callbacks.readFile || !callbacks.writeFile) {
257
+ throw new Error(`[NotAllowedError]File truncation not supported`);
258
+ }
259
+ const existing = await invokeClientCallback(connection, callbacks.readFile.callbackId, [
260
+ fullPath
261
+ ]);
262
+ let existingBytes;
263
+ if (existing instanceof Uint8Array) {
264
+ existingBytes = existing;
265
+ } else if (Array.isArray(existing)) {
266
+ existingBytes = new Uint8Array(existing);
267
+ } else if (existing instanceof ArrayBuffer) {
268
+ existingBytes = new Uint8Array(existing);
269
+ } else {
270
+ existingBytes = new Uint8Array(0);
271
+ }
272
+ const truncated = new Uint8Array(size);
273
+ truncated.set(existingBytes.slice(0, size));
274
+ await invokeClientCallback(connection, callbacks.writeFile.callbackId, [fullPath, truncated]);
275
+ },
276
+ async getFileMetadata(path) {
277
+ const fullPath = resolvePath(path);
278
+ if (!callbacks.stat) {
279
+ throw new Error(`[NotAllowedError]File stat not supported`);
280
+ }
281
+ const stat = await invokeClientCallback(connection, callbacks.stat.callbackId, [
282
+ fullPath
283
+ ]);
284
+ if (!stat.isFile) {
285
+ throw new Error(`[TypeMismatchError]Not a file: ${fullPath}`);
286
+ }
287
+ const ext = path.split(".").pop()?.toLowerCase() || "";
288
+ const mimeTypes = {
289
+ txt: "text/plain",
290
+ html: "text/html",
291
+ htm: "text/html",
292
+ css: "text/css",
293
+ js: "text/javascript",
294
+ json: "application/json",
295
+ xml: "application/xml",
296
+ png: "image/png",
297
+ jpg: "image/jpeg",
298
+ jpeg: "image/jpeg",
299
+ gif: "image/gif",
300
+ svg: "image/svg+xml",
301
+ pdf: "application/pdf"
302
+ };
303
+ const type = mimeTypes[ext] || "application/octet-stream";
304
+ return {
305
+ size: stat.size,
306
+ lastModified: stat.lastModified ?? Date.now(),
307
+ type
308
+ };
309
+ }
310
+ };
311
+ }
312
+ })
313
+
314
+ //# debugId=6BC1EED73784E89264756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/callback-fs-handler.ts"],
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"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "6BC1EED73784E89264756E2164756E21",
9
+ "names": []
10
+ }