Skip to main content
Mutations in CL Sync are the mechanism for making changes that need to reach both the local cache and a remote server. Every mutation is written to a durable outbox in IndexedDB before the network call goes out, so your changes survive browser crashes, network drops, and page reloads. A reducer applies the change optimistically so your UI responds instantly.

Mutation lifecycle

When you call store.enqueueMutation(definition, args), CL Sync runs these steps in order:
  1. Reducer — applies an immediate, synchronous optimistic change to the local cache.
  2. Outbox write — serializes args to IndexedDB so the mutation survives a reload.
  3. Flush — calls your async flush function to send the mutation to the server.
  4. Ack or reject — on success, calls onAck; on failure, calls onReject and marks the item "failed" in the outbox.

Defining a mutation

Mutation options

string
required
A unique identifier for this mutation. Used as the mutation field in OutboxItem. Must be stable across deployments so outbox items can be matched to their definitions on replay.
(store: SyncStore, args: TArgs, clientMutationId: string) => void
Runs synchronously and immediately when enqueueMutation is called. Apply your optimistic state change here. This function should be fast and side-effect-free beyond writing to the store.
(args: TArgs, clientMutationId: string) => Promise<TResult>
Async function that sends the mutation to your server. If it throws, the outbox item is marked "failed". The same clientMutationId is used on retries, enabling server-side deduplication.
(store: SyncStore, args: TArgs, result: TResult, clientMutationId: string) => void
Called after flush resolves successfully. Use it to apply server-authoritative data that differs from your optimistic update — for example, a server-generated timestamp or computed field.
(store: SyncStore, args: TArgs, error: unknown, clientMutationId: string) => void
Called when flush throws. Implement rollback logic here. CL Sync does not automatically revert optimistic changes — you are in full control of conflict resolution.

Enqueuing mutations

Call store.enqueueMutation directly or use the useSyncMutation hook in React:
You can pass an explicit clientMutationId as the third argument to enqueueMutation if you need to coordinate IDs across systems. If omitted, CL Sync generates one automatically.

Outbox and durability

Mutations persist in IndexedDB until they are either flushed successfully or explicitly removed. Each outbox entry carries full replay metadata:
On the next page load, replay pending mutations:

Flush options

flushPendingMutations accepts an options object to control which items are processed:
Use predicate to implement exponential back-off — skip items whose attempts count exceeds your retry budget or whose lastError indicates a non-retriable server error (e.g., 400 Bad Request).

Deduplication with clientMutationId

CL Sync generates a stable clientMutationId for each outbox entry and passes it to both flush and onAck. The same ID is reused on every retry. Include it in your request body and deduplicate on the server using this ID:

Viewing the outbox

Inspect all outbox items for the current scope at any time: