@geee-be/sse-stream-parser
v1.0.2
Published
Tiny zero-dependency Server-Sent Events (SSE) stream parser for browsers and Node.js.
Downloads
137
Maintainers
Readme
@geee-be/sse-stream-parser
Tiny, zero-dependency Server-Sent Events (SSE) stream parser for browsers and Node.js. Feed it chunks from a streaming response and get normalized SSE events back.
- Minimal and fast: no deps, one small function
- Handles multiline data, comments, and CRLF normalization
- Works with Web Streams (
ReadableStream) viagetReader()and with strings
Install
# pnpm (recommended)
pnpm add @geee-be/sse-stream-parser
# npm
npm i @geee-be/sse-stream-parser
# yarn
yarn add @geee-be/sse-stream-parserQuick start
Browser (fetch streaming)
import { createSSEParser } from '@geee-be/sse-stream-parser';
const parser = createSSEParser((e) => {
console.log('event:', e.event); // e.g. "message" (default) or custom type
console.log('data:', e.data);
console.log('id:', e.id);
console.log('retry:', e.retry);
});
const res = await fetch('/sse-endpoint');
if (!res.body) throw new Error('No body');
const reader = res.body.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
if (value) parser.feedBytes(value);
}
parser.finish();Node.js (v18+ with global fetch)
import { createSSEParser } from '@geee-be/sse-stream-parser';
const parser = createSSEParser((e) => {
// handle event
});
const res = await fetch('https://example.com/sse');
if (!res.body) throw new Error('No body');
const reader = res.body.getReader();
for (;;) {
const { value, done } = await reader.read();
if (done) break;
if (value) parser.feedBytes(value);
}
parser.finish();If you already have text (not bytes), you can call parser.feedText(str) instead.
Convenience helper: consumeSSEStream
If you have a Response and want a simple one-liner, use the built-in stream consumer:
import { consumeSSEStream } from '@geee-be/sse-stream-parser';
const res = await fetch('https://example.com/sse');
if (!res.body) throw new Error('No body');
await consumeSSEStream(res.body, (e) => {
console.log(e.event, e.data);
});API
createSSEParser(onEvent)
Creates a parser instance.
- Input
onEvent: (e: SSEEvent) => void— called whenever a complete SSE message (double-newline terminated) is parsed
- Output
- Returns an object:
{ feedBytes(chunk: Uint8Array), feedText(text: string), finish(): void }
- Returns an object:
SSEEvent shape
type SSEEvent = {
event?: string; // defaults to 'message' when not provided in the block
data: string; // joined with "\n" across multiple data: lines
id?: string; // set only if the block contained an id: field
retry?: number; // set only if the block contained a retry: field (parsed as base-10 integer)
};Behavior details
- Lines are normalized:
\r\nbecomes\n. - Comment lines (starting with
:) are ignored. - Each
data:line appends to the current event payload. The finaldatavalue is the\n-joined result. - An empty line (
"") ends the current event and triggersonEvent. event:sets the event type (defaults to"message"if not present).id:sets the event ID for the current event only.retry:is parsed as an integer; invalid numbers are ignored.
Note: This parser does not persist id or retry values across events. They only appear on the event when explicitly present in that event block.
Why use this?
- You want to consume SSE streams without
EventSource, e.g., custom auth/headers or Node.js environments. - You already have a byte stream from
fetchand want a small, well-typed parser to turn it into events.
Examples
From a string buffer
import { createSSEParser } from '@geee-be/sse-stream-parser';
const out: any[] = [];
const parser = createSSEParser((e) => out.push(e));
parser.feedText('event: ping\n');
parser.feedText('data: one\n');
parser.feedText('data: two\n');
parser.feedText('\n'); // end of event
// out[0] -> { event: 'ping', data: 'one\ntwo' }TypeScript
The source is TypeScript and fully typed. Import it in TS or JS projects:
import { createSSEParser } from '@geee-be/sse-stream-parser';Limitations
- This is a low-level parser for SSE wire format. It does not handle reconnects, backoff, or auto-retry.
finish()is currently a no-op; it exists for symmetry and future extension.
Development and releasing (Changesets)
Use Changesets to track changes and publish.
Prereqs
- Node 20+, pnpm installed
- NPM publishing token configured in GitHub repo secrets as
NPM_TOKEN - Default branch is
main
Local dev
- Install:
pnpm install - Build:
pnpm build - Test:
pnpm test
- Install:
Record a change
- Run:
pnpm changeset - Choose bump type (patch/minor/major), write a short summary
- Commit the generated file under
.changeset/along with your code changes
- Run:
CI and releases
- On push/PR to
main, CI builds and tests - After merging feature PRs, the Release workflow opens/updates a “Version Packages” PR
- Merge that PR to publish to npm (uses
NPM_TOKEN); versions and changelog are committed automatically
- On push/PR to
Manual release (optional)
- Version packages and build:
pnpm version - Publish to npm:
pnpm release
- Version packages and build:
License
MIT © Contributors
