Designing a sync engine with automatic rollback
Every write in our system goes through four phases: validate, optimistic update, atomic persist, and push. If any phase fails, the previous phases roll back automatically.
Phase 1: Validate
Before touching any storage, we validate the mutation payload against a Zod schema. This catches type errors, missing required fields, and constraint violations before they corrupt local state.
Phase 2: Optimistic update
We update Zustand immediately. The user sees the result before anything hits the disk.
Phase 3: Atomic persist
We write to IndexedDB in a transaction. The mutation record and the entity record are written together. If the transaction fails, we roll back the Zustand update from phase 2.
Phase 4: Push
We add the mutation to the push queue. The sync manager processes the queue and sends mutations to the server via tRPC. If the push fails, we retry with exponential backoff. After too many retries, the mutation moves to the dead-letter queue.
The dead-letter queue
Mutations in the dead-letter queue are surfaced in the UI so the user can inspect them, retry manually, or discard them. This prevents silent data loss.