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.
| Tier | Where | What confirmation means |
|---|---|---|
"local" | This device | Saved by the local persistence driver, without waiting for the network. |
"global" | Core | Authorized 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.tsxconst todosAtGlobalDurability = useAll(app.todos, { tier: "global" });
App.vueexport function subscribeTodosAtGlobal(db: Db, onCount: (count: number) => void) {return db.subscribe(app.todos.where({ done: false }), (todos) => onCount(todos.length), {tier: ReadTier.Remote,});}
App.svelteconst todosAtGlobalDurability = new QuerySubscription(app.todos, { tier: 'global' });
App.tsxexport function subscribeTodosAtGlobal(db: Db, onCount: (count: number) => void) {return db.subscribe(app.todos.where({ done: false }), (todos) => onCount(todos.length), {tier: ReadTier.Remote,});}
app.tsexport async function readTodosAtEdgeDurability(db: Db) {return db.all(app.todos.where({ done: false }), { tier: ReadTier.Remote });}
app.tsexport const readTodosAtEdgeDurability = Effect.gen(function* () {const jazz = yield* Jazz;return yield* jazz.all(app.todos.where({ done: false }), { tier: ReadTier.Remote });});
main.rspub async fn read_todos_at_edge_durability(client: &JazzClient) -> jazz::tools::Result<usize> {let query = Query::from("todos");let rows = client.query(query, Some(DurabilityTier::GlobalServer)).await?;Ok(rows.len())}
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.