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:

  1. Upsert changed rows.
  2. Delete removed rows.
  3. Skip byte-identical rows when safe.
  4. 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 isSyncing is true.

  • Name
    Handle pending writes explicitly
    Description

    If pendingMutations > 0, avoid claiming remote completion.

Practical tradeoffs

  1. Last-write-wins style behavior is simple and resilient, but business-critical fields may still need explicit user workflows.
  2. Automatic repair keeps clients healthy, but heavy repair can be expensive on large tables.
  3. Stronger user messaging around sync states usually matters more than exposing merge internals.

Next pages

  1. React
  2. Next.js
  3. Expo

Was this page helpful?