Alexandro.Net

Download README · GitHub release

@stackline/sse

Universal, spec-correct and memory-safe Server-Sent Events toolkit for AI streaming, browsers, servers and edge runtimes.

npm version license GitHub repository Docs Reddit community

Documentation | npm | Issues | Repository

Current package version: 1.0.4


Why this package?

One zero-dependency toolkit for consuming, parsing, encoding, serving, and reconnecting Server-Sent Events. It is designed for AI token streams, live interfaces, serverless runtimes, browsers, and Node.js services.

Why this package

SSE projects commonly combine one parser package, another encoder, a stale fetch wrapper, and custom server code. @stackline/sse gives those layers one consistent contract:

Compatibility

Item Value
Package @stackline/sse@1.0.4
Node.js runtime >=14.17.0
CommonJS / primary entry ./dist/index.cjs
ES module entry ./dist/index.js
Type declarations ./dist/index.d.ts

Runtime matrix

Runtime Parser / encoder Fetch client Server helpers
Modern browsers Yes Yes Yes
Node.js 18+ Yes Yes Yes
Node.js 14 / 16 Yes Inject fetch Inject Web Streams if needed
Deno 2 Yes Yes Yes
Bun Yes Yes Yes
Cloudflare Workers Yes Yes Yes

The package ships ESM, CommonJS, a browser IIFE, and declarations tested with TypeScript 3.9, 4.7, 4.9, 5.x, 6.x, and 7.x.

Installation

npm install @stackline/sse

Usage

npm install @stackline/sse

Parse any stream

Async iterator

import { decodeSSE } from '@stackline/sse';

const response = await fetch('/events');

for await (const event of decodeSSE(response)) {
  console.log(event.event, event.data, event.lastEventId);
}

The source can be a Response, ReadableStream, AsyncIterable, or ordinary Iterable of string and Uint8Array chunks.

Incremental callback parser

import { createParser } from '@stackline/sse';

const parser = createParser({
  onEvent(event) {
    console.log(event.data);
  },
  onRetry(milliseconds) {
    console.log('Server retry interval:', milliseconds);
  }
});

parser.feed('id: 7\ndata: first chunk\n');
parser.feed('data: second chunk\n\n');

Each event contains:

interface SSEEvent<T = string> {
  data: T;
  event?: string;
  id?: string;         // ID field in this event block
  lastEventId: string; // committed resume ID, including inherited IDs
}

An id-only block commits lastEventId even when no message is dispatched. That detail matters when a connection closes immediately after a checkpoint.

JSON streams

import { decodeJSON } from '@stackline/sse';

for await (const event of decodeJSON(response, {
  doneSentinel: '[DONE]'
})) {
  console.log(event.data); // parsed JSON value
}

Invalid JSON throws SSEParseError. Set ignoreInvalidJSON: true only when a mixed text and JSON protocol intentionally requires it.

Encode events

import { encodeJSON, encodeSSE } from '@stackline/sse';

encodeSSE({
  id: '42',
  event: 'delta',
  retry: 3000,
  data: 'line one\nline two'
});

encodeJSON({ token: 'hello' }, { event: 'delta', id: '43' });

id and event values cannot contain line breaks. IDs also reject NUL. This prevents a value from injecting additional SSE fields or HTTP resume headers.

Serve events

Response from an async generator

import { eventStreamResponse } from '@stackline/sse';

async function* updates() {
  yield { event: 'ready', data: 'connected', id: '1' };
  yield { event: 'delta', data: 'hello', id: '2' };
}

export function GET() {
  return eventStreamResponse(updates());
}

The response includes text/event-stream, no-cache, no-transform, and X-Accel-Buffering: no headers unless the caller overrides them.

Push channel

import { createSSEChannel } from '@stackline/sse';

const channel = createSSEChannel({
  heartbeatInterval: 15_000
});

const response = channel.toResponse();

if (!channel.sendJSON({ progress: 25 }, { event: 'progress' })) {
  await channel.ready;
}

channel.close();

send and sendJSON return false when the stream applies backpressure. Wait for channel.ready before producing more data.

Features and Integrations

AI streaming

import { fetchSSE } from '@stackline/sse';

const controller = new AbortController();

