PATTERN Cited by 1 source
Record pipe links, resolve at sink¶
Pattern¶
In a streaming API where pipeThrough returns a new
stream and pipeTo terminates the chain at a sink,
don't start piping when pipeThrough is called.
Instead, record the upstream-to-downstream link as
metadata. Only when pipeTo() (or getReader()) runs
at the end of the chain, walk the collected link graph
and resolve the entire pipeline in a single, optimised
call — typically by delegating to a faster underlying
pipe primitive.
Canonical wiki instance — fast-webstreams (2026-04-21)¶
fast-webstreams uses this
pattern to collapse chained pipeThrough /
pipeTo between fast streams into a single
Node.js stream.pipeline() call.
The fast-path chain:
source
.pipeThrough(transform1) // records link, no piping yet
.pipeThrough(transform2) // records link, no piping yet
.pipeTo(sink); // walks upstream, collects Nodes,
// issues ONE stream.pipeline() call
Zero Promises per chunk. Data flows through Node's
C++-optimised pipe path. Measured: ~6,200 MB/s for
a chained fast-to-fast pipeThrough, vs native Web
Streams at ~630 MB/s — 9.8× faster.
(sources/2026-04-21-vercel-we-ralph-wiggumed-webstreams-to-make-them-10x-faster)
Why deferred resolution works¶
Web Streams' pipeThrough is specified to return a new
stream (the downstream end of the transform). An
implementation has latitude to delay what actually
happens when pipeThrough is called — as long as the
returned stream behaves correctly when consumed.
Three observations making the pattern safe:
- No observer sees piping start. Until something
reads from the returned stream's sink side, no data
flows. Delaying flow until
pipeToorgetReaderis observationally equivalent. - The whole chain is discoverable at sink time. Walking upstream links gives the implementation the full graph before committing to a pipe strategy.
- Underlying fast primitive exists. For
fast-webstreams, that primitive is
stream.pipeline()on native Node streams; for other contexts it might be a kernel-levelsplice(), a gRPC bidirectional pipe, or an async-iteration async generator.
Fallback when chain is mixed¶
The pattern requires the whole chain to be fast. If
any link is native (e.g. a built-in CompressionStream
mixed into a fast-webstreams chain), the collected
graph includes a non-native hop, and the implementation
cannot fuse into stream.pipeline() — falling back to
the spec-compliant pipeTo or native pipeThrough
implementation for the heterogeneous segment.
Vercel specifically restricts pipeline() use to
homogeneous-fast chains because
"pipeline() causes 72 WPT failures. The error
propagation, stream locking, and cancellation semantics
are fundamentally different." See
concepts/spec-compliant-optimization for the
discipline that enforces this fallback.
Fetch-body variant¶
Response.prototype.body is a native byte stream owned
by Node's HTTP layer. When
patchGlobalWebStreams() is active, it's wrapped in a
fast shell that records pipeThrough links without
starting piping. At sink, the library resolves the
chain: one Promise at the native-boundary pull (to
drive data in), then zero Promises through the
transform chain, then synchronous reads at output.
This is the pattern's most-impactful instance in
production workloads — most server streams don't start
from new ReadableStream(...) but from fetch().
Measured on fetch() → 3 transforms: native 260
MB/s → fast 830 MB/s = 3.2×.
Sibling patterns¶
- patterns/lazy-pull-pipeline — same defer-until-consumed principle, but at the pull-semantics API altitude (new-streams). This pattern is the same idea applied inside an otherwise push-based API to extract compositional fusion.
- Query-planner phase ordering (concepts/planner-phase-ordering) — sibling at the database altitude. SQL planners don't execute intermediate joins when a parser sees them; they collect the logical plan and compose physical execution once the whole query is known. Same record-then-resolve-at-sink shape.
- Pipe fusion in compilers — shell pipes fused into a single fork/exec graph; streams fused into a single codegen'd loop. This is the JS-stream-layer analogue.
Forces¶
- Composition-at-a-distance.
pipeThroughreturns a stream; downstream code is often far from where the pipe chain started. The implementation cannot plan fusion atpipeThroughcall time because the chain isn't complete yet. - API contract preservation. The observable
behaviour of
pipeThroughmust be preserved — it still returns a stream that behaves correctly when consumed. - Underlying fast primitive must exist. Without a
stream.pipeline()analogue at some lower layer, there's nothing to fuse into.
Counter-indications¶
- Chain is heterogeneous — mixed fast + native streams cannot fuse safely; fall back to spec-compliant pipe.
- Stream is consumed immediately. If
pipeThroughis followed synchronously by a consumer reading from the returned stream, deferring resolution doesn't save work — you just have to fire piping anyway, now. - Chain is short. A 1-deep chain doesn't benefit from fusion; the pattern's wins scale with chain depth. Measured by Vercel: 3 transforms = 9.7×, 8 transforms = 8.7× on native Web Streams, fused path scales similarly.
Consequences¶
- Zero per-chunk Promises in the fused case.
- Deeper chains scale better — pattern fuses arbitrary depth into O(1) pipe calls.
- Added complexity at pipe-construction time — the link-recording and fallback-detection code is non-trivial.
- Debug footgun: stack traces at pipe-start time
don't show up at
pipeThroughcall site; they show up atpipeTocall site. Error attribution needs care.
Seen in¶
- sources/2026-04-21-vercel-we-ralph-wiggumed-webstreams-to-make-them-10x-faster
— canonical wiki instance. fast-webstreams records
source → transform1 → transform2 → ...links; walks atpipeTo(), issues singlestream.pipeline()call. 9.8× speedup on chainedpipeThrough; 3.2× on fetch → 3 transforms via the fast-shell extension toResponse.prototype.body.
Related¶
- systems/fast-webstreams — the library where the pattern is canonicalised.
- systems/web-streams-api — the spec surface the pattern preserves.
- systems/nodejs — the runtime whose
stream.pipeline()primitive the pattern delegates to. - concepts/promise-allocation-overhead — the cost class the fused path eliminates per-chunk.
- concepts/synchronous-fast-path-streaming — the sibling fast-path at the per-read altitude.
- concepts/spec-compliant-optimization — the discipline enforcing correct fallback.
- patterns/lazy-pull-pipeline — sibling pattern at the new-API altitude.