@ai-ecoverse/slicc-kernel 1.19.0 → 1.20.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 +12 -2
- package/dist/driver-worker.js +248 -3
- package/dist/driver-worker.js.map +4 -4
- package/dist/index.js +274 -4
- package/dist/index.js.map +4 -4
- package/dist/kernel-worker.js +1997 -1894
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-driver-worker.js +248 -3
- package/dist/node-driver-worker.js.map +4 -4
- package/dist/node-process-worker.js +23 -0
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +2510 -2169
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +23 -0
- package/dist/process-worker.js.map +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,242 @@
|
|
|
1
|
+
// src/client/dial-stream.ts
|
|
2
|
+
function errorWithCode(message) {
|
|
3
|
+
const code = /^E[A-Z]+/.exec(message)?.[0];
|
|
4
|
+
return Object.assign(new Error(message), code ? { code } : {});
|
|
5
|
+
}
|
|
6
|
+
function dialStream(port, onClose) {
|
|
7
|
+
let closed = false;
|
|
8
|
+
let reader;
|
|
9
|
+
const acks = [];
|
|
10
|
+
const readable = new ReadableStream({
|
|
11
|
+
start(controller) {
|
|
12
|
+
reader = controller;
|
|
13
|
+
},
|
|
14
|
+
pull() {
|
|
15
|
+
if (!closed) port.postMessage({ more: true });
|
|
16
|
+
},
|
|
17
|
+
cancel() {
|
|
18
|
+
reader = void 0;
|
|
19
|
+
close();
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
const finish = (fail2) => {
|
|
23
|
+
const controller = reader;
|
|
24
|
+
reader = void 0;
|
|
25
|
+
for (const ack of acks.splice(0)) {
|
|
26
|
+
if (fail2) ack.reject(fail2);
|
|
27
|
+
else ack.resolve();
|
|
28
|
+
}
|
|
29
|
+
if (!controller) return;
|
|
30
|
+
if (fail2) controller.error(fail2);
|
|
31
|
+
else controller.close();
|
|
32
|
+
};
|
|
33
|
+
function shut() {
|
|
34
|
+
if (closed) return;
|
|
35
|
+
closed = true;
|
|
36
|
+
port.close();
|
|
37
|
+
onClose?.();
|
|
38
|
+
}
|
|
39
|
+
function close() {
|
|
40
|
+
if (!closed) port.postMessage({ close: true });
|
|
41
|
+
shut();
|
|
42
|
+
finish();
|
|
43
|
+
}
|
|
44
|
+
port.addEventListener("message", ({ data }) => {
|
|
45
|
+
if (data instanceof Uint8Array) reader?.enqueue(data);
|
|
46
|
+
else if ("ack" in data) acks.shift()?.resolve();
|
|
47
|
+
else if ("end" in data) finish();
|
|
48
|
+
else finish(errorWithCode(data.error));
|
|
49
|
+
});
|
|
50
|
+
port.start?.();
|
|
51
|
+
const writable = new WritableStream({
|
|
52
|
+
write(chunk) {
|
|
53
|
+
if (closed) throw errorWithCode("EPIPE: the connection is closed");
|
|
54
|
+
const copy = chunk.slice();
|
|
55
|
+
const acked = new Promise((resolve, reject) => acks.push({ resolve, reject }));
|
|
56
|
+
port.postMessage(copy, [copy.buffer]);
|
|
57
|
+
return acked;
|
|
58
|
+
},
|
|
59
|
+
close() {
|
|
60
|
+
if (!closed) port.postMessage({ end: true });
|
|
61
|
+
},
|
|
62
|
+
abort() {
|
|
63
|
+
close();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
const fail = (error) => {
|
|
67
|
+
shut();
|
|
68
|
+
finish(error);
|
|
69
|
+
};
|
|
70
|
+
return { readable, writable, close, fail };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/client/loopback-fetch.ts
|
|
74
|
+
var OWN_HEADERS = /* @__PURE__ */ new Set(["host", "connection", "content-length", "transfer-encoding"]);
|
|
75
|
+
var encoder = new TextEncoder();
|
|
76
|
+
var latin1 = new TextDecoder("latin1");
|
|
77
|
+
var ByteSource = class {
|
|
78
|
+
buffered = new Uint8Array(0);
|
|
79
|
+
reader;
|
|
80
|
+
constructor(reader) {
|
|
81
|
+
this.reader = reader;
|
|
82
|
+
}
|
|
83
|
+
async more() {
|
|
84
|
+
const { done, value } = await this.reader.read();
|
|
85
|
+
if (done) return false;
|
|
86
|
+
const joined = new Uint8Array(this.buffered.length + value.length);
|
|
87
|
+
joined.set(this.buffered);
|
|
88
|
+
joined.set(value, this.buffered.length);
|
|
89
|
+
this.buffered = joined;
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
async until(marker) {
|
|
93
|
+
const needle = encoder.encode(marker);
|
|
94
|
+
for (; ; ) {
|
|
95
|
+
const at = indexOf(this.buffered, needle);
|
|
96
|
+
if (at >= 0) return this.take(at + needle.length);
|
|
97
|
+
if (!await this.more()) return void 0;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async some(max) {
|
|
101
|
+
if (this.buffered.length === 0 && !await this.more()) return void 0;
|
|
102
|
+
return this.take(Math.min(max, this.buffered.length));
|
|
103
|
+
}
|
|
104
|
+
async exactly(count) {
|
|
105
|
+
while (this.buffered.length < count) if (!await this.more()) return void 0;
|
|
106
|
+
return this.take(count);
|
|
107
|
+
}
|
|
108
|
+
take(count) {
|
|
109
|
+
const out = this.buffered.slice(0, count);
|
|
110
|
+
this.buffered = this.buffered.slice(count);
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
function indexOf(haystack, needle) {
|
|
115
|
+
outer: for (let i = 0; i + needle.length <= haystack.length; i++) {
|
|
116
|
+
for (let j = 0; j < needle.length; j++) if (haystack[i + j] !== needle[j]) continue outer;
|
|
117
|
+
return i;
|
|
118
|
+
}
|
|
119
|
+
return -1;
|
|
120
|
+
}
|
|
121
|
+
function parseHead(bytes) {
|
|
122
|
+
const [statusLine = "", ...lines] = latin1.decode(bytes).split("\r\n");
|
|
123
|
+
const match = /^HTTP\/1\.[01] (\d{3})(?: (.*))?$/.exec(statusLine);
|
|
124
|
+
if (!match) throw errorWithCode(`EPROTO: not an HTTP/1.x response: ${statusLine.slice(0, 80)}`);
|
|
125
|
+
const headers = [];
|
|
126
|
+
for (const line of lines) {
|
|
127
|
+
const colon = line.indexOf(":");
|
|
128
|
+
if (colon > 0) headers.push([line.slice(0, colon).trim(), line.slice(colon + 1).trim()]);
|
|
129
|
+
}
|
|
130
|
+
return { status: Number(match[1]), statusText: match[2] ?? "", headers };
|
|
131
|
+
}
|
|
132
|
+
function header(head, name) {
|
|
133
|
+
return head.headers.find(([key]) => key.toLowerCase() === name)?.[1];
|
|
134
|
+
}
|
|
135
|
+
async function* chunked(source) {
|
|
136
|
+
for (; ; ) {
|
|
137
|
+
const line = await source.until("\r\n");
|
|
138
|
+
if (!line) throw errorWithCode("EPROTO: the chunked body ended early");
|
|
139
|
+
const size = Number.parseInt(latin1.decode(line).trim().split(";")[0] ?? "", 16);
|
|
140
|
+
if (!Number.isFinite(size)) throw errorWithCode("EPROTO: a malformed chunk size");
|
|
141
|
+
if (size === 0) return;
|
|
142
|
+
const data = await source.exactly(size);
|
|
143
|
+
if (!data || !await source.exactly(2)) throw errorWithCode("EPROTO: a chunk ended early");
|
|
144
|
+
yield data;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function* counted(source, length) {
|
|
148
|
+
let left = length;
|
|
149
|
+
while (left > 0) {
|
|
150
|
+
const data = await source.some(left);
|
|
151
|
+
if (!data) throw errorWithCode("EPROTO: the body ended early");
|
|
152
|
+
left -= data.length;
|
|
153
|
+
yield data;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function* untilClose(source) {
|
|
157
|
+
for (let data = await source.some(65536); data; data = await source.some(65536)) yield data;
|
|
158
|
+
}
|
|
159
|
+
function bodyOf(source, head, method) {
|
|
160
|
+
if (method === "HEAD" || head.status === 204 || head.status === 304) return void 0;
|
|
161
|
+
if (header(head, "transfer-encoding")?.toLowerCase().includes("chunked")) return chunked(source);
|
|
162
|
+
const length = header(head, "content-length");
|
|
163
|
+
if (length === void 0) return untilClose(source);
|
|
164
|
+
const size = Number(length);
|
|
165
|
+
if (!/^\d+$/.test(length.trim()) || !Number.isSafeInteger(size)) {
|
|
166
|
+
throw errorWithCode(`EPROTO: a malformed Content-Length: ${length}`);
|
|
167
|
+
}
|
|
168
|
+
return counted(source, size);
|
|
169
|
+
}
|
|
170
|
+
function stream(chunks3, socket, signal) {
|
|
171
|
+
return new ReadableStream({
|
|
172
|
+
start(controller) {
|
|
173
|
+
signal?.addEventListener("abort", () => controller.error(signal.reason), { once: true });
|
|
174
|
+
},
|
|
175
|
+
async pull(controller) {
|
|
176
|
+
try {
|
|
177
|
+
const next = await chunks3.next();
|
|
178
|
+
if (next.done) {
|
|
179
|
+
controller.close();
|
|
180
|
+
socket.close();
|
|
181
|
+
} else controller.enqueue(next.value);
|
|
182
|
+
} catch (err) {
|
|
183
|
+
controller.error(err);
|
|
184
|
+
socket.close();
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
cancel() {
|
|
188
|
+
socket.close();
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
async function requestBytes(request) {
|
|
193
|
+
const url = new URL(request.url);
|
|
194
|
+
const body = request.body ? new Uint8Array(await request.arrayBuffer()) : void 0;
|
|
195
|
+
const lines = [`${request.method} ${url.pathname}${url.search} HTTP/1.1`, `Host: ${url.host}`];
|
|
196
|
+
request.headers.forEach((value, name) => {
|
|
197
|
+
if (!OWN_HEADERS.has(name)) lines.push(`${name}: ${value}`);
|
|
198
|
+
});
|
|
199
|
+
if (body) lines.push(`Content-Length: ${body.length}`);
|
|
200
|
+
lines.push("Connection: close", "", "");
|
|
201
|
+
const head = encoder.encode(lines.join("\r\n"));
|
|
202
|
+
const out = new Uint8Array(head.length + (body?.length ?? 0));
|
|
203
|
+
out.set(head);
|
|
204
|
+
if (body) out.set(body, head.length);
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
async function loopbackFetch(dial, input, options) {
|
|
208
|
+
options.signal?.throwIfAborted();
|
|
209
|
+
const request = input instanceof Request ? input : new Request(input);
|
|
210
|
+
const bytes = await requestBytes(request);
|
|
211
|
+
const socket = await dial({
|
|
212
|
+
port: options.port,
|
|
213
|
+
...options.host ? { host: options.host } : {}
|
|
214
|
+
});
|
|
215
|
+
options.signal?.addEventListener("abort", () => socket.close(), { once: true });
|
|
216
|
+
try {
|
|
217
|
+
const writer = socket.writable.getWriter();
|
|
218
|
+
await writer.write(bytes);
|
|
219
|
+
writer.releaseLock();
|
|
220
|
+
const source = new ByteSource(socket.readable.getReader());
|
|
221
|
+
let head;
|
|
222
|
+
do {
|
|
223
|
+
const raw = await source.until("\r\n\r\n");
|
|
224
|
+
if (!raw) throw errorWithCode("ECONNRESET: the connection closed before a response");
|
|
225
|
+
head = parseHead(raw);
|
|
226
|
+
} while (head.status >= 100 && head.status < 200);
|
|
227
|
+
const chunks3 = bodyOf(source, head, request.method);
|
|
228
|
+
if (!chunks3) socket.close();
|
|
229
|
+
return new Response(chunks3 ? stream(chunks3, socket, options.signal) : null, {
|
|
230
|
+
status: head.status,
|
|
231
|
+
statusText: head.statusText,
|
|
232
|
+
headers: head.headers
|
|
233
|
+
});
|
|
234
|
+
} catch (err) {
|
|
235
|
+
socket.close();
|
|
236
|
+
throw err;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
1
240
|
// src/body-idle.ts
|
|
2
241
|
var BODY_IDLE_MS = 5 * 60 * 1e3;
|
|
3
242
|
function idleFailure(ms) {
|
|
@@ -277,7 +516,7 @@ var RemoteTransport = class {
|
|
|
277
516
|
};
|
|
278
517
|
|
|
279
518
|
// src/client/protocol.ts
|
|
280
|
-
var PROTOCOL = [1,
|
|
519
|
+
var PROTOCOL = [1, 4];
|
|
281
520
|
function locksOf(scope = globalThis) {
|
|
282
521
|
const locks = scope.navigator?.locks;
|
|
283
522
|
return typeof locks?.request === "function" ? locks : void 0;
|
|
@@ -304,8 +543,8 @@ var KernelCallError = class extends Error {
|
|
|
304
543
|
this.code = code;
|
|
305
544
|
}
|
|
306
545
|
};
|
|
307
|
-
var
|
|
308
|
-
var bytesOf = (data) => typeof data === "string" ?
|
|
546
|
+
var encoder2 = new TextEncoder();
|
|
547
|
+
var bytesOf = (data) => typeof data === "string" ? encoder2.encode(data) : data;
|
|
309
548
|
var UNAVAILABLE = {
|
|
310
549
|
manualRedirects: false,
|
|
311
550
|
encodedBodies: false,
|
|
@@ -413,6 +652,10 @@ var Channel = class {
|
|
|
413
652
|
else call.resolve(reply.result);
|
|
414
653
|
}
|
|
415
654
|
};
|
|
655
|
+
async function dialOn(channel, { port, host }) {
|
|
656
|
+
const req = { op: "dial", port, ...host ? { host } : {} };
|
|
657
|
+
return dialStream(await since(channel, 4, "dial", () => channel.call(req)));
|
|
658
|
+
}
|
|
416
659
|
async function spawnOn(channel, argv, opts) {
|
|
417
660
|
const { onStdout, onStderr, stdin, group: _, ...rest } = opts;
|
|
418
661
|
if (opts.pgid !== void 0)
|
|
@@ -591,6 +834,8 @@ async function attachKernel(port, options = {}) {
|
|
|
591
834
|
const { signal, ...rest } = req;
|
|
592
835
|
return transport.fetch({ ...rest, signal: signal ?? new AbortController().signal });
|
|
593
836
|
},
|
|
837
|
+
dial: (options2) => dialOn(channel, options2),
|
|
838
|
+
loopbackFetch: (input, options2) => loopbackFetch((o) => dialOn(channel, o), input, options2),
|
|
594
839
|
fs: fsOn(channel),
|
|
595
840
|
async close(o = {}) {
|
|
596
841
|
if (channel.failure) return;
|
|
@@ -717,7 +962,9 @@ async function checkLocalProxy(options) {
|
|
|
717
962
|
probe: {
|
|
718
963
|
rawFetch: reply.rawFetch,
|
|
719
964
|
requestBodyStreaming: reply.requestBodyStreaming,
|
|
720
|
-
maxRequestBodyBytes: reply.maxRequestBodyBytes
|
|
965
|
+
maxRequestBodyBytes: reply.maxRequestBodyBytes,
|
|
966
|
+
...typeof reply.kernelTunnel === "number" ? { kernelTunnel: reply.kernelTunnel } : {},
|
|
967
|
+
...typeof reply.kernelPort === "number" ? { kernelPort: reply.kernelPort } : {}
|
|
721
968
|
}
|
|
722
969
|
};
|
|
723
970
|
}
|
|
@@ -784,6 +1031,24 @@ function streamer(callback) {
|
|
|
784
1031
|
const decoder = new TextDecoder();
|
|
785
1032
|
return (bytes) => callback?.(decoder.decode(bytes, { stream: true }));
|
|
786
1033
|
}
|
|
1034
|
+
function dialer(call) {
|
|
1035
|
+
const open = /* @__PURE__ */ new Set();
|
|
1036
|
+
const dial = async ({ port, host }) => {
|
|
1037
|
+
let reply;
|
|
1038
|
+
try {
|
|
1039
|
+
reply = await call({ op: "dial", port, ...host ? { host } : {} });
|
|
1040
|
+
} catch (err) {
|
|
1041
|
+
throw errorWithCode(err.message);
|
|
1042
|
+
}
|
|
1043
|
+
const handle = dialStream(reply, () => open.delete(handle));
|
|
1044
|
+
open.add(handle);
|
|
1045
|
+
return handle;
|
|
1046
|
+
};
|
|
1047
|
+
const reset = () => {
|
|
1048
|
+
for (const handle of [...open]) handle.fail(errorWithCode("ECONNRESET: the kernel is gone"));
|
|
1049
|
+
};
|
|
1050
|
+
return { dial, reset };
|
|
1051
|
+
}
|
|
787
1052
|
async function createKernel(options = {}) {
|
|
788
1053
|
if (!globalThis.crossOriginIsolated) throw new Error(ISOLATION);
|
|
789
1054
|
const url = options.worker ?? new URL("./kernel-worker.js", import.meta.url);
|
|
@@ -791,7 +1056,9 @@ async function createKernel(options = {}) {
|
|
|
791
1056
|
const pending = /* @__PURE__ */ new Map();
|
|
792
1057
|
let nextId = 0;
|
|
793
1058
|
let failure2;
|
|
1059
|
+
const dials = dialer((req) => call(req));
|
|
794
1060
|
const fail = (error) => {
|
|
1061
|
+
dials.reset();
|
|
795
1062
|
failure2 = error;
|
|
796
1063
|
bridge?.close();
|
|
797
1064
|
for (const call2 of pending.values()) call2.reject(error);
|
|
@@ -897,6 +1164,7 @@ async function createKernel(options = {}) {
|
|
|
897
1164
|
close: () => send2({ action: "close" })
|
|
898
1165
|
};
|
|
899
1166
|
}
|
|
1167
|
+
const { dial } = dials;
|
|
900
1168
|
await call({
|
|
901
1169
|
op: "init",
|
|
902
1170
|
...options.root ? { root: options.root } : {},
|
|
@@ -928,6 +1196,8 @@ async function createKernel(options = {}) {
|
|
|
928
1196
|
},
|
|
929
1197
|
openTerminal,
|
|
930
1198
|
connect: async () => await call({ op: "connect" }),
|
|
1199
|
+
dial,
|
|
1200
|
+
loopbackFetch: (input, fetchOptions) => loopbackFetch(dial, input, fetchOptions),
|
|
931
1201
|
mount: async (spec) => await call({ op: "mount", spec }),
|
|
932
1202
|
umount: async (target) => void await call({ op: "umount", target }),
|
|
933
1203
|
mounts: async () => await call({ op: "mounts" }),
|