GitHub

Getting Started

Installation

pnpm add @vuer-ai/vuer-rtc

Quick Start

import { createGraph } from '@vuer-ai/vuer-rtc';

// Create a graph store
const store = createGraph({
  sessionId: 'my-session',
  onSend: (msg) => websocket.send(msg),
});

// Edit (uncommitted, updates UI immediately)
store.edit({
  otype: 'node.insert',
  key: 'scene',           // Parent node's key
  path: 'children',
  value: { key: 'cube', tag: 'Mesh', name: 'Cube' },
});

// Commit (sends to server as one message)
store.commit('Create cube');

// Receive from server
websocket.onmessage = (msg) => store.receive(msg);

// Undo/redo (synced across all clients)
store.undo();
store.redo();

Core Concepts

State Model

The client maintains four key data structures:

interface ClientState {
  graph: SceneGraph;       // Current computed state
  journal: JournalEntry[]; // Committed messages
  edits: EditBuffer;       // Uncommitted operations
  snapshot: Snapshot;      // Checkpoint for fast replay
}
ComponentPurpose
graphCurrent state shown in UI. Derived from snapshot + journal + edits.
journalCommitted messages. Tracks ack status and deletion (undo) state.
editsUncommitted ops. Merged during gestures, committed as one message.
snapshotPeriodic checkpoint. Bakes in acked entries for fast replay.

Edit Buffer

Operations are accumulated in the edit buffer during gestures (like dragging), then committed as a single message:

// During drag (60fps)
store.edit({ otype: 'vector3.add', key: 'cube', path: 'position', value: [0.1, 0, 0] });
store.edit({ otype: 'vector3.add', key: 'cube', path: 'position', value: [0.1, 0, 0] });
// ... many more

// On drag end - all edits become ONE message
store.commit('Move cube');
// Sends: { ops: [{ otype: 'vector3.add', value: [5.0, 0, 0] }] }

The edit buffer merges additive operations, so 60 small deltas become 1 message.

Messages & Operations

Changes are expressed as messages containing one or more operations:

interface CRDTMessage {
  id: string;           // Unique message ID
  sessionId: string;    // Who sent this
  clock: VectorClock;   // For causal ordering
  lamportTime: number;  // For LWW ordering
  timestamp: number;    // Wall clock
  ops: Operation[];     // Batch of operations
}

Undo / Redo

Undo and redo are synced messages that mark entries with deletedAt:

// Undo the last committed message from this session
const { msg } = store.undo();
// Creates: { ops: [{ otype: 'meta.undo', targetMsgId: 'msg-123' }] }
// Syncs to all clients - everyone sees the undo

// Redo the last undone message
store.redo();
// Creates: { ops: [{ otype: 'meta.redo', targetMsgId: 'msg-123' }] }

All clients see the same undo state. Undo is not local-only.

Conflict Resolution

OperationMerge StrategyExample
*.setLast-Write-Wins (higher lamport)color.set '#ff0000'
*.addSum valuesvector3.add [1, 0, 0]
meta.undoSets deletedAt on targetSynced undo
meta.redoClears deletedAt on targetSynced redo

Next Steps