knitting 0.1.46 → 0.1.50

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 (52) hide show
  1. package/README.md +274 -77
  2. package/map.md +9 -3
  3. package/package.json +2 -1
  4. package/prebuilds/darwin-arm64-node-127/knitting_shared_memory.node +0 -0
  5. package/prebuilds/darwin-arm64-node-137/knitting_shared_memory.node +0 -0
  6. package/prebuilds/darwin-x64-node-127/knitting_shared_memory.node +0 -0
  7. package/prebuilds/darwin-x64-node-137/knitting_shared_memory.node +0 -0
  8. package/prebuilds/linux-x64-node-127/knitting_shared_memory.node +0 -0
  9. package/prebuilds/linux-x64-node-137/knitting_shared_memory.node +0 -0
  10. package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +0 -0
  11. package/prebuilds/win32-x64-node-127/knitting_shared_memory.node +0 -0
  12. package/prebuilds/win32-x64-node-127/knitting_shm.node +0 -0
  13. package/prebuilds/win32-x64-node-137/knitting_shared_memory.node +0 -0
  14. package/prebuilds/win32-x64-node-137/knitting_shm.node +0 -0
  15. package/scripts/build-native-addons.ts +31 -5
  16. package/src/api.js +37 -13
  17. package/src/common/task-source.d.ts +1 -0
  18. package/src/common/task-source.js +1 -0
  19. package/src/connections/bun.d.ts +2 -0
  20. package/src/connections/bun.js +64 -9
  21. package/src/connections/deno.d.ts +2 -0
  22. package/src/connections/deno.js +64 -9
  23. package/src/connections/file-descriptor.d.ts +2 -0
  24. package/src/connections/file-descriptor.js +24 -2
  25. package/src/connections/node.d.ts +2 -1
  26. package/src/connections/node.js +17 -14
  27. package/src/connections/posix.d.ts +1 -0
  28. package/src/connections/posix.js +6 -0
  29. package/src/connections/process-shared-buffer.d.ts +3 -1
  30. package/src/connections/process-shared-buffer.js +17 -13
  31. package/src/connections/types.d.ts +2 -0
  32. package/src/connections/windows.d.ts +28 -0
  33. package/src/connections/windows.js +224 -0
  34. package/src/knitting_shared_memory.cc +310 -24
  35. package/src/knitting_windows_shared_memory.cc +156 -0
  36. package/src/memory/payloadCodec.js +1 -0
  37. package/src/runtime/pool.d.ts +1 -13
  38. package/src/runtime/pool.js +10 -542
  39. package/src/runtime/process-worker.d.ts +92 -0
  40. package/src/runtime/process-worker.js +670 -0
  41. package/src/runtime/tx-queue.d.ts +1 -1
  42. package/src/runtime/tx-queue.js +3 -1
  43. package/src/shared/abortSignal.d.ts +1 -1
  44. package/src/shared/abortSignal.js +10 -2
  45. package/src/types.d.ts +49 -3
  46. package/src/worker/bootstrap.d.ts +5 -0
  47. package/src/worker/bootstrap.js +78 -0
  48. package/src/worker/composable-runners.js +0 -8
  49. package/src/worker/loop.js +19 -154
  50. package/src/worker/process-worker-bootstrap.d.ts +8 -0
  51. package/src/worker/process-worker-bootstrap.js +159 -0
  52. package/src/worker/timers.js +19 -5
