Grove (Grove v0.1.1)
View SourceGrove - Conflict-free replicated trees for Elixir.
Grove provides CRDT-based tree structures for collaborative editing of hierarchical data like documents, forms, and ASTs.
Quick Start
# Convert plain data to a CRDT tree
plain_data = %{
id: "form_1",
type: :form,
attrs: %{title: "Survey"},
children: [
%{id: "field_1", type: :text, attrs: %{label: "Name"}}
]
}
tree = Grove.from_data(plain_data, replica_id: "node_a")
# Convert back to plain data
plain_data = Grove.to_data(tree)Round-Trip Guarantee
When all nodes have explicit IDs:
data == Grove.to_data(Grove.from_data(data, replica_id: "a"))
Summary
Functions
Applies multiple CRDT operations atomically.
Like batch/2 but raises on error.
Buffers an update for debounced broadcast.
Clears the cursor position for this replica.
Clears the selection for this replica.
Flushes the update buffer, broadcasting all pending deltas.
Converts plain hierarchical data to a Grove tree.
Gets the current replica's presence metadata.
Gets all presences for the current document.
Updates the cursor position for this replica.
Updates which node has focus for this replica.
Updates the selection range for this replica.
Converts a Grove tree back to plain hierarchical data.
Functions
@spec batch( struct(), (struct() -> struct()) ) :: Grove.Batch.batch_result(struct())
Applies multiple CRDT operations atomically.
The batch function receives the CRDT and should return the modified CRDT. All operations within the batch accumulate into a single delta buffer.
Returns {:ok, updated_crdt} on success or {:error, reason, original_crdt}
on failure.
Example
alias Grove.Set.ORSet
{:ok, set} = Grove.batch(ORSet.new(:node_a), fn s ->
s
|> ORSet.add("apple")
|> ORSet.add("banana")
end)See Grove.Batch.run/2 for details.
Like batch/2 but raises on error.
See Grove.Batch.run!/2 for details.
@spec buffer_update(map(), (Grove.Tree.t() -> {Grove.Tree.t(), term()}), keyword()) :: map()
Buffers an update for debounced broadcast.
See Grove.LiveView.buffer_update/3 for details.
Clears the cursor position for this replica.
See Grove.LiveView.clear_cursor/1 for details.
Clears the selection for this replica.
See Grove.LiveView.clear_selection/1 for details.
Flushes the update buffer, broadcasting all pending deltas.
See Grove.LiveView.flush_buffer/1 for details.
Converts plain hierarchical data to a Grove tree.
Options
:replica_id- Required. Unique identifier for this replica.
Example
tree = Grove.from_data(data, replica_id: "node_a")See Grove.Data.from_data/2 for details.
Gets the current replica's presence metadata.
See Grove.LiveView.get_my_presence/1 for details.
Gets all presences for the current document.
See Grove.LiveView.get_presences/1 for details.
@spec set_cursor(map(), String.t(), non_neg_integer() | nil) :: map()
Updates the cursor position for this replica.
See Grove.LiveView.set_cursor/3 for details.
Updates which node has focus for this replica.
See Grove.LiveView.set_focus/2 for details.
Updates the selection range for this replica.
See Grove.LiveView.set_selection/4 for details.
Converts a Grove tree back to plain hierarchical data.
Deleted nodes are excluded from the output.
Example
plain_data = Grove.to_data(tree)See Grove.Data.to_data/1 for details.