Mobile Offline-Sync Architect
Expert in building offline-first mobile applications with local databases, conflict resolution, and reliable background synchronization.
Activation Triggers
Activate on: "offline sync", "offline-first app", "local database mobile", "WatermelonDB", "SQLite sync", "CRDT conflict resolution", "background sync", "mobile data persistence", "op-sqlite"
NOT for: Server databases → data-pipeline-engineer | Web caching → pwa-architect | API design → api-architect
Quick Start
- Choose local database — op-sqlite (performance), WatermelonDB (React Native lazy loading), or Realm (object-oriented)
- Design sync schema — add
updated_at, deleted_at, sync_status columns to all synced tables
- Implement conflict resolution — last-write-wins for simple cases, CRDT for collaborative editing
- Build sync engine — pull-push protocol with delta sync (only changed records)
- Add background sync — React Native Background Fetch or WorkManager for periodic sync
Core Capabilities
| Domain |
Technologies |
| Local DB |
op-sqlite, WatermelonDB, Realm, expo-sqlite |
| Sync Protocols |
Delta sync, CRDT (Yjs, Automerge), operational transform |
| Conflict Resolution |
Last-write-wins, merge functions, CRDT automatic merge |
| Background Sync |
react-native-background-fetch, expo-background-fetch |
| Platforms |
PowerSync, ElectricSQL, Replicache, custom sync engines |
Architecture Patterns
Offline-First Sync Architecture
┌─────────────────────────────────┐
│ Mobile App │
│ ┌──────────┐ ┌─────────────┐ │
│ │ UI Layer │──│ Sync Engine │ │
│ └──────────┘ └──────┬──────┘ │
│ │ │
│ ┌────────────────────┴───────┐ │
│ │ Local SQLite DB │ │
│ │ (source of truth offline) │ │
│ └────────────────────────────┘ │
└────────────────┬────────────────┘
│ Background sync
│ (when online)
▼
┌────────────────────────────────┐
│ Server │
│ ┌──────────┐ ┌────────────┐ │
│ │ Sync API │──│ Server DB │ │
│ │ /sync │ │ (Postgres) │ │
│ └──────────┘ └────────────┘ │
└────────────────────────────────┘
Pull: GET /sync?since=<timestamp> → changed records
Push: POST /sync { changes: [...] } → conflicts
Delta Sync Protocol
interface SyncRequest {
lastSyncTimestamp: string;
changes: ChangeSet[]; // Local changes since last sync
}
interface ChangeSet {
table: string;
created: Record[];
updated: Record[];
deleted: { id: string; deleted_at: string }[];
}
interface SyncResponse {
serverTimestamp: string;
changes: ChangeSet[]; // Server changes since client's lastSync
conflicts: Conflict[]; // Records changed on both sides
}
// Conflict resolution strategy
function resolveConflict(local: Record, server: Record): Record {
// Strategy 1: Last-write-wins (simple, data loss possible)
return local.updated_at > server.updated_at ? local : server;
// Strategy 2: Field-level merge (no data loss for non-conflicting fields)
// return mergeFields(local, server, base);
// Strategy 3: CRDT (automatic, no conflicts by design)
// return crdtMerge(local, server);
}
WatermelonDB Sync Implementation
import { synchronize } from '@nozbe/watermelondb/sync';
async function syncDatabase() {
await synchronize({
database,
pullChanges: async ({ lastPulledAt }) => {
const response = await api.get('/sync', {
params: { since: lastPulledAt },
});
return {
changes: response.data.changes,
timestamp: response.data.serverTimestamp,
};
},
pushChanges: async ({ changes, lastPulledAt }) => {
await api.post('/sync', { changes, lastPulledAt });
},
migrationsEnabledAtVersion: 1,
});
}
Anti-Patterns
- Online-first with offline cache — treating offline as an afterthought. Design offline-first: the local DB is the source of truth; sync is background reconciliation.
- Full table sync — downloading entire tables on every sync. Use delta sync with timestamps or change tracking to transfer only modified records.
- Ignoring conflict resolution — assuming conflicts will not happen. Define a clear strategy (LWW, field merge, or CRDT) and handle conflicts explicitly.
- Sync on main thread — blocking the UI during synchronization. Run sync in a background thread or process with progress callbacks.
- No offline indicator — users do not know when they are offline. Show connectivity status and sync state (synced, syncing, pending changes) in the UI.
Quality Checklist
[ ] Local database chosen and configured (op-sqlite, WatermelonDB, Realm)
[ ] Sync schema includes updated_at, deleted_at, sync_status columns
[ ] Delta sync protocol implemented (not full-table)
[ ] Conflict resolution strategy defined and tested
[ ] Background sync configured (periodic + on-reconnect)
[ ] Offline indicator visible in UI
[ ] Pending local changes count displayed
[ ] Sync errors handled gracefully (retry with exponential backoff)
[ ] Data integrity: no data loss during conflict resolution
[ ] Large dataset performance tested (10K+ records)
[ ] Soft deletes used (deleted_at, not hard DELETE)
[ ] Sync works after app kill and restart
1---2name: mobile-offline-sync-architect3description: Mobile offline-first architecture with local databases, CRDT conflict resolution, and background sync. Activate on: offline sync, offline-first, local database, WatermelonDB, SQLite, CRDT, conflict resolution, background sync, mobile persistence. NOT for: server-side databases (use data-pipeline-engineer), web caching strategies (use pwa-architect), API design (use api-architect).4license: Apache-2.05---67# Mobile Offline-Sync Architect89Expert in building offline-first mobile applications with local databases, conflict resolution, and reliable background synchronization.1011## Activation Triggers1213**Activate on:** "offline sync", "offline-first app", "local database mobile", "WatermelonDB", "SQLite sync", "CRDT conflict resolution", "background sync", "mobile data persistence", "op-sqlite"1415**NOT for:** Server databases → `data-pipeline-engineer` | Web caching → `pwa-architect` | API design → `api-architect`1617## Quick Start18191. **Choose local database** — op-sqlite (performance), WatermelonDB (React Native lazy loading), or Realm (object-oriented)202. **Design sync schema** — add `updated_at`, `deleted_at`, `sync_status` columns to all synced tables213. **Implement conflict resolution** — last-write-wins for simple cases, CRDT for collaborative editing224. **Build sync engine** — pull-push protocol with delta sync (only changed records)235. **Add background sync** — React Native Background Fetch or WorkManager for periodic sync2425## Core Capabilities2627| Domain | Technologies |28|--------|-------------|29| **Local DB** | op-sqlite, WatermelonDB, Realm, expo-sqlite |30| **Sync Protocols** | Delta sync, CRDT (Yjs, Automerge), operational transform |31| **Conflict Resolution** | Last-write-wins, merge functions, CRDT automatic merge |32| **Background Sync** | react-native-background-fetch, expo-background-fetch |33| **Platforms** | PowerSync, ElectricSQL, Replicache, custom sync engines |3435## Architecture Patterns3637### Offline-First Sync Architecture3839```40┌─────────────────────────────────┐41│ Mobile App │42│ ┌──────────┐ ┌─────────────┐ │43│ │ UI Layer │──│ Sync Engine │ │44│ └──────────┘ └──────┬──────┘ │45│ │ │46│ ┌────────────────────┴───────┐ │47│ │ Local SQLite DB │ │48│ │ (source of truth offline) │ │49│ └────────────────────────────┘ │50└────────────────┬────────────────┘51 │ Background sync52 │ (when online)53 ▼54┌────────────────────────────────┐55│ Server │56│ ┌──────────┐ ┌────────────┐ │57│ │ Sync API │──│ Server DB │ │58│ │ /sync │ │ (Postgres) │ │59│ └──────────┘ └────────────┘ │60└────────────────────────────────┘6162Pull: GET /sync?since=<timestamp> → changed records63Push: POST /sync { changes: [...] } → conflicts64```6566### Delta Sync Protocol6768```typescript69interface SyncRequest {70 lastSyncTimestamp: string;71 changes: ChangeSet[]; // Local changes since last sync72}7374interface ChangeSet {75 table: string;76 created: Record[];77 updated: Record[];78 deleted: { id: string; deleted_at: string }[];79}8081interface SyncResponse {82 serverTimestamp: string;83 changes: ChangeSet[]; // Server changes since client's lastSync84 conflicts: Conflict[]; // Records changed on both sides85}8687// Conflict resolution strategy88function resolveConflict(local: Record, server: Record): Record {89 // Strategy 1: Last-write-wins (simple, data loss possible)90 return local.updated_at > server.updated_at ? local : server;9192 // Strategy 2: Field-level merge (no data loss for non-conflicting fields)93 // return mergeFields(local, server, base);9495 // Strategy 3: CRDT (automatic, no conflicts by design)96 // return crdtMerge(local, server);97}98```99100### WatermelonDB Sync Implementation101102```typescript103import { synchronize } from '@nozbe/watermelondb/sync';104105async function syncDatabase() {106 await synchronize({107 database,108 pullChanges: async ({ lastPulledAt }) => {109 const response = await api.get('/sync', {110 params: { since: lastPulledAt },111 });112 return {113 changes: response.data.changes,114 timestamp: response.data.serverTimestamp,115 };116 },117 pushChanges: async ({ changes, lastPulledAt }) => {118 await api.post('/sync', { changes, lastPulledAt });119 },120 migrationsEnabledAtVersion: 1,121 });122}123```124125## Anti-Patterns1261271. **Online-first with offline cache** — treating offline as an afterthought. Design offline-first: the local DB is the source of truth; sync is background reconciliation.1282. **Full table sync** — downloading entire tables on every sync. Use delta sync with timestamps or change tracking to transfer only modified records.1293. **Ignoring conflict resolution** — assuming conflicts will not happen. Define a clear strategy (LWW, field merge, or CRDT) and handle conflicts explicitly.1304. **Sync on main thread** — blocking the UI during synchronization. Run sync in a background thread or process with progress callbacks.1315. **No offline indicator** — users do not know when they are offline. Show connectivity status and sync state (synced, syncing, pending changes) in the UI.132133## Quality Checklist134135```136[ ] Local database chosen and configured (op-sqlite, WatermelonDB, Realm)137[ ] Sync schema includes updated_at, deleted_at, sync_status columns138[ ] Delta sync protocol implemented (not full-table)139[ ] Conflict resolution strategy defined and tested140[ ] Background sync configured (periodic + on-reconnect)141[ ] Offline indicator visible in UI142[ ] Pending local changes count displayed143[ ] Sync errors handled gracefully (retry with exponential backoff)144[ ] Data integrity: no data loss during conflict resolution145[ ] Large dataset performance tested (10K+ records)146[ ] Soft deletes used (deleted_at, not hard DELETE)147[ ] Sync works after app kill and restart148```