Collaboration

@nuforge/collaboration syncs the AST between peers in real time over the Loro CRDT. Each peer runs its own Nu runtime, and a sync provider keeps every peer's tree converging on the same state without manual conflict resolution.

Getting started

A LoroSyncProvider binds a Nu runtime to a LoroDoc. The first peer seeds a fresh doc from its local state; any later peer imports that doc and hydrates its local state from it:

import { LoroSyncProvider, createCollabExtension } from '@nuforge/collaboration';
import { LoroDoc } from 'loro-crdt';

// Peer 1 seeds a fresh doc from its local state
const doc1 = new LoroDoc();
const provider1 = new LoroSyncProvider(nu1, doc1);
provider1.init();

// Peer 2 imports peer 1's doc, then loads its (empty) state from it
const doc2 = new LoroDoc();
doc2.import(doc1.export({ mode: 'snapshot' }));
const provider2 = new LoroSyncProvider(nu2, doc2);
provider2.init();

How it works

LoroSyncProvider(nu, doc) keeps a two-way sync between the Nu runtime and a LoroDoc. Calling init() does one of two things depending on the doc:

  • If the doc is empty, it seeds the doc from the runtime's current local state.
  • If the doc already has content, it hydrates the local runtime state from the doc.

Each AST node is stored as JSON under its id in a Loro map. This is node-level CRDT with last-write-wins per node, which avoids the reorder and move corruption seen with naive text-CRDT approaches — a node either moves as a whole or it does not, so the tree never tears.

Merges are tagged with a source, so a peer does not echo its own changes back into a loop. When an incoming update is applied, the provider recognizes its origin and skips re-broadcasting it.

As an extension

You can wire the exact same sync as a nuforge extension instead of constructing a provider directly. Pass createCollabExtension(doc) when creating the runtime:

Nu.create({ extensions: [createCollabExtension(doc)] });

Transport

The provider does not own the network. You exchange updates between peers yourself over any transport — WebSocket, WebRTC, or anything else — by sending the bytes from doc.export(...) and applying them on the other side with doc.import(...). This keeps collaboration transport-agnostic: wire it through your existing realtime channel without adopting a new server.

Next steps

  • Extensions — the extension system createCollabExtension plugs into.
  • Runtime — the Nu runtime each peer syncs.