@kubb/ast 4.33.5 → 4.35.0

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.
@@ -20,7 +20,7 @@ export type ScalarSchemaType = Exclude<
20
20
  /**
21
21
  * Base fields shared by every schema variant. Does not include spec-specific fields.
22
22
  */
23
- interface SchemaNodeBase extends BaseNode {
23
+ type SchemaNodeBase = BaseNode & {
24
24
  kind: 'Schema'
25
25
  /**
26
26
  * Named schema identifier (e.g. `"Pet"` from `#/components/schemas/Pet`). `undefined` for inline schemas.
@@ -48,7 +48,7 @@ interface SchemaNodeBase extends BaseNode {
48
48
  /**
49
49
  * Object schema with ordered property definitions.
50
50
  */
51
- export interface ObjectSchemaNode extends SchemaNodeBase {
51
+ export type ObjectSchemaNode = SchemaNodeBase & {
52
52
  type: 'object'
53
53
  properties?: Array<PropertyNode>
54
54
  /**
@@ -61,7 +61,7 @@ export interface ObjectSchemaNode extends SchemaNodeBase {
61
61
  /**
62
62
  * Array or tuple schema.
63
63
  */
64
- export interface ArraySchemaNode extends SchemaNodeBase {
64
+ export type ArraySchemaNode = SchemaNodeBase & {
65
65
  type: 'array' | 'tuple'
66
66
  items?: Array<SchemaNode>
67
67
  /**
@@ -76,14 +76,14 @@ export interface ArraySchemaNode extends SchemaNodeBase {
76
76
  /**
77
77
  * Shared base for union and intersection schemas.
78
78
  */
79
- interface CompositeSchemaNodeBase extends SchemaNodeBase {
79
+ type CompositeSchemaNodeBase = SchemaNodeBase & {
80
80
  members?: Array<SchemaNode>
81
81
  }
82
82
 
83
83
  /**
84
84
  * Union schema (`oneOf` / `anyOf`).
85
85
  */
86
- export interface UnionSchemaNode extends CompositeSchemaNodeBase {
86
+ export type UnionSchemaNode = CompositeSchemaNodeBase & {
87
87
  type: 'union'
88
88
  /**
89
89
  * Discriminator property from OAS `discriminator.propertyName`.
@@ -94,14 +94,14 @@ export interface UnionSchemaNode extends CompositeSchemaNodeBase {
94
94
  /**
95
95
  * Intersection schema (`allOf`).
96
96
  */
97
- export interface IntersectionSchemaNode extends CompositeSchemaNodeBase {
97
+ export type IntersectionSchemaNode = CompositeSchemaNodeBase & {
98
98
  type: 'intersection'
99
99
  }
100
100
 
101
101
  /**
102
102
  * A named enum variant.
103
103
  */
104
- export interface EnumValueNode {
104
+ export type EnumValueNode = {
105
105
  name: string
106
106
  value: string | number | boolean
107
107
  format: 'string' | 'number' | 'boolean'
@@ -110,7 +110,7 @@ export interface EnumValueNode {
110
110
  /**
111
111
  * Enum schema.
112
112
  */
113
- export interface EnumSchemaNode extends SchemaNodeBase {
113
+ export type EnumSchemaNode = SchemaNodeBase & {
114
114
  type: 'enum'
115
115
  /**
116
116
  * Enum member type. Generators should use const assertions for `'number'` / `'boolean'`.
@@ -129,7 +129,7 @@ export interface EnumSchemaNode extends SchemaNodeBase {
129
129
  /**
130
130
  * Ref schema — pointer to another schema definition.
131
131
  */
132
- export interface RefSchemaNode extends SchemaNodeBase {
132
+ export type RefSchemaNode = SchemaNodeBase & {
133
133
  type: 'ref'
134
134
  name?: string
135
135
  /**
@@ -145,7 +145,7 @@ export interface RefSchemaNode extends SchemaNodeBase {
145
145
  /**
146
146
  * Datetime schema.
147
147
  */
148
- export interface DatetimeSchemaNode extends SchemaNodeBase {
148
+ export type DatetimeSchemaNode = SchemaNodeBase & {
149
149
  type: 'datetime'
150
150
  /**
151
151
  * Includes timezone offset (`dateType: 'stringOffset'`).
@@ -160,7 +160,7 @@ export interface DatetimeSchemaNode extends SchemaNodeBase {
160
160
  /**
161
161
  * Base for `date` and `time` schemas.
162
162
  */
163
- interface TemporalSchemaNodeBase<T extends 'date' | 'time'> extends SchemaNodeBase {
163
+ type TemporalSchemaNodeBase<T extends 'date' | 'time'> = SchemaNodeBase & {
164
164
  type: T
165
165
  /**
166
166
  * Representation in generated code: native `Date` or plain string.
@@ -181,7 +181,7 @@ export type TimeSchemaNode = TemporalSchemaNodeBase<'time'>
181
181
  /**
182
182
  * String schema.
183
183
  */
184
- export interface StringSchemaNode extends SchemaNodeBase {
184
+ export type StringSchemaNode = SchemaNodeBase & {
185
185
  type: 'string'
186
186
  min?: number
187
187
  max?: number
@@ -191,7 +191,7 @@ export interface StringSchemaNode extends SchemaNodeBase {
191
191
  /**
192
192
  * Number, integer, or bigint schema.
193
193
  */
194
- export interface NumberSchemaNode extends SchemaNodeBase {
194
+ export type NumberSchemaNode = SchemaNodeBase & {
195
195
  type: 'number' | 'integer' | 'bigint'
196
196
  min?: number
197
197
  max?: number
@@ -202,7 +202,7 @@ export interface NumberSchemaNode extends SchemaNodeBase {
202
202
  /**
203
203
  * Schema for scalar types with no additional constraints.
204
204
  */
205
- export interface ScalarSchemaNode extends SchemaNodeBase {
205
+ export type ScalarSchemaNode = SchemaNodeBase & {
206
206
  type: ScalarSchemaType
207
207
  }
208
208
 
package/src/printer.ts ADDED
@@ -0,0 +1,127 @@
1
+ import type { SchemaNode, SchemaNodeByType, SchemaType } from './nodes/index.ts'
2
+
3
+ /**
4
+ * Handler context for `definePrinter` — mirrors `PluginContext` from `@kubb/core`.
5
+ * Available as `this` inside each node handler.
6
+ */
7
+ export type PrinterHandlerContext<TOutput, TOptions extends object> = {
8
+ /**
9
+ * Recursively print a nested `SchemaNode`.
10
+ */
11
+ print: (node: SchemaNode) => TOutput | null | undefined
12
+ /**
13
+ * Options for this printer instance.
14
+ */
15
+ options: TOptions
16
+ }
17
+
18
+ /**
19
+ * Handler for a specific `SchemaNode` variant identified by `SchemaType` key `T`.
20
+ * Use a regular function (not an arrow function) so that `this` is available.
21
+ */
22
+ export type PrinterHandler<TOutput, TOptions extends object, T extends SchemaType = SchemaType> = (
23
+ this: PrinterHandlerContext<TOutput, TOptions>,
24
+ node: SchemaNodeByType[T],
25
+ ) => TOutput | null | undefined
26
+
27
+ /**
28
+ * Shape of the type parameter passed to `definePrinter`.
29
+ * Mirrors `AdapterFactoryOptions` / `PluginFactoryOptions` from `@kubb/core`.
30
+ *
31
+ * - `TName` — unique string identifier (e.g. `'zod'`, `'ts'`)
32
+ * - `TOptions` — options passed to and stored on the printer
33
+ * - `TOutput` — the type emitted by `print` (typically `string`)
34
+ */
35
+ export type PrinterFactoryOptions<TName extends string = string, TOptions extends object = object, TOutput = unknown> = {
36
+ name: TName
37
+ options: TOptions
38
+ output: TOutput
39
+ }
40
+
41
+ /**
42
+ * The object returned by calling a `definePrinter` instance.
43
+ * Mirrors the shape of `Adapter` from `@kubb/core`.
44
+ */
45
+ export type Printer<T extends PrinterFactoryOptions = PrinterFactoryOptions> = {
46
+ /**
47
+ * Unique identifier supplied at creation time.
48
+ */
49
+ name: T['name']
50
+ /**
51
+ * Options for this printer instance.
52
+ */
53
+ options: T['options']
54
+ /**
55
+ * Emits `TOutput` from a `SchemaNode`. Returns `null | undefined` when no handler matches.
56
+ */
57
+ print: (node: SchemaNode) => T['output'] | null | undefined
58
+ /**
59
+ * Maps `print` over an array of `SchemaNode`s.
60
+ */
61
+ for: (nodes: Array<SchemaNode>) => Array<T['output'] | null | undefined>
62
+ }
63
+
64
+ type PrinterBuilder<T extends PrinterFactoryOptions> = (options: T['options']) => {
65
+ name: T['name']
66
+ /**
67
+ * Options to store on the printer.
68
+ */
69
+ options: T['options']
70
+ nodes: Partial<{
71
+ [K in SchemaType]: PrinterHandler<T['output'], T['options'], K>
72
+ }>
73
+ }
74
+
75
+ /**
76
+ * Creates a named printer factory. Mirrors the `definePlugin` / `defineAdapter` pattern
77
+ * from `@kubb/core` — wraps a builder to make options optional and separates raw options
78
+ * from resolved options.
79
+ *
80
+ * @example
81
+ * ```ts
82
+ * type ZodPrinter = PrinterFactoryOptions<'zod', { strict?: boolean }, { strict: boolean }, string>
83
+ *
84
+ * export const zodPrinter = definePrinter<ZodPrinter>((options) => {
85
+ * const { strict = true } = options
86
+ * return {
87
+ * name: 'zod',
88
+ * options: { strict },
89
+ * nodes: {
90
+ * string(node) {
91
+ * return `z.string()`
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
+ * },
99
+ * },
100
+ * }
101
+ * })
102
+ *
103
+ * const printer = zodPrinter({ strict: false })
104
+ * printer.name // 'zod'
105
+ * printer.options // { strict: false }
106
+ * printer.print(node) // 'z.string()'
107
+ * ```
108
+ */
109
+ export function definePrinter<T extends PrinterFactoryOptions = PrinterFactoryOptions>(build: PrinterBuilder<T>): (options?: T['options']) => Printer<T> {
110
+ return (options) => {
111
+ const { name, options: resolvedOptions, nodes } = build(options ?? ({} as T['options']))
112
+
113
+ function print(node: SchemaNode): T['output'] | null | undefined {
114
+ const type = node.type as SchemaType
115
+
116
+ const handler = nodes[type]
117
+ if (handler) {
118
+ const context: PrinterHandlerContext<T['output'], T['options']> = { print, options: resolvedOptions }
119
+ return (handler as PrinterHandler<T['output'], T['options']>).call(context, node as SchemaNodeByType[SchemaType])
120
+ }
121
+
122
+ return undefined
123
+ }
124
+
125
+ return { name, options: resolvedOptions, print, for: (nodes) => nodes.map(print) }
126
+ }
127
+ }
package/src/types.ts CHANGED
@@ -23,6 +23,7 @@ export type {
23
23
  PropertyNode,
24
24
  RefSchemaNode,
25
25
  ResponseNode,
26
+ RootMeta,
26
27
  RootNode,
27
28
  ScalarSchemaNode,
28
29
  ScalarSchemaType,
@@ -35,5 +36,6 @@ export type {
35
36
  TimeSchemaNode,
36
37
  UnionSchemaNode,
37
38
  } from './nodes/index.ts'
39
+ export type { Printer } from './printer.ts'
38
40
  export type { RefMap } from './refs.ts'
39
- export type { AsyncVisitor, CollectVisitor, Visitor, VisitorOptions } from './visitor.ts'
41
+ export type { AsyncVisitor, CollectVisitor, Visitor } from './visitor.ts'
package/src/visitor.ts CHANGED
@@ -1,18 +1,51 @@
1
1
  import type { VisitorDepth } from './constants.ts'
2
- import { visitorDepths } from './constants.ts'
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
+ function createLimit(concurrency: number) {
6
+ let active = 0
7
+ const queue: Array<() => void> = []
8
+
9
+ function next() {
10
+ if (active < concurrency && queue.length > 0) {
11
+ active++
12
+ queue.shift()!()
13
+ }
14
+ }
15
+
16
+ return function limit<T>(fn: () => Promise<T> | T): Promise<T> {
17
+ return new Promise<T>((resolve, reject) => {
18
+ queue.push(() => {
19
+ Promise.resolve(fn())
20
+ .then(resolve, reject)
21
+ .finally(() => {
22
+ active--
23
+ next()
24
+ })
25
+ })
26
+ next()
27
+ })
28
+ }
29
+ }
30
+
31
+ type LimitFn = ReturnType<typeof createLimit>
32
+
5
33
  /**
6
34
  * Shared options for `walk`, `transform`, and `collect`.
7
35
  */
8
36
  export type VisitorOptions = {
9
37
  depth?: VisitorDepth
38
+ /**
39
+ * Maximum number of sibling nodes visited concurrently inside `walk`.
40
+ * @default 30
41
+ */
42
+ concurrency?: number
10
43
  }
11
44
 
12
45
  /**
13
46
  * Synchronous visitor for `transform` and `walk`.
14
47
  */
15
- export interface Visitor {
48
+ export type Visitor = {
16
49
  root?(node: RootNode): void | RootNode
17
50
  operation?(node: OperationNode): void | OperationNode
18
51
  schema?(node: SchemaNode): void | SchemaNode
@@ -26,7 +59,7 @@ type MaybePromise<T> = T | Promise<T>
26
59
  /**
27
60
  * Async visitor for `walk`. Synchronous `Visitor` objects are compatible.
28
61
  */
29
- export interface AsyncVisitor {
62
+ export type AsyncVisitor = {
30
63
  root?(node: RootNode): MaybePromise<void | RootNode>
31
64
  operation?(node: OperationNode): MaybePromise<void | OperationNode>
32
65
  schema?(node: SchemaNode): MaybePromise<void | SchemaNode>
@@ -38,7 +71,7 @@ export interface AsyncVisitor {
38
71
  /**
39
72
  * Visitor for `collect`.
40
73
  */
41
- export interface CollectVisitor<T> {
74
+ export type CollectVisitor<T> = {
42
75
  root?(node: RootNode): T | undefined
43
76
  operation?(node: OperationNode): T | undefined
44
77
  schema?(node: SchemaNode): T | undefined
@@ -78,34 +111,38 @@ function getChildren(node: Node, recurse: boolean): Array<Node> {
78
111
 
79
112
  /**
80
113
  * Depth-first traversal for side effects. Visitor return values are ignored.
114
+ * Sibling nodes at each level are visited concurrently up to `options.concurrency` (default: 30).
81
115
  */
82
116
  export async function walk(node: Node, visitor: AsyncVisitor, options: VisitorOptions = {}): Promise<void> {
83
117
  const recurse = (options.depth ?? visitorDepths.deep) === visitorDepths.deep
118
+ const limit = createLimit(options.concurrency ?? WALK_CONCURRENCY)
119
+ return _walk(node, visitor, recurse, limit)
120
+ }
84
121
 
122
+ async function _walk(node: Node, visitor: AsyncVisitor, recurse: boolean, limit: LimitFn): Promise<void> {
85
123
  switch (node.kind) {
86
124
  case 'Root':
87
- await visitor.root?.(node)
125
+ await limit(() => visitor.root?.(node))
88
126
  break
89
127
  case 'Operation':
90
- await visitor.operation?.(node)
128
+ await limit(() => visitor.operation?.(node))
91
129
  break
92
130
  case 'Schema':
93
- await visitor.schema?.(node)
131
+ await limit(() => visitor.schema?.(node))
94
132
  break
95
133
  case 'Property':
96
- await visitor.property?.(node)
134
+ await limit(() => visitor.property?.(node))
97
135
  break
98
136
  case 'Parameter':
99
- await visitor.parameter?.(node)
137
+ await limit(() => visitor.parameter?.(node))
100
138
  break
101
139
  case 'Response':
102
- await visitor.response?.(node)
140
+ await limit(() => visitor.response?.(node))
103
141
  break
104
142
  }
105
143
 
106
- for (const child of getChildren(node, recurse)) {
107
- await walk(child, visitor, options)
108
- }
144
+ const children = getChildren(node, recurse)
145
+ await Promise.all(children.map((child) => _walk(child, visitor, recurse, limit)))
109
146
  }
110
147
 
111
148
  /**