Should PlainMap's Set event and CrdtMap's KeyAdd event also contain the value that was set (not just the key)? In some cases, putting the value in the event can take extra work, which would waste time if nobody is looking at the event anyway. Perhaps a better solution is to include a value field whenever the user might want it, but implement it using a getter method, so that it is only computed if needed?
Should the KeyDelete events include the deleted value? This isn't necessary to reproduce the deletion on a copy of the map, so I have left it out, but then there is no way for a user to know what the deleted value was.
In CrdtMap, should we require that addKey(key) followed immediately by get(key) will always return a non-undefined value? (In ordinary maps, get(key) returns undefined if has(key) is false.) Or should there instead by a method called getForce(key) or similar, that is like get(key) except it always returns a value, adding the key if necessary?
This is required for RegisterPlainMap, which generically constructs a PlainMap from a CrdtMap. We could choose to mandate it for CrdtMaps in general, or we could just mandate it for the input to RegisterPlainMap.
The confusing case: currently, addKey has no effect on ImplicitCrdtMap, which determines key membership solely by whether the corresponding value is nontrivial (not fully reset). But, get(key) still always returns a non-undefined value, since the map is "lazy" (not-present values are created on-demand). So addKey(key) followed by get(key) is safe, even though has(key) will not be true. This also means that ImplicitCrdtMap.get(key) does not follow the rule "get(key) returns undefined is has(key) is false"; adding a method like getForce would allow us to fix that.
Should CrdtMap.addKey be allowed to take arguments, which will be passed to the constructor for value Crdts (similar to CrdtSet.create)? Note this is only useful for Yjs-style maps; Riak-style maps would implement the interface with CreateArgs = [], so they would be unchanged. We would then have to do something about calls to addKey when the key is already present locally - either throw an error, or delete the current value Crdt and overwrite it with a new one. But then what if we want to restore a deleted value, without constructing a new one? Or we are just making sure the key is present and want to do nothing if it already is? Might need more methods, which would then unnecessarily complicate the Riak-style maps.
Planned implementations
These are essentially the same as for the sets.
PlainMap
RegisterPlainMap: constructs a PlainMap from an arbitrary Register Crdt, by storing the map as a CrdtMap<K, Register>.
LwwPlainMap: standard last-writer-wins map, implemented using RegisterPlainMap with an LwwRegister.
SequentialPlainMap
GPlainMap
CrdtMap
Same possibilities as CrdtSet, except that technically it is the keys that are present/deleted, not the valueCrdts. There is one extra distinction between Riak-style and Yjs-style:
In Riak style, if a key is added twice concurrently and then operated on concurrently, the concurrent ops will be merged (both apply to the same value Crdt).
In Yjs style, if a key is added twice concurrently and then operated on concurrently, one value Crdt will "win" and the other will be forgotten, with its ops having no effect on the value. (We could make it user-visible using a getConflicts() method, which returns all conflicting valueCrdts; but you couldn't merge the conflicting values even if you wanted to.)
Also, for the non-memory-safe but otherwise optimal implementations, we could have both a Yjs-style implementation and Riak-style implementation. That way, users could choose which behavior they want for concurrently-added keys (merge vs pick one).
Interfaces
https://github.com/composablesys/compoventuals/blob/master/client/src/crdt/map/interfaces.ts
Questions
Planned implementations
These are essentially the same as for the sets.
PlainMap
CrdtMap
Same possibilities as CrdtSet, except that technically it is the keys that are present/deleted, not the valueCrdts. There is one extra distinction between Riak-style and Yjs-style:
Also, for the non-memory-safe but otherwise optimal implementations, we could have both a Yjs-style implementation and Riak-style implementation. That way, users could choose which behavior they want for concurrently-added keys (merge vs pick one).
Helpers
Same as for Sets.