Durability Tiers

API reference for read and write durability tiers: the options that control how far data must propagate before an operation confirms.

Write waits distinguish local persistence from Core acceptance. Synchronization continues with either choice.

TierWhereWhat confirmation means
"local"This deviceSaved by the local persistence driver, without waiting for the network.
"global"CoreAuthorized and durably accepted by Core. Other clients receive updates through their subscriptions.

For background on how data flows between tiers, see How Sync Works.

Write tiers

Mutations such as insert, upsert, update, delete, and restore apply locally with no durability guarantee and return a MutationResult. Transaction helpers resolve to the same kind of result. Call .wait({ tier: ... }) when you need confirmation that the mutation reached a specific durability tier.

See Writing Data for detailed guidance on which tier to use and code examples.

Mutation errors

Jazz throws errors it can detect locally as soon as you call the mutation. The server may reject the change later, e.g. if the user does not have permissions. The change can appear locally before that rejection arrives. If this happens, .wait(...) rejects and Jazz removes the local change.

If you are not waiting with .wait(...), use db.onMutationError(listener) to handle a rejection. Transactions use .wait(...) and report errors in the same way as individual mutations.

Read tiers

Read choices control which data a query uses and when its first result is delivered. They are intentionally separate from write durability.

New applications should use the product read choices "local-first", "remote", and "local-first-unless-empty":

  • "local-first" uses cached local knowledge and shows pending local writes immediately, while still syncing.
  • "remote" uses the server's current query scope, without pending local writes. It waits while offline.
  • "local-first-unless-empty" is "local-first" with one exception: when the local result is empty and the server is reachable (or its first connection is still being set up), the first result waits for the server's first answer. If local data exists, it is shown immediately and catches up with the server as it syncs.

"local-first-unless-empty" never waits on a server it cannot reach. The first result is local, and immediate, when no server is configured, after db.disconnect(), while the connection is down or retrying, or when the connection attempt fails. While a connection attempt is in progress, an empty result waits at most until five seconds after that attempt started, however late the read begins. Once connected, it waits for the server's first answer without a time limit. If the connection drops while an empty result is waiting, the empty result is delivered at that moment. After the first result, a subscription behaves exactly like "local-first", and remote rows appear as they sync.

Queries with an offset are the exception. A partly synced local cache would apply the offset to the wrong rows, so under "local-first-unless-empty" an offset window reads the server's page whenever the server can answer, and the local page only when it cannot. A window showing the local page switches to the server's page as soon as the server answers, so rows can shift at that moment (for example after a local insert).

Losing remote access does not erase cached data: local-first reads may still show it. An actual synced deletion, however, hides the row from local reads too.

The read aliases "local" and "global" remain accepted. Prefer the read choices above for queries; write durability uses wait({ tier: "local" | "global" }).

These options apply to:

  • db.all(query, options?)
  • db.one(query, options?)
  • db.subscribe(query, callback, options?)
  • useAll(query, options?) / useAllSuspense(query, options?) (React/Expo)
  • new QuerySubscription(query, options?) (Svelte)
  • useAll(query, options?) (Vue)
  • useAll(() => ({ query, options })) (Solid)

The React Native/Expo alpha uses these same read and write durability tiers after opening its account-handle client with createJazzClient. Its native relay owns persistence and upstream connectivity; do not substitute a browser storage driver or a JavaScript-side SQLite path.

App.tsx
const todosAtGlobalDurability = useAll(app.todos, { tier: "global" });

For most queries and subscriptions, omitting a tier is the right choice: Jazz delivers results from local storage immediately and streams in remote updates as they arrive. Reserve explicit tiers for cases where eventual consistency is not acceptable.

Reads and write confirmation are different

Strict remote subscriptions use Core-authorized state for later updates as well as their initial result. Local-first subscriptions may display pending changes before Core accepts them. Waiting for a write does not change the read choice of a separate subscription.