automerge / automerge-classic

A JSON-like data structure (a CRDT) that can be modified concurrently by different users, and merged again automatically.
http://automerge.org/
MIT License
14.75k stars 466 forks source link

`doc.obj.clear()` vs `doc.obj = {}` #406

Closed yann-achard closed 3 years ago

yann-achard commented 3 years ago

It's my understanding that Automerge is based on this paper. I was curious if the merge semantics described in figure 2 of the paper were currently supported in Automerge.

Currently, the following code leads to s1.doc.obj being empty:

s1 = Automerge.change(Automerge.init(), doc => doc.obj = {})
s2 = Automerge.merge(Automerge.init(), s1)
s1 = Automerge.change(s1, doc => doc.obj = {})
s2 = Automerge.change(s2, doc => doc.obj.key = 'will be deleted')
s1 = Automerge.merge(s1, s2)

It occurs to me that one could get the semantics in figure 2 by iterating over existing entries and deleting each. Is that why there is no dedicated operation for it?

Thank you!

ept commented 3 years ago

Hi @yann-achard, Automerge differs a bit from the JSON CRDT paper: in the paper, every object is identified by its location in the tree (the path from the root to the object in question), while in Automerge, every object has a unique ID that is independent of its location. A consequence of Automerge's design is that if you do doc.obj = {} and there is already an existing object at the location doc.obj, then the reference to the old object is replaced with a reference to a new, empty object.

When you do doc.obj.key = 'will be deleted' in s2, this operation updates the old object, and thus this value is not visible in the merged document, since the merged document contains the new object.

If you want to clear out the contents of an object without changing its ID, you can iterate over the keys and delete each individually. If you do that, the concurrent assignment of key will be preserved. In principle we could provide a .clear() API to do this, but since clearing an object is not something that apps need to do very commonly, I think it's clearest to write that loop in application code. Something like this should work:

s1 = Automerge.change(s1, doc => {
  for (let key of Object.keys(doc.obj)) delete doc.obj[key]
})