Skip to main content
Reference status

This page predates the current package split. Use the current package guides for checked installation, public imports, and onboarding examples. The detailed examples below have not all been revalidated.

Operation Selection Guide

This guide helps you choose the right operation for your needs based on the operation hierarchy.

Quick Decision Tree​

Need to modify document?
│
├─ Writing a Command?
│ └─ Use DSL Layer ✅
│ └─ transaction(editor, [control(...), ...])
│
├─ Creating Custom Operation?
│ └─ Use Model Layer ✅
│ └─ defineOperation('customOp', ...)
│
└─ Direct DataStore Access?
└─ Use DataStore Layer ⚠️ (Rare)
└─ dataStore.range.insertText(...)

When to Use DSL Layer​

Use DSL operations when:

  1. ✅ Writing Commands (most common)

    // In Extension command
    const result = await transaction(editor, [
    ...control(selection.startNodeId, [
    insertText(0, 'Hello'),
    toggleMark('bold', [0, 5])
    ])
    ]).commit();
  2. ✅ Type Safety Needed

    • DSL helpers are type-safe
    • Compile-time checking
  3. ✅ Composability

    • Easy to combine multiple operations
    • control() injects nodeId automatically
  4. ✅ Transaction DSL

    • Natural fit for transaction(editor, [ops])

Example:

// ✅ Good: Use DSL
const result = await transaction(editor, [
...control('text-1', [
insertText(5, 'Hello'),
toggleMark('bold', [0, 10])
])
]).commit();

When to Use Model Layer​

Use Model operations when:

  1. ✅ Creating Custom Operations

    defineOperation('customOp', async (operation, context) => {
    // Access context.dataStore, context.selection
    // Implement business logic
    // Return result with inverse
    });
  2. ✅ Complex Business Logic

    • Need transaction context
    • Need selection mapping
    • Need inverse operations
  3. ✅ Extending Functionality

    • Adding new operation types
    • Customizing existing operations

Example:

// ✅ Good: Custom operation
defineOperation('highlightSelection', async (operation, context) => {
const selection = context.selection.current;
if (!selection) return { ok: false };

// Complex logic with selection mapping
// ...

return { ok: true, data: result, inverse: {...} };
});

When to Use DataStore Layer​

Use DataStore operations when:

  1. ⚠️ Direct Access Needed (rare)

    // Direct DataStore manipulation
    const rangeOps = new RangeOperations(dataStore);
    rangeOps.insertText(range, 'Hello');
  2. ⚠️ Performance Critical

    • Need maximum performance
    • Bypassing Model layer
  3. ⚠️ Low-Level Control

    • Need overlay/lock control
    • Custom transaction management

Note: Usually not needed in extensions. Use DSL or Model layers instead.

Example:

// ⚠️ Rare: Direct DataStore access
dataStore.begin();
dataStore.range.insertText(range, 'Hello');
dataStore.commit();

Operation Categories​

Text Operations​

DSL: insertText, deleteTextRange, replaceText

  • ✅ Use for: Text insertion, deletion, replacement
  • ✅ Best for: Commands that modify text

Model: insertText, deleteTextRange, replaceText

  • ✅ Use for: Custom text operations
  • ✅ Best for: Complex text manipulation logic

DataStore: RangeOperations.insertText(), RangeOperations.deleteText()

  • ⚠️ Use for: Direct text manipulation (rare)

Node Operations​

DSL: create, delete, update

  • ✅ Use for: Node creation, deletion, updates
  • ✅ Best for: Commands that modify nodes

Model: create, delete, update

  • ✅ Use for: Custom node operations
  • ✅ Best for: Complex node manipulation

DataStore: CoreOperations.setNode(), CoreOperations.deleteNode()

  • ⚠️ Use for: Direct node manipulation (rare)

Content Operations​

DSL: addChild, removeChild, moveNode, reorderChildren

  • ✅ Use for: Parent-child relationships
  • ✅ Best for: Commands that modify structure

Model: addChild, removeChild, moveNode, reorderChildren

  • ✅ Use for: Custom content operations
  • ✅ Best for: Complex structure manipulation

DataStore: ContentOperations.addChild(), ContentOperations.moveNode()

  • ⚠️ Use for: Direct content manipulation (rare)

Mark Operations​

