Skip to content
LogoLogo

@portable/jsonl

Classes

Jsonl.DecoderStream

A TransformStream that decodes NDJSON (newline-delimited JSON, also called JSON Lines) into values, one per line. Lines may end in LF or CRLF. Blank and whitespace-only lines are skipped. Each line is parsed with JSON.parse, so a null line is emitted as null.

The stream fails with InvalidInputError on a line that isn't valid JSON, unless invalid is 'skip', and with MaxLengthError on a line longer than maxLength.

class DecoderStream extends TransformStream<
  Uint8Array | string,
  unknown
> {
  constructor(options: DecoderStream.Options = {});
}
OptionTypeDefaultDescription
signalAbortSignalErrors the stream with signal.reason when aborted, which cancels the source it is piped from.
invalid"error" | "skip"'error'What to do with a line that isn't valid JSON: fail the stream, or drop the line and continue.
maxLengthnumberInfinityLongest line, in UTF-16 code units, line ending excluded. A longer line fails the stream with MaxLengthError as soon as it grows past the limit, so a line without a newline can't use unbounded memory.
import * as Jsonl from '@portable/jsonl';
 
const response = await fetch('https://example.com/events.ndjson');
const events = response.body!.pipeThrough(new Jsonl.DecoderStream());

Jsonl.EncoderStream

A TransformStream that encodes each value as one line of NDJSON: JSON.stringify(value) followed by \n, as UTF-8 bytes. If JSON.stringify can't encode a value, such as undefined, a function, a symbol, a BigInt or a circular structure, the stream fails with a TypeError instead of dropping the line or writing a malformed one.

class EncoderStream extends TransformStream<
  unknown,
  Uint8Array<ArrayBuffer>
> {
  constructor(options: EncoderStream.Options = {});
}
OptionTypeDefaultDescription
signalAbortSignalErrors the stream with signal.reason when aborted, which cancels the source it is piped from.
import * as Jsonl from '@portable/jsonl';
import * as Streams from '@portable/streams';
 
const body = Streams.fromIterable(rows).pipeThrough(
  new Jsonl.EncoderStream(),
);
return new Response(body, {
  headers: { 'content-type': 'application/x-ndjson' },
});

Errors

Jsonl.InvalidInputError

Thrown when a line isn't valid JSON. The message gives the line number, and cause holds the SyntaxError from JSON.parse.

class InvalidInputError extends Errors.InvalidInputError {
  readonly kind: string;
}
import * as Jsonl from '@portable/jsonl';
import * as Streams from '@portable/streams';
 
try {
  await Streams.toArray(body.pipeThrough(new Jsonl.DecoderStream()));
} catch (error) {
  if (error instanceof Jsonl.InvalidInputError) {
    return new Response('Bad NDJSON', { status: 400 });
  }
  throw error;
}

Jsonl.MaxLengthError

Thrown when a line grows past maxLength, before it is parsed.

class MaxLengthError extends Errors.MaxLengthError {
  constructor(options: Errors.MaxLengthError.Options);
  readonly kind: string;
}
import * as Jsonl from '@portable/jsonl';
import * as Streams from '@portable/streams';
 
try {
  await Streams.toArray(
    body.pipeThrough(new Jsonl.DecoderStream({ maxLength: 1_000_000 })),
  );
} catch (error) {
  if (error instanceof Jsonl.MaxLengthError) {
    return new Response(null, { status: 413 });
  }
  throw error;
}