4.0 KiB
partition
(no submodules)
Purpose
partition splits a single ReadableStream<T> into two output streams using a
boolean predicate. Unlike tee or fanOut, every input chunk is routed to
exactly one output — the truthy stream or the falsy stream — so the union of
the two outputs equals the source with per-side ordering preserved. This is
the right tool when you want to fork a stream by classification (errors vs
successes, even vs odd, hot vs cold) without paying the buffering cost of a
full duplication.
Features
- Single-pass split via one underlying reader on the source
- Synchronous or asynchronous predicate (
(chunk: T) => boolean | Promise<boolean>) - Backpressure flows from either output back to the source — pulls on each side drive the reader on demand
- Predicate exceptions error both output streams and cancel the source
- Per-side
cancel()cooperates: the source is cancelled only when both sides have been cancelled - One-slot per-side buffer absorbs the off-routing case (a pull on truthy yielding a falsy chunk and vice versa)
- Zero workspace runtime dependencies — uses only WHATWG Streams primitives
Usage: Basic Example
import { partition } from "@suaveplan/streams/pipeline/partition/partition";
declare const numbers: ReadableStream<number>;
const { truthy: evens, falsy: odds } = partition(
numbers,
(n) => n % 2 === 0
);
Usage: Advanced Example
import { partition } from "@suaveplan/streams/pipeline/partition/partition";
interface Event {
level: "info" | "warn" | "error";
payload: unknown;
}
declare const events: ReadableStream<Event>;
const { truthy: errors, falsy: rest } = partition(events, async (e) => {
// async predicates are awaited in routing order
await Promise.resolve();
return e.level === "error";
});
// Each side is an independent ReadableStream — pipe through unrelated
// pipelines without worrying about duplication or fan-out cost.
const errorsToSink = errors.pipeTo(errorSink);
const restToSink = rest.pipeTo(analyticsSink);
await Promise.all([errorsToSink, restToSink]);
API Reference
partition<T>(source, predicate): { truthy, falsy }
source: ReadableStream<T>— the input stream.partitionacquires a single default reader and drives it from the output pulls.predicate: (chunk: T) => boolean | Promise<boolean>— routes each chunk:trueenqueues totruthy,falseenqueues tofalsy. Predicate exceptions are caught, both outputs are errored with the original cause, and the source reader is cancelled.- Returns
{ truthy: ReadableStream<T>; falsy: ReadableStream<T> }— two independent readable streams. Both close when the source is exhausted. Cancelling one side does not cancel the source; cancelling both does.
Throws
- Synchronous: none — invalid arguments propagate as TypeErrors from the
underlying
ReadableStreammachinery on first use. - Asynchronous: predicate exceptions surface as stream errors on both
outputs; their
.causeis the original thrown value.
Implementation Notes
The implementation owns a single ReadableStreamDefaultReader on the source
and exposes two consumer streams whose pull() callbacks both drain from
that reader. Each pull loops until it produces a chunk for its own side;
chunks for the other side are stashed in a one-slot buffer that the other
side claims on its next pull. Because pulls only fire under backpressure
(when the consumer side has space), the source is read at most as fast as
the currently-pulling consumer demands — backpressure on either side is
preserved end-to-end.
Cancellation is cooperative. A cancel() on a single output marks that side
closed; the source reader is only cancelled once both sides are closed. This
mirrors tee semantics and lets the live side continue draining the source.
Predicate exceptions are routed once: the first throw errors both outputs
and cancels the reader, after which pullFor short-circuits via the
errored flag.