for await (const event of fetchSSE('https://api.example.com/responses', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.API_TOKEN}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ model: 'example-model', stream: true }),
  signal: controller.signal,
  connectTimeout: 10_000,
  idleTimeout: 45_000,
  totalTimeout: 5 * 60_000,
  retry: {
    retries: 3,
    minDelay: 500,
    maxDelay: 10_000
  }
})) {
  if (event.data === '[DONE]') break;
  console.log(event.event, JSON.parse(event.data));
}

fetchSSE accepts all ordinary fetch request options. Node.js 18 and newer provide fetch; Node.js 14 and 16 can pass an implementation with fetch.

Reconnection behavior

fetchSSE follows SSE resume semantics and adds explicit production controls:

Native EventSource reconnects indefinitely, so the default retry budget is also unlimited. Production applications should pass an AbortSignal, a finite retry.retries, or totalTimeout.

const options = {
  retry: {
    retries: 5,
    minDelay: 500,
    maxDelay: 30_000,
    factor: 2,
    jitter: 'full'
  },
  onRetry({ delay, reconnects, error }) {
    console.warn({ delay, reconnects, error });
  }
};

For a body that must be recreated on every attempt, bodyFactory receives the attempt number, committed resume ID, and that attempt's abort signal:

const options = {
  bodyFactory({ attempt, lastEventId, signal }) {
    return createUploadStream({ attempt, lastEventId, signal });
  }
};

When the input is a Request, its headers are preserved unless options.headers explicitly replaces them. The SSE Accept and resume headers are then merged case-insensitively.

Migration

From eventsource-parser

Direct dependency:

npm install @stackline/sse

The familiar API is available:

import { createParser } from '@stackline/sse';

For a low-change trial, npm aliases preserve the old import name:

npm install eventsource-parser@npm:@stackline/sse

createParser({ onEvent, onRetry, onComment, onError, maxBufferSize }) is supported. The additional lastEventId property follows WHATWG resume semantics. Security limits are enabled by default, unlike unbounded parsers.

From @microsoft/fetch-event-source

npm install @stackline/sse
import { fetchEventSource } from '@stackline/sse';

await fetchEventSource('/events', {
  onopen(response) {},
  onmessage(event) {},
  onclose(context) {},
  onerror(error) {}
});

An alias can support staged migration:

npm install @microsoft/fetch-event-source@npm:@stackline/sse

The callback names are supported. openWhenHidden is accepted but this package does not silently disconnect a healthy stream when a page becomes hidden.

Adoption resources

The examples ship in the npm tarball. Network examples expose functions and do not send requests during installation or import.

Security

Memory safety

The parser is bounded by default:

Limit Default Purpose
maxLineLength 1 MiB unterminated or oversized field line
maxEventSize 1 MiB accumulated multiline event
maxQueuedEvents 4096 callback burst inside one feed slice
feedSize 16 KiB limits work admitted before yielding

Raise a limit explicitly for a trusted protocol that carries larger events. Limit failures terminate the parser with a stable ERR_SSE_* code.

API Surface

Errors

Class Code Meaning
SSEParseError ERR_SSE_PARSE and specific variants malformed or limited stream
SSEEncodeError ERR_SSE_ENCODE unsafe or unsupported output field
SSEHTTPError ERR_SSE_HTTP rejected HTTP response
SSETimeoutError ERR_SSE_TIMEOUT connect, idle, or total deadline
SSERetryError ERR_SSE_RETRY finite reconnect budget exhausted
SSEReplayError ERR_SSE_BODY_REPLAY non-replayable request body

Local Development

git clone https://github.com/alexandroit/stackline-sse.git
cd stackline-sse
npm ci
npm run test

Release tooling uses Node.js 24.20.0 and npm 11.19.0. The consumer runtime contract remains the one documented above.

Consumer Smoke Test

Run the repository's existing consumer/package check after installing development dependencies:

npm run test:install

Release Checklist

Package integrity

See SECURITY.md for vulnerability reporting and CONTRIBUTING.md for development instructions.

Run npm run test and inspect the package contents before release. Publish a new version through the GitHub Actions publishing workflow, using the SHA-512 digest of the reviewed tarball. Verify the exact published version, tarball integrity, and npm provenance after the run.

License

MIT Copyright 2026 Alexandro Paixao Marques.

Credits and original authors

Original copyright, license notices and contributor acknowledgements remain part of this distribution. Stackline maintenance does not replace authorship of the original work.

Community and Links

Use this repository's issue tracker for reproducible bugs and feature requests. Join r/Stackline for examples, usage questions and release discussions.