DSL: applyMark, removeMark, toggleMark, updateMark

  • ✅ Use for: Mark management
  • ✅ Best for: Formatting commands

Model: applyMark, removeMark, toggleMark, updateMark

  • ✅ Use for: Custom mark operations
  • ✅ Best for: Complex mark logic

DataStore: MarkOperations.setMarks(), MarkOperations.toggleMark()

  • ⚠️ Use for: Direct mark manipulation (rare)

Common Patterns​

Pattern 1: Simple Text Insertion​

// ✅ Use DSL
const result = await transaction(editor, [
...control(selection.startNodeId, [
insertText(selection.startOffset, 'Hello')
])
]).commit();

Pattern 2: Format Selection​

// ✅ Use DSL
const result = await transaction(editor, [
...control(selection.startNodeId, [
toggleMark('bold', [selection.startOffset, selection.endOffset])
])
]).commit();

Pattern 3: Create Node Structure​

// ✅ Use DSL
const result = await transaction(editor, [
create('paragraph', {}, [
{ stype: 'inline-text', text: 'Hello' }
])
]).commit();

Pattern 4: Complex Multi-Operation​

// ✅ Use DSL (composable)
const result = await transaction(editor, [
// Create node
create('paragraph', {}, [
{ stype: 'inline-text', text: 'Hello' }
]),

// Modify text
...control('text-1', [
insertText(5, ' World'),
toggleMark('bold', [0, 11])
]),

// Move node
...control('paragraph-1', [
moveNode('new-parent-1', 0)
])
]).commit();

Pattern 5: Custom Operation​

// ✅ Use Model Layer
defineOperation('customHighlight', async (operation, context) => {
const { nodeId, range } = operation.payload;

// Complex logic
const node = context.dataStore.getNode(nodeId);
// ... custom logic ...

// Call DataStore
context.dataStore.mark.setMarks(nodeId, marks);

// Map selection
if (context.selection?.current) {
// Update selection
}

return { ok: true, data: result, inverse: {...} };
});

// Use in DSL
const highlight = defineOperationDSL(
(nodeId: string, range: [number, number]) => ({
type: 'customHighlight',
payload: { nodeId, range }
})
);

// Use in transaction
const result = await transaction(editor, [
...control('text-1', [
highlight([0, 5])
])
]).commit();

Operation Flow Diagram​


Best Practices​

1. Prefer DSL Layer​

// ✅ Good: Use DSL
const result = await transaction(editor, [
...control(nodeId, [
insertText(5, 'Hello')
])
]).commit();

// ❌ Avoid: Direct DataStore
dataStore.range.insertText(range, 'Hello');

2. Use control() for Node-Scoped Operations​

// ✅ Good: control() injects nodeId
control('text-1', [
insertText(5, 'Hello'),
toggleMark('bold', [0, 10])
])

// ❌ Avoid: Manual nodeId
insertText('text-1', 5, 'Hello')
// ✅ Good: Multiple operations in one control
control('text-1', [
insertText(5, 'Hello'),
toggleMark('bold', [0, 10])
])

// ❌ Avoid: Separate transactions
await transaction(editor, [control('text-1', [insertText(5, 'Hello')])]).commit();
await transaction(editor, [control('text-1', [toggleMark('bold', [0, 10])])]).commit();

4. Use Appropriate Layer​

// ✅ Good: DSL for commands
const result = await transaction(editor, [
...control(nodeId, [insertText(5, 'Hello')])
]).commit();

// ✅ Good: Model for custom operations
defineOperation('customOp', async (operation, context) => {
// Custom logic
});

// ⚠️ Rare: DataStore for direct access
dataStore.range.insertText(range, 'Hello');

Operation Comparison​

TaskDSLModelDataStore
Insert text✅ insertText(5, 'Hello')✅ defineOperation('insertText', ...)⚠️ range.insertText()
Delete text✅ deleteTextRange(0, 5)✅ defineOperation('deleteTextRange', ...)⚠️ range.deleteText()
Create node✅ create('paragraph', ...)✅ defineOperation('create', ...)⚠️ core.setNode()
Update node✅ update({ text: 'New' })✅ defineOperation('update', ...)⚠️ core.updateNode()
Add mark✅ applyMark('bold', [0, 5])✅ defineOperation('applyMark', ...)⚠️ mark.setMarks()
Custom logic❌✅ defineOperation('custom', ...)❌