Live queries & change feed

In-process real-time subscriptions over generated WebSocket endpoints — a field-blind change feed and stateful, removal-aware live queries — with honest notes on single-process scope and the O(rows) re-run cost.

ForgeDB generates two real-time WebSocket surfaces over your schema: a change feed that pushes a typed event as each row changes, and live queries that maintain a filtered result set and emit add/update/remove deltas. Both run in-process and are best-effort. Both bind to the same generated filter as the REST list endpoint, so there is no second predicate parser to keep in sync.

Change feed — /subscribe/<model>#

Subscribe to a model to get a typed JSON event on every insert, update, and delete. A query string filters the stream by the same fields the REST list endpoint accepts.

# Subscribe to filtered inserts/updates/deletes for a model:
wscat -c "ws://localhost:8080/subscribe/post?status=published"

Events carry ChangeKind::{Inserted, Updated, Deleted, Linked}. A Deleted event includes the pre-delete row, so the record is still materializable. Filters compare by typed value, not by string — ?views=3 matches a stored 3.0.

Live queries — /live-query/<model>#

A live query is stateful and removal-aware. It sends the current matching set as an Init delta, then streams Added / Updated / Removed deltas as rows enter, change, or leave the set.

wscat -c "ws://localhost:8080/live-query/post?status=published"

One filter, everywhere

The change feed, live queries, and the REST list endpoint all bind to the same generated per-model closed-set matcher (<model>_event_matches). There is no second, drift-prone predicate parser — the field-aware logic is generated once.

Limits#

  • Single-process. Both surfaces are in-process, best-effort broadcasts — there is no cross-process broker on this path. For a durable, resumable, cross-process stream (which feeds the browser read-replica), see the replication broker in Browser read-replica.
  • Live queries re-run O(rows) per matched event per connection, with no coalescing/debounce. A selective filter re-runs a narrow scan and materializes only the matching rows, but a broad filter materializes its whole matching set. This is the scaling cliff to keep in mind for high-throughput or broad subscriptions.
  • The filter is exact-match (=), not range or prefix — for value ranges use an ordered index.

Search documentation

Find pages across the ForgeDB docs