# enterprise-streams-redesign **Package**: `@suaveplan/streams` **Status**: PROPOSED **Type**: refactor ## Why The genesis-original `enterprise-streams` change (archived 2026-04-24) shipped a functional, WHATWG-`TransformStream`-only API. After auditing `../suaveplan/packages/universal/streams/src/` — the canonical implementation on main — three structural gaps surfaced that block downstream packages (`streams-metadata`, `streams-chunk-queue`, future `audio-engine`, future `streams-worker`) from being built consistently: 1. **No plugin contract.** Hooks (`crypto:before-chunk`, `crypto:after-chunk`), events (`start` / `chunk` / `complete` / `error`), parameter introspection, and lifecycle (`init` / `flush` / `dispose`) cannot be expressed in pure `TransformStream` factories. Main's `BaseTransform` abstract class — with `process(input)`, `setOperation`, `setParams`, `hook(name, fn)`, `on(name, fn)`, and `toTransformStream()` bridge — is the canonical plugin shape and is required for the DAW-style audio plugin ambition. 2. **No streaming AEAD transform.** Main ships `CryptoTransform` with pluggable `xchacha20StreamingEngine` / `aesGcmStreamingEngine` that bind a counter and final-flag into the AEAD AAD, detecting reorder/truncate/tamper. Genesis has no equivalent. Encryption-on-the-fly is one of the explicit driving use cases. 3. **No bidirectional / network primitives.** Main ships `BaseDuplexStream` and `NetworkStreamImpl` (id + protocol + metadata Map) for WebRTC / WebSocket / TCP integration. Genesis has none. These are foundational for the planned drive / network-discovery / audio-network packages. Additionally, the existing genesis layout uses redundant `-stream` suffixes on every transform module (`transforms/throttle-stream/`, `transforms/map-stream/`, etc.) — confusing inside a package literally named `streams`. This change normalises naming as part of the redesign. The 11 existing functional transforms, 3 sources, 3 sinks, fan-out, merge, pipeline-builder, instrument-transform, and node-adapters are all preserved as the implementation backbone — every `BaseTransform` subclass either subclasses or composes the equivalent functional logic via `toTransformStream()`, so no behavioural regression is introduced. ## What changes ### ADDED — plugin substrate (ported from main) - ADDED: `BaseTransform` — abstract class with `name`, `setOperation`, `setParams`, `getOperation`, `getParams`, `hook(name, fn)`, `removeHook(name, fn)`, `on(name, fn)`, `off(name, fn)`, `process(input, options?)` abstract, `toTransformStream(): TransformStream` bridge to WHATWG, `dispose()` for cleanup - ADDED: `TransformOptions`, `IBaseTransform`, `TransformHookPayload`, `TransformEventPayload` types - ADDED: `StreamManager` — `pipe(...transforms)`, `execute(input, options?)`, `clear()`, `getTransforms()` - ADDED: `TransformFactory` — registry + `create(type, ...args)` with `TransformType` discriminated union; built-in registrations for every bundled transform - ADDED: `BaseDuplexStream` primitive with `StreamSource` and `StreamSink` configuration - ADDED: `NetworkStreamImpl` with `id`, `protocol`, `metadata: Map`, `closed` flag, `close()` and `reset()` - ADDED: factory functions `createDuplexStream`, `createNetworkStream`, `createTransformStream` ### ADDED — bundled BaseTransform subclasses (port + new) Ported from main: - ADDED: `CryptoTransform` — pluggable streaming AEAD with `engine`, `key`, `operation` ("encrypt" | "decrypt"), hooks (`crypto:before-chunk`, `crypto:after-chunk`), events (`crypto:start`, `crypto:chunk`, `crypto:complete`, `crypto:error`), `process(asyncIterable)` for stream-of-frames, `toTransformStream()` for WHATWG bridge. Depends on `@suaveplan/crypto` streaming AEAD engines. - ADDED: `SplitJoinTransform` — object-mode split with `chunkSize` + `overlap`; `join` reassembles with optional `separator` and `removeOverlap`; supports string / `ArrayBuffer` / array; type guards `isSplittable`, `isJoinable` Existing 11 functional transforms migrated to `BaseTransform` subclasses (see § Renames): - ADDED: `MapTransform`, `FilterTransform`, `FlatMapTransform`, `BatchTransform`, `WindowTransform`, `ThrottleTransform`, `LimitTransform`, `DedupTransform`, `TapTransform`, `TextCodecTransform` (encode/decode operations), `CompressTransform` (compress/decompress operations) Genesis-original gap-fillers (new): - ADDED: `ChunkBytesTransform` — byte-level fixed-frame re-framing with tail policy (`emit` | `pad` | `drop`) - ADDED: `CoalesceBytesTransform` — merges small `Uint8Array` chunks until a byte threshold or max-latency timeout - ADDED: `SlidingBytesTransform` — overlapping byte windows with size + stride - ADDED: `LengthPrefixEncodeTransform`, `LengthPrefixDecodeTransform` — wire-protocol framing with 1/2/4/8-byte prefix and configurable byte-order ### ADDED — pipeline ops (genesis-original) - ADDED: `partition(source, predicate)` → `{ truthy, falsy }` single-pass split (no tee duplication) - ADDED: `route(source, keyFn, keys, options?)` → `Record>`; `onUnknownKey: 'drop' | 'error'` - ADDED: `routeBy(source, keyFn, keys, options?)` → `Map>` (non-string-key variant) - ADDED: `zip(streams)` → tuple stream with `onShortInput: 'close' | 'error' | 'pad'` option - ADDED: `concat(streams)` → sequential drain - ADDED: `mergeOrdered(streams, compare)` → k-way merge of pre-sorted streams - ADDED: `safeBranch(factory, options?)` → wraps a sub-pipeline so its errors emit on `EventBusLike` instead of propagating ### ADDED — sources, sinks, utilities - ADDED: `toAsyncIterable(stream, signal?)` sink - ADDED: `getDataFromFile(filePath)` — Node-only file reader with runtime environment detection (throws in browser) - ADDED: `bufferToUint8Array(buffer)` — handles both Node `Buffer` and `ArrayBuffer` - ADDED: `createUint8ArraySlice(array, start, end)` — explicit-typed copy helper - ADDED: `createDataStream(data, transformer, options?)` — generic source factory used by `StreamManager` for non-stream inputs - ADDED: `processDataStream(stream, options?)` — generic sink factory - ADDED: `streamToUint8Array(stream)`, `uint8ArrayToStream(data, options?)`, `bufferToStream`, `streamToBuffer` cross-runtime helpers - ADDED: `validateStreamIntegrity(stream)` — chunk-validity sweep helper ### MODIFIED — naming convention Drop redundant `-stream` suffix on every module name inside the streams package. Implementation file basename matches the parent folder; class and function names follow PascalCase / camelCase conventions without the redundant suffix. | Old path | New path | Rationale | |---|---|---| | `transforms/map-stream/map-stream.ts` | `transforms/map/map.ts` | inside a streams package, "stream" suffix is redundant | | `transforms/filter-stream/filter-stream.ts` | `transforms/filter/filter.ts` | same | | `transforms/flat-map-stream/` | `transforms/flat-map/` | same | | `transforms/batch-stream/` | `transforms/batch/` | same | | `transforms/window-stream/` | `transforms/window/` | same | | `transforms/throttle-stream/` | `transforms/throttle/` | same | | `transforms/limit-stream/` | `transforms/limit/` | same | | `transforms/dedup-stream/` | `transforms/dedup/` | same | | `transforms/tap-stream/` | `transforms/tap/` | same | | `transforms/text-codec/` | unchanged | already conforms | | `transforms/compress/` | unchanged | already conforms | | `pipeline/pipeline-builder/` | `pipeline/builder/` | redundant package-name prefix | | `instrument/instrument-transform/` | `instrument/transform/` | redundant prefix | | `node/node-adapters.ts` | `node/adapters/adapters.ts` | promoted to feature dir + redundant prefix dropped | | `errors/stream-error.ts` | `errors/error/error.ts` | redundant prefix dropped, promoted to feature dir | The corresponding exported symbols are renamed in lockstep: | Old export | New export | |---|---| | `mapStream` | `mapStream` (kept — describes the *function* shape, not file naming) plus new `MapTransform` class | | `filterStream` | `filterStream` plus `FilterTransform` | | `flatMapStream` | `flatMapStream` plus `FlatMapTransform` | | `batchStream` | `batchStream` plus `BatchTransform` | | `windowStream` | `windowStream` plus `WindowTransform` | | `throttleStream` | `throttleStream` plus `ThrottleTransform` | | `limitStream` | `limitStream` plus `LimitTransform` | | `dedupStream` | `dedupStream` plus `DedupTransform` | | `tapStream` | `tapStream` plus `TapTransform` | | `compressStream`, `decompressStream` | unchanged plus `CompressTransform` (operation = "compress" \| "decompress") | | `encodeTextStream`, `decodeTextStream` | unchanged plus `TextCodecTransform` | | `pipeline()` | unchanged | | `fanOut`, `merge` | unchanged | | `instrumentTransform` | unchanged | | `fromNodeReadable`, `toNodeReadable`, `fromNodeWritable` | unchanged | | `StreamPipeFailedError` | renamed to `StreamPipeFailedError` (lives in `errors/error/error.ts` now) | The function-form factories (`mapStream`, `filterStream`, …) remain exported for tree-shaking and for direct WHATWG composition; the OO subclasses delegate to them via `toTransformStream()`. **Both call paths are first-class.** This is the only "two APIs" pattern this package permits, and it is justified because the OO subclass *uses* the functional factory under the hood — no parallel implementation, no drift surface. ### ADDED — error codes - ADDED: `STREAM.FRAME_TOO_LARGE`, `STREAM.TRUNCATED_FRAME`, `STREAM.ROUTE_UNKNOWN_KEY`, `STREAM.ZIP_LENGTH_MISMATCH`, `STREAM.BRANCH_FAILED`, `STREAM.CRYPTO_DECRYPT_FAILED`, `STREAM.CRYPTO_INVALID_FRAME`, `STREAM.DUPLEX_CLOSED`, `STREAM.NETWORK_RESET`, `STREAM.FILE_READ_FAILED`, `STREAM.FILE_BROWSER_NOT_SUPPORTED` ### REMOVED - REMOVED: nothing — the previous spec is preserved verbatim under `MODIFIED Requirements`. Symbols are renamed (with old paths gone) but exported names are kept on the function-form API. ## Out of scope - **Audio-specific anything** (sample frames, channel-aware ops, mixing, pitch-shift, dynamics-processor) — belongs in the upcoming `audio-engine` package as plugins, not in streams. Karaoke/radio transforms from main do **not** port over. - **Image / video / metadata transforms** — already split into siblings (`streams-metadata` proposed; image/video to follow as separate changes). - **Worker offload** (`pipeInWorker`) — deferred to a future `streams-worker` sibling package once audio-engine demands prove the transport shape. - **`schema-registry` integration** of stream chunk contracts — deferred until `@suaveplan/schema-registry` ships the consumer hooks the streams chunk format would need. - **Mutation testing & contract testing layers** — covered by separate `enterprise-testing-mutation` and `enterprise-testing-contract` changes; this change ships 100% line/branch/function coverage as the baseline. ## Source mapping This change derives from `../suaveplan/enterprise/streams.md` (the generic enterprise-quality template applied to streams) and the canonical implementation at `../suaveplan/packages/universal/streams/src/` (the ground-truth API surface). | Requirement | Source | Notes | |---|---|---| | Pure TypeScript, runs in Bun + Node + browser | `enterprise/streams.md` L38 | verbatim | | WHATWG Streams baseline | `enterprise/streams.md` L40 | verbatim | | Zero-copy `Uint8Array` operations on hot paths | `enterprise/streams.md` L41 | verbatim | | Backpressure-first; bounded queues with declarative high/low watermarks | `enterprise/streams.md` L42 | verbatim | | `AbortSignal` propagates end-to-end | `enterprise/streams.md` L43 | verbatim | | Zod schemas for stream configuration and chunk contracts | `enterprise/streams.md` L44 | partial — schema-registry integration deferred (see Out of scope) | | Property-based tests via fast-check | `enterprise/streams.md` L45 | verbatim | | Deterministic — no `Math.random()` / `Date.now()` at module scope, injectable clock + RNG | `enterprise/streams.md` L46 | verbatim | | OpenTelemetry instrumentation | `enterprise/streams.md` L47 | verbatim | | Structured errors extending `@suaveplan/error` with stable codes | `enterprise/streams.md` L48 | verbatim | | Idempotency + retry primitives for network-backed adapters | `enterprise/streams.md` L49 | applied to `NetworkStreamImpl`'s reset semantics | | Feature flags for risky transform rollouts | `enterprise/streams.md` L50 | applied via `FlagRegistry` injection on factory; opt-in | | Plugin / extension interface | `enterprise/streams.md` L74 | satisfied by `BaseTransform` + `TransformFactory` | | Observability surface — counters, histograms, spans on every externally-visible op | `enterprise/streams.md` L75 | satisfied by `instrumentTransform` + per-class hook surface | | Configurability — Zod-validated options with sensible defaults | `enterprise/streams.md` L76 | verbatim | | Performance — benchmark suite + CI regression gate | `enterprise/streams.md` L77 | verbatim | | Resilience — retries / timeouts / cancellation / circuit breakers | `enterprise/streams.md` L78 | applied via `@suaveplan/retry` composition (caller-side) | | Security — input validation, output encoding, zeroing of sensitive buffers | `enterprise/streams.md` L79 | applied to `CryptoTransform` key zeroize on dispose | | Migration story for public API changes | `enterprise/streams.md` L80 | satisfied by the rename matrix above | | Example app / story demonstrating full feature set | `enterprise/streams.md` L82 | satisfied by `examples/` runnable scripts | ### Requirements with no `enterprise/streams.md` line These derive from `../suaveplan/packages/universal/streams/src/` (canonical implementation on main) and from the `enterprise-streams-redesign` audit conducted in the brainstorming session. | Requirement | Source | Justification | |---|---|---| | `BaseTransform` abstract class with hook + event surface | `../suaveplan/packages/universal/streams/src/transforms/base/base.ts` + `base.md` | canonical plugin shape — required for hooks/events that no functional API can express | | `StreamManager` chainable pipeline | `../suaveplan/packages/universal/streams/src/manager/manager.ts` + `manager.md` | canonical | | `TransformFactory` registry | `../suaveplan/packages/universal/streams/src/transforms/factory/factory.ts` + `factory.md` | canonical | | `BaseDuplexStream` + `NetworkStreamImpl` | `../suaveplan/packages/universal/streams/src/primitives/duplex.ts` + `duplex.md` | canonical | | `CryptoTransform` with pluggable streaming AEAD | `../suaveplan/packages/universal/streams/src/transforms/crypto/crypto.ts` + `crypto.md` | canonical; depends on `@suaveplan/crypto` streaming AEAD engines | | `SplitJoinTransform` | `../suaveplan/packages/universal/streams/src/transforms/split-join/split-join.ts` + `split-join.md` | canonical | | Buffer utilities (`getDataFromFile`, `bufferToUint8Array`, `createUint8ArraySlice`) | `../suaveplan/packages/universal/streams/src/utils/buffer/buffer.md` | canonical | | Stream utilities (`createDataStream`, `processDataStream`, `streamToUint8Array`, `uint8ArrayToStream`, `validateStreamIntegrity`) | `../suaveplan/packages/universal/streams/src/utils/stream/stream.md` | canonical | | `chunkBytes`, `coalesceBytes`, `slidingBytes`, `lengthPrefixEncode/Decode` | genesis audit (this change) | byte-level framing primitives; AEAD requires fixed-byte frames; main's `SplitJoinTransform` is object-mode only | | `partition`, `route`, `routeBy` | genesis audit (this change) | single-pass split (`fanOut` duplicates via `tee`); essential for "split into different plugin chains by criterion" | | `zip`, `concat`, `mergeOrdered` | genesis audit (this change) | merge variants beyond arrival-order race; `zip` enables sample-aligned recombination, `mergeOrdered` enables k-way time-sorted merge | | `safeBranch` | genesis audit (this change) | branch error isolation — without it, one failing FX rack tears down the whole graph | | `toAsyncIterable` sink | genesis audit (this change) | async iterator interop, missing from main | | Naming convention: drop redundant `-stream` suffix | user direction (brainstorming session) | clarity inside a streams package | | 100% test coverage threshold | genesis-wide policy (`openspec/AGENTS.md` § 7) | non-package-specific | | Source-mapped `**Verified by:**` lines | genesis-wide policy (`openspec/AGENTS.md` § 13) | non-package-specific | ## Verification approach Every requirement in the spec delta carries a `**Verified by:**` line pointing at a concrete `it(...)` title in one of the test files below. Tests are co-located with implementation per the genesis 5-file convention. - `src/base/transform/transform.test.ts` — `BaseTransform` lifecycle, hook + event surfaces, `toTransformStream()` bridge, `dispose()` semantics - `src/base/transform/transform.spec-conformance.test.ts` — type-level: generic narrowing, hook-payload type inference, factory return-type preservation - `src/manager/manager/manager.test.ts` — pipeline chaining, sequential execution, `clear()` reset, error propagation - `src/factory/factory/factory.test.ts` — type-discriminated `create()`, registry lookup, unknown-type error - `src/primitives/duplex/duplex.test.ts` — bidirectional flow, byte vs object mode, `cancel()` semantics - `src/primitives/network/network.test.ts` — id, protocol, metadata, `close()`, `reset()` with error - `src/transforms//.test.ts` — per-transform behaviour (one file per BaseTransform subclass) - `src/transforms/crypto/crypto.test.ts` + `crypto.spec-conformance.test.ts` — round-trip encrypt/decrypt, hook payloads, event firing order, reorder/truncate detection - `src/pipeline//.test.ts` — per-op behaviour - `src/sinks/to-async-iterable/to-async-iterable.test.ts` — async-iterator yields all chunks; `signal` cancels - `src/utils/buffer//.test.ts` — Node + browser branches; env-detection error - `src/utils/stream//.test.ts` — round-trip identity, integrity validation - `src/instrument/transform/transform.test.ts` — span + counter + histogram emitted via in-memory OTel `MeterProvider` - `src/node/adapters/adapters.test.ts` — Node ↔ WHATWG bridge round-trip - `tests/integration.test.ts` — full pipelines: encrypt → length-prefix → decrypt round-trip; partition + safe-branch + merge composition - `tests/property.test.ts` — `chunkBytes` byte-conservation, `partition` order preservation, `zip`/`unzip` round-trip, `mergeOrdered` sortedness invariant - `tests/dep-graph.test.ts` — only declared workspace deps reachable; no `node:fs` import in browser entry - `tests/tree-shake.test.ts` — `sideEffects: false` round-trip via `Bun.build` ## Migration impact **This package has consumers on genesis already** — every package that imports from `@suaveplan/streams`. Audit: ```bash grep -rln "@suaveplan/streams" packages/ --include="package.json" ``` The function-form API (`mapStream`, `filterStream`, `pipeline`, `fanOut`, `merge`, `instrumentTransform`, `fromNodeReadable`, `toNodeReadable`, `fromNodeWritable`, `compressStream`, `decompressStream`, `encodeTextStream`, `decodeTextStream`, `toArray`, `toBytes`, `forEach`, `fromArray`, `fromBytes`, `fromAsyncGenerator`) is preserved. Internal subpath imports change: | Old subpath import | New subpath import | |---|---| | `@suaveplan/streams/transforms/map-stream/map-stream` | `@suaveplan/streams/transforms/map/map` | | `@suaveplan/streams/transforms/filter-stream/filter-stream` | `@suaveplan/streams/transforms/filter/filter` | | (… same pattern for the renamed transforms above) | | | `@suaveplan/streams/pipeline/pipeline-builder/pipeline-builder` | `@suaveplan/streams/pipeline/builder/builder` | | `@suaveplan/streams/instrument/instrument-transform/instrument-transform` | `@suaveplan/streams/instrument/transform/transform` | | `@suaveplan/streams/node/node-adapters` | `@suaveplan/streams/node/adapters/adapters` | | `@suaveplan/streams/errors/stream-error` | `@suaveplan/streams/errors/error/error` | **Consumer migration is part of this change.** Every internal consumer is updated in the same change folder; no shim or deprecation alias remains. The barrel `@suaveplan/streams` (root export) is unaffected — top-level imports keep working. **Breaking changes:** subpath imports only (top-level barrel preserved). New OO subclasses are pure additions. **Prerequisites:** `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`, `@suaveplan/crypto` (for `CryptoTransform`) must be at their current genesis versions on Verdaccio.