@kubb/ast 5.0.0-alpha.5 → 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.
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
  }