lattice/crdt

A tagged union over all leaf CRDT types with dynamic dispatch.

The Crdt type wraps individual CRDTs (counters, registers, sets) so they can be stored and merged uniformly — this is how ORMap holds heterogeneous values. For direct use, prefer the individual modules (e.g., g_counter, or_set) for type-safe access.

Maps (LWWMap, ORMap) are not included in this union to avoid circular module dependencies.

Example

import lattice/crdt
import lattice/g_counter

let a = crdt.CrdtGCounter(g_counter.new("node-a") |> g_counter.increment(1))
let b = crdt.CrdtGCounter(g_counter.new("node-b") |> g_counter.increment(2))
let merged = crdt.merge(a, b)

Types

A tagged union wrapping every leaf CRDT type in this library.

Variants:

  • CrdtGCounter — grow-only counter
  • CrdtPnCounter — increment/decrement counter
  • CrdtLwwRegister — last-writer-wins register (String)
  • CrdtMvRegister — multi-value register (String)
  • CrdtGSet — grow-only set (String)
  • CrdtTwoPSet — two-phase set (String)
  • CrdtOrSet — observed-remove set (String)
  • CrdtVersionVector — version vector

Parameterized types are fixed to String for v1. Maps (LWWMap, ORMap) are composite containers and are not included in this union to avoid circular module dependencies.

pub type Crdt {
  CrdtGCounter(g_counter.GCounter)
  CrdtPnCounter(pn_counter.PNCounter)
  CrdtLwwRegister(lww_register.LWWRegister(String))
  CrdtMvRegister(mv_register.MVRegister(String))
  CrdtGSet(g_set.GSet(String))
  CrdtTwoPSet(two_p_set.TwoPSet(String))
  CrdtOrSet(or_set.ORSet(String))
  CrdtVersionVector(version_vector.VersionVector)
}

Constructors

Specifies which leaf CRDT type an ORMap holds as its values.

When or_map.update is called on a key that does not yet exist, the map uses this spec to auto-create a default value via default_crdt. Choosing the right spec at or_map.new time is important because changing the value type after the fact would require migrating all existing values.

pub type CrdtSpec {
  GCounterSpec
  PnCounterSpec
  LwwRegisterSpec
  MvRegisterSpec
  GSetSpec
  TwoPSetSpec
  OrSetSpec
}

Constructors

  • GCounterSpec
  • PnCounterSpec
  • LwwRegisterSpec
  • MvRegisterSpec
  • GSetSpec
  • TwoPSetSpec
  • OrSetSpec

Values

pub fn default_crdt(spec: CrdtSpec, replica_id: String) -> Crdt

Create a new default (bottom) value of the specified CRDT type.

The replica_id is passed to CRDT constructors that require it (counters, registers, OR-Set). For types that don’t use a replica identifier (G-Set, 2P-Set), the argument is ignored.

Default values per spec:

  • GCounterSpec / PnCounterSpec — new counter for replica_id
  • LwwRegisterSpec — empty string "" at timestamp 0 (bottom element)
  • MvRegisterSpec — new MV-Register for replica_id
  • GSetSpec / TwoPSetSpec — empty set (no replica needed)
  • OrSetSpec — new OR-Set for replica_id
pub fn from_json(
  json_string: String,
) -> Result(Crdt, json.DecodeError)

Decode a Crdt from a JSON string produced by to_json.

Reads the "type" field to determine which type-specific decoder to use. Returns Error if the string is not valid JSON, the "type" field is missing, or the type tag is not recognized.

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

Dispatch merge to the type-specific merge function for matching variants.

If a and b hold the same variant, their inner values are merged using the type-specific merge function. On type mismatch (different variants), a is returned unchanged. Type mismatches should not occur in a well-formed system, but this behavior avoids a crash.

pub fn to_json(crdt: Crdt) -> json.Json

Dispatch to_json to the type-specific serializer for the wrapped CRDT.

Each variant delegates to its module’s to_json. The resulting JSON includes a "type" field (e.g., "g_counter") that from_json uses to select the correct decoder on deserialization.

Search Document