@kubb/ast 5.0.0-alpha.15 → 5.0.0-alpha.17

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.js CHANGED
@@ -16,6 +16,17 @@ const nodeKinds = {
16
16
  objectBindingParameter: "ObjectBindingParameter",
17
17
  functionParameters: "FunctionParameters"
18
18
  };
19
+ /**
20
+ * Canonical schema type strings used by AST schema nodes.
21
+ *
22
+ * These values are used across the AST as stable discriminators
23
+ * (for example `schema.type === schemaTypes.object`).
24
+ *
25
+ * The map is grouped by intent:
26
+ * - primitives (`string`, `number`, `boolean`, ...)
27
+ * - structural/composite (`object`, `array`, `union`, ...)
28
+ * - special OpenAPI-oriented types (`ref`, `datetime`, `uuid`, ...)
29
+ */
19
30
  const schemaTypes = {
20
31
  string: "string",
21
32
  number: "number",
@@ -42,6 +53,16 @@ const schemaTypes = {
42
53
  blob: "blob",
43
54
  never: "never"
44
55
  };
56
+ /**
57
+ * Primitive scalar schema types used when simplifying union members.
58
+ */
59
+ const SCALAR_PRIMITIVE_TYPES = new Set([
60
+ "string",
61
+ "number",
62
+ "integer",
63
+ "bigint",
64
+ "boolean"
65
+ ]);
45
66
  const httpMethods = {
46
67
  get: "GET",
47
68
  post: "POST",
@@ -74,745 +95,945 @@ const mediaTypes = {
74
95
  videoMp4: "video/mp4"
75
96
  };
76
97
  //#endregion
77
- //#region src/factory.ts
98
+ //#region ../../internals/utils/dist/index.js
78
99
  /**
79
- * Creates a `RootNode`.
100
+ * Shared implementation for camelCase and PascalCase conversion.
101
+ * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
102
+ * and capitalizes each word according to `pascal`.
103
+ *
104
+ * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
80
105
  */
81
- function createRoot(overrides = {}) {
82
- return {
83
- schemas: [],
84
- operations: [],
85
- ...overrides,
86
- kind: "Root"
87
- };
106
+ function toCamelOrPascal(text, pascal) {
107
+ return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
108
+ if (word.length > 1 && word === word.toUpperCase()) return word;
109
+ if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
110
+ return word.charAt(0).toUpperCase() + word.slice(1);
111
+ }).join("").replace(/[^a-zA-Z0-9]/g, "");
88
112
  }
89
113
  /**
90
- * Creates an `OperationNode`.
114
+ * Splits `text` on `.` and applies `transformPart` to each segment.
115
+ * The last segment receives `isLast = true`, all earlier segments receive `false`.
116
+ * Segments are joined with `/` to form a file path.
117
+ *
118
+ * Only splits on dots followed by a letter so that version numbers
119
+ * embedded in operationIds (e.g. `v2025.0`) are kept intact.
91
120
  */
92
- function createOperation(props) {
93
- return {
94
- tags: [],
95
- parameters: [],
96
- responses: [],
97
- ...props,
98
- kind: "Operation"
99
- };
100
- }
101
- function createSchema(props) {
102
- if (props["type"] === "object") return {
103
- properties: [],
104
- ...props,
105
- kind: "Schema"
106
- };
107
- return {
108
- ...props,
109
- kind: "Schema"
110
- };
121
+ function applyToFileParts(text, transformPart) {
122
+ const parts = text.split(/\.(?=[a-zA-Z])/);
123
+ return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
111
124
  }
112
125
  /**
113
- * Derives `schema.optional` and `schema.nullish` from `required` and `schema.nullable`.
114
- * This keeps `PropertyNode.required` as the single source of truth for optionality.
126
+ * Converts `text` to camelCase.
127
+ * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
128
+ *
129
+ * @example
130
+ * camelCase('hello-world') // 'helloWorld'
131
+ * camelCase('pet.petId', { isFile: true }) // 'pet/petId'
115
132
  */
116
- function syncPropertySchema(required, schema) {
117
- const nullable = schema.nullable ?? false;
118
- return {
119
- ...schema,
120
- optional: !required && !nullable ? true : void 0,
121
- nullish: !required && nullable ? true : void 0
122
- };
133
+ function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
134
+ if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
135
+ prefix,
136
+ suffix
137
+ } : {}));
138
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
123
139
  }
124
140
  /**
125
- * Creates a `PropertyNode`. `required` defaults to `false`.
126
- * `schema.optional` and `schema.nullish` are auto-derived from `required` and `schema.nullable`.
141
+ * Converts `text` to PascalCase.
142
+ * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
143
+ *
144
+ * @example
145
+ * pascalCase('hello-world') // 'HelloWorld'
146
+ * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
127
147
  */
128
- function createProperty(props) {
129
- const required = props.required ?? false;
130
- return {
131
- ...props,
132
- kind: "Property",
133
- required,
134
- schema: syncPropertySchema(required, props.schema)
135
- };
148
+ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
149
+ if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
150
+ prefix,
151
+ suffix
152
+ }) : camelCase(part));
153
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
136
154
  }
