CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-yjs

Conflict-free Replicated Data Type (CRDT) framework for real-time collaborative applications

Overview
Eval results
Files

transaction-system.mddocs/

Transaction System

Atomic change batching for consistent updates and coordinated event handling. The transaction system ensures that multiple operations are executed atomically and that all observers are notified consistently.

Capabilities

Transaction Class

Represents a single atomic change context that groups multiple operations together.

/**
 * Transaction context for atomic operations
 */
class Transaction {
  /** Document this transaction operates on */
  readonly doc: Doc;
  
  /** Origin identifier for this transaction */
  readonly origin: any;
  
  /** Whether this transaction originated locally */
  readonly local: boolean;
  
  /** Set of items deleted in this transaction */
  readonly deleteSet: DeleteSet;
  
  /** Document state before transaction began */
  readonly beforeState: Map<number, number>;
  
  /** Document state after transaction completes */
  readonly afterState: Map<number, number>;
  
  /** Map of changed types to their changed keys/indices */
  readonly changed: Map<AbstractType<YEvent<any>>, Set<string | null>>;
  
  /** Map of parent types that will emit events */
  readonly changedParentTypes: Map<AbstractType<YEvent<any>>, Array<YEvent<any>>>;
  
  /** Transaction metadata */
  readonly meta: Map<any, any>;
  
  /** Subdocuments added during transaction */
  readonly subdocsAdded: Set<Doc>;
  
  /** Subdocuments removed during transaction */
  readonly subdocsRemoved: Set<Doc>;
  
  /** Subdocuments loaded during transaction */
  readonly subdocsLoaded: Set<Doc>;
}

Transaction Function

Execute operations within a transaction context.

/**
 * Execute a function within a transaction context
 * @param doc - Document to operate on
 * @param f - Function to execute within the transaction
 * @param origin - Optional origin identifier for the transaction
 * @returns Result of the function execution
 */
function transact<T>(doc: Doc, f: (transaction: Transaction) => T, origin?: any): T;

Usage Examples:

import * as Y from "yjs";

const doc = new Y.Doc();
const yarray = doc.getArray("items");
const ymap = doc.getMap("metadata");

// Basic transaction
Y.transact(doc, (transaction) => {
  yarray.push(["item1", "item2"]);
  ymap.set("count", yarray.length);
  
  // Access transaction properties
  console.log("Transaction origin:", transaction.origin);
  console.log("Changed types:", transaction.changed.size);
});

// Transaction with origin
Y.transact(doc, () => {
  yarray.delete(0, 1);
}, "user-delete");

// Transaction returning a value
const result = Y.transact(doc, () => {
  yarray.push(["new-item"]);
  return yarray.length;
}, "add-item");

Document Transaction Method

The Doc class also provides a transaction method for convenience.

/**
 * Execute a function within a transaction on this document
 * @param f - Function to execute within the transaction
 * @param origin - Optional origin identifier for the transaction
 * @returns Result of the function execution
 */
transact<T>(f: (transaction: Transaction) => T, origin?: any): T;

Usage Examples:

import * as Y from "yjs";

const doc = new Y.Doc();
const yarray = doc.getArray("items");

// Using doc.transact method
doc.transact(() => {
  yarray.push(["item1", "item2", "item3"]);
}, "batch-insert");

// Accessing transaction in observers
yarray.observe((event, transaction) => {
  console.log("Array changed in transaction:", transaction.origin);
  console.log("Local change:", transaction.local);
});

Transaction Lifecycle Events

Documents emit events during transaction lifecycle for advanced use cases.

/**
 * Transaction lifecycle events
 */
interface TransactionEvents {
  /** Fired before transaction begins */
  beforeTransaction: (transaction: Transaction, doc: Doc) => void;
  
  /** Fired after transaction completes but before observers */
  afterTransaction: (transaction: Transaction, doc: Doc) => void;
  
  /** Fired before observer functions are called */
  beforeObserverCalls: (transaction: Transaction, doc: Doc) => void;
  
