# 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.onError` as `StreamBranchFailedError` - Preserves the original error via the `cause` chain - Output stream closes cleanly after a branch error — no half-emitted state - Silent mode when `onError` is omitted - Synchronous factory throws are caught and reported the same way ## Usage: Basic Example ```ts 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( (input) => input.pipeThrough( new TransformStream({ 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 ```ts 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( (input) => input.pipeThrough( new TransformStream({ 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(factory, options?)` - `factory: (input: ReadableStream) => ReadableStream` — constructs the inner sub-pipeline. May throw synchronously; failures are isolated. - `options.onError?: (err: unknown) => void` — invoked with a `StreamBranchFailedError` when the inner pipeline errors. The original error is exposed via `err.cause`. - **Returns** `(input: ReadableStream) => ReadableStream` — a wrapper function suitable for use anywhere a stream-shape transform is accepted. - **Throws** Never. All errors are converted to `StreamBranchFailedError` and either delivered to `onError` or silently swallowed. ## Implementation Notes - The wrapper acquires a single reader against the inner stream and pulls chunk-by-chunk inside the outer stream's `pull` callback. 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 `closed` flag, closes the outer controller, and calls `onError`. Subsequent pulls return `done` without 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 in `errors/stream-error.ts`). Inspect `err.code` to detect a branch failure programmatically; inspect `err.cause` to recover the original error. - `safeBranch` does not retry or restart the inner pipeline. After failure, the wrapper is terminal. To retry, pair `safeBranch` with `@suaveplan/retry` upstream of the factory.