FFDB Docs

Conflict behavior

Understand server-sequence last-write-wins, mutation receipts, tombstones, and resnapshot behavior.

Resolve competing offline writes

Conflict behavior explains deterministic last-write-wins ordering, tombstones, receipts, and rejected mutations.

Users need predictable outcomes when disconnected writers update or delete the same row.

Use it while designing offline UX, reconciliation, retry behavior, or retention policy.

Requirements for Conflict behavior

  • Prerequisite — Stable mutation IDs, base row versions, and user-visible rejected-operation handling.
  • Prerequisite — A replica that applies server sequence monotonically.
  • Required value — Server sequence, row version, operation, mutation receipt status, tombstone retention, and cursor horizon.
  • Required value — Product rules for showing superseded or rejected edits.

Deterministic ordering

The later server commit sequence wins update/update, update/delete, and delete/recreate conflicts. Client timestamps are diagnostic only and never order writes.

  • Mutation IDs are idempotent within the verified subject and access-token scope.
  • Reusing an ID with different content is rejected.
  • Deletes create retained tombstones so stale replicas cannot resurrect rows.
  • Compaction respects separate change, tombstone, cursor, and receipt horizons.

Handle every mutation result

A push may apply, deduplicate, supersede, or reject each mutation independently. Remove applied and duplicate work from the pending queue, refresh superseded rows from the server sequence, and keep rejected work visible with its stable error code.

conflict-handler.tsts
const push = await ffdb.sync.push({
  schema_version: snapshot.schema_version,
  mutations: pendingMutations,
});

for (const item of push.results) {
  switch (item.status) {
    case "applied":
    case "duplicate":
      await removePendingMutation(item.mutation_id);
      break;
    case "superseded":
      await refreshAffectedRow(item.server_sequence);
      break;
    case "rejected":
      await showSyncIssue(item.mutation_id, item.error_code);
      break;
  }
}

Conflict behavior workflow

  • 1. Capture a row version before editing.
  • 2. Queue the mutation with diagnostic client time.
  • 3. Push and record the server result.
  • 4. Pull the authoritative logical change.
  • 5. Show rejected or superseded outcomes without silently resurrecting data.

Verify conflict behavior

Concurrent updates resolve by server commit sequence and deletes remain protected from stale resurrection.

Troubleshoot conflict behavior

  • Client time changes ordering — remove that logic; timestamps are diagnostic only.
  • A tombstone disappears before lagging cursors advance — extend retention and force safe resnapshot.

Continue from Conflict behavior

  • Design rejected-mutation UX.
  • Test update/delete and delete/recreate races.