# Realtime Streams

> Realtime Streams: a worked guide from route registry through to a production build.

Source: [https://askrjs.com/docs/guides/realtime-streams](https://askrjs.com/docs/guides/realtime-streams)

Status: stable. Packages: @askrjs/askr.

**Published packages are authoritative.** Examples may lag behind a published contract. When guidance differs, verify the exports and TypeScript declarations in your installed package, then file an issue.

## Example

Tie streams and sockets to the request signal so disconnects stop producers and release subscriptions.

```tsx
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.

## Documentation navigation

[Previous](https://askrjs.com/docs/guides/loading-empty-error-and-pending-states/index.md) | [Next](https://askrjs.com/docs/guides/file-uploads-and-artifacts/index.md)
