AI agents: fetch the documentation index at llms.txt. Markdown versions are available by appending .md to any page URL, including this page's markdown.
Conflict resolution
Rebase strategies, field-level conflict detection, and manual resolution for concurrent edits.
When multiple clients modify the same data, the rebase algorithm resolves conflicts automatically. This guide covers detection, strategies, and manual intervention.
When a server delta arrives for a model with pending local transactions, the client replays local changes on top of the new server state.
flowchart TD A[Server delta arrives] --> B[Index pending txs by model+id] B --> C{For each server action} C --> D{Is this our own tx?} D -->|Yes| E[Mark as confirmed] D -->|No| F{Conflicts with pending tx?} F -->|No| G[Keep pending tx] F -->|Yes| H[Detect conflict type] H --> I[Apply resolution strategy] I --> J[Update in-memory model] C --> K[Remaining txs stay pending]
Three outcomes per server action:
Own transaction: clientTxId matches: confirmed and removed from outbox.
No conflict: Different model/ID or different fields. Pending transaction stays.
Conflict: Overlapping fields. Conflict type detected and strategy applied.
Each pending update stores { modelId, payload, original } where original[field] is the value when editing started:
Client original state: { title: "Draft", status: "open" }
Client pending update: { title: "Final" } <- user changed title
Server delta arrives: { title: "Draft", status: "closed" } <- another user changed status
With field-level conflict detection enabled (the default), title has a pending client change but no server change, so no conflict. status has a server change but no pending client change, so no conflict. Both changes merge to { title: "Final", status: "closed" }.
Conflict types
Four conflict types:
Type
Local action
Server action
Example
update-update
Update
Update
Both changed the same field
update-delete
Update
Delete
Client edited, server deleted
delete-update
Delete
Update
Client deleted, server edited
insert-insert
Insert
Insert
Both inserted the same ID (rare)
Archive (A) and Unarchive (V) actions are normalized to Update (U) for conflict detection.
Resolution strategies
Three strategies:
server-wins (default)
Accepts the server's version and discards the pending transaction.
rebaseStrategy: "server-wins" with rebaseConflict event handler for custom UI
Force-update operations
rebaseStrategy: "client-wins"
Field-level conflict detection
Enabled by default. Only changes to the same fields trigger a conflict. Disable to treat any concurrent edit to the same model+ID as a conflict:
const client = createSyncClient({ // ...adapters fieldLevelConflicts: false, // any overlapping model+ID is a conflict});
Overlapping fields: Both client and server changed the same field (true conflict).
Non-overlapping fields: Client and server changed different fields (no conflict, changes coexist).
Most concurrent edits affect different fields, so field-level detection greatly reduces conflicts.
Handling conflicts manually
For critical data requiring user intervention, use the rebaseConflict event. insert-insert conflicts always resolve to manual since they typically indicate a bug in ID generation.
Subscribing to the rebaseConflict event
Subscribe for logging, UI notifications, or custom resolution:
import { createSyncClient } from "@stratasync/client";const client = createSyncClient({ // ...adapters});client.onEvent((event) => { if (event.type !== "rebaseConflict") { return; } // event.modelName, event.modelId, event.conflictType, event.resolution switch (event.conflictType) { case "update-update": // Two users edited the same field on the same model break; case "update-delete": // Local user edited a record that the server deleted break; case "delete-update": // Local user deleted a record that the server updated break; case "insert-insert": // Both clients created a record with the same ID break; }});
Two users editing the same task, starting from { title: "Draft", status: "open", priority: "low" }.
Field-level conflicts + merge (recommended)
User A changes title to "Final Report". User B changes status to "in-review". The changes affect different fields, so no conflict fires. Both users converge on { title: "Final Report", status: "in-review" }.
With fieldLevelConflicts: false, any concurrent edit to the same model+ID is a conflict. User A's pending title change and User B's pending status change both get discarded in favor of the server version. One user's change is lost.