Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

◆ SyncKit

Local-first sync you can watch merge. A ~150-line, zero-dependency CRDT engine plus a live three-device playground: edit a shared list on any device, flip a device offline and keep editing, then reconnect and watch the changes reconcile — with no lost work and no fake winners.

Open index.html in any browser. No build step, no server, no tracking, works fully offline.

Why it exists

"Local-first" software keeps working offline and syncs when it can — but the moment two devices edit the same data while apart, you need a rule for merging that never loses a write and always agrees. SyncKit is the smallest honest version of that rule you can learn in one sitting:

  • Hybrid Logical Clock (HLC). Every write gets a timestamp that is monotonic on each device and globally comparable across devices, without synced wall clocks. (wall, counter, node) gives a total order, so all replicas agree on "which write is newer."
  • Last-Write-Wins map CRDT. State is one HLC-stamped register per field. merge() keeps the higher stamp per field. Because merge is commutative, associative, and idempotent, replicas that have seen the same writes converge — regardless of arrival order or how long a device was dark.
  • Tombstone deletes. A delete is a __deleted LWW field, so "delete on A" vs "edit on B" merges deterministically instead of resurrecting the row.

The payoff you can see in the playground: two devices editing different fields of the same row both win; two devices editing the same field resolve to the later write, identically everywhere.

Use the engine

import { LWWMap } from './synckit.js';

const phone  = new LWWMap('phone');
const laptop = new LWWMap('laptop');

phone.set('milk', 'text', 'Oat milk');   // edit offline on the phone
laptop.set('milk', 'done', true);        // edit offline on the laptop

laptop.merge(phone.snapshot());          // reconnect → exchange state
phone.merge(laptop.snapshot());
// both replicas now agree: { id: 'milk', text: 'Oat milk', done: true }

API

Method What it does
new LWWMap(nodeId) A replica identified by a stable nodeId (used as the clock tiebreak).
.set(rowId, field, value) Local write; stamps the field with a fresh HLC.
.delete(rowId) Tombstones a row (a LWW delete).
.get(rowId, field) Current value, or undefined.
.rows() Live (non-deleted) rows as plain { id, ...fields } objects.
.merge(remoteState) Fold another replica's snapshot() in; returns the number of fields changed.
.snapshot() A JSON-safe copy to send over any transport (WebSocket, postMessage, a file).

Transport is deliberately not included — SyncKit converges on whatever pipe you already have. Gossip snapshots on an interval, exchange them on reconnect, or diff-and-send; the merge is the same.

Honest limitations

  • LWW discards the loser on a same-field conflict. That's the right trade for profiles, settings, toggles, and titles — not for collaborative rich text (reach for a sequence CRDT there).
  • Snapshots are whole-state. Fine to thousands of rows; add delta-sync for very large sets.
  • HLC assumes device clocks are roughly right; it tolerates skew and backward jumps, but a device years in the future would dominate ordering until others catch up.

Files

  • synckit.js — the entire engine (HLC + LWW-Map CRDT). Read it; it's short.
  • index.html — the self-contained playground (imports the engine, no other dependencies).

MIT licensed. Have at it.

About

Local-first sync you can watch merge — a ~150-line HLC + LWW-Map CRDT engine with a live 3-device conflict playground. Zero dependencies.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages