TypeScript
Kubb is built in TypeScript end to end. Every public surface takes a generic that pins down options, resolved options, and the resolver shape: defineConfig, definePlugin, defineParser, createAdapter, defineGenerator, and the AST factories. IntelliSense leads you through every choice, and the compiler catches mistakes before generation runs.
Quick start
Use the kubb.config.ts entry point and the compiler infers everything from there:
import { } from 'kubb'
import { } from '@kubb/plugin-ts'
export default ({
: { : './petStore.yaml' },
: { : './src/gen', : true },
: [
({
: { : 'models' },
// Hover any option to see the inferred type.
}),
],
})Strict mode
Kubb assumes TypeScript strict mode. All exported types compile cleanly under "strict": true, and several APIs (notably the AST node guards and resolvers) narrow correctly only when strictNullChecks is on:
{
"compilerOptions": {
"strict": true,
"moduleResolution": "bundler",
"module": "ESNext",
"target": "ES2022"
}
}IMPORTANT
If you cannot enable full strict, enable at least strictNullChecks. Without it, RefSchemaNode.ref and resolver helpers return widened types and you cast manually.
Typing plugin options
Plugins use a single generic, PluginFactoryOptions, that carries four pieces of information through the plugin lifecycle:
| Generic | Purpose |
|---|---|
TName | The plugin's name literal (e.g. 'plugin-ts'). Used by dependencies lookups. |
TOptions | The user-facing options accepted by the factory. |
TResolvedOptions | The shape of options after defaults are applied (what runs at runtime). |
TResolver | The plugin's Resolver extension (pluginName, naming helpers). |
Declare a PluginFactoryOptions alias once and reuse it:
import { } from '@kubb/core'
import type { , } from '@kubb/core'
type = { ?: string }
type = <>
type = <'plugin-example', , , >
export const = <>(() => {
const : = { : . ?? '.ts' }
return {
: 'plugin-example',
: ,
: {
'kubb:plugin:end'({ }) {
// `files` is FileNode[]; no cast required.
.(`${.} files emitted with suffix ${.}`)
},
},
}
})TIP
Inside hooks, ctx.options is typed as TResolvedOptions and ctx.config is the fully resolved Config. No casts required.
Typing adapter options
Adapters follow the same pattern with AdapterFactoryOptions. Pin TName, TOptions, TResolvedOptions, and the parsed TDocument once:
import { , } from '@kubb/core'
import type { } from '@kubb/core'
type = { ?: boolean }
type = <>
type = { : <string, unknown> }
type = <'adapter-example', , , >
export const = <>(() => ({
: 'adapter-example',
: { : . ?? false },
: null,
async () {
return ..()
},
() {
return []
},
async () {
// Throw or call ctx.error here when the spec is invalid.
},
}))The same alias flows into Adapter<AdapterExample>, so consumers that import the adapter type get full type information about its options and document.
Typing parsers
Parsers receive a FileNode<TMeta> in parse, so typing the parameter keeps plugin-attached metadata typed:
import { , } from '@kubb/core'
type = { : 'ts' | 'tsx' }
export const = ({
: 'parser-typed',
: ['.ts'],
(: .<>) {
const = . // typed as Meta
return `// ${?. ?? 'unknown'}\n`
},
() {
return ''
},
})Narrowing AST nodes
The SchemaNode union shares one kind: 'Schema' discriminator and uses node.type to tell variants apart. Use narrowSchema to narrow a SchemaNode to a specific variant, and isHttpOperationNode to narrow an OperationNode to an HttpOperationNode:
import { } from '@kubb/core'
declare const : .
const = .(, 'ref')
if (?.) {
const : string = .
.()
}
declare const : .
if (.()) {
// op is now HttpOperationNode. method and path are non-nullable
const : . = .
}These are the only two guards @kubb/ast exports. Everything else narrows through the kind and type discriminants directly.
See also
- Plugins:
definePlugin,PluginFactoryOptions, resolvers, generators. - Adapters:
createAdapterandAdapterFactoryOptions. - AST: node types, visitors, guards.
- Configuration: top-level
defineConfigshape.