Advanced Features
This page documents expert-level APIs for custom use cases. These features provide low-level access to vuer-rtc's internal data structures and operations.
These APIs are intended for advanced users who need fine-grained control over CRDT internals, custom conflict resolution, or performance optimization. For most use cases, the standard GraphStore API is recommended.
RangeTree - B-tree Implementation
The RangeTree is a B-tree optimized for string CRDT operations. It stores items with O(log n) lookups by position and maintains character counts in each node.
When to Use
- Building custom text editors with specific performance requirements
- Implementing alternative text CRDT algorithms
- Debugging text CRDT behavior at the tree level
- Performance analysis and benchmarking
Structure
Key Operations
Pitfalls
- Manual count updates: After modifying items directly, you must call
recalcAllCounts()to update character counts - Memory overhead: Tombstones are never removed, only marked as deleted. Use compaction at the higher level.
- No automatic splitting: The tree splits leaf nodes automatically, but you cannot control the branching factor (fixed at 32)
- No parent pointers: Items don't maintain back-references, making parent-based traversal O(n)
createGraphFromServer() - Server Snapshot Initialization
Initialize a client graph store from a server-provided snapshot and journal history.
When to Use
- Client reconnection after disconnect
- Loading archived sessions
- Server-side rendering with hydration
- Multi-device sync on session join
Usage
vs. Regular createGraph()
| Feature | createGraph() | createGraphFromServer() |
|---|---|---|
| Initial state | Empty or local snapshot | Server snapshot + journal |
| Journal replay | None | Automatic, causally ordered |
| Use case | New session | Reconnect/join existing |
| Vector clock | Starts at {sessionId: 0} | Merged from server |
Pitfalls
- Clock skew: Server snapshot's vector clock must be consistent with the journal entries. Mismatched clocks cause duplicate operations.
- Journal ordering: Entries are sorted by
(lamportTime, sessionId)before replay. Out-of-order server journals are handled automatically. - Compaction loss: Journal entries older than the snapshot watermark are lost. Cannot undo operations that were compacted server-side.
- Memory spike: Replaying large journals (10k+ ops) can cause temporary memory pressure during initialization.
compactToWatermark() - Advanced Compaction
Fold locally acknowledged entries covered by a server vector-clock watermark into the snapshot. This is a low-level client action; the room server sends the watermark only after its durable snapshot and journal deletion have committed.
When to Use
- Applying a durable server checkpoint to a connected client
- Removing journal entries only when their operations are known to be represented
Usage
The client folds only an acknowledged, canonically ordered safe prefix. A gap, missing causal dependency, or legacy actor-counter gap stops compaction; a later-arriving predecessor can make the prefix eligible on a subsequent watermark. Text tombstones and character identities are retained. Operations already folded into the server checkpoint are no longer available to generic journal-based undo.
vs. Regular compact()
| Feature | compact() | compactToWatermark() |
|---|---|---|
| Watermark | Implicit (all acked) | Explicit vector clock |
| Undo preservation | Folds acknowledged entries | Folds only safe acknowledged entries covered by the durable watermark |
| Use case | Local acknowledged journal cleanup | Applying the server's durable checkpoint |
| Memory impact | Local journal reduction | Safe reduction; unresolved gaps remain retained |
Pitfalls
- Do not construct a watermark locally: use the server's committed watermark or state-transfer snapshot.
- Legacy counter gaps: unexplained counters remain in the journal until a fresh state transfer establishes a new snapshot baseline.
- Undo window: a server-compacted operation cannot be undone by sending a generic journal undo; applications needing longer undo must retain their own inverse operations.
- Text identity: client watermark compaction retains tombstones and anchors; do not call rope compaction while peers may hold references.
initFromServer() - Server Initialization Patterns
Low-level action for initializing client state from server snapshot and journal. Used internally by createGraphFromServer().
When to Use
- Custom store initialization outside
createGraph() - Server-side state reconciliation
- Manual journal replay with custom filtering
- Testing and debugging CRDT behavior
Usage
Internals
- Clone snapshot graph: Deep-clones nodes, children arrays, and TextRope instances (avoids shared mutable state)
- Sort journal: Orders entries by
(lamportTime, sessionId)for causal replay - Rebuild graph: Applies each journal entry to the cloned graph
- Merge clocks: Merges vector clocks from all journal entries
- Return state: Returns a fresh
ClientStateobject
Pitfalls
- No edit buffer: The returned state has an empty edit buffer. Pending local edits must be reapplied.
- Snapshot mutation: The input snapshot is cloned, but if you pass the same snapshot to multiple
initFromServer()calls, ensure the server snapshot itself is immutable. - Journal ownership: The returned state stores a copy of the journal array. Mutating the input array after
initFromServer()won't affect the state. - No deduplication: Duplicate journal entries (same
id) are replayed multiple times. The server must deduplicate before sending.
EditBufferImpl - Low-Level Edit Buffer
The edit buffer accumulates uncommitted operations during gestures (e.g., dragging) and merges additive operations to reduce message size.
When to Use
- Custom gesture handling with operation merging
- Building higher-level transaction APIs
- Debugging edit buffer behavior
- Performance profiling of operation batching
Usage
Operation Deduplication
Operations are deduplicated by a key computed from (ot, key, path, target):
Additive Operations
These operations are merged (not replaced) when the same dedup key is used:
vector3.add- Component-wise additionnumber.add- Numeric additionnumber.multiply- Numeric multiplicationquaternion.multiply- Quaternion composition
Pitfalls
- LWW replacement: Non-additive ops (e.g.,
vector3.set) replace previous ops with the same dedup key, even if applied in different order - No undo: The edit buffer itself has no undo. Use
GraphStore.undo()which operates on the journal. - Merge order matters: For quaternions,
q1 * q2≠q2 * q1. Merging preserves order, but concurrent quaternion ops may produce unexpected results. - No validation: Invalid operations (e.g.,
vector3.addwith a string value) are stored without validation
findItemById() - Text CRDT Item Lookup
Low-level API for looking up text CRDT items by ID in the TextRope structure.
When to Use
- Debugging text CRDT parent references
- Custom text CRDT algorithms
- Analyzing character-level causality
- Performance profiling of ID lookups
Usage
Agent Index
TextRope maintains an agent-indexed structure for fast lookups:
- Span entries: Each agent's items are grouped into spans
[startSeq, endSeq) - Binary search: Finding an item by seq is O(log m) where m = number of spans for that agent
- Automatic updates: Inserting/deleting items updates the agent index
Pitfalls
- Deleted items: Deleted items (tombstones) are still returned. Check
isDeletedbefore using. - Parent references:
parentIdmay reference an item that doesn't exist (out-of-order delivery). Handlenullreturns gracefully. - No content lookup: There is no reverse lookup by content. Finding "where is the word 'hello'" requires iterating all items.
- Memory overhead: The agent index stores duplicate references to items. Large ropes with many agents consume more memory.
itemIdEquals() & itemIdCompare() - Item ID Utilities
Helper functions for comparing CRDT item IDs.
When to Use
- Custom CRDT implementations
- Sorting items by causal order
- Deduplicating item references
- Debugging parent-child relationships
Usage
Ordering Rules
- Agent first: Compare agent strings lexicographically
- Sequence second: If agents match, compare seq numbers
This ensures deterministic ordering across clients regardless of arrival order.
Pitfalls
- No null in compare:
itemIdCompare()does not handlenull. UseitemIdEquals()for nullable IDs. - String comparison: Agent IDs are compared as strings.
'alice10' < 'alice2'(lexicographic). Use UUIDs or zero-padded numbers. - No causal ordering: This compares IDs, not causality. For causal order, use vector clocks.
VectorClockManager - Advanced Clock Operations
Manages vector clocks for causal ordering and conflict detection.
When to Use
- Custom conflict resolution strategies
- Implementing causal broadcast protocols
- Debugging concurrency issues
- Multi-session coordination logic
Usage
Comparison Results
Causal Rules
- A
<B: All of A's counters ≤ B's counters, and at least one is strictly less - A
>B: All of A's counters ≥ B's counters, and at least one is strictly greater - A
||B: (concurrent) Some of A's counters are greater, some of B's are greater
Pitfalls
- Immutable operations: All methods return new clocks. Don't mutate clocks in place.
- Missing sessions: Missing sessions are treated as
0.{ alice: 1 }and{ alice: 1, bob: 0 }are equivalent. - No garbage collection: Old sessions remain in the clock forever. Large multi-session systems accumulate entries.
- No total order: Concurrent operations have no defined order. Use Lamport time for total ordering.
ConflictResolver - Custom Conflict Resolution
Merges concurrent property updates based on data type and operation semantics.
When to Use
- Implementing custom merge strategies
- Debugging conflict resolution behavior
- Building higher-level CRDT abstractions
- Analyzing concurrent edit patterns
Usage
Supported Operations
See the DType System below for all supported data types and operations.
Pitfalls
- Otype required: Missing
otypedefaults to'any.set'(LWW). Always specify explicit otypes. - Unknown dtypes: Unrecognized dtypes fall back to LWW with a console warning.
- No schema validation: Values are not type-checked. Passing a string to
vector3.addwill cause runtime errors. - Lamport time ties: When lamport times are equal,
sessionIdlexicographic order is used. Ensure Lamport clocks are properly incremented.
DType System - Data Type Abstraction Layer
The DType system defines merge behaviors for concurrent updates. Each data type supports multiple operations.
Structure
All Supported Types
| DType | Operations | Description |
|---|---|---|
number | set, add, multiply, min, max | Numeric values |
string | set, concat | Text values |
boolean | set, or, and | Boolean flags |
vector3 | set, add, multiply | 3D vectors [x, y, z] |
quaternion | set, multiply | Rotations [x, y, z, w] |
color | set, blend | Hex colors |
array | set, union, append | Lists |
object | set, lwwPerKey, merge | Objects |
immutable | set | Write-once values |
Custom Merge Functions
When to Use
- Implementing custom data types
- Building domain-specific merge logic
- Testing CRDT merge behavior
- Analyzing conflict patterns
Pitfalls
- No type checking:
DType.vector3.addaccepts any array. Runtime errors if values aren't[number, number, number]. - Immutable is first-write-wins: Unlike LWW,
immutable.settakes the earliest value, not latest. - Color blend is averaging:
color.blendaverages RGB values, not perceptual blending. May produce muddy colors. - Object.lwwPerKey is shallow: Only top-level keys are merged. Nested objects use LWW for the entire nested object.
- No custom dtypes: The DType registry is closed. To add custom types, fork the library or wrap in
object.lwwPerKey.
Performance Considerations
RangeTree
- Best case: O(log n) position lookups, O(log m) ID lookups
- Worst case: O(n) when tree is degenerate (all items in one leaf)
- Memory: ~64 bytes per item + tree node overhead
TextRope
- Best case: O(log n) insert/delete, O(log m) ID lookup
- Worst case: O(n) when agent index has many small spans
- Memory: ~100 bytes per item (includes agent index, parent refs)
Compaction
- Time: O(j + n) where j = journal length, n = graph size
- Memory spike: 2x graph size during rebuild (old + new)
- Recommendation: Compact every 100-1000 operations, not on every ack
Vector Clocks
- Time: O(s) where s = number of sessions
- Memory: O(s) per message
- Growth: Unbounded as sessions join. Consider pruning old sessions.
Common Patterns
Custom Text Editor
Session Watermark Compaction
Custom Conflict Resolver
Further Reading
- Architecture - Understanding the CRDT graph structure
- Operations - All supported operation types
- Networking & Retry - Message acknowledgement and sync
- Rope (Text CRDT) - High-level text CRDT API
These advanced APIs are subject to change in minor versions. Pin your version and test thoroughly when upgrading.