@kubb/ast 5.0.0-alpha.4 → 5.0.0-alpha.6

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.
@@ -1,7 +1,20 @@
1
1
  import type { BaseNode } from './base.ts'
2
2
  import type { PropertyNode } from './property.ts'
3
3
 
4
- export type PrimitiveSchemaType = 'string' | 'number' | 'integer' | 'bigint' | 'boolean' | 'null' | 'any' | 'unknown' | 'void' | 'object' | 'array' | 'date'
4
+ export type PrimitiveSchemaType =
5
+ | 'string'
6
+ | 'number'
7
+ | 'integer'
8
+ | 'bigint'
9
+ | 'boolean'
10
+ | 'null'
11
+ | 'any'
12
+ | 'unknown'
13
+ | 'void'
14
+ | 'never'
15
+ | 'object'
16
+ | 'array'
17
+ | 'date'
5
18
 
6
19
  export type ComplexSchemaType = 'tuple' | 'union' | 'intersection' | 'enum'
7
20
 
@@ -14,7 +27,7 @@ export type SchemaType = PrimitiveSchemaType | ComplexSchemaType | SpecialSchema
14
27
 
15
28
  export type ScalarSchemaType = Exclude<
16
29
  SchemaType,
17
- 'object' | 'array' | 'tuple' | 'union' | 'intersection' | 'enum' | 'ref' | 'datetime' | 'date' | 'time' | 'string' | 'number' | 'integer' | 'bigint'
30
+ 'object' | 'array' | 'tuple' | 'union' | 'intersection' | 'enum' | 'ref' | 'datetime' | 'date' | 'time' | 'string' | 'number' | 'integer' | 'bigint' | 'url'
18
31
  >
19
32
 
20
33
  /**
@@ -206,6 +219,17 @@ export type ScalarSchemaNode = SchemaNodeBase & {
206
219
  type: ScalarSchemaType
207
220
  }
208
221
 
222
+ /**
223
+ * URL schema, optionally carrying an Express-style path template for template literal generation.
224
+ */
225
+ export type UrlSchemaNode = SchemaNodeBase & {
226
+ type: 'url'
227
+ /**
228
+ * Express-style path (e.g. `'/pets/:petId'`). When set, printers may emit a template literal type.
229
+ */
230
+ path?: string
231
+ }
232
+
209
233
  /**
210
234
  * Maps each schema type string to its `SchemaNode` variant. Used by `narrowSchema`.
211
235
  */
@@ -229,9 +253,10 @@ export type SchemaNodeByType = {
229
253
  any: ScalarSchemaNode
230
254
  unknown: ScalarSchemaNode
231
255
  void: ScalarSchemaNode
256
+ never: ScalarSchemaNode
232
257
  uuid: ScalarSchemaNode
233
258
  email: ScalarSchemaNode
234
- url: ScalarSchemaNode
259
+ url: UrlSchemaNode
235
260
  blob: ScalarSchemaNode
236
261
  }
237
262
 
@@ -250,4 +275,5 @@ export type SchemaNode =
250
275
  | TimeSchemaNode
251
276
  | StringSchemaNode
252
277
  | NumberSchemaNode
278
+ | UrlSchemaNode
253
279
  | ScalarSchemaNode
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` — 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`)
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
- * Emits `TOutput` from a `SchemaNode`. Returns `null | undefined` when no handler matches.
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['output'] | null | undefined
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,6 +75,12 @@ 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
  /**
@@ -77,53 +88,70 @@ type PrinterBuilder<T extends PrinterFactoryOptions> = (options: T['options']) =
77
88
  * from `@kubb/core` — wraps a builder to make options optional and separates raw options
78
89
  * from resolved options.
79
90
  *
80
- * @example
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 }, { strict: boolean }, string>
102
+ * type ZodPrinter = PrinterFactoryOptions<'zod', { strict?: boolean }, string>
83
103
  *
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
- * },
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
+ * ```
116
+ *
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>
102
120
  *
103
- * const printer = zodPrinter({ strict: false })
104
- * printer.name // 'zod'
105
- * printer.options // { strict: false }
106
- * printer.print(node) // 'z.string()'
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
134
  return (options) => {
111
- const { name, options: resolvedOptions, nodes } = build(options ?? ({} as T['options']))
135
+ const { name, options: resolvedOptions, nodes, print: printOverride } = build(options ?? ({} as T['options']))
112
136
 
113
137
  const context: PrinterHandlerContext<T['output'], T['options']> = {
114
138
  options: resolvedOptions,
115
139
  print: (node: SchemaNode) => {
116
- const type = node.type as SchemaType
117
- const handler = nodes[type]
118
- return handler ? (handler as PrinterHandler<T['output'], T['options']>).call(context, node as SchemaNodeByType[SchemaType]) : undefined
140
+ const schemaType = node.type
141
+ const handler = nodes[schemaType]
142
+
143
+ if (!handler) return undefined
144
+
145
+ const typedHandler = handler as PrinterHandler<T['output'], T['options']>
146
+
147
+ return typedHandler.call(context, node as SchemaNodeByType[SchemaType])
119
148
  },
120
149
  }
121
150
 
122
151
  return {
123
152
  name,
124
153
  options: resolvedOptions,
125
- print: context.print,
126
- for: (nodes) => nodes.map(context.print),
154
+ print: (printOverride ? printOverride.bind(context) : context.print) as Printer<T>['print'],
127
155
  }
128
156
  }
129
157
  }
package/src/types.ts CHANGED
@@ -35,6 +35,7 @@ export type {
35
35
  StringSchemaNode,
36
36
  TimeSchemaNode,
37
37
  UnionSchemaNode,
38
+ UrlSchemaNode,
38
39
  } from './nodes/index.ts'
39
40
  export type { Printer, PrinterFactoryOptions, PrinterHandler, PrinterHandlerContext } from './printer.ts'
40
41
  export type { RefMap } from './refs.ts'
package/src/utils.ts CHANGED
@@ -1,5 +1,7 @@
1
+ import { camelCase, isValidVarName } from '@internals/utils'
2
+
1
3
  import { narrowSchema } from './guards.ts'
2
- import type { SchemaNode } from './nodes/index.ts'
4
+ import type { ParameterNode, SchemaNode } from './nodes/index.ts'
3
5
  import type { SchemaType } from './nodes/schema.ts'
4
6
 
5
7
  const plainStringTypes = new Set<SchemaType>(['string', 'uuid', 'email', 'url', 'datetime'])
@@ -22,3 +24,25 @@ export function isPlainStringType(node: SchemaNode): boolean {
22
24
 
23
25
  return false
24
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
- * Traversable children of `node`, respecting `recurse` for schema nodes.
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) {
@@ -119,6 +126,9 @@ export async function walk(node: Node, visitor: AsyncVisitor, options: VisitorOp
119
126
  return _walk(node, visitor, recurse, limit)
120
127
  }
121
128
 
129
+ /**
130
+ * Internal recursive walk implementation — calls visitor then recurses into children.
131
+ */
122
132
  async function _walk(node: Node, visitor: AsyncVisitor, recurse: boolean, limit: LimitFn): Promise<void> {
123
133
  switch (node.kind) {
124
134
  case 'Root':