  /** Fired after all observer functions complete */
  afterObserverCalls: (transaction: Transaction, doc: Doc) => void;
}

Usage Examples:

import * as Y from "yjs";

const doc = new Y.Doc();

// Listen to transaction lifecycle
doc.on("beforeTransaction", (transaction) => {
  console.log("Transaction starting:", transaction.origin);
  transaction.meta.set("startTime", Date.now());
});

doc.on("afterTransaction", (transaction) => {
  const duration = Date.now() - transaction.meta.get("startTime");
  console.log(`Transaction completed in ${duration}ms`);
});

doc.on("beforeObserverCalls", (transaction) => {
  console.log("About to call observers for:", Array.from(transaction.changed.keys()));
});

doc.on("afterObserverCalls", (transaction) => {
  console.log("All observers completed");
});

Advanced Transaction Patterns

Conditional Operations:

import * as Y from "yjs";

const doc = new Y.Doc();
const yarray = doc.getArray("items");

// Conditional operations within transaction
doc.transact((transaction) => {
  if (yarray.length === 0) {
    yarray.push(["initial-item"]);
  }
  
  // Check if this is a local change
  if (transaction.local) {
    yarray.push(["local-change-marker"]);
  }
}, "conditional-update");

Cross-Type Operations:

import * as Y from "yjs";

const doc = new Y.Doc();
const tasks = doc.getArray("tasks");
const metadata = doc.getMap("metadata");

// Coordinate changes across multiple types
doc.transact(() => {
  tasks.push([{ id: 1, title: "New Task", completed: false }]);
  metadata.set("lastTaskId", 1);
  metadata.set("taskCount", tasks.length);
  metadata.set("lastModified", new Date().toISOString());
}, "add-task");

Transaction Metadata:

import * as Y from "yjs";

const doc = new Y.Doc();
const ytext = doc.getText("document");

// Using transaction metadata
doc.on("beforeTransaction", (transaction) => {
  transaction.meta.set("user", "alice");
  transaction.meta.set("timestamp", Date.now());
});

ytext.observe((event, transaction) => {
  const user = transaction.meta.get("user");
  const timestamp = transaction.meta.get("timestamp");
  console.log(`Text changed by ${user} at ${timestamp}`);
});

doc.transact(() => {
  ytext.insert(0, "Hello World!");
}, "text-insert");

Nested Transactions:

import * as Y from "yjs";

const doc = new Y.Doc();
const yarray = doc.getArray("items");

// Note: Nested transact calls are flattened into single transaction
doc.transact(() => {
  yarray.push(["item1"]);
  
  // This doesn't create a nested transaction - operations are merged
  doc.transact(() => {
    yarray.push(["item2"]);
  });
  
  yarray.push(["item3"]);
}, "batch-operation");

// All three push operations happen in single transaction

Transaction Performance Considerations

Batching Operations:

import * as Y from "yjs";

const doc = new Y.Doc();
const yarray = doc.getArray("items");

// Inefficient: Multiple separate transactions
for (let i = 0; i < 1000; i++) {
  yarray.push([`item-${i}`]);
}

// Efficient: Single transaction for bulk operations
doc.transact(() => {
  for (let i = 0; i < 1000; i++) {
    yarray.push([`item-${i}`]);
  }
}, "bulk-insert");

Observer Optimization:

import * as Y from "yjs";

const doc = new Y.Doc();
const yarray = doc.getArray("items");

yarray.observe((event) => {
  // This fires once per transaction, not once per operation
  console.log("Array changed, operations:", event.changes.delta.length);
});

doc.transact(() => {
  yarray.push(["item1"]);
  yarray.push(["item2"]);
  yarray.push(["item3"]);
  // Observer fires once with all changes
});

Install with Tessl CLI

npx tessl i tessl/npm-yjs

docs

document-management.md

event-system.md

index.md

position-tracking.md

shared-data-types.md

snapshot-system.md

synchronization.md

transaction-system.md

undo-redo-system.md

xml-types.md

tile.json