a proof of concept realtime collaborative todo list
0

Configure Feed

Select the types of activity you want to include in your feed.

atodo / crdt.js
1.7 kB 54 lines
1// Each item record carries a $crdt map: { <field>: { clock, actor } }. 2// Merge picks per-field winner by highest clock, lex-greater actor as tiebreak. 3 4export function mergeRecords(records) { 5 if (records.length === 0) return null; 6 const fields = new Set(); 7 for (const r of records) for (const k of Object.keys(r.$crdt || {})) fields.add(k); 8 9 const out = { $crdt: {} }; 10 for (const field of fields) { 11 let best = null; 12 for (const r of records) { 13 const meta = r.$crdt?.[field]; 14 if (!meta) continue; 15 if ( 16 !best || 17 meta.clock > best.meta.clock || 18 (meta.clock === best.meta.clock && meta.actor > best.meta.actor) 19 ) { 20 best = { value: r[field], meta }; 21 } 22 } 23 if (best) { 24 out[field] = best.value; 25 out.$crdt[field] = best.meta; 26 } 27 } 28 return out; 29} 30 31// Build the record we'll write into our own repo. Each editor's record stays 32// fully up-to-date: copy every field from the merged state, then bump $crdt 33// (clock + actor) for the fields we're explicitly changing. 34export function crdtEqual(a, b) { 35 const ak = Object.keys(a || {}); 36 const bk = Object.keys(b || {}); 37 if (ak.length !== bk.length) return false; 38 for (const k of ak) { 39 const x = a[k]; 40 const y = b[k]; 41 if (!y || x.clock !== y.clock || x.actor !== y.actor) return false; 42 } 43 return true; 44} 45 46export function applyUpdates(merged, updates, actor) { 47 const record = merged ? { ...merged, $crdt: { ...(merged.$crdt || {}) } } : { $crdt: {} }; 48 for (const [field, value] of Object.entries(updates)) { 49 record[field] = value; 50 const prevClock = merged?.$crdt?.[field]?.clock ?? 0; 51 record.$crdt[field] = { clock: prevClock + 1, actor }; 52 } 53 return record; 54}