Part 10 · 1 chapters · ~13 min

CRDTs and Client Sync

The client as a replica: why last-writer-wins and locks fail offline, the commutative, associative and idempotent merge that guarantees convergence, counters, observed-remove sets and registers, sequence CRDTs for collaborative text, practical sync with local logs and relays, and the invariants that still need a single authority.

19

Merging without asking anyone

code
// two small CRDTs: a G-counter and an observed-remove set
type GCounter = Record<string, number>;
const inc = (c: GCounter, me: string, n = 1): GCounter => ({ ...c, [me]: (c[me] ?? 0) + n });
const value = (c: GCounter) => Object.values(c).reduce((a, b) => a + b, 0);
const mergeG = (a: GCounter, b: GCounter): GCounter => {
  const out = { ...a };
  for (const [k, v] of Object.entries(b)) out[k] = Math.max(out[k] ?? 0, v);   // commutative, idempotent
  return out;
};

type ORSet<T> = { adds: Map<string, T>; removed: Set<string> };          // tag → element; removed tags
const add = <T,>(s: ORSet<T>, x: T): ORSet<T> => ({ ...s, adds: new Map(s.adds).set(crypto.randomUUID(), x) });
const remove = <T,>(s: ORSet<T>, x: T): ORSet<T> =>                       // removes only the tags it has seen
  ({ ...s, removed: new Set([...s.removed, ...[...s.adds].filter(([, v]) => v === x).map(([t]) => t)]) });
const mergeS = <T,>(a: ORSet<T>, b: ORSet<T>): ORSet<T> =>
  ({ adds: new Map([...a.adds, ...b.adds]), removed: new Set([...a.removed, ...b.removed]) });
const elements = <T,>(s: ORSet<T>) => new Set([...s.adds].filter(([t]) => !s.removed.has(t)).map(([, v]) => v));
datafits a CRDT?why
shared shopping list, notes, draftsyesmerging both edits is what users want
reactions, view countsyes (counters)order never matters
a form being filled offlineoften a simple LWW per fieldone user, rare true conflicts
account balance, seat booking, unique usernamesnoan invariant across replicas needs a single authority or consensus
the frontend link
KinniJije as a PWA, and any offline queue you build, are this chapter in practice. Name which data merges freely and which must wait for the server, and design the UI for the second kind: "saved on this phone, will send when online".
THE CLIENT AS A REPLICA: CRDTS AND SYNC
offline edits, merges without coordination, and the sync protocols behind collaborative apps
swipe the figure sideways, or tap expand for full screen
1/6
the problem
The problem: two devices edit the same note offline. Last-writer-wins by timestamp silently drops one edit, and clocks on phones are wrong (part 1). Locking needs connectivity. We want both edits kept, and every replica to converge on the same result.