Files
skills/docs/foundation/flow/streams/src/pipeline/partition/partition.md
2026-05-29 08:20:56 +01:00

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. partition acquires a single default reader and drives it from the output pulls.
  • predicate: (chunk: T) => boolean | Promise<boolean> — routes each chunk: true enqueues to truthy, false enqueues to falsy. 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 ReadableStream machinery on first use.
  • Asynchronous: predicate exceptions surface as stream errors on both outputs; their .cause is 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.