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
@@ -1,27 +1,37 @@
1
1
  import { withResolvers } from "../common/with-resolvers.js";
2
2
  import RingQueue from "../ipc/tools/ring-queue.js";
3
3
  import { createRuntimeMessageChannel } from "../common/worker-runtime.js";
4
+ // const objects, not `enum`s: Andromeda's Nova engine can't parse `enum`. Same
5
+ // values and emit on Node/Deno/Bun.
6
+ const SlotStateMacro = {
7
+ Free: -1,
8
+ Pending: 0,
9
+ };
10
+ const TimeoutKind = {
11
+ Reject: 0,
12
+ Resolve: 1,
13
+ };
4
14
  const normalizeTimeout = (timeout) => {
5
15
  if (timeout == null)
6
16
  return undefined;
7
17
  if (typeof timeout === "number") {
8
18
  return timeout >= 0
9
- ? { ms: timeout, kind: 0 /* TimeoutKind.Reject */, value: new Error("Task timeout") }
19
+ ? { ms: timeout, kind: TimeoutKind.Reject, value: new Error("Task timeout") }
10
20
  : undefined;
11
21
  }
12
22
  const ms = timeout.time;
13
23
  if (!(ms >= 0))
14
24
  return undefined;
15
25
  if ("default" in timeout) {
16
- return { ms, kind: 1 /* TimeoutKind.Resolve */, value: timeout.default };
26
+ return { ms, kind: TimeoutKind.Resolve, value: timeout.default };
17
27
  }
18
28
  if (timeout.maybe === true) {
19
- return { ms, kind: 1 /* TimeoutKind.Resolve */, value: undefined };
29
+ return { ms, kind: TimeoutKind.Resolve, value: undefined };
20
30
  }
21
31
  if ("error" in timeout) {
22
- return { ms, kind: 0 /* TimeoutKind.Reject */, value: timeout.error };
32
+ return { ms, kind: TimeoutKind.Reject, value: timeout.error };
23
33
  }
24
- return { ms, kind: 0 /* TimeoutKind.Reject */, value: new Error("Task timeout") };
34
+ return { ms, kind: TimeoutKind.Reject, value: new Error("Task timeout") };
25
35
  };
