lattice_maps/lww_map

A last-writer-wins map (LWW-Map) CRDT.

Each key maps to a value and a timestamp. On conflict, the entry with the higher timestamp wins. Removal is timestamp-based (tombstone): a remove at timestamp T beats any set at timestamp < T. Keys are strings; values are strings.

Example

import lattice_maps/lww_map

let a = lww_map.new() |> lww_map.set("name", "Alice", 1)
let b = lww_map.new() |> lww_map.set("name", "Bob", 2)
let merged = lww_map.merge(a, b)
lww_map.get(merged, "name")  // -> Ok("Bob")

Tombstone Management

Removing a key creates a tombstone that persists until pruned. Use tombstone_count to monitor growth and prune to reclaim space once all replicas have synced past a stable timestamp. The embedded pruned_timestamp ensures that stale entries from unsynced replicas are automatically rejected during merge (zombie prevention).

Types

A Last-Writer-Wins Map (LWW-Map) CRDT.

Internally stores each key with an Option(String) value and an Int timestamp. A None value represents a tombstone (removed key). On merge, the entry with the higher timestamp wins for each key; on ties, the first argument’s entry is kept as a consistent tiebreak.

Tombstones accumulate until pruned. Use prune with a stable timestamp to remove them. The embedded pruned_timestamp enables automatic zombie detection on merge — entries from remote replicas at or below the pruned threshold are discarded, preventing deleted keys from resurrecting.

pub opaque type LWWMap

Values

pub fn from_json(
  json_string: String,
) -> Result(LWWMap, json.DecodeError)

Decode a LWWMap from a JSON string produced by to_json.

Supports both v1 (no pruned_timestamp, defaults to 0) and v2 formats. Returns Error if the string is not valid JSON or does not match the expected format.

pub fn get(map: LWWMap, key: String) -> Result(String, Nil)

Get the value for a key.

Returns Ok(value) if the key exists and is not tombstoned. Returns Error(Nil) if the key is missing or has been removed.

pub fn keys(map: LWWMap) -> List(String)

Return all active (non-tombstoned) keys in the map.

Order is not guaranteed.

pub fn merge(a: LWWMap, b: LWWMap) -> LWWMap

Merge two LWW-Maps by resolving each key using the highest timestamp.

Tombstones participate in merge: if a tombstone has a higher timestamp than the active entry for a key, the key remains removed after merging. On equal timestamps, a deterministic tie-break is used: tombstones win over active values, otherwise the lexicographically greater value wins.

Merge is commutative, associative, and idempotent (a valid CRDT join).

pub fn new() -> LWWMap

Create a new empty LWW-Map.

pub fn prune(map: LWWMap, stable_timestamp: Int) -> LWWMap

Prune tombstones at or below the given stable timestamp.

Removes all tombstone entries (keys with None value) whose timestamp is less than or equal to stable_timestamp. Active entries are never removed. The pruned_timestamp is updated monotonically to max(current, stable_timestamp).

After pruning, merge automatically detects zombie entries — entries from remote replicas whose timestamps fall at or below the pruned threshold are discarded, preventing deleted keys from resurrecting.

The stable_timestamp should ideally represent a point that all replicas have synced past. Using a conservative (older) value is always safe; using a value ahead of some replica means that replica’s stale writes will be silently dropped on merge (which is the correct behavior for pruned history).

Examples

let m = lww_map.new()
  |> lww_map.set("a", "alive", 1)
  |> lww_map.remove("b", 5)
  |> lww_map.remove("c", 15)
let pruned = lww_map.prune(m, 10)
lww_map.tombstone_count(pruned)  // -> 1 (only "c" at ts=15 remains)
lww_map.get(pruned, "a")         // -> Ok("alive")
pub fn pruned_timestamp(map: LWWMap) -> Int

Return the pruned timestamp.

This is the highest stable timestamp passed to prune. Entries from remote replicas with timestamps at or below this value are treated as zombies during merge and discarded. A value of 0 means no pruning has occurred.

pub fn remove(map: LWWMap, key: String, timestamp: Int) -> LWWMap

Remove a key at the given timestamp by inserting a tombstone.

If the key already has an entry with an equal or higher timestamp, the remove is rejected and the existing entry wins.

Note: This operation creates a tombstone. Use tombstone_count to monitor growth and prune with a stable timestamp to remove tombstones once all replicas have observed them.

pub fn set(
  map: LWWMap,
  key: String,
  value: String,
  timestamp: Int,
) -> LWWMap

Set a key to a value at the given timestamp.

If the key already has an entry with an equal or higher timestamp, the existing entry is kept (LWW semantics: strictly greater timestamp wins).

pub fn to_json(map: LWWMap) -> json.Json

Encode a LWWMap as a self-describing JSON value.

Entries are encoded as a JSON array where each element has key, value (nullable string for tombstones), and timestamp fields. The pruned_timestamp field records the highest stable timestamp passed to prune, enabling zombie detection after deserialization.

Format: {"type": "lww_map", "v": 2, "state": {"entries": [...], "pruned_timestamp": N}}

The encoded value can be restored with from_json.

pub fn tombstone_count(map: LWWMap) -> Int

Return the number of tombstoned (removed) entries in the map.

Useful for monitoring tombstone growth and deciding when to call prune.

Examples

let m = lww_map.new()
  |> lww_map.set("a", "1", 1)
  |> lww_map.set("b", "2", 1)
  |> lww_map.remove("a", 10)
lww_map.tombstone_count(m)  // -> 1
pub fn values(map: LWWMap) -> List(String)

Return all active (non-tombstoned) values in the map.

Order is not guaranteed and does not correspond to the order of keys.

Search Document