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:
-
✅ Writing Commands (most common)
// In Extension command
const result = await transaction(editor, [
...control(selection.startNodeId, [
insertText(0, 'Hello'),
toggleMark('bold', [0, 5])
])
]).commit(); -
✅ Type Safety Needed
- DSL helpers are type-safe
- Compile-time checking
-
✅ Composability
- Easy to combine multiple operations
control()injects nodeId automatically
-
✅ Transaction DSL
- Natural fit for
transaction(editor, [ops])
- Natural fit for
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:
-
✅ Creating Custom Operations
defineOperation('customOp', async (operation, context) => {
// Access context.dataStore, context.selection
// Implement business logic
// Return result with inverse
}); -
✅ Complex Business Logic
- Need transaction context
- Need selection mapping
- Need inverse operations
-
✅ 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:
-
⚠️ Direct Access Needed (rare)
// Direct DataStore manipulation
const rangeOps = new RangeOperations(dataStore);
rangeOps.insertText(range, 'Hello'); -
⚠️ Performance Critical
- Need maximum performance
- Bypassing Model layer
-
⚠️ 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')
3. Combine Related Operations
// ✅ 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
| Task | DSL | Model | DataStore |
|---|---|---|---|
| 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', ...) | ❌ |
Related
- Operations Overview - Understanding the operation hierarchy
- DataStore Operations API - Complete DataStore operation reference
- Model Operations API - Complete Model operation reference
- Model Operation DSL API - Complete DSL helper reference
- Custom Operations Guide - Creating custom operations