lattice_maps/or_map
Generation-aware observed-remove maps.
Concurrent updates join in one generation, with add-wins membership. Updating after an observed removal creates a fresh generation. The newest generation wins, even if it is removed; older values cannot return. Sparse synchronization needs a baseline or eventual delivery of all required deltas. An isolated later Sequence delta can temporarily have missing origins.
Types
A map with one recursive child schema.
Examples
let documents: or_map.ORMap(Int) =
or_map.new(replica_id.new("A"), crdt.OrMapSpec(crdt.SequenceSpec))
or_map.keys(documents) // -> []
pub type ORMap(a) =
crdt.ORMap(a)
A sparse generation-qualified change, not a map snapshot.
Examples
let map = or_map.new(replica_id.new("A"), crdt.LwwRegisterSpec(0))
let delta: or_map.ORMapDelta(Int) = or_map.empty_delta(map)
or_map.apply_delta(map, delta) // -> Ok(map)
pub type ORMapDelta(a) =
crdt.ORMapDelta(a)
Values
pub fn apply_delta(
map: crdt.ORMap(a),
delta: crdt.ORMapDelta(a),
) -> Result(crdt.ORMap(a), crdt.MergeError)
Apply only touched keys, selecting generations before child changes.
Missing Sequence origins can produce an incomplete view until the required baseline or earlier deltas arrive. Bind the receiving map before editing it.
Examples
let source = or_map.new(replica_id.new("A"), crdt.GCounterSpec)
let assert Ok(#(_, delta)) =
or_map.update_with_delta(source, "score", fn(value) { value })
let receiver = or_map.new(replica_id.new("B"), crdt.GCounterSpec)
let assert Ok(receiver) = or_map.apply_delta(receiver, delta)
or_map.keys(receiver) // -> ["score"]
pub fn bind(
map: crdt.ORMap(a),
replica: replica_id.ReplicaId,
) -> crdt.ORMap(a)
Bind a loaded or received map to the local writer without rewriting history.
Child callbacks receive identities scoped by key and generation. Keep each independent writer’s logical replica ID distinct.
Examples
let source = or_map.new(replica_id.new("A"), crdt.LwwRegisterSpec(""))
let snapshot = source |> or_map.to_json |> json.to_string
let assert Ok(loaded) = or_map.from_json(snapshot)
let local = or_map.bind(loaded, replica_id.new("B"))
or_map.replica_id(local) // -> replica_id.new("B")
pub fn delta_from_json(
input: String,
) -> Result(crdt.ORMapDelta(String), json.DecodeError)
Decode a modern String delta. Legacy map deltas are intentionally rejected.
Examples
let map = or_map.new(replica_id.new("A"), crdt.LwwRegisterSpec(""))
let delta = or_map.empty_delta(map)
let encoded = delta |> or_map.delta_to_json |> json.to_string
let assert Ok(decoded) = or_map.delta_from_json(encoded)
or_map.apply_delta(map, decoded) // -> Ok(map)
pub fn delta_from_json_with(
input: String,
decoder: decode.Decoder(a),
) -> Result(crdt.ORMapDelta(a), json.DecodeError)
Decode a generic sparse delta.
Examples
let map = or_map.new(replica_id.new("A"), crdt.LwwRegisterSpec(42))
let delta = or_map.empty_delta(map)
let encoded = or_map.delta_to_json_with(delta, json.int) |> json.to_string
let assert Ok(decoded) = or_map.delta_from_json_with(encoded, decode.int)
or_map.apply_delta(map, decoded) // -> Ok(map)
pub fn delta_to_json(delta: crdt.ORMapDelta(String)) -> json.Json
Encode a String-payload sparse delta (version 2).
Examples
let map = or_map.new(replica_id.new("A"), crdt.LwwRegisterSpec(""))
let delta = or_map.empty_delta(map)
let encoded = delta |> or_map.delta_to_json |> json.to_string
or_map.delta_from_json(encoded) // -> Ok(delta)
pub fn delta_to_json_with(
delta: crdt.ORMapDelta(a),
encode: fn(a) -> json.Json,
) -> json.Json
Encode a generic sparse delta.
Examples
let map = or_map.new(replica_id.new("A"), crdt.LwwRegisterSpec(42))
let assert Ok(#(_, delta)) = or_map.update_delta(map, "answer", fn(_, _) {
Ok(crdt.NoChange(crdt.LwwRegisterSpec(42)))
})
let encoded = or_map.delta_to_json_with(delta, json.int) |> json.to_string
or_map.delta_from_json_with(encoded, decode.int) // -> Ok(delta)
pub fn empty_delta(map: crdt.ORMap(a)) -> crdt.ORMapDelta(a)
Return an empty transport batch.
Examples
let map = or_map.new(replica_id.new("A"), crdt.TextSpec)
or_map.apply_delta(map, or_map.empty_delta(map)) // -> Ok(map)
pub fn from_json(
input: String,
) -> Result(crdt.ORMap(String), json.DecodeError)
Decode a modern String snapshot. Call bind before editing as another writer.
Examples
let source = or_map.new(replica_id.new("A"), crdt.LwwRegisterSpec(""))
let encoded = source |> or_map.to_json |> json.to_string
let assert Ok(loaded) = or_map.from_json(encoded)
let local = or_map.bind(loaded, replica_id.new("B"))
or_map.replica_id(local) // -> replica_id.new("B")
pub fn from_json_with(
input: String,
decoder: decode.Decoder(a),
) -> Result(crdt.ORMap(a), json.DecodeError)
Decode a generic modern snapshot, including recursive configured defaults.
Legacy map snapshots require import_legacy instead.
Examples
let map = or_map.new(replica_id.new("A"), crdt.LwwRegisterSpec(42))
let encoded = or_map.to_json_with(map, json.int) |> json.to_string
let assert Ok(loaded) = or_map.from_json_with(encoded, decode.int)
or_map.spec(loaded) // -> crdt.LwwRegisterSpec(42)
pub fn get(
map: crdt.ORMap(a),
key: String,
) -> Result(crdt.Crdt(a), Nil)
Get an active child bound to this map’s local editing scope.
Examples
let map = or_map.new(replica_id.new("A"), crdt.GCounterSpec)
let assert Ok(map) = or_map.update(map, "score", fn(value) { value })
let assert Ok(crdt.CrdtGCounter(counter)) = or_map.get(map, "score")
g_counter.value(counter) // -> 0
pub fn import_legacy(
input: String,
spec: crdt.CrdtSpec(a),
decoder: decode.Decoder(a),
replica: replica_id.ReplicaId,
) -> Result(crdt.ORMap(a), json.DecodeError)
Import a coordinated legacy baseline into Initial generations.
Supply the agreed schema/default and a fresh editing identity if old allocation history is missing. Distribute a modern snapshot; do not mix old map deltas.
Examples
Convert an agreed legacy register-map baseline to a modern snapshot:
import gleam/dynamic/decode
import gleam/result
import lattice_core/replica_id
import lattice_maps/crdt
import lattice_maps/or_map
pub fn migrate(legacy_snapshot: String) {
use map <- result.try(or_map.import_legacy(
legacy_snapshot, crdt.LwwRegisterSpec(""), decode.string,
replica_id.new("cutover-writer"),
))
Ok(or_map.to_json(map))
}
pub fn keys(map: crdt.ORMap(a)) -> List(String)
Return active keys, in unspecified order.
Examples
let map = or_map.new(replica_id.new("A"), crdt.TextSpec)
let assert Ok(map) = or_map.update(map, "body", fn(value) { value })
or_map.keys(map) // -> ["body"]
pub fn merge(
a: crdt.ORMap(a),
b: crdt.ORMap(a),
) -> Result(crdt.ORMap(a), crdt.MergeError)
Merge into the left map’s local identity.
Examples
let a = or_map.new(replica_id.new("A"), crdt.GCounterSpec)
let b = or_map.new(replica_id.new("B"), crdt.GCounterSpec)
let assert Ok(merged) = or_map.merge(a, b)
or_map.replica_id(merged) // -> replica_id.new("A")
pub fn merge_as(
a: crdt.ORMap(a),
b: crdt.ORMap(a),
replica: replica_id.ReplicaId,
) -> Result(crdt.ORMap(a), crdt.MergeError)
Merge into an explicit receiving identity, including remote-only children.
Examples
let a = or_map.new(replica_id.new("A"), crdt.SequenceSpec)
let b = or_map.new(replica_id.new("B"), crdt.SequenceSpec)
let local = replica_id.new("C")
let assert Ok(merged) = or_map.merge_as(a, b, local)
or_map.replica_id(merged) // -> local
pub fn merge_deltas(
a: crdt.ORMapDelta(a),
b: crdt.ORMapDelta(a),
) -> Result(crdt.ORMapDelta(a), crdt.MergeError)
Combine sparse changes without turning nested deltas into snapshots.
Examples
let map = or_map.new(replica_id.new("A"), crdt.TextSpec)
let assert Ok(#(first, a)) =
or_map.update_with_delta(map, "first", fn(value) { value })
let assert Ok(#(updated, b)) =
or_map.update_with_delta(first, "second", fn(value) { value })
let assert Ok(batch) = or_map.merge_deltas(a, b)
or_map.apply_delta(map, batch) // -> Ok(updated)
pub fn new(
replica: replica_id.ReplicaId,
spec: crdt.CrdtSpec(a),
) -> crdt.ORMap(a)
Create an empty map.
Examples
or_map.new(replica_id.new("A"), crdt.OrMapSpec(crdt.TextSpec))
pub fn prune(
map: crdt.ORMap(a),
stable: version_vector.VersionVector,
) -> crdt.ORMap(a)
Prune stable membership tombstones, but retain floors and current leaf history.
Outer stability never compacts inner Sequence/Text history or forwardings. The vector must cover the namespaced membership tags in map deltas, not the logical writer’s unrelated leaf clocks. Each key/generation is a causal scope.
Examples
let map = or_map.new(replica_id.new("A"), crdt.TextSpec)
// An empty frontier makes no events stable.
or_map.prune(map, version_vector.new()) // -> map
pub fn remove(map: crdt.ORMap(a), key: String) -> crdt.ORMap(a)
Remove observed membership; keep current-generation child history.
Examples
let map = or_map.new(replica_id.new("A"), crdt.TextSpec)
let assert Ok(map) = or_map.update(map, "body", fn(value) { value })
or_map.remove(map, "body") |> or_map.get("body") // -> Error(Nil)
pub fn remove_with_delta(
map: crdt.ORMap(a),
key: String,
) -> #(crdt.ORMap(a), crdt.ORMapDelta(a))
Remove observed membership and return a generation-bearing removal delta.
Examples
let map = or_map.new(replica_id.new("A"), crdt.TextSpec)
let assert Ok(map) = or_map.update(map, "body", fn(value) { value })
let #(removed, delta) = or_map.remove_with_delta(map, "body")
or_map.apply_delta(map, delta) // -> Ok(removed)
pub fn replica_id(map: crdt.ORMap(a)) -> replica_id.ReplicaId
Return the local editing identity.
Examples
let local = replica_id.new("A")
let map = or_map.new(local, crdt.TextSpec)
or_map.replica_id(map) // -> local
pub fn spec(map: crdt.ORMap(a)) -> crdt.CrdtSpec(a)
Return the complete child schema.
Examples
let schema = crdt.OrMapSpec(crdt.LwwRegisterSpec(42))
let map = or_map.new(replica_id.new("A"), schema)
or_map.spec(map) // -> schema
pub fn to_json(map: crdt.ORMap(String)) -> json.Json
Encode a String-payload modern snapshot (version 3).
Examples
let map = or_map.new(replica_id.new("A"), crdt.LwwRegisterSpec(""))
let encoded = map |> or_map.to_json |> json.to_string
or_map.from_json(encoded) // -> Ok(map)
pub fn to_json_with(
map: crdt.ORMap(a),
encode: fn(a) -> json.Json,
) -> json.Json
Encode a generic modern snapshot.
The encoder handles payloads and configured register defaults at every depth. Text remains a concrete String/grapheme type.
Examples
let map = or_map.new(
replica_id.new("A"), crdt.OrMapSpec(crdt.LwwRegisterSpec(42)),
)
let encoded = or_map.to_json_with(map, json.int) |> json.to_string
or_map.from_json_with(encoded, decode.int) // -> Ok(map)
pub fn update(
map: crdt.ORMap(a),
key: String,
callback: fn(crdt.Crdt(a)) -> crdt.Crdt(a),
) -> Result(crdt.ORMap(a), crdt.MergeError)
Join the callback’s full child state within the current generation.
Removal/re-add, rather than a non-monotone callback, resets a child.
Use update_delta when a leaf operation can return a sparse delta, or when
a new LWWRegister write needs the callback’s explicit author identity.
Examples
let map = or_map.new(replica_id.new("A"), crdt.GSetSpec)
let assert Ok(map) = or_map.update(map, "tags", fn(value) {
let assert crdt.CrdtGSet(tags) = value
crdt.CrdtGSet(g_set.add(tags, "reviewed"))
})
or_map.keys(map) // -> ["tags"]
pub fn update_delta(
map: crdt.ORMap(a),
key: String,
callback: fn(crdt.Crdt(a), crdt.EditContext) -> Result(
crdt.CrdtDelta(a),
e,
),
) -> Result(
#(crdt.ORMap(a), crdt.ORMapDelta(a)),
crdt.UpdateError(e),
)
Run a sparse callback once, apply its delta, and return the resulting map/change.
Errors are atomic. NoChange still refreshes membership, so it is an
add-wins update. The callback context supplies the scoped author for new
LWWRegister writes. Nested maps return crdt.OrMapChange(child_delta).
Examples
Author a register write with the supplied scope, not the old write’s author:
import lattice_registers/lww_register
let map = or_map.new(replica_id.new("A"), crdt.LwwRegisterSpec(""))
let assert Ok(#(updated, delta)) =
or_map.update_delta(map, "title", fn(value, context) {
let assert crdt.CrdtLwwRegister(register) = value
let #(_, change) = lww_register.set_with_delta(
register, "New title", lww_register.timestamp(register) + 1,
context.replica_id,
)
Ok(crdt.StateDelta(crdt.CrdtLwwRegister(change)))
})
or_map.apply_delta(map, delta) // -> Ok(updated)
Keep an inner ORMap change sparse through the outer map:
import gleam/result
import lattice_text/text
let map: or_map.ORMap(String) =
or_map.new(replica_id.new("A"), crdt.OrMapSpec(crdt.TextSpec))
let assert Ok(#(updated, delta)) =
or_map.update_delta(map, "document", fn(value, _) {
let assert crdt.CrdtOrMap(document) = value
use #(_, change) <- result.try(
or_map.update_delta(document, "body", fn(value, _) {
let assert crdt.CrdtText(body) = value
use #(_, change) <- result.try(text.append_with_delta(body, "Hello"))
Ok(crdt.StateDelta(crdt.CrdtText(change)))
}),
)
Ok(crdt.OrMapChange(change))
})
or_map.apply_delta(map, delta) // -> Ok(updated)
pub fn update_with_delta(
map: crdt.ORMap(a),
key: String,
callback: fn(crdt.Crdt(a)) -> crdt.Crdt(a),
) -> Result(#(crdt.ORMap(a), crdt.ORMapDelta(a)), crdt.MergeError)
Run a full-value callback once and return the touched-key state delta.
The delta carries the complete returned child state. For large Sequence/Text
children, use update_delta instead.
Examples
let map = or_map.new(replica_id.new("A"), crdt.GSetSpec)
let assert Ok(#(updated, delta)) =
or_map.update_with_delta(map, "tags", fn(value) {
let assert crdt.CrdtGSet(tags) = value
crdt.CrdtGSet(g_set.add(tags, "ready"))
})
or_map.apply_delta(map, delta) // -> Ok(updated)
pub fn values(map: crdt.ORMap(a)) -> List(crdt.Crdt(a))
Return active, locally bound children, in unspecified order.
Examples
let map = or_map.new(replica_id.new("A"), crdt.TextSpec)
or_map.values(map) // -> []