knitting 0.1.50 → 0.1.52

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 (72) hide show
  1. package/README.md +268 -75
  2. package/knitting.d.ts +1 -0
  3. package/map.md +56 -6
  4. package/package.json +16 -4
  5. package/prebuilds/darwin-arm64-node-127/knitting_buffer_pointer.node +0 -0
  6. package/prebuilds/darwin-arm64-node-127/knitting_shared_memory.node +0 -0
  7. package/prebuilds/darwin-arm64-node-137/knitting_buffer_pointer.node +0 -0
  8. package/prebuilds/darwin-arm64-node-137/knitting_shared_memory.node +0 -0
  9. package/prebuilds/darwin-x64-node-127/knitting_buffer_pointer.node +0 -0
  10. package/prebuilds/darwin-x64-node-127/knitting_shared_memory.node +0 -0
  11. package/prebuilds/darwin-x64-node-137/knitting_buffer_pointer.node +0 -0
  12. package/prebuilds/darwin-x64-node-137/knitting_shared_memory.node +0 -0
  13. package/prebuilds/linux-x64-node-127/knitting_buffer_pointer.node +0 -0
  14. package/prebuilds/linux-x64-node-127/knitting_shared_memory.node +0 -0
  15. package/prebuilds/linux-x64-node-137/knitting_buffer_pointer.node +0 -0
  16. package/prebuilds/linux-x64-node-137/knitting_shared_memory.node +0 -0
  17. package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +0 -0
  18. package/prebuilds/win32-x64-node-127/knitting_buffer_pointer.node +0 -0
  19. package/prebuilds/win32-x64-node-127/knitting_shared_memory.node +0 -0
  20. package/prebuilds/win32-x64-node-127/knitting_shm.node +0 -0
  21. package/prebuilds/win32-x64-node-137/knitting_buffer_pointer.node +0 -0
  22. package/prebuilds/win32-x64-node-137/knitting_shared_memory.node +0 -0
  23. package/prebuilds/win32-x64-node-137/knitting_shm.node +0 -0
  24. package/scripts/build-native-addons.ts +5 -0
  25. package/src/api.d.ts +5 -11
  26. package/src/api.js +103 -22
  27. package/src/common/envelope.d.ts +9 -3
  28. package/src/common/envelope.js +14 -0
  29. package/src/common/task-source.js +4 -0
  30. package/src/common/worker-runtime.d.ts +2 -0
  31. package/src/common/worker-runtime.js +9 -0
  32. package/src/connections/buffer-reference-native.d.ts +56 -0
  33. package/src/connections/buffer-reference-native.js +217 -0
  34. package/src/connections/buffer-reference.d.ts +76 -0
  35. package/src/connections/buffer-reference.js +459 -0
  36. package/src/connections/index.d.ts +1 -0
  37. package/src/connections/index.js +1 -0
  38. package/src/connections/node-addons.d.ts +1 -1
  39. package/src/connections/node-buffer-pointer.d.ts +20 -0
  40. package/src/connections/node-buffer-pointer.js +16 -0
  41. package/src/connections/process-shared-buffer.js +2 -0
  42. package/src/connections/shared-array-buffer-payload.d.ts +36 -0
  43. package/src/connections/shared-array-buffer-payload.js +235 -0
  44. package/src/knitting_buffer_pointer.cc +425 -0
  45. package/src/knitting_shared_memory.cc +9 -2
  46. package/src/memory/lock.d.ts +12 -1
  47. package/src/memory/lock.js +55 -172
  48. package/src/memory/payloadCodec.js +241 -65
  49. package/src/memory/shared-buffer-io.d.ts +2 -0
  50. package/src/memory/shared-buffer-io.js +23 -0
  51. package/src/runtime/inline-executor.d.ts +2 -2
  52. package/src/runtime/inline-executor.js +15 -3
  53. package/src/runtime/pool.d.ts +3 -1
  54. package/src/runtime/pool.js +9 -1
  55. package/src/runtime/process-worker.js +3 -1
  56. package/src/runtime/tx-queue.d.ts +3 -2
  57. package/src/runtime/tx-queue.js +18 -13
  58. package/src/types.d.ts +39 -18
  59. package/src/utils/http.d.ts +21 -0
  60. package/src/utils/http.js +93 -0
  61. package/src/worker/loop.js +26 -4
  62. package/src/worker/process-worker-bootstrap.js +1 -0
  63. package/src/worker/rx-queue.d.ts +4 -1
  64. package/src/worker/rx-queue.js +53 -4
  65. package/src/worker/safety/startup.d.ts +2 -1
  66. package/src/worker/safety/startup.js +5 -2
  67. package/src/worker/task-loader.d.ts +2 -1
  68. package/src/worker/task-loader.js +14 -2
  69. package/unsafe.d.ts +1 -0
  70. package/unsafe.js +1 -0
  71. package/utils.d.ts +1 -0
  72. package/utils.js +1 -0
