# Offline & persistence

Nizhal does not implement the client store. **TanStack DB** provides collections, incremental live queries (`db-ivm`), and a durable outbox (`offline-transactions`).

## Write path

1. UI calls `mutate.someMutator(args)` from `createNizhalMutators`
2. TanStack DB applies optimistic local state
3. `offline-transactions` enqueues with `clientMutationId` / idempotency key
4. When online, the executor POSTs to `/sync/push`
5. Server ack → outbox entry removed; reject → optimistic rebase

Poison mutations (deterministic failures after bounded retries) **dead-letter** without wedging the queue; dependents cascade-cancel (REQ-13).

## Read path

`useLiveQuery` over Nizhal-backed collections updates incrementally as pull applies rows — no manual `refetch()`.

## Persistence

| Export | Platform |
|--------|----------|
| `waSqlitePersistence` | Web — wa-sqlite + OPFS |
| `opSqlitePersistence` | React Native — op-sqlite JSI |
| `migrateClientStore` | Client-store schema migrations |

Pass persistence into `nizhalCollectionOptions({ persistence: waSqlitePersistence({ ... }) })`.

Survives refresh and app restart: the outbox and collection state live in SQLite, not memory.

## TTL / eviction

`ttl` on `createNizhalClient` evicts out-of-scope bucket rows locally when the client's sync scope shrinks — pairs with `removedBuckets` from pull.

## Status inspection

`createNizhalStatus` exposes sync status and poison-quarantine outbox entries for UI surfacing.

## Next

- [Persistence](/client/persistence/)
- [Mutators](/client/mutators/)