AST
The @kubb/ast package defines Kubb's universal Abstract Syntax Tree. Adapters produce it from a specification (OpenAPI, AsyncAPI, JSON Schema, …), and plugins consume it to emit files. Because every plugin reads the same AST, the same plugin works against any spec a custom adapter can supply.
NOTE
@kubb/core re-exports @kubb/ast as the ast namespace. Most plugins do not need to add @kubb/ast as a direct dependency. Install it explicitly only when you want named imports without the ast. prefix.
Quick start
The complete public surface lives behind a handful of factories, three visitors, and a small set of guards:
import { , , , , , , , , } from '@kubb/ast'
import type { , , } from '@kubb/ast'
const : = ({
: [({ : 'Pet', : 'object', : [] })],
: [({ : 'listPets', : 'GET', : '/pets' })],
})Tree shape
A single InputNode sits at the top, containing reusable schemas and operations. Operations point at parameters, an optional request body, and responses. Each of those connects back to schemas.
InputNode
├── schemas: SchemaNode[] (named, reusable schemas)
└── operations: OperationNode[]
├── parameters: ParameterNode[] → SchemaNode
├── requestBody?: RequestBodyNode → content: ContentNode[] → SchemaNode
└── responses: ResponseNode[] → content: ContentNode[] → SchemaNode
SchemaNode (discriminated by `type`)
Structural: object | array | tuple | union | intersection | enum
Scalar: string | number | integer | bigint | boolean
null | any | unknown | void | never
Special: ref | date | datetime | time | uuid | email | url | blobRequest bodies and responses hold one ContentNode per content type (for example application/json), and each content node carries its own body schema. Every child slot in the tree is a node, so a single traversal table drives walk, transform, and collect across the whole tree.
Every node carries a kind field as the discriminant, so switch (node.kind) narrows the type for you.
TIP
The AST is spec-agnostic. Plugins never look at OpenAPI directly. They consume the AST produced by the adapter, which lets the same plugin work for OpenAPI 2.0, 3.0, 3.1, and any custom adapter alike.
An OperationNode is a discriminated union keyed on protocol, so the model stays spec-neutral while keeping HTTP details fully typed. An HttpOperationNode (protocol: 'http') guarantees a non-nullable method (an HttpMethod) and path. A GenericOperationNode describes a non-HTTP transport and omits both. @kubb/adapter-oas produces HttpOperationNodes, so OpenAPI output is unchanged.
TIP
Narrow before you read transport details. Call isHttpOperationNode(node) (or check node.protocol === 'http') to turn an OperationNode into an HttpOperationNode, after which method and path are guaranteed:
import { isHttpOperationNode } from '@kubb/ast'
if (isHttpOperationNode(node)) {
console.log(node.method, node.path) // both are non-nullable here
}Schema node types
Structural types
| Type | Description | TypeScript |
|---|---|---|
object | Object with named properties | { name: string; age: number } |
array | Sequence of items | string[] |
tuple | Fixed-length array with typed positions | [string, number, boolean] |
union | One of multiple types | string | number |
intersection | Combination of multiple types | A & B |
enum | Fixed set of literal values | 'active' | 'inactive' |
Scalar types
| Type | Description | TypeScript |
|---|---|---|
string | Text value | string |
number | Numeric value | number |
integer | Whole number | number |
bigint | Large integer | bigint |
boolean | True/false | boolean |
null | Null value | null |
any | Any value | any |
unknown | Unknown value | unknown |
void | No value | void |
never | Never produced | never |
Special types
| Type | Description | Example |
|---|---|---|
ref | Reference to another schema | Pet (from $ref) |
date | ISO date | 2024-01-15 |
datetime | ISO datetime | 2024-01-15T10:30:00Z |
time | ISO time | 10:30:00 |
uuid | UUID string | 550e8400-e29b-41d4-a716-446655440000 |
email | Email address | [email protected] |
url | URL string | https://example.com |
blob | Binary data | Raw bytes |
Factory functions
Factories return correctly defaulted, fully typed nodes. Use them in adapters and inside generator handlers. Never construct AST literals by hand.
import { , , } from '@kubb/ast'
const = ({
: [({ : 'Pet', : 'object', : [] }), ({ : 'Status', : 'enum', : ['active', 'inactive'] })],
: [({ : 'listPets', : 'GET', : '/pets' })],
})@kubb/ast also exports factories for source files and TypeScript-level artifacts that generators emit:
| Factory | Purpose |
|---|---|
createFile, createSource, createText | Build FileNodes emitted by generators. |
createImport, createExport | Emit import / export statements. |
createConst, createFunction, createArrowFunction, createJsx | Emit TypeScript declarations and JSX. |
createFunctionParameter, createFunctionParameters | Build typed function parameter lists. |
createParameter, createParameterGroup, createParamsType | Describe operation parameters. |
createProperty, createType | Compose object properties and TypeScript types. |
createResponse, createRequestBody, createContent, createOutput | Model responses, request bodies, content entries, and generator outputs. |
createBreak, syncOptionality | Emit line breaks; reconcile optional flags across nodes. |
update | Apply an identity-preserving shallow update to any node. |
Visitors
Three visitor functions cover the common traversal patterns. Visitor objects use lowercase, kind-style keys (input, operation, schema, property, parameter, response).
walk: async traversal with side effects
import { , } from '@kubb/ast'
const = ({ : [], : [] })
await (, {
async () {
.(`Found ${.} ${.}`)
},
async () {
if ('deprecated' in && .) {
.(`Schema ${'name' in ? . : '?'} is deprecated`)
}
},
})Use when: logging, validating, collecting statistics, or triggering side effects per node.
transform: synchronous, returns a new tree
import { , } from '@kubb/ast'
const = ({ : [], : [] })
const = (, {
() {
if (. === 'object' && . === ) {
return { ..., : false }
}
return
},
() {
return { ..., : .?. ? . : ['untagged'] }
},
})Use when: modifying AST structure, normalising inconsistencies, or annotating nodes.
NOTE
transform preserves identity (structural sharing). When a visitor leaves a node and all of its descendants unchanged, transform returns the original reference, so unchanged subtrees and their arrays are reused, not copied. Returning the same node from a visitor is a no-op. Returning a new node replaces it and rebuilds only its ancestors. A no-op pass therefore allocates nothing, and you can detect whether anything changed with result === input.
To apply a change while keeping that guarantee, use the update factory instead of spreading by hand. It returns the same node when every field you pass already matches:
import { , } from '@kubb/ast'
const = ({ : 'Pet', : 'object', : [] })
(, { : 'Pet' }) // -> same `node` reference (no change)
(, { : 'Animal' }) // -> new node with `name` replacedcollect: gather matching nodes
import { , } from '@kubb/ast'
import type { , } from '@kubb/ast'
const = ({ : [], : [] })
const = <>(, {
() {
return . === 'POST' ? :
},
})
const = <>(, {
() {
return 'deprecated' in && . ? :
},
})
.(`POST operations: ${.}`)
.(`Deprecated schemas: ${.}`)Use when: finding specific nodes, filtering by criteria, building lists for further processing.
Guards and narrowing
@kubb/ast exports type guards and a narrowSchema helper for safe discrimination:
import { , , , , , , } from '@kubb/ast'
const = ({ : [], : [] })
await (, {
async () {
if (!()) return
const = (, 'object')
if () {
.(`object with ${..} properties`)
}
if (. === 'ref') {
.(`reference to: ${.}`)
}
},
})
.(()) // true
.(()) // false
.(()) // falseRefs and naming helpers
| Helper | Purpose |
|---|---|
extractRefName | Turn '#/components/schemas/Pet' into 'Pet'. |
childName | Derive a child property name from context. |
collectImports | Collect imports required by a schema subtree. |
enumPropName | Convert an enum value into a valid property name. |
findDiscriminator | Locate a discriminator on a oneOf/union schema. |
import { } from '@kubb/ast'
const = ('#/components/schemas/Pet')
Constants
| Export | Purpose |
|---|---|
httpMethods | Array of supported HTTP verbs. |
mediaTypes | Array of media types Kubb recognises (JSON, form-data, …). |
nodeKinds | Tuple of every AST node kind. |
schemaTypes | Tuple of every schema type discriminant. |
isScalarPrimitive | Type guard for scalar primitive values. |
Transformers
Structural rewrites you can apply inside transform to normalise schemas:
| Export | Purpose |
|---|---|
mergeAdjacentObjects | Collapse neighbouring object schemas into one. |
setDiscriminatorEnum | Attach a discriminator enum to union members. |
setEnumName | Name an anonymous enum schema. |
simplifyUnion | Remove duplicates and dead branches from a union. |
Dispatch
dispatch walks an ordered table of rules and returns the first node a rule produces. Adapters use it to map a source spec's shapes onto AST nodes without a sprawling if/else chain: each rule pairs a match predicate with a convert function, and rules are tried top to bottom.
import { , } from '@kubb/ast'
import type { } from '@kubb/ast'
type = { : string; : boolean }
const : <<, <typeof >>> = [
{ : 'string', : () => . === 'string', : () => ({ : 'string' }) },
{ : 'number', : () => . === 'number', : () => ({ : 'number' }) },
]
const = (, { : 'string', : false }) ?? ({ : 'any' })Order is significant, so list higher-precedence shapes first. A rule whose match returns true may still convert to null to defer to the next rule (useful when a broad predicate, such as "has a format", only handles some values). dispatch returns null when no rule produces a node, leaving the caller to apply its own fallback. See Concepts: Adapters for how an adapter builds its schema table on top of this.
Printers
Lower-level helpers for parsers that turn the AST into source code:
| Export | Purpose |
|---|---|
definePrinter | Typed helper for creating a Printer. |
createPrinterFactory | Build a factory that produces printers for plugins. |
See Concepts: Parsers for how parsers consume printers.
Examples
Collect every operation tag
import { , } from '@kubb/ast'
const = ({ : [], : [] })
const = new (
<string>(, {
() {
return .?.[0]
},
}),
)
.([...])