package/README.md CHANGED
@@ -11,43 +11,55 @@
11
11
  [![Deno](https://img.shields.io/badge/deno-2%2B-000000?logo=deno&logoColor=white)](https://deno.com/)
12
12
  [![Bun](https://img.shields.io/badge/bun-1%2B-f472b6?logo=bun&logoColor=white)](https://bun.sh/)
13
13
 
14
- Knitting is a worker pool over a shared-memory IPC runtime for Node.js, Deno, and Bun. Our mission is to make JavaScript a multicore language with real inter-runtime communication.
14
+ Knitting is a worker pool over a shared-memory IPC runtime for Node.js, Deno,
15
+ and Bun. Our mission is to make JavaScript a multicore language with real
16
+ inter-runtime communication.
15
17
 
16
- Thanks to its memory design, it can be 5x to 25x faster than using `postMessages` , bypassing OS socket communication entirely with a novel protocol written from scratch.
18
+ Thanks to its memory design, it can be 5x to 25x faster than using
19
+ `postMessages` , bypassing OS socket communication entirely with a novel
20
+ protocol written from scratch.
17
21
 
18
- Use it for parts of your program that should run in different environments, such as CPU-intensive tasks, small jobs, runtime-isolated tasks, custom isolation for workers in Docker or bwrap environments, long-running tools, or any processes that require speed and type flexibility.
22
+ Use it for parts of your program that should run in different environments, such
23
+ as CPU-intensive tasks, small jobs, runtime-isolated tasks, custom isolation for
24
+ workers in Docker or bwrap environments, long-running tools, or any processes
25
+ that require speed and type flexibility.
19
26
 
20
- You define a task once, spin up a pool, and call it like a normal async function:
27
+ You export a function or task, spin up a pool, and call it like a normal async
28
+ function:
21
29
 
22
30
  ```ts
23
-
24
31
  const result = await pool.call.resizeImage(file);
25
-
26
32
  ```
27
33
 
28
34
  So you only have to take care of 4 things:
29
- - Create a task
30
- - Create a pool
31
- - Call your task
32
- - Terminate the pool
33
35
 
36
+ - Export a function or task
37
+ - Create a pool
38
+ - Call it
39
+ - Let `using` or `shutdown()` close the pool
34
40
 
35
- Under the hood, we take care of scheduling and orchestration across worker threads or separate processes, also handling signals, timeouts, life cycles, memory allocation, garbage collection, and cross-runtime memory over the processes.
36
-
41
+ Under the hood, we take care of scheduling and orchestration across worker
42
+ threads or separate processes, also handling signals, timeouts, life cycles,
43
+ memory allocation, garbage collection, and cross-runtime memory over the
44
+ processes.
37
45
 
38
46
  ## Why use it?
39
47
 
40
- - Easy to use: Have a multithreaded environment or process with few lines of code.
41
- - Great type support: pass primitives, JSON, Promise of these, and special types (typed arrays, `Node Buffer`, `Envelope`, and `ProcessSharedBuffer`).
48
+ - Easy to use: Have a multithreaded environment or process with few lines of
49
+ code.
50
+ - Great type support: pass primitives, JSON, Promise of these, and special types
51
+ (typed arrays, `Node Buffer`, `Envelope`, and `ProcessSharedBuffer`).
42
52
  - Runtime flexibility: the same API across Node.js, Deno, and Bun.
43
- - Worker choices: use threads for fast pools or processes for stronger isolation.
44
- - All out-of-the-box experiences: strict-by-default permissions, payload-size limits, task timeouts, abort-aware tasks, and worker hard timeouts.
53
+ - Worker choices: use threads for fast pools or processes for stronger
54
+ isolation.
55
+ - All out-of-the-box experiences: strict-by-default permissions, payload-size
56
+ limits, task timeouts, abort-aware tasks, and worker hard timeouts.
45
57
 
46
58
  ## Requirements
47
59
 
48
- - Node.js 22+
49
- - Deno 2+
50
- - Bun 1+
60
+ - Node.js 22+
61
+ - Deno 2+
62
+ - Bun 1+
51
63
 
52
64
  ## Install
53
65
 
@@ -60,41 +72,33 @@ npm install knitting
60
72
  For Deno projects:
61
73
 
62
74
  ```bash
63
- deno add --npm jsr:@vixeny/knitting
75
+ deno add --npm knitting
64
76
  ```
65
77
 
66
78
  ## Quick Start
67
79
 
68
80
  ```ts
69
- import { createPool, isMain, task } from "knitting";
81
+ import { createPool, isMain } from "knitting";
70
82
 
71
- export const square = task<number, number>({
72
- f: (value) => value * value,
73
- });
83
+ export const square = (value: number) => value * value;
74
84
 
75
- export const greet = task<string, string>({
76
- f: (name) => `hello ${name}`,
77
- });
85
+ export const greet = (name: string) => `hello ${name}`;
78
86
 
79
87
  if (isMain) {
80
- const pool = createPool({ threads: 2 })({ square, greet });
88
+ using pool = createPool({ threads: 2 })({ square, greet });
81
89
 
82
- try {
83
- const [four, message] = await Promise.all([
84
- pool.call.square(2),
85
- pool.call.greet("knitting"),
86
- ]);
90
+ const [four, message] = await Promise.all([
91
+ pool.call.square(2),
92
+ pool.call.greet("knitting"),
93
+ ]);
87
94
 
88
- console.log({ four, message });
89
- } finally {
90
- await pool.shutdown();
91
- }
95
+ console.log({ four, message });
92
96
  }
93
97
  ```
94
98
 
95
99
  The `isMain` guard when the same module is loaded by workers or process. Export
96
- exposes the tasks or functions at module scope, so knitting maps down the imports,
97
- then use and use the pool only from the main program.
100
+ exposes the tasks or functions at module scope, so knitting maps down the
101
+ imports, then use and use the pool only from the main program.
98
102
 
99
103
  ## The Mental Model
100
104
 
@@ -107,7 +111,7 @@ import { createPool, isMain, task } from "knitting";
107
111
  - `task(...)` describes a callable worker function (types + implementation).
108
112
 
109
113
  - `createPool(options)({ tasks })` starts workers and gives you a typed `call`
110
- object for invoking tasks.
114
+ object for invoking tasks.
111
115
 
112
116
  - `pool.shutdown()` stops workers when you're done.
113
117
 
@@ -128,6 +132,39 @@ if (isMain) {
128
132
  }
129
133
  ```
130
134
 
135
+ On TypeScript or runtimes that support explicit resource management, the pool is
136
+ also a synchronous disposable:
137
+
138
+ ```ts
139
+ if (isMain) {
140
+ using pool = createPool({ threads: 4 })({ add });
141
+
142
+ const value = await pool.call.add([1, 2]);
143
+ console.log(value);
144
+ }
145
+ ```
146
+
147
+ `using` starts pool shutdown when the scope exits and does not wait for it.
148
+ TypeScript 5.2+ can compile this pattern for runtimes that do not parse `using`
149
+ syntax directly. Use `await pool.shutdown()` when you need to wait for shutdown
150
+ or pass a shutdown delay.
151
+
152
+ For simple tasks that do not need timeout or abort metadata, exported functions
153
+ can be used directly:
154
+
155
+ ```ts
156
+ export const add = ([a, b]: [number, number]) => a + b;
157
+
158
+ if (isMain) {
159
+ using pool = createPool({ threads: 1 })({ add });
160
+ console.log(await pool.call.add([1, 2]));
161
+ }
162
+ ```
163
+
164
+ Bare functions must be exported from the module that creates the pool. Inline
165
+ anonymous functions cannot be imported by workers; use `task(...)` when you need
166
+ metadata or a more explicit task definition.
167
+
131
168
  Once you have a pool, calls are just promises, so batching looks like normal
132
169
  JavaScript:
133
170
 
@@ -156,8 +193,8 @@ export const pixels = task<ResizeInput, number>({
156
193
  ```
157
194
 
158
195
  Supported payloads are listed below. For large binary data, prefer
159
- `ArrayBuffer`, typed arrays, or `ProcessSharedBuffer` instead of serializing
160
- big objects.
196
+ `ArrayBuffer`, typed arrays, or `ProcessSharedBuffer` instead of serializing big
197
+ objects.
161
198
 
162
199
  ### Task timeouts
163
200
 
@@ -241,6 +278,14 @@ When workers import files, keep the pool's permission settings in mind. The
241
278
  default strict mode allows task imports, but custom permission policies can
242
279
  limit reads, writes, environment access, networking, and process execution.
243
280
 
281
+ Imported tasks are never run on the host inline lane, even when the pool enables
282
+ the `inliner`. Inlining would evaluate the imported module on the host and
283
+ bypass the worker permissions that `importTask` exists to enforce, so Knitting
284
+ always routes imported tasks to a worker. You can freely mix `importTask` and
285
+ the `inliner` in one pool — regular tasks get inlined while imported ones stay
286
+ on worker lanes — but the pool needs at least one worker thread for them to run,
287
+ otherwise `createPool` throws.
288
+
244
289
  ### Single-task shorthand
245
290
 
246
291
  For quick scripts, a task can create its own pool:
@@ -278,20 +323,20 @@ const pool = createPool({
278
323
 
279
324
  Common options you might tweak:
280
325
 
281
- | Option | What it does |
282
- | --- | --- |
283
- | `threads` | Number of workers to start. |
284
- | `balancer` | Scheduling strategy: `"roundRobin"`, `"firstIdle"`, `"randomLane"`, `"firstIdleOrRandom"`, or the legacy alias `"robinRound"`. |
285
- | `payload` | Shared payload-buffer settings: `mode`, `payloadInitialBytes`, `payloadMaxByteLength`, and `maxPayloadBytes`. |
286
- | `abortSignalCapacity` | Number of shared abort slots available to abort-aware calls. |
287
- | `worker.resolveAfterFinishingAll` | Let submitted calls finish before shutdown resolves. |
288
- | `worker.bootstrap` | Privileged async hook imported and awaited before task modules load. |
289
- | `worker.hardTimeoutMs` | Force pool shutdown when a task exceeds this many milliseconds. |
290
- | `worker.runtime` | Choose `"thread"` or `"process"` workers. |
291
- | `worker.processSharedMemory` | Process-worker memory discovery: `"inherit"` by default on POSIX, or `"named"` for wrappers/containers that cannot preserve fd 0. |
292
- | `permission` | Runtime permission policy for workers. |
293
- | `debug` | Enable extra diagnostics. |
294
- | `source` | Worker source override for advanced runtimes. |
326
+ | Option | What it does |
327
+ | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
328
+ | `threads` | Number of workers to start. |
329
+ | `balancer` | Scheduling strategy: `"roundRobin"`, `"firstIdle"`, `"randomLane"`, `"firstIdleOrRandom"`, or the legacy alias `"robinRound"`. |
330
+ | `payload` | Shared payload-buffer settings: `mode`, `payloadInitialBytes`, `payloadMaxByteLength`, and `maxPayloadBytes`. |
331
+ | `abortSignalCapacity` | Number of shared abort slots available to abort-aware calls. |
332
+ | `worker.resolveAfterFinishingAll` | Let submitted calls finish before shutdown resolves. |
333
+ | `worker.bootstrap` | Privileged async hook imported and awaited before task modules load. |
334
+ | `worker.hardTimeoutMs` | Force pool shutdown when a task exceeds this many milliseconds. |
335
+ | `worker.runtime` | Choose `"thread"` or `"process"` workers. |
336
+ | `worker.processSharedMemory` | Process-worker memory discovery: `"inherit"` by default on POSIX, or `"named"` for wrappers/containers that cannot preserve fd 0. |
337
+ | `permission` | Runtime permission policy for workers. |
338
+ | `debug` | Enable extra diagnostics. |
339
+ | `source` | Worker source override for advanced runtimes. |
295
340
 
296
341
  ### Worker bootstrap
297
342
 
@@ -340,10 +385,9 @@ const pool = createPool({
340
385
  })({ add });
341
386
  ```
342
387
 
343
- `processRuntime` can be `"node"`, `"deno"`, or `"bun"` and defaults to
344
- `"deno"`. You can also provide a `processCommandPrefix` when workers need to be
345
- launched through a wrapper such as a package manager, container command, or
346
- runtime shim.
388
+ `processRuntime` can be `"node"`, `"deno"`, or `"bun"` and defaults to `"deno"`.
389
+ You can also provide a `processCommandPrefix` when workers need to be launched
390
+ through a wrapper such as a package manager, container command, or runtime shim.
347
391
 
348
392
  That prefix is also useful for sandbox and resource-control tools. The one
349
393
  important detail is that process workers receive their shared-memory handle on
@@ -394,8 +438,8 @@ const pool = createPool({
394
438
 
395
439
  On Windows, Knitting automatically uses named shared memory for the
396
440
  process-worker control channel. You do not need to set
397
- `processSharedMemory: "named"` yourself — the runtime detects Windows and
398
- forces it.
441
+ `processSharedMemory: "named"` yourself — the runtime detects Windows and forces
442
+ it.
399
443
 
400
444
  ```ts
401
445
  // Works on Windows without extra options.
@@ -422,7 +466,6 @@ the inherited fd:
422
466
 
423
467
  ```ts
424
468
  const pool = createPool({
425
- threads: 2,
426
469
  worker: {
427
470
  runtime: "process",
428
471
  processRuntime: "bun",
@@ -492,7 +535,10 @@ Worker calls can carry the following values across the shared-memory transport:
492
535
  - Plain objects and arrays made from supported values.
493
536
  - `ArrayBuffer`, Node `Buffer`, `DataView`, and supported typed arrays.
494
537
  - `ProcessSharedBuffer`.
495
- - `Envelope` for metadata plus binary payloads.
538
+ - `BufferReference` from `knitting/unsafe` for experimental zero-copy buffers to
539
+ thread workers (same process only; see below).
540
+ - `Envelope` for a JSON header plus a binary body (`ArrayBuffer`,
541
+ `SharedArrayBuffer`, `ProcessSharedBuffer`, or `BufferReference`).
496
542
  - `Error`, `Date`, and global symbols created with `Symbol.for(...)`.
497
543
  - Native `Promise<supported-value>` inputs. The promise is awaited before
498
544
  dispatch.
@@ -502,7 +548,8 @@ If it isn't on that list, assume it isn't portable. Some things don't (or
502
548
  shouldn't) cross the boundary:
503
549
 
504
550
  - DOM objects and platform handles.
505
- - Functions, unless they are part of a `task` or `importTask` definition.
551
+ - Functions, unless they are exported pool tasks or part of a `task` or
552
+ `importTask` definition.
506
553
  - Cyclic object graphs.
507
554
  - `Map`, `Set`, `WeakMap`, and non-global symbols.
508
555
  - Objects with behavior that depends on prototypes, getters, setters, or hidden
@@ -510,12 +557,13 @@ shouldn't) cross the boundary:
510
557
 
511
558
  ### Envelope
512
559
 
513
- `Envelope` pairs a JSON-serializable header with a binary `ArrayBuffer`
514
- payload. Use it when a call needs both structured metadata and raw bytes in a
515
- single argument.
560
+ `Envelope` pairs a JSON-serializable header with a binary body. Use it when a
561
+ call needs both structured metadata and raw bytes in a single argument — the
562
+ transport carries one special binary value per call, so an envelope is the way
563
+ to attach a header to one.
516
564
 
517
565
  ```ts
518
- import { Envelope, createPool, isMain, task } from "knitting";
566
+ import { createPool, Envelope, isMain, task } from "knitting";
519
567
 
520
568
  export const processImage = task<
521
569
  Envelope<{ format: string }>,
@@ -543,6 +591,58 @@ if (isMain) {
543
591
  }
544
592
  ```
545
593
 
594
+ #### Body types
595
+
596
+ The body is generic — `Envelope<Header, Body>` — and accepts any of the binary
597
+ shapes the transport understands:
598
+
599
+ | Body | Copy? | Workers | Notes |
600
+ | -------------------- | -------------------- | ---------------- | ------------------------------------------------ |
601
+ | `ArrayBuffer` | copied | thread + process | The default body; works everywhere. |
602
+ | `SharedArrayBuffer` | zero-copy, shared | thread only | Shared by reference; process workers reject it. |
603
+ | `ProcessSharedBuffer`| zero-copy, shared | thread + process | Cross-process shared memory. |
604
+ | `BufferReference` | zero-copy, moved | thread only | From `knitting/unsafe`; same constraints as bare `BufferReference`. |
605
+
606
+ The header keeps its fast paths regardless of the body: a small header is
607
+ written inline, and only large headers spill to the dynamic payload region. A
608
+ zero-copy body keeps its own semantics — a `SharedArrayBuffer` stays shared by
609
+ reference, and a `BufferReference` body is still moved (its source is detached)
610
+ and joins the same borrow/copy/release flow it follows on its own.
611
+
612
+ ```ts
613
+ import { createPool, Envelope, isMain, task } from "knitting";
614
+ import { BufferReference } from "knitting/unsafe";
615
+
616
+ export const invert = task<
617
+ Envelope<{ op: string }, BufferReference>,
618
+ Envelope<{ op: string }, BufferReference>
619
+ >({
620
+ f: (envelope) => {
621
+ const pixels = envelope.payload.toUint8Array();
622
+ const out = new Uint8Array(pixels.length);
623
+ for (let i = 0; i < pixels.length; i++) out[i] = 255 - pixels[i];
624
+ return new Envelope({ op: "inverted" }, new BufferReference(out));
625
+ },
626
+ });
627
+
628
+ if (isMain) {
629
+ using pool = createPool({ threads: 1 })({ invert });
630
+ const pixels = new Uint8Array([0, 64, 128, 192, 255]);
631
+
632
+ using result = await pool.call.invert(
633
+ new Envelope({ op: "invert" }, new BufferReference(pixels)),
634
+ );
635
+ console.log(result.header, [...result.payload.toUint8Array()]);
636
+ }
637
+ ```
638
+
639
+ `Envelope` is disposable: disposing it (via `using` or `Symbol.dispose`)
640
+ disposes a disposable body such as a `BufferReference`, and is a harmless no-op
641
+ for `ArrayBuffer` / `SharedArrayBuffer` bodies. See
642
+ [Experimental zero-copy buffers for thread workers](#experimental-zero-copy-buffers-for-thread-workers)
643
+ for the full `BufferReference` constraints, which apply unchanged to a
644
+ `BufferReference` body.
645
+
546
646
  If a payload is large, set `payload.maxPayloadBytes` deliberately and prefer
547
647
  binary/shared-memory shapes over deeply nested objects.
548
648
 
@@ -709,8 +809,8 @@ There are three moving parts:
709
809
 
710
810
  - `processSharedMemory: "named"` lets the Docker worker find Knitting's control
711
811
  channel.
712
- - `ProcessSharedBuffer.create({ mode: "create", name, size })` makes the
713
- payload buffer reopenable by name.
812
+ - `ProcessSharedBuffer.create({ mode: "create", name, size })` makes the payload
813
+ buffer reopenable by name.
714
814
  - `--ipc=host` lets the container see the same POSIX shared-memory namespace.
715
815
 
716
816
  This is same-host communication. It is fast because both sides map the same
@@ -718,10 +818,103 @@ bytes, but it is not a network transport and it deliberately shares IPC with the
718
818
  container. Use names like capabilities: generate them, keep them private, and
719
819
  unlink them when the shared memory is no longer needed.
720
820
 
821
+ ### Experimental zero-copy buffers for thread workers
822
+
823
+ `BufferReference` lives in `knitting/unsafe`. It is experimental and may be
824
+ changed or removed if its safety tradeoffs are not acceptable. It **moves** a
825
+ buffer's ownership to a **thread** worker: constructing one detaches the source,
826
+ so the bytes travel to the worker without being serialized through the
827
+ transport. Send a result back the same way — return a `BufferReference` from the
828
+ worker. It is the same-process counterpart to `ProcessSharedBuffer`: reach for
829
+ it when you hold a large `ArrayBuffer` or typed array and the copy cost to a
830
+ thread worker actually matters.
831
+
832
+ ```ts
833
+ import { createPool, isMain, task } from "knitting";
834
+ import { BufferReference } from "knitting/unsafe";
835
+
836
+ export const invert = task<BufferReference, BufferReference>({
837
+ f: (ref) => {
838
+ const pixels = ref.toUint8Array(); // the moved bytes, no copy
839
+ const out = new Uint8Array(pixels.length);
840
+ for (let i = 0; i < pixels.length; i++) out[i] = 255 - pixels[i];
841
+ return new BufferReference(out); // move the result back to the host
842
+ },
843
+ });
844
+
845
+ if (isMain) {
846
+ const pixels = new Uint8Array([0, 64, 128, 192, 255]);
847
+ using pool = createPool({ threads: 1 })({ invert });
848
+
849
+ // `pixels` is detached by the move; the result comes back as a BufferReference.
850
+ const result = await pool.call.invert(new BufferReference(pixels));
851
+ console.log([...result.toUint8Array()]); // [255, 191, 127, 63, 0]
852
+ }
853
+ ```
854
+
855
+ Read these constraints before reaching for it:
856
+
857
+ - **Thread workers only.** The handle is a process-local pointer. Process
858
+ workers do not share it, so a `BufferReference` sent to a process worker
859
+ throws. For cross-process sharing use `ProcessSharedBuffer`.
860
+ - **ArrayBuffer only.** `SharedArrayBuffer` is already shareable and cannot be
861
+ detached, so `BufferReference` rejects SAB sources and SAB-backed typed-array
862
+ views.
863
+ - **Move semantics.** Constructing a `BufferReference` detaches its source — the
864
+ original buffer is empty afterward, and reads/writes through it are gone. The
865
+ bytes now belong to the reference; to get a result back, the worker returns
866
+ its own `BufferReference`. Each handle is one-shot. Forward inputs the worker
867
+ materializes with `.toArrayBuffer()`/`.toUint8Array()` are borrowed for the
868
+ duration of the call and detached once it settles; do not keep using them from
869
+ fire-and-forget work after the task returns.
870
+ - **Forward is zero-copy everywhere; the return is zero-copy on Node.** Sending
871
+ a buffer to the worker never copies. On Node the returned buffer is also
872
+ handed back with no copy (the engine co-owns the backing store across
873
+ threads); on Deno and Bun the host takes a single copy of the returned bytes,
874
+ because their FFI cannot co-own a worker-thread backing store. Both are far
875
+ cheaper than serializing a large buffer through the transport.
876
+ - **Borrowed Deno/Bun returns are opt-in.** The default is
877
+ `unsafe: { BufferReferenceReturn: "copy" }` — the safe single copy described
878
+ above. Set it to `"borrow"` on `createPool` to skip that copy on Deno/Bun by
879
+ borrowing the worker's backing store until the returned `BufferReference` is
880
+ released. Call `ref.release()` or use `using`, and do it before shutting down
881
+ the producing worker. **After `release()` the borrowed bytes are gone —
882
+ reading the reference, or any view you took from it, is a use-after-free.**
883
+ If the bytes escape into HTTP responses, streams, timers, callbacks, or caches,
884
+ copy them before the borrowed reference is released.
885
+ - **Unsafe escape hatch.** This is not a security boundary. Forged metadata or
886
+ unsynchronized host/worker mutation can still be unsafe.
887
+ - **Node uses a native addon.** Bun and Deno go through their FFI; Node uses the
888
+ `knitting_buffer_pointer` prebuild shipped with the package (or
889
+ `bun run build:native` when developing on a new ABI). Without it, constructing
890
+ a `BufferReference` on Node throws.
891
+
892
+ Borrowed returns, end to end (Node is always zero-copy; this opts Deno/Bun in):
893
+
894
+ ```ts
895
+ using pool = createPool({
896
+ threads: 1,
897
+ unsafe: {
898
+ BufferReferenceReturn: "borrow",
899
+ },
900
+ })({ invert });
901
+
902
+ {
903
+ using result = await pool.call.invert(new BufferReference(pixels));
904
+ const out = result.toUint8Array(); // borrowed — valid only while `result` lives
905
+ console.log([...out]);
906
+ } // `using` releases the borrow here; do not read `out` after this point
907
+ ```
908
+
909
+ When in doubt, a plain `ArrayBuffer` or typed-array payload — which knitting
910
+ copies through the shared transport — is simpler and works for both thread and
911
+ process workers. Reach for `BufferReference` only when the copy cost of a large
912
+ buffer to a thread worker actually matters: below roughly 256 KiB the per-call
913
+ pointer setup tends to cost more than just copying, so the plain transport wins.
914
+
721
915
  ### Current support
722
916
 
723
- Knitting supports Node.js 22+, Deno 2+, and Bun 1+ on Linux, macOS, and
724
- Windows.
917
+ Knitting supports Node.js 22+, Deno 2+, and Bun 1+ on Linux, macOS, and Windows.
725
918
 
726
919
  Thread workers work without native pieces. Process workers and
727
920
  `ProcessSharedBuffer` use the platform's shared-memory APIs. Release packages
@@ -732,8 +925,8 @@ FFI path; if you are developing locally on a new Node ABI or architecture, run:
732
925
  bun run build:native
733
926
  ```
734
927
 
735
- For Deno projects with permissions enabled, allow FFI when using process
736
- workers or `ProcessSharedBuffer`.
928
+ For Deno projects with permissions enabled, allow FFI when using process workers
929
+ or `ProcessSharedBuffer`.
737
930
 
738
931
  ## Runtime Safety
739
932
 
package/knitting.d.ts CHANGED
@@ -2,3 +2,4 @@ import { workerMainLoop } from "./src/worker/loop.js";
2
2
  import { createPool, importTask, isMain, task } from "./src/api.js";
3
3
  import { Envelope } from "./src/common/envelope.js";
4
4
  export { createPool as createPool, Envelope as Envelope, importTask as importTask, isMain as isMain, task as task, workerMainLoop as workerMainLoop, };
5
+ export type { EnvelopeBody as EnvelopeBody, EnvelopeHeader as EnvelopeHeader, } from "./src/common/envelope.js";
package/map.md CHANGED
@@ -20,6 +20,9 @@ The core flow is:
20
20
  `importTask`, `isMain`, `Envelope`, and `workerMainLoop`.
21
21
  - `process-shared-buffer.ts`: Secondary public export for process-shared-buffer
22
22
  helpers.
23
+ - `unsafe.ts`: Public export for experimental lower-level capabilities such as
24
+ `BufferReference`.
25
+ - `utils.ts`: Public export for request/response buffer conversion helpers.
23
26
  - `README.md`: User-facing documentation, examples, configuration, and command
24
27
  reference.
25
28
  - `package.json`: Package metadata, export map, shipped files, scripts, runtime
@@ -32,6 +35,10 @@ The core flow is:
32
35
  ## Build And Scripts
33
36
 
34
37
  - `build.ts`: Bundles `knitting.ts` to `out/` with Bun for a Node ESM target.
38
+ - `tsconfig.npm.json`: TypeScript config for the npm release build. Emits
39
+ `.js` and `.d.ts` files beside the source tree.
40
+ - `scripts/rewrite-declaration-imports.mjs`: Post-processes emitted `.d.ts`
41
+ files so declaration imports point at `.js` files for npm consumers.
35
42
  - `scripts/build-native-addons.ts`: Compiles the native Node addons into
36
43
  `build/Release/` on Linux, macOS, and Windows. It finds Node headers/libs,
37
44
  splits user flags, builds the shared-memory and futex addons, and emits the
@@ -39,6 +46,27 @@ The core flow is:
39
46
  - `run.sh`: Runs every top-level benchmark in `bench/` across Node, Deno, and
40
47
  Bun. `--json` writes JSON result files.
41
48
 
49
+ ## CI And Release
50
+
51
+ - `.github/workflows/test.yml`: Push/PR test matrix for Deno, Node, and Bun
52
+ across Linux, macOS, and Windows.
53
+ - `.github/workflows/coverage.yml`: Node coverage workflow with a 90% line
54
+ coverage gate.
55
+ - `.github/workflows/build-and-test.yml`: Manual workflow that builds native
56
+ prebuild artifacts and runs runtime tests against them.
57
+ - `.github/workflows/publish.yml`: Manual native-prebuild workflow. Verifies
58
+ Node and Windows FFI prebuilds, checks the JSR package contents, and commits
59
+ updated `prebuilds/` artifacts back to the branch.
60
+
61
+ ## Project Guidance And Docs
62
+
63
+ - `docs/AGENTS.md`: Agent/contributor orientation, invariants, and workflow
64
+ notes.
65
+ - `docs/CLAUDE.md`: Pointer to the shared agent guidance.
66
+ - `docs/buffer-reference-ownership-move.md`: Design record for
67
+ `BufferReference` ownership transfer.
68
+ - `docs/knitting.pdf`: Longer-form project design/theory document.
69
+
42
70
  ## Public API Layer
43
71
 
44
72
  - `src/api.ts`: Defines `task`, `importTask`, `createPool`, `isMain`, task id
@@ -70,6 +98,8 @@ The core flow is:
70
98
  - `src/common/with-resolvers.ts`: `Promise.withResolvers` compatibility helper.
71
99
  - `src/common/worker-runtime.ts`: Runtime-neutral worker/thread/process-worker
72
100
  detection, parent-port access, and message-channel creation.
101
+ - `src/utils/http.ts`: Buffer conversion helpers for HTTP/request-response
102
+ boundaries, including raw bytes, strings, JSON, and numeric arrays.
73
103
 
74
104
  ## Runtime Host Side
75
105
 
@@ -85,12 +115,20 @@ The core flow is:
85
115
  first-idle, random, and first-idle-random.
86
116
  - `src/runtime/inline-executor.ts`: Optional in-process executor used by the
87
117
  inliner path to run tasks without crossing the worker boundary.
118
+ - `src/runtime/process-worker.ts`: Process-worker spawning, command/runtime
119
+ selection, process shared-memory layout, inherited/named mapping metadata, and
120
+ child boot payload construction.
88
121
 
89
122
  ## Worker Side
90
123
 
124
+ - `src/worker/bootstrap.ts`: Optional user bootstrap hook that runs before task
125
+ modules import and revives process-shared-buffer metadata in bootstrap data.
91
126
  - `src/worker/loop.ts`: Worker entrypoint and main loop. Boots worker contexts,
92
127
  installs safety guards, receives tasks, executes batches, writes completions,
93
128
  and supports process-worker bootstrapping.
129
+ - `src/worker/process-worker-bootstrap.ts`: Child-side process-worker boot
130
+ payload validation, shared-memory remapping, runtime primitive setup, and
131
+ startup handoff into the worker loop.
94
132
  - `src/worker/task-loader.ts`: Imports task modules inside workers, finds
95
133
  exported task definitions, filters by id/caller position, and normalizes
96
134
  timeout metadata.
@@ -154,6 +192,8 @@ The core flow is:
154
192
  payload-codec registration.
155
193
  - `src/connections/file-descriptor.ts`: File descriptor wrapper, metadata
156
194
  parsing, mapping support, and descriptor lifecycle helpers.
195
+ - `src/connections/node-addons.ts`: Native addon specifier resolution for
196
+ committed Node ABI prebuilds with `build/Release` fallback loading.
157
197
  - `src/connections/node.ts`: Loads POSIX Node native addons and exposes Node
158
198
  shared memory, mapping, unlink, and futex primitives.
159
199
  - `src/connections/bun.ts`: Bun FFI implementation for POSIX shared memory.
@@ -169,6 +209,10 @@ The core flow is:
169
209
  - `src/knitting_windows_shared_memory.cc`: Runtime-neutral Windows DLL exports
170
210
  for creating, opening, mapping, and closing named shared-memory objects from
171
211
  FFI runtimes.
212
+ - `prebuilds/*/*.node`: Tracked Node native-addon prebuilds for supported
213
+ platform/Node ABI combinations.
214
+ - `prebuilds/win32-x64/*.dll`: Tracked Windows FFI DLL prebuild used by Bun and
215
+ Deno shared-memory primitives on Windows.
172
216
 
173
217
  ## Permissions
174
218
 
@@ -190,6 +234,9 @@ The core flow is:
190
234
  - `bench/withload.ts`: Measures behavior under main-thread load.
191
235
  - `bench/call-growth.ts`: Measures call cost as payload size grows.
192
236
  - `bench/call-growth-batch.ts`: Batch-focused version of call-growth tests.
237
+ - `bench/startup.ts`: Measures `createPool` to first-response startup latency
238
+ across thread and process workers, with optional cross-runtime and named
239
+ shared-memory candidates.
193
240
  - `bench/tokio-mpsc-knitting.ts`: Batch latency benchmark for string, number,
194
241
  and Uint8Array echo tasks.
195
242
  - `bench/payload-sweep.ts`: Uint8Array payload-size sweep promoted from the old
@@ -219,6 +266,7 @@ The core flow is:
219
266
 
220
267
  ## Tests
221
268
 
269
+ - `test/_runner.ts`: Runtime-neutral test runner shim used by the test suite.
222
270
  - `test/abortSignal.test.ts`: Shared abort bitset behavior.
223
271
  - `test/api-cap.test.ts`: API limits such as maximum task id count.
224
272
  - `test/shared-buffer-io.test.ts`: Shared-buffer IO read/write behavior.
@@ -250,6 +298,9 @@ The core flow is:
250
298
  - `test/task-abort-context-api.test.ts`: Worker abort toolkit/context behavior.
251
299
  - `test/tx-queue.test.ts`: Host transmit queue behavior and late-result safety.
252
300
  - `test/type-inference.test.ts`: Public type inference guarantees.
301
+ - `test/utils.test.ts`: Public utility conversion helpers.
302
+ - `test/worker-bootstrap.test.ts`: Worker bootstrap hook behavior, shared-buffer
303
+ metadata revival, startup failure propagation, and inliner incompatibility.
253
304
  - `test/fixtures/*.ts`: Task modules used by tests.
254
305
  - `test/fixtures/probes/*.ts`: Probe programs for crash, permission, process,
255
306
  file-descriptor, and shared-memory-corruption safety cases.
@@ -259,12 +310,11 @@ The core flow is:
259
310
  - `build/Release/*.node`: Native addon output produced by
260
311
  `scripts/build-native-addons.ts`.
261
312
  - `out/`: Bundled output produced by `build.ts`.
313
+ - `dest/`: Scratch output used by the `build:node` package script.
314
+ - `knitting.js`, `knitting.d.ts`, `process-shared-buffer.js`,
315
+ `process-shared-buffer.d.ts`, and `src/**/*.js` / `src/**/*.d.ts`: npm
316
+ release build artifacts produced by `tsconfig.npm.json`.
262
317
  - `results/`: Benchmark output produced by `run.sh`.
318
+ - `log/`, `logs`, and `*.log`: Local runtime/log output.
263
319
  - `node_modules/`: Installed dependencies. Not part of the source map.
264
320
 
265
- ## Deleted Or Intentionally Absent
266
-
267
- - Browser-mode build/smoke files are no longer part of the project.
268
- - The old top-level scratch files `uwu.ts` and `examples.ts` are removed.
269
- - Python graph scripts under `graphs/` were removed; current benchmark output is
270
- kept in the TypeScript benchmark suite.