> ## Documentation Index
> [HTML page](https://blode.co/stratasync/docs/architecture/data-flow)
> [Documentation index](https://blode.co/stratasync/docs/llms.txt)
> Use the index to discover all available pages before exploring further.

# Data flow

How mutations and deltas flow through Strata Sync, from user input to server confirmation and back.

Two primary data flows: **client writes** (mutations going to the server) and **server pushes** (deltas coming back).

## Client write flow

Call a mutation method on the sync client. The change applies optimistically, persists locally, and sends to the server in the background.

```mermaid
sequenceDiagram
  participant UI as React Component
  participant Client as Sync Client
  participant Store as Identity Map
  participant IDB as IndexedDB
  participant Outbox as Outbox Manager
  participant Transport as Transport Adapter
  participant Server as API Server

  UI->>Client: client.update("Task", id, changes)
  Client->>Client: Create transaction (clientTxId + idempotency key)
  Client->>Store: Apply optimistic update
  Store-->>UI: MobX reaction (UI re-renders)
  Client->>IDB: Persist updated row
  Client->>Outbox: Add transaction to outbox
  Client->>IDB: Persist outbox entry
  Outbox->>Transport: Batch and send mutations
  Transport->>Server: POST /sync/mutate
  Server->>Server: Validate, assign syncId, persist
  Server-->>Transport: Mutation result
  Transport-->>Outbox: Acknowledge transaction
  Outbox->>IDB: Remove from outbox
```

### Key steps

1. **Optimistic apply**: Creates a `Transaction` with an idempotency key, applies to the identity map, and persists to IndexedDB.

2. **Batch send**: The outbox batches transactions based on `batchDelay` (default 50 ms), reducing network overhead.

3. **Server processing**: Validates the mutation, assigns a `syncId`, persists, and broadcasts a delta to all clients.

4. **Acknowledgment**: The server responds with the assigned `syncId`. The outbox removes acknowledged transactions.

## Server push flow

Deltas arrive through the WebSocket subscription when another client writes a change or the server processes your own mutation.

```mermaid
sequenceDiagram
  participant Server as API Server
  participant WS as WebSocket
  participant Transport as Transport Adapter
  participant Client as Sync Client
  participant Rebase as Rebase Engine
  participant Store as Identity Map
  participant IDB as IndexedDB
  participant UI as React Component

  Server->>WS: Delta packet (syncActions + lastSyncId)
  WS->>Transport: Receive delta
  Transport->>Client: onDelta(packet)
  Client->>IDB: Write batch (apply all actions)
  Client->>Store: Update identity map
  Client->>Rebase: Rebase pending transactions
  Rebase-->>Store: Re-apply local intent
  Client->>IDB: Update lastSyncId
  Store-->>UI: MobX reaction (UI re-renders)
```

### Key steps

1. **Batch write**: Applies all sync actions from the `DeltaPacket` to IndexedDB in a single atomic write.

2. **Identity map update**: Inserts create instances, updates modify them, deletes remove them.

3. **Rebase**: If affected models have pending local transactions, the rebase engine reconciles server state with local intent.

4. **Watermark advance**: Advances `lastSyncId` to the packet's watermark, so the next delta fetch starts from the right place.

## Rendering during sync

Delta application is designed so the UI lands on final state instead of visibly
stepping through changes.

**One flush per packet, not per action.** All identity map mutations for a packet
run inside a single `batch()`, so observers never see a half-applied packet — server
data and re-applied pending local changes appear together.

**One flush per backlog, not per page.** When catch-up spans multiple pages, the
pages are buffered and applied as one merged packet. A client that has been offline
for a while resumes in one step rather than animating through the queue. Use
`useSyncCatchingUp()` for a quiet indicator while that happens — the client is
already `"syncing"` and rendering cached data throughout, so don't gate rendering
on it.

**One batched write, not two round trips per action.** A packet resolves in memory
and persists as a single `writeBatch`.

**Queries skip irrelevant changes.** `useQuery` tracks which ids currently satisfy
its predicate. A delta for a model that is neither in the result set nor newly
matching is discarded without rescanning.

For per-cell rendering granularity, wrap components that read model properties in
MobX's `observer()`. Each property is its own observable, so a delta touching 50
rows re-renders 50 cells rather than the whole list. See the
[MobX adapter](/packages/mobx).

## Conflict resolution (rebase)

Field-level LWW rebase resolves conflicts between server deltas and pending local transactions. Each pending transaction stores a `patch` and an `original` snapshot. Non-overlapping changes merge cleanly; overlapping fields use the configured `rebaseStrategy` (`"server-wins"`, `"client-wins"`, or `"merge"`).

See [Conflict resolution](/guides/conflict-resolution) for strategies and examples.

## Offline and reconnect flow

Read and write while disconnected: the outbox queues mutations in IndexedDB until the connection returns.

```mermaid
sequenceDiagram
  participant UI as React Component
  participant Client as Sync Client
  participant IDB as IndexedDB
  participant Outbox as Outbox Manager
  participant Transport as Transport Adapter

  Note over Transport: Connection lost
  UI->>Client: client.update("Task", id, changes)
  Client->>Client: Create transaction
  Client->>IDB: Persist optimistic update
  Client->>Outbox: Queue transaction
  Client->>IDB: Persist to outbox
  Note over UI: UI shows changes immediately

  Note over Transport: Connection restored
  Transport->>Client: Reconnected
  Client->>Transport: Fetch deltas after lastSyncId
  Transport-->>Client: Catch-up delta packet
  Client->>Client: Apply deltas + rebase
  Outbox->>Transport: Replay pending transactions
  Transport-->>Outbox: Acknowledge
```

On reconnection, the client fetches missed deltas, then replays the outbox. Idempotency keys make retry safe, even if a mutation was sent but not acknowledged before the disconnect.

See [Offline-first guide](/guides/offline-first) for configuration and best practices.

## Transaction lifecycle

Each outbox transaction moves through these states:

```mermaid
stateDiagram-v2
  [*] --> Queued: created
  Queued --> Sent: transport sends
  Sent --> AwaitingSync: server acknowledges
  AwaitingSync --> Confirmed: delta with syncId arrives
  Sent --> Queued: network error (retry)
  Sent --> Failed: server rejects
  Confirmed --> [*]: removed from outbox
  Failed --> [*]: removed from outbox
```

**Queued** (waiting to send), **Sent** (awaiting response), **AwaitingSync** (acknowledged, delta pending), **Confirmed** (done, removed from outbox), **Failed** (rejected, rolled back). Network errors move back to Queued for retry.