@polyweave/bt-model (1.0.4)
Installation
@polyweave:registry=https://hub.kl1.tenere.ai/api/packages/Polyweave/npm/npm install @polyweave/bt-model@1.0.4"@polyweave/bt-model": "1.0.4"About this package
@polyweave/bt-model
Immutable behavior tree data model with node construction, five traversal orders, structural mutation, serialization, diffing, and validation. Zero dependencies — pure data structures using only Node.js built-ins.
Synopsis
import { createNode, createBehaviorTree, graftSubtree, diffTree, validateTree, traversePreOrder, NodeType } from '@polyweave/bt-model';
const tree = createBehaviorTree({
id: 'root',
type: NodeType.SELECTOR,
children: [
createNode({ id: 'seq1', type: NodeType.SEQUENCE, children: [
createNode({ id: 'task1', type: NodeType.TASK, name: 'codehand' }),
createNode({ id: 'task2', type: NodeType.TASK, name: 'sleuth' })
]}),
createNode({ id: 'fb1', type: NodeType.FALLBACK, children: [
createNode({ id: 'task3', type: NodeType.TASK, name: 'retry' })
]})
]
});
const cloned = cloneTree(tree);
const grafted = graftSubtree(cloned, 'task1', createNode({ id: 'replacement', type: NodeType.TASK, name: 'new' }));
const diff = diffTree(tree, grafted);
// diff → { added: ['replacement'], removed: ['task1'], modified: ['seq1', 'root'] }
const validation = validateTree(tree);
// validation → { valid: true, errors: [] }
Install
npm install @polyweave/bt-model
Why
Behavior trees in Polyweave need a canonical data model that is immutable, serializable, validatable, and diffable. This package provides pure functions for constructing, traversing, mutating (immutably), serializing, diffing, and validating behavior trees. Zero external dependencies — the model is self-contained and portable. Used by @polyweave/bt-harness as the underlying tree representation and by @polyweave/bt-spec-parser as the compilation target.
API
Node Construction
createNode(definition)
Creates a behavior tree node. Returns an immutable node object.
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
Yes | Unique node identifier within the tree |
type |
NodeType |
Yes | Node type (TASK, CONDITION, SELECTOR, SEQUENCE, FALLBACK, PARALLEL, DECORATOR) |
name |
string |
No | Human-readable name |
children |
Node[] |
No | Child nodes (for composite/decorator types) |
metadata |
object |
No | Arbitrary metadata |
createBehaviorTree(rootDef)
Creates a tree from a root node definition. Equivalent to createNode({ ...rootDef, id: rootDef.id || 'root' }).
buildFromFlatList(nodes)
Builds a tree from a flat array of node definitions with parentId references. Each node must have id, type, and optionally parentId. Nodes without parentId or referencing unknown parents become root nodes.
toFlatList(tree)
Flattens a tree into an array of nodes with parentId references. Inverse of buildFromFlatList.
Enums
| Enum | Values |
|---|---|
NodeType |
TASK, CONDITION, SELECTOR, SEQUENCE, FALLBACK, PARALLEL, DECORATOR |
NodeStatus |
PENDING, RUNNING, SUCCESS, FAILURE |
NodeCategory |
LEAF, COMPOSITE, DECORATOR |
nodeTypes
Set: { TASK, CONDITION, SELECTOR, SEQUENCE, FALLBACK, PARALLEL, DECORATOR }
leafTypes
Set: { TASK, CONDITION } — node types that cannot have children.
compositeTypes
Set: { SELECTOR, SEQUENCE, FALLBACK, PARALLEL } — node types that orchestrate children.
decoratorTypes
Set: { DECORATOR } — node types that wrap a single child.
Queries
findNodeById(tree, id)
Finds a node by its id. Returns the node or null.
findNodesByType(tree, type)
Returns all nodes of the given NodeType in the tree. Always returns an array.
getParent(tree, nodeId)
Returns the parent node of the node with the given ID, or null if root.
getPath(tree, nodeId)
Returns the path from root to the node as an array of node IDs.
getRoot(tree)
Returns the root node of the tree.
Traversal
All traversal functions return arrays of nodes in traversal order.
| Function | Order |
|---|---|
traversePreOrder(tree) |
Root first, then children recursively |
traversePostOrder(tree) |
Children first, then root |
traverseBFS(tree) |
Level by level, left to right |
Metrics
| Function | Returns |
|---|---|
treeDepth(tree) |
Maximum depth of the tree (root = 0) |
leafCount(tree) |
Number of leaf nodes (TASK, CONDITION) |
nodeCount(tree) |
Total number of nodes |
Immutable Mutation
All mutation functions return a new tree. The original is never modified.
cloneTree(tree)
Deep-clones the entire tree. All nodes and metadata are copied.
graftSubtree(tree, targetId, newNode)
Replaces the node at targetId with newNode. Returns a new tree or null if target not found.
replaceNode(tree, targetId, newNode)
Alias for graftSubtree.
pruneNode(tree, targetId)
Removes the node at targetId. If the node is a child, its parent's children array is updated. If the node is the root, returns null.
insertChild(tree, parentId, childNode, index?)
Inserts childNode as a child of parentId at the given index (default: end). Returns a new tree or null if parent not found or parent is a leaf type.
Serialization
toSerializable(tree)
Converts a tree to a plain JSON-serializable object. Circular references are broken. All node data is preserved.
fromSerializable(data)
Reconstructs a tree from the output of toSerializable. Validates the structure and returns a valid tree node or throws.
Diffing
diffTree(treeA, treeB)
Computes the structural difference between two trees. Returns:
{
added: string[], // IDs present in B but not A
removed: string[], // IDs present in A but not B
modified: string[], // IDs present in both but with different content
unchanged: string[] // IDs present in both with identical content
}
treeEquals(treeA, treeB)
Returns true if the two trees are structurally identical (same nodes, same hierarchy, same metadata).
Validation
validateTree(tree)
Validates tree structure. Returns { valid: boolean, errors: string[] }.
Checks performed:
- Root node exists
- All node IDs are unique
- All nodes have valid
NodeTypevalues - Leaf nodes (TASK, CONDITION) have no children
- Decorator nodes have exactly one child
- No circular references in parent-child relationships
detectCycles(tree)
Returns true if the tree contains any cycle in its parent-child graph.
validateIds(tree)
Returns { valid: boolean, duplicates: string[] }. Checks for duplicate node IDs.
isWellFormed(tree)
Returns true if the tree passes all validation checks. Shorthand for validateTree(tree).valid.
Tests
npm test
59 native tests + 500 js-rigor property cases covering node construction, all node types, buildFromFlatList/toFlatList round-trip, five traversal orders with expected output ordering, all mutation operations (clone, graft, prune, insert) with idempotency invariants, depth/leaf/node count metrics, toSerializable/fromSerializable round-trip, diffTree completeness (added/removed/modified/unchanged correctness), treeEquals reflexivity/symmetry/transitivity, validateTree error enumeration, cycle detection, and ID uniqueness validation.
Requirements
Node.js 22 or later. ESM only. Zero dependencies.
Dependencies
Development Dependencies
| ID | Version |
|---|---|
| @rigor/core | * |