knitting 0.1.56 → 0.1.60

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.
Files changed (77) hide show
  1. package/README.md +254 -40
  2. package/knitting.d.ts +3 -2
  3. package/knitting.js +3 -3
  4. package/map.md +21 -9
  5. package/package.json +7 -1
  6. package/scripts/build-compiled-worker.ts +276 -0
  7. package/scripts/compiled-worker/porffor.d.ts +5 -0
  8. package/scripts/compiled-worker/runtime.ts +529 -0
  9. package/scripts/compiled-worker/task-shim.ts +32 -0
  10. package/src/api.d.ts +24 -1
  11. package/src/api.js +199 -20
  12. package/src/common/module-url.d.ts +1 -0
  13. package/src/common/module-url.js +30 -2
  14. package/src/common/node-compat.d.ts +2 -0
  15. package/src/common/path-canonical.js +20 -6
  16. package/src/common/runtime.d.ts +2 -1
  17. package/src/common/runtime.js +18 -4
  18. package/src/common/task-source.d.ts +2 -0
  19. package/src/common/task-source.js +17 -1
  20. package/src/common/worker-runtime.js +16 -7
  21. package/src/connections/buffer-reference-native.js +25 -1
  22. package/src/connections/buffer-reference.d.ts +23 -2
  23. package/src/connections/buffer-reference.js +143 -29
  24. package/src/connections/bun.d.ts +1 -1
  25. package/src/connections/bun.js +25 -14
  26. package/src/connections/deno.d.ts +3 -1
  27. package/src/connections/deno.js +35 -14
  28. package/src/connections/external-array-buffer.d.ts +2 -0
  29. package/src/connections/external-array-buffer.js +33 -0
  30. package/src/connections/node-addons.d.ts +11 -0
  31. package/src/connections/node-addons.js +49 -1
  32. package/src/connections/node-ffi-api.d.ts +19 -0
  33. package/src/connections/node-ffi-api.js +28 -0
  34. package/src/connections/node-ffi.d.ts +27 -0
  35. package/src/connections/node-ffi.js +262 -0
  36. package/src/connections/node.d.ts +1 -1
  37. package/src/connections/node.js +20 -20
  38. package/src/connections/package-assets.js +25 -15
  39. package/src/connections/posix.d.ts +42 -0
  40. package/src/connections/posix.js +101 -0
  41. package/src/connections/process-shared-buffer.d.ts +1 -0
  42. package/src/connections/process-shared-buffer.js +11 -45
  43. package/src/connections/types.d.ts +4 -0
  44. package/src/connections/windows.d.ts +19 -10
  45. package/src/connections/windows.js +87 -66
  46. package/src/error.d.ts +7 -6
  47. package/src/error.js +8 -7
  48. package/src/memory/lock.d.ts +87 -77
  49. package/src/memory/lock.js +103 -84
  50. package/src/memory/payloadCodec.d.ts +11 -2
  51. package/src/memory/payloadCodec.js +127 -31
  52. package/src/permission/compatibility.d.ts +28 -0
  53. package/src/permission/compatibility.js +239 -0
  54. package/src/permission/index.d.ts +2 -0
  55. package/src/permission/index.js +1 -0
  56. package/src/permission/protocol.d.ts +1 -0
  57. package/src/permission/protocol.js +54 -14
  58. package/src/runtime/compiled-artifact.d.ts +21 -0
  59. package/src/runtime/compiled-artifact.js +260 -0
  60. package/src/runtime/compiled-builder.d.ts +8 -0
  61. package/src/runtime/compiled-builder.js +42 -0
  62. package/src/runtime/compiled-worker.d.ts +27 -0
  63. package/src/runtime/compiled-worker.js +562 -0
  64. package/src/runtime/inline-executor.js +27 -15
  65. package/src/runtime/pool.d.ts +7 -2
  66. package/src/runtime/pool.js +74 -6
  67. package/src/runtime/process-worker.d.ts +1 -1
  68. package/src/runtime/process-worker.js +48 -8
  69. package/src/runtime/tx-queue.d.ts +3 -1
  70. package/src/runtime/tx-queue.js +4 -1
  71. package/src/types.d.ts +48 -6
  72. package/src/worker/composable-runners.js +3 -2
  73. package/src/worker/loop.d.ts +1 -0
  74. package/src/worker/loop.js +25 -12
  75. package/src/worker/task-loader.d.ts +5 -4
  76. package/src/worker/task-loader.js +13 -7
  77. package/src/worker/timers.js +8 -6
@@ -0,0 +1,42 @@
1
+ import { getNodeBuiltinModule, getNodeProcess } from "../common/node-compat.js";
2
+ const filePath = (href) => {
3
+ const converter = getNodeBuiltinModule("node:url")?.fileURLToPath;
4
+ if (converter !== undefined)
5
+ return converter(href);
6
+ return decodeURIComponent(new URL(href).pathname);
7
+ };
8
+ const runtimeExecutable = () => {
9
+ const process = getNodeProcess();
10
+ return process?.versions?.bun !== undefined && process.execPath !== undefined
11
+ ? process.execPath
12
+ : "bun";
13
+ };
14
+ export const buildCompiledWorkerArtifact = ({ inspection, source, tasks, options, }) => {
15
+ const childProcess = getNodeBuiltinModule("node:child_process");
16
+ if (childProcess === undefined) {
17
+ throw new Error("automatic compilation requires Bun or Node child processes");
18
+ }
19
+ const builder = filePath(new URL("../../scripts/build-compiled-worker.ts", import.meta.url).href);
20
+ const args = [
21
+ builder,
22
+ "--module",
23
+ filePath(source),
24
+ "--out",
25
+ filePath(inspection.artifactHref),
26
+ "--manifest",
27
+ filePath(inspection.manifestHref),
28
+ "--tasks",
29
+ tasks.join(","),
30
+ ];
31
+ if (options?.compiler !== undefined) {
32
+ args.push("--porf", options.compiler);
33
+ }
34
+ const result = childProcess.spawnSync(runtimeExecutable(), args, {
35
+ stdio: "inherit",
36
+ });
37
+ if (result.error !== undefined)
38
+ throw result.error;
39
+ if (result.status !== 0) {
40
+ throw new Error("compiled worker build exited with code " + result.status);
41
+ }
42
+ };
@@ -0,0 +1,27 @@
1
+ import { type SignalAbortStore } from "../shared/abortSignal.js";
2
+ import type { WorkerContext, WorkerSettings } from "../types.js";
3
+ type CompiledAbortChannel = SignalAbortStore & {
4
+ /** Passed to the child so it can map the same bitmap. */
5
+ environment: Record<string, string>;
6
+ close: () => void;
7
+ };
8
+ /**
9
+ * Host half of the abort bitmap. Slot bookkeeping is the same allocator every
10
+ * other lane uses; only the backing store differs, because a compiled child is
11
+ * a separate process and reads the words through named POSIX shared memory.
12
+ */
13
+ export declare const createCompiledAbortChannel: (requestedMax?: number) => CompiledAbortChannel;
14
+ type CompiledWorkerContext = WorkerContext & {
15
+ dispatcherCheck?: never;
16
+ laneWake?: never;
17
+ bindSend?: never;
18
+ };
19
+ export declare const spawnCompiledWorkerContext: ({ list, names, workerOptions, hostDebug, abortSignalCapacity, usesAbortSignal, }: {
20
+ list: string[];
21
+ names: string[];
22
+ workerOptions: WorkerSettings;
23
+ hostDebug?: (message: string) => void;
24
+ abortSignalCapacity?: number;
25
+ usesAbortSignal?: boolean;
26
+ }) => CompiledWorkerContext;
27
+ export {};
@@ -0,0 +1,562 @@
1
+ import { getNodeBuiltinModule, getNodeProcess } from "../common/node-compat.js";
2
+ import { ProcessSharedBuffer, getDefaultProcessSharedBufferPrimitives, isProcessSharedBufferValue, } from "../connections/process-shared-buffer.js";
3
+ import { withResolvers } from "../common/with-resolvers.js";
4
+ import { AbortSignalPoolExhausted, OneShotDeferred, signalAbortFactory, } from "../shared/abortSignal.js";
5
+ import { COMPILED_WORKER_JSON_PROTOCOL, inspectCompiledWorkerArtifact, } from "./compiled-artifact.js";
6
+ import { buildCompiledWorkerArtifact } from "./compiled-builder.js";
7
+ /** Matches the `abortSignalCapacity` default documented on pool options. */
8
+ const DEFAULT_ABORT_SLOTS = 258;
9
+ /** Both directions of the JSON protocols share this frame budget. */
10
+ const MAX_PAYLOAD_BYTES = 1024 * 1024;
11
+ /** Sentinel written to the slot field when a task is not abort-aware. */
12
+ const NO_ABORT_SLOT = -1;
13
+ /** Requests carry i32 task, i32 abort slot, then either a length or an f64. */
14
+ const JSON_HEADER_BYTES = 12;
15
+ const NUMBER_FRAME_BYTES = 16;
16
+ const NUMBER_ARGUMENT_OFFSET = 8;
17
+ const EMPTY_PAYLOAD = new Uint8Array(0);
18
+ const deno = globalThis.Deno;
19
+ const spawnNodeProcess = (artifact, environment) => {
20
+ const childProcess = getNodeBuiltinModule("node:child_process");
21
+ if (childProcess === undefined)
22
+ return undefined;
23
+ const child = childProcess.spawn(artifact, [], {
24
+ stdio: ["pipe", "pipe", "inherit"],
25
+ env: environment === undefined
26
+ ? undefined
27
+ : { ...(getNodeProcess()?.env ?? {}), ...environment },
28
+ });
29
+ let dataListener = () => { };
30
+ let errorListener = () => { };
31
+ child.stdout.on("data", (data) => dataListener(data));
32
+ child.on("error", (error) => errorListener(error));
33
+ const exited = new Promise((resolve) => {
34
+ child.on("exit", (code) => resolve(code ?? -1));
35
+ });
36
+ return {
37
+ write: async (data) => {
38
+ child.stdin.write(data);
39
+ },
40
+ closeInput: () => new Promise((resolve) => {
41
+ child.stdin.end(resolve);
42
+ }),
43
+ kill: () => void child.kill("SIGTERM"),
44
+ exited,
45
+ onData: (listener) => void (dataListener = listener),
46
+ onError: (listener) => void (errorListener = listener),
47
+ };
48
+ };
49
+ const spawnDenoProcess = (artifact, environment) => {
50
+ if (deno?.Command === undefined)
51
+ return undefined;
52
+ const child = new deno.Command(artifact, {
53
+ stdin: "piped",
54
+ stdout: "piped",
55
+ stderr: "inherit",
56
+ ...(environment === undefined ? {} : { env: environment }),
57
+ }).spawn();
58
+ const writer = child.stdin.getWriter();
59
+ const reader = child.stdout.getReader();
60
+ let dataListener = () => { };
61
+ let errorListener = () => { };
62
+ void (async () => {
63
+ try {
64
+ while (true) {
65
+ const next = await reader.read();
66
+ if (next.done)
67
+ break;
68
+ dataListener(next.value);
69
+ }
70
+ }
71
+ catch (error) {
72
+ errorListener(error);
73
+ }
74
+ })();
75
+ return {
76
+ write: async (data) => {
77
+ await writer.write(data);
78
+ },
79
+ closeInput: async () => {
80
+ await writer.close();
81
+ },
82
+ kill: () => child.kill("SIGTERM"),
83
+ exited: child.status.then((status) => status.code),
84
+ onData: (listener) => void (dataListener = listener),
85
+ onError: (listener) => void (errorListener = listener),
86
+ };
87
+ };
88
+ const spawnNativeProcess = (artifact, environment) => {
89
+ const process = spawnNodeProcess(artifact, environment) ??
90
+ spawnDenoProcess(artifact, environment);
91
+ if (process === undefined) {
92
+ throw new Error("Compiled workers require child-process support");
93
+ }
94
+ return process;
95
+ };
96
+ const compiledAbortName = () => {
97
+ const pid = getNodeProcess()?.pid ?? deno?.pid ?? 0;
98
+ const nonce = Math.floor(Math.random() * 0x1000000).toString(36);
99
+ return `knit_abort_${Math.abs(pid).toString(36)}_${nonce}`.slice(0, 30);
100
+ };
101
+ /**
102
+ * Host half of the abort bitmap. Slot bookkeeping is the same allocator every
103
+ * other lane uses; only the backing store differs, because a compiled child is
104
+ * a separate process and reads the words through named POSIX shared memory.
105
+ */
106
+ export const createCompiledAbortChannel = (requestedMax = DEFAULT_ABORT_SLOTS) => {
107
+ const max = Number.isFinite(requestedMax) && requestedMax > 0
108
+ ? Math.floor(requestedMax)
109
+ : DEFAULT_ABORT_SLOTS;
110
+ const byteLength = Math.max(1, Math.ceil(max / 32)) *
111
+ Uint32Array.BYTES_PER_ELEMENT;
112
+ const primitives = getDefaultProcessSharedBufferPrimitives();
113
+ const name = compiledAbortName();
114
+ const shared = ProcessSharedBuffer.create({ mode: "create", name, size: byteLength }, primitives);
115
+ let closed = false;
116
+ return {
117
+ ...signalAbortFactory({
118
+ sab: shared.subbuffer(0, byteLength).getRegion(primitives),
119
+ maxSignals: max,
120
+ }),
121
+ environment: {
122
+ KNITTING_COMPILED_ABORT_SHM: name.startsWith("/") ? name : `/${name}`,
123
+ KNITTING_COMPILED_ABORT_BYTES: String(byteLength),
124
+ },
125
+ close: () => {
126
+ if (closed)
127
+ return;
128
+ closed = true;
129
+ shared.descriptor.mapping?.close?.();
130
+ primitives.unlinkSharedMemory?.(name);
131
+ },
132
+ };
133
+ };
134
+ // One resolved WorkerSettings object is shared by every lane in a pool. This
135
+ // prevents `build: "always"` from recompiling once per lane.
136
+ const forcedBuildPools = new WeakSet();
137
+ const responseError = (status, taskName) => {
138
+ if (status === 1) {
139
+ return new RangeError("Unknown compiled task: " + taskName);
140
+ }
141
+ if (status === 2) {
142
+ return new TypeError("Compiled task " + taskName + " returned an unsupported value");
143
+ }
144
+ if (status === 3)
145
+ return new Error("Compiled task " + taskName + " threw");
146
+ if (status === 4) {
147
+ return new RangeError("Compiled task " + taskName + " result is too large");
148
+ }
149
+ return new Error("Compiled worker returned unknown status " + status);
150
+ };
151
+ /** Reserved wire key: a plain object carrying it would be ambiguous. */
152
+ const COMPILED_VALUE_TAG = "$knitting";
153
+ const ARRAY_BUFFER_TAG = "array-buffer";
154
+ const DATA_VIEW_TAG = "data-view";
155
+ /** Wire tag to view constructor, read by both directions of the codec. */
156
+ const TYPED_ARRAYS = {
157
+ u8: Uint8Array,
158
+ u8c: Uint8ClampedArray,
159
+ i8: Int8Array,
160
+ u16: Uint16Array,
161
+ i16: Int16Array,
162
+ u32: Uint32Array,
163
+ i32: Int32Array,
164
+ f32: Float32Array,
165
+ f64: Float64Array,
166
+ };
167
+ const typedArrayTags = new Map(Object.entries(TYPED_ARRAYS).map(([tag, view]) => [view, tag]));
168
+ const binaryTagOf = (value) => value instanceof ArrayBuffer
169
+ ? ARRAY_BUFFER_TAG
170
+ : value instanceof DataView
171
+ ? DATA_VIEW_TAG
172
+ : typedArrayTags.get(value.constructor);
173
+ const rawBytes = (value) => value instanceof ArrayBuffer
174
+ ? new Uint8Array(value)
175
+ : new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
176
+ // String.fromCharCode is applied in chunks so a megabyte payload cannot
177
+ // overflow the argument list.
178
+ const BASE64_CHUNK = 0x8000;
179
+ const toBase64 = (bytes) => {
180
+ let latin1 = "";
181
+ for (let at = 0; at < bytes.length; at += BASE64_CHUNK) {
182
+ latin1 += String.fromCharCode(...bytes.subarray(at, at + BASE64_CHUNK));
183
+ }
184
+ return btoa(latin1);
185
+ };
186
+ const fromBase64 = (text) => {
187
+ if (typeof text !== "string") {
188
+ throw new TypeError("Compiled binary value is not base64 text");
189
+ }
190
+ return Uint8Array.from(atob(text), (character) => character.charCodeAt(0));
191
+ };
192
+ /**
193
+ * Validates a value and rewrites it for the wire: buffers and views travel as
194
+ * tagged base64, and a named ProcessSharedBuffer travels as a mapping request
195
+ * the child fulfils by mapping the same shared memory itself.
196
+ */
197
+ const encodeCompiledValue = (value, ancestors = new Set()) => {
198
+ if (value === null || typeof value === "string" || typeof value === "boolean")
199
+ return value;
200
+ if (typeof value === "number") {
201
+ if (!Number.isFinite(value) || Object.is(value, -0)) {
202
+ throw new TypeError("Compiled workers require lossless JSON numbers");
203
+ }
204
+ return value;
205
+ }
206
+ if (typeof value !== "object") {
207
+ throw new TypeError("Compiled workers only accept JSON-compatible values");
208
+ }
209
+ if (ancestors.has(value)) {
210
+ throw new TypeError("Compiled workers do not accept cyclic values");
211
+ }
212
+ if (isProcessSharedBufferValue(value)) {
213
+ const name = value.descriptor.name;
214
+ if (name === undefined) {
215
+ throw new TypeError("Compiled workers require a named ProcessSharedBuffer");
216
+ }
217
+ if (value.size > MAX_PAYLOAD_BYTES) {
218
+ throw new RangeError("Compiled ProcessSharedBuffer values are limited to 1 MiB");
219
+ }
220
+ return {
221
+ [COMPILED_VALUE_TAG]: "process-shared",
222
+ name: name.startsWith("/") ? name : `/${name}`,
223
+ size: value.size,
224
+ offset: value.byteOffset,
225
+ length: value.byteLength,
226
+ };
227
+ }
228
+ const tag = binaryTagOf(value);
229
+ if (tag !== undefined) {
230
+ return {
231
+ [COMPILED_VALUE_TAG]: tag,
232
+ data: toBase64(rawBytes(value)),
233
+ };
234
+ }
235
+ if (ArrayBuffer.isView(value)) {
236
+ throw new TypeError("Compiled workers do not support BigInt typed arrays");
237
+ }
238
+ ancestors.add(value);
239
+ let encoded;
240
+ if (Array.isArray(value)) {
241
+ const entries = [];
242
+ for (let index = 0; index < value.length; index++) {
243
+ if (!(index in value)) {
244
+ throw new TypeError("Compiled workers do not accept sparse arrays");
245
+ }
246
+ entries.push(encodeCompiledValue(value[index], ancestors));
247
+ }
248
+ encoded = entries;
249
+ }
250
+ else {
251
+ const prototype = Object.getPrototypeOf(value);
252
+ if (prototype !== Object.prototype && prototype !== null) {
253
+ throw new TypeError("Compiled workers only accept plain objects");
254
+ }
255
+ const record = {};
256
+ for (const [key, entry] of Object.entries(value)) {
257
+ if (key === COMPILED_VALUE_TAG) {
258
+ throw new TypeError("Compiled workers reserve the " + COMPILED_VALUE_TAG + " key");
259
+ }
260
+ record[key] = encodeCompiledValue(entry, ancestors);
261
+ }
262
+ encoded = record;
263
+ }
264
+ ancestors.delete(value);
265
+ return encoded;
266
+ };
267
+ const decodeCompiledValue = (value) => {
268
+ if (Array.isArray(value))
269
+ return value.map(decodeCompiledValue);
270
+ if (value === null || typeof value !== "object")
271
+ return value;
272
+ const record = value;
273
+ const tag = record[COMPILED_VALUE_TAG];
274
+ if (typeof tag === "string") {
275
+ const bytes = fromBase64(record.data);
276
+ if (tag === ARRAY_BUFFER_TAG)
277
+ return bytes.buffer;
278
+ if (tag === DATA_VIEW_TAG)
279
+ return new DataView(bytes.buffer);
280
+ const view = TYPED_ARRAYS[tag];
281
+ if (view === undefined) {
282
+ throw new TypeError("Compiled worker returned an unknown binary value");
283
+ }
284
+ if (bytes.byteLength % view.BYTES_PER_ELEMENT !== 0) {
285
+ throw new TypeError("Compiled worker returned misaligned binary data");
286
+ }
287
+ return new view(bytes.buffer);
288
+ }
289
+ const decoded = {};
290
+ for (const [key, entry] of Object.entries(record)) {
291
+ decoded[key] = decodeCompiledValue(entry);
292
+ }
293
+ return decoded;
294
+ };
295
+ const asciiJson = (value) => {
296
+ const wireValue = encodeCompiledValue(value);
297
+ const serialized = JSON.stringify(wireValue);
298
+ if (serialized !== undefined) {
299
+ for (let index = 0; index < serialized.length; index++) {
300
+ const unit = serialized.charCodeAt(index);
301
+ if (unit >= 0xd800 && unit <= 0xdfff) {
302
+ throw new TypeError("Compiled workers do not yet support non-BMP Unicode");
303
+ }
304
+ }
305
+ }
306
+ return serialized?.replace(/[^\x00-\x7f]/g, (character) => "\\u" + character.charCodeAt(0).toString(16).padStart(4, "0"));
307
+ };
308
+ export const spawnCompiledWorkerContext = ({ list, names, workerOptions, hostDebug, abortSignalCapacity, usesAbortSignal, }) => {
309
+ if (list.length !== 1) {
310
+ throw new Error("A compiled worker pool must use one task module");
311
+ }
312
+ let inspection = inspectCompiledWorkerArtifact({
313
+ source: list[0],
314
+ options: workerOptions.compiled,
315
+ requiredTasks: names,
316
+ });
317
+ const forceBuild = workerOptions.compiled?.build === "always" &&
318
+ !forcedBuildPools.has(workerOptions);
319
+ if (forceBuild)
320
+ forcedBuildPools.add(workerOptions);
321
+ if (forceBuild ||
322
+ (!inspection.compiled && workerOptions.compiled?.build !== false)) {
323
+ try {
324
+ buildCompiledWorkerArtifact({
325
+ inspection,
326
+ source: list[0],
327
+ tasks: names,
328
+ options: workerOptions.compiled,
329
+ });
330
+ }
331
+ catch (error) {
332
+ const message = String(error?.message ?? error);
333
+ throw new Error("Compiled worker could not be built: " + message +
334
+ ". Initial validation: " + (inspection.reason ?? "unknown reason"));
335
+ }
336
+ inspection = inspectCompiledWorkerArtifact({
337
+ source: list[0],
338
+ options: workerOptions.compiled,
339
+ requiredTasks: names,
340
+ });
341
+ }
342
+ if (!inspection.compiled || inspection.taskEntries === undefined) {
343
+ throw new Error("Compiled worker is unavailable: " +
344
+ (inspection.reason ?? "unknown reason") + ". Expected " +
345
+ inspection.artifact + " with " + inspection.manifest);
346
+ }
347
+ const artifactIndex = new Map(inspection.taskEntries.map((task) => [task.exportName, task.index]));
348
+ const taskIndices = names.map((name) => artifactIndex.get(name));
349
+ hostDebug?.("compiled artifact=" + inspection.artifact +
350
+ " compiler=" + inspection.compiler);
351
+ const abortChannel = usesAbortSignal === true
352
+ ? createCompiledAbortChannel(abortSignalCapacity)
353
+ : undefined;
354
+ let native;
355
+ try {
356
+ native = spawnNativeProcess(inspection.artifact, abortChannel?.environment);
357
+ }
358
+ catch (error) {
359
+ abortChannel?.close();
360
+ throw error;
361
+ }
362
+ const jsonProtocol = inspection.protocol === COMPILED_WORKER_JSON_PROTOCOL;
363
+ const requestHeaderBytes = jsonProtocol
364
+ ? JSON_HEADER_BYTES
365
+ : NUMBER_FRAME_BYTES;
366
+ const pending = [];
367
+ let responseBytes = new Uint8Array(0);
368
+ let closing = false;
369
+ let closedReason;
370
+ let writeTail = Promise.resolve();
371
+ const rejectPending = (reason) => {
372
+ while (pending.length > 0)
373
+ pending.shift().reject(reason);
374
+ };
375
+ const fail = (reason) => {
376
+ if (closedReason !== undefined)
377
+ return;
378
+ closedReason = String(reason?.message ?? reason);
379
+ rejectPending(new Error(closedReason));
380
+ };
381
+ native.onError(fail);
382
+ native.onData((chunk) => {
383
+ const merged = new Uint8Array(responseBytes.byteLength + chunk.byteLength);
384
+ merged.set(responseBytes);
385
+ merged.set(chunk, responseBytes.byteLength);
386
+ let offset = 0;
387
+ while (true) {
388
+ const headerLength = jsonProtocol ? 8 : 16;
389
+ if (offset + headerLength > merged.byteLength)
390
+ break;
391
+ const header = new DataView(merged.buffer, merged.byteOffset + offset, headerLength);
392
+ const payloadLength = jsonProtocol ? header.getUint32(4, true) : 8;
393
+ if (payloadLength > 64 * 1024 * 1024) {
394
+ fail("Compiled worker returned an oversized response");
395
+ native.kill();
396
+ return;
397
+ }
398
+ const frameLength = jsonProtocol ? headerLength + payloadLength : 16;
399
+ if (offset + frameLength > merged.byteLength)
400
+ break;
401
+ const pendingCall = pending.shift();
402
+ if (pendingCall === undefined) {
403
+ fail("Compiled worker returned an unexpected response");
404
+ native.kill();
405
+ return;
406
+ }
407
+ const status = header.getInt32(0, true);
408
+ if (status !== 0) {
409
+ pendingCall.reject(responseError(status, pendingCall.taskName));
410
+ }
411
+ else if (jsonProtocol) {
412
+ try {
413
+ const bytes = merged.subarray(offset + 8, offset + frameLength);
414
+ const decoded = JSON.parse(new TextDecoder().decode(bytes));
415
+ pendingCall.resolve(decodeCompiledValue(decoded));
416
+ }
417
+ catch (error) {
418
+ pendingCall.reject(new Error("Compiled worker returned invalid JSON", { cause: error }));
419
+ }
420
+ }
421
+ else {
422
+ pendingCall.resolve(header.getFloat64(8, true));
423
+ }
424
+ offset += frameLength;
425
+ }
426
+ responseBytes = merged.slice(offset);
427
+ });
428
+ void native.exited.then((code) => {
429
+ if (!closing || code !== 0) {
430
+ fail("Compiled worker exited with code " + code);
431
+ }
432
+ });
433
+ const queueWrite = (frame) => {
434
+ writeTail = writeTail.then(() => native.write(frame));
435
+ void writeTail.catch(fail);
436
+ return writeTail;
437
+ };
438
+ /**
439
+ * Everything that can reject a call lives here, so it all runs before an
440
+ * abort slot is claimed and a claimed slot can never leak.
441
+ */
442
+ const encodePayload = (taskName, value) => {
443
+ if (!jsonProtocol) {
444
+ if (typeof value !== "number") {
445
+ throw new TypeError("Compiled worker task " + taskName + " only accepts a number");
446
+ }
447
+ return EMPTY_PAYLOAD;
448
+ }
449
+ // Porffor's JSON parser is reliable with escaped UTF-16 code units;
450
+ // keeping request bytes ASCII also avoids depending on its internal
451
+ // string representation for supplementary Unicode code points.
452
+ const serialized = asciiJson(value);
453
+ if (serialized === undefined) {
454
+ throw new TypeError("Compiled worker task " + taskName +
455
+ " only accepts JSON-compatible values");
456
+ }
457
+ const payload = new TextEncoder().encode(serialized);
458
+ if (payload.byteLength > MAX_PAYLOAD_BYTES) {
459
+ throw new RangeError("Compiled worker input is too large");
460
+ }
461
+ return payload;
462
+ };
463
+ const call = ({ fnNumber, abortSignal, }) => {
464
+ const taskName = names[fnNumber];
465
+ const targetIndex = taskIndices[fnNumber];
466
+ const usesSignal = abortSignal !== undefined;
467
+ return (rawArgs) => {
468
+ const deferred = withResolvers();
469
+ const send = (value) => {
470
+ if (closedReason !== undefined || closing) {
471
+ deferred.reject(new Error(closedReason ?? "Compiled worker is shut down"));
472
+ return;
473
+ }
474
+ if (usesSignal && abortChannel === undefined) {
475
+ deferred.reject(new Error("Compiled worker abort channel is unavailable"));
476
+ return;
477
+ }
478
+ let payload;
479
+ try {
480
+ payload = encodePayload(taskName, value);
481
+ }
482
+ catch (error) {
483
+ deferred.reject(error);
484
+ return;
485
+ }
486
+ const signal = usesSignal ? abortChannel.getSignal() : NO_ABORT_SLOT;
487
+ if (usesSignal && signal === abortChannel.closeNow) {
488
+ deferred.reject(AbortSignalPoolExhausted);
489
+ return;
490
+ }
491
+ if (usesSignal) {
492
+ new OneShotDeferred(deferred, () => abortChannel.resetSignal(signal), () => abortChannel.setSignal(signal));
493
+ }
494
+ const frame = new Uint8Array(requestHeaderBytes + payload.byteLength);
495
+ const header = new DataView(frame.buffer);
496
+ header.setInt32(0, targetIndex, true);
497
+ header.setInt32(4, signal, true);
498
+ if (jsonProtocol) {
499
+ header.setUint32(8, payload.byteLength, true);
500
+ frame.set(payload, requestHeaderBytes);
501
+ }
502
+ else {
503
+ header.setFloat64(NUMBER_ARGUMENT_OFFSET, value, true);
504
+ }
505
+ pending.push({
506
+ taskName,
507
+ resolve: deferred.resolve,
508
+ reject: deferred.reject,
509
+ });
510
+ void queueWrite(frame);
511
+ };
512
+ if (rawArgs instanceof Promise) {
513
+ void rawArgs.then(send, deferred.reject);
514
+ }
515
+ else {
516
+ send(rawArgs);
517
+ }
518
+ return deferred.promise;
519
+ };
520
+ };
521
+ let closePromise;
522
+ return {
523
+ txIdle: () => pending.length === 0,
524
+ call,
525
+ kills: () => {
526
+ if (closePromise !== undefined)
527
+ return closePromise;
528
+ closing = true;
529
+ const hadPendingCalls = pending.length > 0;
530
+ rejectPending(new Error("Compiled worker is shutting down"));
531
+ closePromise = (async () => {
532
+ if (hadPendingCalls) {
533
+ native.kill();
534
+ await native.exited;
535
+ closedReason ??= "Compiled worker is shut down";
536
+ abortChannel?.close();
537
+ return;
538
+ }
539
+ const shutdown = new Uint8Array(requestHeaderBytes);
540
+ new DataView(shutdown.buffer).setInt32(0, -1, true);
541
+ try {
542
+ await queueWrite(shutdown);
543
+ await native.closeInput();
544
+ let timeoutId;
545
+ const exited = await Promise.race([
546
+ native.exited.then(() => true),
547
+ new Promise((resolve) => timeoutId = setTimeout(() => resolve(false), 1000)),
548
+ ]);
549
+ if (timeoutId !== undefined)
550
+ clearTimeout(timeoutId);
551
+ if (!exited)
552
+ native.kill();
553
+ }
554
+ finally {
555
+ closedReason ??= "Compiled worker is shut down";
556
+ abortChannel?.close();
557
+ }
558
+ })();
559
+ return closePromise;
560
+ },
561
+ };
562
+ };