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