Skip to content

The SSE parsing pipeline

Convert a streamed HTTP response into ReadableStream<ServerSentEvent>. This package parses an existing fetch response; it is not EventSource and does not reconnect or resend Last-Event-ID automatically.

Use the direct converter when the caller deliberately accepts the body as SSE even without an SSE Content-Type. For a protocol-checked response, prefer requiredEventStream() from the Response helpers. Neither route checks status on its own; Fetcher supplies status validation before result extraction.

Conversion stages

toServerSentEventStream(response: Response): ServerSentEventStream requires a non-null body, otherwise throws EventStreamConvertError(response, 'Response body is null'). It connects response.body → TextDecoderStream('utf-8') → TextLineTransformStream → ServerSentEventTransformStream. This direct converter does not validate status or content type. The body becomes locked by the pipeline and cannot be independently read at the same time.

APIInput → outputConfiguration
TextLineTransformerstring chunks → strings without line endingsNo constructor arguments; retains partial-line state.
TextLineTransformStreamTransformStream wrapper of the aboveNo arguments.
ServerSentEventTransformerLines → ServerSentEventNo arguments; retains event state.
ServerSentEventTransformStreamTransformStream wrapper of the aboveNo arguments.
ServerSentEventStreamAlias for ReadableStream<ServerSentEvent>A one-consumer stream, not an event bus.

Text lines support LF, CR, and CRLF, including CR/LF split across chunks. An unterminated final nonempty line is flushed. Network chunk boundaries need not match lines or events, and UTF-8 decoding handles split bytes before line parsing.

Event fields and boundaries

ServerSentEvent has required event: string and data: string, optional id?: string and retry?: number. ServerSentEventFields exposes static constants ID = 'id', EVENT = 'event', DATA = 'data', and RETRY = 'retry'.

Blank lines dispatch only when at least one data field has been seen. Multiple data lines join with \n. A comment line beginning : and unknown fields are ignored. Field/value split occurs at the first colon, removing at most one leading space from its value; a colonless line has an empty value. Event name defaults to 'message' on each event. Output id defaults to ''; id and retry persist between events until updated. IDs containing NUL are ignored, and retry accepts ASCII digits only.

On normal EOF the parser emits a pending data event even without a final blank line. An id-only/retry-only frame updates state but emits no event. Retry is metadata only: the parser does not schedule reconnection. See JSON decoding for typed data and protocol-specific terminal markers.

Complete example

ts
import { toServerSentEventStream } from '@ahoo-wang/fetcher-eventstream';

const encoder = new TextEncoder();
const response = new Response(
  new ReadableStream<Uint8Array>({
    start(controller) {
      for (const chunk of [
        'id: 1\r',
        '\ndata: hel',
        'lo\r\n\r\n',
        'data: tail',
      ]) {
        controller.enqueue(encoder.encode(chunk));
      }
      controller.close();
    },
  }),
);
const events = [];
for await (const event of toServerSentEventStream(response)) events.push(event);
console.assert(events.length === 2 && events[0].data === 'hello');
console.assert(events[1].data === 'tail' && events[1].id === '1');

Public symbols and source

SymbolImplementation
ServerSentEventStreameventStreamConverter.ts:31
toServerSentEventStreameventStreamConverter.ts:127
ServerSentEventserverSentEventTransformStream.ts:21
ServerSentEventFieldsserverSentEventTransformStream.ts:35
ServerSentEventTransformerserverSentEventTransformStream.ts:88
ServerSentEventTransformStreamserverSentEventTransformStream.ts:178
TextLineTransformertextLineTransformStream.ts:23
TextLineTransformStreamtextLineTransformStream.ts:71

Package index

Released under the Apache License 2.0.