137
- /**
138
- * Creates a `ParameterNode`. `required` defaults to `false`.
139
- * `schema.optional` is auto-derived from `required` and `schema.nullable`.
140
- */
141
- function createParameter(props) {
142
- const required = props.required ?? false;
143
- return {
144
- ...props,
145
- kind: "Parameter",
146
- required,
147
- schema: syncPropertySchema(required, props.schema)
148
- };
155
+ /** Returns a `CLIAdapter` with type inference. Pass a different adapter to `createCLI` to swap the CLI engine. */
156
+ function defineCLIAdapter(adapter) {
157
+ return adapter;
149
158
  }
150
159
  /**
151
- * Creates a `ResponseNode`.
160
+ * Serializes `CommandDefinition[]` to a plain, JSON-serializable structure.
161
+ * Use to expose CLI capabilities to AI agents or MCP tools.
152
162
  */
153
- function createResponse(props) {
154
- return {
155
- ...props,
156
- kind: "Response"
157
- };
163
+ function getCommandSchema(defs) {
164
+ return defs.map(serializeCommand);
158
165
  }
159
- /**
160
- * Creates a `FunctionParameterNode`. `optional` defaults to `false`.
161
- *
162
- * @example Required typed param
163
- * ```ts
164
- * createFunctionParameter({ name: 'petId', type: 'string' })
165
- * // → petId: string
166
- * ```
167
- *
168
- * @example Optional param
169
- * ```ts
170
- * createFunctionParameter({ name: 'params', type: 'QueryParams', optional: true })
171
- * // → params?: QueryParams
172
- * ```
173
- *
174
- * @example Param with default (implicitly optional — cannot combine with `optional: true`)
175
- * ```ts
176
- * createFunctionParameter({ name: 'config', type: 'RequestConfig', default: '{}' })
177
- * // → config: RequestConfig = {}
178
- * ```
179
- */
180
- function createFunctionParameter(props) {
166
+ function serializeCommand(def) {
181
167
  return {
182
- optional: false,
183
- ...props,
184
- kind: "FunctionParameter"
168
+ name: def.name,
169
+ description: def.description,
170
+ arguments: def.arguments,
171
+ options: serializeOptions(def.options ?? {}),
172
+ subCommands: def.subCommands ? def.subCommands.map(serializeCommand) : []
185
173
  };
186
174
  }
187
- /**
188
- * Creates an `ObjectBindingParameterNode` — an object-destructured parameter group.
189
- *
190
- * @example Destructured object param
191
- * ```ts
192
- * createObjectBindingParameter({
193
- * properties: [
194
- * createFunctionParameter({ name: 'id', type: 'string', optional: false }),
195
- * createFunctionParameter({ name: 'name', type: 'string', optional: true }),
196
- * ],
197
- * default: '{}',
198
- * })
199
- * // declaration → { id, name? }: { id: string; name?: string } = {}
200
- * // call → { id, name }
201
- * ```
202
- *
203
- * @example Inline — children emitted as individual top-level params
204
- * ```ts
205
- * createObjectBindingParameter({
206
- * properties: [createFunctionParameter({ name: 'petId', type: 'string', optional: false })],
207
- * inline: true,
208
- * })
209
- * // declaration → petId: string
210
- * // call → petId
211
- * ```
212
- */
213
- function createObjectBindingParameter(props) {
214
- return {
215
- ...props,
216
- kind: "ObjectBindingParameter"
217
- };
175
+ function serializeOptions(options) {
176
+ return Object.entries(options).map(([name, opt]) => {
177
+ return {
178
+ name,
179
+ flags: `${opt.short ? `-${opt.short}, ` : ""}--${name}${opt.type === "string" ? ` <${opt.hint ?? name}>` : ""}`,
180
+ type: opt.type,
181
+ description: opt.description,
182
+ ...opt.default !== void 0 ? { default: opt.default } : {},
183
+ ...opt.hint ? { hint: opt.hint } : {},
184
+ ...opt.enum ? { enum: opt.enum } : {},
185
+ ...opt.required ? { required: opt.required } : {}
186
+ };
187
+ });
218
188
  }
219
- /**
220
- * Creates a `FunctionParametersNode` from an ordered list of params.
221
- *
222
- * @example
223
- * ```ts
224
- * createFunctionParameters({
225
- * params: [
226
- * createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
227
- * createFunctionParameter({ name: 'config', type: 'RequestConfig', optional: false, default: '{}' }),
228
- * ],
229
- * })
230
- * ```
231
- */
232
- function createFunctionParameters(props = {}) {
233
- return {
234
- params: [],
235
- ...props,
236
- kind: "FunctionParameters"
189
+ /** Prints formatted help output for a command using its `CommandDefinition`. */
190
+ function renderHelp(def, parentName) {
191
+ const schema = getCommandSchema([def])[0];
192
+ const programName = parentName ? `${parentName} ${schema.name}` : schema.name;
193
+ const argsPart = schema.arguments?.length ? ` ${schema.arguments.join(" ")}` : "";
194
+ const subCmdPart = schema.subCommands.length ? " <command>" : "";
195
+ console.log(`\n${styleText("bold", "Usage:")} ${programName}${argsPart}${subCmdPart} [options]\n`);
196
+ if (schema.description) console.log(` ${schema.description}\n`);
197
+ if (schema.subCommands.length) {
198
+ console.log(styleText("bold", "Commands:"));
199
+ for (const sub of schema.subCommands) console.log(` ${styleText("cyan", sub.name.padEnd(16))}${sub.description}`);
200
+ console.log();
201
+ }
202
+ const options = [...schema.options, {
203
+ name: "help",
204
+ flags: "-h, --help",
205
+ type: "boolean",
206
+ description: "Show help"
207
+ }];
208
+ console.log(styleText("bold", "Options:"));
209
+ for (const opt of options) {
210
+ const flags = styleText("cyan", opt.flags.padEnd(30));
211
+ const defaultPart = opt.default !== void 0 ? styleText("dim", ` (default: ${opt.default})`) : "";
212
+ console.log(` ${flags}${opt.description}${defaultPart}`);
213
+ }
214
+ console.log();
215
+ }
216
+ function buildParseOptions(def) {
217
+ const result = { help: {
218
+ type: "boolean",
219
+ short: "h"
220
+ } };
221
+ for (const [name, opt] of Object.entries(def.options ?? {})) result[name] = {
222
+ type: opt.type,
223
+ ...opt.short ? { short: opt.short } : {},
224
+ ...opt.default !== void 0 ? { default: opt.default } : {}
237
225
  };
226
+ return result;
238
227
  }
239
- //#endregion
240
- //#region src/printer.ts
241
- /**
242
- * Creates a named printer factory. Mirrors the `createPlugin` / `createAdapter` pattern
243
- * from `@kubb/core` — wraps a builder to make options optional and separates raw options
244
- * from resolved options.
245
- *
246
- * The builder receives resolved options and returns:
247
- * - `name` — a unique identifier for the printer
248
- * - `options` — options stored on the returned printer instance
249
- * - `nodes` — a map of `SchemaType` → handler functions that convert a `SchemaNode` to `TOutput`
250
- * - `print` _(optional)_ — a root-level override that becomes the public `printer.print`.
251
- * Inside it, `this.print(node)` still dispatches to the `nodes` map — safe recursion, no infinite loop.
252
- *
253
- * When no `print` override is provided, `printer.print` is the node-level dispatcher directly.
254
- *
255
- * @example Basic usage — Zod schema printer
256
- * ```ts
257
- * type ZodPrinter = PrinterFactoryOptions<'zod', { strict?: boolean }, string>
258
- *
259
- * export const zodPrinter = definePrinter<ZodPrinter>((options) => ({
260
- * name: 'zod',
261
- * options: { strict: options.strict ?? true },
262
- * nodes: {
263
- * string: () => 'z.string()',
264
- * object(node) {
265
- * const props = node.properties.map(p => `${p.name}: ${this.print(p.schema)}`).join(', ')
266
- * return `z.object({ ${props} })`
267
- * },
268
- * },
269
- * }))
270
- * ```
228
+ async function runCommand(def, argv, parentName) {
229
+ const parseOptions = buildParseOptions(def);
230
+ let parsed;
231
+ try {
232
+ const result = parseArgs({
233
+ args: argv,
234
+ options: parseOptions,
235
+ allowPositionals: true,
236
+ strict: false
237
+ });
238
+ parsed = {
239
+ values: result.values,
240
+ positionals: result.positionals
241
+ };
242
+ } catch {
243
+ renderHelp(def, parentName);
244
+ process.exit(1);
245
+ }
246
+ if (parsed.values["help"]) {
247
+ renderHelp(def, parentName);
248
+ process.exit(0);
249
+ }
250
+ for (const [name, opt] of Object.entries(def.options ?? {})) if (opt.required && parsed.values[name] === void 0) {
251
+ console.error(styleText("red", `Error: --${name} is required`));
252
+ renderHelp(def, parentName);
253
+ process.exit(1);
254
+ }
255
+ if (!def.run) {
256
+ renderHelp(def, parentName);
257
+ process.exit(0);
258
+ }
259
+ try {
260
+ await def.run(parsed);
261
+ } catch (err) {
262
+ console.error(styleText("red", `Error: ${err instanceof Error ? err.message : String(err)}`));
263
+ renderHelp(def, parentName);
264
+ process.exit(1);
265
+ }
266
+ }
267
+ function printRootHelp(programName, version, defs) {
268
+ console.log(`\n${styleText("bold", "Usage:")} ${programName} <command> [options]\n`);
269
+ console.log(` Kubb generation — v${version}\n`);
270
+ console.log(styleText("bold", "Commands:"));
271
+ for (const def of defs) console.log(` ${styleText("cyan", def.name.padEnd(16))}${def.description}`);
272
+ console.log();
273
+ console.log(styleText("bold", "Options:"));
274
+ console.log(` ${styleText("cyan", "-v, --version".padEnd(30))}Show version number`);
275
+ console.log(` ${styleText("cyan", "-h, --help".padEnd(30))}Show help`);
276
+ console.log();
277
+ console.log(`Run ${styleText("cyan", `${programName} <command> --help`)} for command-specific help.\n`);
278
+ }
279
+ defineCLIAdapter({
280
+ renderHelp(def, parentName) {
281
+ renderHelp(def, parentName);
282
+ },
283
+ async run(defs, argv, opts) {
284
+ const { programName, defaultCommandName, version } = opts;
285
+ const args = argv.length >= 2 && argv[0]?.includes("node") ? argv.slice(2) : argv;
286
+ if (args[0] === "--version" || args[0] === "-v") {
287
+ console.log(version);
288
+ process.exit(0);
289
+ }
290
+ if (args[0] === "--help" || args[0] === "-h") {
291
+ printRootHelp(programName, version, defs);
292
+ process.exit(0);
293
+ }
294
+ if (args.length === 0) {
295
+ const defaultDef = defs.find((d) => d.name === defaultCommandName);
296
+ if (defaultDef?.run) await runCommand(defaultDef, [], programName);
297
+ else printRootHelp(programName, version, defs);
298
+ return;
299
+ }
300
+ const [first, ...rest] = args;
301
+ const isKnownSubcommand = defs.some((d) => d.name === first);
302
+ let def;
303
+ let commandArgv;
304
+ let parentName;
305
+ if (isKnownSubcommand) {
306
+ def = defs.find((d) => d.name === first);
307
+ commandArgv = rest;
308
+ parentName = programName;
309
+ } else {
310
+ def = defs.find((d) => d.name === defaultCommandName);
311
+ commandArgv = args;
312
+ parentName = programName;
313
+ }
314
+ if (!def) {
315
+ console.error(`Unknown command: ${first}`);
316
+ printRootHelp(programName, version, defs);
317
+ process.exit(1);
318
+ }
319
+ if (def.subCommands?.length) {
320
+ const [subName, ...subRest] = commandArgv;
321
+ const subDef = def.subCommands.find((s) => s.name === subName);
322
+ if (subName === "--help" || subName === "-h") {
323
+ renderHelp(def, parentName);
324
+ process.exit(0);
325
+ }
326
+ if (!subDef) {
327
+ renderHelp(def, parentName);
328
+ process.exit(subName ? 1 : 0);
329
+ }
330
+ await runCommand(subDef, subRest, `${parentName} ${def.name}`);
331
+ return;
332
+ }
333
+ await runCommand(def, commandArgv, parentName);
334
+ }
335
+ });
336
+ /**
337
+ * Parses a CSS hex color string (`#RGB`) into its RGB channels.
338
+ * Falls back to `255` for any channel that cannot be parsed.
339
+ */
340
+ function parseHex(color) {
341
+ const int = Number.parseInt(color.replace("#", ""), 16);
342
+ return Number.isNaN(int) ? {
343
+ r: 255,
344
+ g: 255,
345
+ b: 255
346
+ } : {
347
+ r: int >> 16 & 255,
348
+ g: int >> 8 & 255,
349
+ b: int & 255
350
+ };
351
+ }
352
+ /**
353
+ * Returns a function that wraps a string in a 24-bit ANSI true-color escape sequence
354
+ * for the given hex color.
355
+ */
356
+ function hex(color) {
357
+ const { r, g, b } = parseHex(color);
358
+ return (text) => `\x1b[38;2;${r};${g};${b}m${text}\x1b[0m`;
359
+ }
360
+ hex("#F55A17"), hex("#F5A217"), hex("#F58517"), hex("#B45309"), hex("#FFFFFF"), hex("#adadc6"), hex("#FDA4AF");
361
+ /**
362
+ * Returns `true` when `name` is a syntactically valid JavaScript variable name.
363
+ */
364
+ function isValidVarName(name) {
365
+ try {
366
+ new Function(`var ${name}`);
367
+ } catch {
368
+ return false;
369
+ }
370
+ return true;
371
+ }
372
+ //#endregion
373
+ //#region src/guards.ts
374
+ /**
375
+ * Narrows a `SchemaNode` to the variant that matches `type`.
271
376
  *
272
- * @example With a root-level `print` override to wrap output in a full declaration
377
+ * @example
273
378
  * ```ts
274
- * type TsPrinter = PrinterFactoryOptions<'ts', { typeName?: string }, ts.TypeNode, ts.Node>
275
- *
276
- * export const printerTs = definePrinter<TsPrinter>((options) => ({
277
- * name: 'ts',
278
- * options,
279
- * nodes: { string: () => factory.keywordTypeNodes.string },
280
- * print(node) {
281
- * const type = this.print(node) // calls the node-level dispatcher
282
- * if (!type || !this.options.typeName) return type
283
- * return factory.createTypeAliasDeclaration(this.options.typeName, type)
284
- * },
285
- * }))
379
+ * const schema = createSchema({ type: 'string' })
380
+ * const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | undefined
286
381
  * ```
287
382
  */
288
- function definePrinter(build) {
289
- return createPrinterFactory((node) => node.type)(build);
383
+ function narrowSchema(node, type) {
384
+ return node?.type === type ? node : void 0;
385
+ }
386
+ function isKind(kind) {
387
+ return (node) => node.kind === kind;
290
388
  }
291
389
  /**
292
- * Generic printer factory. Extracts the core dispatch + context logic so it can be reused
293
- * for any node type — not just `SchemaNode`. `definePrinter` is built on top of this.
294
- *
295
- * @param getKey — derives the handler-map key from a node. Return `undefined` to skip.
390
+ * Returns `true` when the input is a `RootNode`.
296
391
  *
297
392
  * @example
298
393
  * ```ts
299
- * export const defineFunctionPrinter = createPrinterFactory<FunctionNode, FunctionNodeType, FunctionNodeByType>(
300
- * (node) => kindToHandlerKey[node.kind],
301
- * )
394
+ * if (isRootNode(node)) {
395
+ * console.log(node.schemas.length)
396
+ * }
302
397
  * ```
303
398
  */
304
- function createPrinterFactory(getKey) {
305
- return function(build) {
306
- return (options) => {
307
- const { name, options: resolvedOptions, nodes, print: printOverride } = build(options ?? {});
308
- const context = {
309
- options: resolvedOptions,
310
- print: (node) => {
311
- const key = getKey(node);
312
- if (key === void 0) return void 0;
313
- const handler = nodes[key];
314
- if (!handler) return void 0;
315
- return handler.call(context, node);
316
- }
317
- };
318
- return {
319
- name,
320
- options: resolvedOptions,
321
- print: printOverride ? printOverride.bind(context) : context.print
322
- };
323
- };
324
- };
325
- }
326
- //#endregion
327
- //#region src/functionPrinter.ts
328
- const kindToHandlerKey = {
329
- FunctionParameter: "functionParameter",
330
- ObjectBindingParameter: "objectBindingParameter",
331
- FunctionParameters: "functionParameters"
332
- };
399
+ const isRootNode = isKind("Root");
333
400
  /**
334
- * Creates a named function-signature printer factory.
335
- * Built on `createPrinterFactory` — dispatches on `node.kind` instead of `node.type`.
401
+ * Returns `true` when the input is an `OperationNode`.
336
402
  *
337
403
  * @example
338
404
  * ```ts
339
- * type MyPrinter = PrinterFactoryOptions<'my', { mode: 'declaration' | 'call' }, string>
340
- *
341
- * export const myPrinter = defineFunctionPrinter<MyPrinter>((options) => ({
342
- * name: 'my',
343
- * options,
344
- * nodes: {
345
- * functionParameter(node) {
346
- * return options.mode === 'declaration' && node.type ? `${node.name}: ${node.type}` : node.name
347
- * },
348
- * objectBindingParameter(node) {
349
- * const inner = node.properties.map(p => this.print(p)).filter(Boolean).join(', ')
350
- * return `{ ${inner} }`
351
- * },
352
- * functionParameters(node) {
353
- * return node.params.map(p => this.print(p)).filter(Boolean).join(', ')
354
- * },
355
- * },
356
- * }))
405
+ * if (isOperationNode(node)) {
406
+ * console.log(node.operationId)
407
+ * }
357
408
  * ```
358
409
  */
359
- const defineFunctionPrinter = createPrinterFactory((node) => kindToHandlerKey[node.kind]);
360
- function rank(param) {
361
- if (param.kind === "ObjectBindingParameter") {
362
- if (param.default) return 2;
363
- return param.optional ?? param.properties.every((p) => p.optional || p.default !== void 0) ? 1 : 0;
364
- }
365
- if (param.rest) return 3;
366
- if (param.default) return 2;
367
- return param.optional ? 1 : 0;
368
- }
369
- function sortParams(params) {
370
- return [...params].sort((a, b) => rank(a) - rank(b));
371
- }
372
- function sortChildParams(params) {
373
- return [...params].sort((a, b) => rank(a) - rank(b));
374
- }
410
+ const isOperationNode = isKind("Operation");
375
411
  /**
376
- * Default function-signature printer. Covers the four standard output modes
377
- * used throughout Kubb plugins.
412
+ * Returns `true` when the input is a `SchemaNode`.
378
413
  *
379
414
  * @example
380
415
  * ```ts
381
- * const printer = functionSignaturePrinter({ mode: 'declaration' })
382
- *
383
- * const sig = createFunctionParameters({
384
- * params: [
385
- * createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
386
- * createFunctionParameter({ name: 'config', type: 'Config', optional: false, default: '{}' }),
387
- * ],
388
- * })
389
- *
390
- * printer.print(sig) // → "petId: string, config: Config = {}"
416
+ * if (isSchemaNode(node)) {
417
+ * console.log(node.type)
418
+ * }
391
419
  * ```
392
420
  */
393
- const functionPrinter = defineFunctionPrinter((options) => ({
394
- name: "functionParameters",
395
- options,
396
- nodes: {
397
- functionParameter(node) {
398
- const { mode, transformName, transformType } = this.options;
399
- const name = transformName ? transformName(node.name) : node.name;
400
- const type = node.type && transformType ? transformType(node.type) : node.type;
401
- if (mode === "keys" || mode === "values") return node.rest ? `...${name}` : name;
402
- if (mode === "call") return node.rest ? `...${name}` : name;
403
- if (node.rest) return type ? `...${name}: ${type}` : `...${name}`;
404
- if (type) {
405
- if (node.optional) return `${name}?: ${type}`;
406
- return node.default ? `${name}: ${type} = ${node.default}` : `${name}: ${type}`;
407
- }
408
- return node.default ? `${name} = ${node.default}` : name;
409
- },
410
- objectBindingParameter(node) {
411
- const { mode, transformName, transformType } = this.options;
412
- const sorted = sortChildParams(node.properties);
413
- const isOptional = node.optional ?? sorted.every((p) => p.optional || p.default !== void 0);
414
- if (node.inline) return sorted.map((p) => this.print(p)).filter(Boolean).join(", ");
415
- if (mode === "keys" || mode === "values") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
416
- if (mode === "call") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
417
- const names = sorted.map((p) => {
418
- return transformName ? transformName(p.name) : p.name;
419
- });
420
- const nameStr = names.length ? `{ ${names.join(", ")} }` : void 0;
421
- if (!nameStr) return null;
422
- let typeAnnotation = node.type;
423
- if (!typeAnnotation) {
424
- const typeParts = sorted.filter((p) => p.type).map((p) => {
425
- const t = transformType && p.type ? transformType(p.type) : p.type;
426
- return p.optional || p.default !== void 0 ? `${p.name}?: ${t}` : `${p.name}: ${t}`;
427
- });
428
- typeAnnotation = typeParts.length ? `{ ${typeParts.join("; ")} }` : void 0;
429
- }
430
- if (typeAnnotation) {
431
- if (isOptional) return `${nameStr}: ${typeAnnotation} = ${node.default ?? "{}"}`;
432
- return node.default ? `${nameStr}: ${typeAnnotation} = ${node.default}` : `${nameStr}: ${typeAnnotation}`;
433
- }
434
- return node.default ? `${nameStr} = ${node.default}` : nameStr;
435
- },
436
- functionParameters(node) {
437
- return sortParams(node.params).map((p) => this.print(p)).filter(Boolean).join(", ");
438
- }
439
- }
440
- }));
441
- //#endregion
442
- //#region src/guards.ts
443
- /**
444
- * Narrows a `SchemaNode` to the specific variant matching `type`.
445
- */
446
- function narrowSchema(node, type) {
447
- return node?.type === type ? node : void 0;
448
- }
449
- function isKind(kind) {
450
- return (node) => node.kind === kind;
451
- }
452
- /**
453
- * Type guard for `RootNode`.
454
- */
455
- const isRootNode = isKind("Root");
456
- /**
457
- * Type guard for `OperationNode`.
458
- */
459
- const isOperationNode = isKind("Operation");
460
- /**
461
- * Type guard for `SchemaNode`.
462
- */
463
421
  const isSchemaNode = isKind("Schema");
464
422
  /**
465
- * Type guard for `PropertyNode`.
423
+ * Returns `true` when the input is a `PropertyNode`.
466
424
  */
467
425
  const isPropertyNode = isKind("Property");
468
426
  /**
469
- * Type guard for `ParameterNode`.
427
+ * Returns `true` when the input is a `ParameterNode`.
470
428
  */
471
429
  const isParameterNode = isKind("Parameter");
472
430
  /**
473
- * Type guard for `ResponseNode`.
431
+ * Returns `true` when the input is a `ResponseNode`.
474
432
  */
475
433
  const isResponseNode = isKind("Response");
476
434
  /**
477
- * Type guard for `FunctionParameterNode`.
435
+ * Returns `true` when the input is a `FunctionParameterNode`.
478
436
  */
479
437
  const isFunctionParameterNode = isKind("FunctionParameter");
480
438
  /**
481
- * Type guard for `ObjectBindingParameterNode`.
439
+ * Returns `true` when the input is an `ObjectBindingParameterNode`.
482
440
  */
483
441
  const isObjectBindingParameterNode = isKind("ObjectBindingParameter");
484
442
  /**
485
- * Type guard for `FunctionParametersNode`.
443
+ * Returns `true` when the input is a `FunctionParametersNode`.
486
444
  */
487
445
  const isFunctionParametersNode = isKind("FunctionParameters");
488
446
  //#endregion
489
- //#region src/refs.ts
447
+ //#region src/utils.ts
448
+ const plainStringTypes = new Set([
449
+ "string",
450
+ "uuid",
451
+ "email",
452
+ "url",
453
+ "datetime"
454
+ ]);
490
455
  /**
491
- * Indexes named schemas from `root.schemas` by name. Unnamed schemas are skipped.
456
+ * Returns `true` when a schema is emitted as a plain TypeScript `string`.
457
+ *
458
+ * - `string`, `uuid`, `email`, `url`, `datetime` are always plain strings.
459
+ * - `date` and `time` are plain strings when their `representation` is `'string'` rather than `'date'`.
460
+ *
461
+ * @example
462
+ * ```ts
463
+ * isStringType(createSchema({ type: 'uuid' })) // true
464
+ * isStringType(createSchema({ type: 'date', representation: 'date' })) // false
465
+ * ```
492
466
  */
493
- function buildRefMap(root) {
494
- const map = /* @__PURE__ */ new Map();
495
- for (const schema of root.schemas) if (schema.name) map.set(schema.name, schema);
496
- return map;
467
+ function isStringType(node) {
468
+ if (plainStringTypes.has(node.type)) return true;
469
+ const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
470
+ if (temporal) return temporal.representation !== "date";
471
+ return false;
497
472
  }
498
473
  /**
499
- * Looks up a schema by name. Prefer over `RefMap.get()` to keep the resolution strategy swappable.
474
+ * Applies casing rules to parameter names and returns a new parameter array.
475
+ *
476
+ * The input array is not mutated.
477
+ * If `casing` is not set, the original array is returned unchanged.
478
+ *
479
+ * Use this before passing parameters to schema builders so that property keys
480
+ * in generated output match the desired casing while preserving
481
+ * `OperationNode.parameters` for other consumers.
482
+ *
483
+ * @example
484
+ * ```ts
485
+ * const params = [createParameter({ name: 'pet_id', in: 'query', schema: createSchema({ type: 'string' }) })]
486
+ * const cased = caseParams(params, 'camelcase')
487
+ * // cased[0].name === 'petId'
488
+ * ```
500
489
  */
501
- function resolveRef(refMap, ref) {
502
- return refMap.get(ref);
490
+ function caseParams(params, casing) {
491
+ if (!casing) return params;
492
+ return params.map((param) => {
493
+ const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
494
+ return {
495
+ ...param,
496
+ name: transformed
497
+ };
498
+ });
503
499
  }
504
500
  /**
505
- * Converts a `RefMap` to a plain object.
501
+ * Syncs property/parameter schema optionality flags from `required` and `schema.nullable`.
502
+ *
503
+ * - `optional` is set for non-required, non-nullable schemas.
504
+ * - `nullish` is set for non-required, nullable schemas.
506
505
  */
507
- function refMapToObject(refMap) {
508
- return Object.fromEntries(refMap);
506
+ function syncOptionality(required, schema) {
507
+ const nullable = schema.nullable ?? false;
508
+ return {
509
+ ...schema,
510
+ optional: !required && !nullable ? true : void 0,
511
+ nullish: !required && nullable ? true : void 0
512
+ };
509
513
  }
510
514
  //#endregion
511
- //#region ../../internals/utils/dist/index.js
515
+ //#region src/factory.ts
512
516
  /**
513
- * Shared implementation for camelCase and PascalCase conversion.
514
- * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
515
- * and capitalizes each word according to `pascal`.
517
+ * Creates a `RootNode` with stable defaults for `schemas` and `operations`.
516
518
  *
517
- * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
519
+ * @example
520
+ * ```ts
521
+ * const root = createRoot()
522
+ * // { kind: 'Root', schemas: [], operations: [] }
523
+ * ```
524
+ *
525
+ * @example
526
+ * ```ts
527
+ * const root = createRoot({ schemas: [petSchema] })
528
+ * // keeps default operations: []
529
+ * ```
518
530
  */
519
- function toCamelOrPascal(text, pascal) {
520
- return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
521
- if (word.length > 1 && word === word.toUpperCase()) return word;
522
- if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
523
- return word.charAt(0).toUpperCase() + word.slice(1);
524
- }).join("").replace(/[^a-zA-Z0-9]/g, "");
531
+ function createRoot(overrides = {}) {
532
+ return {
533
+ schemas: [],
534
+ operations: [],
535
+ ...overrides,
536
+ kind: "Root"
537
+ };
525
538
  }
526
539
  /**
527
- * Splits `text` on `.` and applies `transformPart` to each segment.
528
- * The last segment receives `isLast = true`, all earlier segments receive `false`.
529
- * Segments are joined with `/` to form a file path.
540
+ * Creates an `OperationNode` with default empty arrays for `tags`, `parameters`, and `responses`.
530
541
  *
531
- * Only splits on dots followed by a letter so that version numbers
532
- * embedded in operationIds (e.g. `v2025.0`) are kept intact.
542
+ * @example
543
+ * ```ts
544
+ * const operation = createOperation({
545
+ * operationId: 'getPetById',
546
+ * method: 'GET',
547
+ * path: '/pet/{petId}',
548
+ * })
549
+ * // tags, parameters, and responses are []
550
+ * ```
551
+ *
552
+ * @example
553
+ * ```ts
554
+ * const operation = createOperation({
555
+ * operationId: 'findPets',
556
+ * method: 'GET',
557
+ * path: '/pet/findByStatus',
558
+ * tags: ['pet'],
559
+ * })
560
+ * ```
533
561
  */
534
- function applyToFileParts(text, transformPart) {
535
- const parts = text.split(/\.(?=[a-zA-Z])/);
536
- return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
562
+ function createOperation(props) {
563
+ return {
564
+ tags: [],
565
+ parameters: [],
566
+ responses: [],
567
+ ...props,
568
+ kind: "Operation"
569
+ };
570
+ }
571
+ function createSchema(props) {
572
+ if (props["type"] === "object") return {
573
+ properties: [],
574
+ ...props,
575
+ kind: "Schema"
576
+ };
577
+ return {
578
+ ...props,
579
+ kind: "Schema"
580
+ };
537
581
  }
538
582
  /**
539
- * Converts `text` to camelCase.
540
- * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
583
+ * Creates a `PropertyNode`.
584
+ *
585
+ * `required` defaults to `false`.
586
+ * `schema.optional` and `schema.nullish` are derived from `required` and `schema.nullable`.
541
587
  *
542
588
  * @example
543
- * camelCase('hello-world') // 'helloWorld'
544
- * camelCase('pet.petId', { isFile: true }) // 'pet/petId'
589
+ * ```ts
590
+ * const property = createProperty({
591
+ * name: 'status',
592
+ * schema: createSchema({ type: 'string' }),
593
+ * })
594
+ * // required=false, schema.optional=true
595
+ * ```
596
+ *
597
+ * @example
598
+ * ```ts
599
+ * const property = createProperty({
600
+ * name: 'status',
601
+ * required: true,
602
+ * schema: createSchema({ type: 'string', nullable: true }),
603
+ * })
604
+ * // required=true, no optional/nullish
605
+ * ```
545
606
  */
546
- function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
547
- if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
548
- prefix,
549
- suffix
550
- } : {}));
551
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
552
- }
553
- /** Returns a `CLIAdapter` with type inference. Pass a different adapter to `createCLI` to swap the CLI engine. */
554
- function defineCLIAdapter(adapter) {
555
- return adapter;
607
+ function createProperty(props) {
608
+ const required = props.required ?? false;
609
+ return {
610
+ ...props,
611
+ kind: "Property",
612
+ required,
613
+ schema: syncOptionality(required, props.schema)
614
+ };
556
615
  }
557
616
  /**
558
- * Serializes `CommandDefinition[]` to a plain, JSON-serializable structure.
559
- * Use to expose CLI capabilities to AI agents or MCP tools.
617
+ * Creates a `ParameterNode`.
618
+ *
619
+ * `required` defaults to `false`.
620
+ * Nested schema flags are set from `required` and `schema.nullable`.
621
+ *
622
+ * @example
623
+ * ```ts
624
+ * const param = createParameter({
625
+ * name: 'petId',
626
+ * in: 'path',
627
+ * required: true,
628
+ * schema: createSchema({ type: 'string' }),
629
+ * })
630
+ * ```
631
+ *
632
+ * @example
633
+ * ```ts
634
+ * const param = createParameter({
635
+ * name: 'status',
636
+ * in: 'query',
637
+ * schema: createSchema({ type: 'string', nullable: true }),
638
+ * })
639
+ * // required=false, schema.nullish=true
640
+ * ```
560
641
  */
561
- function getCommandSchema(defs) {
562
- return defs.map(serializeCommand);
642
+ function createParameter(props) {
643
+ const required = props.required ?? false;
644
+ return {
645
+ ...props,
646
+ kind: "Parameter",
647
+ required,
648
+ schema: syncOptionality(required, props.schema)
649
+ };
563
650
  }
564
- function serializeCommand(def) {
651
+ /**
652
+ * Creates a `ResponseNode`.
653
+ *
654
+ * @example
655
+ * ```ts
656
+ * const response = createResponse({
657
+ * statusCode: '200',
658
+ * description: 'Success',
659
+ * schema: createSchema({ type: 'object', properties: [] }),
660
+ * })
661
+ * ```
662
+ */
663
+ function createResponse(props) {
565
664
  return {
566
- name: def.name,
567
- description: def.description,
568
- arguments: def.arguments,
569
- options: serializeOptions(def.options ?? {}),
570
- subCommands: def.subCommands ? def.subCommands.map(serializeCommand) : []
665
+ ...props,
666
+ kind: "Response"
571
667
  };
572
668
  }
573
- function serializeOptions(options) {
574
- return Object.entries(options).map(([name, opt]) => {
575
- return {
576
- name,
577
- flags: `${opt.short ? `-${opt.short}, ` : ""}--${name}${opt.type === "string" ? ` <${opt.hint ?? name}>` : ""}`,
578
- type: opt.type,
579
- description: opt.description,
580
- ...opt.default !== void 0 ? { default: opt.default } : {},
581
- ...opt.hint ? { hint: opt.hint } : {},
582
- ...opt.enum ? { enum: opt.enum } : {},
583
- ...opt.required ? { required: opt.required } : {}
584
- };
669
+ /**
670
+ * Creates a single-property object schema used as a discriminator literal.
671
+ *
672
+ * @example
673
+ * ```ts
674
+ * createDiscriminantNode({ propertyName: 'type', value: 'dog' })
675
+ * // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
676
+ * ```
677
+ */
678
+ function createDiscriminantNode({ propertyName, value }) {
679
+ return createSchema({
680
+ type: "object",
681
+ primitive: "object",
682
+ properties: [createProperty({
683
+ name: propertyName,
684
+ schema: createSchema({
685
+ type: "enum",
686
+ primitive: "string",
687
+ enumValues: [value]
688
+ }),
689
+ required: true
690
+ })]
585
691
  });
586
692
  }
587
- /** Prints formatted help output for a command using its `CommandDefinition`. */
588
- function renderHelp(def, parentName) {
589
- const schema = getCommandSchema([def])[0];
590
- const programName = parentName ? `${parentName} ${schema.name}` : schema.name;
591
- const argsPart = schema.arguments?.length ? ` ${schema.arguments.join(" ")}` : "";
592
- const subCmdPart = schema.subCommands.length ? " <command>" : "";
593
- console.log(`\n${styleText("bold", "Usage:")} ${programName}${argsPart}${subCmdPart} [options]\n`);
594
- if (schema.description) console.log(` ${schema.description}\n`);
595
- if (schema.subCommands.length) {
596
- console.log(styleText("bold", "Commands:"));
597
- for (const sub of schema.subCommands) console.log(` ${styleText("cyan", sub.name.padEnd(16))}${sub.description}`);
598
- console.log();
599
- }
600
- const options = [...schema.options, {
601
- name: "help",
602
- flags: "-h, --help",
603
- type: "boolean",
604
- description: "Show help"
605
- }];
606
- console.log(styleText("bold", "Options:"));
607
- for (const opt of options) {
608
- const flags = styleText("cyan", opt.flags.padEnd(30));
609
- const defaultPart = opt.default !== void 0 ? styleText("dim", ` (default: ${opt.default})`) : "";
610
- console.log(` ${flags}${opt.description}${defaultPart}`);
611
- }
612
- console.log();
693
+ /**
694
+ * Creates a `FunctionParameterNode`.
695
+ *
696
+ * `optional` defaults to `false`.
697
+ *
698
+ * @example Required typed param
699
+ * ```ts
700
+ * createFunctionParameter({ name: 'petId', type: 'string' })
701
+ * // → petId: string
702
+ * ```
703
+ *
704
+ * @example Optional param
705
+ * ```ts
706
+ * createFunctionParameter({ name: 'params', type: 'QueryParams', optional: true })
707
+ * // → params?: QueryParams
708
+ * ```
709
+ *
710
+ * @example Param with default (implicitly optional; cannot combine with `optional: true`)
711
+ * ```ts
712
+ * createFunctionParameter({ name: 'config', type: 'RequestConfig', default: '{}' })
713
+ * // config: RequestConfig = {}
714
+ * ```
715
+ */
716
+ function createFunctionParameter(props) {
717
+ return {
718
+ optional: false,
719
+ ...props,
720
+ kind: "FunctionParameter"
721
+ };
613
722
  }
614
- function buildParseOptions(def) {
615
- const result = { help: {
616
- type: "boolean",
617
- short: "h"
618
- } };
619
- for (const [name, opt] of Object.entries(def.options ?? {})) result[name] = {
620
- type: opt.type,
621
- ...opt.short ? { short: opt.short } : {},
622
- ...opt.default !== void 0 ? { default: opt.default } : {}
723
+ /**
724
+ * Creates an `ObjectBindingParameterNode` for object-destructured parameter groups.
725
+ *
726
+ * @example Destructured object param
727
+ * ```ts
728
+ * createObjectBindingParameter({
729
+ * properties: [
730
+ * createFunctionParameter({ name: 'id', type: 'string', optional: false }),
731
+ * createFunctionParameter({ name: 'name', type: 'string', optional: true }),
732
+ * ],
733
+ * default: '{}',
734
+ * })
735
+ * // declaration → { id, name? }: { id: string; name?: string } = {}
736
+ * // call → { id, name }
737
+ * ```
738
+ *
739
+ * @example Inline mode — children emitted as individual top-level parameters
740
+ * ```ts
741
+ * createObjectBindingParameter({
742
+ * properties: [createFunctionParameter({ name: 'petId', type: 'string', optional: false })],
743
+ * inline: true,
744
+ * })
745
+ * // declaration → petId: string
746
+ * // call → petId
747
+ * ```
748
+ */
749
+ function createObjectBindingParameter(props) {
750
+ return {
751
+ ...props,
752
+ kind: "ObjectBindingParameter"
623
753
  };
624
- return result;
625
754
  }
626
- async function runCommand(def, argv, parentName) {
627
- const parseOptions = buildParseOptions(def);
628
- let parsed;
629
- try {
630
- const result = parseArgs({
631
- args: argv,
632
- options: parseOptions,
633
- allowPositionals: true,
634
- strict: false
635
- });
636
- parsed = {
637
- values: result.values,
638
- positionals: result.positionals
639
- };
640
- } catch {
641
- renderHelp(def, parentName);
642
- process.exit(1);
643
- }
644
- if (parsed.values["help"]) {
645
- renderHelp(def, parentName);
646
- process.exit(0);
647
- }
648
- for (const [name, opt] of Object.entries(def.options ?? {})) if (opt.required && parsed.values[name] === void 0) {
649
- console.error(styleText("red", `Error: --${name} is required`));
650
- renderHelp(def, parentName);
651
- process.exit(1);
652
- }
653
- if (!def.run) {
654
- renderHelp(def, parentName);
655
- process.exit(0);
656
- }
657
- try {
658
- await def.run(parsed);
659
- } catch (err) {
660
- console.error(styleText("red", `Error: ${err instanceof Error ? err.message : String(err)}`));
661
- renderHelp(def, parentName);
662
- process.exit(1);
663
- }
755
+ /**
756
+ * Creates a `FunctionParametersNode` from an ordered list of parameters.
757
+ *
758
+ * @example
759
+ * ```ts
760
+ * createFunctionParameters({
761
+ * params: [
762
+ * createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
763
+ * createFunctionParameter({ name: 'config', type: 'RequestConfig', optional: false, default: '{}' }),
764
+ * ],
765
+ * })
766
+ * ```
767
+ *
768
+ * @example
769
+ * ```ts
770
+ * const empty = createFunctionParameters()
771
+ * // { kind: 'FunctionParameters', params: [] }
772
+ * ```
773
+ */
774
+ function createFunctionParameters(props = {}) {
775
+ return {
776
+ params: [],
777
+ ...props,
778
+ kind: "FunctionParameters"
779
+ };
664
780
  }
665
- function printRootHelp(programName, version, defs) {
666
- console.log(`\n${styleText("bold", "Usage:")} ${programName} <command> [options]\n`);
667
- console.log(` Kubb generation — v${version}\n`);
668
- console.log(styleText("bold", "Commands:"));
669
- for (const def of defs) console.log(` ${styleText("cyan", def.name.padEnd(16))}${def.description}`);
670
- console.log();
671
- console.log(styleText("bold", "Options:"));
672
- console.log(` ${styleText("cyan", "-v, --version".padEnd(30))}Show version number`);
673
- console.log(` ${styleText("cyan", "-h, --help".padEnd(30))}Show help`);
674
- console.log();
675
- console.log(`Run ${styleText("cyan", `${programName} <command> --help`)} for command-specific help.\n`);
781
+ //#endregion
782
+ //#region src/printers/printer.ts
783
+ /**
784
+ * Creates a schema printer factory.
785
+ *
786
+ * This function wraps a builder and makes options optional at call sites.
787
+ *
788
+ * The builder receives resolved options and returns:
789
+ * - `name` — a unique identifier for the printer
790
+ * - `options` — options stored on the returned printer instance
791
+ * - `nodes` a map of `SchemaType` handler functions that convert a `SchemaNode` to `TOutput`
792
+ * - `print` _(optional)_ — top-level override exposed as `printer.print`
793
+ * - Inside this function, `this.print(node)` still dispatches to the `nodes` map
794
+ * - This keeps recursion safe and avoids self-calls
795
+ *
796
+ * When no `print` override is provided, `printer.print` is the node-level dispatcher directly.
797
+ *
798
+ * @example Basic usage — Zod schema printer
799
+ * ```ts
800
+ * type ZodPrinter = PrinterFactoryOptions<'zod', { strict?: boolean }, string>
801
+ *
802
+ * export const zodPrinter = definePrinter<ZodPrinter>((options) => ({
803
+ * name: 'zod',
804
+ * options: { strict: options.strict ?? true },
805
+ * nodes: {
806
+ * string: () => 'z.string()',
807
+ * object(node) {
808
+ * const props = node.properties.map(p => `${p.name}: ${this.print(p.schema)}`).join(', ')
809
+ * return `z.object({ ${props} })`
810
+ * },
811
+ * },
812
+ * }))
813
+ * ```
814
+ */
815
+ function definePrinter(build) {
816
+ return createPrinterFactory((node) => node.type)(build);
676
817
  }
677
- defineCLIAdapter({
678
- renderHelp(def, parentName) {
679
- renderHelp(def, parentName);
680
- },
681
- async run(defs, argv, opts) {
682
- const { programName, defaultCommandName, version } = opts;
683
- const args = argv.length >= 2 && argv[0]?.includes("node") ? argv.slice(2) : argv;
684
- if (args[0] === "--version" || args[0] === "-v") {
685
- console.log(version);
686
- process.exit(0);
687
- }
688
- if (args[0] === "--help" || args[0] === "-h") {
689
- printRootHelp(programName, version, defs);
690
- process.exit(0);
691
- }
692
- if (args.length === 0) {
693
- const defaultDef = defs.find((d) => d.name === defaultCommandName);
694
- if (defaultDef?.run) await runCommand(defaultDef, [], programName);
695
- else printRootHelp(programName, version, defs);
696
- return;
697
- }
698
- const [first, ...rest] = args;
699
- const isKnownSubcommand = defs.some((d) => d.name === first);
700
- let def;
701
- let commandArgv;
702
- let parentName;
703
- if (isKnownSubcommand) {
704
- def = defs.find((d) => d.name === first);
705
- commandArgv = rest;
706
- parentName = programName;
707
- } else {
708
- def = defs.find((d) => d.name === defaultCommandName);
709
- commandArgv = args;
710
- parentName = programName;
711
- }
712
- if (!def) {
713
- console.error(`Unknown command: ${first}`);
714
- printRootHelp(programName, version, defs);
715
- process.exit(1);
716
- }
717
- if (def.subCommands?.length) {
718
- const [subName, ...subRest] = commandArgv;
719
- const subDef = def.subCommands.find((s) => s.name === subName);
720
- if (subName === "--help" || subName === "-h") {
721
- renderHelp(def, parentName);
722
- process.exit(0);
818
+ /**
819
+ * Generic printer-factory function used by `definePrinter` and `defineFunctionPrinter`.
820
+ **
821
+ * @example
822
+ * ```ts
823
+ * export const defineFunctionPrinter = createPrinterFactory<FunctionNode, FunctionNodeType, FunctionNodeByType>(
824
+ * (node) => kindToHandlerKey[node.kind],
825
+ * )
826
+ * ```
827
+ */
828
+ function createPrinterFactory(getKey) {
829
+ return function(build) {
830
+ return (options) => {
831
+ const { name, options: resolvedOptions, nodes, print: printOverride } = build(options ?? {});
832
+ const context = {
833
+ options: resolvedOptions,
834
+ print: (node) => {
835
+ const key = getKey(node);
836
+ if (key === void 0) return void 0;
837
+ const handler = nodes[key];
838
+ if (!handler) return void 0;
839
+ return handler.call(context, node);
840
+ }
841
+ };
842
+ return {
843
+ name,
844
+ options: resolvedOptions,
845
+ print: printOverride ? printOverride.bind(context) : context.print
846
+ };
847
+ };
848
+ };
849
+ }
850
+ //#endregion
851
+ //#region src/printers/functionPrinter.ts
852
+ const kindToHandlerKey = {
853
+ FunctionParameter: "functionParameter",
854
+ ObjectBindingParameter: "objectBindingParameter",
855
+ FunctionParameters: "functionParameters"
856
+ };
857
+ /**
858
+ * Creates a function-parameter printer factory.
859
+ *
860
+ * This wrapper uses `createPrinterFactory` and dispatches handlers by `node.kind`
861
+ * (for function nodes) rather than by `node.type` (for schema nodes).
862
+ *
863
+ * @example
864
+ * ```ts
865
+ * type MyPrinter = PrinterFactoryOptions<'my', { mode: 'declaration' | 'call' }, string>
866
+ *
867
+ * export const myPrinter = defineFunctionPrinter<MyPrinter>((options) => ({
868
+ * name: 'my',
869
+ * options,
870
+ * nodes: {
871
+ * functionParameter(node) {
872
+ * return options.mode === 'declaration' && node.type ? `${node.name}: ${node.type}` : node.name
873
+ * },
874
+ * objectBindingParameter(node) {
875
+ * const inner = node.properties.map(p => this.print(p)).filter(Boolean).join(', ')
876
+ * return `{ ${inner} }`
877
+ * },
878
+ * functionParameters(node) {
879
+ * return node.params.map(p => this.print(p)).filter(Boolean).join(', ')
880
+ * },
881
+ * },
882
+ * }))
883
+ * ```
884
+ */
885
+ const defineFunctionPrinter = createPrinterFactory((node) => kindToHandlerKey[node.kind]);
886
+ function rank(param) {
887
+ if (param.kind === "ObjectBindingParameter") {
888
+ if (param.default) return 2;
889
+ return param.optional ?? param.properties.every((p) => p.optional || p.default !== void 0) ? 1 : 0;
890
+ }
891
+ if (param.rest) return 3;
892
+ if (param.default) return 2;
893
+ return param.optional ? 1 : 0;
894
+ }
895
+ function sortParams(params) {
896
+ return [...params].sort((a, b) => rank(a) - rank(b));
897
+ }
898
+ function sortChildParams(params) {
899
+ return [...params].sort((a, b) => rank(a) - rank(b));
900
+ }
901
+ /**
902
+ * Default function-signature printer.
903
+ * Covers the four standard output modes used across Kubb plugins.
904
+ *
905
+ * @example
906
+ * ```ts
907
+ * const printer = functionPrinter({ mode: 'declaration' })
908
+ *
909
+ * const sig = createFunctionParameters({
910
+ * params: [
911
+ * createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
912
+ * createFunctionParameter({ name: 'config', type: 'Config', optional: false, default: '{}' }),
913
+ * ],
914
+ * })
915
+ *
916
+ * printer.print(sig) // → "petId: string, config: Config = {}"
917
+ * ```
918
+ */
919
+ const functionPrinter = defineFunctionPrinter((options) => ({
920
+ name: "functionParameters",
921
+ options,
922
+ nodes: {
923
+ functionParameter(node) {
924
+ const { mode, transformName, transformType } = this.options;
925
+ const name = transformName ? transformName(node.name) : node.name;
926
+ const type = node.type && transformType ? transformType(node.type) : node.type;
927
+ if (mode === "keys" || mode === "values") return node.rest ? `...${name}` : name;
928
+ if (mode === "call") return node.rest ? `...${name}` : name;
929
+ if (node.rest) return type ? `...${name}: ${type}` : `...${name}`;
930
+ if (type) {
931
+ if (node.optional) return `${name}?: ${type}`;
932
+ return node.default ? `${name}: ${type} = ${node.default}` : `${name}: ${type}`;
723
933
  }
724
- if (!subDef) {
725
- renderHelp(def, parentName);
726
- process.exit(subName ? 1 : 0);
934
+ return node.default ? `${name} = ${node.default}` : name;
935
+ },
936
+ objectBindingParameter(node) {
937
+ const { mode, transformName, transformType } = this.options;
938
+ const sorted = sortChildParams(node.properties);
939
+ const isOptional = node.optional ?? sorted.every((p) => p.optional || p.default !== void 0);
940
+ if (node.inline) return sorted.map((p) => this.print(p)).filter(Boolean).join(", ");
941
+ if (mode === "keys" || mode === "values") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
942
+ if (mode === "call") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
943
+ const names = sorted.map((p) => {
944
+ return transformName ? transformName(p.name) : p.name;
945
+ });
946
+ const nameStr = names.length ? `{ ${names.join(", ")} }` : void 0;
947
+ if (!nameStr) return null;
948
+ let typeAnnotation = node.type;
949
+ if (!typeAnnotation) {
950
+ const typeParts = sorted.filter((p) => p.type).map((p) => {
951
+ const t = transformType && p.type ? transformType(p.type) : p.type;
952
+ return p.optional || p.default !== void 0 ? `${p.name}?: ${t}` : `${p.name}: ${t}`;
953
+ });
954
+ typeAnnotation = typeParts.length ? `{ ${typeParts.join("; ")} }` : void 0;
727
955
  }
728
- await runCommand(subDef, subRest, `${parentName} ${def.name}`);
729
- return;
956
+ if (typeAnnotation) {
957
+ if (isOptional) return `${nameStr}: ${typeAnnotation} = ${node.default ?? "{}"}`;
958
+ return node.default ? `${nameStr}: ${typeAnnotation} = ${node.default}` : `${nameStr}: ${typeAnnotation}`;
959
+ }
960
+ return node.default ? `${nameStr} = ${node.default}` : nameStr;
961
+ },
962
+ functionParameters(node) {
963
+ return sortParams(node.params).map((p) => this.print(p)).filter(Boolean).join(", ");
730
964
  }
731
- await runCommand(def, commandArgv, parentName);
732
965
  }
733
- });
734
- /**
735
- * Parses a CSS hex color string (`#RGB`) into its RGB channels.
736
- * Falls back to `255` for any channel that cannot be parsed.
737
- */
738
- function parseHex(color) {
739
- const int = Number.parseInt(color.replace("#", ""), 16);
740
- return Number.isNaN(int) ? {
741
- r: 255,
742
- g: 255,
743
- b: 255
744
- } : {
745
- r: int >> 16 & 255,
746
- g: int >> 8 & 255,
747
- b: int & 255
748
- };
749
- }
966
+ }));
967
+ //#endregion
968
+ //#region src/refs.ts
750
969
  /**
751
- * Returns a function that wraps a string in a 24-bit ANSI true-color escape sequence
752
- * for the given hex color.
970
+ * Returns the last path segment of a reference string.
971
+ *
972
+ * Example: `#/components/schemas/Pet` becomes `Pet`.
973
+ *
974
+ * @example
975
+ * ```ts
976
+ * extractRefName('#/components/schemas/Pet') // 'Pet'
977
+ * ```
753
978
  */
754
- function hex(color) {
755
- const { r, g, b } = parseHex(color);
756
- return (text) => `\x1b[38;2;${r};${g};${b}m${text}\x1b[0m`;
979
+ function extractRefName(ref) {
980
+ return ref.split("/").at(-1) ?? ref;
757
981
  }
758
- hex("#F55A17"), hex("#F5A217"), hex("#F58517"), hex("#B45309"), hex("#FFFFFF"), hex("#adadc6"), hex("#FDA4AF");
759
982
  /**
760
- * Returns `true` when `name` is a syntactically valid JavaScript variable name.
983
+ * Builds a `RefMap` from `root.schemas` using each schema's `name`.
984
+ *
985
+ * Unnamed schemas are skipped.
986
+ *
987
+ * @example
988
+ * ```ts
989
+ * const refMap = buildRefMap(root)
990
+ * const pet = refMap.get('Pet')
991
+ * ```
761
992
  */
762
- function isValidVarName(name) {
763
- try {
764
- new Function(`var ${name}`);
765
- } catch {
766
- return false;
767
- }
768
- return true;
993
+ function buildRefMap(root) {
994
+ const map = /* @__PURE__ */ new Map();
995
+ for (const schema of root.schemas) if (schema.name) map.set(schema.name, schema);
996
+ return map;
769
997
  }
770
- //#endregion
771
- //#region src/utils.ts
772
- const plainStringTypes = new Set([
773
- "string",
774
- "uuid",
775
- "email",
776
- "url",
777
- "datetime"
778
- ]);
779
998
  /**
780
- * Returns `true` when a schema node will be represented as a plain string in generated code.
999
+ * Resolves a schema by name from a `RefMap`.
781
1000
  *
782
- * - `string`, `uuid`, `email`, `url`, `datetime` are always plain strings.
783
- * - `date` and `time` are plain strings when their `representation` is `'string'` rather than `'date'`.
1001
+ * @example
1002
+ * ```ts
1003
+ * const petSchema = resolveRef(refMap, 'Pet')
1004
+ * ```
784
1005
  */
785
- function isPlainStringType(node) {
786
- if (plainStringTypes.has(node.type)) return true;
787
- const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
788
- if (temporal) return temporal.representation !== "date";
789
- return false;
1006
+ function resolveRef(refMap, ref) {
1007
+ return refMap.get(ref);
790
1008
  }
791
1009
  /**
792
- * Transforms the `name` field of each parameter node according to the given casing strategy.
793
- *
794
- * The original `params` array is never mutated — a new array of cloned nodes is returned.
795
- * When no `casing` is provided the original array is returned as-is.
1010
+ * Converts a `RefMap` into a plain object.
796
1011
  *
797
- * Use this before passing parameters to schema builders so that property keys
798
- * in the generated output match the desired casing while the original
799
- * `OperationNode.parameters` array remains untouched for other consumers.
1012
+ * @example
1013
+ * ```ts
1014
+ * const refsObject = refMapToObject(refMap)
1015
+ * ```
800
1016
  */
801
- function applyParamsCasing(params, casing) {
802
- if (!casing) return params;
803
- return params.map((param) => {
804
- const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
805
- return {
806
- ...param,
807
- name: transformed
808
- };
809
- });
1017
+ function refMapToObject(refMap) {
1018
+ return Object.fromEntries(refMap);
810
1019
  }
811
1020
  //#endregion
812
1021
  //#region src/visitor.ts
813
1022
  /**
814
- * Creates a concurrency-limiting wrapper. At most `concurrency` promises may be
815
- * in-flight simultaneously; additional calls are queued and dispatched as slots free.
1023
+ * Creates a small async concurrency limiter.
1024
+ *
1025
+ * At most `concurrency` tasks are in flight at once. Extra tasks are queued.
1026
+ *
1027
+ * @example
1028
+ * ```ts
1029
+ * const limit = createLimit(2)
1030
+ * await Promise.all([
1031
+ * limit(() => taskA()),
1032
+ * limit(() => taskB()),
1033
+ * limit(() => taskC()),
1034
+ * ])
1035
+ * // only 2 tasks run at the same time
1036
+ * ```
816
1037
  */
817
1038
  function createLimit(concurrency) {
818
1039
  let active = 0;
@@ -838,8 +1059,15 @@ function createLimit(concurrency) {
838
1059
  /**
839
1060
  * Returns the immediate traversable children of `node`.
840
1061
  *
841
- * For `Schema` nodes, children (properties, items, members) are only included
842
- * when `recurse` is `true`; shallow traversal omits them entirely.
1062
+ * For `Schema` nodes, children (`properties`, `items`, `members`, and non-boolean
1063
+ * `additionalProperties`) are only included
1064
+ * when `recurse` is `true`; shallow mode skips them.
1065
+ *
1066
+ * @example
1067
+ * ```ts
1068
+ * const children = getChildren(operationNode, true)
1069
+ * // returns parameters, requestBody schema (if present), and responses
1070
+ * ```
843
1071
  */
844
1072
  function getChildren(node, recurse) {
845
1073
  switch (node.kind) {
@@ -868,7 +1096,23 @@ function getChildren(node, recurse) {
868
1096
  }
869
1097
  /**
870
1098
  * Depth-first traversal for side effects. Visitor return values are ignored.
871
- * Sibling nodes at each level are visited concurrently up to `options.concurrency` (default: 30).
1099
+ * Sibling nodes at each level are visited concurrently up to `options.concurrency`
1100
+ * (default: `WALK_CONCURRENCY`).
1101
+ *
1102
+ * @example
1103
+ * ```ts
1104
+ * await walk(root, {
1105
+ * operation(node) {
1106
+ * console.log(node.operationId)
1107
+ * },
1108
+ * })
1109
+ * ```
1110
+ *
1111
+ * @example
1112
+ * ```ts
1113
+ * // Visit only the current node
1114
+ * await walk(root, { depth: 'shallow', root: () => {} })
1115
+ * ```
872
1116
  */
873
1117
  async function walk(node, options) {
874
1118
  return _walk(node, options, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30), void 0);
@@ -1001,8 +1245,18 @@ function transform(node, options) {
1001
1245
  }
1002
1246
  }
1003
1247
  /**
1004
- * Combines multiple visitors into a single visitor that applies them sequentially (left to right).
1005
- * For each node kind, the output of one visitor becomes the input of the next.
1248
+ * Composes multiple visitors into one visitor, applied left to right.
1249
+ *
1250
+ * For each node kind, output from one visitor is input to the next.
1251
+ * If a visitor returns `undefined`, the previous node value is kept.
1252
+ *
1253
+ * @example
1254
+ * ```ts
1255
+ * const visitor = composeTransformers(
1256
+ * { operation: (node) => ({ ...node, operationId: `a_${node.operationId}` }) },
1257
+ * { operation: (node) => ({ ...node, operationId: `b_${node.operationId}` }) },
1258
+ * )
1259
+ * ```
1006
1260
  */
1007
1261
  function composeTransformers(...visitors) {
1008
1262
  return {
@@ -1027,7 +1281,24 @@ function composeTransformers(...visitors) {
1027
1281
  };
1028
1282
  }
1029
1283
  /**
1030
- * Depth-first synchronous reduction. Collects non-`undefined` visitor return values into an array.
1284
+ * Runs a depth-first synchronous collection pass.
1285
+ *
1286
+ * Non-`undefined` values returned by visitor callbacks are appended to the result.
1287
+ *
1288
+ * @example
1289
+ * ```ts
1290
+ * const ids = collect(root, {
1291
+ * operation(node) {
1292
+ * return node.operationId
1293
+ * },
1294
+ * })
1295
+ * ```
1296
+ *
1297
+ * @example
1298
+ * ```ts
1299
+ * // Collect from only the current node
1300
+ * const values = collect(root, { depth: 'shallow', root: () => 'root' })
1301
+ * ```
1031
1302
  */
1032
1303
  function collect(node, options) {
1033
1304
  const { depth, parent, ...visitor } = options;
@@ -1065,6 +1336,165 @@ function collect(node, options) {
1065
1336
  return results;
1066
1337
  }
1067
1338
  //#endregion
1068
- export { applyParamsCasing, buildRefMap, collect, composeTransformers, createFunctionParameter, createFunctionParameters, createObjectBindingParameter, createOperation, createParameter, createProperty, createResponse, createRoot, createSchema, definePrinter, functionPrinter, httpMethods, isFunctionParameterNode, isFunctionParametersNode, isObjectBindingParameterNode, isOperationNode, isParameterNode, isPlainStringType, isPropertyNode, isResponseNode, isRootNode, isSchemaNode, mediaTypes, narrowSchema, nodeKinds, refMapToObject, resolveRef, schemaTypes, syncPropertySchema, transform, walk };
1339
+ //#region src/resolvers.ts
1340
+ function findDiscriminator(mapping, ref) {
1341
+ if (!mapping || !ref) return void 0;
1342
+ return Object.entries(mapping).find(([, value]) => value === ref)?.[0];
1343
+ }
1344
+ function childName(parentName, propName) {
1345
+ return parentName ? pascalCase([parentName, propName].join(" ")) : void 0;
1346
+ }
1347
+ function enumPropName(parentName, propName, enumSuffix) {
1348
+ return pascalCase([
1349
+ parentName,
1350
+ propName,
1351
+ enumSuffix
1352
+ ].filter(Boolean).join(" "));
1353
+ }
1354
+ /**
1355
+ * Collects import entries for all `ref` schema nodes in `node`.
1356
+ */
1357
+ function collectImports({ node, nameMapping, resolve }) {
1358
+ return collect(node, { schema(schemaNode) {
1359
+ const schemaRef = narrowSchema(schemaNode, "ref");
1360
+ if (!schemaRef?.ref) return;
1361
+ const rawName = extractRefName(schemaRef.ref);
1362
+ const result = resolve(nameMapping.get(rawName) ?? rawName);
1363
+ if (!result) return;
1364
+ return result;
1365
+ } });
1366
+ }
1367
+ //#endregion
1368
+ //#region src/transformers.ts
1369
+ /**
1370
+ * Replaces a discriminator property's schema with a string enum of allowed values.
1371
+ *
1372
+ * If `node` is not an object schema, or if the property does not exist, the input
1373
+ * node is returned as-is.
1374
+ *
1375
+ * @example
1376
+ * ```ts
1377
+ * const schema = createSchema({
1378
+ * type: 'object',
1379
+ * properties: [createProperty({ name: 'type', required: true, schema: createSchema({ type: 'string' }) })],
1380
+ * })
1381
+ * const result = setDiscriminatorEnum({ node: schema, propertyName: 'type', values: ['dog', 'cat'] })
1382
+ * ```
1383
+ */
1384
+ function setDiscriminatorEnum({ node, propertyName, values, enumName }) {
1385
+ const objectNode = narrowSchema(node, "object");
1386
+ if (!objectNode?.properties?.length) return node;
1387
+ if (!objectNode.properties.some((prop) => prop.name === propertyName)) return node;
1388
+ return createSchema({
1389
+ ...objectNode,
1390
+ properties: objectNode.properties.map((prop) => {
1391
+ if (prop.name !== propertyName) return prop;
1392
+ return createProperty({
1393
+ ...prop,
1394
+ schema: createSchema({
1395
+ type: "enum",
1396
+ primitive: "string",
1397
+ enumValues: values,
1398
+ name: enumName,
1399
+ readOnly: prop.schema.readOnly,
1400
+ writeOnly: prop.schema.writeOnly
1401
+ })
1402
+ });
1403
+ })
1404
+ });
1405
+ }
1406
+ /**
1407
+ * Merges adjacent anonymous object members into a single anonymous object member.
1408
+ *
1409
+ * @example
1410
+ * ```ts
1411
+ * const merged = mergeAdjacentObjects([
1412
+ * createSchema({ type: 'object', properties: [createProperty({ name: 'a', schema: createSchema({ type: 'string' }) })] }),
1413
+ * createSchema({ type: 'object', properties: [createProperty({ name: 'b', schema: createSchema({ type: 'number' }) })] }),
1414
+ * ])
1415
+ * ```
1416
+ */
1417
+ function mergeAdjacentObjects(members) {
1418
+ return members.reduce((acc, member) => {
1419
+ const objectMember = narrowSchema(member, "object");
1420
+ if (objectMember && !objectMember.name) {
1421
+ const previous = acc.at(-1);
1422
+ const previousObject = previous ? narrowSchema(previous, "object") : void 0;
1423
+ if (previousObject && !previousObject.name) {
1424
+ acc[acc.length - 1] = createSchema({
1425
+ ...previousObject,
1426
+ properties: [...previousObject.properties ?? [], ...objectMember.properties ?? []]
1427
+ });
1428
+ return acc;
1429
+ }
1430
+ }
1431
+ acc.push(member);
1432
+ return acc;
1433
+ }, []);
1434
+ }
1435
+ /**
1436
+ * Removes enum members that are covered by broader scalar primitives in the same union.
1437
+ *
1438
+ * @example
1439
+ * ```ts
1440
+ * const simplified = simplifyUnion([
1441
+ * createSchema({ type: 'enum', primitive: 'string', enumValues: ['active'] }),
1442
+ * createSchema({ type: 'string' }),
1443
+ * ])
1444
+ * // keeps only string member
1445
+ * ```
1446
+ */
1447
+ function simplifyUnion(members) {
1448
+ const scalarPrimitives = new Set(members.filter((member) => SCALAR_PRIMITIVE_TYPES.has(member.type)).map((m) => m.type));
1449
+ if (!scalarPrimitives.size) return members;
1450
+ return members.filter((member) => {
1451
+ const enumNode = narrowSchema(member, "enum");
1452
+ if (!enumNode) return true;
1453
+ const primitive = enumNode.primitive;
1454
+ if (!primitive) return true;
1455
+ if ((enumNode.namedEnumValues?.length ?? enumNode.enumValues?.length ?? 0) <= 1) return true;
1456
+ if (scalarPrimitives.has(primitive)) return false;
1457
+ if ((primitive === "integer" || primitive === "number") && (scalarPrimitives.has("integer") || scalarPrimitives.has("number"))) return false;
1458
+ return true;
1459
+ });
1460
+ }
1461
+ function setEnumName(propNode, parentName, propName, enumSuffix) {
1462
+ const enumNode = narrowSchema(propNode, "enum");
1463
+ if (enumNode?.primitive === "boolean") return {
1464
+ ...propNode,
1465
+ name: void 0
1466
+ };
1467
+ if (enumNode) return {
1468
+ ...propNode,
1469
+ name: enumPropName(parentName, propName, enumSuffix)
1470
+ };
1471
+ return propNode;
1472
+ }
1473
+ /**
1474
+ * Walks a schema tree and resolves `ref`/`enum` names through callbacks.
1475
+ */
1476
+ function resolveNames({ node, nameMapping, resolveName, resolveEnumName }) {
1477
+ return transform(node, { schema(schemaNode) {
1478
+ const schemaRef = narrowSchema(schemaNode, "ref");
1479
+ if (schemaRef && (schemaRef.ref || schemaRef.name)) {
1480
+ const rawRef = schemaRef.ref ?? schemaRef.name;
1481
+ const resolved = resolveName(nameMapping.get(rawRef) ?? rawRef);
1482
+ if (resolved) return {
1483
+ ...schemaNode,
1484
+ name: resolved
1485
+ };
1486
+ }
1487
+ const schemaEnum = narrowSchema(schemaNode, "enum");
1488
+ if (schemaEnum?.name) {
1489
+ const resolved = (resolveEnumName ?? resolveName)(schemaEnum.name);
1490
+ if (resolved) return {
1491
+ ...schemaNode,
1492
+ name: resolved
1493
+ };
1494
+ }
1495
+ } });
1496
+ }
1497
+ //#endregion
1498
+ export { SCALAR_PRIMITIVE_TYPES, buildRefMap, caseParams, childName, collect, collectImports, composeTransformers, createDiscriminantNode, createFunctionParameter, createFunctionParameters, createObjectBindingParameter, createOperation, createParameter, createProperty, createResponse, createRoot, createSchema, defineFunctionPrinter, definePrinter, enumPropName, extractRefName, findDiscriminator, functionPrinter, httpMethods, isFunctionParameterNode, isFunctionParametersNode, isObjectBindingParameterNode, isOperationNode, isParameterNode, isPropertyNode, isResponseNode, isRootNode, isSchemaNode, isStringType, mediaTypes, mergeAdjacentObjects, narrowSchema, nodeKinds, refMapToObject, resolveNames, resolveRef, schemaTypes, setDiscriminatorEnum, setEnumName, simplifyUnion, syncOptionality, transform, walk };
1069
1499
 
1070
1500
  //# sourceMappingURL=index.js.map