@kubb/ast 5.0.0-alpha.16 → 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",
@@ -45,7 +56,7 @@ const schemaTypes = {
45
56
  never: "never"
46
57
  };
47
58
  /**
48
- * Scalar primitive schema types used for union member simplification.
59
+ * Primitive scalar schema types used when simplifying union members.
49
60
  */
50
61
  const SCALAR_PRIMITIVE_TYPES = new Set([
51
62
  "string",
@@ -86,818 +97,945 @@ const mediaTypes = {
86
97
  videoMp4: "video/mp4"
87
98
  };
88
99
  //#endregion
89
- //#region src/factory.ts
100
+ //#region ../../internals/utils/dist/index.js
90
101
  /**
91
- * 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.
92
107
  */
93
- function createRoot(overrides = {}) {
94
- return {
95
- schemas: [],
96
- operations: [],
97
- ...overrides,
98
- kind: "Root"
99
- };
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, "");
100
114
  }
101
115
  /**
102
- * 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.
103
122
  */
104
- function createOperation(props) {
105
- return {
106
- tags: [],
107
- parameters: [],
108
- responses: [],
109
- ...props,
110
- kind: "Operation"
111
- };
112
- }
113
- function createSchema(props) {
114
- if (props["type"] === "object") return {
115
- properties: [],
116
- ...props,
117
- kind: "Schema"
118
- };
119
- return {
120
- ...props,
121
- kind: "Schema"
122
- };
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("/");
123
126
  }
124
127
  /**
125
- * Derives `schema.optional` and `schema.nullish` from `required` and `schema.nullable`.
126
- * 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'
127
134
  */
128
- function syncPropertySchema(required, schema) {
129
- const nullable = schema.nullable ?? false;
130
- return {
131
- ...schema,
132
- optional: !required && !nullable ? true : void 0,
133
- nullish: !required && nullable ? true : void 0
134
- };
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);
135
141
  }
136
142
  /**
137
- * Creates a `PropertyNode`. `required` defaults to `false`.
138
- * `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'
139
149
  */
140
- function createProperty(props) {
141
- const required = props.required ?? false;
142
- return {
143
- ...props,
144
- kind: "Property",
145
- required,
146
- schema: syncPropertySchema(required, props.schema)
147
- };
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);
148
156
  }
149
- /**
150
- * Creates a `ParameterNode`. `required` defaults to `false`.
151
- * `schema.optional` is auto-derived from `required` and `schema.nullable`.
152
- */
153
- function createParameter(props) {
154
- const required = props.required ?? false;
155
- return {
156
- ...props,
157
- kind: "Parameter",
158
- required,
159
- schema: syncPropertySchema(required, props.schema)
160
- };
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;
161
160
  }
162
161
  /**
163
- * Creates a `ResponseNode`.
162
+ * Serializes `CommandDefinition[]` to a plain, JSON-serializable structure.
163
+ * Use to expose CLI capabilities to AI agents or MCP tools.
164
164
  */
165
- function createResponse(props) {
166
- return {
167
- ...props,
168
- kind: "Response"
169
- };
165
+ function getCommandSchema(defs) {
166
+ return defs.map(serializeCommand);
170
167
  }
171
- /**
172
- * Creates a `FunctionParameterNode`. `optional` defaults to `false`.
173
- *
174
- * @example Required typed param
175
- * ```ts
176
- * createFunctionParameter({ name: 'petId', type: 'string' })
177
- * // → petId: string
178
- * ```
179
- *
180
- * @example Optional param
181
- * ```ts
182
- * createFunctionParameter({ name: 'params', type: 'QueryParams', optional: true })
183
- * // → params?: QueryParams
184
- * ```
185
- *
186
- * @example Param with default (implicitly optional — cannot combine with `optional: true`)
187
- * ```ts
188
- * createFunctionParameter({ name: 'config', type: 'RequestConfig', default: '{}' })
189
- * // → config: RequestConfig = {}
190
- * ```
191
- */
192
- function createFunctionParameter(props) {
168
+ function serializeCommand(def) {
193
169
  return {
194
- optional: false,
195
- ...props,
196
- 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) : []
197
175
  };
198
176
  }
199
- /**
200
- * Creates an `ObjectBindingParameterNode` — an object-destructured parameter group.
201
- *
202
- * @example Destructured object param
203
- * ```ts
204
- * createObjectBindingParameter({
205
- * properties: [
206
- * createFunctionParameter({ name: 'id', type: 'string', optional: false }),
207
- * createFunctionParameter({ name: 'name', type: 'string', optional: true }),
208
- * ],
209
- * default: '{}',
210
- * })
211
- * // declaration → { id, name? }: { id: string; name?: string } = {}
212
- * // call → { id, name }
213
- * ```
214
- *
215
- * @example Inline — children emitted as individual top-level params
216
- * ```ts
217
- * createObjectBindingParameter({
218
- * properties: [createFunctionParameter({ name: 'petId', type: 'string', optional: false })],
219
- * inline: true,
220
- * })
221
- * // declaration → petId: string
222
- * // call → petId
223
- * ```
224
- */
225
- function createObjectBindingParameter(props) {
226
- return {
227
- ...props,
228
- kind: "ObjectBindingParameter"
229
- };
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
+ });
230
190
  }
