@kubb/ast 4.36.1 → 5.0.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +630 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +80 -2
- package/dist/index.js +622 -57
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/{visitor-CmsfJzro.d.ts → visitor-D-l3dMbN.d.ts} +299 -49
- package/package.json +4 -3
- package/src/constants.ts +5 -1
- package/src/factory.ts +102 -2
- package/src/functionPrinter.ts +195 -0
- package/src/guards.ts +29 -1
- package/src/index.ts +25 -2
- package/src/mocks.ts +7 -1
- package/src/nodes/base.ts +10 -1
- package/src/nodes/function.ts +119 -0
- package/src/nodes/index.ts +4 -1
- package/src/nodes/operation.ts +4 -0
- package/src/nodes/response.ts +1 -1
- package/src/nodes/root.ts +13 -3
- package/src/nodes/schema.ts +29 -3
- package/src/printer.ts +111 -52
- package/src/types.ts +7 -1
- package/src/utils.ts +48 -0
- package/src/visitor.ts +28 -2
package/src/printer.ts
CHANGED
|
@@ -2,11 +2,12 @@ import type { SchemaNode, SchemaNodeByType, SchemaType } from './nodes/index.ts'
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Handler context for `definePrinter` — mirrors `PluginContext` from `@kubb/core`.
|
|
5
|
-
* Available as `this` inside each node handler
|
|
5
|
+
* Available as `this` inside each node handler and the optional root-level `print`.
|
|
6
|
+
* `this.print` always dispatches to the `nodes` handlers (node-level printer).
|
|
6
7
|
*/
|
|
7
8
|
export type PrinterHandlerContext<TOutput, TOptions extends object> = {
|
|
8
9
|
/**
|
|
9
|
-
* Recursively print a nested `SchemaNode
|
|
10
|
+
* Recursively print a nested `SchemaNode` using the node-level handlers.
|
|
10
11
|
*/
|
|
11
12
|
print: (node: SchemaNode) => TOutput | null | undefined
|
|
12
13
|
/**
|
|
@@ -28,19 +29,20 @@ export type PrinterHandler<TOutput, TOptions extends object, T extends SchemaTyp
|
|
|
28
29
|
* Shape of the type parameter passed to `definePrinter`.
|
|
29
30
|
* Mirrors `AdapterFactoryOptions` / `PluginFactoryOptions` from `@kubb/core`.
|
|
30
31
|
*
|
|
31
|
-
* - `TName`
|
|
32
|
-
* - `TOptions`
|
|
33
|
-
* - `TOutput`
|
|
32
|
+
* - `TName` — unique string identifier (e.g. `'zod'`, `'ts'`)
|
|
33
|
+
* - `TOptions` — options passed to and stored on the printer
|
|
34
|
+
* - `TOutput` — the type emitted by node handlers
|
|
35
|
+
* - `TPrintOutput` — the type emitted by the public `print` override (defaults to `TOutput`)
|
|
34
36
|
*/
|
|
35
|
-
export type PrinterFactoryOptions<TName extends string = string, TOptions extends object = object, TOutput = unknown> = {
|
|
37
|
+
export type PrinterFactoryOptions<TName extends string = string, TOptions extends object = object, TOutput = unknown, TPrintOutput = TOutput> = {
|
|
36
38
|
name: TName
|
|
37
39
|
options: TOptions
|
|
38
40
|
output: TOutput
|
|
41
|
+
printOutput: TPrintOutput
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
/**
|
|
42
45
|
* The object returned by calling a `definePrinter` instance.
|
|
43
|
-
* Mirrors the shape of `Adapter` from `@kubb/core`.
|
|
44
46
|
*/
|
|
45
47
|
export type Printer<T extends PrinterFactoryOptions = PrinterFactoryOptions> = {
|
|
46
48
|
/**
|
|
@@ -52,15 +54,18 @@ export type Printer<T extends PrinterFactoryOptions = PrinterFactoryOptions> = {
|
|
|
52
54
|
*/
|
|
53
55
|
options: T['options']
|
|
54
56
|
/**
|
|
55
|
-
*
|
|
57
|
+
* Public printer. If the builder provides a root-level `print`, this calls that
|
|
58
|
+
* higher-level function (which may produce full declarations). Otherwise falls back
|
|
59
|
+
* to the node-level dispatcher
|
|
56
60
|
*/
|
|
57
|
-
print: (node: SchemaNode) => T['
|
|
58
|
-
/**
|
|
59
|
-
* Maps `print` over an array of `SchemaNode`s.
|
|
60
|
-
*/
|
|
61
|
-
for: (nodes: Array<SchemaNode>) => Array<T['output'] | null | undefined>
|
|
61
|
+
print: (node: SchemaNode) => T['printOutput'] | null | undefined
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Builder function passed to `definePrinter`. Receives the resolved options and returns the
|
|
66
|
+
* printer configuration: a unique `name`, the stored `options`, node-level `nodes` handlers,
|
|
67
|
+
* and an optional root-level `print` override.
|
|
68
|
+
*/
|
|
64
69
|
type PrinterBuilder<T extends PrinterFactoryOptions> = (options: T['options']) => {
|
|
65
70
|
name: T['name']
|
|
66
71
|
/**
|
|
@@ -70,60 +75,114 @@ type PrinterBuilder<T extends PrinterFactoryOptions> = (options: T['options']) =
|
|
|
70
75
|
nodes: Partial<{
|
|
71
76
|
[K in SchemaType]: PrinterHandler<T['output'], T['options'], K>
|
|
72
77
|
}>
|
|
78
|
+
/**
|
|
79
|
+
* Optional root-level print override. When provided, becomes the public `printer.print`.
|
|
80
|
+
* `this.print(node)` inside this function calls the node-level dispatcher (`nodes` handlers),
|
|
81
|
+
* not the override itself — so recursion is safe.
|
|
82
|
+
*/
|
|
83
|
+
print?: (this: PrinterHandlerContext<T['output'], T['options']>, node: SchemaNode) => T['printOutput'] | null | undefined
|
|
73
84
|
}
|
|
74
85
|
|
|
75
86
|
/**
|
|
76
|
-
* Creates a named printer factory. Mirrors the `
|
|
87
|
+
* Creates a named printer factory. Mirrors the `createPlugin` / `createAdapter` pattern
|
|
77
88
|
* from `@kubb/core` — wraps a builder to make options optional and separates raw options
|
|
78
89
|
* from resolved options.
|
|
79
90
|
*
|
|
80
|
-
*
|
|
91
|
+
* The builder receives resolved options and returns:
|
|
92
|
+
* - `name` — a unique identifier for the printer
|
|
93
|
+
* - `options` — options stored on the returned printer instance
|
|
94
|
+
* - `nodes` — a map of `SchemaType` → handler functions that convert a `SchemaNode` to `TOutput`
|
|
95
|
+
* - `print` _(optional)_ — a root-level override that becomes the public `printer.print`.
|
|
96
|
+
* Inside it, `this.print(node)` still dispatches to the `nodes` map — safe recursion, no infinite loop.
|
|
97
|
+
*
|
|
98
|
+
* When no `print` override is provided, `printer.print` is the node-level dispatcher directly.
|
|
99
|
+
*
|
|
100
|
+
* @example Basic usage — Zod schema printer
|
|
81
101
|
* ```ts
|
|
82
|
-
* type ZodPrinter = PrinterFactoryOptions<'zod', { strict?: boolean },
|
|
102
|
+
* type ZodPrinter = PrinterFactoryOptions<'zod', { strict?: boolean }, string>
|
|
83
103
|
*
|
|
84
|
-
* export const zodPrinter = definePrinter<ZodPrinter>((options) => {
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* },
|
|
93
|
-
* object(node) {
|
|
94
|
-
* const props = node.properties
|
|
95
|
-
* ?.map(p => `${p.name}: ${this.print(p)}`)
|
|
96
|
-
* .join(', ') ?? ''
|
|
97
|
-
* return `z.object({ ${props} })`
|
|
98
|
-
* },
|
|
104
|
+
* export const zodPrinter = definePrinter<ZodPrinter>((options) => ({
|
|
105
|
+
* name: 'zod',
|
|
106
|
+
* options: { strict: options.strict ?? true },
|
|
107
|
+
* nodes: {
|
|
108
|
+
* string: () => 'z.string()',
|
|
109
|
+
* object(node) {
|
|
110
|
+
* const props = node.properties.map(p => `${p.name}: ${this.print(p.schema)}`).join(', ')
|
|
111
|
+
* return `z.object({ ${props} })`
|
|
99
112
|
* },
|
|
100
|
-
* }
|
|
101
|
-
* })
|
|
113
|
+
* },
|
|
114
|
+
* }))
|
|
115
|
+
* ```
|
|
102
116
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
117
|
+
* @example With a root-level `print` override to wrap output in a full declaration
|
|
118
|
+
* ```ts
|
|
119
|
+
* type TsPrinter = PrinterFactoryOptions<'ts', { typeName?: string }, ts.TypeNode, ts.Node>
|
|
120
|
+
*
|
|
121
|
+
* export const printerTs = definePrinter<TsPrinter>((options) => ({
|
|
122
|
+
* name: 'ts',
|
|
123
|
+
* options,
|
|
124
|
+
* nodes: { string: () => factory.keywordTypeNodes.string },
|
|
125
|
+
* print(node) {
|
|
126
|
+
* const type = this.print(node) // calls the node-level dispatcher
|
|
127
|
+
* if (!type || !this.options.typeName) return type
|
|
128
|
+
* return factory.createTypeAliasDeclaration(this.options.typeName, type)
|
|
129
|
+
* },
|
|
130
|
+
* }))
|
|
107
131
|
* ```
|
|
108
132
|
*/
|
|
109
133
|
export function definePrinter<T extends PrinterFactoryOptions = PrinterFactoryOptions>(build: PrinterBuilder<T>): (options?: T['options']) => Printer<T> {
|
|
110
|
-
return (options) =>
|
|
111
|
-
|
|
134
|
+
return createPrinterFactory<SchemaNode, SchemaType, SchemaNodeByType>((node) => node.type)(build) as (options?: T['options']) => Printer<T>
|
|
135
|
+
}
|
|
112
136
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Generic printer factory. Extracts the core dispatch + context logic so it can be reused
|
|
139
|
+
* for any node type — not just `SchemaNode`. `definePrinter` is built on top of this.
|
|
140
|
+
*
|
|
141
|
+
* @param getKey — derives the handler-map key from a node. Return `undefined` to skip.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```ts
|
|
145
|
+
* export const defineFunctionPrinter = createPrinterFactory<FunctionNode, FunctionNodeType, FunctionNodeByType>(
|
|
146
|
+
* (node) => kindToHandlerKey[node.kind],
|
|
147
|
+
* )
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export function createPrinterFactory<TNode, TKey extends string, TNodeByKey extends Partial<Record<TKey, TNode>>>(getKey: (node: TNode) => TKey | undefined) {
|
|
151
|
+
return function <T extends PrinterFactoryOptions>(
|
|
152
|
+
build: (options: T['options']) => {
|
|
153
|
+
name: T['name']
|
|
154
|
+
options: T['options']
|
|
155
|
+
nodes: Partial<{
|
|
156
|
+
[K in TKey]: (
|
|
157
|
+
this: { print: (node: TNode) => T['output'] | null | undefined; options: T['options'] },
|
|
158
|
+
node: TNodeByKey[K],
|
|
159
|
+
) => T['output'] | null | undefined
|
|
160
|
+
}>
|
|
161
|
+
print?: (this: { print: (node: TNode) => T['output'] | null | undefined; options: T['options'] }, node: TNode) => T['printOutput'] | null | undefined
|
|
162
|
+
},
|
|
163
|
+
): (options?: T['options']) => { name: T['name']; options: T['options']; print: (node: TNode) => T['printOutput'] | null | undefined } {
|
|
164
|
+
return (options) => {
|
|
165
|
+
const { name, options: resolvedOptions, nodes, print: printOverride } = build(options ?? ({} as T['options']))
|
|
166
|
+
|
|
167
|
+
const context = {
|
|
168
|
+
options: resolvedOptions,
|
|
169
|
+
print: (node: TNode): T['output'] | null | undefined => {
|
|
170
|
+
const key = getKey(node)
|
|
171
|
+
if (key === undefined) return undefined
|
|
172
|
+
|
|
173
|
+
const handler = nodes[key]
|
|
174
|
+
|
|
175
|
+
if (!handler) return undefined
|
|
176
|
+
|
|
177
|
+
return (handler as (this: typeof context, node: TNode) => T['output'] | null | undefined).call(context, node)
|
|
178
|
+
},
|
|
179
|
+
}
|
|
121
180
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
181
|
+
return {
|
|
182
|
+
name,
|
|
183
|
+
options: resolvedOptions,
|
|
184
|
+
print: (printOverride ? printOverride.bind(context) : context.print) as (node: TNode) => T['printOutput'] | null | undefined,
|
|
185
|
+
}
|
|
127
186
|
}
|
|
128
187
|
}
|
|
129
188
|
}
|
package/src/types.ts
CHANGED
|
@@ -8,6 +8,10 @@ export type {
|
|
|
8
8
|
DatetimeSchemaNode,
|
|
9
9
|
EnumSchemaNode,
|
|
10
10
|
EnumValueNode,
|
|
11
|
+
FunctionNode,
|
|
12
|
+
FunctionNodeType,
|
|
13
|
+
FunctionParameterNode,
|
|
14
|
+
FunctionParametersNode,
|
|
11
15
|
HttpMethod,
|
|
12
16
|
HttpStatusCode,
|
|
13
17
|
IntersectionSchemaNode,
|
|
@@ -15,6 +19,7 @@ export type {
|
|
|
15
19
|
Node,
|
|
16
20
|
NodeKind,
|
|
17
21
|
NumberSchemaNode,
|
|
22
|
+
ObjectBindingParameterNode,
|
|
18
23
|
ObjectSchemaNode,
|
|
19
24
|
OperationNode,
|
|
20
25
|
ParameterLocation,
|
|
@@ -35,7 +40,8 @@ export type {
|
|
|
35
40
|
StringSchemaNode,
|
|
36
41
|
TimeSchemaNode,
|
|
37
42
|
UnionSchemaNode,
|
|
43
|
+
UrlSchemaNode,
|
|
38
44
|
} from './nodes/index.ts'
|
|
39
|
-
export type { Printer, PrinterFactoryOptions
|
|
45
|
+
export type { Printer, PrinterFactoryOptions } from './printer.ts'
|
|
40
46
|
export type { RefMap } from './refs.ts'
|
|
41
47
|
export type { AsyncVisitor, CollectVisitor, Visitor } from './visitor.ts'
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { camelCase, isValidVarName } from '@internals/utils'
|
|
2
|
+
|
|
3
|
+
import { narrowSchema } from './guards.ts'
|
|
4
|
+
import type { ParameterNode, SchemaNode } from './nodes/index.ts'
|
|
5
|
+
import type { SchemaType } from './nodes/schema.ts'
|
|
6
|
+
|
|
7
|
+
const plainStringTypes = new Set<SchemaType>(['string', 'uuid', 'email', 'url', 'datetime'] as const)
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Returns `true` when a schema node will be represented as a plain string in generated code.
|
|
11
|
+
*
|
|
12
|
+
* - `string`, `uuid`, `email`, `url`, `datetime` are always plain strings.
|
|
13
|
+
* - `date` and `time` are plain strings when their `representation` is `'string'` rather than `'date'`.
|
|
14
|
+
*/
|
|
15
|
+
export function isPlainStringType(node: SchemaNode): boolean {
|
|
16
|
+
if (plainStringTypes.has(node.type)) {
|
|
17
|
+
return true
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const temporal = narrowSchema(node, 'date') ?? narrowSchema(node, 'time')
|
|
21
|
+
if (temporal) {
|
|
22
|
+
return temporal.representation !== 'date'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Transforms the `name` field of each parameter node according to the given casing strategy.
|
|
30
|
+
*
|
|
31
|
+
* The original `params` array is never mutated — a new array of cloned nodes is returned.
|
|
32
|
+
* When no `casing` is provided the original array is returned as-is.
|
|
33
|
+
*
|
|
34
|
+
* Use this before passing parameters to schema builders so that property keys
|
|
35
|
+
* in the generated output match the desired casing while the original
|
|
36
|
+
* `OperationNode.parameters` array remains untouched for other consumers.
|
|
37
|
+
*/
|
|
38
|
+
export function applyParamsCasing(params: Array<ParameterNode>, casing: 'camelcase' | undefined): Array<ParameterNode> {
|
|
39
|
+
if (!casing) {
|
|
40
|
+
return params
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return params.map((param) => {
|
|
44
|
+
const transformed = casing === 'camelcase' || !isValidVarName(param.name) ? camelCase(param.name) : param.name
|
|
45
|
+
|
|
46
|
+
return { ...param, name: transformed }
|
|
47
|
+
})
|
|
48
|
+
}
|
package/src/visitor.ts
CHANGED
|
@@ -2,6 +2,10 @@ import type { VisitorDepth } from './constants.ts'
|
|
|
2
2
|
import { visitorDepths, WALK_CONCURRENCY } from './constants.ts'
|
|
3
3
|
import type { Node, OperationNode, ParameterNode, PropertyNode, ResponseNode, RootNode, SchemaNode } from './nodes/index.ts'
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Creates a concurrency-limiting wrapper. At most `concurrency` promises may be
|
|
7
|
+
* in-flight simultaneously; additional calls are queued and dispatched as slots free.
|
|
8
|
+
*/
|
|
5
9
|
function createLimit(concurrency: number) {
|
|
6
10
|
let active = 0
|
|
7
11
|
const queue: Array<() => void> = []
|
|
@@ -81,7 +85,10 @@ export type CollectVisitor<T> = {
|
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
/**
|
|
84
|
-
*
|
|
88
|
+
* Returns the immediate traversable children of `node`.
|
|
89
|
+
*
|
|
90
|
+
* For `Schema` nodes, children (properties, items, members) are only included
|
|
91
|
+
* when `recurse` is `true`; shallow traversal omits them entirely.
|
|
85
92
|
*/
|
|
86
93
|
function getChildren(node: Node, recurse: boolean): Array<Node> {
|
|
87
94
|
switch (node.kind) {
|
|
@@ -106,6 +113,10 @@ function getChildren(node: Node, recurse: boolean): Array<Node> {
|
|
|
106
113
|
return [node.schema]
|
|
107
114
|
case 'Response':
|
|
108
115
|
return node.schema ? [node.schema] : []
|
|
116
|
+
case 'FunctionParameter':
|
|
117
|
+
case 'ObjectBindingParameter':
|
|
118
|
+
case 'FunctionParameters':
|
|
119
|
+
return []
|
|
109
120
|
}
|
|
110
121
|
}
|
|
111
122
|
|
|
@@ -119,6 +130,9 @@ export async function walk(node: Node, visitor: AsyncVisitor, options: VisitorOp
|
|
|
119
130
|
return _walk(node, visitor, recurse, limit)
|
|
120
131
|
}
|
|
121
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Internal recursive walk implementation — calls visitor then recurses into children.
|
|
135
|
+
*/
|
|
122
136
|
async function _walk(node: Node, visitor: AsyncVisitor, recurse: boolean, limit: LimitFn): Promise<void> {
|
|
123
137
|
switch (node.kind) {
|
|
124
138
|
case 'Root':
|
|
@@ -139,6 +153,10 @@ async function _walk(node: Node, visitor: AsyncVisitor, recurse: boolean, limit:
|
|
|
139
153
|
case 'Response':
|
|
140
154
|
await limit(() => visitor.response?.(node))
|
|
141
155
|
break
|
|
156
|
+
case 'FunctionParameter':
|
|
157
|
+
case 'ObjectBindingParameter':
|
|
158
|
+
case 'FunctionParameters':
|
|
159
|
+
break
|
|
142
160
|
}
|
|
143
161
|
|
|
144
162
|
const children = getChildren(node, recurse)
|
|
@@ -221,9 +239,13 @@ export function transform(node: Node, visitor: Visitor, options: VisitorOptions
|
|
|
221
239
|
|
|
222
240
|
return {
|
|
223
241
|
...response,
|
|
224
|
-
schema:
|
|
242
|
+
schema: transform(response.schema, visitor, options),
|
|
225
243
|
}
|
|
226
244
|
}
|
|
245
|
+
case 'FunctionParameter':
|
|
246
|
+
case 'ObjectBindingParameter':
|
|
247
|
+
case 'FunctionParameters':
|
|
248
|
+
return node
|
|
227
249
|
}
|
|
228
250
|
}
|
|
229
251
|
|
|
@@ -254,6 +276,10 @@ export function collect<T>(node: Node, visitor: CollectVisitor<T>, options: Visi
|
|
|
254
276
|
case 'Response':
|
|
255
277
|
v = visitor.response?.(node)
|
|
256
278
|
break
|
|
279
|
+
case 'FunctionParameter':
|
|
280
|
+
case 'ObjectBindingParameter':
|
|
281
|
+
case 'FunctionParameters':
|
|
282
|
+
break
|
|
257
283
|
}
|
|
258
284
|
if (v !== undefined) results.push(v)
|
|
259
285
|
|