lattice_counters/g_counter

A grow-only counter (G-Counter) CRDT.

Each replica maintains its own monotonically increasing count. The global value is the sum across all replicas. Merge takes the pairwise maximum of each replica’s count, guaranteeing convergence.

G-Counter is opaque: use the provided functions to interact with it. pn_counter in the same package can access internal fields directly.

Example

import lattice_core/replica_id
import lattice_counters/g_counter

let a = g_counter.new(replica_id.new("node-a")) |> g_counter.increment(3)
let b = g_counter.new(replica_id.new("node-b")) |> g_counter.increment(5)
let merged = g_counter.merge(a, b)
g_counter.value(merged)  // -> 8

Types

A grow-only counter that tracks per-replica counts.

Each replica identified by a ReplicaId maintains its own count. The global value is the sum of all per-replica counts.

pub opaque type GCounter
pub type IncrementError {
  NegativeDelta(Int)
}

Constructors

  • NegativeDelta(Int)

Values

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

Decode a G-Counter from a JSON string produced by to_json.

Returns Ok(GCounter) on success, or Error(json.DecodeError) if the input is not a valid G-Counter JSON envelope.

pub fn increment(counter: GCounter, delta: Int) -> GCounter

Increment the counter by delta.

Adds delta to this replica’s count. delta should be a non-negative integer; passing a negative value will decrease the local count, which violates the grow-only invariant and may cause incorrect merge results.

See increment_with_delta for the delta-state variant that also returns a small payload suitable for incremental sync (e.g. over websockets).

pub fn increment_with_delta(
  counter: GCounter,
  delta: Int,
) -> #(GCounter, GCounter)

Increment the counter by delta and return both the new state and a delta.

The returned delta is itself a GCounter containing only this replica’s new count. Merging the delta into a remote replica via merge produces the same result as merging the full new state — but the delta is a minimal payload suitable for incremental sync (e.g., over websockets).

See try_increment_with_delta for an error-safe variant. delta should be non-negative; a negative value will panic.

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

Merge two G-Counters using pairwise maximum.

For each replica, the merged count is the maximum of the two inputs. The result’s self_id is taken from a.

This operation is commutative, associative, and idempotent, satisfying the CRDT join-semilattice laws. Any ordering of concurrent merges will produce the same final state.

pub fn new(replica_id: replica_id.ReplicaId) -> GCounter

Create a new G-Counter for the given replica.

Returns a fresh counter where all per-replica counts are zero. The replica_id identifies this node and is used when incrementing.

pub fn to_json(counter: GCounter) -> json.Json

Encode a G-Counter as a self-describing JSON value.

Produces an envelope with type, v (schema version), and state. Format: {"type": "g_counter", "v": 1, "state": {"self_id": "...", "counts": {...}}}

Use from_json to decode the result back into a GCounter.

pub fn try_increment(
  counter: GCounter,
  delta: Int,
) -> Result(GCounter, IncrementError)

Safely increment the counter by delta.

Returns Error(NegativeDelta(delta)) if delta is negative.

See try_increment_with_delta for the delta-state variant.

pub fn try_increment_with_delta(
  counter: GCounter,
  delta: Int,
) -> Result(#(GCounter, GCounter), IncrementError)

Safely increment the counter by delta, returning the new state and a delta.

Returns Error(NegativeDelta(delta)) if delta is negative. On success, returns Ok(#(new_state, delta)) where delta is a GCounter containing only the changed self-replica entry.

pub fn value(counter: GCounter) -> Int

Get the current value of the counter.

Returns the sum of all per-replica counts, which represents the total number of increments applied across all replicas observed by this counter.

Search Document