lattice_registers/lww_register
A last-writer-wins register (LWW-Register) CRDT.
Stores a single value with an associated timestamp. When two replicas
conflict, the value with the strictly higher timestamp wins. On equal
timestamps, the replica with the lexicographically greater replica_id
wins, ensuring fully commutative merge.
Example
import lattice_core/replica_id
import lattice_registers/lww_register
let a = lww_register.new("hello", 1, replica_id.new("node-a"))
let b = lww_register.new("world", 2, replica_id.new("node-b"))
let merged = lww_register.merge(a, b)
lww_register.value(merged) // -> "world"
Types
A register holding a single value alongside its write timestamp and replica identifier.
value is the stored payload, timestamp is an integer logical clock
used to resolve conflicts, and replica_id provides a deterministic
tie-breaker when timestamps are equal.
pub opaque type LWWRegister(a)
Values
pub fn from_json(
json_string: String,
) -> Result(LWWRegister(String), json.DecodeError)
Decode a LWWRegister(String) from a JSON string produced by to_json.
Supports both v1 (no replica_id, defaults to “”) and v2 (with replica_id)
envelopes. Returns Ok(LWWRegister(String)) on success, or
Error(json.DecodeError) if the input is not a valid LWW-Register JSON
envelope.
pub fn merge(
a: LWWRegister(a),
b: LWWRegister(a),
) -> LWWRegister(a)
Merge two LWW-Registers by returning the one with the higher timestamp.
When a.timestamp > b.timestamp, returns a. When b.timestamp > a.timestamp, returns b. On equal timestamps, the register whose
replica_id is lexicographically greater wins, providing a fully
commutative, associative, and idempotent merge.
pub fn new(
val val: a,
timestamp timestamp: Int,
replica_id replica_id: replica_id.ReplicaId,
) -> LWWRegister(a)
Create a new LWW-Register with an initial value, timestamp, and replica ID.
timestamp should be a positive integer representing the logical time of
the write. Use a monotonically increasing source (e.g., wall-clock
milliseconds or a Lamport clock) so that later writes have higher values.
replica_id identifies the writing node and is used as a deterministic
tie-breaker when two registers have equal timestamps during merge.
pub fn replica_id(
register: LWWRegister(a),
) -> replica_id.ReplicaId
Return the replica that owns the value the register currently holds.
set preserves the original replica, so for a locally written register
this is the replica that created it. After merge it is the replica whose
write won, which makes it useful for provenance and for tie-breaking
consistently with merge in downstream code.
pub fn set(
register register: LWWRegister(a),
val val: a,
timestamp timestamp: Int,
) -> LWWRegister(a)
Update the register if timestamp is strictly greater than the current one.
If timestamp > register.timestamp, replaces the stored value and
timestamp with the new ones. Otherwise returns the register unchanged.
This ensures only strictly newer writes are accepted.
The replica_id is preserved from the original register.
Note that the comparison is strict, so a wall clock is not a safe source
on its own: it stalls for a millisecond at a time, and a second write
inside the same tick is silently dropped even when it came from this same
replica. Callers that write faster than their clock ticks should stamp
int.max(wall_clock, timestamp(register) + 1), which keeps every local
write ordered while leaving merge commutative.
See set_with_delta for the delta-state variant that also returns a
small payload suitable for incremental sync (e.g. over websockets).
pub fn set_with_delta(
register register: LWWRegister(a),
val val: a,
timestamp timestamp: Int,
) -> #(LWWRegister(a), LWWRegister(a))
Set a value and return both the new state and a delta.
The returned delta is an LWWRegister carrying the write that was
actually accepted locally. When timestamp strictly exceeds the current
timestamp the delta carries the new (value, timestamp, replica_id);
otherwise the local set is a no-op and the delta carries the unchanged
register so that no rejected write can win on a remote replica.
Merging the delta into a remote via merge produces the same result as
merging the new local state, preserving convergence.
pub fn timestamp(register: LWWRegister(a)) -> Int
Return the timestamp of the write the register currently holds.
Because set accepts only strictly greater timestamps, a caller stamping
writes from a wall clock needs to know the timestamp already held in order
to stay ahead of it — two writes inside the same clock tick are otherwise
unordered and the second is dropped. Reading this back from a decoded
snapshot lets such a caller seed its logical clock before its first write.
Examples
let register = lww_register.new("hello", 42, replica_id.new("node-a"))
lww_register.timestamp(register) // -> 42
// Stamp the next write so it cannot collide with the one held.
let next = int.max(wall_clock_ms(), lww_register.timestamp(register) + 1)
pub fn to_json(register: LWWRegister(String)) -> json.Json
Encode a LWWRegister(String) as a self-describing JSON value.
Produces an envelope with type, v (schema version = 2), and state.
Format: {"type": "lww_register", "v": 2, "state": {"value": "...", "timestamp": ..., "replica_id": "..."}}
Use from_json to decode the result back into a LWWRegister(String).
pub fn value(register: LWWRegister(a)) -> a
Return the current value of the register.
Provided for a uniform functional API since the type is opaque.