Files
skills/docs/foundation/flow/streams/src/utils/stream/round-trip/round-trip.md
2026-05-29 08:20:56 +01:00

3.2 KiB

round-trip (streamToUint8Array / uint8ArrayToStream)

(no submodules)

Purpose

The round-trip module provides the two halves of byte ↔ stream conversion that underpin most file/network adapters in @suaveplan/streams. uint8ArrayToStream turns an in-memory buffer into a backpressure-respecting ReadableStream, and streamToUint8Array drains an arbitrary readable stream back into a single Uint8Array. They are deliberately co-located because most consumers reach for both in the same call path (e.g. test fixtures that load a buffer, push it through a transform pipeline, then re-collect the output for assertion).

The drain side is AbortSignal-aware and rejects with StreamAbortedError when cancellation is requested, cancelling the underlying reader so upstream producers can release resources promptly.

Features

  • uint8ArrayToStream: configurable chunk size; defaults to a single emission.
  • Per-chunk copy emission so consumers can safely retain references.
  • streamToUint8Array: total-length pre-sum, single allocation for output.
  • Honours AbortSignal both before and during read; cancels the reader.
  • Pure WHATWG Streams API — no Node-only or DOM-only deps.

Usage: Basic Example

import {
    streamToUint8Array,
    uint8ArrayToStream,
} from "@suaveplan/streams/utils/stream/round-trip/round-trip";

const original = new Uint8Array([1, 2, 3, 4]);
const drained = await streamToUint8Array(uint8ArrayToStream(original));
// drained equals original

Usage: Advanced Example

import {
    streamToUint8Array,
    uint8ArrayToStream,
} from "@suaveplan/streams/utils/stream/round-trip/round-trip";

async function pipeWithTimeout(
    data: Uint8Array,
    transform: TransformStream<Uint8Array, Uint8Array>,
    ms: number
): Promise<Uint8Array> {
    const ac = new AbortController();
    const timer = setTimeout(() => ac.abort(new Error("timeout")), ms);
    try {
        const piped = uint8ArrayToStream(data, { chunkSize: 4096 }).pipeThrough(
            transform
        );
        return await streamToUint8Array(piped, ac.signal);
    } finally {
        clearTimeout(timer);
    }
}

API Reference

uint8ArrayToStream(data, options?)

  • data: Uint8Array — bytes to emit.
  • options.chunkSize?: number — bytes per chunk; defaults to entire array.
  • Returns — ReadableStream<Uint8Array> that closes after the last chunk.

streamToUint8Array(stream, signal?)

  • stream: ReadableStream<Uint8Array> — source stream.
  • signal?: AbortSignal — cancellation token; cancelling rejects the call with StreamAbortedError and cancels the reader.
  • Returns — Promise<Uint8Array> of the concatenated bytes.
  • Throws — StreamAbortedError on cancellation.

Implementation Notes

  • The drain pre-collects chunks and sums lengths so the final Uint8Array is allocated exactly once — avoids the quadratic-copy trap of repeatedly appending into a growing buffer.
  • Each emission of uint8ArrayToStream is a copy via data.slice, so the source Uint8Array may be mutated independently after the stream is built.
  • The abort listener is removed in finally to keep the signal listener count bounded across long-lived signals.