# `stream` _(no submodules)_ ## Purpose `stream.ts` exposes `sendStream`, a streaming RPC primitive that returns an `AsyncIterable` driven by frames the worker pushes back to the main thread. It complements the `sendRequest` request/response API by letting workers stream partial results — useful for progressive renders, chunked compression, log tails, or any long-running operation that produces incremental output. The protocol is intentionally minimal: the consumer posts an initial envelope `{ streamId, payload }` to the worker; the worker pushes `{ id: streamId, payload: { chunk } }` frames, and signals completion with `{ id: streamId, payload: { done: true } }`. Aborting the consumer's signal posts a `stream:cancel` frame to the worker and throws `WEB_WORKERS.STREAM_ABORTED`. ## Features - Async-iterable interface: `for await (const chunk of stream) ...`. - `AbortSignal` cancellation: aborts both the iterator and posts a cancel frame to the worker. - Pre-aborted signal short-circuits with `WEB_WORKERS.STREAM_ABORTED` and no initial message is posted. - Frame correlation by stream id: foreign-worker frames and foreign-id frames are ignored, so multiple concurrent streams over the same manager coexist cleanly. - Optional `idFactory` for deterministic ids in tests. - Optional `transfer` list for zero-copy initial payload handoff. ## Usage: Basic Example ```ts import { sendStream, WorkerManager } from "@suaveplan/web-workers"; const manager = new WorkerManager({ factory }); manager.create({ name: "tail", url: WORKER_URL }); for await (const line of sendStream( manager, "tail", "tail-log", "/var/log/app.log" )) { appendLine(line); } ``` ## Usage: Advanced Example ```ts import { sendStream, WEB_WORKERS_CODES } from "@suaveplan/web-workers"; const ctrl = new AbortController(); const buf = new ArrayBuffer(8 * 1024 * 1024); const stream = sendStream( manager, "compress", "lz4-stream", buf, { signal: ctrl.signal, transfer: [buf] } ); const chunks: ArrayBuffer[] = []; try { for await (const chunk of stream) { chunks.push(chunk); if (cancelRequested) ctrl.abort(); } } catch (err) { if ( err instanceof Error && (err as { code?: string }).code === WEB_WORKERS_CODES.STREAM_ABORTED ) { // expected when ctrl.abort() fires } else { throw err; } } ``` ## API Reference - `sendStream(manager, name, type, payload, options?): AsyncIterable`. - `interface StreamCapableManager { events; postMessage(name, type, payload, options?) }`. - `interface StreamRequestEnvelope { streamId: string; payload: TReq }`. - `type StreamIdFactory = () => string`. - `interface SendStreamOptions { signal?: AbortSignal; transfer?: Transferable[]; idFactory?: StreamIdFactory }`. ## Implementation Notes The iterator uses an internal queue + `resolveNext` continuation pattern: every received frame pushes onto the queue and calls `resolveNext` if the consumer was waiting; the consumer's next-iteration loop drains the queue and then awaits on a fresh promise. This avoids buffering the whole stream in memory and gives the consumer natural backpressure (the consumer pulls). The initial payload is wrapped in a `StreamRequestEnvelope` so the worker sees both the `streamId` and the original payload — without that wrapper the worker would have no way to correlate frame ids to incoming requests. The cancel frame `stream:cancel` is sent via the same `postMessage` channel so the worker side can stop producing without needing a separate control channel.