Reference

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.

TierWhereWhen to use
"local"On the deviceDefault. Instant writes, no network needed.
"edge"Nearest sync serverConfirm data has left the device before continuing.
"global"Central serverEnsure 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 calls db.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.

App.tsx
const todosAtEdgeDurability = useAll(app.todos, { tier: "edge" });
App.vue
export function subscribeTodosAtEdge(db: Db, onCount: (count: number) => void) {
  return db.subscribe(app.todos.where({ done: false }), (todos) => onCount(todos.length), {
    tier: ReadTier.Remote,
  });
}
App.svelte
const todosAtEdgeDurability = new QuerySubscription(app.todos, { tier: 'edge' });
App.tsx
export function subscribeTodosAtEdge(db: Db, onCount: (count: number) => void) {
  return db.subscribe(app.todos.where({ done: false }), (todos) => onCount(todos.length), {
    tier: ReadTier.Remote,
  });
}
app.ts
export async function readTodosAtEdgeDurability(db: Db) {
  return db.all(app.todos.where({ done: false }), { tier: ReadTier.Remote });
}
main.rs
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'.

On this page