DataStore Operations API
DataStore operations are low-level operations that directly manipulate the DataStore with overlay and lock support. They are organized into logical classes.
Overview
DataStore operations are grouped into the following classes:
- CoreOperations: Basic CRUD operations (setNode, getNode, deleteNode, etc.)
- ContentOperations: Parent-child relationship management (addChild, removeChild, moveNode, etc.)
- RangeOperations: Text range operations (insertText, deleteText, replaceText, etc.)
- MarkOperations: Mark management (normalizeMarks, addMark, removeMark, etc.)
- QueryOperations: Node querying and searching
- SplitMergeOperations: Node splitting and merging
- DecoratorOperations: Decorator management
- UtilityOperations: Utility functions (editable node detection, traversal, etc.)
- SerializationOperations: Serialization and deserialization
Usage Pattern
All DataStore operations are accessed through the DataStore instance:
import { DataStore } from '@barocss/datastore';
const dataStore = new DataStore();
// Operations are accessed via dataStore properties
dataStore.core.setNode(node);
dataStore.content.addChild(parentId, child);
dataStore.range.insertText(range, 'Hello');
dataStore.mark.normalizeMarks(nodeId);
dataStore.query.findNodesByType('paragraph');
dataStore.splitMerge.splitTextNode('text-1', 5);
dataStore.utility.getPreviousEditableNode('text-1');
Note: DataStore operations automatically use overlay when a transaction is active (via dataStore.begin()).
Operation Categories
When to Use Each Class
| Task | Use This Class | Example |
|---|---|---|
| Create/Read/Update/Delete nodes | CoreOperations | dataStore.core.setNode(), dataStore.core.getNode() |
| Manage parent-child relationships | ContentOperations | dataStore.content.addChild(), dataStore.content.moveNode() |
| Manipulate text ranges | RangeOperations | dataStore.range.insertText(), dataStore.range.deleteText() |
| Manage marks | MarkOperations | dataStore.mark.normalizeMarks(), dataStore.mark.toggleMark() |
| Query/search nodes | QueryOperations | dataStore.query.findNodesByType(), dataStore.query.searchText() |
| Split/merge nodes | SplitMergeOperations | dataStore.splitMerge.splitTextNode(), dataStore.splitMerge.mergeTextNodes() |
| Utility functions | UtilityOperations | dataStore.utility.getParent(), dataStore.utility.isLeafNode() |
| Decorator management | DecoratorOperations | See Decorators Guide |
| Serialization | SerializationOperations | See DataStore README |
CoreOperations
Basic CRUD operations for nodes.
setNode(node: INode, validate?: boolean): void
Creates or updates a node in DataStore.
Parameters:
node: Node to set (assignssidif missing)validate: Whether to validate against schema (default:true)
Behavior:
- Assigns
sidif missing usingDataStore.generateId() - Validates against schema if
validate=trueand schema exists - Converts object children in content to IDs recursively
- Overlay-aware: writes go to overlay if transaction active
- Emits
'create'or'update'operation event
Example:
dataStore.core.setNode({
sid: 'p1',
stype: 'paragraph',
text: 'Hello',
content: []
});
getNode(nodeId: string): INode | undefined
Retrieves a node by ID.
Parameters:
nodeId: Node ID (SID)
Returns:
INode | undefined: Node if found,undefinedotherwise
Read Path:
- Check
deletedNodeIds→ returnundefinedif deleted - Check
overlayNodes→ return overlay version if exists - Fallback to
baseNodes→ return base version
Example:
const node = dataStore.core.getNode('text-1');
deleteNode(nodeId: string): boolean
Deletes a node from DataStore.
Parameters:
nodeId: Node ID to delete
Returns:
boolean:trueif deleted,falseif node not found
Behavior:
- Cannot delete root node (throws error)
- Removes node from parent's content array
- Emits
'delete'operation event - Overlay-aware
Example:
const deleted = dataStore.core.deleteNode('node-1');
updateNode(nodeId: string, updates: Partial<INode>, validate?: boolean): { valid: boolean; errors: string[] } | null
Updates a node with partial changes.
Parameters:
nodeId: Node ID to updateupdates: Partial node data to applyvalidate: Whether to validate (default:true)
Returns:
- Validation result:
{ valid: boolean; errors: string[] }ornull
Behavior:
- Merges fields (attributes shallow-merge)
- Validates against schema if
validate=true - Overlay-aware: writes go to overlay if transaction active
- Emits
'update'operation event
Example:
const result = dataStore.core.updateNode('text-1', {
text: 'Updated text'
});
createNodeWithChildren(node: INode, schema?: Schema): INode
Creates a node with all its children recursively.
Parameters:
node: Node with nested children (objects)schema: Optional schema for validation
Returns:
INode: Created node with assigned IDs
Behavior:
- Recursively creates all child nodes
- Assigns IDs to all nodes
- Converts object children to ID arrays
- Overlay-aware
Example:
const root = dataStore.core.createNodeWithChildren({
stype: 'document',
content: [
{
stype: 'paragraph',
content: [
{ stype: 'inline-text', text: 'Hello' }
]
}
]
});
transformNode(nodeId: string, newType: string, newAttrs?: Record<string, any>): { valid: boolean; errors: string[]; newNodeId?: string }
Transforms a node to a different type.
Parameters:
nodeId: Node ID to transformnewType: New schema type (stype)newAttrs: Optional new attributes
Returns:
- Validation result with optional
newNodeId
Example:
const result = dataStore.core.transformNode('p1', 'heading', { level: 1 });
ContentOperations
Manages parent-child relationships and content ordering.
addChild(parentId: string, child: INode | string, position?: number): string
Adds a child node to a parent's content array.
Parameters:
parentId: Parent node IDchild: Child node (object) or child ID (string)position: Insert position (default: end)
Returns:
string: Child node ID
Behavior:
- Creates child if object provided (assigns ID if missing)
- Inserts child ID at position in parent's content array
- Updates child's
parentId - Emits
'update'for parent - Overlay-aware
Example:
// Add existing node
const childId = dataStore.content.addChild('parent-1', 'child-1', 0);
// Create and add new node
const newChildId = dataStore.content.addChild('parent-1', {
stype: 'paragraph',
text: 'New paragraph'
}, 0);
removeChild(parentId: string, childId: string): boolean
Removes a child from parent's content array.
Parameters:
parentId: Parent node IDchildId: Child node ID to remove
Returns:
boolean:trueif removed,falseif not found
Behavior:
- Removes child ID from parent's content array
- Clears child's
parentId - Emits
'update'for parent - Overlay-aware
Example:
const removed = dataStore.content.removeChild('parent-1', 'child-1');
moveNode(nodeId: string, newParentId: string, position?: number): boolean
Moves a node to a new parent.
Parameters:
nodeId: Node ID to movenewParentId: New parent node IDposition: Position in new parent (default: end)
Returns:
boolean:trueif moved,falseif failed
Behavior:
- Removes from old parent's content array
- Adds to new parent's content array at position
- Updates node's
parentId - Emits
'move'operation event - Overlay-aware
Example:
const moved = dataStore.content.moveNode('node-1', 'new-parent-1', 0);
reorderChildren(parentId: string, childIds: string[]): void
Reorders children in a parent's content array.
Parameters:
parentId: Parent node IDchildIds: New order of child IDs
Behavior:
- Updates parent's content array with new order
- Validates all child IDs exist
- Emits
'update'for parent - Overlay-aware
Example:
dataStore.content.reorderChildren('parent-1', ['child-3', 'child-1', 'child-2']);
RangeOperations
Text range operations for manipulating text content within ranges.
insertText(contentRange: ModelSelection, text: string): string
Inserts text at the specified range position.
Parameters:
contentRange: Range where to insert (start=end for insertion point)text: Text to insert
Returns:
string: Inserted text
Behavior:
- Inserts text at range position
- Updates marks to reflect insertion
- Uses
updateNode()(overlay-aware) - Handles single-node and multi-node ranges
Example:
const inserted = dataStore.range.insertText({
startNodeId: 'text-1',
startOffset: 5,
endNodeId: 'text-1',
endOffset: 5
}, 'Hello');
deleteText(contentRange: ModelSelection): string
Deletes text in the specified range.
Parameters:
contentRange: Range to delete
Returns:
string: Deleted text
Behavior:
- Fast-path for same-node deletion
- Multi-node: trims start node, clears middle nodes, trims end node
- Updates marks to reflect deletion (split/trim/shift)
- Uses
updateNode()(overlay-aware)
Example:
const deleted = dataStore.range.deleteText({
startNodeId: 'text-1',
startOffset: 0,
endNodeId: 'text-1',
endOffset: 5
});
replaceText(contentRange: ModelSelection, newText: string): string
Replaces text in the specified range.
Parameters:
contentRange: Range to replacenewText: New text
Returns:
string: Replaced text (old text)
Behavior:
- Deletes old text and inserts new text
- Updates marks appropriately
- Uses
updateNode()(overlay-aware)
Example:
const replaced = dataStore.range.replaceText({
startNodeId: 'text-1',
startOffset: 0,
endNodeId: 'text-1',
endOffset: 5
}, 'New text');
extractText(contentRange: ModelSelection): string
Extracts text from range without modifying nodes.
Parameters:
contentRange: Range to extract
Returns:
string: Extracted text
Behavior:
- Read-only operation (does not modify nodes)
- Uses DocumentIterator to traverse range
- Concatenates text from all nodes
Example:
const extracted = dataStore.range.extractText({
startNodeId: 'text-1',
startOffset: 0,
endNodeId: 'text-3',
endOffset: 10
});
applyMark(contentRange: ModelSelection, mark: IMark): IMark
Applies a mark to the specified range.
Parameters:
contentRange: Range to apply markmark: Mark to apply
Returns:
IMark: Applied mark
Behavior:
- Adds mark to nodes in range
- Merges with existing marks
- Updates marks via
updateNode()(overlay-aware)
Example:
const mark = dataStore.range.applyMark({
startNodeId: 'text-1',
startOffset: 0,
endNodeId: 'text-1',
endOffset: 5
}, {
type: 'bold',
range: [0, 5]
});
removeMark(contentRange: ModelSelection, markType: string): number
Removes a mark type from the specified range.
Parameters:
contentRange: Range to remove markmarkType: Mark type to remove
Returns:
number: Number of marks removed
Example:
const removed = dataStore.range.removeMark({
startNodeId: 'text-1',
startOffset: 0,
endNodeId: 'text-1',
endOffset: 5
}, 'bold');
toggleMark(contentRange: ModelSelection, markType: string, attrs?: Record<string, any>): void
Toggles a mark on/off in the specified range.
Parameters:
contentRange: Range to toggle markmarkType: Mark type to toggleattrs: Optional mark attributes
Behavior:
- Adds mark if not present
- Removes mark if present
- Uses
updateNode()(overlay-aware)
Example:
dataStore.range.toggleMark({
startNodeId: 'text-1',
startOffset: 0,
endNodeId: 'text-1',
endOffset: 5
}, 'bold');
MarkOperations
Mark management and normalization.
normalizeMarks(nodeId: string): void
Normalizes marks for a node.
Behavior:
- Assigns full-range to marks missing range
- Clamps ranges to
[0, textLength] - Drops empty/invalid ranges
- Removes duplicates by
(type, attrs, range) - Merges overlapping marks with identical
(type, attrs) - Sorts by start position
- Persists via
updateNode()(overlay-aware)
Example:
dataStore.mark.normalizeMarks('text-1');
normalizeAllMarks(): number
Normalizes marks for all nodes in DataStore.
Returns:
number: Number of nodes normalized
Example:
const count = dataStore.mark.normalizeAllMarks();
setMarks(nodeId: string, marks: IMark[], replace?: boolean): void
Sets marks for a node.
Parameters:
nodeId: Node IDmarks: Array of marks to setreplace: Whether to replace existing marks (default:true)
Behavior:
- Replaces or merges marks based on
replaceflag - Uses
updateNode()(overlay-aware)
Example:
dataStore.mark.setMarks('text-1', [
{ type: 'bold', range: [0, 5] },
{ type: 'italic', range: [2, 7] }
]);
removeMark(nodeId: string, markType: string, range?: [number, number]): number
Removes a mark type from a node.
Parameters:
nodeId: Node IDmarkType: Mark type to removerange: Optional range to remove from
Returns:
number: Number of marks removed
Example:
const removed = dataStore.mark.removeMark('text-1', 'bold', [0, 5]);
updateMark(nodeId: string, markType: string, attrs: Record<string, any>, range?: [number, number]): number
Updates attributes of a mark type.
Parameters:
nodeId: Node IDmarkType: Mark type to updateattrs: New attributesrange: Optional range to update
Returns:
number: Number of marks updated
Example:
const updated = dataStore.mark.updateMark('text-1', 'link', { href: 'https://example.com' });
toggleMark(nodeId: string, markType: string, attrs?: Record<string, any>, range?: [number, number]): void
Toggles a mark on/off for a node.
Parameters:
nodeId: Node IDmarkType: Mark type to toggleattrs: Optional mark attributesrange: Optional range to toggle
Example:
dataStore.mark.toggleMark('text-1', 'bold', undefined, [0, 5]);
QueryOperations
Node querying and searching.
findNodes(predicate: (node: INode) => boolean): INode[]
Finds nodes matching a predicate.
Parameters:
predicate: Function that returnstruefor matching nodes
Returns:
INode[]: Array of matching nodes
Example:
const nodes = dataStore.query.findNodes(node => node.stype === 'paragraph');
findNodesByType(stype: string): INode[]
Finds nodes by schema type.
Parameters:
stype: Schema type to find
Returns:
INode[]: Array of matching nodes
Example:
const paragraphs = dataStore.query.findNodesByType('paragraph');
findNodesByAttribute(key: string, value: any): INode[]
Finds nodes by attribute value.
Parameters:
key: Attribute keyvalue: Attribute value
Returns:
INode[]: Array of matching nodes
Example:
const headings = dataStore.query.findNodesByAttribute('level', 1);
findNodesByText(text: string): INode[]
Finds nodes containing text.
Parameters:
text: Text to search for
Returns:
INode[]: Array of matching nodes
Example:
const nodes = dataStore.query.findNodesByText('Hello');
getNodeChildren(nodeId: string): INode[]
Gets direct children of a node.
Parameters:
nodeId: Parent node ID
Returns:
INode[]: Array of child nodes
Example:
const children = dataStore.query.getNodeChildren('parent-1');
getNodeChildrenDeep(nodeId: string): INode[]
Gets all descendants of a node (recursive).
Parameters:
nodeId: Parent node ID
Returns:
INode[]: Array of all descendant nodes
Example:
const allDescendants = dataStore.query.getNodeChildrenDeep('parent-1');
SplitMergeOperations
Node splitting and merging operations.
splitTextNode(nodeId: string, splitPosition: number): string
Splits a text node at the specified position.
Parameters:
nodeId: Text node ID to splitsplitPosition: Position to split at
Returns:
string: ID of the new right node
Behavior:
- Creates new node with text after split position
- Updates original node with text before split position
- Preserves marks appropriately (splits mark ranges)
- Updates parent's content array
- Overlay-aware
Example:
const rightNodeId = dataStore.splitMerge.splitTextNode('text-1', 5);
// Original: 'Hello World' → Left: 'Hello', Right: ' World'
mergeTextNodes(leftNodeId: string, rightNodeId: string): string
Merges two adjacent text nodes.
Parameters:
leftNodeId: Left node IDrightNodeId: Right node ID
Returns:
string: ID of merged node (left node ID)
Behavior:
- Concatenates text from both nodes
- Merges marks (adjusts ranges)
- Removes right node
- Updates parent's content array
- Overlay-aware
Example:
const mergedId = dataStore.splitMerge.mergeTextNodes('text-1', 'text-2');
// Left: 'Hello', Right: ' World' → Merged: 'Hello World'
splitBlockNode(nodeId: string, splitPosition: number): string
Splits a block node at the specified position.
Parameters:
nodeId: Block node ID to splitsplitPosition: Position in content array to split at
Returns:
string: ID of the new right node
Behavior:
- Creates new block with children after split position
- Updates original block with children before split position
- Updates parent's content array
- Overlay-aware
Example:
const rightNodeId = dataStore.splitMerge.splitBlockNode('block-1', 2);
// Original: [child-1, child-2, child-3, child-4]
// Left: [child-1, child-2], Right: [child-3, child-4]
mergeBlockNodes(leftNodeId: string, rightNodeId: string): string
Merges two adjacent block nodes.
Parameters:
leftNodeId: Left block node IDrightNodeId: Right block node ID
Returns:
string: ID of merged node (left node ID)
Behavior:
- Merges children from both blocks
- Removes right node
- Updates parent's content array
- Overlay-aware
Example:
const mergedId = dataStore.splitMerge.mergeBlockNodes('block-1', 'block-2');
// Left: [child-1, child-2], Right: [child-3, child-4]
// Merged: [child-1, child-2, child-3, child-4]
autoMergeTextNodes(nodeId: string): string
Automatically merges adjacent text nodes if they can be merged.
Parameters:
nodeId: Node ID to check
Returns:
string: ID of merged node or original node ID
Behavior:
- Checks if node can be merged with next sibling
- Merges if same type and no marks
- Overlay-aware
Example:
const mergedId = dataStore.splitMerge.autoMergeTextNodes('text-1');
insertText(nodeId: string, position: number, text: string): string
Inserts text at a position in a text node (single-node operation).
Parameters:
nodeId: Text node IDposition: Position to insert attext: Text to insert
Returns:
string: Inserted text
Behavior:
- Single-node text insertion
- Updates marks appropriately
- Overlay-aware
Example:
const inserted = dataStore.splitMerge.insertText('text-1', 5, 'Hello');
deleteTextRange(nodeId: string, startPosition: number, endPosition: number): string
Deletes text in a range within a single node.
Parameters:
nodeId: Text node IDstartPosition: Start positionendPosition: End position
Returns:
string: Deleted text
Behavior:
- Single-node text deletion
- Updates marks appropriately
- Overlay-aware
Example:
const deleted = dataStore.splitMerge.deleteTextRange('text-1', 0, 5);
replaceTextRange(nodeId: string, startPosition: number, endPosition: number, newText: string): string
Replaces text in a range within a single node.
Parameters:
nodeId: Text node IDstartPosition: Start positionendPosition: End positionnewText: New text
Returns:
string: Replaced text (old text)
Behavior:
- Single-node text replacement
- Updates marks appropriately
- Overlay-aware
Example:
const replaced = dataStore.splitMerge.replaceTextRange('text-1', 0, 5, 'New');
QueryOperations
Node querying and searching operations.
findNodes(predicate: (node: INode) => boolean): INode[]
Finds nodes matching a predicate (includes orphaned nodes).
Parameters:
predicate: Function that returnstruefor matching nodes
Returns:
INode[]: Array of matching nodes
Behavior:
- Iterates through ALL nodes (including orphaned)
- Overlay-aware
- Order not guaranteed
Example:
const nodes = dataStore.query.findNodes(node => node.stype === 'paragraph');
findNodesByType(stype: string): INode[]
Finds nodes by schema type (uses DocumentIterator for performance).
Parameters:
stype: Schema type to find
Returns:
INode[]: Array of matching nodes
Behavior:
- Uses DocumentIterator with type filter
- Order follows document traversal
- More efficient than
findNodes()
Example:
const paragraphs = dataStore.query.findNodesByType('paragraph');
findNodesByAttribute(key: string, value: any): INode[]
Finds nodes by attribute value.
Parameters:
key: Attribute keyvalue: Attribute value
Returns:
INode[]: Array of matching nodes
Example:
const headings = dataStore.query.findNodesByAttribute('level', 1);
findNodesByText(text: string): INode[]
Finds nodes containing text.
Parameters:
text: Text to search for
Returns:
INode[]: Array of matching nodes
Example:
const nodes = dataStore.query.findNodesByText('Hello');
findChildrenByParentId(parentId: string): INode[]
Gets direct children of a node.
Parameters:
parentId: Parent node ID
Returns:
INode[]: Array of direct child nodes
Example:
const children = dataStore.query.findChildrenByParentId('parent-1');
getNodeChildren(nodeId: string): INode[]
Gets direct children (alias for findChildrenByParentId).
Example:
const children = dataStore.query.getNodeChildren('parent-1');
getNodeChildrenDeep(nodeId: string): INode[]
Gets all descendants recursively.
Parameters:
nodeId: Parent node ID
Returns:
INode[]: Array of all descendant nodes
Example:
const allDescendants = dataStore.query.getNodeChildrenDeep('parent-1');
findRootNodes(): INode[]
Finds all root nodes (nodes without parent).
Returns:
INode[]: Array of root nodes
Example:
const roots = dataStore.query.findRootNodes();
searchText(query: string): INode[]
Searches for nodes containing text (full-text search).
Parameters:
query: Search query
Returns:
INode[]: Array of matching nodes
Example:
const results = dataStore.query.searchText('Hello World');
UtilityOperations
Utility functions for node operations and traversal.
hasNode(nodeId: string): boolean
Checks if a node exists.
Parameters:
nodeId: Node ID to check
Returns:
boolean:trueif node exists
Example:
const exists = dataStore.utility.hasNode('node-1');
getChildCount(nodeId: string): number
Gets the number of direct children.
Parameters:
nodeId: Parent node ID
Returns:
number: Number of direct children
Example:
const count = dataStore.utility.getChildCount('parent-1');
isLeafNode(nodeId: string): boolean
Checks if a node is a leaf (no children).
Parameters:
nodeId: Node ID to check
Returns:
boolean:trueif leaf node
Example:
const isLeaf = dataStore.utility.isLeafNode('text-1');
isRootNode(nodeId: string): boolean
Checks if a node is the root node.
Parameters:
nodeId: Node ID to check
Returns:
boolean:trueif root node
Example:
const isRoot = dataStore.utility.isRootNode('document-1');
getChildren(nodeId: string): INode[]
Gets direct children nodes.
Parameters:
nodeId: Parent node ID
Returns:
INode[]: Array of child nodes
Example:
const children = dataStore.utility.getChildren('parent-1');
getParent(nodeId: string): INode | undefined
Gets the parent node.
Parameters:
nodeId: Child node ID
Returns:
INode | undefined: Parent node orundefined
Example:
const parent = dataStore.utility.getParent('child-1');
getSiblings(nodeId: string): INode[]
Gets sibling nodes (same parent).
Parameters:
nodeId: Node ID
Returns:
INode[]: Array of sibling nodes
Example:
const siblings = dataStore.utility.getSiblings('node-1');
getPreviousSibling(nodeId: string): string | null
Gets the previous sibling node ID.
Parameters:
nodeId: Node ID
Returns:
string | null: Previous sibling ID ornull
Example:
const prevSibling = dataStore.utility.getPreviousSibling('node-2');
getNextSibling(nodeId: string): string | null
Gets the next sibling node ID.
Parameters:
nodeId: Node ID
Returns:
string | null: Next sibling ID ornull
Example:
const nextSibling = dataStore.utility.getNextSibling('node-1');
getPreviousEditableNode(nodeId: string): string | null
Gets the previous editable node in document order.
Parameters:
nodeId: Current node ID
Returns:
string | null: Previous editable node ID ornull
Behavior:
- Skips non-editable nodes (blocks, documents)
- Returns only editable nodes (text, inline)
Example:
const prevNode = dataStore.utility.getPreviousEditableNode('text-3');
getNextEditableNode(nodeId: string): string | null
Gets the next editable node in document order.
Parameters:
nodeId: Current node ID
Returns:
string | null: Next editable node ID ornull
Example:
const nextNode = dataStore.utility.getNextEditableNode('text-1');
DecoratorOperations
Decorator management operations.
adjustRanges(...)
Adjusts decorator ranges after text changes.
Note: See Decorators Guide for detailed documentation.
SerializationOperations
Serialization and deserialization operations.
Note: See DataStore README for detailed documentation.
Overlay and Lock Behavior
All DataStore operations automatically respect:
- Overlay: When
dataStore.begin()is called, all writes go to overlay - Lock: Operations should be executed within a lock (acquired via
acquireLock())
Example with Transaction:
// Lock and overlay are managed by TransactionManager
const result = await transaction(editor, [
...control('text-1', [
insertText(5, 'Hello')
])
]).commit();
// Internally:
// 1. acquireLock()
// 2. begin() (overlay)
// 3. RangeOperations.insertText() → updateNode() → overlay
// 4. end() (get operations)
// 5. commit() (apply to base)
// 6. releaseLock()
Related
- Operations Overview - Understanding the operation hierarchy
- Model Operations API - Model layer operations
- Model Operation DSL API - DSL helpers
- Operation Selection Guide - How to choose operations