Loading...
Loading...
Yjs CRDT patterns, shared types, conflict resolution, and meta data structures. Use when building collaborative apps with Yjs, handling Y.Map/Y.Array/Y.Text, implementing drag-and-drop reordering, or optimizing document storage.
npx skill4agent add epicenterhq/epicenter yjsY.MapY.ArrayY.TextY.XmlElementY.XmlFragmentY.XmlTextclientIDconst doc = new Y.Doc();
console.log(doc.clientID); // Random number like 1090160253"The 'winner' is decided byof the document (which is a generated number). The higher clientID wins."ydoc.clientID
return dec2.curr.id.client - dec1.curr.id.client; // Higher clientID wins// BAD: Both clients read 5, both write 6, one click lost
function increment(ymap) {
const count = ymap.get('count') || 0;
ymap.set('count', count + 1);
}// GOOD: Each client writes to their own key
function increment(ymap) {
const key = ymap.doc.clientID;
const count = ymap.get(key) || 0;
ymap.set(key, count + 1);
}
function getCount(ymap) {
let sum = 0;
for (const value of ymap.values()) {
sum += value;
}
return sum;
}// BAD: "Move" = delete + insert = broken
function move(yarray, from, to) {
const [item] = yarray.delete(from, 1);
yarray.insert(to, [item]);
}index// GOOD: Reorder by changing index property
function move(yarray, from, to) {
const sorted = [...yarray].sort((a, b) => a.get('index') - b.get('index'));
const item = sorted[from];
const earlier = from > to;
const before = sorted[earlier ? to - 1 : to];
const after = sorted[earlier ? to : to + 1];
const start = before?.get('index') ?? 0;
const end = after?.get('index') ?? 1;
// Add randomness to prevent collisions
const index = (end - start) * (Math.random() + Number.MIN_VALUE) + start;
item.set('index', index);
}// BAD: Alice changes nullable, Bob changes default, one loses
schema.set('title', {
type: 'text',
nullable: true,
default: 'Untitled',
});// GOOD: Each property is independent
const titleSchema = schema.get('title'); // Y.Map
titleSchema.set('type', 'text');
titleSchema.set('nullable', true);
titleSchema.set('default', 'Untitled');
// Alice and Bob edit different keys = no conflictY.Mapymap.set(key, value)YKeyValueyjs/y-utility// YKeyValue stores {key, val} pairs in Y.Array
// Deletions are structural, not per-key tombstones
import { YKeyValue } from 'y-utility/y-keyvalue';
const kv = new YKeyValue(yarray);
kv.set('myKey', { data: 'value' });// Compact a Y.Doc by re-encoding current state
const snapshot = Y.encodeStateAsUpdate(doc);
const freshDoc = new Y.Doc({ guid: doc.guid });
Y.applyUpdate(freshDoc, snapshot);
// freshDoc has same content, no history overheady-lwwmap// BAD: Orphan Y.Map
const orphan = new Y.Map();
orphan.set('key', 'value'); // Works but doesn't sync
// GOOD: Attached to document
const attached = doc.getMap('myMap');
attached.set('key', 'value'); // Syncs to peers// This creates a NEW item, not a moved item
yarray.delete(0);
yarray.push([sameItem]); // Different Y.Map instance internallyconsole.log(doc.toJSON()); // Full document as plain JSON// See who would win a conflict
console.log('My ID:', doc.clientID);