Releases: MatthewWid/better-sse
v0.16.1
Better SSE Version 0.16.1
npm install better-sse@latestyarn add better-sse@latestpnpm add better-sse@latestThis release fixes a minor crash when using frameworks that emulate but do not fully implement the Node HTTP/1 or HTTP/2 Compatibility APIs.
Changes
Crash fix for Node connection adapters
This fixes a crash that occurred when the Node web framework did not define the setNoDelay method on the request or response socket objects.
Full Changelog
Fixed
- Fixed a crash that occurred when the underlying socket of the Node request or response did not define an implementation of the
setNoDelaymethod.
v0.16.0
Better SSE Version 0.16.0
npm install better-sse@latestyarn add better-sse@latestpnpm add better-sse@latestThis release adds connection adapters that enable Better SSE to be truly be compatible with any protocol, framework or runtime environment.
Changes
Connection Adapters
Where sessions manage the request and response lifecycle, the formatting of data and other SSE-specific behaviour, connection adapters implement the underlying connection itself, such as extracting request headers and query parameters, sending response headers and data chunks, and reporting when the connection closes.
Sessions already use several built-in adapters to provide support for the Fetch API and Node HTTP/1 and HTTP/2 Compatibility APIs. Now, these adapters are made public and can be used to create your own custom compatibility layers simply by extending from one of the built-in adapters or the base Connection class itself and implementing its abstract properties and methods:
import { Connection } from "better-sse"
export class MyCustomConnection extends Connection {
url: URL
request: Request
response: Response
sendHead = (): void => {}
sendChunk = (chunk: string): void => {}
cleanup = (): void => {}
}import { createSession } from "better-sse"
import { MyCustomConnection } from "./MyCustomConnection"
app.get("/sse", async (req, res) => {
const connection = new MyCustomConnection()
const session = await createSession(connection)
session.push("Universal compatibility!")
})See the documentatin for more:
Full Changelog
Added
- Added support for passing custom connection adapters to the
Sessionconstructor that enables compatibility with any protocol, framework or runtime environment.
Changed
- Update event ID generation to use
randomUUIDfrom the Web Crypto API instead ofnode:crypto. - Update Node HTTP/1 and HTTP/2 Compatibility adapters to disable Nagle's algorithm on the underlying socket to reduce latency.
Fixed
- Fixed a warning being printed by the Node internals when adding more than ten listeners to events emitted by sessions and channels.
- Fixed a crash that occurred when passing a
Requestbut no correspondingResponseobject from the Fetch API to theSessionconstructor. - Fixed a crash that occurred when the request had no
Hostheader when using the Node HTTP/1 or Node HTTP/2 Compatibility API.
v0.15.1
Better SSE Version 0.15.1
npm install better-sse@latestyarn add better-sse@latestpnpm add better-sse@latestThis release publishes Better SSE on the JavaScript Registry (JSR).
Changes
Publishing on the JSR
Better SSE is now available as a package on the JSR under the name @mwid/better-sse.
You can now install it from the JSR instead of npm, if you prefer:
npx jsr add @mwid/better-sseyarn add jsr:@mwid/better-ssepnpm add jsr:@mwid/better-ssebunx jsr add @mwid/better-ssedeno add jsr:@mwid/better-sseWhen using Deno you can import the package directly like so:
import { createSession } from "jsr:@mwid/better-sse"Full Changelog
Added
- Added support for publishing on the JavaScript Registry (JSR).
Fixed
- Fixed the
stream,iterateandEventBuffer#clearmethods not having explicit return types, resulting in slow inference in certain environments.
v0.15.0
Better SSE Version 0.15.0
npm install better-sse@latestyarn add better-sse@latestpnpm add better-sse@latestThis release brings support for web standards to Better SSE.
Most notably, sessions can now take in a Request and an optional Response object, enabling compatibility with the growing number of modern web-server frameworks based on the Fetch API.
This means the following frameworks and runtimes, among others, are now supported by Better SSE:
See the Recipes section of the documentation for example usage with various Fetch-based frameworks.
Changes
Sessions now support the Fetch API
Sessions can now take in a Request and an optional Response object, enabling compatibility with modern web-server frameworks based on the Fetch API.
Its overloads are now:
new Session(IncomingMessage, ServerResponse, SessionOptions?)
new Session(Http2ServerRequest, Http2ServerResponse, SessionOptions?)
new Session(Request, Response, SessionOptions?)
new Session(Request, SessionOptions?)To get started, use the new createResponse function to create a new session and return a corresponding Response object from your route handler. For example (using Hono):
app.get("/sse", (c) =>
createResponse(c.req.raw, (session) => {
session.push("Hello world!")
})
)Why a new createResponse function instead of createSession?
You can still use createSession when using the Fetch API, but createResponse removes the small amount of boilerplate code necessary to do so.
After creating a session we must wait for the underlying connection to be initialized before we can begin pushing events to it.
When using the Node HTTP/1 or HTTP/2 APIs we can flush the response headers and any other preamble data ahead of time and thus immediately begin pushing events to the session:
// Express uses the Node HTTP/1 API
app.get("/sse", async (req, res) => {
const session = await createSession(req, res)
session.push("Hello world!")
})When using a Fetch-based framework, however, we must first return a Response object from our route handler for the session to able to connect and send data.
We can either do this manually (using the new getResponse method):
// Hono uses the Fetch API
app.get("/sse", async (c) => {
const session = await createSession(c.req.raw)
session.addListener("connected", () => {
session.push("Hello world!")
})
return session.getResponse()
})Or use the shorthand createResponse utility function:
app.get("/sse", (c) =>
createResponse(c.req.raw, (session) => {
session.push("Hello world!")
})
)The previous two code snippets are equivalent.
Once you have a connected session instance passed to the callback function you can use it the exact same as you would before, pushing events or registering it to channels.
For more details, see the original pull request and the API reference for createResponse.
Support for web streams
The Session#stream and EventBuffer#stream methods can now accept a ReadableStream from the standardised Web Streams API:
const stream = ReadableStream.from([1, 2, 3])
await session.stream(stream)⚠️ BREAKING: Omit response headers with undefined instead of an empty string
Previously, if you passed an empty string as a header value in the Session constructor options it would be omitted from the response headers:
// Before
res.setHeader("Connection", "keep-alive")
res.setHeader("Cache-Control", "no-cache")
res.setHeader("Content-Type", "text/html")
const session = await createSession(req, res, {
headers: {
Connection: "overwritten",
"Cache-Control": "",
"Content-Type": undefined,
},
})
/**
* Sent headers:
* {
* Connection: "overwritten"
* Content-Type: "text/html"
* }
*/This behaviour has now been changed. Passing an empty string will now simply override the header value with an empty string, while passing undefined will fully omit the header from being sent at all:
// After
res.setHeader("Connection", "keep-alive")
res.setHeader("Cache-Control", "no-cache")
res.setHeader("Content-Type", "text/html")
const session = await createSession(req, res, {
headers: {
Connection: "overwritten",
"Cache-Control": "",
"Content-Type": undefined,
},
})
/**
* Sent headers:
* {
* Connection: "overwritten"
* Cache-Control: ""
* }
*/This is an improvement as previously it was impossible to set header values to an empty string whilst still preserving the header in the response.
⚠️ BREAKING: Deprecated session buffer modification methods removed
The ability to modify the internal session buffer was deprecated almost two years ago and has now been removed.
Specifically, the .event, .data, .id, .retry, .comment, .dispatch and .flush methods are no longer available on the Session class.
If you wish to write raw SSE fields over the wire, use an EventBuffer instead.
See the original deprecation issue for more details.
Full Changelog
Added
- Added support for the
Sessionconstructor to be able to take in aRequest(and optionally aResponse) object from the Fetch API. - Added the
Session#getRequestandSession#getResponsemethods to retrieve the Fetch APIRequestandResponseobjects, respectively. - Added the
createResponseutility function to create aSessioninstance and immediately return its associatedResponseobject. - Added type overloads for each combination of arguments to
createSessionand theSessionconstructor. - Added support for passing a
ReadableStreamfrom the Web Streams API toSession#streamandEventBuffer#stream.
Changed
- Update type of
Sessionconstructoroptions#headersargument to accept anystring->(string | string[] | undefined)type rather than onlyOutgoingHttpHeaders. - Update the
Sessionconstructoroptions#headersargument to omit headers whose values are given asundefined, rather than an empty string. - Update the
Sessionconstructor to throw if given anIncomingMessageorHttp2ServerRequestwith no correspondingServerResponseorHttp2ServerResponse, respectively.
Fixed
- Fixed connection-specific headers being sent when using HTTP/2, causing the response to be treated as malformed.
Removed
- Removed the deprecated Session
.event,.data,.id,.retry,.comment,.dispatchand.flushmethods.
v0.14.1
Better SSE Version 0.14.1
npm install better-sse@latestyarn add better-sse@latestpnpm add better-sse@latestThis release updates all relevant links to point to the new documentation website.
Changes
New documentation site
The documentation for Better SSE has now been rewritten from raw Markdown files to a beautiful new static website made with Starlight.
On the new site you can find benchmarks, feature comparisons, walk-through guides, comprehensive API documentation and more.
Link to the new documentation.
Full Changelog
Changed
- Update links to point to new documentation website.
v0.14.0
Better SSE Version 0.14.0
npm install better-sse@latestyarn add better-sse@latestpnpm add better-sse@latestThis release includes fixes for minor issues when importing the package into an ESM module as well as to the TypeScript types for the createSession and createChannel factory functions.
ESM Import Fixes
For consumers using ESModules (with either JavaScript or TypeScript) to import the package, named exports did not work and you would have to extract individual exports out of the default-exported object:
// Before
import sse from "better-sse";
const { createSession } = sse;This has now been fixed and importing the package will work correctly with both ESM and CJS, allowing you to use named imports directly:
// After
import { createSession } from "better-sse";If you still wish to group your imports under an sse namespace, you can use a namespace import:
import * as sse from "better-sse";Factory Function Type Fixes
Previously, when creating a session or channel with createSession or createChannel, if no explicit type is given to the first generic State, the type of the returned Session#state or Channel#state properties would be incorrectly set to unknown rather than DefaultSessionState or DefaultChannelState, respectively:
// Before
const session = await createSession(req, res);
const channel = createChannel();
session.state; // unknown
channel.state; // unknownThis has now been fixed:
// After
const session = await createSession(req, res);
const channel = createChannel();
session.state; // DefaultSessionState
channel.state; // DefaultChannelStateFull Changelog
Fixed
- Fixed default state type when creating sessions and channels with
createSessionandcreateChannelbeing set tounknowninstead ofDefaultSessionStateandDefaultChannelState, respectively. - Fixed package directly exporting a single object containing exports, breaking named imports when using ESModules, and instead dual-export two separate builds to support both ESM and CJS.
Removed
- Dropped support for Node 17 and below.
v0.13.0
Better SSE Version 0.13.0
npm install better-sse@latestyarn add better-sse@latestpnpm add better-sse@latestThis release adds the ability to pass an initial value for the state property when creating sessions and channels.
Changes
Session and Channel state initialization
You can now initialize the value of the state property in the Session and Channel constructor options objects.
While this enables you to construct and set the session/channel state in a single statement, this also allows the TypeScript compiler to automatically infer the state type from the given state value, rather than you having to explicitly define it by passing a type/interface to the State generic parameter.
Before:
interface SessionState {
name: string;
}
const session = await createSession<SessionState>(req, res);
session.state.name = "Bob";
session.state.name = 123; // Error!After:
const session = await createSession(req, res, {
state: {
name: "Bob"
}
});
session.state.name = 123; // Error!Removed index signature constraint on state type
The state type for sessions and channels is now no longer required to have an index signature, meaning your state can be well-defined to have only the exact properties you specify.
Before:
interface SessionState {
name: string;
}
const session = await createSession<SessionState>(req, res); // Error! Index signature for type 'string' is missing in type 'SessionState'.
session.state.someUnknownProperty; // unknownAfter:
interface SessionState {
name: string;
}
const session = await createSession<SessionState>(req, res); // Works!
session.state.someUnknownProperty; // Error! Property 'someUnknownProperty' does not exist on type 'SessionState'.Note that if you do not pass an explicit state type, either via the constructor options or by passing a type/interface to the State generic, the state type will still default to Record<string, unknown>, allowing you to access unknown properties on the state object without an error being thrown.
Full Changelog
Added
- Added the ability to set an initial value for the
stateproperty in theSessionandChannelconstructoroptionsobjects.
Removed
- Removed constraints that enforced that
Stategenerics passed toSessionandChannelextend fromRecord<string, unknown>.
v0.12.1
Better SSE Version 0.12.1
npm install better-sse@latestyarn add better-sse@latestpnpm add better-sse@latestThis release fixes an issue relating to types for the sessions and channels (thanks to @Codpoe.)
Changes
Session and channel event name type fixes
You will now receive suggestions on event names when adding event listeners to the Session and Channel classes.
Full Changelog
Fixed
- Fixed types for channel and session emitted event names.
v0.11.0
Better SSE Version 0.11.0
npm install better-sse@latestyarn add better-sse@latestpnpm add better-sse@latestThis release adds better error handling and minor fixes related to sessions detecting a disconnect (thanks to @agix.)
Changes
Session disconnect detection improvements (#57)
Sessions will now also detect a disconnect if the outgoing HTTP response stream closes, rather than just the incoming request stream.
Additionally, the Session#push method (and subsequently other methods that call it such as the Session stream, iterate and the Channel#broadcast methods) will now throw when being called on a session that is not active.
Custom errors
All SSE-related errors thrown from within Better SSE will now be wrapped with the newly exported SseError custom error class.
This enables you to differentiate generic errors from SSE-related errors using instanceof. For example:
try {
session.push("some data");
} catch (error) {
if (error instanceof SseError) {
console.error(`Oops! ${error.message}`);
} else {
...
}
}Full Changelog
Added
- Added the
SseErrorcustom error object that wraps all thrown errors.
Changed
- Update the
Session#pushmethod to throw if the session is not connected.
Fixed
- Fixed session not detecting a response stream disconnect.
v0.10.0
Better SSE Version 0.10.0
npm install better-sse@latestyarn add better-sse@latestpnpm add better-sse@latestThis release adds the ability to batch and send multiple events in a single transmission.
In addition, it adds the new EventBuffer class for users who still want to write out raw spec-compliant SSE fields into a text buffer that can be sent directly over the wire, but without all of the extra functionality that using a Session provides.
Changes
Event batching (#42)
You can now batch and send multiple events in a single transmission using the new Session#batch method, saving bandwidth and greatly improving performance in cases where you need to send multiple events at a time.
To do so, simply invoke the batch method and pass a callback that takes an EventBuffer as its first argument:
await session.batch(async (buffer) => {
await buffer.iterate(<my huge event list>);
});You can use the same helper methods as you would with a session itself (push, iterate and stream).
When your callback finishes execution - or resolves if it returns a promise - every event created with the buffer will be sent to the client all at once in a single network transmission.
See the API documentation for more.
New EventBuffer class
The new EventBuffer class allows you to write raw spec-compliant SSE fields into a text buffer that can be sent directly over the wire.
This is useful for users who do not need all the extra functionality that a Session provides and instead want fine-grained control over the individual event fields they want to send to the client.
This is an advanced use-case. For most users, you should still stick with using Session by default.
import { createEventBuffer } from "better-sse";
const myBuffer = createEventBuffer();
myBuffer
.retry(2400)
.event("my-event")
.id("123")
.data("one")
.data("two")
.data("three")
.dispatch();
res.write(myBuffer.read());
myBuffer.clear();You can also pass an EventBuffer instance directly to the Session#batch method to write its contents to the session connection:
const myBuffer = createEventBuffer();
...
await session.batch(myBuffer);See the API documentation for more.
⚠ DEPRECATED: Session internal buffer modification methods (#52)
The ability to access and modify the internal Session buffer is now deprecated, meaning that the following methods will be removed in the future: .event, .data, .id, .retry, .comment, .dispatch and .flush.
These methods are now redundant, as instead of modifying the internal session buffer to be able to create and send individual events fields you can use the new EventBuffer class separately yourself.
For users who only used the helper methods push, stream and iterate this change will not affect you.
Full Changelog
Added
- Added the
Session#batchmethod that can be used to batch multiple events into a single transmission over the wire. - Added the
EventBufferclass that can be used to write raw spec-compliant SSE fields into a text buffer that can be sent directly over the wire.
Deprecated
- Deprecate the Session
.event,.data,.id,.retry,.comment,.dispatchand.flushmethods in favour of using event buffers instead.


