Most apps do not need a new service the first time one function gets expensive.
They need a better place for that function to run.
A parser gets large. A validator gets complicated. A hash, render, or
compression step starts taking more of the event loop than it should. The rest
of the app is still fine. The problem is smaller than the architecture we often
reach for.
Knitting is built for that middle place: keep the code local, but move the work
off the main thread.
CPU pressure in a JavaScript app rarely spreads evenly. Real codebases often
have thousands of functions, but only a few of them create most of the cost.
That matters because it changes the size of the fix. You may not have an
application problem. You may have a function problem. If the heat is local, the
solution should be local too.
When the main thread starts paying for those functions, JavaScript developers
usually reach for one of three options.
Do nothing. It is simple, and for a while it works. But every
millisecond a hot function spends on the main thread is a millisecond the server
cannot spend on everything else. Cheap routes queue behind expensive ones.
Scaling out dilutes the pain, but it does not remove the hot path from the
event loop.
Workers. The shape is closer: move the work somewhere else on the
same machine. The rough part is everything around it. The runtime gives you
postMessage; the rest is yours: request IDs, routing, errors, promises,
lifecycle, payload choices. That plumbing is the original reason Knitting
exists.
Make it a service. Sometimes that is the right call. Separate ownership,
deploy cadence, and failure domains are all real reasons to cross the network.
But for one hot function, owned by the same team, in the same repo, the bill is
strange: a network hop, serialization, deployment, monitoring, and one more
thing to operate. The function did not ask for a hostname. It asked for
somewhere else to run.
The function stays in your repo, in your language, in your types — one
import away from its caller. What changes is where it runs and what it can
touch. You export it, hand it to a pool, and call it like the async function it
already was. Underneath, it runs on a real thread or an isolated process.
if (isMain) console.log(await pool.call.hello("World!"));
That is the whole boundary: one export, one pool, one call. hello still reads
like a plain function, but it no longer runs on the main thread.
So Knitting is a function-level execution boundary. Not a new service, and
not just a worker helper — a way to move the expensive part without moving
the whole app.
Compare what each option costs for the same few hot functions:
In-process
Hand-rolled workers
Microservice
Knitting
Main thread protected
no
yes
yes
yes
Code stays in the app
yes
mostly
no
yes
Call-site ergonomics
function call
protocol you wrote
HTTP client
function call
Transport cost
none
postMessage per call
network + JSON
shared-memory mailboxes
Isolation available
none
thread only
full, always-on
a dial, per pool
New deploy unit
no
no
yes
no
Two details make the fourth shape useful.
Transport cost matters. Workers can feel disappointing when reaching the
worker costs more than the work. Knitting uses shared-memory mailboxes instead
of the runtime’s message queue, so small and medium calls stay practical. The
Architecture page explains the mechanism, and the
benchmarks show where the shape wins and where it
does not.
Isolation should match the task. A microservice gives you process isolation
whether or not you need it. Knitting lets each pool pick its own boundary:
in-process guards, a bootstrap hook, runtime-native permissions, or a real
OS-sandboxed process. Trusted math can stay on cheap threads. An untrusted
plugin can run behind bwrap, and
importTask keeps its code off the
host entirely.
Knitting does not replace distributed systems. If you need cross-machine scale,
separate failure domains, or team-level ownership boundaries, you still need the
network. Knitting is for the moment before that, when the code belongs in the
app but the work no longer belongs on the main thread.
Knitting today is task-call oriented: one request in, one response out. The
transport underneath it is more general than that. Mailboxes, payload buffers,
and named mappings leave room for other shapes.
01Nearer term
Channels
Some same-host work is not a task call: progress, long-lived coordination,
producer/consumer flows. Today the honest answer is MessagePort.
A future channel API would keep that shape on the same shared-memory
transport, without pretending every conversation is request/response.
02Longer term
Cross-language, same-host
The mailbox protocol is not tied to JavaScript. Process workers already
open named mappings, which makes the boundary more about memory than a
specific runtime.
A later version could let JavaScript hand large payloads to another
runtime on the same machine without copying through JSON or
re-marshalling at every hop.
That is why the transport is built with more care than a worker pool strictly
needs. Until those APIs ship, though, judge Knitting on what it does now.