4.8 KiB
safeBranch
(no submodules)
Purpose
safeBranch is a pipeline operator that wraps a sub-pipeline factory in a fault boundary. It runs the supplied factory(input) to construct an inner ReadableStream, then forwards every chunk it produces to the wrapper's output stream. If the inner pipeline throws — synchronously while the factory is being invoked, or asynchronously while its stream is being read — the wrapper's output stream closes cleanly (no further chunks emitted) and the error is forwarded to options.onError as a StreamBranchFailedError whose cause is the original error.
The outer pipeline never observes the inner error. This is the difference between a normal pipeThrough (which propagates errors and tears down the pipeline) and safeBranch (which contains them). Use it for opportunistic side branches — analytics, mirroring, telemetry — where a failure must not bring down the primary data path.
Features
- Isolates errors from inner sub-pipelines so the outer pipeline keeps running
- Forwards wrapped errors to
options.onErrorasStreamBranchFailedError - Preserves the original error via the
causechain - Output stream closes cleanly after a branch error — no half-emitted state
- Silent mode when
onErroris omitted - Synchronous factory throws are caught and reported the same way
Usage: Basic Example
import { fromArray } from "@suaveplan/streams/readable/from-array/from-array";
import { safeBranch } from "@suaveplan/streams/pipeline/safe-branch/safe-branch";
import { toArray } from "@suaveplan/streams/sinks/to-array/to-array";
const wrapped = safeBranch<number, number>(
(input) =>
input.pipeThrough(
new TransformStream<number, number>({
transform(chunk, c) {
c.enqueue(chunk * 2);
},
})
)
);
const out = wrapped(fromArray([1, 2, 3]));
const result = await toArray(out); // [2, 4, 6]
Usage: Advanced Example
import { fromArray } from "@suaveplan/streams/readable/from-array/from-array";
import { safeBranch } from "@suaveplan/streams/pipeline/safe-branch/safe-branch";
import { StreamBranchFailedError } from "@suaveplan/streams/errors/stream-error";
import { toArray } from "@suaveplan/streams/sinks/to-array/to-array";
let lastError: StreamBranchFailedError | undefined;
const analyticsBranch = safeBranch<number, number>(
(input) =>
input.pipeThrough(
new TransformStream<number, number>({
transform(chunk, c) {
if (chunk < 0) throw new Error("negative not allowed");
c.enqueue(chunk);
},
})
),
{
onError: (err) => {
lastError = err as StreamBranchFailedError;
},
}
);
const collected = await toArray(analyticsBranch(fromArray([1, -2, 3])));
// collected captures whatever was emitted before the error;
// the outer pipeline does not throw.
console.log(lastError?.code); // "STREAM.BRANCH_FAILED"
console.log(lastError?.cause); // Error: negative not allowed
API Reference
safeBranch<TIn, TOut>(factory, options?)
factory: (input: ReadableStream<TIn>) => ReadableStream<TOut>— constructs the inner sub-pipeline. May throw synchronously; failures are isolated.options.onError?: (err: unknown) => void— invoked with aStreamBranchFailedErrorwhen the inner pipeline errors. The original error is exposed viaerr.cause.- Returns
(input: ReadableStream<TIn>) => ReadableStream<TOut>— a wrapper function suitable for use anywhere a stream-shape transform is accepted. - Throws Never. All errors are converted to
StreamBranchFailedErrorand either delivered toonErroror silently swallowed.
Implementation Notes
- The wrapper acquires a single reader against the inner stream and pulls chunk-by-chunk inside the outer stream's
pullcallback. This preserves backpressure end-to-end: the outer consumer's pull cadence drives the inner sub-pipeline's read cadence. - On error, the wrapper sets a
closedflag, closes the outer controller, and callsonError. Subsequent pulls returndonewithout re-invoking the inner reader. - Cancellation of the wrapper propagates to the inner reader via
reader.cancel(reason). Cancellation errors from the inner stream are swallowed — they are part of the isolated branch by definition. - The error code is
STREAM.BRANCH_FAILED(registered inerrors/stream-error.ts). Inspecterr.codeto detect a branch failure programmatically; inspecterr.causeto recover the original error. safeBranchdoes not retry or restart the inner pipeline. After failure, the wrapper is terminal. To retry, pairsafeBranchwith@suaveplan/retryupstream of the factory.