first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
# safeBranch
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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<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
```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<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 a `StreamBranchFailedError` when the inner pipeline errors. The original error is exposed via `err.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 `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.