lattice_text
Plain-text CRDT for Gleam using YATA-style left/right origins.
Use this package when replicas need to edit shared text concurrently and converge without coordination. All operations are grapheme-based, so emoji and combining sequences count as one unit. For a generic list CRDT, use lattice_sequence directly.
Installation
gleam add lattice_text
Quick example
import lattice_core/replica_id
import lattice_text/text
pub fn main() {
let local = replica_id.new("node-a")
let assert Ok(node_a) =
text.new(local)
|> text.insert(0, "Hello world")
let assert Ok(node_b) =
text.new(replica_id.new("node-b"))
|> text.append("!")
let merged = text.merge(node_a, node_b, local)
text.value(merged)
// -> "Hello world!"
}
Modules
| Module | Purpose |
|---|---|
lattice_text/text | Grapheme-based text CRDT with editing, range, and delta operations. |
Notes
- Editing operations:
insert,delete,delete_range,replace_range,move, andappend. - Query helpers:
value,values,length,substring, andtry_substring. - Edits (including
append),anchor_at, andresolve_anchorreturnResult. Range operations returnRangeErrorwhen0 <= start <= end <= lengthdoes not hold. Handle errors for untrusted inputs; the example asserts only known-valid operations. substringstill clamps bounds;try_substringretains its strictResultcontract.- Delta-state variants (
*_with_delta) returnOk(#(updated, delta)); apply a delta withmerge(state, delta, local_replica). merge(a, b, replica)requires the identity for subsequent local edits.merge_asis a forwarding alias. Either operand order produces the same full state for a fixed output identity. Independent writers must use distinct replica IDs.- Decoded remote snapshots retain their serialized identity. Use
bind(state, local_id)to adopt a snapshot without rebuilding it, ormergeto combine it with local history. - Cursor anchors:
anchor_atcreates a stable position that survives concurrent edits and merges,resolve_anchormaps it back to a current grapheme index, andanchor_to_json/anchor_from_jsonlet anchors travel between replicas (e.g. shared cursors). merge,to_json, andfrom_jsonround-trip the full CRDT state using the canonical sequence JSON envelope.- Backed by
lattice_sequencestable item IDs, so concurrent edits converge deterministically on both Erlang and JavaScript targets.
Map composition
lattice_maps/crdt.TextSpec creates an empty Text child. Use ORMap when
concurrent child edits must merge; use its sparse callback with the delta
from a Text *_with_delta operation to avoid sending the full document.
This remains sparse through nested ORMap paths.
Map re-addition creates a fresh generation with a new editing namespace. Newer generations replace older content. A LWWMap Text assignment instead replaces a whole snapshot and does not merge losing concurrent edits.
Text dispatch uses a distinct wrapper around this package’s unchanged Sequence JSON envelope. Do not infer Text from a bare Sequence envelope. Outer map clocks do not authorize inner sequence compaction.
Links
- Project site: https://lattice.tylerbutler.com
- API docs: https://hexdocs.pm/lattice_text
- Hex package: https://hex.pm/packages/lattice_text
- Repository: https://github.com/tylerbutler/lattice
License
MIT