231
- /**
232
- * Creates a `FunctionParametersNode` from an ordered list of params.
233
- *
234
- * @example
235
- * ```ts
236
- * createFunctionParameters({
237
- * params: [
238
- * createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
239
- * createFunctionParameter({ name: 'config', type: 'RequestConfig', optional: false, default: '{}' }),
240
- * ],
241
- * })
242
- * ```
243
- */
244
- function createFunctionParameters(props = {}) {
245
- return {
246
- params: [],
247
- ...props,
248
- 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 } : {}
249
227
  };
228
+ return result;
250
229
  }
251
- //#endregion
252
- //#region src/printer.ts
253
- /**
254
- * Creates a named printer factory. Mirrors the `createPlugin` / `createAdapter` pattern
255
- * from `@kubb/core` wraps a builder to make options optional and separates raw options
256
- * from resolved options.
257
- *
258
- * The builder receives resolved options and returns:
259
- * - `name` — a unique identifier for the printer
260
- * - `options` — options stored on the returned printer instance
261
- * - `nodes` — a map of `SchemaType` → handler functions that convert a `SchemaNode` to `TOutput`
262
- * - `print` _(optional)_ — a root-level override that becomes the public `printer.print`.
263
- * Inside it, `this.print(node)` still dispatches to the `nodes` map — safe recursion, no infinite loop.
264
- *
265
- * When no `print` override is provided, `printer.print` is the node-level dispatcher directly.
266
- *
267
- * @example Basic usage — Zod schema printer
268
- * ```ts
269
- * type ZodPrinter = PrinterFactoryOptions<'zod', { strict?: boolean }, string>
270
- *
271
- * export const zodPrinter = definePrinter<ZodPrinter>((options) => ({
272
- * name: 'zod',
273
- * options: { strict: options.strict ?? true },
274
- * nodes: {
275
- * string: () => 'z.string()',
276
- * object(node) {
277
- * const props = node.properties.map(p => `${p.name}: ${this.print(p.schema)}`).join(', ')
278
- * return `z.object({ ${props} })`
279
- * },
280
- * },
281
- * }))
282
- * ```
283
- *
284
- * @example With a root-level `print` override to wrap output in a full declaration
285
- * ```ts
286
- * type TsPrinter = PrinterFactoryOptions<'ts', { typeName?: string }, ts.TypeNode, ts.Node>
287
- *
288
- * export const printerTs = definePrinter<TsPrinter>((options) => ({
289
- * name: 'ts',
290
- * options,
291
- * nodes: { string: () => factory.keywordTypeNodes.string },
292
- * print(node) {
293
- * const type = this.print(node) // calls the node-level dispatcher
294
- * if (!type || !this.options.typeName) return type
295
- * return factory.createTypeAliasDeclaration(this.options.typeName, type)
296
- * },
297
- * }))
298
- * ```
299
- */
300
- function definePrinter(build) {
301
- return createPrinterFactory((node) => node.type)(build);
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`);
302
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
+ });
303
338
  /**
304
- * Generic printer factory. Extracts the core dispatch + context logic so it can be reused
305
- * for any node type — not just `SchemaNode`. `definePrinter` is built on top of this.
306
- *
307
- * @param getKey — derives the handler-map key from a node. Return `undefined` to skip.
308
- *
309
- * @example
310
- * ```ts
311
- * export const defineFunctionPrinter = createPrinterFactory<FunctionNode, FunctionNodeType, FunctionNodeByType>(
312
- * (node) => kindToHandlerKey[node.kind],
313
- * )
314
- * ```
339
+ * Parses a CSS hex color string (`#RGB`) into its RGB channels.
340
+ * Falls back to `255` for any channel that cannot be parsed.
315
341
  */
316
- function createPrinterFactory(getKey) {
317
- return function(build) {
318
- return (options) => {
319
- const { name, options: resolvedOptions, nodes, print: printOverride } = build(options ?? {});
320
- const context = {
321
- options: resolvedOptions,
322
- print: (node) => {
323
- const key = getKey(node);
324
- if (key === void 0) return void 0;
325
- const handler = nodes[key];
326
- if (!handler) return void 0;
327
- return handler.call(context, node);
328
- }
329
- };
330
- return {
331
- name,
332
- options: resolvedOptions,
333
- print: printOverride ? printOverride.bind(context) : context.print
334
- };
335
- };
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
336
352
  };
337
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
+ }
338
374
  //#endregion
339
- //#region src/functionPrinter.ts
340
- const kindToHandlerKey = {
341
- FunctionParameter: "functionParameter",
342
- ObjectBindingParameter: "objectBindingParameter",
343
- FunctionParameters: "functionParameters"
344
- };
375
+ //#region src/guards.ts
345
376
  /**
346
- * Creates a named function-signature printer factory.
347
- * Built on `createPrinterFactory` — dispatches on `node.kind` instead of `node.type`.
377
+ * Narrows a `SchemaNode` to the variant that matches `type`.
348
378
  *
349
379
  * @example
350
380
  * ```ts
351
- * type MyPrinter = PrinterFactoryOptions<'my', { mode: 'declaration' | 'call' }, string>
352
- *
353
- * export const myPrinter = defineFunctionPrinter<MyPrinter>((options) => ({
354
- * name: 'my',
355
- * options,
356
- * nodes: {
357
- * functionParameter(node) {
358
- * return options.mode === 'declaration' && node.type ? `${node.name}: ${node.type}` : node.name
359
- * },
360
- * objectBindingParameter(node) {
361
- * const inner = node.properties.map(p => this.print(p)).filter(Boolean).join(', ')
362
- * return `{ ${inner} }`
363
- * },
364
- * functionParameters(node) {
365
- * return node.params.map(p => this.print(p)).filter(Boolean).join(', ')
366
- * },
367
- * },
368
- * }))
381
+ * const schema = createSchema({ type: 'string' })
382
+ * const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | undefined
369
383
  * ```
370
384
  */
371
- const defineFunctionPrinter = createPrinterFactory((node) => kindToHandlerKey[node.kind]);
372
- function rank(param) {
373
- if (param.kind === "ObjectBindingParameter") {
374
- if (param.default) return 2;
375
- return param.optional ?? param.properties.every((p) => p.optional || p.default !== void 0) ? 1 : 0;
376
- }
377
- if (param.rest) return 3;
378
- if (param.default) return 2;
379
- return param.optional ? 1 : 0;
380
- }
381
- function sortParams(params) {
382
- return [...params].sort((a, b) => rank(a) - rank(b));
385
+ function narrowSchema(node, type) {
386
+ return node?.type === type ? node : void 0;
383
387
  }
384
- function sortChildParams(params) {
385
- return [...params].sort((a, b) => rank(a) - rank(b));
388
+ function isKind(kind) {
389
+ return (node) => node.kind === kind;
386
390
  }
387
391
  /**
388
- * Default function-signature printer. Covers the four standard output modes
389
- * used throughout Kubb plugins.
392
+ * Returns `true` when the input is a `RootNode`.
390
393
  *
391
394
  * @example
392
395
  * ```ts
393
- * const printer = functionSignaturePrinter({ mode: 'declaration' })
396
+ * if (isRootNode(node)) {
397
+ * console.log(node.schemas.length)
398
+ * }
399
+ * ```
400
+ */
401
+ const isRootNode = isKind("Root");
402
+ /**
403
+ * Returns `true` when the input is an `OperationNode`.
394
404
  *
395
- * const sig = createFunctionParameters({
396
- * params: [
397
- * createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
398
- * createFunctionParameter({ name: 'config', type: 'Config', optional: false, default: '{}' }),
399
- * ],
400
- * })
405
+ * @example
406
+ * ```ts
407
+ * if (isOperationNode(node)) {
408
+ * console.log(node.operationId)
409
+ * }
410
+ * ```
411
+ */
412
+ const isOperationNode = isKind("Operation");
413
+ /**
414
+ * Returns `true` when the input is a `SchemaNode`.
401
415
  *
402
- * printer.print(sig) // → "petId: string, config: Config = {}"
416
+ * @example
417
+ * ```ts
418
+ * if (isSchemaNode(node)) {
419
+ * console.log(node.type)
420
+ * }
403
421
  * ```
404
422
  */
405
- const functionPrinter = defineFunctionPrinter((options) => ({
406
- name: "functionParameters",
407
- options,
408
- nodes: {
409
- functionParameter(node) {
410
- const { mode, transformName, transformType } = this.options;
411
- const name = transformName ? transformName(node.name) : node.name;
412
- const type = node.type && transformType ? transformType(node.type) : node.type;
413
- if (mode === "keys" || mode === "values") return node.rest ? `...${name}` : name;
414
- if (mode === "call") return node.rest ? `...${name}` : name;
415
- if (node.rest) return type ? `...${name}: ${type}` : `...${name}`;
416
- if (type) {
417
- if (node.optional) return `${name}?: ${type}`;
418
- return node.default ? `${name}: ${type} = ${node.default}` : `${name}: ${type}`;
419
- }
420
- return node.default ? `${name} = ${node.default}` : name;
421
- },
422
- objectBindingParameter(node) {
423
- const { mode, transformName, transformType } = this.options;
424
- const sorted = sortChildParams(node.properties);
425
- const isOptional = node.optional ?? sorted.every((p) => p.optional || p.default !== void 0);
426
- if (node.inline) return sorted.map((p) => this.print(p)).filter(Boolean).join(", ");
427
- if (mode === "keys" || mode === "values") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
428
- if (mode === "call") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
429
- const names = sorted.map((p) => {
430
- return transformName ? transformName(p.name) : p.name;
431
- });
432
- const nameStr = names.length ? `{ ${names.join(", ")} }` : void 0;
433
- if (!nameStr) return null;
434
- let typeAnnotation = node.type;
435
- if (!typeAnnotation) {
436
- const typeParts = sorted.filter((p) => p.type).map((p) => {
437
- const t = transformType && p.type ? transformType(p.type) : p.type;
438
- return p.optional || p.default !== void 0 ? `${p.name}?: ${t}` : `${p.name}: ${t}`;
439
- });
440
- typeAnnotation = typeParts.length ? `{ ${typeParts.join("; ")} }` : void 0;
441
- }
442
- if (typeAnnotation) {
443
- if (isOptional) return `${nameStr}: ${typeAnnotation} = ${node.default ?? "{}"}`;
444
- return node.default ? `${nameStr}: ${typeAnnotation} = ${node.default}` : `${nameStr}: ${typeAnnotation}`;
445
- }
446
- return node.default ? `${nameStr} = ${node.default}` : nameStr;
447
- },
448
- functionParameters(node) {
449
- return sortParams(node.params).map((p) => this.print(p)).filter(Boolean).join(", ");
450
- }
451
- }
452
- }));
453
- //#endregion
454
- //#region src/guards.ts
455
- /**
456
- * Narrows a `SchemaNode` to the specific variant matching `type`.
457
- */
458
- function narrowSchema(node, type) {
459
- return node?.type === type ? node : void 0;
460
- }
461
- function isKind(kind) {
462
- return (node) => node.kind === kind;
463
- }
464
- /**
465
- * Type guard for `RootNode`.
466
- */
467
- const isRootNode = isKind("Root");
468
- /**
469
- * Type guard for `OperationNode`.
470
- */
471
- const isOperationNode = isKind("Operation");
472
- /**
473
- * Type guard for `SchemaNode`.
474
- */
475
423
  const isSchemaNode = isKind("Schema");
476
424
  /**
477
- * Type guard for `PropertyNode`.
425
+ * Returns `true` when the input is a `PropertyNode`.
478
426
  */
479
427
  const isPropertyNode = isKind("Property");
480
428
  /**
481
- * Type guard for `ParameterNode`.
429
+ * Returns `true` when the input is a `ParameterNode`.
482
430
  */
483
431
  const isParameterNode = isKind("Parameter");
484
432
  /**
485
- * Type guard for `ResponseNode`.
433
+ * Returns `true` when the input is a `ResponseNode`.
486
434
  */
487
435
  const isResponseNode = isKind("Response");
488
436
  /**
489
- * Type guard for `FunctionParameterNode`.
437
+ * Returns `true` when the input is a `FunctionParameterNode`.
490
438
  */
491
439
  const isFunctionParameterNode = isKind("FunctionParameter");
492
440
  /**
493
- * Type guard for `ObjectBindingParameterNode`.
441
+ * Returns `true` when the input is an `ObjectBindingParameterNode`.
494
442
  */
495
443
  const isObjectBindingParameterNode = isKind("ObjectBindingParameter");
496
444
  /**
497
- * Type guard for `FunctionParametersNode`.
445
+ * Returns `true` when the input is a `FunctionParametersNode`.
498
446
  */
499
447
  const isFunctionParametersNode = isKind("FunctionParameters");
500
448
  //#endregion
501
- //#region src/refs.ts
502
- /**
503
- * Extracts the final segment from a reference string.
504
- * Falls back to the original string when no slash exists.
505
- */
506
- function extractRefName(ref) {
507
- return ref.split("/").at(-1) ?? ref;
508
- }
449
+ //#region src/utils.ts
450
+ const plainStringTypes = new Set([
451
+ "string",
452
+ "uuid",
453
+ "email",
454
+ "url",
455
+ "datetime"
456
+ ]);
509
457
  /**
510
- * 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
+ * ```
511
468
  */
512
- function buildRefMap(root) {
513
- const map = /* @__PURE__ */ new Map();
514
- for (const schema of root.schemas) if (schema.name) map.set(schema.name, schema);
515
- 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;
516
474
  }
517
475
  /**
518
- * 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
+ * ```
519
491
  */
520
- function resolveRef(refMap, ref) {
521
- 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
+ });
522
501
  }
523
502
  /**
524
- * 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.
525
507
  */
526
- function refMapToObject(refMap) {
527
- 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
+ };
528
515
  }
529
516
  //#endregion
530
- //#region src/transforms.ts
517
+ //#region src/factory.ts
531
518
  /**
532
- * Replaces the discriminator property's schema inside an object node with
533
- * an enum of the provided values.
519
+ * Creates a `RootNode` with stable defaults for `schemas` and `operations`.
520
+ *
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
+ * ```
534
532
  */
535
- function applyDiscriminatorEnum({ node, propertyName, values, enumName }) {
536
- const objectNode = narrowSchema(node, "object");
537
- if (!objectNode?.properties?.length) return node;
538
- if (!objectNode.properties.some((prop) => prop.name === propertyName)) return node;
539
- return createSchema({
540
- ...objectNode,
541
- properties: objectNode.properties.map((prop) => {
542
- if (prop.name !== propertyName) return prop;
543
- return createProperty({
544
- ...prop,
545
- schema: createSchema({
546
- type: "enum",
547
- primitive: "string",
548
- enumValues: values,
549
- name: enumName,
550
- readOnly: prop.schema.readOnly,
551
- writeOnly: prop.schema.writeOnly
552
- })
553
- });
554
- })
555
- });
533
+ function createRoot(overrides = {}) {
534
+ return {
535
+ schemas: [],
536
+ operations: [],
537
+ ...overrides,
538
+ kind: "Root"
539
+ };
556
540
  }
557
541
  /**
558
- * Merges adjacent anonymous object members into a single anonymous object.
542
+ * Creates an `OperationNode` with default empty arrays for `tags`, `parameters`, and `responses`.
543
+ *
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
+ * ```
559
563
  */
560
- function mergeAdjacentAnonymousObjects(members) {
561
- return members.reduce((acc, member) => {
562
- const objectMember = narrowSchema(member, "object");
563
- if (objectMember && !objectMember.name) {
564
- const previous = acc.at(-1);
565
- const previousObject = previous ? narrowSchema(previous, "object") : void 0;
566
- if (previousObject && !previousObject.name) {
567
- acc[acc.length - 1] = createSchema({
568
- ...previousObject,
569
- properties: [...previousObject.properties ?? [], ...objectMember.properties ?? []]
570
- });
571
- return acc;
572
- }
573
- }
574
- acc.push(member);
575
- return acc;
576
- }, []);
564
+ function createOperation(props) {
565
+ return {
566
+ tags: [],
567
+ parameters: [],
568
+ responses: [],
569
+ ...props,
570
+ kind: "Operation"
571
+ };
577
572
  }
578
- /**
579
- * Removes enum members subsumed by broader scalar members in the same union.
580
- */
581
- function simplifyUnionMembers(members) {
582
- const scalarPrimitives = new Set(members.filter((member) => SCALAR_PRIMITIVE_TYPES.has(member.type)).map((m) => m.type));
583
- if (!scalarPrimitives.size) return members;
584
- return members.filter((member) => {
585
- const enumNode = narrowSchema(member, "enum");
586
- if (!enumNode) return true;
587
- const primitive = enumNode.primitive;
588
- if (!primitive) return true;
589
- if (!enumNode.enumType) return true;
590
- if (scalarPrimitives.has(primitive)) return false;
591
- if ((primitive === "integer" || primitive === "number") && (scalarPrimitives.has("integer") || scalarPrimitives.has("number"))) return false;
592
- return true;
593
- });
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
+ };
594
583
  }
595
- //#endregion
596
- //#region ../../internals/utils/dist/index.js
597
584
  /**
598
- * Shared implementation for camelCase and PascalCase conversion.
599
- * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
600
- * and capitalizes each word according to `pascal`.
585
+ * Creates a `PropertyNode`.
601
586
  *
602
- * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
587
+ * `required` defaults to `false`.
588
+ * `schema.optional` and `schema.nullish` are derived from `required` and `schema.nullable`.
589
+ *
590
+ * @example
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
+ * ```
603
608
  */
604
- function toCamelOrPascal(text, pascal) {
605
- 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) => {
606
- if (word.length > 1 && word === word.toUpperCase()) return word;
607
- if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
608
- return word.charAt(0).toUpperCase() + word.slice(1);
609
- }).join("").replace(/[^a-zA-Z0-9]/g, "");
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
+ };
610
617
  }
611
618
  /**
612
- * Splits `text` on `.` and applies `transformPart` to each segment.
613
- * The last segment receives `isLast = true`, all earlier segments receive `false`.
614
- * Segments are joined with `/` to form a file path.
619
+ * Creates a `ParameterNode`.
615
620
  *
616
- * Only splits on dots followed by a letter so that version numbers
617
- * embedded in operationIds (e.g. `v2025.0`) are kept intact.
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
+ * ```
618
643
  */
619
- function applyToFileParts(text, transformPart) {
620
- const parts = text.split(/\.(?=[a-zA-Z])/);
621
- return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
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
+ };
622
652
  }
623
653
  /**
624
- * Converts `text` to camelCase.
625
- * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
654
+ * Creates a `ResponseNode`.
626
655
  *
627
656
  * @example
628
- * camelCase('hello-world') // 'helloWorld'
629
- * camelCase('pet.petId', { isFile: true }) // 'pet/petId'
657
+ * ```ts
658
+ * const response = createResponse({
659
+ * statusCode: '200',
660
+ * description: 'Success',
661
+ * schema: createSchema({ type: 'object', properties: [] }),
662
+ * })
663
+ * ```
630
664
  */
631
- function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
632
- if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
633
- prefix,
634
- suffix
635
- } : {}));
636
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
665
+ function createResponse(props) {
666
+ return {
667
+ ...props,
668
+ kind: "Response"
669
+ };
637
670
  }
638
- /** Returns a `CLIAdapter` with type inference. Pass a different adapter to `createCLI` to swap the CLI engine. */
639
- function defineCLIAdapter(adapter) {
640
- return adapter;
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
+ })]
693
+ });
641
694
  }
642
695
  /**
643
- * Serializes `CommandDefinition[]` to a plain, JSON-serializable structure.
644
- * Use to expose CLI capabilities to AI agents or MCP tools.
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
+ * ```
645
717
  */
646
- function getCommandSchema(defs) {
647
- return defs.map(serializeCommand);
718
+ function createFunctionParameter(props) {
719
+ return {
720
+ optional: false,
721
+ ...props,
722
+ kind: "FunctionParameter"
723
+ };
648
724
  }
649
- function serializeCommand(def) {
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) {
650
752
  return {
651
- name: def.name,
652
- description: def.description,
653
- arguments: def.arguments,
654
- options: serializeOptions(def.options ?? {}),
655
- subCommands: def.subCommands ? def.subCommands.map(serializeCommand) : []
753
+ ...props,
754
+ kind: "ObjectBindingParameter"
656
755
  };
657
756
  }
658
- function serializeOptions(options) {
659
- return Object.entries(options).map(([name, opt]) => {
660
- return {
661
- name,
662
- flags: `${opt.short ? `-${opt.short}, ` : ""}--${name}${opt.type === "string" ? ` <${opt.hint ?? name}>` : ""}`,
663
- type: opt.type,
664
- description: opt.description,
665
- ...opt.default !== void 0 ? { default: opt.default } : {},
666
- ...opt.hint ? { hint: opt.hint } : {},
667
- ...opt.enum ? { enum: opt.enum } : {},
668
- ...opt.required ? { required: opt.required } : {}
669
- };
670
- });
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
+ };
671
782
  }
672
- /** Prints formatted help output for a command using its `CommandDefinition`. */
673
- function renderHelp(def, parentName) {
674
- const schema = getCommandSchema([def])[0];
675
- const programName = parentName ? `${parentName} ${schema.name}` : schema.name;
676
- const argsPart = schema.arguments?.length ? ` ${schema.arguments.join(" ")}` : "";
677
- const subCmdPart = schema.subCommands.length ? " <command>" : "";
678
- console.log(`\n${(0, node_util.styleText)("bold", "Usage:")} ${programName}${argsPart}${subCmdPart} [options]\n`);
679
- if (schema.description) console.log(` ${schema.description}\n`);
680
- if (schema.subCommands.length) {
681
- console.log((0, node_util.styleText)("bold", "Commands:"));
682
- for (const sub of schema.subCommands) console.log(` ${(0, node_util.styleText)("cyan", sub.name.padEnd(16))}${sub.description}`);
683
- console.log();
684
- }
685
- const options = [...schema.options, {
686
- name: "help",
687
- flags: "-h, --help",
688
- type: "boolean",
689
- description: "Show help"
690
- }];
691
- console.log((0, node_util.styleText)("bold", "Options:"));
692
- for (const opt of options) {
693
- const flags = (0, node_util.styleText)("cyan", opt.flags.padEnd(30));
694
- const defaultPart = opt.default !== void 0 ? (0, node_util.styleText)("dim", ` (default: ${opt.default})`) : "";
695
- console.log(` ${flags}${opt.description}${defaultPart}`);
696
- }
697
- console.log();
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);
698
819
  }
699
- function buildParseOptions(def) {
700
- const result = { help: {
701
- type: "boolean",
702
- short: "h"
703
- } };
704
- for (const [name, opt] of Object.entries(def.options ?? {})) result[name] = {
705
- type: opt.type,
706
- ...opt.short ? { short: opt.short } : {},
707
- ...opt.default !== void 0 ? { default: opt.default } : {}
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
+ };
708
850
  };
709
- return result;
710
851
  }
711
- async function runCommand(def, argv, parentName) {
712
- const parseOptions = buildParseOptions(def);
713
- let parsed;
714
- try {
715
- const result = (0, node_util.parseArgs)({
716
- args: argv,
717
- options: parseOptions,
718
- allowPositionals: true,
719
- strict: false
720
- });
721
- parsed = {
722
- values: result.values,
723
- positionals: result.positionals
724
- };
725
- } catch {
726
- renderHelp(def, parentName);
727
- process.exit(1);
728
- }
729
- if (parsed.values["help"]) {
730
- renderHelp(def, parentName);
731
- process.exit(0);
732
- }
733
- for (const [name, opt] of Object.entries(def.options ?? {})) if (opt.required && parsed.values[name] === void 0) {
734
- console.error((0, node_util.styleText)("red", `Error: --${name} is required`));
735
- renderHelp(def, parentName);
736
- process.exit(1);
737
- }
738
- if (!def.run) {
739
- renderHelp(def, parentName);
740
- process.exit(0);
741
- }
742
- try {
743
- await def.run(parsed);
744
- } catch (err) {
745
- console.error((0, node_util.styleText)("red", `Error: ${err instanceof Error ? err.message : String(err)}`));
746
- renderHelp(def, parentName);
747
- process.exit(1);
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;
748
892
  }
893
+ if (param.rest) return 3;
894
+ if (param.default) return 2;
895
+ return param.optional ? 1 : 0;
749
896
  }
750
- function printRootHelp(programName, version, defs) {
751
- console.log(`\n${(0, node_util.styleText)("bold", "Usage:")} ${programName} <command> [options]\n`);
752
- console.log(` Kubb generation — v${version}\n`);
753
- console.log((0, node_util.styleText)("bold", "Commands:"));
754
- for (const def of defs) console.log(` ${(0, node_util.styleText)("cyan", def.name.padEnd(16))}${def.description}`);
755
- console.log();
756
- console.log((0, node_util.styleText)("bold", "Options:"));
757
- console.log(` ${(0, node_util.styleText)("cyan", "-v, --version".padEnd(30))}Show version number`);
758
- console.log(` ${(0, node_util.styleText)("cyan", "-h, --help".padEnd(30))}Show help`);
759
- console.log();
760
- console.log(`Run ${(0, node_util.styleText)("cyan", `${programName} <command> --help`)} for command-specific help.\n`);
897
+ function sortParams(params) {
898
+ return [...params].sort((a, b) => rank(a) - rank(b));
761
899
  }
762
- defineCLIAdapter({
763
- renderHelp(def, parentName) {
764
- renderHelp(def, parentName);
765
- },
766
- async run(defs, argv, opts) {
767
- const { programName, defaultCommandName, version } = opts;
768
- const args = argv.length >= 2 && argv[0]?.includes("node") ? argv.slice(2) : argv;
769
- if (args[0] === "--version" || args[0] === "-v") {
770
- console.log(version);
771
- process.exit(0);
772
- }
773
- if (args[0] === "--help" || args[0] === "-h") {
774
- printRootHelp(programName, version, defs);
775
- process.exit(0);
776
- }
777
- if (args.length === 0) {
778
- const defaultDef = defs.find((d) => d.name === defaultCommandName);
779
- if (defaultDef?.run) await runCommand(defaultDef, [], programName);
780
- else printRootHelp(programName, version, defs);
781
- return;
782
- }
783
- const [first, ...rest] = args;
784
- const isKnownSubcommand = defs.some((d) => d.name === first);
785
- let def;
786
- let commandArgv;
787
- let parentName;
788
- if (isKnownSubcommand) {
789
- def = defs.find((d) => d.name === first);
790
- commandArgv = rest;
791
- parentName = programName;
792
- } else {
793
- def = defs.find((d) => d.name === defaultCommandName);
794
- commandArgv = args;
795
- parentName = programName;
796
- }
797
- if (!def) {
798
- console.error(`Unknown command: ${first}`);
799
- printRootHelp(programName, version, defs);
800
- process.exit(1);
801
- }
802
- if (def.subCommands?.length) {
803
- const [subName, ...subRest] = commandArgv;
804
- const subDef = def.subCommands.find((s) => s.name === subName);
805
- if (subName === "--help" || subName === "-h") {
806
- renderHelp(def, parentName);
807
- process.exit(0);
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}`;
808
935
  }
809
- if (!subDef) {
810
- renderHelp(def, parentName);
811
- 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;
812
957
  }
813
- await runCommand(subDef, subRest, `${parentName} ${def.name}`);
814
- 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(", ");
815
966
  }
816
- await runCommand(def, commandArgv, parentName);
817
967
  }
818
- });
819
- /**
820
- * Parses a CSS hex color string (`#RGB`) into its RGB channels.
821
- * Falls back to `255` for any channel that cannot be parsed.
822
- */
823
- function parseHex(color) {
824
- const int = Number.parseInt(color.replace("#", ""), 16);
825
- return Number.isNaN(int) ? {
826
- r: 255,
827
- g: 255,
828
- b: 255
829
- } : {
830
- r: int >> 16 & 255,
831
- g: int >> 8 & 255,
832
- b: int & 255
833
- };
834
- }
968
+ }));
969
+ //#endregion
970
+ //#region src/refs.ts
835
971
  /**
836
- * Returns a function that wraps a string in a 24-bit ANSI true-color escape sequence
837
- * 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
+ * ```
838
980
  */
839
- function hex(color) {
840
- const { r, g, b } = parseHex(color);
841
- return (text) => `\x1b[38;2;${r};${g};${b}m${text}\x1b[0m`;
981
+ function extractRefName(ref) {
982
+ return ref.split("/").at(-1) ?? ref;
842
983
  }
843
- hex("#F55A17"), hex("#F5A217"), hex("#F58517"), hex("#B45309"), hex("#FFFFFF"), hex("#adadc6"), hex("#FDA4AF");
844
984
  /**
845
- * 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
+ * ```
846
994
  */
847
- function isValidVarName(name) {
848
- try {
849
- new Function(`var ${name}`);
850
- } catch {
851
- return false;
852
- }
853
- 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;
854
999
  }
855
- //#endregion
856
- //#region src/utils.ts
857
- const plainStringTypes = new Set([
858
- "string",
859
- "uuid",
860
- "email",
861
- "url",
862
- "datetime"
863
- ]);
864
1000
  /**
865
- * 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`.
866
1002
  *
867
- * - `string`, `uuid`, `email`, `url`, `datetime` are always plain strings.
868
- * - `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
+ * ```
869
1007
  */
870
- function isPlainStringType(node) {
871
- if (plainStringTypes.has(node.type)) return true;
872
- const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
873
- if (temporal) return temporal.representation !== "date";
874
- return false;
1008
+ function resolveRef(refMap, ref) {
1009
+ return refMap.get(ref);
875
1010
  }
876
1011
  /**
877
- * Transforms the `name` field of each parameter node according to the given casing strategy.
878
- *
879
- * The original `params` array is never mutated — a new array of cloned nodes is returned.
880
- * When no `casing` is provided the original array is returned as-is.
1012
+ * Converts a `RefMap` into a plain object.
881
1013
  *
882
- * Use this before passing parameters to schema builders so that property keys
883
- * in the generated output match the desired casing while the original
884
- * `OperationNode.parameters` array remains untouched for other consumers.
1014
+ * @example
1015
+ * ```ts
1016
+ * const refsObject = refMapToObject(refMap)
1017
+ * ```
885
1018
  */
886
- function applyParamsCasing(params, casing) {
887
- if (!casing) return params;
888
- return params.map((param) => {
889
- const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
890
- return {
891
- ...param,
892
- name: transformed
893
- };
894
- });
1019
+ function refMapToObject(refMap) {
1020
+ return Object.fromEntries(refMap);
895
1021
  }
896
1022
  //#endregion
897
1023
  //#region src/visitor.ts
898
1024
  /**
899
- * Creates a concurrency-limiting wrapper. At most `concurrency` promises may be
900
- * 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
+ * ```
901
1039
  */
902
1040
  function createLimit(concurrency) {
903
1041
  let active = 0;
@@ -923,8 +1061,15 @@ function createLimit(concurrency) {
923
1061
  /**
924
1062
  * Returns the immediate traversable children of `node`.
925
1063
  *
926
- * For `Schema` nodes, children (properties, items, members) are only included
927
- * 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
+ * ```
928
1073
  */
929
1074
  function getChildren(node, recurse) {
930
1075
  switch (node.kind) {
@@ -953,7 +1098,23 @@ function getChildren(node, recurse) {
953
1098
  }
954
1099
  /**
955
1100
  * Depth-first traversal for side effects. Visitor return values are ignored.
956
- * 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
+ * ```
957
1118
  */
958
1119
  async function walk(node, options) {
959
1120
  return _walk(node, options, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30), void 0);
@@ -1086,8 +1247,18 @@ function transform(node, options) {
1086
1247
  }
1087
1248
  }
1088
1249
  /**
1089
- * Combines multiple visitors into a single visitor that applies them sequentially (left to right).
1090
- * 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
+ * ```
1091
1262
  */
1092
1263
  function composeTransformers(...visitors) {
1093
1264
  return {
@@ -1112,7 +1283,24 @@ function composeTransformers(...visitors) {
1112
1283
  };
1113
1284
  }
1114
1285
  /**
1115
- * 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
+ * ```
1116
1304
  */
1117
1305
  function collect(node, options) {
1118
1306
  const { depth, parent, ...visitor } = options;
@@ -1150,12 +1338,173 @@ function collect(node, options) {
1150
1338
  return results;
1151
1339
  }
1152
1340
  //#endregion
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
1153
1500
  exports.SCALAR_PRIMITIVE_TYPES = SCALAR_PRIMITIVE_TYPES;
1154
- exports.applyDiscriminatorEnum = applyDiscriminatorEnum;
1155
- exports.applyParamsCasing = applyParamsCasing;
1156
1501
  exports.buildRefMap = buildRefMap;
1502
+ exports.caseParams = caseParams;
1503
+ exports.childName = childName;
1157
1504
  exports.collect = collect;
1505
+ exports.collectImports = collectImports;
1158
1506
  exports.composeTransformers = composeTransformers;
1507
+ exports.createDiscriminantNode = createDiscriminantNode;
1159
1508
  exports.createFunctionParameter = createFunctionParameter;
1160
1509
  exports.createFunctionParameters = createFunctionParameters;
1161
1510
  exports.createObjectBindingParameter = createObjectBindingParameter;
@@ -1165,8 +1514,11 @@ exports.createProperty = createProperty;
1165
1514
  exports.createResponse = createResponse;
1166
1515
  exports.createRoot = createRoot;
1167
1516
  exports.createSchema = createSchema;
1517
+ exports.defineFunctionPrinter = defineFunctionPrinter;
1168
1518
  exports.definePrinter = definePrinter;
1519
+ exports.enumPropName = enumPropName;
1169
1520
  exports.extractRefName = extractRefName;
1521
+ exports.findDiscriminator = findDiscriminator;
1170
1522
  exports.functionPrinter = functionPrinter;
1171
1523
  exports.httpMethods = httpMethods;
1172
1524
  exports.isFunctionParameterNode = isFunctionParameterNode;
@@ -1174,20 +1526,23 @@ exports.isFunctionParametersNode = isFunctionParametersNode;
1174
1526
  exports.isObjectBindingParameterNode = isObjectBindingParameterNode;
1175
1527
  exports.isOperationNode = isOperationNode;
1176
1528
  exports.isParameterNode = isParameterNode;
1177
- exports.isPlainStringType = isPlainStringType;
1178
1529
  exports.isPropertyNode = isPropertyNode;
1179
1530
  exports.isResponseNode = isResponseNode;
1180
1531
  exports.isRootNode = isRootNode;
1181
1532
  exports.isSchemaNode = isSchemaNode;
1533
+ exports.isStringType = isStringType;
1182
1534
  exports.mediaTypes = mediaTypes;
1183
- exports.mergeAdjacentAnonymousObjects = mergeAdjacentAnonymousObjects;
1535
+ exports.mergeAdjacentObjects = mergeAdjacentObjects;
1184
1536
  exports.narrowSchema = narrowSchema;
1185
1537
  exports.nodeKinds = nodeKinds;
1186
1538
  exports.refMapToObject = refMapToObject;
1539
+ exports.resolveNames = resolveNames;
1187
1540
  exports.resolveRef = resolveRef;
1188
1541
  exports.schemaTypes = schemaTypes;
1189
- exports.simplifyUnionMembers = simplifyUnionMembers;
1190
- exports.syncPropertySchema = syncPropertySchema;
1542
+ exports.setDiscriminatorEnum = setDiscriminatorEnum;
1543
+ exports.setEnumName = setEnumName;
1544
+ exports.simplifyUnion = simplifyUnion;
1545
+ exports.syncOptionality = syncOptionality;
1191
1546
  exports.transform = transform;
1192
1547
  exports.walk = walk;
1193
1548