Conflict-free Replicated Data Type (CRDT) framework for real-time collaborative applications
npx @tessl/cli install tessl/npm-yjs@13.6.0Yjs is a Conflict-free Replicated Data Type (CRDT) framework that enables real-time collaborative editing without conflicts. It provides shared data types that multiple users can edit simultaneously, with automatic synchronization and conflict resolution. Yjs is network-agnostic and supports peer-to-peer synchronization, offline editing, undo/redo functionality, and efficient binary serialization.
npm install yjsimport * as Y from "yjs";For individual imports:
import { Doc, Array as YArray, Map as YMap, Text as YText } from "yjs";CommonJS:
const Y = require("yjs");
const { Doc, Array: YArray, Map: YMap, Text: YText } = require("yjs");import * as Y from "yjs";
// Create a new document
const doc = new Y.Doc();
// Get shared types
const yarray = doc.getArray("my-array");
const ymap = doc.getMap("my-map");
const ytext = doc.getText("my-text");
// Use shared types
yarray.push(["hello", "world"]);
ymap.set("key", "value");
ytext.insert(0, "Hello collaborative world!");
// Observe changes
yarray.observe((event) => {
console.log("Array changed:", event.changes);
});
// Create updates for synchronization
const update = Y.encodeStateAsUpdate(doc);
// Apply updates from other clients
const otherDoc = new Y.Doc();
Y.applyUpdate(otherDoc, update);Yjs is built around several key components:
Core document functionality for creating, managing, and synchronizing collaborative documents.
class Doc {
constructor(options?: DocOpts);
readonly clientID: number;
readonly guid: string;
readonly isLoaded: boolean;
readonly isSynced: boolean;
readonly isDestroyed: boolean;
readonly whenLoaded: Promise<Doc>;
readonly whenSynced: Promise<void>;
readonly subdocs: Set<Doc>;
get<Type>(name: string, TypeConstructor?: Type): InstanceType<Type>;
getArray<T>(name?: string): YArray<T>;
getText(name?: string): YText;
getMap<T>(name?: string): YMap<T>;
getXmlElement(name?: string): YXmlElement;
getXmlFragment(name?: string): YXmlFragment;
transact<T>(f: (transaction: Transaction) => T, origin?: any): T;
load(): void;
getSubdocs(): Set<Doc>;
getSubdocGuids(): Set<string>;
destroy(): void;
toJSON(): any;
}
interface DocOpts {
clientID?: number;
guid?: string;
collectionid?: string | null;
gc?: boolean;
gcFilter?: (item: Item) => boolean;
meta?: any;
autoLoad?: boolean;
shouldLoad?: boolean;
}Observable CRDT types for different collaborative editing scenarios with automatic conflict resolution.
class YArray<T> {
constructor();
static from<T>(items: Array<T>): YArray<T>;
readonly length: number;
insert(index: number, content: Array<T>): void;
push(content: Array<T>): void;
unshift(content: Array<T>): void;
delete(index: number, length?: number): void;
get(index: number): T;
toArray(): Array<T>;
}
class YMap<T> {
constructor(entries?: Iterable<readonly [string, any]>);
readonly size: number;
set<VAL>(key: string, value: VAL): VAL;
get(key: string): T | undefined;
has(key: string): boolean;
delete(key: string): void;
clear(): void;
}
class YText {
constructor(string?: string);
readonly length: number;
insert(index: number, text: string, attributes?: TextAttributes): void;
insertEmbed(index: number, embed: Object | AbstractType<any>, attributes?: TextAttributes): void;
delete(index: number, length: number): void;
format(index: number, length: number, attributes: TextAttributes): void;
removeAttribute(attributeName: string): void;
setAttribute(attributeName: string, attributeValue: any): void;
getAttribute(attributeName: string): any;
getAttributes(): {[key: string]: any};
toString(): string;
}XML-aware collaborative types for structured document editing with DOM integration.
class YXmlElement<KV = { [key: string]: any }> {
constructor(nodeName?: string);
readonly nodeName: string;
setAttribute<KEY>(attributeName: KEY, attributeValue: KV[KEY]): void;
getAttribute<KEY>(attributeName: KEY): KV[KEY] | undefined;
hasAttribute(attributeName: string): boolean;
toDOM(document?: Document): Element;
}
class YXmlFragment {
constructor();
readonly length: number;
insert(index: number, content: Array<YXmlElement | YXmlText>): void;
insertAfter(ref: null | Item | YXmlElement | YXmlText, content: Array<YXmlElement | YXmlText>): void;
querySelector(query: string): YXmlElement | YXmlText | YXmlHook | null;
querySelectorAll(query: string): Array<YXmlElement | YXmlText | YXmlHook>;
createTreeWalker(filter?: (node: AbstractType<any>) => boolean): YXmlTreeWalker;
}Atomic change batching for consistent updates and coordinated event handling.
class Transaction {
readonly doc: Doc;
readonly origin: any;
readonly local: boolean;
readonly beforeState: Map<number, number>;
readonly afterState: Map<number, number>;
}
function transact<T>(doc: Doc, f: (transaction: Transaction) => T, origin?: any): T;Comprehensive event handling for observing changes to shared types with detailed change information.
abstract class YEvent<T> {
readonly target: T;
readonly transaction: Transaction;
readonly path: Array<string | number>;
readonly changes: {
added: Set<Item>;
deleted: Set<Item>;
keys: Map<string, any>;
delta: Array<any>;
};
}
interface AbstractType<EventType> {
observe(f: (event: EventType, transaction: Transaction) => void): void;
unobserve(f: (event: EventType, transaction: Transaction) => void): void;
observeDeep(f: (events: Array<YEvent<any>>, transaction: Transaction) => void): void;
}Position system that maintains references across concurrent edits and structural changes.
class RelativePosition {
readonly type: ID | null;
readonly tname: string | null;
readonly item: ID | null;
readonly assoc: number;
}
class AbsolutePosition {
readonly type: AbstractType<any>;
readonly index: number;
readonly assoc: number;
}
function createRelativePositionFromTypeIndex(
type: AbstractType<any>,
index: number,
assoc?: number
): RelativePosition;
function createAbsolutePositionFromRelativePosition(
rpos: RelativePosition,
doc: Doc
): AbsolutePosition | null;
function createRelativePositionFromJSON(json: any): RelativePosition;
function encodeRelativePosition(rpos: RelativePosition): Uint8Array;
function decodeRelativePosition(encoded: Uint8Array): RelativePosition;
function relativePositionToJSON(rpos: RelativePosition): any;
function compareRelativePositions(a: RelativePosition, b: RelativePosition): number;Binary update system for efficient network synchronization and state management.
function encodeStateAsUpdate(doc: Doc, encodedTargetStateVector?: Uint8Array): Uint8Array;
function encodeStateAsUpdateV2(doc: Doc, encodedTargetStateVector?: Uint8Array): Uint8Array;
function applyUpdate(doc: Doc, update: Uint8Array, transactionOrigin?: any): void;
function applyUpdateV2(doc: Doc, update: Uint8Array, transactionOrigin?: any): void;
function mergeUpdates(updates: Array<Uint8Array>): Uint8Array;
function mergeUpdatesV2(updates: Array<Uint8Array>): Uint8Array;
function readUpdate(decoder: UpdateDecoder, doc: Doc, transactionOrigin?: any): void;
function readUpdateV2(decoder: UpdateDecoder, doc: Doc, transactionOrigin?: any): void;
function encodeStateVector(doc: Doc): Uint8Array;
function decodeStateVector(stateVector: Uint8Array): Map<number, number>;
function parseUpdateMeta(update: Uint8Array): { from: Map<number, number>; to: Map<number, number> };
function parseUpdateMetaV2(update: Uint8Array): { from: Map<number, number>; to: Map<number, number> };
function encodeStateVectorFromUpdate(update: Uint8Array): Uint8Array;
function encodeStateVectorFromUpdateV2(update: Uint8Array): Uint8Array;
function diffUpdate(update: Uint8Array, stateVector: Uint8Array): Uint8Array;
function diffUpdateV2(update: Uint8Array, stateVector: Uint8Array): Uint8Array;
function convertUpdateFormatV1ToV2(update: Uint8Array): Uint8Array;
function convertUpdateFormatV2ToV1(update: Uint8Array): Uint8Array;
function logUpdate(update: Uint8Array): void;
function logUpdateV2(update: Uint8Array): void;Time-travel functionality for document state at specific points with diff capabilities.
class Snapshot {
readonly ds: DeleteSet;
readonly sv: Map<number, number>;
}
function snapshot(doc: Doc): Snapshot;
function createSnapshot(doc: Doc): Snapshot;
function createDocFromSnapshot(originDoc: Doc, snapshot: Snapshot, newDoc?: Doc): Doc;
function equalSnapshots(snap1: Snapshot, snap2: Snapshot): boolean;
function encodeSnapshot(snapshot: Snapshot): Uint8Array;
function decodeSnapshot(encoded: Uint8Array): Snapshot;
function encodeSnapshotV2(snapshot: Snapshot): Uint8Array;
function decodeSnapshotV2(encoded: Uint8Array): Snapshot;
function snapshotContainsUpdate(snapshot: Snapshot, update: Uint8Array): boolean;Multi-level undo/redo functionality with scope management and origin tracking.
class UndoManager {
constructor(
typeScope: Doc | AbstractType<any> | Array<AbstractType<any>>,
options?: UndoManagerOptions
);
readonly undoStack: Array<StackItem>;
readonly redoStack: Array<StackItem>;
undo(): StackItem | null;
redo(): StackItem | null;
canUndo(): boolean;
canRedo(): boolean;
clear(clearUndoStack?: boolean, clearRedoStack?: boolean): void;
}
interface UndoManagerOptions {
captureTimeout?: number;
captureTransaction?: (transaction: Transaction) => boolean;
deleteFilter?: (item: Item) => boolean;
trackedOrigins?: Set<any>;
}Essential utility functions for working with Yjs internals and advanced operations.
// Item and structure utilities
function getItem(store: StructStore, id: ID): Item;
function getItemCleanStart(transaction: Transaction, id: ID): Item;
function getItemCleanEnd(transaction: Transaction, id: ID): Item;
function isDeleted(deleteSet: DeleteSet, id: ID): boolean;
function isParentOf(parent: AbstractType<any>, child: Item): boolean;
// ID utilities
function createID(client: number, clock: number): ID;
function compareIDs(a: ID | null, b: ID | null): boolean;
function getState(store: StructStore, client: number): number;
// Delete set utilities
function createDeleteSet(): DeleteSet;
function createDeleteSetFromStructStore(store: StructStore): DeleteSet;
function equalDeleteSets(ds1: DeleteSet, ds2: DeleteSet): boolean;
function mergeDeleteSets(deleteSets: Array<DeleteSet>): DeleteSet;
// Advanced operations
function transact<T>(doc: Doc, f: (transaction: Transaction) => T, origin?: any): T;
function tryGc(deleteSet: DeleteSet, store: StructStore, gcFilter: Function): void;
function cleanupYTextFormatting(type: YText): number;
function findRootTypeKey(type: AbstractType<any>): string;
function getTypeChildren(type: AbstractType<any>): Array<AbstractType<any>>;interface ID {
readonly client: number;
readonly clock: number;
}
interface DeleteSet {
readonly clients: Map<number, Array<DeleteItem>>;
}
interface TextAttributes {
[key: string]: any;
}
interface UpdateDecoder {
readUint8(): number;
readUint32(): number;
readVarUint(): number;
readVarString(): string;
}
interface UpdateEncoder {
writeUint8(value: number): void;
writeUint32(value: number): void;
writeVarUint(value: number): void;
writeVarString(value: string): void;
toUint8Array(): Uint8Array;
}
interface DeleteItem {
clock: number;
len: number;
}
interface StructStore {
clients: Map<number, Array<AbstractStruct>>;
pendingStructs: null | { missing: Map<number, number>; update: Uint8Array };
}
interface Item {
readonly id: ID;
readonly left: Item | null;
readonly right: Item | null;
readonly origin: ID | null;
readonly rightOrigin: ID | null;
readonly parent: AbstractType<any> | ID | null;
readonly parentSub: string | null;
readonly content: AbstractContent;
}
// Abstract base classes
abstract class AbstractType<EventType> {
observe(f: (event: EventType, transaction: Transaction) => void): void;
unobserve(f: (event: EventType, transaction: Transaction) => void): void;
observeDeep(f: (events: Array<YEvent<any>>, transaction: Transaction) => void): void;
clone(): AbstractType<EventType>;
toJSON(): any;
}
abstract class AbstractStruct {
readonly id: ID;
readonly length: number;
}
class YXmlTreeWalker {
filter: (node: AbstractType<any>) => boolean;
nextNode(): AbstractType<any> | null;
}