Crdt.Mval

crdt · API reference

Multi-value register extension built on arr node

Multi-Value Register extension (mval) - Concurrent write visibility.

A Multi-Value Register is a CRDT that preserves all concurrent writes, making conflicts visible rather than silently resolving them with LWW. This allows applications to implement custom conflict resolution.

It's built on an arr node where:

  • Each write creates a new element in the array
  • Concurrent writes from different sessions are all preserved
  • Sequential writes from the same session replace the previous value
  • Reading returns all concurrent values The key insight is that arr nodes (RGA) preserve all concurrent inserts. By using the arr's RGA semantics, we get concurrent write visibility for free.

Usage:

  let model = Model.create session_id in
  let mval = Mval.create model in
  Mval.set mval (Value.string "value1");
  (* Concurrent set from another session would add another value *)
  match Mval.get mval with
  | [v] -> (* No conflict - single value *)
  | values -> (* Conflict - multiple concurrent values *)

Conflict resolution strategies:

  • First-wins: List.hd (Mval.get mval)
  • Last-wins: List.hd (List.rev (Mval.get mval))
  • Custom: Application-specific merge

Types

type t = {
  model : Model.t;
  arr_node : Node.t;
  mutable last_write : Clock.timestamp option; (* Track last write from this session for sequential deduplication *)
}

Multi-value register handle wrapping an arr node

Creation

val create : Model.t -> t

Create a new multi-value register. Creates an arr node in the model.

val of_node : Model.t -> Node.t -> t

Create a multi-value register from an existing arr node. Use this when loading from a decoded model.

val id : t -> Clock.timestamp

Get the arr node's timestamp ID

Reading

val get : t -> Value.t list

Get all values in the register.

Returns a list of values representing all concurrent writes. If there's only one value, there are no conflicts. Multiple values indicate concurrent writes from different sessions.

Values are ordered by RGA ordering (timestamp-based).

val count : t -> int

Get the number of concurrent values (conflict count).

Returns 0 if empty, 1 if no conflict, >1 if conflict exists.

val has_conflict : t -> bool

Check if there's a conflict (multiple concurrent values).

val is_empty : t -> bool

Check if the register is empty (no values set).

val first : t -> Value.t option

Get the first value (if any). Useful for first-wins conflict resolution.

val last : t -> Value.t option

Get the last value (if any). Useful for last-wins conflict resolution.

Writing

val set : t -> Value.t -> unit

Set a value in the register.

If this is a sequential write from the same session, it replaces the previous value. If this is the first write or a concurrent write, it adds to the set of values.

parameter value The value to set

val clear : t -> unit

Clear all values from the register.

val set_value_only : t -> Value.t -> unit

Internal: set a value without clearing our previous write

val set_all : t -> Value.t list -> unit

Set multiple values atomically.

Creates entries for all values. Useful for explicitly setting multiple concurrent values during conflict resolution.

Conflict Resolution

type resolve_strategy = 
  | First (* Keep the first value (by RGA ordering) *)
  | Last (* Keep the last value (by RGA ordering) *)
  | Custom of Value.t list -> Value.t (* Custom resolver function *)

Resolve conflicts by keeping only one value.

parameter strategy How to select the value: `First | `Last | `Custom of (Value.t list -> Value.t)

val resolve : t -> resolve_strategy -> unit

Resolve conflicts using the given strategy.

After resolution, the register will have exactly one value (or zero if empty).

Inspection

val node : t -> Node.t

Get the raw arr node for advanced operations.

val timestamps : t -> Clock.timestamp list

Get all element timestamps (useful for debugging).

val pp : Format.formatter -> t -> unit

Pretty print the multi-value register state.

val to_string : t -> string

Convert to string for debugging.