Client

Live

api.live SSE subscribe, exposure matching, resume gaps, and live resource queries.

signal.live is HTTP SSE. Expose with .live(signal) on GET (or http.live(signal) for GET /_oke/live/{name}), then subscribe with a callback. for await stays on the server.

For developers streaming shipment status, checkout progress, or live inbox rows into a browser or ops console.

The one rule

Subscribe with a callback and an unsubscribe function. Reconnects send Last-Event-ID from the last id: the client actually received. A 410 LiveResumeGap (OKE1210) means that cursor is gone.

Smallest Example

Subscribe to a shipment feed

import { shipmentStatus } from "@/signals/orders";

const stop = api.live(
  shipmentStatus,
  { orderId: "ord_9c2e" },
  {
    onEvent: (event) => {
      // { orderId, status: "packed" | "shipped" | "delivered", eta? }
      updateTrackingCard(event.status, event.eta);
    },
    onError: (err) => {
      showTrackingBanner(err);
    },
    autoResubscribe: false, // default — true reopens after a drop (500ms…30s backoff)
  },
);

Clean up when the shopper leaves the page

stop(); // useEffect cleanup / process exit

api.orders.events({ orderId }, { onEvent }) is the same shape on the exposing Flow.

Progressive Patterns

const stop = api.live(
  shipmentStatus,
  { orderId: "ord_9c2e" },
  {
    onEvent: (event) => updateTrackingCard(event.status, event.eta),
  },
);
stop();

Live handlers

OptionTypeDefaultMeaning
onEvent(event) => voidRequired — each SSE payload
onError(err) => void4xx, envelope, network drop
onOpen() => voidStream connected
autoResubscribebooleanfalseReopen after drop (500ms…30s backoff)
via"unit.flow" stringDisambiguate when two exposures match equally

Exposure matching

The client picks the unique exposure whose matchKey fields are a subset of the input, preferring the largest match ({ orderId } beats firehose). A tie needs via: "unit.flow".

api.live(
  shipmentStatus,
  { orderId: "ord_9c2e" },
  {
    onEvent: updateTrackingCard,
    via: "orders.events",
  },
);

Or call the exposing Flow directly: api.orders.events(input, { onEvent }).

Resume and LiveResumeGap

Reconnects send Last-Event-ID from the last id: the client actually received.

A 410 LiveResumeGap (OKE1210) means that cursor is gone — onError fires, the cursor is dropped, and autoResubscribe replays the remaining tape after backoff.

Server exposure: Signal · Live and HTTP · Live Streams.

Live queries (store.resource({ live: true }))

kindMeaning
upsertRow visible under stamp + query — merge/replace by primary key
revokedRow left visibility (reason: "rls" | "query") — remove
deleteRow deleted in CDC — remove

Every mutate from useLiveQuery generates a client UUID sent as the X-Oke-Mutation-Id header — the server echoes it onto that write's CDC events, so:

  • Your own late SSE echoes never double-apply (pending-set dedupe).
  • Reconnects replay-guard by event seq (isReplayedEvent).
  • Manual refetch() re-runs only the HTTP list read; reconnects always do a full subscribe-protocol cycle (new snapshot + replay).
StateMeaning
isLoadingWaiting for the first snapshot — no data yet
isConnectedSSE stream is open
isReconnectingStream dropped after a successful load; reconnect backoff in flight

Consequence: optimistic patches roll back automatically when the Flow returns error !== null. Server CDC / the successful response clear the override so the next upsert is authoritative.

Full React wiring: React.

Troubleshooting

Learn more

Next

On this page