Realtime Streams
Use realtime streams with the current published Askr packages.
@askrjs/askr0.0.59
How to use realtime streams
Tie streams and sockets to the request signal so disconnects stop producers and release subscriptions.
- Import the published @askrjs/askr entrypoint.
- Keep realtime streams 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;
});Goal and architecture
Askr's server context gives you two real primitives for pushing data to the client: `context.sse(options)`, which returns an `EventStream` for Server-Sent Events, and `context.upgrade(handler)` (backed by a `WebSocketAdapter`), which hands your `WebSocketHandler` a `WebSocketLike` socket with `send()` and `close()`. There's no framework-level pub/sub or realtime store on top of these — you pick SSE for one-way server-to-client feeds (progress updates, notifications) and WebSockets when the client needs to talk back, and you own the message format either way.
Implementation
For SSE, call `context.sse()` inside a route handler and write events to the returned `EventStream`; `formatServerSentEvent` and the `ServerSentEvent` type describe the wire format if you need to hand-craft frames. For WebSockets, declare `upgrade` on an `ApiRoute` or call `context.upgrade(handler)`, where `handler(socket, context)` gets the live connection and the same `ServerContext` (so `context.auth` and `context.params` are already resolved). On the client, a plain `stream()` codec from `@askrjs/fetch` decodes a response as a `ReadableStream<Uint8Array>`, useful for consuming chunked or SSE responses through the typed client instead of a raw `fetch`.
Failure states
`context.signal` is an `AbortSignal` tied to the request lifecycle — check it in long-lived SSE loops so a closed connection stops your write loop instead of leaking a handler that writes to a dead stream. WebSocket handlers should treat `socket.close(code, reason)` as the normal teardown path and be defensive about `send()` after a close, since the browser can drop a connection without your handler being told first. If a realtime feed is supplementary rather than essential, fall back to `invalidateOnInterval(prefix, { intervalMs })` polling for the same data — it's simpler to reason about and doesn't need connection-recovery logic.
Verification
Test SSE handlers by opening a real `EventSource` (or reading the raw response stream) against a test server and asserting on the events received in order, since the format is just newline-delimited text over an HTTP response. For WebSocket handlers, drive `handler(socket, context)` directly with a mock `WebSocketLike` in unit tests to assert on what gets sent for a given inbound message, then layer a smaller number of real-socket integration tests on top. Always assert that closing the client connection triggers cleanup on the server side — a common realtime bug is a handler that keeps a timer or subscription alive after the socket is gone.