Skip to content

Releases: MatthewWid/better-sse

v0.16.1

29 Dec 09:08

Choose a tag to compare

Better SSE Version 0.16.1

npm install better-sse@latest
yarn add better-sse@latest
pnpm add better-sse@latest

This 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

v0.16.0

29 Dec 06:03

Choose a tag to compare

Better SSE Version 0.16.0

npm install better-sse@latest
yarn add better-sse@latest
pnpm add better-sse@latest

This 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 Session constructor that enables compatibility with any protocol, framework or runtime environment.

Changed

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 Request but no corresponding Response object from the Fetch API to the Session constructor.
  • Fixed a crash that occurred when the request had no Host header when using the Node HTTP/1 or Node HTTP/2 Compatibility API.

v0.15.1

26 May 07:08
bb7aec0

Choose a tag to compare

Better SSE Version 0.15.1

npm install better-sse@latest
yarn add better-sse@latest
pnpm add better-sse@latest

This 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-sse
yarn add jsr:@mwid/better-sse
pnpm add jsr:@mwid/better-sse
bunx jsr add @mwid/better-sse
deno add jsr:@mwid/better-sse

When using Deno you can import the package directly like so:

import { createSession } from "jsr:@mwid/better-sse"

Full Changelog

Added

Fixed

  • Fixed the stream, iterate and EventBuffer#clear methods not having explicit return types, resulting in slow inference in certain environments.

v0.15.0

22 May 08:24

Choose a tag to compare

Better SSE Version 0.15.0

npm install better-sse@latest
yarn add better-sse@latest
pnpm add better-sse@latest

This 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 Session constructor to be able to take in a Request (and optionally a Response) object from the Fetch API.
  • Added the Session#getRequest and Session#getResponse methods to retrieve the Fetch API Request and Response objects, respectively.
  • Added the createResponse utility function to create a Session instance and immediately return its associated Response object.
  • Added type overloads for each combination of arguments to createSession and the Session constructor.
  • Added support for passing a ReadableStream from the Web Streams API to Session#stream and EventBuffer#stream.

Changed

  • Update type of Session constructor options#headers argument to accept any string->(string | string[] | undefined) type rather than only OutgoingHttpHeaders.
  • Update the Session constructor options#headers argument to omit headers whose values are given as undefined, rather than an empty string.
  • Update the Session constructor to throw if given an IncomingMessage or Http2ServerRequest with no corresponding ServerResponse or Http2ServerResponse, respectively.

Fixed

Removed

  • Removed the deprecated Session .event, .data, .id, .retry, .comment, .dispatch and .flush methods.

v0.14.1

27 Oct 09:10

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

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

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

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

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

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.