Live reads
Every read has a live form. A live read answers now, and again whenever its answer changes. A change that doesn’t touch what a read read doesn’t wake it.
const deals = matter.table('Deals')
const stop = deals.watch({ index: 'by_stage', eq: { Stage: 'Lead' }, limit: 50 }, (state) => { if (state.status === 'ready') render(state.value.rows) else if (state.status === 'error') showError(state.error.message)})
// Later, when the view goes away:stop()| Live read | Its value |
|---|---|
watch(read, listener) |
A page of rows, as query gives it. |
watchCount(read, listener) |
A count. |
watchExists(read, listener) |
true or false. |
watchGroupCount(read, listener) |
Groups, as groupCount gives them. |
watchIds(read, listener) |
{ ids, nextCursor }. |
watchRows(ids, listener) |
{ rows }: these rows, whenever one of them changes. |
They take the same options as the reads, and each returns a function that stops it.
The listener’s state
Section titled “The listener’s state”The listener first gets { status: 'loading' }, then:
{ status: 'ready', value }, now and after every change.sourcesays whether the answer came from this computer ('local') or the server ('server').cached: truemarks an earlier answer shown while the read refreshes.{ status: 'error', error: { code, message } }if the read fails.
Your own writes show in live reads on this computer at once, before the server accepts them.
One stream per page
Section titled “One stream per page”All the live reads of a page, or of a Node process, share one event stream, which reconnects by itself and registers every read again. You don’t manage connections.
Workers watch too. Prospector’s worker wakes whenever one of the member’s actions becomes approved, using watchIds, and sends it:
matter .table('Actions') .watchIds( { index: 'by_sender_status', eq: { Sender: '$viewer', Status: ['approved', 'sending'] }, limit: 500 }, (state) => { if (state.status === 'ready') void sendApproved() }, )In React
Section titled “In React”Wrap the live reads in hooks. A minimal one:
import { useEffect, useState } from 'react'import matter, { type Row, type Table, type TableRead, type Tables } from '/_matter/sdk.js'
type TableName = keyof Tables & string
export function useQuery<N extends TableName>(table: N, read: TableRead<Tables[N]> | null) { const [rows, setRows] = useState<Row<Tables[N]['values']>[]>([]) const key = JSON.stringify(read) useEffect(() => { if (!read) return // The read is checked by its type above; watch it through the untyped table. const untyped = matter.table(table) as unknown as Table return untyped.watch(read as never, (state) => { if (state.status === 'ready') setRows(state.value.rows as Row<Tables[N]['values']>[]) }) }, [table, key]) return rows}TableRead, TableMatch and TableGroupRead keep each index’s own checks in your helpers. See Typed tables.