Durability Tiers
API reference for read and write durability tiers: the options that control how far data must propagate before an operation confirms.
Jazz data propagates through tiers, from fast local confirmation to broader reach.
| Tier | Where | When to use |
|---|---|---|
"local" | On the device | Default. Instant writes, no network needed. |
"edge" | Nearest sync server | Confirm data has left the device before continuing. |
"global" | Central server | Ensure all clients can see the data, regardless of location. |
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 "remote-if-possible":
"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."remote-if-possible"prioritizes remote data, but falls back to local data if the app explicitly callsdb.disconnect()(a timeout, connection error, or slow response never triggers that fallback). Online, pending edits/deletes to in-scope rows and matching new inserts appear before server approval. Editing an existing out-of-scope row does not bring it into the result; cached related rows are not pulled in automatically.
A one-shot keeps its initial choice. A "remote-if-possible" subscription follows
confirmed disconnect/reconnect transitions in both directions, waiting for a fresh
remote scope on reconnect. Losing remote access does not erase cached data:
local-first and offline fallback may still show it. An actual synced deletion,
however, hides the row from local reads too.
The older read values "local", "edge", and "global" remain accepted
with their existing semantics during migration. They are legacy read controls only;
write durability and wait({ tier }) continue to use those values.
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.
const todosAtEdgeDurability = useAll(app.todos, { tier: "edge" });export function subscribeTodosAtEdge(db: Db, onCount: (count: number) => void) {
return db.subscribe(app.todos.where({ done: false }), (todos) => onCount(todos.length), {
tier: ReadTier.Remote,
});
}const todosAtEdgeDurability = new QuerySubscription(app.todos, { tier: 'edge' });export function subscribeTodosAtEdge(db: Db, onCount: (count: number) => void) {
return db.subscribe(app.todos.where({ done: false }), (todos) => onCount(todos.length), {
tier: ReadTier.Remote,
});
}export async function readTodosAtEdgeDurability(db: Db) {
return db.all(app.todos.where({ done: false }), { tier: ReadTier.Remote });
}pub 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::EdgeServer))
.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.
The read tier gates the first delivery of a subscription only. After the initial snapshot
arrives at the requested tier, subsequent updates are delivered as they reach the local node,
regardless of which tier they've propagated to. For example, a subscription with tier: "global"
guarantees a globally-consistent initial snapshot, but later incremental updates from other
clients may arrive through edge tiers before being globally available even if the durability of
the write is set to 'global'.