SEDA bus · TypeScript · 0.2.0
The odd one out: the shared pool is the event loop itself.
Node runs application code on one thread. seda-bus-ts adapts rather than pretends
otherwise: an InlineTransport for the I/O-bound stages that make up most Node work, and
an optional WorkerTransport — real OS threads — for the CPU-bound ones that
actually need them.
Worker pool (worker stages)worker-configured stages@resolvingarchitecture/ra-commonra-common's Envelope (rewired at 0.2.0)DynamicRoutingSlip — LIFOglobalInFlight < globalLimitNodeNext resolutionHow it implements the design
Everything async, no locks needed on the main thread.
Two transports sit behind a StageTransport interface. Inline
(the default) runs consumers as functions on the event loop — a stage's concurrency is
the number of drain turns interleaved while awaiting I/O, making the bus a structured
alternative to p-queue/bottleneck for the overwhelming majority of
Node work. Worker (the worker channel option) runs the stage
handler as a module — not a closure, since closures can't cross a thread boundary —
across a fixed pool of real Worker threads, with the envelope crossing by
structured clone. Pub/sub is forbidden on worker stages.
publish returns a Promise<boolean>; consumers may return a
promise; shutdown is async. Because JavaScript is single-threaded, the
inFlight/globalInFlight counters need no locks at all — the
scheduler is a plain while loop. Block back-pressure parks the
publisher in a waiters list and supports both a timeout and an AbortSignal to
abandon the wait.
Results
No CPU parallelism at all — and splitting channels still wins.
| Config | Sustained eps | 0.5× | 1.0× | 1.5× |
|---|---|---|---|---|
cap1 | 203,898 | 0.98 | 0.96 | 0.66 |
cap8 | 219,670 | 0.95 | 0.92 | 0.66 |
TypeScript's ratios at 0.5× and 1.0× are the best-tracking (or tied for best) in the whole comparison — a bounded, backpressured queue behaving exactly as designed under load at or below what it was just measured to sustain, even without OS-level parallelism to fall back on.
| Config | eps | vs. seq |
|---|---|---|
seq (1 producer) | 22,035 | — |
par (8 producers, 1 channel) | 14,798 | 0.67× |
chan (8 producers, 8 channels) | 129,225 | 5.86× |
Reading the numbers
The best ratio in the whole report — and also the smallest absolute number.
chan's 5.86× gain over seq is the best ratio in this
entire comparison, with zero real OS parallelism available — splitting work
across independent channels reduces per-operation coordination overhead even on one thread.
But read the ratio and the absolute number as answering different questions: TypeScript's
chan throughput is still the lowest of all eight ports/builds measured, because
the ratio is partly inflated by an already-degraded seq/par baseline
(both deep in multi-second backlog once producers aren't artificially throttled by envelope
construction cost).
par is a real 0.67× collapse, not the flat number an
earlier, construction-inclusive pass measured — eight concurrent async producer loops
sharing one channel genuinely cost more (promise/microtask scheduling, await
handoffs) than one loop does, once producers publish as fast as pre-built envelopes allow.
The full cross-language results, including the latency backlog discussion
Source
Read the code, or the rest of the family.
seda-bus-ts on GitHub
— source, its own DESIGN.md, and the node:test suite.