Skip to content

records

Eugene Lazutkin edited this page Jul 3, 2026 · 1 revision

Records

io.records consumes streamed record responses — JSONL / NDJSON and RFC 7464 application/json-seq — as a lazy async iterable of parsed records. One record at a time, memory stays flat, and the request fires on the first iteration.

import io from 'double-meh';

for await (const user of io.records.get('https://api.example.com/users/export')) {
  process(user); // already parsed — {id: 42, name: 'Bob', ...}
}

io.records.get(url, query?, options?) / io.records.post(url, data?, options?)

Both return an AsyncIterableIterator of parsed records. get takes a query as the 2nd argument; post sends the 2nd argument as the request body (a streamed search is the classic case). The request runs through the full pipeline (inspectors, retry if opted in); stream: true is implied, so the response is never tracked or cached.

  • Framing is negotiated from the response content type: a type containing json-seq selects RS-delimited RFC 7464 framing; anything else parses as JSONL/NDJSON (one JSON value per line, blank lines ignored). Force it with {framing: 'jsonl' | 'json-seq'}.
  • The default Accept is application/x-ndjson, application/json-seq (overridable with accept).
  • Lazy and cancelable: nothing is sent until the first next(); breaking out of the loop cancels the response stream and releases the connection; an AbortSignal in options.signal stops iteration even when a custom transport ignores signals.

Errors

  • A non-2xx reads the streamed error body first and throws a BadStatus whose data is the parsed body (problem+json friendly) — you never receive an error carrying an unread stream.
  • A malformed record throws a FailedIO with the SyntaxError on .cause; records before it are delivered normally.
try {
  for await (const row of io.records.post('https://api.example.com/search', {q: 'audio'})) {
    render(row);
  }
} catch (error) {
  if (error instanceof io.BadStatus) console.error(error.status, error.data);
}

Where stream-chain takes over

io.records is the consume-simply path. For record processing pipelines (transforms, fan-out, backpressure tuning, file edges) and for the upload/encode side, use stream-chain on top of the byte-level io.stream surface — see Cookbook: streaming pipelines for the recipes. double-meh deliberately ships no JSONL stringer or pipeline machinery of its own.

Clone this wiki locally