Skip to content

Knitting

A runtime library built from scratch just to make JavaScript a real multiprocess language.
0runtime dependencies
1unified api
2modes: threads or processes
3runtimes: Node.js, Deno, Bun
4safety layers, from guards to OS sandbox

The shape of the API

Export a function, create a pool, call it.

Start with plain exports, add task metadata only when you need timeouts or abort signals, then let using close the pool when the scope exits.

install.sh
npm install knitting
deno add --npm knitting
main.ts
import { createPool, isMain } from "knitting";
export const square = (value: number) => value * value;
export const greet = (name: string) => "hello " + name;
if (isMain) {
using pool = createPool({ threads: 2 })({ square, greet });
const [four, message] = await Promise.all([
pool.call.square(2),
pool.call.greet("knitting"),
]);
console.log({ four, message });
}

Benchmarks

Real Native Speed

Scheduling roundtrip538 nsNode, 53.8 us batch / 100
Zero-copy roundtrip816 nsNode, 81.6 us batch / 100
1 MiB complete copy in both directions313 usBun, 31.3 ms batch / 100

Measured against Tokio: same order of magnitude on scheduling, and ~17% faster on a 1 MiB round copy at batch scale (31.3 ms vs 37.7 ms / 100). Source: bench.

The missing middle

Not every hot path deserves a hostname.

Knitting is the missing middle: keep the function in your app, move where it runs.

01

Move the heat

Export the expensive function once. Call pool.call.name().

Define work
02

Control the worker

Runtime, permissions, bootstrap, process wrapper, timeouts, scheduling.

Creating pools
03

Direct memory access

Request and response live in shared memory, so task calls do not have to become little socket.

See the architecture

Knitting is not a replacement for distributed systems — it is for the moment before one, when your hot function just needs a better place to run. The full argument, and the roadmap, live in Why Knitting.

What’s in the box

Batteries you’d otherwise wire up by hand.

The nice part is calling workers like functions. The useful part is what comes with that boundary: payloads, deadlines, isolation, scheduling, and cleanup in one place.

Typed payloads

Primitives, typed arrays, Buffer, Envelope, and zero-copy ProcessSharedBuffer. Promise inputs resolve on the host before dispatch.

Payloads

Timeouts and aborts

Give a task a deadline, or cancel it cooperatively from either side of the boundary.

Defining tasks

Threads or processes

Fast runtime threads by default, or separate processes — even inside a bwrap sandbox or a container — when isolation matters.

Process workers

Real errors, real diagnostics

Errors return as real Errors with stack and cause chain; debug: true streams per-worker diagnostics — zero cost when off.

Creating pools

Scheduling control

Balance lanes with pluggable strategies, or add the host itself as an inline lane for tiny compute.

Inliner

Shared memory

ProcessSharedBuffer moves bytes between processes with no copy — the lower-level channel under process workers.

Shared memory

Four safety layers

Isolation is a dial, not a cliff.

Four layers stack from cheap guardrails to an OS sandbox. Trusted code stays on fast threads; each pool pays only for the boundary it needs.

1. In-process guards

Termination APIs — process.exit, Deno.exit, and friends — are neutralized before any task code loads. Workers can’t take the host down.

Architecture

2. Bootstrap hook

worker.bootstrap runs privileged setup before task imports: strip env vars, freeze globals, add your own guards.

Creating pools

3. Runtime permissions

Strict by default and enforced by the runtime itself — not a library check task code could bypass.

Permissions

4. Process + sandbox

The OS-enforced boundary: process workers behind bwrap, Docker, or systemd-run, with importTask keeping the code off the host entirely.

Process workers

Where it helps

Use it where coordination cost is the thing stopping you.

Good fits

  • CPU-heavy or bursty work: parsing, validation, crypto, compression, SSR.
  • Small-to-medium calls where normal worker overhead would erase the win.
  • Routes that need the main thread to stay responsive while expensive work runs elsewhere.
  • Workloads that benefit from explicit batching, balancing, timeouts, and abort-aware tasks.

Boundaries

  • Same-host multicore, not a distributed scheduler — add a network layer to scale across machines.
  • It replaces postMessage for task calls, not for pub/sub or streaming — reach for MessagePort there.
  • Tasks aren’t preempted mid-run: abortSignal cancels cooperatively, worker.hardTimeoutMs hard-stops runaway CPU.
  • For a harder boundary, run process workers in a bwrap or container sandbox, with importTask keeping untrusted code off the host.