Skip to main content
A synced table is a table in your schema that is bound to an integration collection. The collection’s sync is its only writer: your queries read it like any other table, and your mutations can’t change it.
bijection/schema.ts
Here customers is filled by the crm integration’s customers collection, and notes is an ordinary table your app writes, pointing at synced customers by their document ID.

Binding a table

Call .source(...) on a table definition with a collection handle, the property of the same name on your integration:
The table’s validator must be exactly the collection’s schema. Passing crm.customers.schema to defineTable is the simplest way to keep the two identical; a table whose fields differ is refused with Source schema must match the table validator. You can add indexes, search indexes and vector indexes to a synced table as usual. A synced table can’t also be a link table, have a governing rule or use a staged validator, and a table binds exactly one collection. The binding holds as soon as you deploy the schema. Until a connection is installed and its first sync completes, the table is simply empty.

Documents in a synced table

Each provider record becomes one document. The document has the fields of the collection’s schema, the usual system fields _id and _creationTime, and one more field that Bijection sets:
  • source_id: an opaque string identifying the installed source that published the record.
A record keeps the same _id when the provider updates it, so your own tables can refer to it with v.id("customers"). When the provider reports that a record was deleted, its document is removed. source_id is part of the generated Doc<"customers"> type and can be indexed. The collection’s schema can’t declare a field of that name.

Reading synced data

Queries read synced tables with the ordinary ctx.db API, and subscriptions update when a sync publishes new data:
bijection/customers.ts
Everything a sync publishes for one page appears in one transaction, so a query never sees half of a page.

Synced tables are read-only

The generated types leave synced tables out of the write methods, so this doesn’t type-check:
bijection/customers.ts
A write that reaches the database anyway fails with a ReadOnlySource error: customers accepts changes only through its admitted source. To keep your own data about a synced record, store it in an ordinary table that references the record, like notes above. To change the record at the provider, send an integration command; see Operations.

Coverage and freshness

An empty result from a synced table can mean two different things: the provider has no matching records, or the source has never finished a sync. To tell them apart, read the source’s coverage with ctx.sources.coverage, in a query or a mutation:
bijection/customers.ts
Pass the source_id of the rows you read. The optional table option also checks that the source publishes into that table. The result has these fields:
  • acquired: whether an initial sync has completed.
  • observedFrom and observedTo: the window the current data covers, in milliseconds since the epoch, or null before the first completed sync.
  • age: how old the newest observation is, in milliseconds.
  • checkedAt: when the source last answered a check successfully.
  • continuityLost: whether the source lost continuity. The data it has is still valid but can no longer be extended.
  • refreshOutstanding: whether a requested refresh hasn’t completed yet.
Coverage is read under the same access rules as the source’s rows, and it is tracked like any other read, so a query that depends on it re-runs when the coverage changes.

Several sources in one table

A synced table can receive records from more than one installed source, for example one per connected account. Each record carries its own source_id. Installing a second source into a table is refused with SourceReadAccessRequired until the table declares a read access rule, because readers of one source’s records aren’t automatically allowed to read another’s. See Access rules.