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

SSE

io.sse subscribes to Server-Sent Events as a lazy async iterable of parsed events, with the reconnect semantics of EventSource — but riding the double-meh pipeline, so it can send Authorization and custom headers, use POST, and re-run request inspectors on every reconnect (fresh tokens included). Native EventSource can do none of that.

import io from 'double-meh';

for await (const event of io.sse('https://api.example.com/notifications')) {
  console.log(event.event, event.data, event.id);
}

io.sse(url, data?, options?)

Returns an AsyncIterableIterator of {data, event, id}:

  • data — the event payload; multiple data: lines are joined with \n. Parse JSON payloads yourself (JSON.parse(event.data)).
  • event — the event type; 'message' when the server didn't name one.
  • id — the current last-event id (the stream position), or undefined.

The parser implements the SSE wire format: data: / event: / id: / retry: fields, : comments (keep-alives) are skipped, and \r\n, \n, \r line endings all work. data is the query for a GET; pass {method: 'POST'} to stream events from a POST endpoint (the common shape for LLM APIs).

Reconnects

A dropped connection reconnects automatically, EventSource-style:

  • The delay is io.sse.reconnectDelay (3000 ms) by default; a per-call number sets it ({reconnect: 500}), and the server's retry: hint overrides it.
  • Each reconnect carries the Last-Event-ID header so the server can resume the stream. Seed a resume across process restarts with {lastEventId: savedId}.
  • {reconnect: false} reads one connection and ends when the stream closes.
  • A 204 response ends the subscription — the server's "no more events" signal.
  • An abort (options.signal) stops immediately and never reconnects; breaking out of the loop cancels the stream and stops too.

Errors

  • A non-2xx is fatal: the streamed error body is read and a BadStatus with the parsed data is thrown.
  • A response whose content type isn't text/event-stream is fatal (FailedIO) — it usually means the endpoint isn't an SSE endpoint at all.
  • A mid-stream network failure is not fatal — that's what the reconnect loop is for.
const controller = new AbortController();

try {
  for await (const event of io.sse('https://api.example.com/feed', null, {
    signal: controller.signal,
    lastEventId: localStorage.getItem('feed-position') ?? undefined
  })) {
    handle(JSON.parse(event.data));
    if (event.id) localStorage.setItem('feed-position', event.id);
  }
} catch (error) {
  if (error.name !== 'AbortError') throw error;
}

The article-style push channel for cache invalidation is a natural consumer: subscribe with io.sse, call io.cache.remove(pattern) per event.

Clone this wiki locally