or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

changes.mdcharacter-utils.mdeditor-state.mdextensions.mdindex.mdrange-sets.mdselection.mdtext.mdtransactions.md
tile.json

transactions.mddocs/

Transaction System

Transaction-based state updates that bundle document changes, selection updates, and side effects into atomic operations.

Capabilities

Transaction Class

Represents a state transition with document changes, selection updates, and side effects.

/**
 * A transaction represents a state change. It is the result of calling EditorState.update.
 */
class Transaction {
  /** The state this transaction was created from */
  readonly startState: EditorState;
  
  /** The state produced by this transaction (lazily computed) */
  readonly state: EditorState;
  
  /** The document changes made by this transaction */
  readonly changes: ChangeSet;
  
  /** The new selection produced by this transaction */
  readonly selection: EditorSelection;
  
  /** State effects attached to this transaction */
  readonly effects: readonly StateEffect<any>[];
  
  /** Annotations attached to this transaction */
  readonly annotations: readonly Annotation<any>[];
  
  /** Whether the document changed */
  readonly docChanged: boolean;
  
  /** Whether the configuration was reconfigured */
  readonly reconfigured: boolean;
  
  /** Whether this transaction should scroll the selection into view */
  readonly scrollIntoView: boolean;
  
  /** The new document produced by the transaction (computed on demand) */
  readonly newDoc: Text;
  
  /** The new selection produced by the transaction (computed on demand) */
  readonly newSelection: EditorSelection;
  
  /** Check whether this transaction has a given annotation */
  annotation<T>(type: AnnotationType<T>): T | undefined;
  
  /** Returns true if the transaction has a user event annotation matching the given event */
  isUserEvent(event: string): boolean;
}

Usage Examples:

import { EditorState, EditorSelection } from "@codemirror/state";

const state = EditorState.create({
  doc: "Hello, world!"
});

// Create a transaction with document changes
const transaction = state.update({
  changes: { from: 7, to: 12, insert: "CodeMirror" },
  selection: EditorSelection.single(18)
});

console.log(transaction.docChanged); // true
console.log(transaction.changes.length); // 13 (original doc length)
console.log(transaction.changes.newLength); // 18 (new doc length)
console.log(transaction.startState.doc.toString()); // "Hello, world!"
console.log(transaction.state.doc.toString()); // "Hello, CodeMirror!"

TransactionSpec Interface

Specification for creating transactions.

/**
 * Describes a Transaction when calling the EditorState.update method.
 */
interface TransactionSpec {
  /** The changes to the document made by this transaction */
  changes?: ChangeSpec;
  
  /** When set, this transaction explicitly updates the selection */
  selection?: EditorSelection | {anchor: number, head?: number};
  
  /** Attach state effects to this transaction */
  effects?: StateEffect<any> | readonly StateEffect<any>[];
  
  /** Set annotations for this transaction */
  annotations?: Annotation<any> | readonly Annotation<any>[];
  
  /** Shorthand for annotations: Transaction.userEvent.of(...) */
  userEvent?: string;
  
  /** When set to true, the transaction is marked as needing to scroll the current selection into view */
  scrollIntoView?: boolean;
  
  /** By default, transactions can be modified by change filters and transaction filters */
  filter?: boolean;
  
  /** When a spec has sequential set to true, its changes are mapped through previous specs */
  sequential?: boolean;
}

Usage Examples:

// Basic change transaction
const simpleChange: TransactionSpec = {
  changes: { from: 0, to: 5, insert: "Hi" }
};

// Transaction with selection update
const changeWithSelection: TransactionSpec = {
  changes: { from: 0, to: 5, insert: "Hello" },
  selection: EditorSelection.single(5),
  scrollIntoView: true
};

// Transaction with effects and annotations
const complexTransaction: TransactionSpec = {
  changes: { from: 0, insert: "// " },
  effects: [SomeStateEffect.of(someValue)],
  annotations: [Transaction.userEvent.of("input.comment")],
  userEvent: "input.type"
};

// Apply the transaction
const newState = state.update(simpleChange).state;

Annotation System

System for attaching metadata to transactions.

/**
 * Annotations are tagged values that are used to add metadata to transactions
 */
class Annotation<T> {
  /** Define a new type of annotation */
  static define<T>(): AnnotationType<T>;
  
  /** The annotation type */
  readonly type: AnnotationType<T>;
  
  /** The value of this annotation */
  readonly value: T;
}

/**
 * Marker that identifies a type of annotation
 */
class AnnotationType<T> {
  /** Create an instance of this annotation */
  of(value: T): Annotation<T>;
}

Usage Examples:

// Define custom annotation types
const MyAnnotation = Annotation.define<string>();
const CounterAnnotation = Annotation.define<number>();

// Create annotations
const myAnnotation = MyAnnotation.of("custom data");
const counterAnnotation = CounterAnnotation.of(42);

// Use in transaction
const transaction = state.update({
  changes: { from: 0, insert: "text" },
  annotations: [myAnnotation, counterAnnotation]
});

// Read annotations from transaction
const customData = transaction.annotation(MyAnnotation); // "custom data"
const counter = transaction.annotation(CounterAnnotation); // 42

State Effects

System for representing side effects that should be applied alongside document changes.

/**
 * State effects can be used to represent additional effects associated with a transaction
 */
class StateEffect<Value> {
  /** Define a new effect type */
  static define<Value = null>(spec?: StateEffectSpec<Value>): StateEffectType<Value>;
  
  /** Map an array of effects through a change set */
  static mapEffects(effects: readonly StateEffect<any>[], mapping: ChangeDesc): StateEffect<any>[];
  
  /** This effect can be used to reconfigure the root extensions of the editor */
  static reconfigure: StateEffectType<Extension>;
  
  /** Append extensions to the top-level configuration of the editor */
  static appendConfig: StateEffectType<Extension>;
  
  /** The effect type */
  readonly type: StateEffectType<Value>;
  
  /** The value of this effect */
  readonly value: Value;
  
  /** Map this effect through a position mapping */
  map(mapping: ChangeDesc): StateEffect<Value> | undefined;
  
  /** Tells you whether this effect object is of a given type */
  is<T>(type: StateEffectType<T>): this is StateEffect<T>;
}

/**
 * Representation of a type of state effect
 */
class StateEffectType<Value> {
  /** Create a state effect instance of this type */
  of(value: Value): StateEffect<Value>;
}

interface StateEffectSpec<Value> {
  /** Provides a way to map an effect through a position mapping */
  map?: (value: Value, mapping: ChangeDesc) => Value | undefined;
}

Usage Examples:

// Define custom effect types
const HighlightEffect = StateEffect.define<{from: number, to: number}>();
const LogEffect = StateEffect.define<string>();

// Create effects
const highlight = HighlightEffect.of({from: 5, to: 10});
const log = LogEffect.of("User performed action");

// Use effects in transaction
const transaction = state.update({
  changes: { from: 0, insert: "New " },
  effects: [highlight, log]
});

// Check for specific effects
for (const effect of transaction.effects) {
  if (effect.is(HighlightEffect)) {
    console.log("Highlight:", effect.value); // {from: 5, to: 10}
  }
  if (effect.is(LogEffect)) {
    console.log("Log:", effect.value); // "User performed action"  
  }
}

// Built-in reconfiguration effect
const reconfigureEffect = StateEffect.reconfigure.of([someExtension]);
const reconfigureTransaction = state.update({
  effects: reconfigureEffect
});

Built-in Annotations

Standard annotation types provided by the system.

/**
 * Built-in annotation types available on the Transaction class
 */
class Transaction {
  /** Annotation that stores a timestamp */
  static time: AnnotationType<number>;
  
  /** Annotation that holds a user event string */
  static userEvent: AnnotationType<string>;
  
  /** Annotation that indicates whether a transaction should be added to history */
  static addToHistory: AnnotationType<boolean>;
  
  /** Annotation that indicates whether a transaction is from a remote source */
  static remote: AnnotationType<boolean>;
}

Usage Examples:

// Add timestamp to transaction
const timedTransaction = state.update({
  changes: { from: 0, insert: "text" },
  annotations: Transaction.time.of(Date.now())
});

// Mark user event
const userTransaction = state.update({
  changes: { from: 0, to: 4, insert: "" },
  annotations: Transaction.userEvent.of("delete.selection")
});

// Control history behavior
const historyTransaction = state.update({
  changes: { from: 0, insert: "auto-save: " },
  annotations: [
    Transaction.addToHistory.of(false), // Don't add to undo history
    Transaction.userEvent.of("auto-save")
  ]
});

// Mark as remote transaction
const remoteTransaction = state.update({
  changes: { from: 0, insert: "remote change" },
  annotations: Transaction.remote.of(true)
});

Transaction Filtering

System for intercepting and modifying transactions before they are applied.

/**
 * Function type for change filters
 */
type ChangeFilter = (tr: Transaction) => boolean | readonly number[];

/**
 * Function type for transaction filters  
 */
type TransactionFilter = (tr: Transaction) => TransactionSpec | readonly TransactionSpec[];

/**
 * Function type for transaction extenders
 */
type TransactionExtender = (tr: Transaction) => Pick<TransactionSpec, "effects" | "annotations"> | null;

Usage Examples:

// Define a change filter that prevents certain changes
const preventDeleteFilter: ChangeFilter = (tr) => {
  // Return false to block the entire transaction
  if (tr.annotation(Transaction.userEvent) === "delete.dangerous") {
    return false;
  }
  // Return true to allow all changes
  return true;
};

// Define a transaction filter that modifies transactions
const addTimestampFilter: TransactionFilter = (tr) => {
  // Return modified transaction spec
  return {
    ...tr,
    annotations: [
      ...tr.annotations,
      Transaction.time.of(Date.now())
    ]
  };
};

// Define a transaction extender that adds effects
const logExtender: TransactionExtender = (tr) => {
  if (tr.docChanged) {
    return {
      effects: [LogEffect.of("Document changed")]
    };
  }
  return null;
};

// Register filters via facets (typically done in extensions)
const extension = [
  EditorState.changeFilter.of(preventDeleteFilter),
  EditorState.transactionFilter.of(addTimestampFilter),
  EditorState.transactionExtender.of(logExtender)
];

Types

/**
 * Options for resolving transactions
 */
interface TransactionOptions {
  sequential?: boolean;
}

/**
 * Interface for objects that can receive transactions
 */
interface TransactionReceiver {
  dispatch(transaction: Transaction): void;
}