{
  "slug": "persist/sync",
  "title": "Sync & Conflict Resolution",
  "description": "Configure automatic sync strategies and conflict resolution for Persist stores.",
  "url": "https://cuitty.com/docs/persist/sync",
  "markdown_url": "https://cuitty.com/docs/persist/sync.md",
  "json_url": "https://cuitty.com/docs/persist/sync.json",
  "frontmatter": {
    "title": "Sync & Conflict Resolution",
    "description": "Configure automatic sync strategies and conflict resolution for Persist stores.",
    "order": 3,
    "section": "Persist",
    "updatedAt": "2026-05-12"
  },
  "headings": [
    {
      "depth": 1,
      "slug": "sync--conflict-resolution",
      "text": "Sync & Conflict Resolution"
    },
    {
      "depth": 2,
      "slug": "how-sync-works",
      "text": "How sync works"
    },
    {
      "depth": 2,
      "slug": "configuration",
      "text": "Configuration"
    },
    {
      "depth": 3,
      "slug": "options",
      "text": "Options"
    },
    {
      "depth": 2,
      "slug": "strategies",
      "text": "Strategies"
    },
    {
      "depth": 3,
      "slug": "last-write-wins-lww",
      "text": "Last-write-wins (LWW)"
    },
    {
      "depth": 3,
      "slug": "crdts",
      "text": "CRDTs"
    },
    {
      "depth": 3,
      "slug": "custom-merge",
      "text": "Custom merge"
    },
    {
      "depth": 2,
      "slug": "conflict-resolution-callbacks",
      "text": "Conflict resolution callbacks"
    },
    {
      "depth": 2,
      "slug": "offline-first-behavior",
      "text": "Offline-first behavior"
    },
    {
      "depth": 2,
      "slug": "monitoring-sync-status",
      "text": "Monitoring sync status"
    }
  ],
  "body_markdown": "# Sync & Conflict Resolution\n\n## How sync works\n\nPersist is offline-first. Writes go to the local store immediately and never block on network. A background sync queue pushes changes to the remote and pulls remote changes back.\n\n```\nlocal write --> local store --> sync queue --> remote\n                                   ^\n                                   |\n                         pull remote changes\n```\n\n## Configuration\n\n```ts\nimport { createStore } from \"@cuitty/persist\";\n\nconst store = await createStore({\n  name: \"my-app\",\n  adapter: \"sqlite\",\n  path: \"./data/my-app.db\",\n  sync: {\n    remote: \"postgres\",\n    strategy: \"last-write-wins\",\n    interval: 5000, // sync every 5 seconds (default: 10000)\n  },\n});\n```\n\n### Options\n\n| Option       | Type     | Default            | Description                         |\n| ------------ | -------- | ------------------ | ----------------------------------- |\n| `remote`     | `string` | --                 | Remote adapter type or connection   |\n| `strategy`   | `string` | `\"last-write-wins\"`| Conflict resolution strategy        |\n| `interval`   | `number` | `10000`            | Sync interval in milliseconds       |\n\n## Strategies\n\n### Last-write-wins (LWW)\n\nThe simplest strategy. Each record carries a timestamp; the most recent write wins on conflict.\n\n```ts\nsync: {\n  remote: \"postgres\",\n  strategy: \"last-write-wins\",\n}\n```\n\nGood for settings, preferences, and data where overwrites are acceptable.\n\n### CRDTs\n\nConflict-free replicated data types that merge automatically without data loss. Best for collaborative data.\n\n```ts\nsync: {\n  remote: \"postgres\",\n  strategy: \"crdt\",\n}\n```\n\nPersist uses operation-based CRDTs under the hood. Counters, sets, and maps merge deterministically across peers.\n\n### Custom merge\n\nSupply a function that receives both versions and returns the resolved value.\n\n```ts\nsync: {\n  remote: \"postgres\",\n  strategy: \"custom\",\n  merge(local, remote) {\n    // Example: keep the record with more fields\n    const localKeys = Object.keys(local.value);\n    const remoteKeys = Object.keys(remote.value);\n    return localKeys.length >= remoteKeys.length ? local : remote;\n  },\n}\n```\n\n## Conflict resolution callbacks\n\nRegister a callback to handle conflicts interactively or log them.\n\n```ts\nstore.events.on(\"conflict\", (event) => {\n  console.log(`Conflict on key: ${event.key}`);\n  console.log(\"Local:\", event.local);\n  console.log(\"Remote:\", event.remote);\n  console.log(\"Resolved:\", event.resolved);\n});\n```\n\n## Offline-first behavior\n\nWhen the device is offline, Persist continues to accept reads and writes against the local store. Changes accumulate in the sync queue and flush automatically when connectivity returns.\n\nThe sync queue is persisted to disk, so pending changes survive app restarts.\n\n## Monitoring sync status\n\n```ts\nconst status = store.sync.status();\n// { state: \"synced\" | \"syncing\" | \"offline\", pending: number, lastSync: Date }\n\nstore.events.on(\"sync\", (event) => {\n  console.log(`Synced ${event.pushed} up, ${event.pulled} down`);\n});\n```",
  "body_html": "<h1 id=\"sync--conflict-resolution\">Sync &#x26; Conflict Resolution</h1>\n<h2 id=\"how-sync-works\">How sync works</h2>\n<p>Persist is offline-first. Writes go to the local store immediately and never block on network. A background sync queue pushes changes to the remote and pulls remote changes back.</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"plaintext\"><code><span class=\"line\"><span>local write --> local store --> sync queue --> remote</span></span>\n<span class=\"line\"><span>                                   ^</span></span>\n<span class=\"line\"><span>                                   |</span></span>\n<span class=\"line\"><span>                         pull remote changes</span></span></code></pre>\n<h2 id=\"configuration\">Configuration</h2>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"ts\"><code><span class=\"line\"><span style=\"color:#F97583\">import</span><span style=\"color:#E1E4E8\"> { createStore } </span><span style=\"color:#F97583\">from</span><span style=\"color:#9ECBFF\"> \"@cuitty/persist\"</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">const</span><span style=\"color:#79B8FF\"> store</span><span style=\"color:#F97583\"> =</span><span style=\"color:#F97583\"> await</span><span style=\"color:#B392F0\"> createStore</span><span style=\"color:#E1E4E8\">({</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  name: </span><span style=\"color:#9ECBFF\">\"my-app\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  adapter: </span><span style=\"color:#9ECBFF\">\"sqlite\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  path: </span><span style=\"color:#9ECBFF\">\"./data/my-app.db\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  sync: {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">    remote: </span><span style=\"color:#9ECBFF\">\"postgres\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">    strategy: </span><span style=\"color:#9ECBFF\">\"last-write-wins\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">    interval: </span><span style=\"color:#79B8FF\">5000</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#6A737D\">// sync every 5 seconds (default: 10000)</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  },</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">});</span></span></code></pre>\n<h3 id=\"options\">Options</h3>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<table><thead><tr><th>Option</th><th>Type</th><th>Default</th><th>Description</th></tr></thead><tbody><tr><td><code>remote</code></td><td><code>string</code></td><td>—</td><td>Remote adapter type or connection</td></tr><tr><td><code>strategy</code></td><td><code>string</code></td><td><code>\"last-write-wins\"</code></td><td>Conflict resolution strategy</td></tr><tr><td><code>interval</code></td><td><code>number</code></td><td><code>10000</code></td><td>Sync interval in milliseconds</td></tr></tbody></table>\n<h2 id=\"strategies\">Strategies</h2>\n<h3 id=\"last-write-wins-lww\">Last-write-wins (LWW)</h3>\n<p>The simplest strategy. Each record carries a timestamp; the most recent write wins on conflict.</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"ts\"><code><span class=\"line\"><span style=\"color:#B392F0\">sync</span><span style=\"color:#E1E4E8\">: {</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">  remote</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"postgres\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">  strategy</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"last-write-wins\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>Good for settings, preferences, and data where overwrites are acceptable.</p>\n<h3 id=\"crdts\">CRDTs</h3>\n<p>Conflict-free replicated data types that merge automatically without data loss. Best for collaborative data.</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"ts\"><code><span class=\"line\"><span style=\"color:#B392F0\">sync</span><span style=\"color:#E1E4E8\">: {</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">  remote</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"postgres\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">  strategy</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"crdt\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>Persist uses operation-based CRDTs under the hood. Counters, sets, and maps merge deterministically across peers.</p>\n<h3 id=\"custom-merge\">Custom merge</h3>\n<p>Supply a function that receives both versions and returns the resolved value.</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"ts\"><code><span class=\"line\"><span style=\"color:#B392F0\">sync</span><span style=\"color:#E1E4E8\">: {</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">  remote</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"postgres\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">  strategy</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"custom\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">  merge</span><span style=\"color:#E1E4E8\">(local, remote) {</span></span>\n<span class=\"line\"><span style=\"color:#6A737D\">    // Example: keep the record with more fields</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">    const</span><span style=\"color:#79B8FF\"> localKeys</span><span style=\"color:#F97583\"> =</span><span style=\"color:#E1E4E8\"> Object.</span><span style=\"color:#B392F0\">keys</span><span style=\"color:#E1E4E8\">(local.value);</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">    const</span><span style=\"color:#79B8FF\"> remoteKeys</span><span style=\"color:#F97583\"> =</span><span style=\"color:#E1E4E8\"> Object.</span><span style=\"color:#B392F0\">keys</span><span style=\"color:#E1E4E8\">(remote.value);</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">    return</span><span style=\"color:#E1E4E8\"> localKeys.</span><span style=\"color:#79B8FF\">length</span><span style=\"color:#F97583\"> >=</span><span style=\"color:#E1E4E8\"> remoteKeys.</span><span style=\"color:#79B8FF\">length</span><span style=\"color:#F97583\"> ?</span><span style=\"color:#E1E4E8\"> local </span><span style=\"color:#F97583\">:</span><span style=\"color:#E1E4E8\"> remote;</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  },</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<h2 id=\"conflict-resolution-callbacks\">Conflict resolution callbacks</h2>\n<p>Register a callback to handle conflicts interactively or log them.</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"ts\"><code><span class=\"line\"><span style=\"color:#E1E4E8\">store.events.</span><span style=\"color:#B392F0\">on</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"conflict\"</span><span style=\"color:#E1E4E8\">, (</span><span style=\"color:#FFAB70\">event</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">=></span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  console.</span><span style=\"color:#B392F0\">log</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">`Conflict on key: ${</span><span style=\"color:#E1E4E8\">event</span><span style=\"color:#9ECBFF\">.</span><span style=\"color:#E1E4E8\">key</span><span style=\"color:#9ECBFF\">}`</span><span style=\"color:#E1E4E8\">);</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  console.</span><span style=\"color:#B392F0\">log</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"Local:\"</span><span style=\"color:#E1E4E8\">, event.local);</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  console.</span><span style=\"color:#B392F0\">log</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"Remote:\"</span><span style=\"color:#E1E4E8\">, event.remote);</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  console.</span><span style=\"color:#B392F0\">log</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"Resolved:\"</span><span style=\"color:#E1E4E8\">, event.resolved);</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">});</span></span></code></pre>\n<h2 id=\"offline-first-behavior\">Offline-first behavior</h2>\n<p>When the device is offline, Persist continues to accept reads and writes against the local store. Changes accumulate in the sync queue and flush automatically when connectivity returns.</p>\n<p>The sync queue is persisted to disk, so pending changes survive app restarts.</p>\n<h2 id=\"monitoring-sync-status\">Monitoring sync status</h2>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"ts\"><code><span class=\"line\"><span style=\"color:#F97583\">const</span><span style=\"color:#79B8FF\"> status</span><span style=\"color:#F97583\"> =</span><span style=\"color:#E1E4E8\"> store.sync.</span><span style=\"color:#B392F0\">status</span><span style=\"color:#E1E4E8\">();</span></span>\n<span class=\"line\"><span style=\"color:#6A737D\">// { state: \"synced\" | \"syncing\" | \"offline\", pending: number, lastSync: Date }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">store.events.</span><span style=\"color:#B392F0\">on</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"sync\"</span><span style=\"color:#E1E4E8\">, (</span><span style=\"color:#FFAB70\">event</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">=></span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  console.</span><span style=\"color:#B392F0\">log</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">`Synced ${</span><span style=\"color:#E1E4E8\">event</span><span style=\"color:#9ECBFF\">.</span><span style=\"color:#E1E4E8\">pushed</span><span style=\"color:#9ECBFF\">} up, ${</span><span style=\"color:#E1E4E8\">event</span><span style=\"color:#9ECBFF\">.</span><span style=\"color:#E1E4E8\">pulled</span><span style=\"color:#9ECBFF\">} down`</span><span style=\"color:#E1E4E8\">);</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">});</span></span></code></pre>",
  "links_out": []
}