package/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  [![JSR Version](https://jsr.io/badges/@vixeny/knitting)](https://jsr.io/@vixeny/knitting)
4
4
  [![JSR Score](https://jsr.io/badges/@vixeny/knitting/score)](https://jsr.io/@vixeny/knitting)
5
+ [![npm Version](https://img.shields.io/npm/v/knitting?logo=npm&logoColor=white)](https://www.npmjs.com/package/knitting)
5
6
  [![Tests](https://github.com/mimiMonads/knitting/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/mimiMonads/knitting/actions/workflows/test.yml)
6
7
  [![Coverage Workflow](https://github.com/mimiMonads/knitting/actions/workflows/coverage.yml/badge.svg?branch=main)](https://github.com/mimiMonads/knitting/actions/workflows/coverage.yml)
7
8
  [![Coverage](https://img.shields.io/badge/coverage-92.10%25-brightgreen)](https://github.com/mimiMonads/knitting/actions/workflows/coverage.yml)
@@ -10,45 +11,37 @@
10
11
  [![Deno](https://img.shields.io/badge/deno-2%2B-000000?logo=deno&logoColor=white)](https://deno.com/)
11
12
  [![Bun](https://img.shields.io/badge/bun-1%2B-f472b6?logo=bun&logoColor=white)](https://bun.sh/)
12
13
 
13
- Knitting is a worker-pool over shared-memory IPC runtime for Node.js,
14
- Deno, and Bun. Which mission is make javascript a real multicore language,
15
- Thanks to its memory design, it can be from 5x to 25x faster than using `postMessages`
16
- bypassing OS sockect comunnication entielly with a novel protocol written from scratch.
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.
17
15
 
18
- Use it for the parts of your program that should run somewhere else: CPU-heavy to small
19
- work, runtime-isolated jobs, long-running tools, or anything that needs to move
20
- speed and type felixbility.
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.
21
17
 
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
19
 
23
- You define a task once, spin up a pool, and call it like a normal async
24
- function:
20
+ You define a task once, spin up a pool, and call it like a normal async function:
25
21
 
26
22
  ```ts
23
+
27
24
  const result = await pool.call.resizeImage(file);
25
+
28
26
  ```
29
27
 
30
- Under the hood, Knitting schedules work across worker threads or separate
31
- processes (depending on runtime and your settings), keeps arguments typed, and
32
- uses shared memory for transport.
28
+ 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
+
33
34
 
34
- ## So
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.
35
36
 
36
- - Call worker code like `pool.call.myTask(arg)`.
37
- - Keep the resolved types end-to-end
38
- - Choose `threads` for speed or `processes` for stronger isolation.
39
- - Move supported payloads via shared memory (great for binary data).
40
- - Run on Node.js, Deno, or Bun.
41
37
 
42
38
  ## Why use it?
43
39
 
44
- - Easy to use: Have a multi-thread enviroment or process with few lines of code.
45
- - Great type support: pass primitives, JSON, Promise of these and special types (typed arrays, Node
46
- `Buffer`, `Envelope`, and `ProcessSharedBuffer`).
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`).
47
42
  - Runtime flexibility: the same API across Node.js, Deno, and Bun.
48
- - Worker choices: use threads for fast pools, or processes for stronger
49
- isolation.
50
- - All out-of-the-box expierince: strict-by-default permissions, payload-size limits, task
51
- timeouts, abort-aware tasks, and worker hard timeouts.
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.
52
45
 
53
46
  ## Requirements
54
47
 
@@ -56,27 +49,18 @@ uses shared memory for transport.
56
49
  - Deno 2+
57
50
  - Bun 1+
58
51
 
59
- Process workers and `ProcessSharedBuffer` use the native shared-memory layer.
60
- On Linux and macOS, build it before running process/shared-memory tests.
61
- If you're only using thread workers, you can typically ignore this and use
62
- Knitting without building native addons.
63
-
64
52
  ## Install
65
53
 
66
- ```bash
67
- npm install knitting
68
- ```
69
-
70
- Via JSR's npm compatibility:
54
+ From npm:
71
55
 
72
56
  ```bash
73
- jsr add --npm @vixeny/knitting
57
+ npm install knitting
74
58
  ```
75
59
 
76
60
  For Deno projects:
77
61
 
78
62
  ```bash
79
- deno add jsr:@vixeny/knitting
63
+ deno add --npm jsr:@vixeny/knitting
80
64
  ```
81
65
 
82
66
  ## Quick Start
@@ -301,12 +285,37 @@ Common options you might tweak:
301
285
  | `payload` | Shared payload-buffer settings: `mode`, `payloadInitialBytes`, `payloadMaxByteLength`, and `maxPayloadBytes`. |
302
286
  | `abortSignalCapacity` | Number of shared abort slots available to abort-aware calls. |
303
287
  | `worker.resolveAfterFinishingAll` | Let submitted calls finish before shutdown resolves. |
288
+ | `worker.bootstrap` | Privileged async hook imported and awaited before task modules load. |
304
289
  | `worker.hardTimeoutMs` | Force pool shutdown when a task exceeds this many milliseconds. |
305
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. |
306
292
  | `permission` | Runtime permission policy for workers. |
307
293
  | `debug` | Enable extra diagnostics. |
308
294
  | `source` | Worker source override for advanced runtimes. |
309
295
 
296
+ ### Worker bootstrap
297
+
298
+ Use `worker.bootstrap` when a worker needs privileged setup before task modules
299
+ are imported. The bootstrap module is imported once per worker, and its selected
300
+ export is awaited before Knitting imports task definitions.
301
+
302
+ ```ts
303
+ const pool = createPool({
304
+ worker: {
305
+ bootstrap: {
306
+ href: "./worker-bootstrap.ts",
307
+ name: "setup",
308
+ data: { env: "worker-only" },
309
+ },
310
+ },
311
+ })({ add });
312
+ ```
313
+
314
+ Bootstrap code runs with worker startup privileges, so keep it trusted. It is a
315
+ good place to remove environment variables, install runtime guards, open shared
316
+ memory metadata, or prepare globals that task modules should see at import time.
317
+ Bootstrap is worker-only and cannot be combined with the inline host lane.
318
+
310
319
  ## Worker Runtimes
311
320
 
312
321
  By default, workers use runtime-local threads where possible (the lowest
@@ -326,14 +335,15 @@ const pool = createPool({
326
335
  threads: 2,
327
336
  worker: {
328
337
  runtime: "process",
329
- processRuntime: "node",
338
+ processRuntime: "deno",
330
339
  },
331
340
  })({ add });
332
341
  ```
333
342
 
334
- `processRuntime` can be `"node"`, `"deno"`, or `"bun"`. You can also provide a
335
- `processCommandPrefix` when workers need to be launched through a wrapper such
336
- as a package manager, container command, or runtime shim.
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.
337
347
 
338
348
  That prefix is also useful for sandbox and resource-control tools. The one
339
349
  important detail is that process workers receive their shared-memory handle on
@@ -341,6 +351,68 @@ stdin, which is file descriptor 0. Wrappers that leave stdin alone usually work;
341
351
  wrappers that replace, close, or proxy stdin without passing the fd through will
342
352
  stop the worker from booting.
343
353
 
354
+ For wrappers that cannot preserve fd 0, use named process-worker memory instead.
355
+ The worker process must share the same OS IPC namespace as the host so it can
356
+ reopen the named mapping.
357
+
358
+ ```ts
359
+ const pool = createPool({
360
+ threads: 2,
361
+ worker: {
362
+ runtime: "process",
363
+ processRuntime: "node",
364
+ processSharedMemory: {
365
+ mode: "named",
366
+ namePrefix: "knit_worker",
367
+ },
368
+ processCommandPrefix: [
369
+ // The prefix runs before Knitting appends:
370
+ // node --no-warnings --experimental-transform-types <worker-file>
371
+ "docker",
372
+ "run",
373
+ // Remove the container when the worker exits.
374
+ "--rm",
375
+ // Required for named POSIX shared memory across host/container.
376
+ "--ipc=host",
377
+ // The worker imports the same files as the host, at the same path.
378
+ "-v",
379
+ `${process.cwd()}:${process.cwd()}`,
380
+ "-w",
381
+ process.cwd(),
382
+ // Forward Knitting's process-worker boot metadata into the container.
383
+ "-e",
384
+ "KNITTING_PROCESS_WORKER",
385
+ "-e",
386
+ "KNITTING_PROCESS_WORKER_BOOT",
387
+ "knitting-node-worker",
388
+ ],
389
+ },
390
+ })({ add });
391
+ ```
392
+
393
+ ### Windows process workers
394
+
395
+ On Windows, Knitting automatically uses named shared memory for the
396
+ process-worker control channel. You do not need to set
397
+ `processSharedMemory: "named"` yourself — the runtime detects Windows and
398
+ forces it.
399
+
400
+ ```ts
401
+ // Works on Windows without extra options.
402
+ const pool = createPool({
403
+ threads: 2,
404
+ worker: {
405
+ runtime: "process",
406
+ processRuntime: "node",
407
+ },
408
+ })({ add });
409
+ ```
410
+
411
+ If you also pass `ProcessSharedBuffer` payloads to Docker workers running on
412
+ Windows, create the payload buffer with `mode: "create"` and a name, and add
413
+ `--ipc=host` to the Docker prefix — the pool-level control channel is already
414
+ named, but the payload buffer needs its own name so the container can reopen it.
415
+
344
416
  When the goal is isolation, define the worker code with `importTask()` instead
345
417
  of importing the task function directly into the host. That keeps the code you
346
418
  want to isolate out of the host process; only the worker imports and runs it.
@@ -436,6 +508,41 @@ shouldn't) cross the boundary:
436
508
  - Objects with behavior that depends on prototypes, getters, setters, or hidden
437
509
  process-local state.
438
510
 
511
+ ### Envelope
512
+
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.
516
+
517
+ ```ts
518
+ import { Envelope, createPool, isMain, task } from "knitting";
519
+
520
+ export const processImage = task<
521
+ Envelope<{ format: string }>,
522
+ Envelope<{ width: number; height: number }>
523
+ >({
524
+ f: (envelope) => {
525
+ const pixels = new Uint8Array(envelope.payload);
526
+ // ... process pixels
527
+ return new Envelope({ width: 800, height: 600 }, pixels.buffer);
528
+ },
529
+ });
530
+
531
+ if (isMain) {
532
+ const pool = createPool({ threads: 2 })({ processImage });
533
+
534
+ try {
535
+ const buffer = new ArrayBuffer(1024);
536
+ const result = await pool.call.processImage(
537
+ new Envelope({ format: "png" }, buffer),
538
+ );
539
+ console.log(result.header); // { width: 800, height: 600 }
540
+ } finally {
541
+ await pool.shutdown();
542
+ }
543
+ }
544
+ ```
545
+
439
546
  If a payload is large, set `payload.maxPayloadBytes` deliberately and prefer
440
547
  binary/shared-memory shapes over deeply nested objects.
441
548
 
@@ -446,7 +553,10 @@ memory. Use it when two workers or processes need to see the same bytes without
446
553
  copying the whole payload for every call.
447
554
 
448
555
  ```ts
449
- import { ProcessSharedBuffer } from "knitting/process-shared-buffer";
556
+ import {
557
+ getDefaultProcessSharedBufferPrimitives,
558
+ ProcessSharedBuffer,
559
+ } from "knitting/process-shared-buffer";
450
560
  import { createPool, isMain, task } from "knitting";
451
561
 
452
562
  export const readFirstCell = task<ProcessSharedBuffer, number>({
@@ -455,13 +565,14 @@ export const readFirstCell = task<ProcessSharedBuffer, number>({
455
565
 
456
566
  if (isMain) {
457
567
  const pool = createPool({ threads: 1 })({ readFirstCell });
458
- const shared = ProcessSharedBuffer.create(64);
568
+ const primitives = getDefaultProcessSharedBufferPrimitives();
569
+ const shared = ProcessSharedBuffer.create(64, primitives);
459
570
 
460
571
  try {
461
572
  Atomics.store(shared.view(Int32Array), 0, 42);
462
573
  console.log(await pool.call.readFirstCell(shared));
463
574
  } finally {
464
- shared.close();
575
+ shared.descriptor.mapping?.close?.();
465
576
  await pool.shutdown();
466
577
  }
467
578
  }
@@ -482,19 +593,24 @@ programs do not accidentally inherit them.
482
593
 
483
594
  ### Named channels for independent processes
484
595
 
485
- Sometimes you do want two unrelated processes to rendezvous on purpose. Use a
486
- named channel for that.
596
+ Use a named channel when two processes need to find the same shared memory
597
+ without inheriting an fd from each other. One process creates the channel by
598
+ name; the other opens that same name.
487
599
 
488
600
  ```ts
489
- import { ProcessSharedBuffer } from "knitting/process-shared-buffer";
601
+ import {
602
+ getDefaultProcessSharedBufferPrimitives,
603
+ ProcessSharedBuffer,
604
+ } from "knitting/process-shared-buffer";
490
605
 
491
606
  const name = "knitting-demo-channel";
607
+ const primitives = getDefaultProcessSharedBufferPrimitives();
492
608
 
493
609
  const owner = ProcessSharedBuffer.create({
494
610
  name,
495
611
  size: 64,
496
612
  mode: "create",
497
- });
613
+ }, primitives);
498
614
 
499
615
  try {
500
616
  Atomics.store(owner.view(Int32Array), 0, 7);
@@ -503,46 +619,121 @@ try {
503
619
  name,
504
620
  size: 64,
505
621
  mode: "open",
506
- });
622
+ }, primitives);
507
623
 
508
624
  try {
509
625
  console.log(Atomics.load(peer.view(Int32Array), 0));
510
626
  } finally {
511
- peer.close();
627
+ peer.descriptor.mapping?.close?.();
512
628
  }
513
629
  } finally {
514
- owner.close();
515
- ProcessSharedBuffer.unlink(name);
630
+ owner.descriptor.mapping?.close?.();
631
+ primitives.unlinkSharedMemory?.(name);
516
632
  }
517
633
  ```
518
634
 
519
- Use `"create"` for the process that owns the channel and `"open"` for peers.
520
- Treat the channel name like a capability: make it unique, do not accept it from
521
- untrusted input without validation, and clean it up when the channel is no
522
- longer needed. On POSIX runtimes `unlink` removes the name. On platforms where
523
- named mappings are lifetime-managed by the OS, closing the last handle is the
524
- important cleanup step.
635
+ Use `"create"` on the owner side and `"open"` on the peer side. The name is the
636
+ thing that grants access, so generate a hard-to-guess name and keep it private.
637
+ When you are done, close the mappings and unlink the name where the runtime
638
+ supports it.
639
+
640
+ ### Sending `ProcessSharedBuffer` to Docker workers
641
+
642
+ Docker process workers can receive a `ProcessSharedBuffer`, but it needs to be
643
+ named. The default anonymous form is fd-backed and private to the parent-child
644
+ process path; Docker does not inherit that fd in a way the worker can reopen.
645
+
646
+ Use a named buffer for the payload and named process-worker memory for the pool:
647
+
648
+ ```ts
649
+ import { createPool, isMain, task } from "knitting";
650
+ import {
651
+ getDefaultProcessSharedBufferPrimitives,
652
+ ProcessSharedBuffer,
653
+ } from "knitting/process-shared-buffer";
654
+
655
+ export const readCounter = task<ProcessSharedBuffer, number>({
656
+ f: (shared) => Atomics.load(shared.view(Int32Array), 0),
657
+ });
658
+
659
+ if (isMain) {
660
+ const cwd = process.cwd();
661
+ const name = `knitting-docker-counter-${process.pid}`;
662
+ const primitives = getDefaultProcessSharedBufferPrimitives();
663
+ const shared = ProcessSharedBuffer.create({
664
+ mode: "create",
665
+ name,
666
+ size: 64,
667
+ }, primitives);
668
+
669
+ const pool = createPool({
670
+ threads: 1,
671
+ worker: {
672
+ runtime: "process",
673
+ processRuntime: "node",
674
+ processSharedMemory: "named",
675
+ processCommandPrefix: [
676
+ // Knitting appends the actual Node worker command after this prefix.
677
+ "docker",
678
+ "run",
679
+ // Named shared memory needs a shared IPC namespace.
680
+ "--ipc=host",
681
+ // Mount the project so the container can import the worker module.
682
+ "-v",
683
+ `${cwd}:${cwd}`,
684
+ "-w",
685
+ cwd,
686
+ // Pass Knitting's boot payload through Docker.
687
+ "-e",
688
+ "KNITTING_PROCESS_WORKER",
689
+ "-e",
690
+ "KNITTING_PROCESS_WORKER_BOOT",
691
+ "node:24-trixie-slim",
692
+ ],
693
+ },
694
+ permission: "unsafe",
695
+ })({ readCounter });
696
+
697
+ try {
698
+ Atomics.store(shared.view(Int32Array), 0, 42);
699
+ console.log(await pool.call.readCounter(shared));
700
+ } finally {
701
+ await pool.shutdown();
702
+ shared.descriptor.mapping?.close?.();
703
+ primitives.unlinkSharedMemory?.(name);
704
+ }
705
+ }
706
+ ```
707
+
708
+ There are three moving parts:
709
+
710
+ - `processSharedMemory: "named"` lets the Docker worker find Knitting's control
711
+ channel.
712
+ - `ProcessSharedBuffer.create({ mode: "create", name, size })` makes the
713
+ payload buffer reopenable by name.
714
+ - `--ipc=host` lets the container see the same POSIX shared-memory namespace.
715
+
716
+ This is same-host communication. It is fast because both sides map the same
717
+ bytes, but it is not a network transport and it deliberately shares IPC with the
718
+ container. Use names like capabilities: generate them, keep them private, and
719
+ unlink them when the shared memory is no longer needed.
525
720
 
526
721
  ### Current support
527
722
 
528
- Thread workers are the broadest path: they do not need native prebuilds or FFI.
529
- Process workers and `ProcessSharedBuffer` both use OS-backed shared memory, so
530
- their support follows the native backend for each runtime.
531
-
532
- For now, Windows support means thread workers only. Process workers and
533
- `ProcessSharedBuffer` are supported on POSIX targets: Linux and macOS.
534
-
535
- | Runtime and target | Thread workers | Process workers | `ProcessSharedBuffer` | Native path |
536
- | --- | --- | --- | --- | --- |
537
- | Node.js 22 / 24 on Linux x64 | Supported | Supported | Supported | Shipped Node `.node` prebuilds. |
538
- | Node.js 22 / 24 on macOS x64 | Supported | Supported | Supported | Shipped Node `.node` prebuilds. |
539
- | Node.js 22 / 24 on macOS arm64 | Supported | Supported | Supported | Shipped Node `.node` prebuilds. |
540
- | Node.js 22 / 24 on Windows x64 | Supported | Not supported | Not supported | Native shared memory is POSIX-only. |
541
- | Other POSIX Node.js ABI or arch | Supported | Local native build needed | Local native build needed | Run `bun run build:native` before using native shared memory. |
542
- | Deno 2+ on Linux/macOS, runtime-supported arch | Supported | Supported | Supported | Uses Deno FFI into libc; allow FFI permission when permissions are enabled. |
543
- | Deno 2+ on Windows | Supported | Not supported | Not supported | Current Deno backend is POSIX-only. |
544
- | Bun 1+ on Linux/macOS, runtime-supported arch | Supported | Supported | Supported | Uses Bun FFI into libc. |
545
- | Bun 1+ on Windows | Supported | Not supported | Not supported | Current Bun backend is POSIX-only. |
723
+ Knitting supports Node.js 22+, Deno 2+, and Bun 1+ on Linux, macOS, and
724
+ Windows.
725
+
726
+ Thread workers work without native pieces. Process workers and
727
+ `ProcessSharedBuffer` use the platform's shared-memory APIs. Release packages
728
+ include the native prebuilds needed for the supported Node targets and Windows
729
+ FFI path; if you are developing locally on a new Node ABI or architecture, run:
730
+
731
+ ```bash
732
+ bun run build:native
733
+ ```
734
+
735
+ For Deno projects with permissions enabled, allow FFI when using process
736
+ workers or `ProcessSharedBuffer`.
546
737
 
547
738
  ## Runtime Safety
548
739
 
@@ -604,7 +795,7 @@ Build the package:
604
795
  bun run build
605
796
  ```
606
797
 
607
- Build the native shared-memory addon on Linux or macOS:
798
+ Build the native shared-memory addon/prebuild for the current platform:
608
799
 
609
800
  ```bash
610
801
  bun run build:native
@@ -625,6 +816,12 @@ Emit JSON benchmark results:
625
816
  ./run.sh --json
626
817
  ```
627
818
 
819
+ Compare inherited-fd and named-shared-memory process-worker startup:
820
+
821
+ ```bash
822
+ node --no-warnings --experimental-transform-types bench/startup.ts --named-process-shm
823
+ ```
824
+
628
825
  For a file-by-file orientation, see [map.md](./map.md).
629
826
 
630
827
  ## License
package/map.md CHANGED
@@ -33,8 +33,9 @@ The core flow is:
33
33
 
34
34
  - `build.ts`: Bundles `knitting.ts` to `out/` with Bun for a Node ESM target.
35
35
  - `scripts/build-native-addons.ts`: Compiles the native Node addons into
36
- `build/Release/` on Linux and macOS. It finds Node headers/libs, splits user
37
- flags, and builds the shared-memory and futex addons.
36
+ `build/Release/` on Linux, macOS, and Windows. It finds Node headers/libs,
37
+ splits user flags, builds the shared-memory and futex addons, and emits the
38
+ Windows FFI DLL used by Deno and Bun.
38
39
  - `run.sh`: Runs every top-level benchmark in `bench/` across Node, Deno, and
39
40
  Bun. `--json` writes JSON result files.
40
41
 
@@ -159,10 +160,15 @@ The core flow is:
159
160
  - `src/connections/deno.ts`: Deno FFI implementation for POSIX shared memory.
160
161
  - `src/connections/posix.ts`: POSIX constants, shared-memory naming, libc path
161
162
  detection, and close-on-exec helpers.
163
+ - `src/connections/windows.ts`: Windows FFI loader and named file-mapping
164
+ primitives used by Deno and Bun.
162
165
  - `src/knitting_shared_memory.cc`: Native Node addon for shared-memory create,
163
- map, unlink, and descriptor operations.
166
+ map, unlink, and descriptor operations, including Windows named mappings.
164
167
  - `src/knitting_shm.cc`: Native Node addon for futex/wait helpers used by parked
165
168
  workers.
169
+ - `src/knitting_windows_shared_memory.cc`: Runtime-neutral Windows DLL exports
170
+ for creating, opening, mapping, and closing named shared-memory objects from
171
+ FFI runtimes.
166
172
 
167
173
  ## Permissions
168
174
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knitting",
3
- "version": "0.1.46",
3
+ "version": "0.1.50",
4
4
  "description": "Shared-memory IPC runtime for Node.js, Deno, and Bun.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -30,6 +30,7 @@
30
30
  "map.md",
31
31
  "LICENSE",
32
32
  "prebuilds/**/*.node",
33
+ "prebuilds/**/*.dll",
33
34
  "scripts/build-native-addons.ts",
34
35
  "src/**/*.cc",
35
36
  "src/**/*.d.ts",
@@ -70,11 +70,6 @@ const nodeInfo = JSON.parse(runCapture(nodeBinary, [
70
70
  "JSON.stringify({arch:process.arch,execPath:process.execPath,modules:process.versions.modules,nodedir:process.config.variables.nodedir||null,platform:process.platform,version:process.versions.node})",
71
71
  ])) as NodeInfo;
72
72
  const isWindows = nodeInfo.platform === "win32";
73
- if (isWindows) {
74
- throw new Error(
75
- "Knitting native shared-memory addons are supported on Linux and macOS only",
76
- );
77
- }
78
73
  const cacheRoot = join(
79
74
  resolve(
80
75
  Bun.env.KNITTING_NODE_CACHE_DIR ??
@@ -238,11 +233,25 @@ const addons = [
238
233
  output: "build/Release/knitting_shm.node",
239
234
  },
240
235
  ];
236
+ const ffiLibraries = isWindows
237
+ ? [
238
+ {
239
+ name: "knitting_windows_shared_memory",
240
+ source: "src/knitting_windows_shared_memory.cc",
241
+ output: "build/Release/knitting_windows_shared_memory.dll",
242
+ },
243
+ ]
244
+ : [];
241
245
  const prebuildDir = join(
242
246
  root,
243
247
  "prebuilds",
244
248
  `${nodeInfo.platform}-${nodeInfo.arch}-node-${nodeInfo.modules}`,
245
249
  );
250
+ const ffiPrebuildDir = join(
251
+ root,
252
+ "prebuilds",
253
+ `${nodeInfo.platform}-${nodeInfo.arch}`,
254
+ );
246
255
 
247
256
  console.log(`Using Node: ${nodeInfo.execPath}`);
248
257
  console.log(`Using Node module ABI: ${nodeInfo.modules}`);
@@ -282,6 +291,23 @@ for (const addon of addons) {
282
291
  copiedPrebuilds.push(prebuildPath);
283
292
  }
284
293
 
294
+ for (const library of ffiLibraries) {
295
+ const outputPath = join(root, library.output);
296
+ run(cxx, [
297
+ ...compileFlags,
298
+ join(root, library.source),
299
+ "/link",
300
+ `/OUT:${outputPath}`,
301
+ ...extraLdFlags,
302
+ ]);
303
+ builtAddons.push(library.output);
304
+
305
+ mkdirSync(ffiPrebuildDir, { recursive: true });
306
+ const prebuildPath = join(ffiPrebuildDir, `${library.name}.dll`);
307
+ copyFileSync(outputPath, prebuildPath);
308
+ copiedPrebuilds.push(prebuildPath);
309
+ }
310
+
285
311
  console.log(
286
312
  `Built ${builtAddons.length} native addon${
287
313
  builtAddons.length === 1 ? "" : "s"