Skip to content

Releases: MatthewWid/better-sse

v0.14.1

27 Oct 09:10
Compare
Choose a tag to compare

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.

image

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

v0.14.0

18 Oct 06:35
Compare
Choose a tag to compare

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 and createChannel being set to unknown instead of DefaultSessionState and DefaultChannelState, 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

23 Aug 13:49
Compare
Choose a tag to compare

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 the Session and Channel constructor options objects.

Removed

  • Removed constraints that enforced that State generics passed to Session and Channel extend from Record<string, unknown>.

v0.12.1

10 May 03:39
Compare
Choose a tag to compare

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.

Screenshot_1

Screenshot_2

Full Changelog

Fixed

  • Fixed types for channel and session emitted event names.

v0.11.0

08 Feb 03:07
Compare
Choose a tag to compare

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

28 Sep 11:24
2c34275
Compare
Choose a tag to compare

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

14 Aug 11:36
Compare
Choose a tag to compare

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 Sessions 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 Channels 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 the Channel constructor.
  • Added the DefaultChannelState interface that may be used via module augmentation to alter the default channel state type for all channels.

Changed

v0.8.0

01 Jun 14:48
Compare
Choose a tag to compare

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: Sessions 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 the Cache-Control default header to further disable all forms of caching.
  • Added support for supplying the Node HTTP/2 compatibility API Http2ServerRequest and Http2ServerResponse objects to the Session constructor req and res parameters, respectively.

Changed

  • Update the Session#event, Session#data, Session#id, Session#retry and Session#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 its broadcast event callback function.
  • Update the Channel#register and Channel#deregister to not do anything if the channel is already registered or deregistered, respectively.
  • Update the Session constructor options header 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 to Session#id. Give no arguments at all instead.

v0.7.1

11 Jan 09:51
Compare
Choose a tag to compare

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

08 Jan 10:55
Compare
Choose a tag to compare

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 the Session#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 and Channel#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 a boolean 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 new SessionState 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 to Session.
  • Rename the Session and Channel Events interfaces to SessionEvents and ChannelEvents respectively and export them publicly allowing the user to properly type non-inlined event handler callback functions.