@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 +36 -31
- package/bin/daemon.js +98 -0
- package/dist/cjs/callback-fs-handler.cjs +314 -0
- package/dist/cjs/callback-fs-handler.cjs.map +10 -0
- package/dist/cjs/connection.cjs +580 -0
- package/dist/cjs/connection.cjs.map +10 -0
- package/dist/cjs/index.cjs +132 -0
- package/dist/cjs/index.cjs.map +10 -0
- package/dist/cjs/package.json +5 -0
- package/dist/cjs/types.cjs +26 -0
- package/dist/cjs/types.cjs.map +9 -0
- package/dist/mjs/callback-fs-handler.mjs +283 -0
- package/dist/mjs/callback-fs-handler.mjs.map +10 -0
- package/dist/mjs/connection.mjs +561 -0
- package/dist/mjs/connection.mjs.map +10 -0
- package/dist/mjs/index.mjs +101 -0
- package/dist/mjs/index.mjs.map +10 -0
- package/dist/mjs/package.json +5 -0
- package/dist/mjs/types.mjs +3 -0
- package/dist/mjs/types.mjs.map +9 -0
- package/dist/types/callback-fs-handler.d.ts +25 -0
- package/dist/types/connection.d.ts +9 -0
- package/dist/types/index.d.ts +14 -0
- package/dist/types/types.d.ts +91 -0
- package/package.json +55 -6
package/README.md
CHANGED
|
@@ -1,45 +1,50 @@
|
|
|
1
1
|
# @ricsam/isolate-daemon
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
5
|
+
```bash
|
|
6
|
+
npm add @ricsam/isolate-daemon
|
|
7
|
+
```
|
|
6
8
|
|
|
7
|
-
|
|
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
|
-
|
|
17
|
+
**Starting the Daemon:**
|
|
10
18
|
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
29
|
+
console.log(`Daemon listening on ${daemon.socketPath}`);
|
|
19
30
|
|
|
20
|
-
|
|
31
|
+
// Get stats
|
|
32
|
+
const stats = daemon.getStats();
|
|
33
|
+
console.log(`Active isolates: ${stats.activeIsolates}`);
|
|
21
34
|
|
|
22
|
-
|
|
35
|
+
// Graceful shutdown
|
|
36
|
+
await daemon.close();
|
|
37
|
+
```
|
|
23
38
|
|
|
24
|
-
|
|
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
|
-
|
|
41
|
+
```bash
|
|
42
|
+
# Start daemon on default socket
|
|
43
|
+
npx isolate-daemon
|
|
30
44
|
|
|
31
|
-
|
|
32
|
-
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
+
}
|