Releases: MatthewWid/better-sse
v0.14.1
Better SSE Version 0.14.1
npm install better-sse@latest
yarn add better-sse@latest
pnpm add better-sse@latest
This 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@latest
yarn add better-sse@latest
pnpm add better-sse@latest
This 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; // unknown
This has now been fixed:
// After
const session = await createSession(req, res);
const channel = createChannel();
session.state; // DefaultSessionState
channel.state; // DefaultChannelState
Full Changelog
Fixed
- Fixed default state type when creating sessions and channels with
createSession
andcreateChannel
being set tounknown
instead ofDefaultSessionState
andDefaultChannelState
, 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@latest
yarn add better-sse@latest
pnpm add better-sse@latest
This 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; // unknown
After:
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
state
property in theSession
andChannel
constructoroptions
objects.
Removed
- Removed constraints that enforced that
State
generics passed toSession
andChannel
extend fromRecord<string, unknown>
.
v0.12.1
Better SSE Version 0.12.1
npm install better-sse@latest
yarn add better-sse@latest
pnpm add better-sse@latest
This 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@latest
yarn add better-sse@latest
pnpm add better-sse@latest
This 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
SseError
custom error object that wraps all thrown errors.
Changed
- Update the
Session#push
method 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@latest
yarn add better-sse@latest
pnpm add better-sse@latest
This 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#batch
method that can be used to batch multiple events into a single transmission over the wire. - Added the
EventBuffer
class 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
,.dispatch
and.flush
methods in favour of using event buffers instead.
v0.9.0
Better SSE Version 0.9.0
npm install better-sse@latest
yarn add better-sse@latest
pnpm add better-sse@latest
This release includes some minor fixes and quality-of-life improvements to the TypeScript types shipped with Better SSE.
Changes
⚠ BREAKING: SessionState
interface renamed to DefaultSessionState
The exported SessionState
interface has been renamed to DefaultSessionState
.
This is to avoid confusion as the type of each sessions' state
property can vary between different session instances if you provide one as a generic argument to its constructor, whereas the exported interface here is designed to only be used to define the default type if you do not explicitly give one when you create the session.
// Before
declare module "better-sse" {
interface SessionState {
myProperty: string;
}
}
// After
declare module "better-sse" {
interface DefaultSessionState {
myProperty: string;
}
}
Strong typing for the session state
property of sessions registered with a channel
You can now pass an optional second generic argument to the Channel
constructor and the createChannel
factory function that allows you to define the type of the state
property for all Session
s registered to that channel.
This allows you to have strong typing when accessing the state
property of sessions in the activeSessions
list and in the filter
callback option in the broadcast
method, as well as allowing you to enforce that only sessions with the same state
type can be registered with that channel.
Strong typing for channel session states:
// Before
type AuthedSessionState = { isAdmin: boolean };
const channel = createChannel();
const session = createSession<AuthedSessionState>(req, res);
channel.register(session);
channel.activeSessions[0].state.isAdmin; // `unknown`
// After
type AuthedSessionState = { isAdmin: boolean };
const channel = createChannel<*, AuthedSessionState>(); // Provide the type for the state of every session on this channel
const session = createSession<AuthedSessionState>(req, res);
channel.register(session);
channel.activeSessions[0].state.isAdmin; // `boolean`
Only sessions with the same state type given to the channel may be registered:
const channel = createChannel<*, { propertyA: string }>();
const session = await createSession<{ propertyB: string }>(req, res);
channel.register(session); // TypeScript error! Only sessions with the same type can be registered
Channels may now have a default state type defined globally
A new DefaultChannelState
interface is exported that allows you to use module augmentation and declaration merging to define a default type for the Channel#state
property if you do not provide one during construction.
This functions identically to the DefaultSessionState
interface, but for Channel
s instead.
declare module "better-sse" {
interface DefaultChannelState {
myProperty: string;
}
}
Full Changelog
Added
- Added the ability to type the
state
property of sessions registered with a Channel via an optional second generic argument to theChannel
constructor. - Added the
DefaultChannelState
interface that may be used via module augmentation to alter the default channel state type for all channels.
Changed
- Update the
SessionState
interface to be namedDefaultSessionState
.
v0.8.0
Better SSE Version 0.8.0
npm install better-sse@latest
This release adds significant performance improvements as well as adding an intermediary data buffer to each session, support for HTTP/2 and the ability to overwrite the default response headers.
Changes
⚠ BREAKING: Session
s will now buffer data before sending it to the client
The Session
class has been updated to buffer all data until it is explicitly flushed to the client.
This significantly improves performance due to less calls to the underlying res.write
method, as well as reducing overall bandwidth usage by sending all data at once (and thus less TCP ACKs being sent back and forth).
If you only use the helper methods push
, iterate
and stream
this change should be transparent to you. More advanced users using the individual field-writing methods event
, data
, id
, retry
or comment
should keep in mind that the client will now no longer receive that data until it is sent explicitly with the flush
method.
The dispatch
method will now also only write a newline to the buffer, instead of writing a newline and flushing the data to the client.
// Before
session.event().data().id().retry().comment().dispatch();
// After
session.event().data().id().retry().comment().dispatch().flush(); // All data is sent once over the network
// Or to match the exact previous behaviour, flush after each field write
session.event().flush().data().flush().id().flush().retry().flush().comment().flush().dispatch().flush();
⚠ BREAKING: Event ID format guarantee removed
Event IDs are no longer guaranteed to be 8-character alphanumeric strings, but are still guaranteed to be cryptographically pseudo random.
This is due to a performance bottleneck being caused by Node's random byte generation, which has been replaced with a faster mechanism.
⚠ BREAKING: Session
id
method no longer accepts null
The Session#id
method no longer accepts null
as an argument. Simply pass no arguments instead to wipe the ID.
⚠ BREAKING: Channel registration methods short-circuit on already-registered sessions
The Channel#register
and Channel#deregister
now short-circuit and thus do not emit events when the session is already registered or not registered, respectively.
Performance improvements
This release brings significant performance improvements when using both individual sessions or multiple sessions with channels.
v0.7.1:
Benchmarking "Push events with channels".
better-sse x 11,421 ops/sec ±6.10% (75 runs sampled)
v0.8.0
Benchmarking "Push events with channels".
better-sse x 44,702 ops/sec ±6.08% (81 runs sampled)
That's almost a 4x performance improvement!
Feel free to try the benchmarks yourself in the examples
directory.
Session
default headers can now be overridden
Headers you pass in the header
option will now overwrite the values of the default headers they are the same as, instead of being ignored.
// Before
const session = await createSession({ headers: { Connection: "something-else" } });
// "Connection: keep-alive" is sent
// After
const session = await createSession({ headers: { Connection: "something-else" } });
// "Connection: something-else" is sent
HTTP/2 Support
HTTP/2 support is now available via the Node HTTP/2 compatibility API.
Note that this is only adding support for the compatibility version of the Node HTTP/2 implementation, not the direct core API, as that would be a more challenging task most likely requiring breaking changes to the package API. Support for the core API is not currently planned.
const server = http2.createServer().listen();
server.on("request", (req, res) => {
const session = new Session(req, res);
});
Full Changelog
Added
- Added an internal data buffer to Session that buffers written data internally until it is flushed to the client using the new
Session#flush
method. - Added the
Pragma
,X-Accel-Buffering
headers and add additional values to theCache-Control
default header to further disable all forms of caching. - Added support for supplying the Node HTTP/2 compatibility API
Http2ServerRequest
andHttp2ServerResponse
objects to theSession
constructorreq
andres
parameters, respectively.
Changed
- Update the
Session#event
,Session#data
,Session#id
,Session#retry
andSession#comment
methods to write to the internal data buffer instead of sending the field data over the wire immediately. - Update the
Session#dispatch
method to only write a newline (and to the internal data buffer) and not flush the written data to the client. - Update the
Channel#broadcast
method to generate its own custom event ID and thus add it as an additional argument to itsbroadcast
event callback function. - Update the
Channel#register
andChannel#deregister
to not do anything if the channel is already registered or deregistered, respectively. - Update the
Session
constructor optionsheader
field to overwrite conflicting default headers instead of being ignored. - Update auto-generated event IDs to be guaranteed to be a cryptographically unique string instead of a pseudorandomly generated string of eight characters.
Fixed
- Fixed the Channel
session-disconnected
being fired after instead of before the session is deregistered.
Removed
- Removed the ability to pass
null
toSession#id
. Give no arguments at all instead.
v0.7.1
Better SSE Version 0.7.1
npm install better-sse@latest
This release removes unused type-declaration files generated from the internal unit testing files.
Full Changelog
Fixed
- Removed type-declarations generated from unit testing files.
v0.7.0
Better SSE Version 0.7.0
npm install better-sse@latest
This release adds improved TypeScript typings and makes various improvements (including breaking changes) to the session and channel event push methods to more safely type their arguments.
This release is the precursor to the introduction of the new history API, and the changes here will make the interoperability with existing code and the new API much easier.
Changes
⚠ BREAKING: Session#push
and Channel#broadcast
arguments re-ordered
The Session
push
and Channel
broadcast
method arguments have been re-ordered to place the event data first and the event name second.
This is allows their arguments to be strongly typed, as before you were able to pass a non-serializable object to the event name argument as its type was only unknown
, where it is now always string
.
In addition, it also allows the Channel
broadcast
method event name to be an optional argument as the event name can now be differentiated from the event data.
// Before
session.push("Hello there!");
session.push("greeting", "Hello there!");
channel.broadcast("greeting", "Hello there!");
// After
session.push("Hello there!");
session.push("Hello there!", "greeting");
channel.broadcast("Hello there!");
channel.broadcast("Hello there!", "greeting");
⚠ BREAKING: Channel#broadcast
filter
option now explicitly requires a boolean
to be returned
The Channel
broadcast
method filter
option explicitly now requires a boolean
to be returned instead of allowing any value with unknown
.
Session#state
global type interface
The Session
State
generic argument now defaults to an exported SessionState
interface that can be augmented via declaration merging.
This is now the recommended way to add typings for the Session#state
property as it works globally across your application, even without a reference to the original session object.
// Before
const channel = createChannel();
...
const session = createSession<{isTrusted: boolean}>(req, res);
session.state.isTrusted = true; // 'boolean'
channel.register(session);
channel.broadcast("A new user joined.", "join-notification", {
filter: (session) => session.state.isTrusted // Error: Type 'unknown' is not assignable to type 'boolean'
});
// After
declare module "better-sse" {
interface SessionState {
isTrusted: boolean;
}
}
const channel = createChannel();
...
const session = createSession(req, res);
session.state.isTrusted = true; // Still 'boolean'
channel.register(session);
channel.broadcast("A new user joined.", "join-notification", {
filter: (session) => session.state.isTrusted // Works!
});
Session
push
custom event ID
You can now specify a custom event ID as an optional third argument to the Session#push
method.
Note that it is still recommended to allow the library to generate its own event IDs instead of doing it yourself as their uniqueness is guaranteed. This argument is mostly intended to be used internally, though it is exposed to the user as a matter of convenience if you have a more advanced use-case.
// Before
session.push("Hello there!", "greeting"); // Auto-generated ID
// After
session.push("Hello there!", "greeting", "my-unique-id-1");
New Session
push
event
Similar to channels, sessions will now emit a push
event each time a new event is sent with the push
method.
session.on("push", (data: unknown, eventName: string, eventId: string) => {});
Optional Channel#broadcast
event name argument
The Channel
broadcast
method eventName
argument is now optional and will default to "message"
if not given.
// Before
channel.broadcast("Hello there!"); // Error: Expected 2-3 arguments, but got 1.
// After
channel.broadcast("Hello there!"); // Works!
Full Changelog
Added
- Added the ability to the
Session#push
method to set a custom event ID. - Added a new Session
push
event that is emitted with the event data, name and ID when theSession#push
method is called. - Added the
Channel#state
property to have a safe namespace for keeping information attached to the channel.
Changed
- Update the arguments for the
Session#push
andChannel#broadcast
methods and their corresponding emitted event callbacks to always have the event data first and event name as an optional argument second. - Update the
Channel#broadcast
method options TypeScript typings to explicitly mandate aboolean
return-type instead of allowing any truthy or falsy value. - Update the
Channel#broadcast
method event name argument to be optional and default to"message"
if not given. - Update the
Session#state
generic argument to default to a newSessionState
interface that can be augmented via declaration merging to override the session state type for all session objects without explicitly providing a generic argument to each reference toSession
. - Rename the Session and Channel
Events
interfaces toSessionEvents
andChannelEvents
respectively and export them publicly allowing the user to properly type non-inlined event handler callback functions.