Sync Engine
How Persist replicates data across devices using a queue-based sync engine with pluggable conflict resolution.
Architecture
The sync engine sits between your local store and one or more remote targets. Every write operation produces a sync entry in a local queue. A background worker drains the queue by pushing entries to configured remotes and pulling entries from them.
App → Store → Local Adapter → Sync Queue → Remote(s)
↑
Pull + Merge
Enabling sync
Sync is opt-in per store. Call enableSync after creating a store to attach one or more remote targets.
import { createStore, enableSync } from "@cuitty/persist";
const store = await createStore("tasks", {
adapter: "sqlite",
path: "./tasks.db",
});
await enableSync(store, {
remote: "https://sync.persist.one",
strategy: "last-write-wins",
interval: 5000, // poll every 5 seconds
});
Conflict strategies
When two devices modify the same record before syncing, a conflict occurs. Persist supports three strategies:
Last-write-wins
The write with the most recent timestamp takes precedence. Simple and suitable for most cases.
await enableSync(store, {
remote: "https://sync.persist.one",
strategy: "last-write-wins",
});
Custom merge function
You provide a function that receives both versions and returns the merged result.
await enableSync(store, {
remote: "https://sync.persist.one",
strategy: "merge",
merge: (local, remote) => ({
...remote.value,
tags: [...new Set([...local.value.tags, ...remote.value.tags])],
updatedAt: Math.max(local.updatedAt, remote.updatedAt),
}),
});
Manual resolution
Conflicts are surfaced to the application for user-driven resolution. Persist stores both versions and flags the record as conflicted.
await enableSync(store, {
remote: "https://sync.persist.one",
strategy: "manual",
});
// Later, resolve conflicts
const conflicts = await store.records.conflicts();
for (const c of conflicts) {
await store.records.resolve(c.key, c.remote); // pick remote version
}
Sync queue and planner
The sync planner optimizes network usage by batching writes, deduplicating consecutive updates to the same key, and compressing payloads. If the same record is updated five times while offline, only the final state is transmitted during sync.
The queue persists to disk so pending operations survive process restarts.