Realtime
Use realtime with the current published Askr packages.
@askrjs/askr0.0.59
How to use realtime
Tie streams and sockets to the request signal so disconnects stop producers and release subscriptions.
- Import the published @askrjs/askr entrypoint.
- Keep realtime configuration next to the component, route, or server composition root that owns it.
- Handle pending, unavailable, cancellation, and failure states, then verify the production path.
router.get('/events', (context) => {
const stream = createEventStream({ signal: context.signal });
void (async () => {
for await (const event of projects.events(context.signal)) {
await stream.send({ event: 'project', data: event });
}
})();
return stream.response;
});Server-sent events
ctx.sse(options) returns an EventStream built on the request's own AbortSignal — the option type explicitly omits signal, since cancellation is derived from the context instead of passed in. The returned object exposes response (the actual Response to hand back from the handler), closed (a Promise that resolves once the stream ends), and send(event) / comment(value) / close() for driving it. createEventStream(options) is the same machinery exported standalone for cases where a stream needs to be built outside a full ServerContext, in which case you do supply your own AbortSignal.
Event formatting
A ServerSentEvent is a plain object with optional data, event, id, and retry fields, matching the SSE wire format's field names directly. formatServerSentEvent(event) turns one of these into the literal text an EventSource on the other end expects — send() calls it internally, so the standalone function is really only needed when you want the formatted string somewhere outside a live EventStream, such as a test assertion. data is typed as unknown rather than string, so whatever you pass gets serialized the same way the rest of the response helpers serialize JSON.
WebSocket adapters
router.ws(path, handler) registers a WebSocket route the same way router.get() registers an HTTP one — path matching, middleware, and params all work identically — but the actual upgrade handshake is delegated to a WebSocketAdapter supplied through ServerAppOptions.websocket. That adapter's single method, upgrade(request, handler, context), is where a Node, Bun, Deno, or edge-specific upgrade API gets called; the router itself carries no runtime-specific code. Without an adapter configured, a matched WebSocket route returns 501 Not Implemented instead of failing in some less predictable way.
Cancellation and cleanup
ctx.signal is an AbortSignal scoped to the request, so an SSE loop or a long-running handler can check signal.aborted, or listen for its abort event, and stop pushing work once the client disconnects rather than running to completion for no one. EventStream.closed resolves once the stream itself is done, which is the hook for any cleanup — releasing a subscription, decrementing a counter — that should run exactly once per connection. On the WebSocket side, WebSocketLike.close(code?, reason?) is how a handler ends a socket deliberately, separate from whatever cancellation the adapter's own transport triggers when the underlying connection simply drops.