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.

Search Document