Realtime — Observe
Watch a single record. First yield is the current state; every subsequent yield is an update. When the record is deleted, the iterator ends.
observe is the natural primitive for detail pages, ticket views, and
any UI where you want "one row, always fresh."
Signature
observe is on the root BeamClient, not wrapped per-lens in the
generated Beam. Call as beam.observe('lensName', 'rowId').
Unlike subscribe, which yields { kind, record } events, observe
yields the record directly. Event semantics:
- First yield — current state of the row (the server does a fetch).
- Subsequent yields — the full updated record after every change.
- Delete — iterator ends cleanly with
done: true.
React hook
useObserve wraps the iterator for detail pages:
recordisnullbefore the first yield AND after a delete — checkloadingto disambiguate.- Passing
enabled: false(orrecordId: null) holds the subscription without opening it. Useful for routes that don't know the id yet. - The hook re-subscribes on
recordIdchange — navigating from one order to another closes the old op and opens a new one.
The raw WebSocket op
For non-TS clients:
The client SDK hides the kind field for observe — it just yields the
record. If you need the change kind, use subscribe with a narrow
filter ({ id: 'o_4021' }) instead.
When to reach for subscribe instead
observe is the right primitive when the UI is truly
single-record-focused: a detail page, a ticket view, an order summary.
For anything broader — a list where multiple rows might change, a
dashboard — subscribe is the cleaner abstraction.
Rule of thumb:
| UI shape | Primitive |
|---|---|
| "This one row, always fresh" | observe |
| "Every row matching a filter, as changes happen" | subscribe |
| "Top N items ranked by scorers, with 'new content' ticks" | feed.<name>.subscribe |
Access rules
Same gates as subscribe:
observe also honors the lens's search or query grant (whichever is
set) for row-level security — the first yield may return empty or error
if the caller doesn't have access to that specific row.
Limits
- One
observeper record per connection. Opening a second observe for the samerecordIdon the same WebSocket reuses the existing op rather than opening a new one. - Counts against
maxLiveSubscriptionsjust likesubscribe. - Record not found on first lookup → iterator throws with a
404 not_founderror before any yield. Catch and handle as "this row doesn't exist" — the more common case than a mid-observe delete.
Errors
| Error | Meaning |
|---|---|
404 not_found (first yield) | Record doesn't exist, never has. Stop observing. |
403 forbidden (first yield) | Row-level rule denied access to this specific record. |
Iterator done mid-stream | The record was deleted. Show a "this record no longer exists" state. |
BeamStreamClosedError | WebSocket dropped. Retry with backoff. |
The useObserve hook surfaces the first two as error and the third
(delete) as record === null. Retry for the WS drop is handled inside
the hook.