Conflict behavior
Conflicts happen when local state and remote state diverge. FFDB resolves this during sync with a pragmatic strategy: push queued writes, then reconcile local tables with remote changes using upserts, deletes, and targeted repair.
Why conflict handling is explicit
Offline-first apps need predictable merge behavior, not hidden magic.
- Name
Stable outcomes- Description
Writes queued locally are pushed first, then fresh data is pulled to align local state.
- Name
Selective reconciliation- Description
Changed rows are upserted, removed rows are deleted, and no-op rows are skipped when possible.
- Name
Self-healing paths- Description
If local counts drift too far from remote counts, FFDB can rebuild table state from a clean pull.
Reconciliation concepts
type SyncTableResult = {
upserts: Record<string, unknown>[]
deletes: unknown[]
rowCount?: number
hasMore: boolean
}
In practice, conflict handling combines:
- Upsert changed rows.
- Delete removed rows.
- Skip byte-identical rows when safe.
- Trigger focused repair or full refresh when drift exceeds a threshold.
End-to-end conflict-safe pattern
import { createClient } from 'ffdb-client'
import type { Database } from './ffdb.types'
const { db, sync } = await createClient<Database>({
config: { apiUrl: import.meta.env.VITE_FFDB_API_URL },
offline: {
adapter: offlineAdapter,
syncOnReconnect: true,
syncOnFocus: true,
},
})
await db
.updateTable('task')
.set({ title: 'Updated title' })
.where('id', '=', taskId)
.execute()
if (sync) {
const { pushed, failed } = await sync.run()
if (failed > 0) {
console.warn('Some writes still pending reconciliation')
}
await sync.waitForIdle(10_000)
}
UI strategy for conflict-sensitive screens
- Name
Observe sync status- Description
Use sync status to display “syncing”, “offline”, and “retry required” states.
- Name
Avoid hard assumptions during active sync- Description
Treat data as converging while
isSyncingis true.
- Name
Handle pending writes explicitly- Description
If
pendingMutations > 0, avoid claiming remote completion.
Practical tradeoffs
- Last-write-wins style behavior is simple and resilient, but business-critical fields may still need explicit user workflows.
- Automatic repair keeps clients healthy, but heavy repair can be expensive on large tables.
- Stronger user messaging around sync states usually matters more than exposing merge internals.
The most effective conflict UX pattern is clear status plus retry controls, not forcing users to reason about merge algorithms.