@sylphx/synth
Core AST infrastructure - types, traversal, query index, and utilities.
Installation
bash
npm install @sylphx/synthTypes
Tree
typescript
interface Tree {
meta: TreeMetadata
root: NodeId
nodes: BaseNode[]
strings: Map<string, number>
}
interface TreeMetadata {
language: string
source: string
created: number
modified: number
data?: Record<string, unknown>
}BaseNode
typescript
interface BaseNode {
id: NodeId
type: string
span?: Span
parent: NodeId | null
children: NodeId[]
data?: Record<string, unknown>
}Span & Position
typescript
interface Span {
start: Position
end: Position
}
interface Position {
line: number // 1-based
column: number // 0-based
offset: number // 0-based byte offset
}NodeId
typescript
type NodeId = numberTree Operations
createTree
Create an empty tree:
typescript
import { createTree } from '@sylphx/synth'
const tree = createTree('markdown', '# Hello')getNode
Get a node by ID:
typescript
import { getNode } from '@sylphx/synth'
const node = getNode(tree, 0) // Returns BaseNode | undefinedgetRoot
Get the root node:
typescript
import { getRoot } from '@sylphx/synth'
const root = getRoot(tree) // Returns RootNodegetChildren
Get all children of a node:
typescript
import { getChildren } from '@sylphx/synth'
const children = getChildren(tree, nodeId) // Returns BaseNode[]getParent
Get the parent of a node:
typescript
import { getParent } from '@sylphx/synth'
const parent = getParent(tree, nodeId) // Returns BaseNode | nulladdNode
Add a new node to the tree:
typescript
import { addNode } from '@sylphx/synth'
const newId = addNode(tree, {
type: 'paragraph',
parent: 0,
children: []
})updateNode
Update a node in place:
typescript
import { updateNode } from '@sylphx/synth'
updateNode(tree, nodeId, {
data: { custom: 'value' }
})removeNode
Remove a node from its parent's children:
typescript
import { removeNode } from '@sylphx/synth'
removeNode(tree, nodeId)Traversal
traverse
Traverse the tree with a visitor:
typescript
import { traverse, TraversalOrder } from '@sylphx/synth'
traverse(tree, {
enter: (ctx) => {
console.log(ctx.node.type)
// Return false to skip subtree
},
leave: (ctx) => {
console.log('leaving', ctx.node.type)
},
// Type-specific visitors
heading: (ctx) => { ... },
paragraph: (ctx) => { ... }
}, {
order: TraversalOrder.PreOrder,
maxDepth: 10,
filter: (ctx) => ctx.node.type !== 'comment'
})VisitorContext
typescript
interface VisitorContext {
tree: Tree
nodeId: NodeId
node: BaseNode
parentId: NodeId | null
depth: number
index: number
ancestors: NodeId[]
}TraversalOrder
typescript
const TraversalOrder = {
PreOrder: 'pre-order',
PostOrder: 'post-order',
BreadthFirst: 'breadth-first'
}select
Find all nodes matching a predicate:
typescript
import { select } from '@sylphx/synth'
const nodes = select(tree, (ctx) => ctx.depth > 2)find
Find the first node matching a predicate:
typescript
import { find } from '@sylphx/synth'
const node = find(tree, (ctx) => ctx.node.type === 'heading')selectByType
Find all nodes of a specific type:
typescript
import { selectByType } from '@sylphx/synth'
const headings = selectByType(tree, 'heading')Query Index
createIndex
Build a query index for O(1) lookups:
typescript
import { createIndex } from '@sylphx/synth'
const index = createIndex(tree)ASTIndex Methods
typescript
// Find by type (O(1))
index.findByType('heading')
index.findByTypes(['heading', 'paragraph'])
// Find by data attribute (O(1))
index.findByData('depth', 2)
// Find by depth (O(1))
index.findByDepth(1)
index.findByDepthRange(1, 3)
// Find relationships (O(1))
index.findChildren(parentId)
index.findParent(childId)
// Complex queries
index.query({
type: ['heading', 'paragraph'],
depth: { min: 1, max: 3 },
hasChildren: true,
data: { depth: 2 }
})
// Statistics
index.getTypes()
index.getTypeCount('heading')
index.getTypeCounts()
index.getStats()Zipper
Functional tree navigation:
typescript
import { createZipper } from '@sylphx/synth'
const zipper = createZipper(tree)
// Navigation
const child = zipper.down()
const sibling = child?.right()
const parent = sibling?.up()
// Get current node
const node = zipper.node
// Modify (immutable)
const modified = zipper.replace({ ...zipper.node, data: { foo: 'bar' } })
// Get back to root with changes
const newRoot = modified?.root()Plugin System
createTransformPlugin
Create a plugin that transforms the tree:
typescript
import { createTransformPlugin } from '@sylphx/synth'
const myPlugin = createTransformPlugin(
{ name: 'my-plugin', version: '1.0.0' },
(tree) => {
// Modify tree
return tree
}
)createVisitorPlugin
Create a plugin that visits nodes:
typescript
import { createVisitorPlugin } from '@sylphx/synth'
const myPlugin = createVisitorPlugin(
{ name: 'counter', version: '1.0.0' },
{
enter: (node, context) => {
context.data.count = (context.data.count || 0) + 1
}
}
)Type Guards
typescript
import { isTextNode, isParentNode } from '@sylphx/synth'
if (isTextNode(node)) {
console.log(node.value)
}
if (isParentNode(node)) {
console.log(node.children.length)
}Errors
typescript
import { SynthError, TreeStructureError, IndexNotBuiltError } from '@sylphx/synth'
try {
// ...
} catch (e) {
if (e instanceof IndexNotBuiltError) {
// Index not built yet
}
}