Adapters

SSE

Point Flux at any endpoint that speaks standard Server-Sent Events and it takes care of turning that stream into live store updates — reconnect handling, single-connection coordination across tabs, automatic backfill via URL parameters, and optional POST reconciliation all come for free once the adapter is wired in.

Prerequisites
  • A backend serving Server-Sent Events on a per-channel URL — any language or framework
  • @tsworldtech/flux and @tsworldtech/flux-sse installed
1

Create the adapter

createSseAdapter() accepts a config object with a url function and an optional reconcileUrl function. The adapter opens connections lazily, one per channel, only once a store actually registers.
typescript
1import { createFlux } from '@tsworldtech/flux'
2import { createSseAdapter } from '@tsworldtech/flux-sse'
3
4const flux = createFlux({
5 adapter: createSseAdapter({
6 // URL builder for per-channel SSE streaming endpoints
7 url: (channel) => `${process.env.NEXT_PUBLIC_API_URL}/api/stream/${channel}`,
8
9 // Optional POST endpoint for deletion reconciliation (since SSE is unidirectional)
10 reconcileUrl: (channel) => `${process.env.NEXT_PUBLIC_API_URL}/api/reconcile/${channel}`,
11 }),
12})
2

Register a store

channel determines which URL gets opened; event is the named SSE event your backend dispatches under on that stream — commonly 'UPDATE', matching the event name on your backend's event: UPDATE\ndata: ...\n\n frames.
typescript
1flux.register({
2 store: useItemsStore,
3 channel: 'items',
4 event: 'UPDATE',
5 idbKey: 'items:all',
6 ttl: 'medium',
7 ingestionType: 'COLLECTION_ALL',
8 diffBeforeUpdate: true,
9 hydrateState: (store, data) => {
10 // store.getState().setState handles both array hydrations and single delta frames
11 store.getState().setState(data)
12 },
13})
14
15// Opens one EventSource at /api/stream/items?syncAnchor=<unixMs>
16// and listens for an 'UPDATE' named event frame on it.
3

One connection per channel with URL sync anchors

The url callback is executed per registered channel, automatically appending ?syncAnchor=<unixMs> (defaulting to 0). Your backend reads this parameter to stream backfill deltas before transitioning to live broadcasts.
typescript
1// One EventSource is opened per registered channel — the url callback
2// runs once per channel and always appends ?syncAnchor= (defaulting to 0)
3createSseAdapter({
4 url: (channel) => `https://api.myapp.com/api/stream/${channel}`,
5})
6
7// flux.register({ channel: 'items', ... }) → opens /api/stream/items?syncAnchor=0
8// flux.register({ channel: 'orders', ... }) → opens /api/stream/orders?syncAnchor=0
9// Each channel runs as an independent, persistent HTTP stream connection.
4

Deletion reconciliation (POST)

Because Server-Sent Events is strictly unidirectional (server to client), client-to-server operations like deletion reconciliation use an explicit HTTP POST endpoint if reconcileUrl is configured:
typescript
1// Because browser EventSource cannot send messages client -> server,
2// deletion reconciliation is handled via standard HTTP POST if reconcileUrl is configured:
3//
4// 1. Client posts cached local IDs to: POST /api/reconcile/items
5// Body: { "localIds": ["id_1", "id_2", "id_3"] }
6//
7// 2. Server checks database and returns missing IDs:
8// Response: { "channel": "items", "deletedIds": ["id_2"] }
9//
10// 3. Adapter dispatches op: 'DELETE' to store for each missing ID.
5

Reconnects and catch-up are automatic

You don't write any of this yourself

You never need to write reconnect logic yourself. If a connection drops — network blip, tab backgrounded, server restart — Flux reopens it automatically with backoff. The adapter always appends ?syncAnchor=<unixMs> to the channel URL using the highest processed event timestamp, allowing your server to execute a targeted changelog query and stream backfill events down the new connection.

6

Any backend that speaks SSE

Not tied to any particular server stack

Anything that can hold an HTTP connection open and write text/event-stream frames works here — Node, Python, Go, Rails, PHP, whatever you already run. Flux only ever talks to a plain URL over standard SSE; there's no required backend framework or language.

7

Authentication

EventSource can't send custom headers

The browser's native EventSource can't attach custom headers, so a bearer token in an Authorization header isn't an option. The common patterns are a short-lived signed token appended to the channel URL as a query parameter, or same-origin cookies if your app and API share a domain.

Everything else — reconnecting after a dropped connection, making sure only one browser tab holds each live stream at a time, and catching back up on whatever changed while you were offline — happens automatically once the steps above are done. There's nothing further to configure on the client for a standard setup.

Building the server side of this contract from scratch? See getting-started/node-sse for a full worked example. For what happens during reconnect specifically, see core-concepts/sync-anchors and core-concepts/activity-bus.