26
36
  const raceTimeout = (promise, spec) => new Promise((resolve, reject) => {
27
37
  let done = false;
@@ -29,7 +39,7 @@ const raceTimeout = (promise, spec) => new Promise((resolve, reject) => {
29
39
  if (done)
30
40
  return;
31
41
  done = true;
32
- if (spec.kind === 1 /* TimeoutKind.Resolve */) {
42
+ if (spec.kind === TimeoutKind.Resolve) {
33
43
  resolve(spec.value);
34
44
  }
35
45
  else {
@@ -52,8 +62,10 @@ const raceTimeout = (promise, spec) => new Promise((resolve, reject) => {
52
62
  });
53
63
  const INLINE_ABORT_TOOLKIT = (() => {
54
64
  const hasAborted = () => false;
65
+ const now = () => performance.now();
55
66
  return {
56
67
  hasAborted,
68
+ now,
57
69
  };
58
70
  })();
59
71
  const composeInlineCallable = (fn, timeout, useAbortToolkit = false) => {
@@ -86,7 +98,7 @@ export const createInlineExecutor = ({ tasks, genTaskID, batchSize, }) => {
86
98
  });
87
99
  const initCap = 16;
88
100
  let fnByIndex = new Int32Array(initCap);
89
- let stateByIndex = new Int8Array(initCap).fill(-1 /* SlotStateMacro.Free */);
101
+ let stateByIndex = new Int8Array(initCap).fill(SlotStateMacro.Free);
90
102
  let argsByIndex = new Array(initCap);
91
103
  let taskIdByIndex = new Array(initCap).fill(-1);
92
104
  let deferredByIndex = new Array(initCap);
@@ -126,13 +138,13 @@ export const createInlineExecutor = ({ tasks, genTaskID, batchSize, }) => {
126
138
  send();
127
139
  };
128
140
  const enqueueIfCurrent = (index, taskID) => {
129
- if (stateByIndex[index] !== 0 /* SlotStateMacro.Pending */ ||
141
+ if (stateByIndex[index] !== SlotStateMacro.Pending ||
130
142
  taskIdByIndex[index] !== taskID)
131
143
  return;
132
144
  enqueue(index);
133
145
  };
134
146
  const settleIfCurrent = (index, taskID, isError, value) => {
135
- if (stateByIndex[index] !== 0 /* SlotStateMacro.Pending */ ||
147
+ if (stateByIndex[index] !== SlotStateMacro.Pending ||
136
148
  taskIdByIndex[index] !== taskID)
137
149
  return;
138
150
  const deferred = deferredByIndex[index];
@@ -153,7 +165,7 @@ export const createInlineExecutor = ({ tasks, genTaskID, batchSize, }) => {
153
165
  nextFnByIndex.set(fnByIndex);
154
166
  fnByIndex = nextFnByIndex;
155
167
  const nextStateByIndex = new Int8Array(newCap);
156
- nextStateByIndex.fill(-1 /* SlotStateMacro.Free */);
168
+ nextStateByIndex.fill(SlotStateMacro.Free);
157
169
  nextStateByIndex.set(stateByIndex);
158
170
  stateByIndex = nextStateByIndex;
159
171
  argsByIndex.length = newCap;
@@ -172,7 +184,7 @@ export const createInlineExecutor = ({ tasks, genTaskID, batchSize, }) => {
172
184
  if (maybeIndex === undefined)
173
185
  break;
174
186
  const index = maybeIndex | 0;
175
- if (stateByIndex[index] !== 0 /* SlotStateMacro.Pending */)
187
+ if (stateByIndex[index] !== SlotStateMacro.Pending)
176
188
  continue;
177
189
  const taskID = taskIdByIndex[index];
178
190
  try {
@@ -213,7 +225,7 @@ export const createInlineExecutor = ({ tasks, genTaskID, batchSize, }) => {
213
225
  }
214
226
  function cleanup(index) {
215
227
  working--;
216
- stateByIndex[index] = -1 /* SlotStateMacro.Free */;
228
+ stateByIndex[index] = SlotStateMacro.Free;
217
229
  fnByIndex[index] = 0;
218
230
  taskIdByIndex[index] = -1;
219
231
  argsByIndex[index] = undefined;
@@ -230,7 +242,7 @@ export const createInlineExecutor = ({ tasks, genTaskID, batchSize, }) => {
230
242
  argsByIndex[index] = args;
231
243
  fnByIndex[index] = fnNumber | 0;
232
244
  deferredByIndex[index] = deferred;
233
- stateByIndex[index] = 0 /* SlotStateMacro.Pending */;
245
+ stateByIndex[index] = SlotStateMacro.Pending;
234
246
  working++;
235
247
  if (args instanceof Promise) {
236
248
  args.then((value) => {
@@ -250,7 +262,7 @@ export const createInlineExecutor = ({ tasks, genTaskID, batchSize, }) => {
250
262
  return {
251
263
  kills: async () => {
252
264
  for (let index = 0; index < stateByIndex.length; index++) {
253
- if (stateByIndex[index] !== 0 /* SlotStateMacro.Pending */)
265
+ if (stateByIndex[index] !== SlotStateMacro.Pending)
254
266
  continue;
255
267
  try {
256
268
  deferredByIndex[index]?.reject("Thread closed");
@@ -271,7 +283,7 @@ export const createInlineExecutor = ({ tasks, genTaskID, batchSize, }) => {
271
283
  taskIdByIndex.fill(-1);
272
284
  deferredByIndex.fill(undefined);
273
285
  fnByIndex.fill(0);
274
- stateByIndex.fill(-1 /* SlotStateMacro.Free */);
286
+ stateByIndex.fill(SlotStateMacro.Free);
275
287
  working = 0;
276
288
  isInMacro = false;
277
289
  isInMicro = false;
@@ -2,6 +2,7 @@ import { type ProcessSharedMemoryBacking } from "./process-worker.js";
2
2
  import { type Sab } from "../ipc/transport/shared-memory.js";
3
3
  import { ChannelHandler, type DispatcherCheck } from "./dispatcher.js";
4
4
  import { lock2 } from "../memory/lock.js";
5
+ import "../memory/payloadCodec.js";
5
6
  import type { DebugOptions, DispatcherSettings, WorkerContext, WorkerData, WorkerSettings } from "../types.js";
6
7
  import "../worker/loop.js";
7
8
  import { type PayloadBufferOptions } from "../memory/payload-config.js";
@@ -29,11 +30,15 @@ export declare const spawnWorkerContext: ({ list, ids, names, sab, thread, debug
29
30
  * macro channel that runs all lane checks.
30
31
  */
31
32
  sharedChannelHandler?: ChannelHandler;
32
- }) => WorkerContext & {
33
+ }) => (WorkerContext & {
34
+ dispatcherCheck?: never;
35
+ laneWake?: never;
36
+ bindSend?: never;
37
+ }) | (WorkerContext & {
33
38
  lock: ReturnType<typeof lock2>;
34
39
  processSharedMemoryBackings?: readonly ProcessSharedMemoryBacking[];
35
40
  dispatcherCheck?: DispatcherCheck;
36
41
  laneWake?: () => void;
37
42
  bindSend?: (fn: () => void) => void;
38
- };
43
+ });
39
44
  export type CreateContext = WorkerContext;
@@ -3,6 +3,9 @@ import { cleanupProcessWorkerMemoryQuietly, createProcessSharedMemoryAllocator,
3
3
  import { createSharedMemoryTransport, TRANSPORT_SIGNAL_BYTES, } from "../ipc/transport/shared-memory.js";
4
4
  import { ChannelHandler, hostDispatcherLoop, } from "./dispatcher.js";
5
5
  import { HEADER_SLOT_STRIDE_U32, lock2, LOCK_SECTOR_BYTE_LENGTH, LockBound, } from "../memory/lock.js";
6
+ // Side-effect import: registers the payload codec (cycle break for Andromeda;
7
+ // see lock.ts). Must run before any lock2() call.
8
+ import "../memory/payloadCodec.js";
6
9
  import "../worker/loop.js";
7
10
  import { createSharedArrayBuffer, createWasmSharedArrayBuffer, } from "../common/runtime.js";
8
11
  import { HAS_NODE_WORKER_THREADS, RUNTIME_WORKER, } from "../common/worker-runtime.js";
@@ -10,7 +13,8 @@ import { probeLockBufferTextCompat } from "../common/shared-buffer-text.js";
10
13
  import { signalAbortFactory } from "../shared/abortSignal.js";
11
14
  import { createLockControlCarpet } from "../memory/byte-carpet.js";
12
15
  import { resolvePayloadBufferOptions, } from "../memory/payload-config.js";
13
- import { createBufferReferenceReturnReleaseMessage } from "../connections/buffer-reference.js";
16
+ import { createBufferReferenceReturnReleaseMessage, detachArrayBufferBestEffort, } from "../connections/buffer-reference.js";
17
+ import { spawnCompiledWorkerContext } from "./compiled-worker.js";
14
18
  const WORKER_FATAL_MESSAGE_KEY = "__knittingWorkerFatal";
15
19
  const isWorkerFatalMessage = (value) => !!value &&
16
20
  typeof value === "object" &&
@@ -35,6 +39,16 @@ const withFixedPayloadConfig = (config) => ({
35
39
  payloadInitialBytes: config.payloadMaxByteLength,
36
40
  });
37
41
  export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostDebug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, bufferReferenceReturn, abortSignalCapacity, usesAbortSignal, sharedChannelHandler, }) => {
42
+ if (workerOptions?.runtime === "compiled") {
43
+ return spawnCompiledWorkerContext({
44
+ list,
45
+ names,
46
+ workerOptions,
47
+ hostDebug,
48
+ abortSignalCapacity,
49
+ usesAbortSignal,
50
+ });
51
+ }
38
52
  const tsFileUrl = new URL(import.meta.url);
39
53
  const poliWorker = RUNTIME_WORKER;
40
54
  const resolvedWorkerOptions = serializeWorkerBootstrapData(withDefaultWorkerTimers(workerOptions));
@@ -144,6 +158,7 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
144
158
  payloadSector: lockBuffers.payloadSector,
145
159
  payloadConfig: resolvedPayloadConfig,
146
160
  textCompat: lockBuffers.textCompat,
161
+ processBoundary: useProcessWorkerRuntime,
147
162
  });
148
163
  const returnLock = lock2({
149
164
  headers: returnLockBuffers.headers,
@@ -153,6 +168,7 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
153
168
  payloadSector: returnLockBuffers.payloadSector,
154
169
  payloadConfig: resolvedPayloadConfig,
155
170
  textCompat: returnLockBuffers.textCompat,
171
+ processBoundary: useProcessWorkerRuntime,
156
172
  });
157
173
  const abortSignalSAB = usesAbortSignal === true
158
174
  ? controlLayout.abortSignals
@@ -178,13 +194,61 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
178
194
  processRuntime: processWorkerRuntime,
179
195
  signal: signalBox.opView,
180
196
  });
197
+ const outstandingBorrowedReturns = bufferReferenceReturn === "borrow" && typeof WeakRef === "function"
198
+ ? new Map()
199
+ : undefined;
200
+ const releaseBorrowedReturnToken = (token) => {
201
+ outstandingBorrowedReturns?.delete(token);
202
+ worker?.postMessage?.(createBufferReferenceReturnReleaseMessage(token));
203
+ };
204
+ const revokeOutstandingBorrowedReturns = (copyBytes) => {
205
+ if (outstandingBorrowedReturns === undefined)
206
+ return;
207
+ for (const [token, record] of outstandingBorrowedReturns) {
208
+ try {
209
+ const ref = record.ref.deref();
210
+ if (ref !== undefined) {
211
+ ref.revokeBorrow(copyBytes);
212
+ continue;
213
+ }
214
+ const aliasBuffer = record.aliasBuffer?.deref();
215
+ if (aliasBuffer !== undefined &&
216
+ detachArrayBufferBestEffort(record.runtime, aliasBuffer) &&
217
+ copyBytes) {
218
+ releaseBorrowedReturnToken(token);
219
+ }
220
+ }
221
+ catch {
222
+ // best effort
223
+ }
224
+ }
225
+ outstandingBorrowedReturns.clear();
226
+ };
181
227
  const queue = createHostTxQueue({
182
228
  lock,
183
229
  returnLock,
184
230
  abortSignals,
185
231
  releaseBufferReferenceReturn: bufferReferenceReturn === "borrow"
186
- ? (token) => {
187
- worker?.postMessage?.(createBufferReferenceReturnReleaseMessage(token));
232
+ ? {
233
+ release: releaseBorrowedReturnToken,
234
+ track: (ref, token, aliasBuffer) => {
235
+ if (outstandingBorrowedReturns === undefined)
236
+ return;
237
+ const record = outstandingBorrowedReturns.get(token);
238
+ if (record !== undefined) {
239
+ if (aliasBuffer !== undefined) {
240
+ record.aliasBuffer = new WeakRef(aliasBuffer);
241
+ }
242
+ return;
243
+ }
244
+ outstandingBorrowedReturns.set(token, {
245
+ ref: new WeakRef(ref),
246
+ aliasBuffer: aliasBuffer === undefined
247
+ ? undefined
248
+ : new WeakRef(aliasBuffer),
249
+ runtime: ref.runtime,
250
+ });
251
+ },
188
252
  }
189
253
  : undefined,
190
254
  });
@@ -325,17 +389,21 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
325
389
  worker.postMessage?.(workerDataPayload);
326
390
  }
327
391
  let closedReason;
328
- const markWorkerClosed = (reason) => {
392
+ // `workerBytesReadable`: while the worker is still alive its pinned bytes
393
+ // can be copied out; after a crash/exit they are gone and borrows are
394
+ // detached so stale reads fail loud instead of aliasing freed memory.
395
+ const markWorkerClosed = (reason, workerBytesReadable = false) => {
329
396
  if (closedReason)
330
397
  return;
331
398
  closedReason = reason;
399
+ revokeOutstandingBorrowedReturns(workerBytesReadable);
332
400
  rejectAll(reason);
333
401
  channelHandler?.close();
334
402
  };
335
403
  const onWorkerMessage = (message) => {
336
404
  if (!isWorkerFatalMessage(message))
337
405
  return;
338
- markWorkerClosed(`Worker startup failed: ${message[WORKER_FATAL_MESSAGE_KEY]}`);
406
+ markWorkerClosed(`Worker startup failed: ${message[WORKER_FATAL_MESSAGE_KEY]}`, true);
339
407
  terminateWorkerQuietly(worker);
340
408
  };
341
409
  const onWorkerError = (error) => {
@@ -388,7 +456,7 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
388
456
  txIdle,
389
457
  call,
390
458
  kills: async () => {
391
- markWorkerClosed("Thread closed");
459
+ markWorkerClosed("Thread closed", true);
392
460
  terminateWorkerQuietly(worker);
393
461
  cleanupProcessWorkerMemoryQuietly(processWorkerMemory);
394
462
  },
@@ -36,7 +36,7 @@ export type ProcessWorkerMemoryLayout = {
36
36
  returnPayload: SharedBufferSource;
37
37
  cleanup: () => void;
38
38
  };
39
- export type ProcessWorkerRuntime = NonNullable<WorkerSettings["processRuntime"]>;
39
+ export type ProcessWorkerRuntime = "bun" | "deno" | "node";
40
40
  export type ProcessWorkerCommandPrefix = NonNullable<WorkerSettings["processCommandPrefix"]>;
41
41
  export type ResolvedProcessSharedMemorySettings = {
42
42
  mode: "inherit" | "named";
@@ -1,4 +1,3 @@
1
- import { fileURLToPath as fileURLToPathCompat } from "node:url";
2
1
  import { HEADER_SLOT_STRIDE_U32, LOCK_SECTOR_BYTE_LENGTH, LockBound, } from "../memory/lock.js";
3
2
  import { createByteCarpet, getHeaderBlockByteLength, makeSharedBufferRegion, } from "../memory/byte-carpet.js";
4
3
  import { RUNTIME } from "../common/runtime.js";
@@ -12,6 +11,15 @@ import { FileDescriptor, ProcessSharedBuffer, } from "../connections/index.js";
12
11
  import { createNodeConnectionPrimitives, loadNodeFutexAddon, } from "../connections/node.js";
13
12
  import { loadNodeNativeAddon } from "../connections/node-addons.js";
14
13
  import { detectPosixPlatform } from "../connections/posix.js";
14
+ // `node:url` resolved lazily so this module evaluates on runtimes without it
15
+ // (e.g. Andromeda); process workers aren't used there, so it never runs.
16
+ const fileURLToPathCompat = (value) => {
17
+ const url = getNodeBuiltinModule("node:url");
18
+ if (url === undefined) {
19
+ throw new Error("node:url is not available in this runtime");
20
+ }
21
+ return url.fileURLToPath(value);
22
+ };
15
23
  const execFlagKey = (flag) => flag.split("=", 1)[0];
16
24
  const NODE_PERMISSION_EXEC_FLAGS = new Set([
17
25
  "--permission",
@@ -20,10 +28,13 @@ const NODE_PERMISSION_EXEC_FLAGS = new Set([
20
28
  "--allow-fs-write",
21
29
  "--allow-worker",
22
30
  "--allow-child-process",
31
+ "--allow-net",
23
32
  "--allow-addons",
33
+ "--allow-ffi",
24
34
  "--allow-wasi",
25
35
  ]);
26
36
  const NODE_WORKER_SAFE_EXEC_FLAGS = new Set([
37
+ "--experimental-ffi",
27
38
  "--experimental-transform-types",
28
39
  "--expose-gc",
29
40
  "--no-warnings",
@@ -107,10 +118,23 @@ const DENO_PROCESS_WORKER_INTERNAL_FLAGS = [
107
118
  `--allow-env=${DENO_PROCESS_WORKER_BOOT_ENV_ALLOW}`,
108
119
  "--allow-ffi",
109
120
  ];
110
- const NODE_PROCESS_WORKER_EXEC_ARGV = [
111
- "--no-warnings",
112
- "--experimental-transform-types",
113
- ];
121
+ const nodeMajorVersion = () => {
122
+ const version = getNodeProcess()?.versions?.node ?? "";
123
+ const major = Number.parseInt(version.split(".", 1)[0] ?? "", 10);
124
+ return Number.isInteger(major) ? major : 0;
125
+ };
126
+ const nodeProcessWorkerUsesFfi = () => {
127
+ const major = nodeMajorVersion();
128
+ return major >= 26 && major % 2 === 0;
129
+ };
130
+ const nodeProcessWorkerInternalExecArgv = () => {
131
+ return [
132
+ "--no-warnings",
133
+ ...(nodeProcessWorkerUsesFfi()
134
+ ? ["--experimental-ffi"]
135
+ : ["--experimental-transform-types"]),
136
+ ];
137
+ };
114
138
  const getProcessWorkerSharedMemoryPrimitives = () => {
115
139
  switch (RUNTIME) {
116
140
  case "bun":
@@ -393,7 +417,7 @@ const processWorkerNodeBinary = (commandPrefix) => {
393
417
  : undefined) ??
394
418
  DEFAULT_NODE_BINARY;
395
419
  };
396
- const processWorkerNodeExecArgv = () => {
420
+ const processWorkerNodeExecArgv = (permission) => {
397
421
  const out = [];
398
422
  const seen = new Set();
399
423
  const add = (flag) => {
@@ -405,8 +429,17 @@ const processWorkerNodeExecArgv = () => {
405
429
  for (const flag of toWorkerCompatExecArgv(getNodeProcess()?.execArgv) ?? []) {
406
430
  add(flag);
407
431
  }
408
- for (const flag of NODE_PROCESS_WORKER_EXEC_ARGV)
432
+ for (const flag of nodeProcessWorkerInternalExecArgv())
409
433
  add(flag);
434
+ if (permission?.enabled === true && permission.unsafe !== true) {
435
+ for (const flag of permission.node.flags)
436
+ add(flag);
437
+ if (permission.netAll && nodeMajorVersion() >= 25)
438
+ add("--allow-net");
439
+ // Node process workers need one native transport capability regardless of
440
+ // task permissions: addons on Node 22/24, node:ffi on supported Node 26+ LTS.
441
+ add(nodeProcessWorkerUsesFfi() ? "--allow-ffi" : "--allow-addons");
442
+ }
410
443
  return out;
411
444
  };
412
445
  const processWorkerCommand = ({ processRuntime, workerUrl, bun, deno, commandPrefix, permission, }) => {
@@ -423,7 +456,7 @@ const processWorkerCommand = ({ processRuntime, workerUrl, bun, deno, commandPre
423
456
  else if (processRuntime === "node") {
424
457
  command = [
425
458
  processWorkerNodeBinary(commandPrefix),
426
- ...processWorkerNodeExecArgv(),
459
+ ...processWorkerNodeExecArgv(permission),
427
460
  workerPath,
428
461
  ];
429
462
  }
@@ -670,4 +703,11 @@ export const cleanupProcessWorkerMemoryQuietly = (memory) => {
670
703
  }
671
704
  catch {
672
705
  }
706
+ finally {
707
+ try {
708
+ memory?.mapping.close?.();
709
+ }
710
+ catch {
711
+ }
712
+ }
673
713
  };
@@ -1,6 +1,8 @@
1
+ import "../memory/payloadCodec.js";
1
2
  import { type Lock2, type Task } from "../memory/lock.js";
2
3
  import type { AbortSignalOption, TaskTimeout } from "../types.js";
3
4
  import { type SignalAbortStore } from "../shared/abortSignal.js";
5
+ import { type BufferReferenceReturnHooks } from "../connections/buffer-reference.js";
4
6
  type RawArguments = unknown;
5
7
  type FunctionID = number;
6
8
  type QueueTask = Task;
@@ -9,7 +11,7 @@ type CreateHostTxQueueArgs = {
9
11
  max?: number;
10
12
  lock: Lock2;
11
13
  returnLock: Lock2;
12
- releaseBufferReferenceReturn?: (token: bigint) => void;
14
+ releaseBufferReferenceReturn?: ((token: bigint) => void) | BufferReferenceReturnHooks;
13
15
  abortSignals?: Pick<SignalAbortStore, "getSignal" | "setSignal" | "resetSignal" | "closeNow">;
14
16
  now?: () => number;
15
17
  };
@@ -1,7 +1,10 @@
1
+ // Side-effect import: registers the payload codec (cycle break for Andromeda;
2
+ // see lock.ts), ensuring registration before any lock2() call.
3
+ import "../memory/payloadCodec.js";
1
4
  import { makeTask, resetTaskLocalFlags, runTaskFinalizers, TaskIndex, } from "../memory/lock.js";
2
5
  import { withResolvers } from "../common/with-resolvers.js";
3
6
  import { AbortSignalPoolExhausted, OneShotDeferred, } from "../shared/abortSignal.js";
4
- import { withBufferReferenceReturnReleaser } from "../connections/buffer-reference.js";
7
+ import { withBufferReferenceReturnReleaser, } from "../connections/buffer-reference.js";
5
8
  const SLOT_INDEX_MASK = 31;
6
9
  const SLOT_META_MASK = 0x07ffffff;
7
10
  const SLOT_META_SHIFT = 5;
package/src/types.d.ts CHANGED
@@ -83,6 +83,7 @@ type AbortSignalConfig = {
83
83
  type AbortSignalOption = true | AbortSignalConfig | undefined;
84
84
  type AbortSignalMethods<AS extends AbortSignalOption> = AS extends undefined ? never : {
85
85
  hasAborted: () => boolean;
86
+ now: () => number;
86
87
  };
87
88
  type AbortSignalToolkit<AS extends AbortSignalOption> = AbortSignalMethods<AS>;
88
89
  type TaskFn<A extends TaskInput, B extends Args, AS extends AbortSignalOption = undefined> = BivariantCallback<AS extends undefined ? [NoBlob<Awaited<A>>] : [NoBlob<Awaited<A>>, AbortSignalToolkit<AS>], MaybePromise<NoBlob<B>>>;
@@ -220,6 +221,41 @@ type WorkerBootstrapOptions<Data = unknown> = {
220
221
  */
221
222
  data?: Data;
222
223
  };
224
+ /** Native worker artifact and on-demand compiler settings. */
225
+ type CompiledWorkerOptions = {
226
+ /**
227
+ * Native executable path. Relative paths resolve from the createPool caller.
228
+ * Defaults to the task module path with its extension replaced by `.knt`.
229
+ */
230
+ artifact?: string;
231
+ /**
232
+ * Versioned metadata sidecar. Defaults to `<artifact>.json`.
233
+ */
234
+ manifest?: string;
235
+ /**
236
+ * Build a missing/incompatible artifact, or use `"always"` to rebuild once
237
+ * whenever a pool is created. Defaults to true.
238
+ */
239
+ build?: boolean | "always";
240
+ /**
241
+ * Porffor main executable or checkout. Defaults to PORFFOR_MAIN/PORF, then
242
+ * `porf` on PATH; a pinned compiler is cached locally when none is present.
243
+ */
244
+ compiler?: string;
245
+ };
246
+ type CompiledWorkerSource = string | URL | {
247
+ readonly importedFrom: string;
248
+ };
249
+ type CompiledWorkerCheck = {
250
+ /** True only when the executable and compatible manifest both validate. */
251
+ compiled: boolean;
252
+ artifact: string;
253
+ manifest: string;
254
+ reason?: string;
255
+ compiler?: string;
256
+ protocol?: string;
257
+ tasks?: readonly string[];
258
+ };
223
259
  type WorkerSettings = {
224
260
  resolveAfterFinishingAll?: true;
225
261
  /**
@@ -230,13 +266,18 @@ type WorkerSettings = {
230
266
  /**
231
267
  * Experimental worker runtime.
232
268
  * "thread" uses Worker/worker_threads. "process" spawns another JavaScript
233
- * runtime, useful for process isolation, bwrap, and containers.
269
+ * runtime, useful for process isolation, bwrap, and containers. "compiled"
270
+ * validates and runs a `.knt` native worker, building it on first use.
234
271
  */
235
- runtime?: "thread" | "process";
272
+ runtime?: "thread" | "process" | "compiled";
273
+ /** Artifact settings used when runtime is "compiled". */
274
+ compiled?: CompiledWorkerOptions;
236
275
  /**
237
- * Runtime executable to use when runtime is "process". Defaults to "deno".
276
+ * Runtime executable for process workers. Standalone `"porffor"` selects the
277
+ * compiled backend and rebuilds once per pool; with `runtime: "compiled"` it
278
+ * reuses the validated artifact.
238
279
  */
239
- processRuntime?: "bun" | "deno" | "node";
280
+ processRuntime?: "bun" | "deno" | "node" | "porffor";
240
281
  /**
241
282
  * Command argv to prepend before the process worker runtime command.
242
283
  * Useful for wrappers such as systemd-run, cgexec, nice, taskset, or
@@ -351,7 +392,8 @@ type CreatePool = {
351
392
  debug?: DebugOptions;
352
393
  source?: string;
353
394
  };
354
- export type { AbortSignalConfig as AbortSignalConfig, AbortSignalMethods as AbortSignalMethods, AbortSignalOption as AbortSignalOption, AbortSignalToolkit as AbortSignalToolkit, Args as Args, Balancer as Balancer, BalancerStrategy as BalancerStrategy, Composed as Composed, ComposedWithKey as ComposedWithKey, CreateContext as CreateContext, CreatePool as CreatePool, DebugNamespace as DebugNamespace, DebugOptions as DebugOptions, DispatcherSettings as DispatcherSettings, Envelope as Envelope, EnvelopeBody as EnvelopeBody, EnvelopeHeader as EnvelopeHeader, External as External, FixPoint as FixPoint, FunctionMapType as FunctionMapType, ImportTaskOptions as ImportTaskOptions, Inliner as Inliner, LockBuffers as LockBuffers, LockBufferTextCompat as LockBufferTextCompat, MaybePromise as MaybePromise, PayloadBufferMode as PayloadBufferMode, PayloadBufferOptions as PayloadBufferOptions, PermissionProtocol as PermissionProtocol, PermissionProtocolInput as PermissionProtocolInput, Pool as Pool, ProcessSharedMemoryMode as ProcessSharedMemoryMode, ProcessSharedMemorySettings as ProcessSharedMemorySettings, ResolvedPermissionProtocol as ResolvedPermissionProtocol, ReturnFixed as ReturnFixed, SecondPart as SecondPart, SharedBufferRegion as SharedBufferRegion, SharedBufferSource as SharedBufferSource, SharedBufferTextCompat as SharedBufferTextCompat, SingleTaskPool as SingleTaskPool, TaskFn as TaskFn, TaskInput as TaskInput, tasks as tasks, TaskTimeout as TaskTimeout, ValidInput as ValidInput, WorkerBootstrapContext as WorkerBootstrapContext, WorkerBootstrapFunction as WorkerBootstrapFunction, WorkerBootstrapOptions as WorkerBootstrapOptions, WorkerCall as WorkerCall, WorkerContext as WorkerContext, WorkerData as WorkerData, WorkerInvoke as WorkerInvoke, WorkerSettings as WorkerSettings, WorkerTimers as WorkerTimers, };
355
- export type { Task as Task } from "./memory/lock.js";
395
+ export type { AbortSignalConfig as AbortSignalConfig, AbortSignalMethods as AbortSignalMethods, AbortSignalOption as AbortSignalOption, AbortSignalToolkit as AbortSignalToolkit, Args as Args, Balancer as Balancer, BalancerStrategy as BalancerStrategy, CompiledWorkerCheck as CompiledWorkerCheck, CompiledWorkerOptions as CompiledWorkerOptions, CompiledWorkerSource as CompiledWorkerSource, Composed as Composed, ComposedWithKey as ComposedWithKey, CreateContext as CreateContext, CreatePool as CreatePool, DebugNamespace as DebugNamespace, DebugOptions as DebugOptions, DispatcherSettings as DispatcherSettings, Envelope as Envelope, EnvelopeBody as EnvelopeBody, EnvelopeHeader as EnvelopeHeader, External as External, FixPoint as FixPoint, FunctionMapType as FunctionMapType, ImportTaskOptions as ImportTaskOptions, Inliner as Inliner, LockBuffers as LockBuffers, LockBufferTextCompat as LockBufferTextCompat, MaybePromise as MaybePromise, PayloadBufferMode as PayloadBufferMode, PayloadBufferOptions as PayloadBufferOptions, PermissionProtocol as PermissionProtocol, PermissionProtocolInput as PermissionProtocolInput, Pool as Pool, ProcessSharedMemoryMode as ProcessSharedMemoryMode, ProcessSharedMemorySettings as ProcessSharedMemorySettings, ResolvedPermissionProtocol as ResolvedPermissionProtocol, ReturnFixed as ReturnFixed, SecondPart as SecondPart, SharedBufferRegion as SharedBufferRegion, SharedBufferSource as SharedBufferSource, SharedBufferTextCompat as SharedBufferTextCompat, SingleTaskPool as SingleTaskPool, TaskFn as TaskFn, TaskInput as TaskInput, tasks as tasks, TaskTimeout as TaskTimeout, ValidInput as ValidInput, WorkerBootstrapContext as WorkerBootstrapContext, WorkerBootstrapFunction as WorkerBootstrapFunction, WorkerBootstrapOptions as WorkerBootstrapOptions, WorkerCall as WorkerCall, WorkerContext as WorkerContext, WorkerData as WorkerData, WorkerInvoke as WorkerInvoke, WorkerSettings as WorkerSettings, WorkerTimers as WorkerTimers, };
396
+ import type { Task as TaskType } from "./memory/lock.js";
356
397
  export { LockBound as LockBound, PayloadBuffer as PayloadBuffer, PayloadSignal as PayloadSignal, TaskIndex as TaskIndex, } from "./memory/lock.js";
398
+ export type Task = TaskType;
357
399
  export type { RegisterMalloc as RegisterMalloc } from "./memory/regionRegistry.js";
@@ -49,7 +49,7 @@ const readSignal = (slot) => {
49
49
  const signal = (encodedSignal - ABORT_SIGNAL_META_OFFSET) | 0;
50
50
  return signal >= 0 ? signal : NO_ABORT_SIGNAL;
51
51
  };
52
- const makeToolkitCache = (hasAborted) => {
52
+ const makeToolkitCache = (hasAborted, now) => {
53
53
  const bySignal = [];
54
54
  return (signal) => {
55
55
  let toolkit = bySignal[signal];
@@ -58,6 +58,7 @@ const makeToolkitCache = (hasAborted) => {
58
58
  const hasAbortedMethod = () => hasAborted(signal);
59
59
  toolkit = {
60
60
  hasAborted: hasAbortedMethod,
61
+ now,
61
62
  };
62
63
  bySignal[signal] = toolkit;
63
64
  return toolkit;
@@ -76,7 +77,7 @@ export const composeWorkerRunner = ({ job, timeout, hasAborted, now, }) => {
76
77
  return applyTimeoutBudget(result, slot, timeout, nowTime);
77
78
  };
78
79
  }
79
- const getToolkit = makeToolkitCache(hasAborted);
80
+ const getToolkit = makeToolkitCache(hasAborted, nowTime);
80
81
  if (!timeout) {
81
82
  return (slot) => {
82
83
  const signal = readSignal(slot);
@@ -1,2 +1,3 @@
1
+ import "../memory/payloadCodec.js";
1
2
  import type { WorkerData } from "../types.js";
2
3
  export declare const workerMainLoop: (startupData: WorkerData) => Promise<void>;
@@ -4,9 +4,12 @@ import { isLockBufferTextCompat } from "../common/shared-buffer-text.js";
4
4
  import { createWorkerRxQueue } from "./rx-queue.js";
5
5
  import { createSharedMemoryTransport } from "../ipc/transport/shared-memory.js";
6
6
  import { lock2 } from "../memory/lock.js";
7
+ // Side-effect import: registers the payload codec (cycle break for Andromeda;
8
+ // see lock.ts). Must run before any lock2() call.
9
+ import "../memory/payloadCodec.js";
7
10
  import { getFunctions } from "./task-loader.js";
8
11
  import { pauseGeneric, sleepUntilChanged, whilePausing } from "./timers.js";
9
- import { RUNTIME, SET_IMMEDIATE } from "../common/runtime.js";
12
+ import { IS_ANDROMEDA, RUNTIME, SET_IMMEDIATE } from "../common/runtime.js";
10
13
  import { getNodeProcess } from "../common/node-compat.js";
11
14
  import { assertWorkerImportsResolved, assertWorkerSharedMemoryBootData, installPerformanceNowGuard, installTerminationGuard, installUnhandledRejectionSilencer, scrubWorkerDataSensitiveBuffers, } from "./safety/index.js";
12
15
  import { signalAbortFactory } from "../shared/abortSignal.js";
@@ -81,10 +84,10 @@ export const workerMainLoop = async (startupData) => {
81
84
  namespaces: debugNamespaces,
82
85
  }))
83
86
  : undefined;
84
- let Comment;
85
- (function (Comment) {
86
- Comment[Comment["thisIsAHint"] = 0] = "thisIsAHint";
87
- })(Comment || (Comment = {}));
87
+ // const object, not `enum`: Andromeda's Nova engine can't parse `enum`.
88
+ const Comment = {
89
+ thisIsAHint: 0,
90
+ };
88
91
  const signals = createSharedMemoryTransport({
89
92
  sabObject: {
90
93
  sharedSab: sab,
@@ -101,6 +104,7 @@ export const workerMainLoop = async (startupData) => {
101
104
  payloadSector: lock.payloadSector,
102
105
  payloadConfig,
103
106
  textCompat: lock.textCompat,
107
+ processBoundary: RUNTIME_IS_PROCESS_WORKER,
104
108
  });
105
109
  const returnLockState = lock2({
106
110
  headers: returnLock.headers,
@@ -110,6 +114,7 @@ export const workerMainLoop = async (startupData) => {
110
114
  payloadSector: returnLock.payloadSector,
111
115
  payloadConfig,
112
116
  textCompat: returnLock.textCompat,
117
+ processBoundary: RUNTIME_IS_PROCESS_WORKER,
113
118
  });
114
119
  const timers = workerOptions?.timers;
115
120
  const spinMicroseconds = timers?.spinMicroseconds ??
@@ -295,15 +300,23 @@ export const workerMainLoop = async (startupData) => {
295
300
  };
296
301
  const isWorkerGlobalScope = () => {
297
302
  const scopeCtor = globalThis.WorkerGlobalScope;
298
- if (typeof scopeCtor !== "function")
299
- return false;
300
- try {
301
- return globalThis instanceof
302
- scopeCtor;
303
+ if (typeof scopeCtor === "function") {
304
+ try {
305
+ if (globalThis instanceof scopeCtor) {
306
+ return true;
307
+ }
308
+ }
309
+ catch {
310
+ // fall through to runtime-specific checks
311
+ }
303
312
  }
304
- catch {
305
- return false;
313
+ // Andromeda has no `WorkerGlobalScope`; its worker scope carries a `self`
314
+ // global that the main thread lacks.
315
+ if (IS_ANDROMEDA &&
316
+ typeof globalThis.self !== "undefined") {
317
+ return true;
306
318
  }
319
+ return false;
307
320
  };
308
321
  const isLockBuffers = (value) => {
309
322
  if (!value || typeof value !== "object")