@ai-ecoverse/slicc-kernel 1.12.0 → 1.14.0
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 +22 -1
- package/dist/driver-worker.js +574 -0
- package/dist/driver-worker.js.map +7 -0
- package/dist/driver.js +115 -0
- package/dist/driver.js.map +7 -0
- package/dist/index.js +18 -1
- package/dist/index.js.map +2 -2
- package/dist/kernel-worker.js +1018 -41
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-driver-worker.js +576 -0
- package/dist/node-driver-worker.js.map +7 -0
- package/dist/node-process-worker.js +51 -16
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +1010 -22
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +51 -16
- package/dist/process-worker.js.map +3 -3
- package/package.json +4 -2
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
// src/node-driver-worker.ts
|
|
2
|
+
import { parentPort } from "node:worker_threads";
|
|
3
|
+
|
|
4
|
+
// src/kernel/net/remote-transport.ts
|
|
5
|
+
var TransportError = class extends Error {
|
|
6
|
+
status;
|
|
7
|
+
code;
|
|
8
|
+
constructor(message, status, code) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.code = code;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var RemoteTransport = class {
|
|
15
|
+
traits;
|
|
16
|
+
port;
|
|
17
|
+
waiting = /* @__PURE__ */ new Map();
|
|
18
|
+
nextId = 0;
|
|
19
|
+
failure;
|
|
20
|
+
constructor(port, traits) {
|
|
21
|
+
this.port = port;
|
|
22
|
+
this.traits = traits;
|
|
23
|
+
}
|
|
24
|
+
receive(reply) {
|
|
25
|
+
const waiter = this.waiting.get(reply.nid);
|
|
26
|
+
if (!waiter) return;
|
|
27
|
+
this.waiting.delete(reply.nid);
|
|
28
|
+
if (reply.net === "error") {
|
|
29
|
+
waiter.reject(new TransportError(reply.message, reply.status, reply.code));
|
|
30
|
+
} else waiter.resolve(reply);
|
|
31
|
+
}
|
|
32
|
+
fail(error) {
|
|
33
|
+
this.failure = error;
|
|
34
|
+
for (const waiter of this.waiting.values()) waiter.reject(error);
|
|
35
|
+
this.waiting.clear();
|
|
36
|
+
}
|
|
37
|
+
ask(call) {
|
|
38
|
+
if (this.failure) return Promise.reject(this.failure);
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
this.waiting.set(call.nid, { resolve, reject });
|
|
41
|
+
this.port.postMessage(call);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
cancel(nid) {
|
|
45
|
+
const waiter = this.waiting.get(nid);
|
|
46
|
+
this.waiting.delete(nid);
|
|
47
|
+
waiter?.reject(new TransportError("the request was cancelled"));
|
|
48
|
+
this.port.postMessage({ net: "cancel", nid });
|
|
49
|
+
}
|
|
50
|
+
async fetch(request) {
|
|
51
|
+
const nid = ++this.nextId;
|
|
52
|
+
const { signal } = request;
|
|
53
|
+
signal.throwIfAborted();
|
|
54
|
+
let finished = false;
|
|
55
|
+
const abort = () => {
|
|
56
|
+
finished = true;
|
|
57
|
+
this.cancel(nid);
|
|
58
|
+
};
|
|
59
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
60
|
+
const finish = () => {
|
|
61
|
+
if (finished) return;
|
|
62
|
+
finished = true;
|
|
63
|
+
signal.removeEventListener("abort", abort);
|
|
64
|
+
};
|
|
65
|
+
let head;
|
|
66
|
+
try {
|
|
67
|
+
head = await this.ask({
|
|
68
|
+
net: "fetch",
|
|
69
|
+
nid,
|
|
70
|
+
url: request.url,
|
|
71
|
+
method: request.method,
|
|
72
|
+
headers: request.headers,
|
|
73
|
+
...request.body ? { body: request.body } : {}
|
|
74
|
+
});
|
|
75
|
+
} catch (e) {
|
|
76
|
+
finish();
|
|
77
|
+
throw e;
|
|
78
|
+
}
|
|
79
|
+
const { status, statusText, headers } = head;
|
|
80
|
+
const read = async () => {
|
|
81
|
+
if (finished) return void 0;
|
|
82
|
+
const reply = await this.ask({ net: "read", nid }).catch((e) => {
|
|
83
|
+
finish();
|
|
84
|
+
throw e;
|
|
85
|
+
});
|
|
86
|
+
if (reply.net === "chunk") return reply.bytes;
|
|
87
|
+
finish();
|
|
88
|
+
return void 0;
|
|
89
|
+
};
|
|
90
|
+
const cancel = async () => {
|
|
91
|
+
if (finished) return;
|
|
92
|
+
finish();
|
|
93
|
+
this.cancel(nid);
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
status,
|
|
97
|
+
statusText,
|
|
98
|
+
headers,
|
|
99
|
+
cancel,
|
|
100
|
+
body: {
|
|
101
|
+
[Symbol.asyncIterator]: () => ({
|
|
102
|
+
next: async () => {
|
|
103
|
+
const bytes = await read();
|
|
104
|
+
return bytes ? { value: bytes, done: false } : { value: void 0, done: true };
|
|
105
|
+
},
|
|
106
|
+
return: async () => {
|
|
107
|
+
await cancel();
|
|
108
|
+
return { value: void 0, done: true };
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// src/client/protocol.ts
|
|
117
|
+
var PROTOCOL = [1, 2];
|
|
118
|
+
function locksOf(scope = globalThis) {
|
|
119
|
+
const locks = scope.navigator?.locks;
|
|
120
|
+
return typeof locks?.request === "function" ? locks : void 0;
|
|
121
|
+
}
|
|
122
|
+
function versionError(theirs) {
|
|
123
|
+
const major = Array.isArray(theirs) ? theirs[0] : void 0;
|
|
124
|
+
if (major === PROTOCOL[0]) return void 0;
|
|
125
|
+
const named = typeof major === "number" ? `${major}.x` : "without a version";
|
|
126
|
+
return `slicc-kernel client protocol ${named} is not supported: this side speaks ${PROTOCOL.join(".")}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/client/attach.ts
|
|
130
|
+
var KernelGoneError = class extends Error {
|
|
131
|
+
constructor(message = "the slicc-kernel this client was attached to is gone") {
|
|
132
|
+
super(message);
|
|
133
|
+
this.name = "KernelGoneError";
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
var KernelCallError = class extends Error {
|
|
137
|
+
code;
|
|
138
|
+
constructor(message, code) {
|
|
139
|
+
super(message);
|
|
140
|
+
this.name = "KernelCallError";
|
|
141
|
+
this.code = code;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
var encoder = new TextEncoder();
|
|
145
|
+
var bytesOf = (data) => typeof data === "string" ? encoder.encode(data) : data;
|
|
146
|
+
var UNAVAILABLE = {
|
|
147
|
+
manualRedirects: false,
|
|
148
|
+
encodedBodies: false,
|
|
149
|
+
maxRequestBody: 0,
|
|
150
|
+
unavailable: true
|
|
151
|
+
};
|
|
152
|
+
async function holdLock(locks) {
|
|
153
|
+
if (!locks) return { release() {
|
|
154
|
+
} };
|
|
155
|
+
const name = `slicc-kernel-client:${crypto.randomUUID()}`;
|
|
156
|
+
const granted = Promise.withResolvers();
|
|
157
|
+
const held = Promise.withResolvers();
|
|
158
|
+
void locks.request(name, () => {
|
|
159
|
+
granted.resolve();
|
|
160
|
+
return held.promise;
|
|
161
|
+
});
|
|
162
|
+
await granted.promise;
|
|
163
|
+
return { name, release: () => held.resolve() };
|
|
164
|
+
}
|
|
165
|
+
function concat(chunks) {
|
|
166
|
+
const out = new Uint8Array(chunks.reduce((n, c) => n + c.length, 0));
|
|
167
|
+
let at = 0;
|
|
168
|
+
for (const chunk of chunks) {
|
|
169
|
+
out.set(chunk, at);
|
|
170
|
+
at += chunk.length;
|
|
171
|
+
}
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
var Channel = class {
|
|
175
|
+
greeted = Promise.withResolvers();
|
|
176
|
+
ended = Promise.withResolvers();
|
|
177
|
+
remote;
|
|
178
|
+
failure;
|
|
179
|
+
pending = /* @__PURE__ */ new Map();
|
|
180
|
+
watching = /* @__PURE__ */ new Map();
|
|
181
|
+
protocol = PROTOCOL;
|
|
182
|
+
nextId = 0;
|
|
183
|
+
port;
|
|
184
|
+
release;
|
|
185
|
+
constructor(port, release) {
|
|
186
|
+
this.port = port;
|
|
187
|
+
this.release = release;
|
|
188
|
+
this.greeted.promise.catch(() => void 0);
|
|
189
|
+
port.addEventListener("message", (event) => this.receive(event.data));
|
|
190
|
+
port.addEventListener("close", () => this.end(new KernelGoneError()));
|
|
191
|
+
port.start?.();
|
|
192
|
+
}
|
|
193
|
+
post(message) {
|
|
194
|
+
if (!this.failure) this.port.postMessage(message);
|
|
195
|
+
}
|
|
196
|
+
end(error) {
|
|
197
|
+
if (this.failure) return;
|
|
198
|
+
this.failure = error;
|
|
199
|
+
this.greeted.reject(error);
|
|
200
|
+
this.remote?.fail(error);
|
|
201
|
+
for (const call of this.pending.values()) call.reject(error);
|
|
202
|
+
this.pending.clear();
|
|
203
|
+
this.release();
|
|
204
|
+
this.port.close?.();
|
|
205
|
+
this.ended.resolve(error);
|
|
206
|
+
}
|
|
207
|
+
request(call, handlers = {}) {
|
|
208
|
+
const id = ++this.nextId;
|
|
209
|
+
const { failure } = this;
|
|
210
|
+
if (failure) return { id, done: Promise.reject(failure) };
|
|
211
|
+
const done = new Promise((resolve, reject) => {
|
|
212
|
+
this.pending.set(id, { resolve, reject, ...handlers });
|
|
213
|
+
this.port.postMessage({ ...call, id });
|
|
214
|
+
});
|
|
215
|
+
return { id, done };
|
|
216
|
+
}
|
|
217
|
+
call(call) {
|
|
218
|
+
return this.request(call).done;
|
|
219
|
+
}
|
|
220
|
+
receive(data) {
|
|
221
|
+
if (data.watch !== void 0) {
|
|
222
|
+
this.watching.get(data.watch)?.(data.change);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (data.bye !== void 0) {
|
|
226
|
+
this.end(new KernelGoneError(data.bye));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (data.hello) {
|
|
230
|
+
this.greeted.resolve(data.hello);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (data.net !== void 0) {
|
|
234
|
+
this.remote?.receive(data);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const reply = data;
|
|
238
|
+
const call = this.pending.get(reply.id);
|
|
239
|
+
if (!call) return;
|
|
240
|
+
if (reply.fd !== void 0) {
|
|
241
|
+
call.output?.(reply.fd, reply.bytes);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (reply.started !== void 0) {
|
|
245
|
+
call.started?.(reply.started);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
this.pending.delete(reply.id);
|
|
249
|
+
if (reply.error !== void 0) call.reject(new KernelCallError(reply.error, reply.code));
|
|
250
|
+
else call.resolve(reply.result);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
async function spawnOn(channel, argv, opts) {
|
|
254
|
+
const { onStdout, onStderr, stdin, group: _, ...rest } = opts;
|
|
255
|
+
const started = Promise.withResolvers();
|
|
256
|
+
const options = { ...rest, ...stdin !== void 0 ? { stdin: bytesOf(stdin) } : {} };
|
|
257
|
+
const { done } = channel.request(
|
|
258
|
+
{ op: "spawn", argv, options },
|
|
259
|
+
{
|
|
260
|
+
output: (fd, bytes) => (fd === 1 ? onStdout : onStderr)?.(bytes),
|
|
261
|
+
started: started.resolve
|
|
262
|
+
}
|
|
263
|
+
);
|
|
264
|
+
done.catch(started.reject);
|
|
265
|
+
const pid = await started.promise;
|
|
266
|
+
return {
|
|
267
|
+
pid,
|
|
268
|
+
pgid: pid,
|
|
269
|
+
exited: done,
|
|
270
|
+
signal: async (name = "SIGTERM") => void await channel.call({ op: "kill", pid: -pid, signal: name })
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
async function runOn(channel, argv, opts) {
|
|
274
|
+
const { onStdout, onStderr, ...rest } = opts;
|
|
275
|
+
const chunks = { 1: [], 2: [] };
|
|
276
|
+
const live = { 1: new TextDecoder(), 2: new TextDecoder() };
|
|
277
|
+
const tee = (fd, on) => (bytes) => {
|
|
278
|
+
chunks[fd].push(bytes);
|
|
279
|
+
on?.(live[fd].decode(bytes, { stream: true }));
|
|
280
|
+
};
|
|
281
|
+
const child = await spawnOn(channel, argv, {
|
|
282
|
+
...rest,
|
|
283
|
+
onStdout: tee(1, onStdout),
|
|
284
|
+
onStderr: tee(2, onStderr)
|
|
285
|
+
});
|
|
286
|
+
const status = await child.exited;
|
|
287
|
+
const text = (fd) => new TextDecoder().decode(concat(chunks[fd]));
|
|
288
|
+
return { pid: child.pid, status, stdout: text(1), stderr: text(2) };
|
|
289
|
+
}
|
|
290
|
+
async function terminalOn(channel, argv, opts) {
|
|
291
|
+
const { onData, ...options } = opts;
|
|
292
|
+
let listener = onData ?? null;
|
|
293
|
+
const backlog = [];
|
|
294
|
+
const started = Promise.withResolvers();
|
|
295
|
+
const { id, done } = channel.request(
|
|
296
|
+
{ op: "open-terminal", argv, options },
|
|
297
|
+
{
|
|
298
|
+
output: (_fd, bytes) => listener ? listener(bytes) : backlog.push(bytes),
|
|
299
|
+
started: started.resolve
|
|
300
|
+
}
|
|
301
|
+
);
|
|
302
|
+
done.catch(started.reject);
|
|
303
|
+
const pid = await started.promise;
|
|
304
|
+
const send = (action) => void channel.call({ op: "terminal", terminal: id, ...action }).catch(() => void 0);
|
|
305
|
+
return {
|
|
306
|
+
pid,
|
|
307
|
+
exited: done,
|
|
308
|
+
get onData() {
|
|
309
|
+
return listener;
|
|
310
|
+
},
|
|
311
|
+
set onData(next) {
|
|
312
|
+
listener = next;
|
|
313
|
+
if (next) for (const bytes of backlog.splice(0)) next(bytes);
|
|
314
|
+
},
|
|
315
|
+
write: (data) => send({ action: "write", bytes: bytesOf(data) }),
|
|
316
|
+
resize: (cols, rows) => send({ action: "resize", cols, rows }),
|
|
317
|
+
signal: (name) => send({ action: "signal", signal: name }),
|
|
318
|
+
close: () => send({ action: "close" })
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
function fsOn(channel) {
|
|
322
|
+
const fs = (method, ...args) => channel.call({ op: "fs", method, args });
|
|
323
|
+
const done = async (method, ...args) => void await fs(method, ...args);
|
|
324
|
+
return {
|
|
325
|
+
readFile: (path) => fs("readFile", path),
|
|
326
|
+
readText: async (path) => new TextDecoder().decode(await fs("readFile", path)),
|
|
327
|
+
writeFile: (path, data) => done("writeFile", path, data),
|
|
328
|
+
stat: (path) => fs("stat", path),
|
|
329
|
+
lstat: (path) => fs("lstat", path),
|
|
330
|
+
readdir: (path) => fs("readdir", path),
|
|
331
|
+
mkdir: (path) => done("mkdir", path),
|
|
332
|
+
rm: (path, o = {}) => done("rm", path, o.force === true),
|
|
333
|
+
rename: (from, to) => done("rename", from, to),
|
|
334
|
+
realpath: (path) => fs("realpath", path),
|
|
335
|
+
symlink: (target, path) => done("symlink", target, path),
|
|
336
|
+
readlink: (path) => fs("readlink", path),
|
|
337
|
+
exists: (path) => fs("exists", path),
|
|
338
|
+
async watch(paths, options, onChange) {
|
|
339
|
+
if (channel.protocol[1] < 1) {
|
|
340
|
+
throw new KernelCallError(
|
|
341
|
+
`this kernel speaks protocol ${channel.protocol.join(".")}, which has no watch`,
|
|
342
|
+
"ENOSYS"
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
const { id, done: done2 } = channel.request({
|
|
346
|
+
op: "watch",
|
|
347
|
+
paths,
|
|
348
|
+
recursive: options.recursive === true
|
|
349
|
+
});
|
|
350
|
+
channel.watching.set(id, onChange);
|
|
351
|
+
try {
|
|
352
|
+
await done2;
|
|
353
|
+
} catch (error) {
|
|
354
|
+
channel.watching.delete(id);
|
|
355
|
+
throw error;
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
close: () => {
|
|
359
|
+
if (!channel.watching.delete(id)) return;
|
|
360
|
+
void channel.call({ op: "unwatch", watch: id }).catch(() => void 0);
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
function since(channel, minor, what, run) {
|
|
367
|
+
if (channel.protocol[1] < minor) {
|
|
368
|
+
return Promise.reject(
|
|
369
|
+
new KernelCallError(
|
|
370
|
+
`this kernel speaks protocol ${channel.protocol.join(".")}, which has no ${what}`,
|
|
371
|
+
"ENOSYS"
|
|
372
|
+
)
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
return run();
|
|
376
|
+
}
|
|
377
|
+
async function greet(channel, lock, timeoutMs) {
|
|
378
|
+
channel.post({ hello: { protocol: PROTOCOL, ...lock ? { lock } : {} } });
|
|
379
|
+
const timer = setTimeout(
|
|
380
|
+
() => channel.end(
|
|
381
|
+
new KernelGoneError(`no slicc-kernel answered on this port within ${timeoutMs} ms`)
|
|
382
|
+
),
|
|
383
|
+
timeoutMs
|
|
384
|
+
);
|
|
385
|
+
let hello;
|
|
386
|
+
try {
|
|
387
|
+
hello = await channel.greeted.promise;
|
|
388
|
+
} finally {
|
|
389
|
+
clearTimeout(timer);
|
|
390
|
+
}
|
|
391
|
+
const refused = hello.error ?? versionError(hello.protocol);
|
|
392
|
+
if (refused) {
|
|
393
|
+
const error = new Error(refused);
|
|
394
|
+
channel.end(error);
|
|
395
|
+
throw error;
|
|
396
|
+
}
|
|
397
|
+
return hello;
|
|
398
|
+
}
|
|
399
|
+
async function attachKernel(port, options = {}) {
|
|
400
|
+
const locks = options.locks === null ? void 0 : options.locks ?? locksOf();
|
|
401
|
+
const own = await holdLock(locks);
|
|
402
|
+
const channel = new Channel(port, own.release);
|
|
403
|
+
const hello = await greet(channel, own.name, options.timeoutMs ?? 1e4);
|
|
404
|
+
channel.protocol = hello.protocol;
|
|
405
|
+
if (hello.lock && locks) void locks.request(hello.lock, () => channel.end(new KernelGoneError()));
|
|
406
|
+
const transport = new RemoteTransport(
|
|
407
|
+
{ postMessage: (call) => channel.post(call) },
|
|
408
|
+
hello.traits ?? UNAVAILABLE
|
|
409
|
+
);
|
|
410
|
+
channel.remote = transport;
|
|
411
|
+
return {
|
|
412
|
+
protocol: hello.protocol,
|
|
413
|
+
transport,
|
|
414
|
+
closed: channel.ended.promise,
|
|
415
|
+
spawn: (argv, opts = {}) => spawnOn(channel, argv, opts),
|
|
416
|
+
run: (argv, opts = {}) => runOn(channel, argv, opts),
|
|
417
|
+
openTerminal: (argv, opts = {}) => terminalOn(channel, argv, opts),
|
|
418
|
+
ps: () => channel.call({ op: "ps" }),
|
|
419
|
+
mount: (spec) => since(channel, 2, "mount", () => channel.call({ op: "mount", spec })),
|
|
420
|
+
umount: async (target) => void await since(channel, 2, "umount", () => channel.call({ op: "umount", target })),
|
|
421
|
+
mounts: () => since(channel, 2, "mounts", () => channel.call({ op: "mounts" })),
|
|
422
|
+
kill: async (pid, signal = "SIGTERM") => void await channel.call({ op: "kill", pid, signal }),
|
|
423
|
+
fetch: (req) => {
|
|
424
|
+
const { signal, ...rest } = req;
|
|
425
|
+
return transport.fetch({ ...rest, signal: signal ?? new AbortController().signal });
|
|
426
|
+
},
|
|
427
|
+
fs: fsOn(channel),
|
|
428
|
+
async close(o = {}) {
|
|
429
|
+
if (channel.failure) return;
|
|
430
|
+
await channel.call({ op: "detach", ...o.kill ? { kill: true } : {} }).catch(() => void 0);
|
|
431
|
+
channel.end(new KernelGoneError("this client is closed"));
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// src/mount/protocol.ts
|
|
437
|
+
var DRIVER_PROTOCOL = [1, 0];
|
|
438
|
+
function driverVersionError(theirs) {
|
|
439
|
+
const major = Array.isArray(theirs) ? theirs[0] : void 0;
|
|
440
|
+
if (major === DRIVER_PROTOCOL[0]) return void 0;
|
|
441
|
+
const named = typeof major === "number" ? `${major}.x` : "without a version";
|
|
442
|
+
return `slicc-kernel driver protocol ${named} is not supported: this side speaks ${DRIVER_PROTOCOL.join(".")}`;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// src/mount/driver.ts
|
|
446
|
+
function fsError(code, message = code) {
|
|
447
|
+
return Object.assign(new Error(message), { code });
|
|
448
|
+
}
|
|
449
|
+
async function dispatch(h, req) {
|
|
450
|
+
switch (req.op) {
|
|
451
|
+
case "getattr":
|
|
452
|
+
return h.getattr(req.path);
|
|
453
|
+
case "readdir":
|
|
454
|
+
return h.readdir(req.path);
|
|
455
|
+
case "open":
|
|
456
|
+
return h.open(req.path, {
|
|
457
|
+
write: req.write,
|
|
458
|
+
create: req.create,
|
|
459
|
+
truncate: req.truncate,
|
|
460
|
+
exclusive: req.exclusive
|
|
461
|
+
});
|
|
462
|
+
case "read":
|
|
463
|
+
return h.read(req.fh, req.offset, req.size);
|
|
464
|
+
case "write":
|
|
465
|
+
return h.write(req.fh, req.offset, req.bytes);
|
|
466
|
+
case "release":
|
|
467
|
+
return h.release(req.fh);
|
|
468
|
+
case "mkdir":
|
|
469
|
+
return h.mkdir(req.path);
|
|
470
|
+
case "rmdir":
|
|
471
|
+
return h.rmdir(req.path);
|
|
472
|
+
case "unlink":
|
|
473
|
+
return h.unlink(req.path);
|
|
474
|
+
case "rename":
|
|
475
|
+
return h.rename(req.from, req.to);
|
|
476
|
+
case "symlink":
|
|
477
|
+
if (!h.symlink) throw fsError("EPERM", "this file system has no symlinks");
|
|
478
|
+
return h.symlink(req.target, req.path);
|
|
479
|
+
case "readlink":
|
|
480
|
+
if (!h.readlink) throw fsError("EINVAL", `${req.path} is not a symlink`);
|
|
481
|
+
return h.readlink(req.path);
|
|
482
|
+
case "setattr":
|
|
483
|
+
return h.setattr?.(req.path, {
|
|
484
|
+
...req.mode !== void 0 ? { mode: req.mode } : {},
|
|
485
|
+
...req.mtime !== void 0 ? { mtime: req.mtime } : {}
|
|
486
|
+
});
|
|
487
|
+
case "statfs":
|
|
488
|
+
return h.statfs?.() ?? null;
|
|
489
|
+
default:
|
|
490
|
+
throw fsError("ENOSYS", `unknown driver op ${req.op}`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
function serveFilesystem(port, handlers, capabilities = {}) {
|
|
494
|
+
let open = false;
|
|
495
|
+
const reply = (message, transfer = []) => port.postMessage(message, transfer);
|
|
496
|
+
async function hello(message) {
|
|
497
|
+
const refused = driverVersionError(message.protocol);
|
|
498
|
+
if (refused) {
|
|
499
|
+
reply({ hello: { protocol: DRIVER_PROTOCOL, error: refused, errno: "EPROTO" } });
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
try {
|
|
503
|
+
const refined = await handlers.mount?.(message.mount);
|
|
504
|
+
open = true;
|
|
505
|
+
reply({
|
|
506
|
+
hello: { protocol: DRIVER_PROTOCOL, capabilities: { ...capabilities, ...refined } }
|
|
507
|
+
});
|
|
508
|
+
} catch (err) {
|
|
509
|
+
const code = err?.code;
|
|
510
|
+
reply({
|
|
511
|
+
hello: {
|
|
512
|
+
protocol: DRIVER_PROTOCOL,
|
|
513
|
+
error: err instanceof Error ? err.message : String(err),
|
|
514
|
+
errno: typeof code === "string" ? code : "EIO"
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
port.addEventListener("message", (event) => {
|
|
520
|
+
const data = event.data;
|
|
521
|
+
if (data.hello) {
|
|
522
|
+
void hello(data.hello);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
if (!open) return;
|
|
526
|
+
dispatch(handlers, data).then(
|
|
527
|
+
(result) => {
|
|
528
|
+
if (result instanceof Uint8Array) {
|
|
529
|
+
const bytes = result.slice();
|
|
530
|
+
reply({ id: data.id, result: bytes }, [bytes.buffer]);
|
|
531
|
+
} else reply({ id: data.id, result: result ?? null });
|
|
532
|
+
},
|
|
533
|
+
(err) => {
|
|
534
|
+
const code = err?.code;
|
|
535
|
+
reply({
|
|
536
|
+
id: data.id,
|
|
537
|
+
errno: typeof code === "string" ? code : "EIO",
|
|
538
|
+
message: err instanceof Error ? err.message : String(err)
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
);
|
|
542
|
+
});
|
|
543
|
+
port.start?.();
|
|
544
|
+
return { invalidate: (paths) => reply({ invalidate: paths }) };
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// src/mount/host.ts
|
|
548
|
+
async function hostDriver(start, load) {
|
|
549
|
+
try {
|
|
550
|
+
const factory = (await load(start.code)).default;
|
|
551
|
+
if (typeof factory !== "function") {
|
|
552
|
+
throw new Error("a slicc file system module must export a default function");
|
|
553
|
+
}
|
|
554
|
+
const client = await attachKernel(start.client, { locks: null });
|
|
555
|
+
const made = await factory({ fetch: (request) => client.fetch(request) });
|
|
556
|
+
serveFilesystem(start.driver, made.handlers, made.capabilities ?? {});
|
|
557
|
+
} catch (err) {
|
|
558
|
+
start.driver.postMessage({
|
|
559
|
+
hello: {
|
|
560
|
+
protocol: DRIVER_PROTOCOL,
|
|
561
|
+
error: `the driver did not start: ${err instanceof Error ? err.message : String(err)}`,
|
|
562
|
+
errno: "EIO"
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// src/node-driver-worker.ts
|
|
569
|
+
parentPort?.once(
|
|
570
|
+
"message",
|
|
571
|
+
(start) => hostDriver(
|
|
572
|
+
start,
|
|
573
|
+
(code) => import(`data:text/javascript;base64,${Buffer.from(code).toString("base64")}`)
|
|
574
|
+
)
|
|
575
|
+
);
|
|
576
|
+
//# sourceMappingURL=node-driver-worker.js.map
|