Node Operations
CRUD, tree traversal, ordering, history and relationships.
NodeOperations
const ws = db.workspace('content');
const nodes = ws.nodes();
create()
create(options: NodeCreateOptions): Promise<Node>
interface NodeCreateOptions {
type: string; // node type, e.g. 'raisin:Page'
path: string; // full path of the new node
properties?: Record<string, PropertyValue>;
content?: unknown;
}
createDeep()
Create a node and any missing ancestor folders along path. Ancestors are created as parentNodeType (default raisin:Folder).
createDeep(options: NodeCreateDeepOptions): Promise<Node>
interface NodeCreateDeepOptions extends NodeCreateOptions {
parentNodeType?: string;
}
upsertDeep()
Create-or-update by path, creating missing ancestors.
upsertDeep(options: NodeCreateDeepOptions): Promise<Node>
get()
get(id: string): Promise<Node | null>
getByPath()
getByPath(path: string): Promise<Node | null>
update()
properties replaces the stored properties.
update(id: string, options: { properties?: Record<string, PropertyValue>; content?: unknown }): Promise<Node>
delete()
delete(id: string): Promise<boolean>
query(), queryByType(), queryByProperty()
query(options: { query: unknown; limit?: number; offset?: number }): Promise<Node[]>
queryByType(nodeType: string, limit?: number): Promise<Node[]>
queryByProperty(name: string, value: PropertyValue, limit?: number): Promise<Node[]>
query() accepts { type } or { parent: parentId } as the filter, which is what queryByType() and getChildren() send. getByPath() and queryByProperty() use their own request types (node_query_by_path, node_query_by_property); queryByProperty() matches a top-level property by exact value.
History and audit
history()
Revision history of a node, newest first. Available for every node regardless of the NodeType's auditable flag.
history(id: string, options?: { limit?: number }): Promise<RevisionEntry[]>
historyByPath(path: string, options?: { limit?: number }): Promise<RevisionEntry[]>
interface RevisionEntry {
revision: string; // usable with atRevision()
updated_at?: string; // ISO 8601
updated_by?: string;
deleted: boolean;
}
const revisions = await nodes.history(nodeId, { limit: 50 });
for (const rev of revisions) {
const snapshot = await ws.atRevision(rev.revision).nodes().get(nodeId);
}
auditLog()
Audit entries, recorded only for NodeTypes marked auditable: true.
auditLog(id: string): Promise<AuditLogEntry[]>
auditLogByPath(path: string): Promise<AuditLogEntry[]>
interface AuditLogEntry {
id: string;
node_id: string;
path: string;
workspace: string;
user_id?: string;
action: string; // "Create" | "Update" | "Delete" | "Publish" | ...
timestamp: string;
details?: string;
}
Both are filtered by row-level security: you only see entries for nodes you can read.
Tree operations
listChildren()
All children of a parent in editorial (drag-and-drop) order.
listChildren(parentPath: string): Promise<Node[]>
listChildrenPage()
One page of children in editorial order, with keyset pagination. Pass the previous page's nextCursor back as cursor; it is null on the last page.
listChildrenPage(parentPath: string, options?: { cursor?: string; limit?: number }): Promise<{ items: Node[]; nextCursor: string | null }>
let cursor: string | undefined;
do {
const page = await nodes.listChildrenPage('/menu', { cursor, limit: 50 });
for (const child of page.items) { /* ... */ }
cursor = page.nextCursor ?? undefined;
} while (cursor);
The cursor is opaque. A page can hold fewer than limit items without being the last page (permission filtering happens per page), so loop on nextCursor, not on the item count. See Pagination.
getChildren()
getChildren(parentId: string, limit?: number): Promise<Node[]>
getChildrenByPath(parentPath: string, limit?: number): Promise<Node[]>
getTree(), getTreeFlat()
getTree(rootPath: string, maxDepth?: number): Promise<Node>
getTreeFlat(rootPath: string, maxDepth?: number): Promise<Node[]>
move()
move(fromPath: string, toParentPath: string): Promise<Node>
rename()
rename(nodePath: string, newName: string): Promise<Node>
copy(), copyTree()
Shallow copy (node only) and deep copy (node and descendants).
copy(fromPath: string, toParentPath: string, newName?: string): Promise<Node>
copyTree(fromPath: string, toParentPath: string, newName?: string): Promise<Node>
Ordering
Siblings have an explicit order (see Child Ordering). Order is per branch and is carried by a merge. You name a position or a neighbour and the server assigns the order key. Children are identified by name.
reorder()
Move a child to a 0-based position among its siblings; a position past the end appends. Returns the node with its new order_key, the value the __order SQL column reports.
reorder(parentPath: string, childName: string, position: number): Promise<Node>
await nodes.reorder('/articles', 'item-1', 0); // to the front
moveChildBefore(), moveChildAfter()
moveChildBefore(parentPath: string, childName: string, beforeChildName: string): Promise<void>
moveChildAfter(parentPath: string, childName: string, afterChildName: string): Promise<void>
applyChildOrder()
Reorder a parent's children on the current branch to match their order on sourceBranch. Only children present under the parent on both branches are moved.
applyChildOrder(parentPath: string, sourceBranch: string): Promise<void>
Use it when promoting content by copying nodes between branches (for example a main to publish flow): a copy carries content but not sibling order. A full branch merge carries order already.
await db.onBranch('publish').workspace('content').nodes().applyChildOrder('/menu', 'main');
Relationships
addRelation()
addRelation(
nodePath: string,
relationType: string,
targetNodePath: string,
weightOrOptions?: number | { weight?: number; targetWorkspace?: string }
): Promise<boolean>
removeRelation()
removeRelation(nodePath: string, targetPath: string, options?: { targetWorkspace?: string }): Promise<boolean>
removeRelation(nodePath: string, relationType: string, targetPath: string, options?): Promise<boolean>
getRelationships()
getRelationships(nodePath: string): Promise<{ outgoing: Relation[]; incoming: Relation[] }>