Grove.Tree.Event (Grove v0.1.1)

View Source

Represents an operation in the event graph (DAG).

Each event has parent references forming a DAG structure, enabling causal ordering and critical version detection for Eg-walker integration.

Fields

  • :id - Unique identifier {replica_id, sequence}
  • :parents - Set of parent event IDs (DAG edges)
  • :timestamp - Unix timestamp in milliseconds when the operation occurred
  • :operation - The operation tuple (may include trailing metadata)
  • :node_ids - List of node IDs affected by this operation
  • :parent_was_critical - Whether the parent version was critical (enables fast-path)

Event Graph Structure

Events form a DAG where:

  • Sequential edits create a linear chain (each event has one parent)
  • Concurrent edits create branches (multiple events share parents)
  • Merging creates join points (an event has multiple parents)

When the "frontier" (tips of the DAG) has size 1, we're in sequential editing mode - the "critical version" fast path from Eg-walker.

Summary

Functions

Converts a legacy HistoryEntry to an Event with empty parents.

Returns true if this event has multiple parents (merge event).

Creates a new event with the given ID, parents, operation, and affected node IDs.

Returns true if this event has no parents (is a root event).

Returns true if this event has exactly one parent (sequential edit).

Types

event_id()

@type event_id() :: {String.t(), non_neg_integer()}

t()

@type t() :: %Grove.Tree.Event{
  id: event_id(),
  node_ids: [String.t()],
  operation: tuple(),
  parent_was_critical: boolean(),
  parents: MapSet.t(event_id()),
  timestamp: non_neg_integer()
}

Functions

from_history_entry(entry)

@spec from_history_entry(Grove.Tree.HistoryEntry.t()) :: t()

Converts a legacy HistoryEntry to an Event with empty parents.

Used for migrating existing history data. Legacy entries have no parent information, so they are treated as isolated events.

Examples

iex> entry = %Grove.Tree.HistoryEntry{id: {"r1", 1}, timestamp: 12345, operation: {:put_node, "n1", node}, node_ids: ["n1"]}
iex> event = Grove.Tree.Event.from_history_entry(entry)
iex> event.id
{"r1", 1}
iex> MapSet.size(event.parents)
0

merge?(event)

@spec merge?(t()) :: boolean()

Returns true if this event has multiple parents (merge event).

Examples

iex> parents = MapSet.new([{"r1", 1}, {"r2", 1}])
iex> event = Grove.Tree.Event.new({"r1", 2}, parents, {:put_node, "n2", node}, ["n2"])
iex> Grove.Tree.Event.merge?(event)
true

new(id, parents, operation, node_ids, opts \\ [])

@spec new(event_id(), MapSet.t(event_id()), tuple(), [String.t()], keyword()) :: t()

Creates a new event with the given ID, parents, operation, and affected node IDs.

Options

  • :parent_was_critical - Whether the parent version was critical (default: true for root events)

Examples

iex> event = Grove.Tree.Event.new({"replica_1", 1}, MapSet.new(), {:put_node, "n1", node}, ["n1"])
iex> event.id
{"replica_1", 1}
iex> MapSet.size(event.parents)
0

iex> event = Grove.Tree.Event.new({"replica_1", 2}, MapSet.new([{"replica_1", 1}]), {:put_node, "n2", node}, ["n2"], parent_was_critical: true)
iex> event.parent_was_critical
true

root?(event)

@spec root?(t()) :: boolean()

Returns true if this event has no parents (is a root event).

Examples

iex> event = Grove.Tree.Event.new({"r1", 1}, MapSet.new(), {:put_node, "n1", node}, ["n1"])
iex> Grove.Tree.Event.root?(event)
true

sequential?(event)

@spec sequential?(t()) :: boolean()

Returns true if this event has exactly one parent (sequential edit).

Examples

iex> parent = {"r1", 1}
iex> event = Grove.Tree.Event.new({"r1", 2}, MapSet.new([parent]), {:put_node, "n2", node}, ["n2"])
iex> Grove.Tree.Event.sequential?(event)
true