lattice / orientation
Quick Start
Get up and running with lattice in minutes.
Install the umbrella package:
gleam add lattice_crdtThis gives you the core CRDT packages. See Installation for individual package options, including distributed presence.
Merge counters from multiple replicas
Section titled “Merge counters from multiple replicas”GCounter is the simplest place to start: each replica can only increase its
own contribution, and merging takes the per-replica maximum.
Save this as src/quick_start.gleam:
On narrow screens, scroll code blocks horizontally to read each full line.
import gleam/intimport gleam/ioimport lattice_core/replica_idimport lattice_counters/g_counter
pub fn main() { let counter_a = g_counter.new(replica_id.new("node-a")) |> g_counter.increment(2)
let counter_b = g_counter.new(replica_id.new("node-b")) |> g_counter.increment(3)
let merged = g_counter.merge(counter_a, counter_b)
merged |> g_counter.value |> int.to_string |> io.println}Run the example from your project directory:
gleam run -m quick_startExpected output:
5Success: you now have a convergent counter: two replicas updated independently, then merged into the same combined value.
g_counter.increment rejects negative deltas. If you need a counter that can
go down, use pn_counter from lattice_counters; both increment and
decrement still take non-negative deltas because the underlying state is
grow-only.
For incremental replication, counters also expose *_with_delta mutators that
return both the new state and a compact delta. See
Delta-State Replication.
Advanced recipes
Section titled “Advanced recipes”Open these when you are ready to move beyond the first convergent counter. Each recipe has a stable fragment link: LWW register and OR-Map.
Advanced recipeResolve ties deterministically with LWW registersChoose a value by timestamp, with replica IDs breaking equal-timestamp ties.
LWWRegister stores a value plus a timestamp. Newer timestamps win. If two
replicas write at the same timestamp, lattice breaks the tie deterministically
using the replica ID.
import lattice_core/replica_idimport lattice_registers/lww_register
pub fn main() { let left = lww_register.new("apple", 7, replica_id.new("node-a")) let right = lww_register.new("zebra", 7, replica_id.new("node-b"))
let merged = lww_register.merge(left, right)
lww_register.value(merged) // -> "zebra" (node-b > node-a lexicographically)}lww_register.set only applies when the new timestamp is strictly greater than
the current one.
Advanced recipeStore CRDT values inside an OR-MapTrack add-wins keys while merging a nested CRDT value for each key.
ORMap tracks keys with add-wins semantics and stores a CRDT value at each
key. The CrdtSpec you choose when creating the map determines the default
value used when or_map.update sees a missing key.
import lattice_core/replica_idimport lattice_counters/g_counterimport lattice_maps/crdtimport lattice_maps/or_map
fn add_points(value: crdt.Crdt, delta: Int) -> crdt.Crdt { case value { crdt.CrdtGCounter(counter) -> crdt.CrdtGCounter(g_counter.increment(counter, delta))
other -> other }}
pub fn main() { let scoreboard_a = or_map.new(replica_id.new("node-a"), crdt.GCounterSpec) |> or_map.update("alice", fn(value) { add_points(value, 2) })
let scoreboard_b = or_map.new(replica_id.new("node-b"), crdt.GCounterSpec) |> or_map.update("alice", fn(value) { add_points(value, 3) })
let assert Ok(merged) = or_map.merge(scoreboard_a, scoreboard_b)
case or_map.get(merged, "alice") { Ok(crdt.CrdtGCounter(counter)) -> g_counter.value(counter) _ -> 0 } // -> 5}For more detail, see the Counters guide, Registers guide, Sets guide, and Maps guide. For topic/key presence tracking, see the Presence guide.
