Skip to content

Real threads. Normal functions. Knitting, run it like a function.

A multi-thread framework for JavaScript with a function-call API.
Knitting
PostMessage
WebSockets
HTTP

IPC microbenchmark (Node.js, 50-message roundtrip, ~12 B JSON). Measures communication overhead, not end-to-end app throughput.

Parallelism in about 10 lines

Start by wrapping an existing function with task({ f }) or loading one with importTask(), then add the knobs you need.

import { createPool, isMain, task } from "@vixeny/knitting";
export const hello = task({
f: (name: string) => `hello ${name}`,
});
const { call, shutdown } = createPool({
threads: 2
})({ hello });
if (isMain) {
console.log(await call.hello("world"));
await shutdown();
}

See Installation, Defining tasks, and Creating pools for full options.

Move hot paths off the main thread

Move heavy work into workers and the main thread stays responsive under mixed traffic.

Hono + SSR + JWT + Zod benchmark

Throughput (requests/sec, higher is better)

  • /ping +82%
    before 2,998 after 5,471
  • /ssr +81%
    before 2,998 after 5,421
  • /jwt +87%
    before 2,916 after 5,454
Overall p99.9 tail latency 48% lower

Median and p99 also drop across routes in the same benchmark, with the JWT p99.9 tail falling by about half.

Try the full Hono + SSR + JWT + Zod example

Throughput

This is batched transport capacity, not full app throughput. We measured a 1 MB echo roundtrip (batch=64, host -> worker -> host), which is 2 MB transferred per call.

Here’s what we saw (GB/s, one-way equivalent):

RuntimeUint8Arraystring
Bun16.2111.86
Deno5.783.37
Node7.501.44

We report one-way equivalent (half the roundtrip) to keep units comparable. Treat these as communication-layer upper bounds: they do not mean your app will be 3.5x faster. Real gains depend on workload shape and bottlenecks (CPU contention, serialization cost, DB/network waits, etc.).

See the full benchmarks

Permission-aware workers

Worker pools can run with runtime permission policies instead of full access.

  • strict mode applies conservative defaults and blocks sensitive paths.
  • unsafe mode keeps things open for local development/debugging.
  • Node, Deno, and Bun each get runtime-specific behavior with one API.

Start with strict defaults, then open only what your tasks actually need.

See the permissions guide

Examples you can actually use

These are not toy snippets. They are working patterns you can lift into a real project, and the list keeps growing as new workflows come up.

  • Directoryexamples/
    • intro_examples.mdx
    • Directorydata_transforms/
      • intro_data_transforms.mdx
      • Directoryrendering_output/
        • React_ssr.mdx
        • Hono_server.mdx
      • Directoryvalidation/
        • Schema_validate.mdx
        • Jwt_revalidation.mdx
    • Directorymaths/
      • Big_prime.mdx
      • Monte_pi.mdx
      • Physics_loop.mdx

Start with Browse examples. Guides and benchmarks live in the sidebar, and the examples are ready to copy.

Shared-memory request/response mailboxes keep the control path cheap; payload buffers and batching keep the data path fast.

The core idea is simple: instead of routing every task through the runtime’s message queue, Knitting coordinates through shared memory. That keeps task calls cheap and predictable.

Control path: shared mailboxes

There are two mailboxes: one for requests and one for responses. Think of it as a full-duplex pipe in shared memory.

  • A call claims a slot, writes a small header, and wakes up a worker.
  • The worker does the work, writes the result back, and wakes up the host.
  • Slot ownership uses bitsets, so it’s all lock-free.

Data path: payload buffers

When values are too large for the header, they go into reusable payload buffers.

  • Each worker gets two SharedArrayBuffers: one for requests and one for returns.
  • You control the sizes (payload.payloadInitialBytes, payload.payloadMaxByteLength), so you can trade memory for speed.
  • Full details: Supported payloads.

Worker runtime: the spin-park-wake loop

Each worker is a real OS thread (worker_threads on Node, Worker on Deno/Bun).

On startup, each worker:

  1. Loads tasks — imports task modules from spawn-time URLs and builds a task ID -> function table.
  2. Signals ready — writes its ready flag in shared memory.
  3. Runs the loop — per iteration:
    • Enqueue — reads new request slots.
    • Service — executes pending tasks (sync or async) in batches up to 32.
    • Write-back — flushes completed results so host promises can resolve.
  4. Idles efficiently — spins for spinMicroseconds; if still idle, runs global.gc(), then parks with Atomics.wait until new work arrives.

Async tasks are awaited inside the worker before write-back, so only settled results cross the thread boundary.

Want to understand the cost model and when to batch? That’s all in the Performance model.

Why use this instead of the usual worker options?

Section titled “Why use this instead of the usual worker options?”

The goal is not just “workers, but faster.” It is making task execution cheap enough that small, frequent calls are worth offloading.

KnittingpostMessage / MessagePortchild_process / cluster
Primary goalLow-overhead task callsGeneral messagingIsolation + scaling out
Typical APIWrap functions, then call.*() promisesCustom protocolCustom protocol
Coordination pathShared memory + wakeupsMessage queueOS IPC
Code changesUsually wrap exported functions with task()Build handlers and routingSplit process boundaries and protocols
Best fitHigh-frequency CPU workEvent-style messagesMulti-process services

The benchmarks show what this difference looks like in practice: Benchmarks overview.

When Knitting Helps (and When It Doesn’t)

Section titled “When Knitting Helps (and When It Doesn’t)”

Good fit

  • CPU-heavy or bursty work (SSR, parsing, validation, crypto, compression).
  • Lots of small-to-medium calls where IPC overhead adds up.
  • You care about tail latency and don’t want one hot request blocking everything.
  • You can batch calls to squeeze out more throughput.

Not the right tool

  • Mostly I/O-bound work (waiting on the network or a database).
  • Calls are rare, or each job is so big that coordination cost doesn’t matter.
  • You need a general-purpose message bus — use postMessage / MessagePort for that.
  • You need to run across machines (Knitting is local, in-process only).

Here’s how Knitting behaves so you’re not guessing:

  • Async is deterministic: promises resolve on the host before dispatch, and async results are awaited in the worker before returning. No half-resolved surprises on either side.
  • Errors stay contained: if a task throws or rejects, that single call fails — the worker keeps going for the next one. One bad call doesn’t take down the pool.
  • No busy-waiting by default: idle workers spin briefly, trigger a GC if no work comes (so idle time is productive), then park until there’s work. The host dispatcher backs off too. Your CPU isn’t burning cycles on nothing.
  • Fair by default: round-robin lane selection keeps things balanced. But Knitting won’t preempt a long-running task — if one call takes forever, that lane is busy until it’s done.
  • Functions aren’t payloads: you can’t send a function to a worker. Knitting catches that early and fails fast before dispatch.
  • Works on Node.js, Deno, and Bun.
  • Edge runtimes aren’t targeted right now.
  • Watch out for remote imports and href overrides — they widen your trust boundary. Avoid in production unless you know what you’re doing.

Worker APIs are great for message passing. Knitting is for the cases where you want a function-call API and lower coordination overhead.

Instead of building message protocols, you write function calls. Instead of paying for runtime IPC on every hop, you coordinate through shared memory. Same API on Node, Deno, and Bun.

  • Call functions instead of routing messages.
  • Less IPC overhead, especially for high-frequency workloads.
  • One programming model across all three runtimes.
  • You control batching, inlining, and shutdown when you need to.

Knitting does one thing: local multi-thread execution with shared-memory IPC. It’s not a distributed scheduler or a cluster manager.