chunkwise.

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);
}
ArgumentNotes
sourceA WHATWG ReadableStream or a Node Readable. Node streams are wrapped, not copied.
sizePositive 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
Multipart ranges (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

RuntimeStatus
Node 18, 20, 22Tested in CI
Deno 1.40+Works, not in CI
Chrome, Firefox, Safari 16.4+Tested in CI
Node 16 and belowUnsupported since 1.0