Live Queries

Connect to ws://<host>/live and subscribe to a query by name. The server pushes fresh results whenever a mutation writes a table that the query reads — whether the write came over HTTP or over the socket. Because a Durable Object sees every write to its store (single-writer), invalidation is exact: only subscriptions whose read set intersects the mutation's write set are re-run.

Subscriptions inherit the connecting identity, so every push respects the same row-level scope and field projection as a normal read.

Protocol

// client -> server
{ "type": "subscribe",   "id": "s1", "name": "listNotes" }
{ "type": "call",        "id": "c1", "name": "createNote", "input": { "title": "hi", "body": "x" } }
{ "type": "unsubscribe", "id": "s1" }

// server -> client
{ "type": "data",   "id": "s1", "result": [ /* ... */ ] }  // initial + every update
{ "type": "result", "id": "c1", "result": { /* ... */ } }  // reply to a call
{ "type": "error",  "id": "s1", "error": "..." }

The WebSocket uses Cloudflare's hibernation API, so idle connections don't pin the DO in memory.

Browser tokens

Browser WebSockets can't set headers, so /live also accepts the bearer token and tenant via the query string (?token=...&tenant=...); the Worker folds them into headers for the rest of the flow. The @pramen/client library handles this for you — see Clients.