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
The event loop (inline) / a Worker pool (worker stages)
True stage parallelism
Only for worker-configured stages
Dependencies
@resolvingarchitecture/ra-common
Envelope
ra-common's Envelope (rewired at 0.2.0)
Routing slip
DynamicRoutingSlip — LIFO
Concurrency
Per-stage and bus-wide caps — globalInFlight < globalLimit
Guaranteed delivery
No — in-memory only
Batch size
32 envelopes per drain
Module system
ESM-only, NodeNext resolution
Race/sanitizer verified
Not run

How 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.

Capacity curve — sustained throughput under real, bounded back-pressure
ConfigSustained eps0.5×1.0×1.5×
cap1203,8980.980.960.66
cap8219,6700.950.920.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.

Firehose throughput (envelopes/sec, unthrottled — diagnostic, not capacity planning)
Configepsvs. seq
seq (1 producer)22,035
par (8 producers, 1 channel)14,7980.67×
chan (8 producers, 8 channels)129,2255.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.