/** * Adapter-agnostic streaming responses. A handler returns * `stream(source, { contentType, … })`; each adapter sends the bytes against * its own transport without buffering them (Fastify `reply.send(stream)`, * Express `pipeline()`, a `Response` over a web stream on Hono). * * Same idiom as {@link sse}: the helper returns an inert marker object, the * pipeline hands it back to the adapter untouched, and the adapter renders it. */ import { Readable } from 'node:stream'; /** What a streaming response may be built from. */ export type StreamSource = Readable | ReadableStream | AsyncIterable; export interface StreamOptions { /** Response media type. Default `application/octet-stream`. */ contentType?: string; /** * Exact body size in bytes, when it is known (a stored file's size). Sent as * `Content-Length` so clients can show progress; omit it and the response is * chunked. Never guess: a wrong value truncates or hangs the download. */ contentLength?: number; /** * Download filename. Sent as `Content-Disposition`, sanitised * ({@link sanitizeFilename}) and encoded per RFC 5987 — a client-supplied * name can carry no CR/LF, quotes or path separators into the header. */ filename?: string; /** * How the browser should treat the body. Default `attachment` — an uploaded * HTML/SVG file must never render on your origin. Only set `inline` when * rendering it is deliberate. Ignored unless `filename` is given. */ disposition?: 'attachment' | 'inline'; /** Extra response headers (CR/LF stripped from every value). */ headers?: Record; /** Response status. Default `200`. */ status?: number; } /** What the adapter needs to send a {@link stream} response. */ export interface StreamPayload { source: StreamSource; status: number; /** Lower-cased header names, ready to write. */ headers: Record; } declare const STREAM: unique symbol; export interface StreamResponse { readonly [STREAM]: StreamPayload; } /** * Wrap a byte source as a streaming response for a route handler to return. * * ```ts * const { record, stream: body } = await files.downloadStream(id) * return stream(body, { contentType: record.contentType, contentLength: record.size, filename: record.name }) * ``` * * The bytes are never collected in memory: a slow client slows the source * (real backpressure), and a client that disconnects destroys it, so no file * descriptor or backend socket is leaked. */ export declare function stream(source: StreamSource, options?: StreamOptions): StreamResponse; export declare function isStreamResponse(value: unknown): value is StreamResponse; export declare function streamPayloadOf(value: StreamResponse): StreamPayload; /** * A `Content-Disposition` value for a client-supplied filename: the name goes * through {@link sanitizeFilename} (no directories, control characters or bidi * overrides), the plain `filename=` parameter keeps only printable ASCII with * quotes and backslashes replaced, and anything lost that way is carried by an * RFC 5987 `filename*=UTF-8''…` parameter. */ export declare function contentDisposition(filename: string, disposition?: 'attachment' | 'inline'): string; /** * The source as a Node `Readable`, for the adapters whose transport is a Node * response. Destroying the returned stream releases the original source: a web * stream is cancelled, an async iterable gets its `return()`. */ export declare function toNodeStream(source: StreamSource): Readable; /** * Releases a source nobody will read — a `HEAD` request, or a response the * adapter is about to abandon. Never throws. */ export declare function destroyStreamSource(source: StreamSource, error?: Error): void; /** * A pull reader over any {@link StreamSource}: one chunk at a time, with an * explicit `close()`. Used by the adapters whose transport is a web * `ReadableStream`, where pulling on demand is what makes backpressure real. */ export interface StreamPump { /** The next chunk, or `null` at the end of the source. */ next(): Promise; /** Releases the source (destroy/cancel/`return`). Idempotent, never throws. */ close(error?: Error): Promise; readonly closed: boolean; } export declare function streamPump(source: StreamSource): StreamPump; /** * Pulls the first chunk before any header is flushed, so a source that fails * immediately (a deleted object, a refused S3 request) can still become a * normal JSON error response instead of a truncated body. The source is * released before the failure is rethrown. */ export declare function openStreamPump(source: StreamSource): Promise<{ pump: StreamPump; first: Uint8Array | null; }>; /** * A Node `Readable` over a pump whose first chunk has already been pulled. * Destroying it (the client disconnected, the response was torn down) closes * the pump, which destroys the original source. */ export declare function nodeStreamFrom(pump: StreamPump, first: Uint8Array | null): Readable; /** * A web `ReadableStream` over a pump whose first chunk has already been pulled. * `pull` runs only when the consumer has room, so a slow client slows the * source; `cancel` (the client disconnecting) closes it. */ export declare function webStreamFrom(pump: StreamPump, first: Uint8Array | null, onError?: (error: unknown) => void): ReadableStream; export {};