3 ms·
I tried to use this library for a side project, but ultimately I found that I couldn't predict how it would merge two documents(at least without reading the ess
by Tade0 5y ago
I tried to use this library for a side project, but ultimately I found that I couldn't predict how it would merge two documents(at least without reading the essay).
The case where two actors add items to a list in parallel was especially troubling. I would expect that the result would be a two-element list, but that was not the case:
https://runkit.com/embed/2hv6rx14lp12 https://runkit.com/embed/2hv6rx14lp12
Code:
const Automerge = require('automerge');
let doc1 = Automerge.from({ cards: [] });
let doc2 = Automerge.from({ cards: [] });
doc1 = Automerge.change(doc1, doc => {
doc.cards.push({ title: 'card1' });
});
doc2 = Automerge.change(doc2, doc => {
doc.cards.push({ title: 'card2' });
});
doc2 = Automerge.merge(doc2, doc1);
console.log(doc1);
console.log(doc2);
- folkrav 5y agoUnless I'm mistaken, according to their own TS types[0], `Automerge.merge` should return a `{ readonly [P in keyof T]: Freeze<T[P]> }`. Getting `undefined` here is probably a bug. [0] https://github.com/automerge/automerge/blob/main/%40types/automerge/index.d.ts#L368 https://github.com/automerge/automerge/blob/main/%40types/au...
- Tade0 5y agoI think that undefined at the end is not related to my code - it's there before those console logs appear and they're appended to the start of this table. In any case I get the same result locally: I suspected doc2.cards would have length: 2, but it's only 1. I couldn't explain why so I rolled my own conflict-resolution scheme that fit my particular use case. It's primitive and doesn't actually give any guarantees, but gets the job done.
- xyzzy_plugh 5y agoThis is because you are updating properties, not adding items to a list in parallel. It's totally not obvious, but the lines let doc1 = Automerge.from({ cards: [] }); let doc2 = Automerge.from({ cards: [] }); describe two different property updates, as I understand it. This is equivalent to let doc1 = Automerge.change(Automerge.init(), doc => { doc.cards = []; }); doc1 = Automerge.change(doc1, doc => { doc.cards.push({ title: 'card1' }); }); for each document. It's that initial assignment that is causing you trouble. If you start from a shared state, then adding items in parallel works exactly how you would expect: let doc1 = Automerge.from({ cards: [] }) let doc2 = Automerge.init() // Merge doc1 into doc2 doc2 = Automerge.merge(doc2, doc1) doc1 = Automerge.change(doc1, doc => { doc.cards.push({ title: 'card1' }) }); doc2 = Automerge.change(doc2, doc => { doc.cards.push({ title: 'card2' }) }) doc1 = Automerge.merge(doc1, doc2) doc2 = Automerge.merge(doc2, doc1) Both docs are equal. N.B. this is just my understanding of how this works based on reading the docs, I haven't actually done much depth of confirming beyond running the above code. In particular the warning about properties is extremely important: > The only case Automerge cannot handle automatically, because there is no well-defined resolution, is when users concurrently update the same property in the same object (or, similarly, the same index in the same list). In this case, Automerge arbitrarily picks one of the concurrently written values as the "winner" which is exactly what you are seeing here.
- Tade0 5y agoThanks for the writeup, makes sense now.