set

fun set(key: String, node: JsonNode): Patch<JsonCrdt>

Set key to node — and return the change: one key, the node you supplied, and a short causal note, rather than the whole new document.

If the key already exists, its current value is merged with node via JsonNode.piece — a write is additive within the node's own lattice. This is ORMap.put's semantics, which the root is, and it preserves the add-wins invariant for nested structure. The merge happens at each receiver, against that receiver's own copy, which is why the delta carries node and not the locally merged result.

This is what to put on the wire. A replicator broadcasts a patch's delta verbatim, so a mutator that handed back the new document would ship every key and every key's subtree on every write, at a cost that grows with the document; this frame's size does not depend on how large the document is. The idiom is quilter.mutate { it.set(key, node) } — read-modify-write inside the replicator's own lock. To hold the resulting document locally, absorb the patch: doc.piece(doc.set(key, node)), or doc.piece { it.set(key, node) }.

Flat in the document, not in node. The saving is over the rest of the document. The frame still carries the whole node you passed, and a write nested inside an existing JsonNode.Object/JsonNode.Array is expressed today by rebuilding the enclosing node and setting it here — so the frame is one key whose value is that whole rebuilt subtree. A genuinely minimal nested write needs a path-addressed mutator; that is #2469, not something this method can do. It also carries whatever this replica has already contributed to key, for the reason ORMap.put spells out: the tag this write mints supersedes the sender's older tags on the key and therefore has to carry what they were holding.

Throws

if the document was not configured with a replica id (i.e. it was deserialized and withReplica was not called beforehand).

Samples

val a = ReplicaId("A")
val b = ReplicaId("B")

fun text(writer: ReplicaId, value: String) =
    JsonNode.Leaf(MVRegister.empty<JsonValue>().set(writer, JsonValue.Str(value)))

// Two peers have converged on a document with a title and a long body.
var alpha = JsonCrdt.empty(a)
    .piece { it.set("title", text(a, "Draft")) }
    .piece { it.set("body", text(a, "a very long document body")) }
var bravo = alpha.withReplica(b)

// B retitles the document and puts only that key on the wire. The body does not travel —
// that is the whole saving, and it holds however large the rest of the document gets.
val retitle = bravo.set("title", text(b, "Final"))
check(retitle.delta.keys == setOf("title"))
check(retitle.delta["body"] == null)

alpha = alpha.piece(retitle)
bravo = bravo.piece(retitle)
check(alpha == bravo)

// Both scalar writes are retained, because neither observed the other: the leaf is a
// multi-value register the caller resolves by writing again once it has read both.
val title = alpha["title"] as JsonNode.Leaf
check(title.register.values == setOf(JsonValue.Str("Draft"), JsonValue.Str("Final")))

// A concurrent write beats a concurrent remove: B's write mints a tag A's remove never saw.
val concurrent = alpha.withReplica(b).set("title", text(b, "Revived"))
check("title" in alpha.piece(alpha.remove("title")).piece(concurrent).keys)

// A remove ships the retired tags and nothing else — its delta holds no key at all.
val drop = alpha.remove("body")
check(drop.delta.keys.isEmpty())
alpha = alpha.piece(drop)
bravo = bravo.piece(drop)
check("body" !in alpha.keys)
check("body" !in bravo.keys)