Overview
Transactions group multiple operations into atomic units. Either all operations succeed, or none are applied. This ensures document consistency and enables undo/redo functionality.
Transaction Model
Atomic Operations
Group operations into transactions for atomicity:
class Transaction {
#operations = [];
#snapshot = null;
#editor = null;
constructor(editor) {
this.#editor = editor;
this.#snapshot = this.#editor.getDocument();
}
add(operation) {
this.#operations.push(operation);
}
async commit() {
// Validate all operations
for (const op of this.#operations) {
if (!this.#editor.canApply(op)) {
return false;
}
}
// Apply all operations atomically
try {
for (const op of this.#operations) {
this.#editor.applyOperation(op);
}
// Commit successful
return true;
} catch (error) {
// Rollback on error
this.rollback();
return false;
}
}
rollback() {
// Restore snapshot
this.#editor.setDocument(this.#snapshot);
this.#operations = [];
}
getOperations() {
return [...this.#operations];
}
}
// Usage
const transaction = editor.beginTransaction();
transaction.add({ type: 'insert', path: [0], content: 'Hello' });
transaction.add({ type: 'insert', path: [0], content: ' World' });
if (await transaction.commit()) {
console.log('Transaction committed');
} else {
console.log('Transaction failed, rolled back');
}Transaction Lifecycle
Complete transaction lifecycle:
class TransactionManager {
#activeTransactions = [];
beginTransaction(editor) {
const transaction = new Transaction(editor);
this.#activeTransactions.push(transaction);
return transaction;
}
async commitTransaction(transaction) {
const index = this.#activeTransactions.indexOf(transaction);
if (index === -1) {
throw new Error('Transaction not found');
}
const success = await transaction.commit();
if (success) {
this.#activeTransactions.splice(index, 1);
}
return success;
}
rollbackTransaction(transaction) {
const index = this.#activeTransactions.indexOf(transaction);
if (index === -1) {
throw new Error('Transaction not found');
}
transaction.rollback();
this.#activeTransactions.splice(index, 1);
}
rollbackAll() {
this.#activeTransactions.forEach(transaction => {
transaction.rollback();
});
this.#activeTransactions = [];
}
}Rollback Mechanism
Operation Inversion
Implement rollback using operation inversion:
class RollbackManager {
#history = [];
#inverseHistory = [];
apply(operation) {
// Apply operation
this.#applyOperation(operation);
// Generate inverse operation
const inverse = this.#inverse(operation);
// Store both
this.#history.push(operation);
this.#inverseHistory.push(inverse);
return inverse;
}
#inverse(operation) {
switch (operation.type) {
case 'insert':
return {
type: 'delete',
path: operation.path,
length: typeof operation.content === 'string'
? operation.content.length
: 1
};
case 'delete':
return {
type: 'insert',
path: operation.path,
content: operation.content // Restore deleted content
};
case 'update':
return {
type: 'update',
path: operation.path,
attrs: operation.previousAttrs // Restore previous attributes
};
}
}
undo() {
if (this.#inverseHistory.length === 0) {
return false;
}
const inverse = this.#inverseHistory.pop();
const original = this.#history.pop();
// Apply inverse
this.#applyOperation(inverse);
return true;
}
redo() {
if (this.#history.length === 0) {
return false;
}
// Re-apply last operation
const operation = this.#history[this.#history.length - 1];
this.#applyOperation(operation);
return true;
}
}Snapshot Restore
Alternative approach using snapshots:
class SnapshotManager {
#snapshots = [];
#currentIndex = -1;
createSnapshot(document) {
// Deep clone document
const snapshot = this.#deepClone(document);
// Remove old snapshots after current index
this.#snapshots = this.#snapshots.slice(0, this.#currentIndex + 1);
// Add new snapshot
this.#snapshots.push(snapshot);
this.#currentIndex = this.#snapshots.length - 1;
return this.#currentIndex;
}
restoreSnapshot(index) {
if (index < 0 || index >= this.#snapshots.length) {
return false;
}
const snapshot = this.#snapshots[index];
this.#currentIndex = index;
// Restore document
return this.#deepClone(snapshot);
}
undo() {
if (this.#currentIndex > 0) {
this.#currentIndex--;
return this.#snapshots[this.#currentIndex];
}
return null;
}
redo() {
if (this.#currentIndex < this.#snapshots.length - 1) {
this.#currentIndex++;
return this.#snapshots[this.#currentIndex];
}
return null;
}
#deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
}
}Nested Transactions
Support nested transactions:
class NestedTransaction extends Transaction {
#parent = null;
#children = [];
constructor(editor, parent = null) {
super(editor);
this.#parent = parent;
if (parent) {
parent.#children.push(this);
}
}
async commit() {
// Commit all child transactions first
for (const child of this.#children) {
const success = await child.commit();
if (!success) {
return false;
}
}
// Then commit this transaction
return await super.commit();
}
rollback() {
// Rollback all child transactions
this.#children.forEach(child => child.rollback());
// Then rollback this transaction
super.rollback();
}
}
// Usage
const outer = editor.beginTransaction();
outer.add({ type: 'insert', path: [0], content: 'Outer' });
const inner = editor.beginTransaction(outer);
inner.add({ type: 'insert', path: [1], content: 'Inner' });
// Committing outer will also commit inner
await outer.commit();Transaction Isolation
Ensure transaction isolation:
class IsolatedTransaction extends Transaction {
#isolatedDocument = null;
constructor(editor) {
super(editor);
// Create isolated copy of document
this.#isolatedDocument = this.#deepClone(editor.getDocument());
}
add(operation) {
// Apply to isolated document
this.#isolatedDocument = this.#applyToDocument(
this.#isolatedDocument,
operation
);
this.#operations.push(operation);
}
async commit() {
// Validate on isolated document
const valid = this.#validate(this.#isolatedDocument);
if (!valid) {
return false;
}
// Apply all operations to real document
for (const op of this.#operations) {
this.#editor.applyOperation(op);
}
return true;
}
// Read operations work on isolated document
read(path) {
return this.#getNodeAtPath(this.#isolatedDocument, path);
}
}Related Pages
Model & Schema
Overview of model and schema concepts
Collaborative Editing
Operational Transformation and CRDTs
Schema Migration
Version management and migration strategies
Indexing Strategies
Document, position, and content indexing
Advanced Validation
Recursive validation and performance optimization
Node Types
Comprehensive node type examples