JsonCrdt
A CRDT-backed JSON document: a recursive, convergent, arbitrary-depth JSON value that merges correctly under concurrent edits from multiple replicas.
The root is always a JSON object keyed by String. Values at each key can be nested objects, arrays, or scalars:
JsonNode = JsonObject(ORMap<String, JsonNode>)
| JsonArray(Rga<JsonNode>)
| JsonLeaf(MVRegister<JsonValue>)Conflict resolution. Merge is structural and recursive:
Key presence — add-wins: a concurrent
putof the same key survives aremove.Nested values — recursed via JsonNode.piece: objects merge their maps, arrays merge their op-logs, leaves merge their multi-value registers.
Concurrent scalar writes — the JsonNode.Leaf's MVRegister retains all concurrent values; the caller resolves by calling
setagain once they read the multi-value state.Cross-type conflicts (e.g. one replica replaces an object with a scalar concurrently with the other replica adding a key to the object) — the richer structural type wins:
Object > Array > Leaf. This is a data-loss decision, not a data-preservation one. The losing node's entire subtree is silently discarded. The scalar equivalent (JsonNode.Leaf vs JsonNode.Leaf) surfaces both values via MVRegister, but a Leaf-vs-Object cross-type conflict does not. This is a deliberate v1 simplification; a future version may model cross-type conflicts as a multi-valued register at the type level.
Known limitations (v1):
Move / subtree-reattachment — not supported.
Nested Rga GC — arrays embedded inside a JSON document do not participate in the Rga.compact / us.tractat.kuilt.quilter.Quilter GC path. Tombstones inside array elements accumulate without bound until an explicit compact is triggered by the caller.
Conflict-free re-typing — concurrent changes of a key's type are resolved by the precedence rule above, not by surfacing a conflict.
Serialization. Use JsonCrdt.serializer to obtain a KSerializer. The replica id is not included in the wire format — it is a local identity. After deserializing, call withReplica to restore the local replica id before performing mutations.
Caution — mutate after withReplica. The deserialized document defaults to ReplicaId(""), which collides with RgaId.HEAD's sentinel replica and may corrupt Dot uniqueness if used to mint new operations. Always call withReplica before invoking set or remove on a deserialized document.
See also
the node algebra this document is built over.
the scalar type for JsonNode.Leaf registers.
Properties
Functions
Unions the Rga.causalDots of every JsonNode.Array reachable from the root, recursing through JsonNode.Object values. This feeds the causal-stability GC barrier in us.tractat.kuilt.quilter.Quilter: without this override, embedded Rga tombstones in nested arrays would never be considered for compaction because the delivered frontier would always be empty.
Returns a copy of this document configured to issue mutations on behalf of replica. Call this after deserialization to restore the local replica id.