CRDT Adapters
Production CRDT backends — Yjs, Automerge, Loro — behind the same TextCrdtAdapter contract as the in-package rgaText. Swap one in and every call site stays the same.
#Why adapters
@absolutejs/sync/crdt is a small, zero-dependency RGA — great for offline-merge and moderate collaboration. Production-scale collaborative text wants the things mature CRDT libraries solve: efficient binary deltas, tombstone management, interleaving guarantees. The sync-adapters repo houses first-party- maintained wrappers around the staples so the core stays dependency-free and you only install the backend you actually use.
#The contract — one shape for every backend
Every backend (including the first-party RGA) implements TextCrdtAdapter<State>. The client controller and server engine only ever talk to that contract, which is why swapping one in is a one-line change on each side:
// The contract every backend (first-party rgaText included) implements:
import type { CrdtText, TextCrdtAdapter, CrdtMergeable } from '@absolutejs/sync/crdt';
// What the engine needs server-side to auto-merge a CRDT field on write:
type CrdtMergeable<State> = {
empty: () => State;
merge: (a: State, b: State) => State;
};
// What a collaborative-text backend exposes:
type TextCrdtAdapter<State> = CrdtMergeable<State> & {
create: (replica: string, initial?: State) => CrdtText<State>;
textOf: (state: State) => string;
compact?: (state: State) => State; // optional, for GC-style backends
};
// The live doc the client controller drives:
type CrdtText<State> = {
text: () => string;
setText: (next: string) => void;
merge: (state: State) => void;
state: () => State;
takeDelta?: () => State; // optional; the controller falls
// back to state() when missing
};#@absolutejs/sync-yjs — Yjs
The most widely-used collaborative CRDT in the web ecosystem. The adapter wraps Yjs's Y.Text, serialises state as a base64 Yjs update, and implements takeDelta via state vectors for true delta uploads:
import { yjsText, createYjsText } from '@absolutejs/sync-yjs';
// Server
engine.registerCrdt('issues', { body: yjsText });
// Client — only the create factory changes
const doc = useCollaborativeText({
url, collection: 'issues', field: 'body', id,
create: createYjsText,
});
// State serialises as a base64 Yjs update (JSON-safe for the change feed).
// takeDelta is implemented via Y.encodeStateAsUpdate(doc, lastVector) and the
// vector advances on merge — remote ops aren't re-broadcast.#@absolutejs/sync-automerge — Automerge
A mature, battle-tested CRDT. The adapter wraps an Automerge doc with a single text field and serialises state as a base64 Automerge document.
import { automergeText, createAutomergeText } from '@absolutejs/sync-automerge';
engine.registerCrdt('issues', { body: automergeText });
const doc = useCollaborativeText({
url, collection: 'issues', field: 'body', id,
create: createAutomergeText,
});
// State = base64 of an Automerge doc. Notes:
// - The 'replica' option is accepted for contract symmetry; Automerge manages
// actor identity internally.
// - Mutators concurrently creating the SAME top-level key on independent docs
// can fork; the empty() snapshot is the shared base so clients load it for
// a common root.#@absolutejs/sync-loro — Loro
A fast Rust/wasm CRDT. The adapter wraps a LoroDoc's text container and serialises state as a base64 Loro snapshot.
import { loroText, createLoroText } from '@absolutejs/sync-loro';
engine.registerCrdt('issues', { body: loroText });
const doc = useCollaborativeText({
url, collection: 'issues', field: 'body', id,
create: createLoroText,
});
// State = base64 of a Loro snapshot. Loro is a fast Rust/wasm CRDT;
// importing a snapshot or update into a doc merges. The 'replica' option is
// accepted for contract symmetry; Loro assigns a peer id internally.