@kubb/ast
Spec-agnostic AST layer for Kubb
Defines the node tree, visitor pattern, factory functions, and type guards used across every Kubb code generation plugin.
Imports
@kubb/ast | Runtime: node definitions, guards, visitor, macro engine, constants |
ast.factory (via @kubb/ast) | Node constructors (createSchema, createFile, and friends), the ts.factory analogue |
@kubb/ast/types | Types only: all node interfaces, type aliases, visitor types |
kubb/kit | Re-exports the ast and factory namespaces, the way most Kubb code reaches the AST without a direct dependency |
@kubb/ast is an internal library. Inside the Kubb ecosystem the whole surface travels on the ast namespace from kubb/kit, so plugins and generators reach it there instead of depending on this package directly. The examples below import from @kubb/ast for clarity; through kubb/kit the same calls read as ast.walk, ast.factory.createSchema, and so on.
Node tree
RootNode
├── schemas: SchemaNode[]
└── operations: OperationNode[]
├── parameters: ParameterNode[] → SchemaNode
├── requestBody?: SchemaNode
└── responses: ResponseNode[] → SchemaNode?
SchemaNode (discriminated union)
object → properties: PropertyNode[] → SchemaNode
array | tuple → items: SchemaNode[]
union | intersection → members: SchemaNode[]
enum | ref | string | number | integer | bigint
boolean | null | any | unknown | void
date | datetime | time | uuid | email | url | blob
Usage
Factory
Constructors are available as ast.factory from @kubb/ast, mirroring ts.factory.createX.
import { ast } from '@kubb/ast'
const { createInput, createSchema, createProperty } = ast.factory
const root = createInput({
schemas: [
createSchema({
name: 'Pet',
type: 'object',
properties: [
createProperty({
name: 'id',
schema: createSchema({ type: 'integer' }),
required: true,
}),
createProperty({
name: 'name',
schema: createSchema({ type: 'string' }),
required: true,
}),
],
}),
],
})
Visitor
import { collectSync, transform } from '@kubb/ast'
const updated = transform(root, {
schema(node) {
return { ...node, description: 'generated' }
},
})
const types = collectSync<string>(root, {
schema(node) {
return node.type
},
})
Guards
import { narrowSchema, schemaDef } from '@kubb/ast'
import type { Node } from '@kubb/ast/types'
function process(node: Node) {
if (schemaDef.is(node)) {
const obj = narrowSchema(node, 'object')
obj?.properties?.forEach((p) => console.log(p.name))
}
}
Refs
import { resolveRefName } from '@kubb/ast'
resolveRefName({ kind: 'Schema', type: 'ref', ref: '#/components/schemas/Pet' })
Adding a node
Adding a node touches three files. The barrels and visitor tables derive the rest.
- Define the node in its own
src/nodes/*.ts file. Call defineNode and export the resulting fooDef, the createFoo constructor, and the node's type.
- Add
fooDef to the nodeDefs array in src/registry.ts.
- Re-export
createFoo from src/factory.ts.
Everything else follows from there. @kubb/ast/types picks up the node type through export type *, the @kubb/ast barrel picks up fooDef through export * from './registry.ts', and the visitor tables (VISITOR_KEYS, VISITOR_KEY_BY_KIND, nodeRebuilders) come from the def's children, visitorKey, and rebuild fields. registry.test.ts fails when a def has no matching factory.create*, so missing wiring is caught in CI.
Supporting Kubb
Kubb is an open source project, and its development is funded entirely by sponsors. If you would like to become a sponsor, please consider:
License
MIT