queryDb creates a LiveQueryDef — a reusable, reactive query definition that runs against the local SQLite state. When the tables it reads change (because events were committed), any subscriber to the query is notified and the query re-executes.
Function signature
Return type
queryDb returns a LiveQueryDef<TResult>. This is a reusable definition — not an active query. Pass it to store.query() for a one-shot read or store.subscribe() / store.useQuery() for reactive updates.
Parameters
The query to execute. Three forms are accepted:
- Query builder — type-safe builder expression from a table definition (e.g.
tables.todos.where({ completed: false })) - Raw SQL object —
{ query, schema, bindValues? }for queries the builder cannot express - Thunk function —
(get) => QueryBuilder | QueryInputRawfor queries that depend on other reactive state
Human-readable name shown in the DevTools. Recommended for all queries to ease debugging.
Explicit dependency array. Required on Expo / React Native where
fn.toString() returns [native code]. Also use this when the query function closes over external variables that should invalidate the query definition.Optional transform applied to the raw query result before it is stored. Useful for post-processing or reshaping data.
Usage
Query builder (recommended)
The query builder is the primary way to query data. It provides type safety and automatically tracks which tables the query reads.Raw SQL
Use the{ query, schema, bindValues } form for queries that the builder cannot express, such as GROUP BY, JOIN, or window functions.
Dynamic queries with deps
When a query depends on external variables (for example, a search string from a URL parameter), wrap the query builder in a function. Include the variable in deps so the query definition is re-keyed when it changes.
Reactive dependencies with get
When a query should automatically react to another signal or computed value, use a function that calls get(otherQuery$) to read its current value and register a dependency.
uiState$ changes, todos$ re-executes its query function and fetches a fresh result. The dependency is established automatically — no deps array is required unless you are on Expo / React Native.
How reactive queries work
- You call
store.commit(event). - The materializer writes to one or more SQLite tables.
- LiveStore tracks which tables changed and invalidates the reactive refs for those tables.
- All
queryDbqueries that read any of those tables are scheduled to re-execute. - Subscribers (React hooks,
store.subscribe()callbacks) receive the new result.
Accessing query results
ALiveQueryDef is a definition, not a result. Access the data in one of these ways:
Raw SQL object shape
When passing a raw SQL object, theschema field describes the shape of one row. LiveStore decodes the raw SQLite result using this schema.
Performance considerations
- Memoization — Results are compared with Effect Schema equivalence. Structurally identical results do not re-trigger downstream effects.
- Batch commits — When committing many events at once, use
{ skipRefresh: true }and callstore.manualRefresh()after to batch reactive notifications. - Table granularity — Reactivity is per-table. A commit to
todosdoes not invalidate queries that only readuiState. - Deps stability — Keep the
depsarray stable across renders. Changing a dep key forces the query definition to be recreated and re-executed.