2.1 KiB
range/range-stream-parse.ts
Purpose
Streams an HTTP response body line-by-line through a TextDecoderStream
and a newline framer, yielding { suffix, count } records as soon as
each line crosses a complete byte boundary. The parser deliberately
never calls Response.text() and never collects the body into a single
string buffer, so even a 30 KiB Add-Padding response is processed in
bounded memory. Mid-flight AbortSignal is honoured between chunks.
Features
- Async generator interface — drop in to
for awaitloops. - CRLF and LF newlines both work; trailing line without newline is emitted; empty lines are skipped.
- Per-line validation delegated to
parseRangeLineso strict-mode rejection happens at first-fault. - AbortSignal threading: pre-aborted and mid-flight aborts both throw
HIBP.ABORTEDand the underlying reader is released in afinallyguard.
Usage: Basic Example
import { streamParseRangeBody } from "@suaveplan/hibp";
for await (const line of streamParseRangeBody(response.body!, { strict: true })) {
if (line.suffix === expected) /* ... */;
}
Usage: Advanced Example
import { streamParseRangeBody } from "@suaveplan/hibp";
const ac = new AbortController();
const iter = streamParseRangeBody(response.body!, {
strict: false,
signal: ac.signal,
});
setTimeout(() => ac.abort(), 100);
try {
for await (const _line of iter) { /* ... */ }
} catch (err) {
if ((err as { code?: string }).code === "HIBP.ABORTED") {
// user cancelled
}
}
API Reference
streamParseRangeBody(body, { strict, signal? }): AsyncGenerator<RangeLine>.
Implementation Notes
The TextDecoderStream typings model the writable side as
BufferSource, which trips the structural pipeThrough constraint, so
the parser casts at the boundary. Calls to Response.text() are
forbidden by the spec — the test suite asserts zero invocations. The
generator's finally clause releases the underlying reader even when
the consumer breaks out early, preventing leaked readable streams when
a caller chooses to short-circuit on the first match.