Model API
The Model API provides high-level model operations, transaction DSL, and operation definitions.
Transaction DSL
transaction(editor, operations)
Creates a transaction builder.
Parameters:
editor: Editor- Editor instanceoperations: (TransactionOperation | TransactionOperation[] | OpFunction)[]- Operations to execute
Returns:
TransactionBuilder- Transaction builder instance
Example:
import { transaction, control, insertText } from '@barocss/model';
const result = await transaction(editor, [
...control('text-1', [
insertText(5, 'Hello')
])
]).commit();
TransactionBuilder
Transaction builder interface.
commit(): Promise<TransactionResult>
Commits the transaction.
Returns:
Promise<TransactionResult>- Transaction result
Behavior:
- Triggers
onBeforeTransactionhooks (extensions can intercept/modify) - Executes all operations
- Commits to DataStore
- Adds to history
- Emits
editor:content.changeevent - Triggers
onTransactionhooks
Example:
const result = await transaction(editor, operations).commit();
if (result.success) {
console.log('Transaction succeeded:', result.transactionId);
} else {
console.error('Transaction failed:', result.errors);
}
TransactionResult
interface TransactionResult {
success: boolean; // Whether transaction succeeded
errors: string[]; // Array of error messages
data?: any; // Transaction data
transactionId?: string; // Transaction ID
operations?: TransactionOperation[]; // Executed operations
selectionBefore?: ModelSelection | null; // Selection before transaction
selectionAfter?: ModelSelection | null; // Selection after transaction
}
Control Function
control(target, actions)
Injects nodeId into operation payloads.
Parameters:
target: HandleOrId- Target node IDactions: Array<{ type: string; payload?: any }>- Operations to inject nodeId into
Returns:
TransactionOperation[]- Operations with injected nodeId
Example:
control('text-1', [
insertText(5, 'Hello'),
toggleMark('bold', [0, 10])
])
// Becomes:
[
{ type: 'insertText', payload: { nodeId: 'text-1', pos: 5, text: 'Hello' } },
{ type: 'toggleMark', payload: { nodeId: 'text-1', markType: 'bold', range: [0, 10] } }
]
Node Creation Helpers
node(stype, attributes?, content?)
Creates a container node.
Parameters:
stype: string- Node typeattributes?: Record<string, any>- Optional attributescontent?: INode[]- Optional child nodes
Returns:
INode- Node object
Example:
const paragraph = node('paragraph', {}, [
textNode('inline-text', 'Hello')
]);
textNode(stype, text, marks?, attributes?)
Creates a text node.
Overloads:
textNode(stype: string, text: string): INode
textNode(stype: string, text: string, marks: MarkDescriptor[]): INode
textNode(stype: string, text: string, attributes: Record<string, any>): INode
textNode(stype: string, text: string, marks: MarkDescriptor[], attributes: Record<string, any>): INode
Parameters:
stype: string- Node typetext: string- Text contentmarks?: MarkDescriptor[]- Optional marksattributes?: Record<string, any>- Optional attributes
Returns:
INode- Text node object
Example:
// Basic text node
const text = textNode('inline-text', 'Hello');
// Text node with marks
const boldText = textNode('inline-text', 'Hello', [
mark('bold', { range: [0, 5] })
]);
// Text node with attributes
const styledText = textNode('inline-text', 'Hello', {
color: 'red'
});
// Text node with marks and attributes
const richText = textNode('inline-text', 'Hello', [
mark('bold', { range: [0, 5] })
], {
color: 'red'
});
mark(stype, attrs?)
Creates a mark descriptor.
Parameters:
stype: string- Mark typeattrs?: Record<string, any>- Optional attributes (can includerange)
Returns:
MarkDescriptor- Mark descriptor
Example:
// Basic mark
const boldMark = mark('bold');
// Mark with attributes
const linkMark = mark('link', {
href: 'https://example.com',
target: '_blank'
});
// Mark with range
const boldRange = mark('bold', {
range: [0, 5]
});
Operation Definition
defineOperation(name, executor, selectionMapper?)
Defines a custom operation.
Parameters:
name: string- Operation nameexecutor: (operation: T, context: TransactionContext) => Promise<void | INode>- Operation executorselectionMapper?: (operation: T, context: TransactionContext) => any- Optional selection mapper
Example:
import { defineOperation } from '@barocss/model';
import type { TransactionContext } from '@barocss/model';
defineOperation('customOp', async (operation, context) => {
const { nodeId, data } = operation.payload;
// Access DataStore
const node = context.dataStore.getNode(nodeId);
// Perform operation
context.dataStore.updateNode(nodeId, data);
// Map selection if needed
if (context.selection?.current) {
// Update selection
}
// Return result with inverse
return {
ok: true,
data: context.dataStore.getNode(nodeId),
inverse: { type: 'customOp', payload: { nodeId, data: oldData } }
};
});
See: Custom Operations Guide for detailed examples.
defineOperationDSL(builder, options?)
Defines a DSL helper for an operation.
Parameters:
builder: BuilderFn<Args, P>- Builder functionoptions?: DefineOperationDSLOptions- Optional options
Returns:
- DSL helper function
Example:
import { defineOperationDSL } from '@barocss/model';
export const customOp = defineOperationDSL(
(nodeId: string, data: Record<string, any>) => ({
type: 'customOp',
payload: { nodeId, data }
}),
{ atom: false, category: 'custom' }
);
// Usage
const ops = control('node-1', [
customOp({ key: 'value' })
]);
Options:
interface DefineOperationDSLOptions {
atom?: boolean; // Whether operation is atomic
category?: string; // Operation category
}
See: Model Operation DSL API for complete DSL helper reference.
Functional DSL
op(operationFn)
Creates a functional operation.
Parameters:
operationFn: (context: TransactionContext) => OpResult | void | Promise<OpResult | void>- Operation function
Returns:
OpFunction- Functional operation
Example:
import { op } from '@barocss/model';
const result = await transaction(editor, [
op(async (context) => {
// Custom logic
const node = context.dataStore.getNode('node-1');
context.dataStore.updateNode('node-1', { text: 'Updated' });
return {
success: true,
data: context.dataStore.getNode('node-1'),
inverse: { type: 'update', payload: { nodeId: 'node-1', data: node } }
};
})
]).commit();
OpResult
interface OpResult {
success: boolean;
data?: any;
error?: string;
inverse?: TransactionOperation; // Inverse operation for undo
}
Transaction Context
Operations receive a TransactionContext:
interface TransactionContext {
dataStore: DataStore; // DataStore instance
selectionManager: SelectionManager; // SelectionManager instance
selection: SelectionContext; // Selection context (before/current)
schema?: Schema; // Active schema
}
SelectionContext
class SelectionContext {
before: ModelSelection | null; // Selection before transaction
current: ModelSelection | null; // Current selection (mutable)
}
Example:
defineOperation('myOp', async (operation, context) => {
// Access selection
const before = context.selection.before;
const current = context.selection.current;
// Update selection
if (context.selection.current) {
context.selection.current.startOffset += 5;
}
// Access DataStore
const node = context.dataStore.getNode('node-1');
// Access schema
const nodeType = context.schema?.getNodeType('paragraph');
});
Transaction Manager
TransactionManager Class
Manages transaction execution.
Constructor
new TransactionManager(editor: Editor)
Parameters:
editor: Editor- Editor instance
Methods
execute(operations: (TransactionOperation | OpFunction)[]): Promise<TransactionResult>
Executes a transaction.
Parameters:
operations: (TransactionOperation | OpFunction)[]- Operations to execute
Returns:
Promise<TransactionResult>- Transaction result
Behavior:
- Acquires global lock
- Begins transaction
- Starts DataStore overlay
- Executes all operations
- Ends overlay and commits
- Adds to history
- Emits events
- Calls after hooks
Note: Usually called via transaction().commit(), not directly.
setSchema(schema: Schema): void
Sets the active schema.
Parameters:
schema: Schema- Schema instance
Position Calculator
Calculates positions in the document.
PositionCalculator Class
import { PositionCalculator } from '@barocss/model';
Constructor
new PositionCalculator(dataStore: DataStore)
Parameters:
dataStore: DataStore- DataStore instance
Methods
calculateAbsolutePosition(nodeId: string, offset: number): number
Calculates absolute position from node ID and offset.
Parameters:
nodeId: string- Node IDoffset: number- Offset in node
Returns:
number- Absolute position
resolveAbsolutePosition(absolutePos: number): { nodeId: string; offset: number } | null
Resolves absolute position to node ID and offset.
Parameters:
absolutePos: number- Absolute position
Returns:
{ nodeId: string; offset: number } | null- Node ID and offset ornull
Operation Registry
Global Operation Registry
Operations are registered in a global registry.
globalOperationRegistry
Global operation registry instance.
Methods:
register(name: string, definition: OperationDefinition): voidget(name: string): OperationDefinition | undefinedgetAll(): Map<string, OperationDefinition>clear(): void
Example:
import { globalOperationRegistry } from '@barocss/model';
// Get all operations
const allOps = globalOperationRegistry.getAll();
// Get specific operation
const insertTextOp = globalOperationRegistry.get('insertText');
DSL Operation Registry
DSL operations are registered separately.
getDefinedDSLOperations(): ReadonlyMap<string, { options?: DefineOperationDSLOptions }>
Gets all defined DSL operations.
Returns:
ReadonlyMap<string, { options?: DefineOperationDSLOptions }>- Map of DSL operations
Example:
import { getDefinedDSLOperations } from '@barocss/model';
const dslOps = getDefinedDSLOperations();
for (const [name, config] of dslOps) {
console.log(`DSL Operation: ${name}`, config);
}
Complete Examples
Example 1: Simple Transaction
import { transaction, control, insertText } from '@barocss/model';
const result = await transaction(editor, [
...control('text-1', [
insertText(5, 'Hello')
])
]).commit();
if (result.success) {
console.log('Text inserted successfully');
}
Example 2: Complex Transaction
import { transaction, control, insertText, toggleMark, create } from '@barocss/model';
const result = await transaction(editor, [
// Create new paragraph
create('paragraph', {}, [
{ stype: 'inline-text', text: 'New paragraph' }
]),
// Insert text and apply mark
...control('text-1', [
insertText(5, ' World'),
toggleMark('bold', [0, 11])
])
]).commit();
Example 3: Custom Operation
import { defineOperation, defineOperationDSL } from '@barocss/model';
// Define operation
defineOperation('highlight', async (operation, context) => {
const { nodeId, range, color } = operation.payload;
// Apply highlight
context.dataStore.mark.setMarks(nodeId, [
{
type: 'highlight',
range,
attrs: { color }
}
]);
return {
ok: true,
data: context.dataStore.getNode(nodeId),
inverse: { type: 'removeHighlight', payload: { nodeId, range } }
};
});
// Define DSL helper
export const highlight = defineOperationDSL(
(range: [number, number], color: string) => ({
type: 'highlight',
payload: { range, color }
})
);
// Use in transaction
const result = await transaction(editor, [
...control('text-1', [
highlight([0, 10], 'yellow')
])
]).commit();
Example 4: Functional Operation
import { transaction, op } from '@barocss/model';
const result = await transaction(editor, [
op(async (context) => {
// Complex multi-step operation
const node1 = context.dataStore.getNode('node-1');
const node2 = context.dataStore.getNode('node-2');
// Swap nodes
context.dataStore.content.moveNode('node-1', node2.parentId, 0);
context.dataStore.content.moveNode('node-2', node1.parentId, 0);
return {
success: true,
data: { swapped: true },
inverse: {
type: 'swapNodes',
payload: { node1: 'node-1', node2: 'node-2' }
}
};
})
]).commit();
Related
- Operations Overview - Understanding the operation hierarchy
- Model Operations API - Complete Model operation reference
- Model Operation DSL API - Complete DSL helper reference
- Custom Operations Guide - Creating custom operations
- DataStore Operations API - DataStore layer operations