client.query(), you subscribe once — the server re-executes the query and pushes a new result every time a matching CRDT changes.
How it works
- Client sends
SubscribeQueryover the existing WebSocket connection with a query spec and aquery_id. - Server executes the query immediately and pushes a first
QueryResultframe. - Whenever a CRDT in the namespace changes, the server checks all active live query subscriptions. Queries whose
fromglob matches the changed CRDT (and whose optionaltypefilter matches) are re-executed, and a newQueryResultis pushed. - Client sends
UnsubscribeQueryto cancel. The server also cleans up automatically on disconnect.
SDK — client.liveQuery()
LiveQueryHandle
| Method | Description |
|---|---|
onResult(listener) | Register a listener. Returns an unsubscribe function. Multiple listeners can be registered. |
close() | Cancel the subscription and notify the server. |
LiveQueryResult
| Field | Type | Description |
|---|---|---|
value | unknown | Aggregated result. Same shape as the HTTP Query Engine. null when nothing matched. |
matched | number | Number of CRDTs that contributed to the result. |
React — useLiveQuery()
data on every push, and unsubscribes on unmount. loading is true until the first result arrives.
| State | Type | Description |
|---|---|---|
data | LiveQueryResult | null | Most recent result. null until first push. |
loading | boolean | true until the first result arrives. |
error | Error | null | Set if the subscription setup throws. |
Supported query specs
Live Queries accept the same spec asclient.query():
| Field | Type | Required | Description |
|---|---|---|---|
from | string | ✓ | Glob pattern matched against CRDT IDs. |
type | string | — | Filter by CRDT type. Prevents re-execution for unrelated types. |
aggregate | string | ✓ | Same aggregation functions as the HTTP endpoint. |
where | object | — | contains (ORSet) or updatedAfter (LwwRegister). |
Performance notes
Type filter skips unrelated deltas. When you settype: "gcounter", the server skips re-execution entirely when an ORSet or LwwRegister changes — even if the glob pattern would match the key. Always set type when your pattern is cross-type.
SubscribeQuery frames automatically after a WebSocket reconnect. No manual re-subscription is needed.
Multiple handles per spec. Each liveQuery() call creates an independent subscription with its own query_id. If you need multiple listeners on the same query, use handle.onResult() multiple times on a single handle instead of calling liveQuery() twice.
Difference from useQuery
useQuery | useLiveQuery | |
|---|---|---|
| Transport | HTTP POST | WebSocket push |
| When it updates | On spec change (re-fetch) | On every matching CRDT delta |
scanned / execution_ms | Available | Not available (push model) |
| Use case | One-shot reads, low-frequency data | Dashboards, live counters, reactive aggregates |