Skip to main content

Server-Sent Events

Instead of returning one response, you can stream events with an async generator. Every yielded object is sent to the client as an event.

The supported methods are: getSSE, postSSE, putSSE, patchSSE, deleteSSE

Example​

builder
.chain(authLink)
.getSSE(async function* ({ data, disconnectSignal }) {
for await (const event of activityFeed.subscribe(disconnectSignal)) {
yield { action: event.action, by: data.account.name };
}
});

In this example:

  • Middlewares run before the stream opens. Opening a stream sends the status (200) first, so after that an error can only close the stream. Running auth and validation before it means a rejection is still a normal JSON result with its own status.
  • When the client disconnects, the generator is stopped, and disconnectSignal is aborted.

Client​

const stream = await fetchCupleSSE(client.activity.get, {}).thenKeepSuccess();

for await (const event of stream) {
console.log(event.by, event.action);
}

Like fetchCuple, it resolves with any result the server sent: a rejection by a middleware is a JSON result, not a stream. thenKeepSuccess() keeps only the stream.

In React, use useStream.

  • NOTE: By default the generator is stopped when the client disconnects, so a client that navigates away doesn't leave a subscription or a loop running on the server. If your generator ends on its own, and it should finish even without the client, use getSSE(generator, { returnOnDisconnect: false }). Never do this with an endless loop.
Why it works this way

A stream is an async generator because yield is the closest thing to return: a handler that streams reads like one that responds once, and the yielded type flows to the client.

SSE is here because it was low-hanging fruit on plain HTTP. WebSockets are planned. The separate methods (getSSE, postSSE, …) are a typing limit and an ergonomics choice: a streaming handler has a different type from a normal one.