API reference
Every export is a plain function. There is no configuration object and no global state.
Installation
npm i chunkwise pnpm add chunkwise yarn add chunkwise
The package ships ESM and CommonJS builds plus type declarations. Named imports are the only supported form; there is no default export.
Types
interface Range { start: number; // inclusive end: number; // inclusive, per RFC 7233 }
Both ends are inclusive. This trips people up coming from Array.slice, but it matches the wire format, and converting once at the boundary is less error-prone than converting on every use.
chunkBy
chunkBy(source: ReadableStream | Readable, size: number): AsyncIterable<Uint8Array>
Yields buffers of exactly size bytes until the source is exhausted. The last chunk contains whatever remains and may be shorter.
for await (const chunk of chunkBy(stream, 16 * 1024)) { hash.update(chunk); }
| Argument | Notes |
|---|---|
source | A WHATWG ReadableStream or a Node Readable. Node streams are wrapped, not copied. |
size | Positive integer. Values above 1 MiB rarely help and increase peak memory. |
Breaking out of the loop cancels the underlying stream. If you need the remainder later, tee the stream first.
parseRange
parseRange(header: string | undefined, total: number): Range | null
Parses a single-range Range request header and clamps it to total. Returns null when the header is absent, malformed, or cannot be satisfied — respond with 416 in that case.
parseRange('bytes=0-499', 1000) // { start: 0, end: 499 } parseRange('bytes=500-', 1000) // { start: 500, end: 999 } parseRange('bytes=-200', 1000) // { start: 800, end: 999 } parseRange('bytes=900-', 100) // null
bytes=0-99,200-299) are not supported and return null. They are vanishingly rare outside PDF readers, and supporting them properly means emitting a multipart body.formatContentRange
formatContentRange(range: Range, total: number): string
Builds the matching Content-Range response header.
formatContentRange({ start: 0, end: 499 }, 1000) // 'bytes 0-499/1000'
throttle
throttle(source: ReadableStream, bytesPerSecond: number): ReadableStream
Returns a stream that delivers at most bytesPerSecond bytes per second, measured as a token bucket over accumulated bytes rather than elapsed time. Bursts up to one second of budget are allowed.
const paced = throttle(file.stream(), 2 * 1024 * 1024);
Backpressure propagates, so a slow consumer slows the source rather than filling an internal queue.
readAll
readAll(source: ReadableStream, opts?: { limit?: number }): Promise<Uint8Array>
Collects a stream into one buffer. limit defaults to 8 MiB; exceeding it rejects with a LimitExceededError and cancels the source.
const body = await readAll(req.body, { limit: 1 << 20 });
Error handling
Two error classes are exported: LimitExceededError and RangeParseError. The second is only thrown by the strict parser; parseRange itself never throws and returns null instead, because in a request handler a null check is easier to get right than a try block.
Compatibility
| Runtime | Status |
|---|---|
| Node 18, 20, 22 | Tested in CI |
| Deno 1.40+ | Works, not in CI |
| Chrome, Firefox, Safari 16.4+ | Tested in CI |
| Node 16 and below | Unsupported since 1.0 |