Skip to main content
A TreeCRDT is a hierarchical CRDT where nodes can be added, moved, updated, and deleted concurrently across multiple clients. All replicas converge to the same tree without conflicts. Move operations use the algorithm from Kleppmann et al. (2021) — moves that would create cycles are silently discarded, and concurrent moves to different parents are resolved deterministically via Hybrid Logical Clock ordering.

Usage

React

Effect stream

Node IDs

addNode() returns the new node’s ID as a string ("wall_ms:logical:node_id"). Pass this ID to moveNode(), updateNode(), and deleteNode().

Sibling ordering

Siblings are ordered lexicographically by their position string, with ties broken by author ID descending. Use a fractional indexing scheme (e.g. "a0", "a0V", "b0") to insert between existing siblings without rewriting all positions.

API

Conflict semantics

  • Concurrent adds to the same parent converge — siblings are ordered by (position, id desc)
  • Concurrent moves: both moves apply in HLC order. The last move (highest op_id) wins for each node’s parent pointer
  • Cycle prevention: a move that would place a node under its own descendant is silently discarded
  • Tombstones: deleted nodes are never GC’d — children are preserved and restored if a concurrent move targets a deleted parent
  • Value updates: last-write-wins via the updated_at HLC timestamp

Encryption

Tree node values support end-to-end encryption. Pass an encryption map to MeridianClient.create():
addNode and updateNode encrypt the value argument before sending. deleteNode and moveNode do not involve a value and are unaffected. The decrypted value is transparent via value(), onChange(), and stream(). See Encryption for key generation and distribution patterns.

CRDT key prefix

Use the tr: prefix by convention: