TypeScript Live Queries
Use client.live() to receive the current rows of a KalamDB query. The SDK loads the initial snapshot and reconciles later inserts, updates, and deletes by primary key. Replace your displayed list on each callback; do not append the whole callback array.
For React, start with the React inbox example. For typed Drizzle tables outside React, use the ORM live-table example. The Dart/Flutter and Rust examples use the same table.
Prepare the live-query example
Start a server using the server quick start. Run this schema as an account allowed to create tables, in the SQL Studio or your schema migration:
This is a private inbox: use the same authenticated user for the subscriber and the test writes. A room filter does not make a USER table shared between users. Collaborative rooms need a SHARED table and access policies.
Run a TypeScript live query
In a Node.js TypeScript project:
Save the following as inbox.ts before running the last command. Credentials here are for a local terminal; browser apps should use their signed-in user’s token through an auth provider.
The outer catch handles connection and startup failures. onError handles server subscription errors reported after startup. Connection callbacks report transport problems. This CLI deliberately stops on a subscription error; a UI can retain its last rows with a visible stale/error state and offer a fresh subscription after the cause is resolved.
Verify inserts, updates, and deletes
While the example is running, execute these statements one at a time through another connection using the same identity:
On a fresh table, expect an empty inbox, then one message, then the edited body without a duplicate, then an empty inbox again. Press Ctrl+C to unsubscribe and disconnect. Re-running the app starts a fresh snapshot, so existing rows appear too.
Choose rows, typed tables, or events
| Need | API | What your code receives |
|---|---|---|
| A filtered list | client.live(sql, onRows, options) | The current materialized rows |
| All rows in a table | client.liveTable('app.live_messages', onRows) | The same row-list behavior |
| Generated Drizzle models | ORM liveTable(client, table, onRows, options) | Typed properties rather than cell wrappers |
| Protocol events or custom persistence | client.liveEvents(sql, onEvent, options) | Snapshot batches and individual changes |
With no mapRow, fields are KalamCellValue objects: use asString(), asBool(), and other cell accessors. Include the primary key in your SELECT. Use getKey: ['first_key', 'second_key'] for declared key columns; a JavaScript key function uses the TypeScript reconciliation path.
Limits, reconnects, and recovery
- Direct
client.live()SQL supportsSELECT ... FROM ... WHERE .... Keep joins, aggregates,ORDER BY, andLIMITout of this subscription SQL. batchSizecontrols snapshot chunks.lastRowsrestricts the initial history. Directclient.live()’slimitbounds the retained row set; it does not promise a SQL-sorted top-N window.- ORM and React
orderBy/limitare local presentation controls. They retain the matching set so a removed visible row can be replaced. Narrow the filter for large datasets. - The SDK manages connection recovery. Do not create another subscription on every disconnect notification. If you explicitly retry, close the previous subscription first.
- Do not restore a cursor into an empty UI cache and assume it reloads all rows. A durable resume needs matching cached data, query/identity, and checkpoint; an ordinary screen can start fresh without
from. - Replay is bounded. A delivery gap or expired cursor needs recovery, not a claim that the displayed rows are current. See reconnection and resume and the WebSocket recovery contract.
To check failures, temporarily use a missing table or invalid credentials. Expect a visible error instead of an empty-success state. Fix the cause and restart. Disconnect the network to exercise connection callbacks; after recovery, compare the list with a normal SELECT before treating it as current.
Only the owner of a shared client should disconnect it. A component should stop its own subscription; the React hook handles that on unmount.