All questions

SSE Frame Decoder

Premium

SSE Frame Decoder

Server-Sent Events (SSE) is the text protocol behind streaming HTTP responses — it is how the browser's EventSource, and most LLM streaming APIs, push a sequence of events over one long-lived connection. The catch is that the network delivers that text in arbitrary chunks that rarely line up with event boundaries: you receive half a line, then the rest of it plus two more, then a lone fragment. A frame decoder buffers those chunks and emits whole, parsed events only once each one is complete.

Implement sseFrameDecoder(). It returns an object with a push(chunk) method. Feed it string chunks in any split, and each call returns an array of the events that became complete during that call (often empty).

The wire format: an event is a group of lines terminated by a blank line, and each line is field: value. The fields to handle:

  • data — the payload. Multiple data lines in one event are joined with a newline.
  • event — the event type. Defaults to message when absent.
  • id — an identifier that sticks: once set, it carries to later events until changed.

A line beginning with : is a comment (a keep-alive) and is ignored. Exactly one space after the colon is stripped.

Signature

function sseFrameDecoder(): {
  push(chunk: string): Array<{ type: string; data: string; id?: string }>;
};

Examples

const d = sseFrameDecoder();
d.push('data: hello\n\n');
// → [{ type: 'message', data: 'hello' }]
const d = sseFrameDecoder();
d.push('event: ping\ndata: tick\n'); // → []  (no blank line yet)
d.push('\n');                        // → [{ type: 'ping', data: 'tick' }]

Notes

  • Dispatch on the blank line — an event is emitted only when a blank line closes its block; a block still open at the end of a chunk waits for the next one.
  • Buffer across chunks — a chunk may end mid-line, so hold the partial line and finish it when more text arrives.
  • Join multiple datadata: a then data: b in one block become a\nb.
  • Sticky id — reuse the last id on events that do not set their own.
  • Emit only real blocks — a lone comment or a stray blank line produces nothing.
  • Out of scope — the retry field, byte (Uint8Array) input, and the strict spec rule that an empty-data block is not dispatched.

FAQ

Why can't I just split the whole response on a blank line?
Because the network delivers the response in chunks that do not line up with event boundaries. A chunk can end mid-line, so you must buffer the partial tail across calls and only dispatch an event when a blank line actually closes its block.
How are multiple data lines in one event combined?
They are joined with a single newline. `data: a` followed by `data: b` in the same block produces the payload `a\nb`.
Does the id reset between events?
No. The id is sticky: once an `id` field sets it, later events that omit their own id inherit the last value until a new `id` field changes it.

Unlock the solution & editor

  • Runnable editor + tests

    Solve in the browser with instant Jest feedback.

  • Detailed solutions

    Walkthroughs, edge cases, and complexity notes.

  • Multi-framework variants

    React, Vue, Vanilla, Angular — same question, different stacks.

Upgrade to Premium