10 KiB
OpenSpec Change: enterprise-streams
- Change ID: enterprise-streams
- Package:
@suaveplan/streams - Category: universal
- Target path:
universal/streams/streams - Status: PROPOSED
- Date: 2026-04-24
- Reference spec:
/mnt/s/web/suaveplan/enterprise/streams.md
Summary
Deliver @suaveplan/streams on the genesis branch from scratch at enterprise quality. This is the foundational stream-processing package: WHATWG-Streams-based primitives, backpressure-first transforms, zero-copy Uint8Array operations where possible, AbortSignal integration end-to-end, structured errors, OTel instrumentation, and property-based invariant proofs. All other Suaveplan packages that need pipelined data processing, file handling, network body processing, or event bridging build on this substrate.
Current-state audit (genesis)
The package does not exist on genesis yet — fresh build. No prior source, no test results, no coverage data.
Available workspace deps on genesis
| Package | Role |
|---|---|
@suaveplan/error (workspace:^) |
Base class for StreamError; defineCodes for STREAM.* codes |
@suaveplan/types (workspace:^) |
LoggerLike, Clock injection interfaces |
zod (catalog:forms) |
Zod schemas for stream configuration types |
@opentelemetry/api (catalog:observability) |
Pipeline spans, chunk-count / byte-count histograms per stage |
tslib (catalog:build) |
Runtime helpers |
Dev deps: vitest, @vitest/coverage-istanbul, fast-check, mitata (all catalog:testing).
Public API surface
Stream creation utilities
// Readable stream factories
function fromArray<T>(items: T[], options?: ReadableOptions): ReadableStream<T>
function fromAsyncGenerator<T>(gen: () => AsyncGenerator<T>, options?: ReadableOptions): ReadableStream<T>
function fromBytes(data: Uint8Array, chunkSize?: number): ReadableStream<Uint8Array>
// Byte stream utilities
function concat(streams: ReadableStream<Uint8Array>[]): ReadableStream<Uint8Array>
function slice(stream: ReadableStream<Uint8Array>, start: number, end?: number): ReadableStream<Uint8Array>
interface ReadableOptions {
highWaterMark?: number
signal?: AbortSignal
}
Transform factories
// Data transforms
function mapStream<TIn, TOut>(fn: (chunk: TIn) => TOut | Promise<TOut>): TransformStream<TIn, TOut>
function filterStream<T>(predicate: (chunk: T) => boolean | Promise<boolean>): TransformStream<T, T>
function flatMapStream<TIn, TOut>(fn: (chunk: TIn) => TOut[] | Promise<TOut[]>): TransformStream<TIn, TOut>
function batchStream<T>(size: number): TransformStream<T, T[]>
function tapStream<T>(fn: (chunk: T) => void | Promise<void>): TransformStream<T, T>
function limitStream<T>(max: number): TransformStream<T, T>
// Byte transforms
function encodeTextStream(encoding?: string): TransformStream<string, Uint8Array>
function decodeTextStream(encoding?: string): TransformStream<Uint8Array, string>
function compressStream(format: "gzip" | "deflate"): TransformStream<Uint8Array, Uint8Array>
function decompressStream(format: "gzip" | "deflate"): TransformStream<Uint8Array, Uint8Array>
interface TransformOptions {
highWaterMark?: number
signal?: AbortSignal
}
Sink utilities
// Consumption
function toArray<T>(stream: ReadableStream<T>, signal?: AbortSignal): Promise<T[]>
function toBytes(stream: ReadableStream<Uint8Array>, signal?: AbortSignal): Promise<Uint8Array>
function toString(stream: ReadableStream<Uint8Array>, encoding?: string): Promise<string>
function forEach<T>(stream: ReadableStream<T>, fn: (chunk: T) => void | Promise<void>, signal?: AbortSignal): Promise<void>
function toWritable<T>(stream: ReadableStream<T>, writable: WritableStream<T>, signal?: AbortSignal): Promise<void>
Pipeline composition
// Type-safe pipeline builder
function pipeline<T>(source: ReadableStream<T>): PipelineBuilder<T>
interface PipelineBuilder<T> {
pipe<TNext>(transform: TransformStream<T, TNext>): PipelineBuilder<TNext>
pipe<TNext>(fn: (chunk: T) => TNext | Promise<TNext>): PipelineBuilder<TNext>
collect(signal?: AbortSignal): Promise<T[]>
drain(writable: WritableStream<T>, signal?: AbortSignal): Promise<void>
forEach(fn: (chunk: T) => void | Promise<void>, signal?: AbortSignal): Promise<void>
}
// Parallel fan-out
function fanOut<T>(stream: ReadableStream<T>, n: number): ReadableStream<T>[]
// Merge fan-in
function merge<T>(streams: ReadableStream<T>[]): ReadableStream<T>
Node.js interop (server-only adapter)
// Available under @suaveplan/streams/node subpath
function fromNodeReadable(stream: NodeReadable): ReadableStream<Uint8Array>
function toNodeReadable(stream: ReadableStream<Uint8Array>): NodeReadable
function fromNodeWritable(stream: NodeWritable): WritableStream<Uint8Array>
Instrumented pipeline
// Wraps any transform with OTel span + counters
function instrumentTransform<TIn, TOut>(
transform: TransformStream<TIn, TOut>,
name: string,
options?: InstrumentOptions
): TransformStream<TIn, TOut>
interface InstrumentOptions {
tracer?: Tracer
meter?: Meter
attributes?: Record<string, string>
}
Error handling
class StreamError extends SuaveplanError<StreamErrorCode>
type StreamErrorCode =
| "STREAM_ABORTED"
| "STREAM_BACKPRESSURE_EXCEEDED"
| "STREAM_CHUNK_INVALID"
| "STREAM_PIPE_FAILED"
| "STREAM_TRANSFORM_FAILED"
| "STREAM_SOURCE_EXHAUSTED"
function registerStreamErrorCodes(): void
Zod schemas
const ReadableOptionsSchema: ZodSchema
const TransformOptionsSchema: ZodSchema
const InstrumentOptionsSchema: ZodSchema
New additions for genesis
1. Backpressure-first design
All transforms use WHATWG Streams highWaterMark and queuing strategies. batchStream accumulates into a bounded internal buffer; when the buffer is full it applies downstream backpressure by not pulling from the source.
2. Zero-copy byte path
concat, slice, compressStream, and decompressStream operate on Uint8Array views without intermediate copying where the underlying platform supports it (Bun and modern V8).
3. AbortSignal propagation
Every async operation (toArray, toBytes, forEach, drain, pipeline.*) accepts an AbortSignal. On abort, open streams are cancelled and pending promises reject with StreamError("STREAM_ABORTED").
4. OTel instrumentation
instrumentTransformwraps any named transform with: spanstream.transform.<name>, counterstream.chunks_total(attribute:stream.stage), histogramstream.chunk_bytes(byte streams only), histogramstream.transform_duration_mspipelinebuilder instruments each piped stage automatically when a tracer is provided viaPipelineOptions.
5. Property-based tests (fast-check)
toArray(fromArray(items)) ≡ itemsfor arbitrary arraysbatchStreamcompleteness: flattening all batches yields the original sequencemapStreamcomposition:pipe(map(f), map(g)) ≡ map(x => g(f(x)))for pure functionsconcatbyte order: concatenating two byte arrays via streams yields the same bytes asUint8Arrayconcat- AbortSignal: cancelling mid-flight halts processing and releases all locks
6. Benchmark suite (src/__benchmarks__/)
fromArray→mapStream→toArray, 100k itemsfromBytes→batchStream(1024)→toArray, 10MBcompressStream+decompressStreamround-trip, 1MB
File layout
universal/streams/streams/
package.json
tsconfig.json
vitest.config.ts
src/
index.ts
error-codes.ts
constants/
index.ts
types/
common.ts
common.md
readable/
from-array/
from-array.ts
from-array.md
from-array.test.ts
index.ts
from-generator/
from-generator.ts
from-generator.md
from-generator.test.ts
index.ts
from-bytes/
from-bytes.ts
from-bytes.md
from-bytes.test.ts
index.ts
transforms/
map-stream/
map-stream.ts
map-stream.md
map-stream.test.ts
index.ts
filter-stream/
filter-stream.ts
filter-stream.md
filter-stream.test.ts
index.ts
batch-stream/
batch-stream.ts
batch-stream.md
batch-stream.test.ts
index.ts
flat-map-stream/
flat-map-stream.ts
flat-map-stream.md
flat-map-stream.test.ts
index.ts
tap-stream/
tap-stream.ts
tap-stream.md
tap-stream.test.ts
index.ts
limit-stream/
limit-stream.ts
limit-stream.md
limit-stream.test.ts
index.ts
text-codec/
text-codec.ts
text-codec.md
text-codec.test.ts
index.ts
compress/
compress.ts
compress.md
compress.test.ts
index.ts
sinks/
to-array/
to-array.ts
to-array.md
to-array.test.ts
index.ts
to-bytes/
to-bytes.ts
to-bytes.md
to-bytes.test.ts
index.ts
for-each/
for-each.ts
for-each.md
for-each.test.ts
index.ts
pipeline/
pipeline-builder/
pipeline-builder.ts
pipeline-builder.md
pipeline-builder.types.ts
pipeline-builder.test.ts
index.ts
fan-out/
fan-out.ts
fan-out.md
fan-out.test.ts
index.ts
merge/
merge.ts
merge.md
merge.test.ts
index.ts
instrument/
instrument-transform/
instrument-transform.ts
instrument-transform.md
instrument-transform.types.ts
instrument-transform.test.ts
index.ts
node/
node-adapters.ts
node-adapters.md
node-adapters.test.ts
__benchmarks__/
streams.bench.ts
tests/
integration.test.ts
property.test.ts
Impact
Downstream packages: @suaveplan/streams-metadata, @suaveplan/streams-chunk-queue, @suaveplan/etl, and any future package doing pipelined data processing.
Workspace dep prerequisites: @suaveplan/error, @suaveplan/types must be published to Verdaccio before this package can be built.
Breaking changes: none (new package).