@kubb/ast 5.0.0-alpha.16 → 5.0.0-alpha.18

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
@@ -1,7 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  Object.defineProperty;
3
3
  //#endregion
4
- let node_util = require("node:util");
5
4
  //#region src/constants.ts
6
5
  const visitorDepths = {
7
6
  shallow: "shallow",
@@ -18,6 +17,17 @@ const nodeKinds = {
18
17
  objectBindingParameter: "ObjectBindingParameter",
19
18
  functionParameters: "FunctionParameters"
20
19
  };
20
+ /**
21
+ * Canonical schema type strings used by AST schema nodes.
22
+ *
23
+ * These values are used across the AST as stable discriminators
24
+ * (for example `schema.type === schemaTypes.object`).
25
+ *
26
+ * The map is grouped by intent:
27
+ * - primitives (`string`, `number`, `boolean`, ...)
28
+ * - structural/composite (`object`, `array`, `union`, ...)
29
+ * - special OpenAPI-oriented types (`ref`, `datetime`, `uuid`, ...)
30
+ */
21
31
  const schemaTypes = {
22
32
  string: "string",
23
33
  number: "number",
@@ -45,7 +55,7 @@ const schemaTypes = {
45
55
  never: "never"
46
56
  };
47
57
  /**
48
- * Scalar primitive schema types used for union member simplification.
58
+ * Primitive scalar schema types used when simplifying union members.
49
59
  */
50
60
  const SCALAR_PRIMITIVE_TYPES = new Set([
51
61
  "string",
@@ -86,9 +96,241 @@ const mediaTypes = {
86
96
  videoMp4: "video/mp4"
87
97
  };
88
98
  //#endregion
99
+ //#region ../../internals/utils/src/casing.ts
100
+ /**
101
+ * Shared implementation for camelCase and PascalCase conversion.
102
+ * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
103
+ * and capitalizes each word according to `pascal`.
104
+ *
105
+ * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
106
+ */
107
+ function toCamelOrPascal(text, pascal) {
108
+ 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) => {
109
+ if (word.length > 1 && word === word.toUpperCase()) return word;
110
+ if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
111
+ return word.charAt(0).toUpperCase() + word.slice(1);
112
+ }).join("").replace(/[^a-zA-Z0-9]/g, "");
113
+ }
114
+ /**
115
+ * Splits `text` on `.` and applies `transformPart` to each segment.
116
+ * The last segment receives `isLast = true`, all earlier segments receive `false`.
117
+ * Segments are joined with `/` to form a file path.
118
+ *
119
+ * Only splits on dots followed by a letter so that version numbers
120
+ * embedded in operationIds (e.g. `v2025.0`) are kept intact.
121
+ */
122
+ function applyToFileParts(text, transformPart) {
123
+ const parts = text.split(/\.(?=[a-zA-Z])/);
124
+ return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
125
+ }
126
+ /**
127
+ * Converts `text` to camelCase.
128
+ * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
129
+ *
130
+ * @example
131
+ * camelCase('hello-world') // 'helloWorld'
132
+ * camelCase('pet.petId', { isFile: true }) // 'pet/petId'
133
+ */
134
+ function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
135
+ if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
136
+ prefix,
137
+ suffix
138
+ } : {}));
139
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
140
+ }
141
+ /**
142
+ * Converts `text` to PascalCase.
143
+ * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
144
+ *
145
+ * @example
146
+ * pascalCase('hello-world') // 'HelloWorld'
147
+ * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
148
+ */
149
+ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
150
+ if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
151
+ prefix,
152
+ suffix
153
+ }) : camelCase(part));
154
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
155
+ }
156
+ //#endregion
157
+ //#region ../../internals/utils/src/reserved.ts
158
+ /**
159
+ * Returns `true` when `name` is a syntactically valid JavaScript variable name.
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * isValidVarName('status') // true
164
+ * isValidVarName('class') // false (reserved word)
165
+ * isValidVarName('42foo') // false (starts with digit)
166
+ * ```
167
+ */
168
+ function isValidVarName(name) {
169
+ try {
170
+ new Function(`var ${name}`);
171
+ } catch {
172
+ return false;
173
+ }
174
+ return true;
175
+ }
176
+ //#endregion
177
+ //#region src/guards.ts
178
+ /**
179
+ * Narrows a `SchemaNode` to the variant that matches `type`.
180
+ *
181
+ * @example
182
+ * ```ts
183
+ * const schema = createSchema({ type: 'string' })
184
+ * const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | undefined
185
+ * ```
186
+ */
187
+ function narrowSchema(node, type) {
188
+ return node?.type === type ? node : void 0;
189
+ }
190
+ function isKind(kind) {
191
+ return (node) => node.kind === kind;
192
+ }
193
+ /**
194
+ * Returns `true` when the input is a `RootNode`.
195
+ *
196
+ * @example
197
+ * ```ts
198
+ * if (isRootNode(node)) {
199
+ * console.log(node.schemas.length)
200
+ * }
201
+ * ```
202
+ */
203
+ const isRootNode = isKind("Root");
204
+ /**
205
+ * Returns `true` when the input is an `OperationNode`.
206
+ *
207
+ * @example
208
+ * ```ts
209
+ * if (isOperationNode(node)) {
210
+ * console.log(node.operationId)
211
+ * }
212
+ * ```
213
+ */
214
+ const isOperationNode = isKind("Operation");
215
+ /**
216
+ * Returns `true` when the input is a `SchemaNode`.
217
+ *
218
+ * @example
219
+ * ```ts
220
+ * if (isSchemaNode(node)) {
221
+ * console.log(node.type)
222
+ * }
223
+ * ```
224
+ */
225
+ const isSchemaNode = isKind("Schema");
226
+ /**
227
+ * Returns `true` when the input is a `PropertyNode`.
228
+ */
229
+ const isPropertyNode = isKind("Property");
230
+ /**
231
+ * Returns `true` when the input is a `ParameterNode`.
232
+ */
233
+ const isParameterNode = isKind("Parameter");
234
+ /**
235
+ * Returns `true` when the input is a `ResponseNode`.
236
+ */
237
+ const isResponseNode = isKind("Response");
238
+ /**
239
+ * Returns `true` when the input is a `FunctionParameterNode`.
240
+ */
241
+ const isFunctionParameterNode = isKind("FunctionParameter");
242
+ /**
243
+ * Returns `true` when the input is an `ObjectBindingParameterNode`.
244
+ */
245
+ const isObjectBindingParameterNode = isKind("ObjectBindingParameter");
246
+ /**
247
+ * Returns `true` when the input is a `FunctionParametersNode`.
248
+ */
249
+ const isFunctionParametersNode = isKind("FunctionParameters");
250
+ //#endregion
251
+ //#region src/utils.ts
252
+ const plainStringTypes = new Set([
253
+ "string",
254
+ "uuid",
255
+ "email",
256
+ "url",
257
+ "datetime"
258
+ ]);
259
+ /**
260
+ * Returns `true` when a schema is emitted as a plain TypeScript `string`.
261
+ *
262
+ * - `string`, `uuid`, `email`, `url`, `datetime` are always plain strings.
263
+ * - `date` and `time` are plain strings when their `representation` is `'string'` rather than `'date'`.
264
+ *
265
+ * @example
266
+ * ```ts
267
+ * isStringType(createSchema({ type: 'uuid' })) // true
268
+ * isStringType(createSchema({ type: 'date', representation: 'date' })) // false
269
+ * ```
270
+ */
271
+ function isStringType(node) {
272
+ if (plainStringTypes.has(node.type)) return true;
273
+ const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
274
+ if (temporal) return temporal.representation !== "date";
275
+ return false;
276
+ }
277
+ /**
278
+ * Applies casing rules to parameter names and returns a new parameter array.
279
+ *
280
+ * The input array is not mutated.
281
+ * If `casing` is not set, the original array is returned unchanged.
282
+ *
283
+ * Use this before passing parameters to schema builders so that property keys
284
+ * in generated output match the desired casing while preserving
285
+ * `OperationNode.parameters` for other consumers.
286
+ *
287
+ * @example
288
+ * ```ts
289
+ * const params = [createParameter({ name: 'pet_id', in: 'query', schema: createSchema({ type: 'string' }) })]
290
+ * const cased = caseParams(params, 'camelcase')
291
+ * // cased[0].name === 'petId'
292
+ * ```
293
+ */
294
+ function caseParams(params, casing) {
295
+ if (!casing) return params;
296
+ return params.map((param) => {
297
+ const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
298
+ return {
299
+ ...param,
300
+ name: transformed
301
+ };
302
+ });
303
+ }
304
+ /**
305
+ * Syncs property/parameter schema optionality flags from `required` and `schema.nullable`.
306
+ *
307
+ * - `optional` is set for non-required, non-nullable schemas.
308
+ * - `nullish` is set for non-required, nullable schemas.
309
+ */
310
+ function syncOptionality(required, schema) {
311
+ const nullable = schema.nullable ?? false;
312
+ return {
313
+ ...schema,
314
+ optional: !required && !nullable ? true : void 0,
315
+ nullish: !required && nullable ? true : void 0
316
+ };
317
+ }
318
+ //#endregion
89
319
  //#region src/factory.ts
90
320
  /**
91
- * Creates a `RootNode`.
321
+ * Creates a `RootNode` with stable defaults for `schemas` and `operations`.
322
+ *
323
+ * @example
324
+ * ```ts
325
+ * const root = createRoot()
326
+ * // { kind: 'Root', schemas: [], operations: [] }
327
+ * ```
328
+ *
329
+ * @example
330
+ * ```ts
331
+ * const root = createRoot({ schemas: [petSchema] })
332
+ * // keeps default operations: []
333
+ * ```
92
334
  */
93
335
  function createRoot(overrides = {}) {
94
336
  return {
@@ -99,7 +341,27 @@ function createRoot(overrides = {}) {
99
341
  };
100
342
  }
101
343
  /**
102
- * Creates an `OperationNode`.
344
+ * Creates an `OperationNode` with default empty arrays for `tags`, `parameters`, and `responses`.
345
+ *
346
+ * @example
347
+ * ```ts
348
+ * const operation = createOperation({
349
+ * operationId: 'getPetById',
350
+ * method: 'GET',
351
+ * path: '/pet/{petId}',
352
+ * })
353
+ * // tags, parameters, and responses are []
354
+ * ```
355
+ *
356
+ * @example
357
+ * ```ts
358
+ * const operation = createOperation({
359
+ * operationId: 'findPets',
360
+ * method: 'GET',
361
+ * path: '/pet/findByStatus',
362
+ * tags: ['pet'],
363
+ * })
364
+ * ```
103
365
  */
104
366
  function createOperation(props) {
105
367
  return {
@@ -122,20 +384,29 @@ function createSchema(props) {
122
384
  };
123
385
  }
124
386
  /**
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.
127
- */
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
- }
136
- /**
137
- * Creates a `PropertyNode`. `required` defaults to `false`.
138
- * `schema.optional` and `schema.nullish` are auto-derived from `required` and `schema.nullable`.
387
+ * Creates a `PropertyNode`.
388
+ *
389
+ * `required` defaults to `false`.
390
+ * `schema.optional` and `schema.nullish` are derived from `required` and `schema.nullable`.
391
+ *
392
+ * @example
393
+ * ```ts
394
+ * const property = createProperty({
395
+ * name: 'status',
396
+ * schema: createSchema({ type: 'string' }),
397
+ * })
398
+ * // required=false, schema.optional=true
399
+ * ```
400
+ *
401
+ * @example
402
+ * ```ts
403
+ * const property = createProperty({
404
+ * name: 'status',
405
+ * required: true,
406
+ * schema: createSchema({ type: 'string', nullable: true }),
407
+ * })
408
+ * // required=true, no optional/nullish
409
+ * ```
139
410
  */
140
411
  function createProperty(props) {
141
412
  const required = props.required ?? false;
@@ -143,12 +414,34 @@ function createProperty(props) {
143
414
  ...props,
144
415
  kind: "Property",
145
416
  required,
146
- schema: syncPropertySchema(required, props.schema)
417
+ schema: syncOptionality(required, props.schema)
147
418
  };
148
419
  }
149
420
  /**
150
- * Creates a `ParameterNode`. `required` defaults to `false`.
151
- * `schema.optional` is auto-derived from `required` and `schema.nullable`.
421
+ * Creates a `ParameterNode`.
422
+ *
423
+ * `required` defaults to `false`.
424
+ * Nested schema flags are set from `required` and `schema.nullable`.
425
+ *
426
+ * @example
427
+ * ```ts
428
+ * const param = createParameter({
429
+ * name: 'petId',
430
+ * in: 'path',
431
+ * required: true,
432
+ * schema: createSchema({ type: 'string' }),
433
+ * })
434
+ * ```
435
+ *
436
+ * @example
437
+ * ```ts
438
+ * const param = createParameter({
439
+ * name: 'status',
440
+ * in: 'query',
441
+ * schema: createSchema({ type: 'string', nullable: true }),
442
+ * })
443
+ * // required=false, schema.nullish=true
444
+ * ```
152
445
  */
153
446
  function createParameter(props) {
154
447
  const required = props.required ?? false;
@@ -156,11 +449,20 @@ function createParameter(props) {
156
449
  ...props,
157
450
  kind: "Parameter",
158
451
  required,
159
- schema: syncPropertySchema(required, props.schema)
452
+ schema: syncOptionality(required, props.schema)
160
453
  };
161
454
  }
162
455
  /**
163
456
  * Creates a `ResponseNode`.
457
+ *
458
+ * @example
459
+ * ```ts
460
+ * const response = createResponse({
461
+ * statusCode: '200',
462
+ * description: 'Success',
463
+ * schema: createSchema({ type: 'object', properties: [] }),
464
+ * })
465
+ * ```
164
466
  */
165
467
  function createResponse(props) {
166
468
  return {
@@ -169,7 +471,33 @@ function createResponse(props) {
169
471
  };
170
472
  }
171
473
  /**
172
- * Creates a `FunctionParameterNode`. `optional` defaults to `false`.
474
+ * Creates a single-property object schema used as a discriminator literal.
475
+ *
476
+ * @example
477
+ * ```ts
478
+ * createDiscriminantNode({ propertyName: 'type', value: 'dog' })
479
+ * // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
480
+ * ```
481
+ */
482
+ function createDiscriminantNode({ propertyName, value }) {
483
+ return createSchema({
484
+ type: "object",
485
+ primitive: "object",
486
+ properties: [createProperty({
487
+ name: propertyName,
488
+ schema: createSchema({
489
+ type: "enum",
490
+ primitive: "string",
491
+ enumValues: [value]
492
+ }),
493
+ required: true
494
+ })]
495
+ });
496
+ }
497
+ /**
498
+ * Creates a `FunctionParameterNode`.
499
+ *
500
+ * `optional` defaults to `false`.
173
501
  *
174
502
  * @example Required typed param
175
503
  * ```ts
@@ -183,7 +511,7 @@ function createResponse(props) {
183
511
  * // → params?: QueryParams
184
512
  * ```
185
513
  *
186
- * @example Param with default (implicitly optional cannot combine with `optional: true`)
514
+ * @example Param with default (implicitly optional; cannot combine with `optional: true`)
187
515
  * ```ts
188
516
  * createFunctionParameter({ name: 'config', type: 'RequestConfig', default: '{}' })
189
517
  * // → config: RequestConfig = {}
@@ -197,7 +525,7 @@ function createFunctionParameter(props) {
197
525
  };
198
526
  }
199
527
  /**
200
- * Creates an `ObjectBindingParameterNode` an object-destructured parameter group.
528
+ * Creates an `ObjectBindingParameterNode` for object-destructured parameter groups.
201
529
  *
202
530
  * @example Destructured object param
203
531
  * ```ts
@@ -212,7 +540,7 @@ function createFunctionParameter(props) {
212
540
  * // call → { id, name }
213
541
  * ```
214
542
  *
215
- * @example Inline — children emitted as individual top-level params
543
+ * @example Inline mode — children emitted as individual top-level parameters
216
544
  * ```ts
217
545
  * createObjectBindingParameter({
218
546
  * properties: [createFunctionParameter({ name: 'petId', type: 'string', optional: false })],
@@ -229,7 +557,7 @@ function createObjectBindingParameter(props) {
229
557
  };
230
558
  }
231
559
  /**
232
- * Creates a `FunctionParametersNode` from an ordered list of params.
560
+ * Creates a `FunctionParametersNode` from an ordered list of parameters.
233
561
  *
234
562
  * @example
235
563
  * ```ts
@@ -240,6 +568,12 @@ function createObjectBindingParameter(props) {
240
568
  * ],
241
569
  * })
242
570
  * ```
571
+ *
572
+ * @example
573
+ * ```ts
574
+ * const empty = createFunctionParameters()
575
+ * // { kind: 'FunctionParameters', params: [] }
576
+ * ```
243
577
  */
244
578
  function createFunctionParameters(props = {}) {
245
579
  return {
@@ -249,18 +583,19 @@ function createFunctionParameters(props = {}) {
249
583
  };
250
584
  }
251
585
  //#endregion
252
- //#region src/printer.ts
586
+ //#region src/printers/printer.ts
253
587
  /**
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.
588
+ * Creates a schema printer factory.
589
+ *
590
+ * This function wraps a builder and makes options optional at call sites.
257
591
  *
258
592
  * The builder receives resolved options and returns:
259
593
  * - `name` — a unique identifier for the printer
260
594
  * - `options` — options stored on the returned printer instance
261
595
  * - `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.
596
+ * - `print` _(optional)_ — top-level override exposed as `printer.print`
597
+ * - Inside this function, `this.print(node)` still dispatches to the `nodes` map
598
+ * - This keeps recursion safe and avoids self-calls
264
599
  *
265
600
  * When no `print` override is provided, `printer.print` is the node-level dispatcher directly.
266
601
  *
@@ -280,32 +615,13 @@ function createFunctionParameters(props = {}) {
280
615
  * },
281
616
  * }))
282
617
  * ```
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
618
  */
300
619
  function definePrinter(build) {
301
620
  return createPrinterFactory((node) => node.type)(build);
302
621
  }
303
622
  /**
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
- *
623
+ * Generic printer-factory function used by `definePrinter` and `defineFunctionPrinter`.
624
+ **
309
625
  * @example
310
626
  * ```ts
311
627
  * export const defineFunctionPrinter = createPrinterFactory<FunctionNode, FunctionNodeType, FunctionNodeByType>(
@@ -321,9 +637,9 @@ function createPrinterFactory(getKey) {
321
637
  options: resolvedOptions,
322
638
  print: (node) => {
323
639
  const key = getKey(node);
324
- if (key === void 0) return void 0;
640
+ if (key === void 0) return null;
325
641
  const handler = nodes[key];
326
- if (!handler) return void 0;
642
+ if (!handler) return null;
327
643
  return handler.call(context, node);
328
644
  }
329
645
  };
@@ -336,15 +652,17 @@ function createPrinterFactory(getKey) {
336
652
  };
337
653
  }
338
654
  //#endregion
339
- //#region src/functionPrinter.ts
655
+ //#region src/printers/functionPrinter.ts
340
656
  const kindToHandlerKey = {
341
657
  FunctionParameter: "functionParameter",
342
658
  ObjectBindingParameter: "objectBindingParameter",
343
659
  FunctionParameters: "functionParameters"
344
660
  };
345
661
  /**
346
- * Creates a named function-signature printer factory.
347
- * Built on `createPrinterFactory` — dispatches on `node.kind` instead of `node.type`.
662
+ * Creates a function-parameter printer factory.
663
+ *
664
+ * This wrapper uses `createPrinterFactory` and dispatches handlers by `node.kind`
665
+ * (for function nodes) rather than by `node.type` (for schema nodes).
348
666
  *
349
667
  * @example
350
668
  * ```ts
@@ -385,12 +703,12 @@ function sortChildParams(params) {
385
703
  return [...params].sort((a, b) => rank(a) - rank(b));
386
704
  }
387
705
  /**
388
- * Default function-signature printer. Covers the four standard output modes
389
- * used throughout Kubb plugins.
706
+ * Default function-signature printer.
707
+ * Covers the four standard output modes used across Kubb plugins.
390
708
  *
391
709
  * @example
392
710
  * ```ts
393
- * const printer = functionSignaturePrinter({ mode: 'declaration' })
711
+ * const printer = functionPrinter({ mode: 'declaration' })
394
712
  *
395
713
  * const sig = createFunctionParameters({
396
714
  * params: [
@@ -451,63 +769,30 @@ const functionPrinter = defineFunctionPrinter((options) => ({
451
769
  }
452
770
  }));
453
771
  //#endregion
454
- //#region src/guards.ts
772
+ //#region src/refs.ts
455
773
  /**
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
- const isSchemaNode = isKind("Schema");
476
- /**
477
- * Type guard for `PropertyNode`.
478
- */
479
- const isPropertyNode = isKind("Property");
480
- /**
481
- * Type guard for `ParameterNode`.
482
- */
483
- const isParameterNode = isKind("Parameter");
484
- /**
485
- * Type guard for `ResponseNode`.
486
- */
487
- const isResponseNode = isKind("Response");
488
- /**
489
- * Type guard for `FunctionParameterNode`.
490
- */
491
- const isFunctionParameterNode = isKind("FunctionParameter");
492
- /**
493
- * Type guard for `ObjectBindingParameterNode`.
494
- */
495
- const isObjectBindingParameterNode = isKind("ObjectBindingParameter");
496
- /**
497
- * Type guard for `FunctionParametersNode`.
498
- */
499
- const isFunctionParametersNode = isKind("FunctionParameters");
500
- //#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.
774
+ * Returns the last path segment of a reference string.
775
+ *
776
+ * Example: `#/components/schemas/Pet` becomes `Pet`.
777
+ *
778
+ * @example
779
+ * ```ts
780
+ * extractRefName('#/components/schemas/Pet') // 'Pet'
781
+ * ```
505
782
  */
506
783
  function extractRefName(ref) {
507
784
  return ref.split("/").at(-1) ?? ref;
508
785
  }
509
786
  /**
510
- * Indexes named schemas from `root.schemas` by name. Unnamed schemas are skipped.
787
+ * Builds a `RefMap` from `root.schemas` using each schema's `name`.
788
+ *
789
+ * Unnamed schemas are skipped.
790
+ *
791
+ * @example
792
+ * ```ts
793
+ * const refMap = buildRefMap(root)
794
+ * const pet = refMap.get('Pet')
795
+ * ```
511
796
  */
512
797
  function buildRefMap(root) {
513
798
  const map = /* @__PURE__ */ new Map();
@@ -515,389 +800,44 @@ function buildRefMap(root) {
515
800
  return map;
516
801
  }
517
802
  /**
518
- * Looks up a schema by name. Prefer over `RefMap.get()` to keep the resolution strategy swappable.
803
+ * Resolves a schema by name from a `RefMap`.
804
+ *
805
+ * @example
806
+ * ```ts
807
+ * const petSchema = resolveRef(refMap, 'Pet')
808
+ * ```
519
809
  */
520
810
  function resolveRef(refMap, ref) {
521
811
  return refMap.get(ref);
522
812
  }
523
813
  /**
524
- * Converts a `RefMap` to a plain object.
814
+ * Converts a `RefMap` into a plain object.
815
+ *
816
+ * @example
817
+ * ```ts
818
+ * const refsObject = refMapToObject(refMap)
819
+ * ```
525
820
  */
526
821
  function refMapToObject(refMap) {
527
822
  return Object.fromEntries(refMap);
528
823
  }
529
824
  //#endregion
530
- //#region src/transforms.ts
531
- /**
532
- * Replaces the discriminator property's schema inside an object node with
533
- * an enum of the provided values.
534
- */
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
- });
556
- }
557
- /**
558
- * Merges adjacent anonymous object members into a single anonymous object.
559
- */
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
- }, []);
577
- }
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
- });
594
- }
595
- //#endregion
596
- //#region ../../internals/utils/dist/index.js
597
- /**
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`.
601
- *
602
- * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
603
- */
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, "");
610
- }
825
+ //#region src/visitor.ts
611
826
  /**
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.
827
+ * Creates a small async concurrency limiter.
615
828
  *
616
- * Only splits on dots followed by a letter so that version numbers
617
- * embedded in operationIds (e.g. `v2025.0`) are kept intact.
618
- */
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("/");
622
- }
623
- /**
624
- * Converts `text` to camelCase.
625
- * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
829
+ * At most `concurrency` tasks are in flight at once. Extra tasks are queued.
626
830
  *
627
831
  * @example
628
- * camelCase('hello-world') // 'helloWorld'
629
- * camelCase('pet.petId', { isFile: true }) // 'pet/petId'
630
- */
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);
637
- }
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;
641
- }
642
- /**
643
- * Serializes `CommandDefinition[]` to a plain, JSON-serializable structure.
644
- * Use to expose CLI capabilities to AI agents or MCP tools.
645
- */
646
- function getCommandSchema(defs) {
647
- return defs.map(serializeCommand);
648
- }
649
- function serializeCommand(def) {
650
- 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) : []
656
- };
657
- }
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
- });
671
- }
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();
698
- }
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 } : {}
708
- };
709
- return result;
710
- }
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);
748
- }
749
- }
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`);
761
- }
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);
808
- }
809
- if (!subDef) {
810
- renderHelp(def, parentName);
811
- process.exit(subName ? 1 : 0);
812
- }
813
- await runCommand(subDef, subRest, `${parentName} ${def.name}`);
814
- return;
815
- }
816
- await runCommand(def, commandArgv, parentName);
817
- }
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
- }
835
- /**
836
- * Returns a function that wraps a string in a 24-bit ANSI true-color escape sequence
837
- * for the given hex color.
838
- */
839
- function hex(color) {
840
- const { r, g, b } = parseHex(color);
841
- return (text) => `\x1b[38;2;${r};${g};${b}m${text}\x1b[0m`;
842
- }
843
- hex("#F55A17"), hex("#F5A217"), hex("#F58517"), hex("#B45309"), hex("#FFFFFF"), hex("#adadc6"), hex("#FDA4AF");
844
- /**
845
- * Returns `true` when `name` is a syntactically valid JavaScript variable name.
846
- */
847
- function isValidVarName(name) {
848
- try {
849
- new Function(`var ${name}`);
850
- } catch {
851
- return false;
852
- }
853
- return true;
854
- }
855
- //#endregion
856
- //#region src/utils.ts
857
- const plainStringTypes = new Set([
858
- "string",
859
- "uuid",
860
- "email",
861
- "url",
862
- "datetime"
863
- ]);
864
- /**
865
- * Returns `true` when a schema node will be represented as a plain string in generated code.
866
- *
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'`.
869
- */
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;
875
- }
876
- /**
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.
881
- *
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.
885
- */
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
- });
895
- }
896
- //#endregion
897
- //#region src/visitor.ts
898
- /**
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.
832
+ * ```ts
833
+ * const limit = createLimit(2)
834
+ * await Promise.all([
835
+ * limit(() => taskA()),
836
+ * limit(() => taskB()),
837
+ * limit(() => taskC()),
838
+ * ])
839
+ * // only 2 tasks run at the same time
840
+ * ```
901
841
  */
902
842
  function createLimit(concurrency) {
903
843
  let active = 0;
@@ -923,8 +863,15 @@ function createLimit(concurrency) {
923
863
  /**
924
864
  * Returns the immediate traversable children of `node`.
925
865
  *
926
- * For `Schema` nodes, children (properties, items, members) are only included
927
- * when `recurse` is `true`; shallow traversal omits them entirely.
866
+ * For `Schema` nodes, children (`properties`, `items`, `members`, and non-boolean
867
+ * `additionalProperties`) are only included
868
+ * when `recurse` is `true`; shallow mode skips them.
869
+ *
870
+ * @example
871
+ * ```ts
872
+ * const children = getChildren(operationNode, true)
873
+ * // returns parameters, requestBody schema (if present), and responses
874
+ * ```
928
875
  */
929
876
  function getChildren(node, recurse) {
930
877
  switch (node.kind) {
@@ -953,7 +900,23 @@ function getChildren(node, recurse) {
953
900
  }
954
901
  /**
955
902
  * 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).
903
+ * Sibling nodes at each level are visited concurrently up to `options.concurrency`
904
+ * (default: `WALK_CONCURRENCY`).
905
+ *
906
+ * @example
907
+ * ```ts
908
+ * await walk(root, {
909
+ * operation(node) {
910
+ * console.log(node.operationId)
911
+ * },
912
+ * })
913
+ * ```
914
+ *
915
+ * @example
916
+ * ```ts
917
+ * // Visit only the current node
918
+ * await walk(root, { depth: 'shallow', root: () => {} })
919
+ * ```
957
920
  */
958
921
  async function walk(node, options) {
959
922
  return _walk(node, options, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30), void 0);
@@ -1086,8 +1049,18 @@ function transform(node, options) {
1086
1049
  }
1087
1050
  }
1088
1051
  /**
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.
1052
+ * Composes multiple visitors into one visitor, applied left to right.
1053
+ *
1054
+ * For each node kind, output from one visitor is input to the next.
1055
+ * If a visitor returns `undefined`, the previous node value is kept.
1056
+ *
1057
+ * @example
1058
+ * ```ts
1059
+ * const visitor = composeTransformers(
1060
+ * { operation: (node) => ({ ...node, operationId: `a_${node.operationId}` }) },
1061
+ * { operation: (node) => ({ ...node, operationId: `b_${node.operationId}` }) },
1062
+ * )
1063
+ * ```
1091
1064
  */
1092
1065
  function composeTransformers(...visitors) {
1093
1066
  return {
@@ -1112,7 +1085,24 @@ function composeTransformers(...visitors) {
1112
1085
  };
1113
1086
  }
1114
1087
  /**
1115
- * Depth-first synchronous reduction. Collects non-`undefined` visitor return values into an array.
1088
+ * Runs a depth-first synchronous collection pass.
1089
+ *
1090
+ * Non-`undefined` values returned by visitor callbacks are appended to the result.
1091
+ *
1092
+ * @example
1093
+ * ```ts
1094
+ * const ids = collect(root, {
1095
+ * operation(node) {
1096
+ * return node.operationId
1097
+ * },
1098
+ * })
1099
+ * ```
1100
+ *
1101
+ * @example
1102
+ * ```ts
1103
+ * // Collect from only the current node
1104
+ * const values = collect(root, { depth: 'shallow', root: () => 'root' })
1105
+ * ```
1116
1106
  */
1117
1107
  function collect(node, options) {
1118
1108
  const { depth, parent, ...visitor } = options;
@@ -1150,12 +1140,173 @@ function collect(node, options) {
1150
1140
  return results;
1151
1141
  }
1152
1142
  //#endregion
1143
+ //#region src/resolvers.ts
1144
+ function findDiscriminator(mapping, ref) {
1145
+ if (!mapping || !ref) return null;
1146
+ return Object.entries(mapping).find(([, value]) => value === ref)?.[0] ?? null;
1147
+ }
1148
+ function childName(parentName, propName) {
1149
+ return parentName ? pascalCase([parentName, propName].join(" ")) : null;
1150
+ }
1151
+ function enumPropName(parentName, propName, enumSuffix) {
1152
+ return pascalCase([
1153
+ parentName,
1154
+ propName,
1155
+ enumSuffix
1156
+ ].filter(Boolean).join(" "));
1157
+ }
1158
+ /**
1159
+ * Collects import entries for all `ref` schema nodes in `node`.
1160
+ */
1161
+ function collectImports({ node, nameMapping, resolve }) {
1162
+ return collect(node, { schema(schemaNode) {
1163
+ const schemaRef = narrowSchema(schemaNode, "ref");
1164
+ if (!schemaRef?.ref) return;
1165
+ const rawName = extractRefName(schemaRef.ref);
1166
+ const result = resolve(nameMapping.get(rawName) ?? rawName);
1167
+ if (!result) return;
1168
+ return result;
1169
+ } });
1170
+ }
1171
+ //#endregion
1172
+ //#region src/transformers.ts
1173
+ /**
1174
+ * Replaces a discriminator property's schema with a string enum of allowed values.
1175
+ *
1176
+ * If `node` is not an object schema, or if the property does not exist, the input
1177
+ * node is returned as-is.
1178
+ *
1179
+ * @example
1180
+ * ```ts
1181
+ * const schema = createSchema({
1182
+ * type: 'object',
1183
+ * properties: [createProperty({ name: 'type', required: true, schema: createSchema({ type: 'string' }) })],
1184
+ * })
1185
+ * const result = setDiscriminatorEnum({ node: schema, propertyName: 'type', values: ['dog', 'cat'] })
1186
+ * ```
1187
+ */
1188
+ function setDiscriminatorEnum({ node, propertyName, values, enumName }) {
1189
+ const objectNode = narrowSchema(node, "object");
1190
+ if (!objectNode?.properties?.length) return node;
1191
+ if (!objectNode.properties.some((prop) => prop.name === propertyName)) return node;
1192
+ return createSchema({
1193
+ ...objectNode,
1194
+ properties: objectNode.properties.map((prop) => {
1195
+ if (prop.name !== propertyName) return prop;
1196
+ return createProperty({
1197
+ ...prop,
1198
+ schema: createSchema({
1199
+ type: "enum",
1200
+ primitive: "string",
1201
+ enumValues: values,
1202
+ name: enumName,
1203
+ readOnly: prop.schema.readOnly,
1204
+ writeOnly: prop.schema.writeOnly
1205
+ })
1206
+ });
1207
+ })
1208
+ });
1209
+ }
1210
+ /**
1211
+ * Merges adjacent anonymous object members into a single anonymous object member.
1212
+ *
1213
+ * @example
1214
+ * ```ts
1215
+ * const merged = mergeAdjacentObjects([
1216
+ * createSchema({ type: 'object', properties: [createProperty({ name: 'a', schema: createSchema({ type: 'string' }) })] }),
1217
+ * createSchema({ type: 'object', properties: [createProperty({ name: 'b', schema: createSchema({ type: 'number' }) })] }),
1218
+ * ])
1219
+ * ```
1220
+ */
1221
+ function mergeAdjacentObjects(members) {
1222
+ return members.reduce((acc, member) => {
1223
+ const objectMember = narrowSchema(member, "object");
1224
+ if (objectMember && !objectMember.name) {
1225
+ const previous = acc.at(-1);
1226
+ const previousObject = previous ? narrowSchema(previous, "object") : void 0;
1227
+ if (previousObject && !previousObject.name) {
1228
+ acc[acc.length - 1] = createSchema({
1229
+ ...previousObject,
1230
+ properties: [...previousObject.properties ?? [], ...objectMember.properties ?? []]
1231
+ });
1232
+ return acc;
1233
+ }
1234
+ }
1235
+ acc.push(member);
1236
+ return acc;
1237
+ }, []);
1238
+ }
1239
+ /**
1240
+ * Removes enum members that are covered by broader scalar primitives in the same union.
1241
+ *
1242
+ * @example
1243
+ * ```ts
1244
+ * const simplified = simplifyUnion([
1245
+ * createSchema({ type: 'enum', primitive: 'string', enumValues: ['active'] }),
1246
+ * createSchema({ type: 'string' }),
1247
+ * ])
1248
+ * // keeps only string member
1249
+ * ```
1250
+ */
1251
+ function simplifyUnion(members) {
1252
+ const scalarPrimitives = new Set(members.filter((member) => SCALAR_PRIMITIVE_TYPES.has(member.type)).map((m) => m.type));
1253
+ if (!scalarPrimitives.size) return members;
1254
+ return members.filter((member) => {
1255
+ const enumNode = narrowSchema(member, "enum");
1256
+ if (!enumNode) return true;
1257
+ const primitive = enumNode.primitive;
1258
+ if (!primitive) return true;
1259
+ if ((enumNode.namedEnumValues?.length ?? enumNode.enumValues?.length ?? 0) <= 1) return true;
1260
+ if (scalarPrimitives.has(primitive)) return false;
1261
+ if ((primitive === "integer" || primitive === "number") && (scalarPrimitives.has("integer") || scalarPrimitives.has("number"))) return false;
1262
+ return true;
1263
+ });
1264
+ }
1265
+ function setEnumName(propNode, parentName, propName, enumSuffix) {
1266
+ const enumNode = narrowSchema(propNode, "enum");
1267
+ if (enumNode?.primitive === "boolean") return {
1268
+ ...propNode,
1269
+ name: void 0
1270
+ };
1271
+ if (enumNode) return {
1272
+ ...propNode,
1273
+ name: enumPropName(parentName, propName, enumSuffix)
1274
+ };
1275
+ return propNode;
1276
+ }
1277
+ /**
1278
+ * Walks a schema tree and resolves `ref`/`enum` names through callbacks.
1279
+ */
1280
+ function resolveNames({ node, nameMapping, resolveName, resolveEnumName }) {
1281
+ return transform(node, { schema(schemaNode) {
1282
+ const schemaRef = narrowSchema(schemaNode, "ref");
1283
+ if (schemaRef && (schemaRef.ref || schemaRef.name)) {
1284
+ const rawRef = schemaRef.ref ?? schemaRef.name;
1285
+ const resolved = resolveName(nameMapping.get(rawRef) ?? rawRef);
1286
+ if (resolved) return {
1287
+ ...schemaNode,
1288
+ name: resolved
1289
+ };
1290
+ }
1291
+ const schemaEnum = narrowSchema(schemaNode, "enum");
1292
+ if (schemaEnum?.name) {
1293
+ const resolved = (resolveEnumName ?? resolveName)(schemaEnum.name);
1294
+ if (resolved) return {
1295
+ ...schemaNode,
1296
+ name: resolved
1297
+ };
1298
+ }
1299
+ } });
1300
+ }
1301
+ //#endregion
1153
1302
  exports.SCALAR_PRIMITIVE_TYPES = SCALAR_PRIMITIVE_TYPES;
1154
- exports.applyDiscriminatorEnum = applyDiscriminatorEnum;
1155
- exports.applyParamsCasing = applyParamsCasing;
1156
1303
  exports.buildRefMap = buildRefMap;
1304
+ exports.caseParams = caseParams;
1305
+ exports.childName = childName;
1157
1306
  exports.collect = collect;
1307
+ exports.collectImports = collectImports;
1158
1308
  exports.composeTransformers = composeTransformers;
1309
+ exports.createDiscriminantNode = createDiscriminantNode;
1159
1310
  exports.createFunctionParameter = createFunctionParameter;
1160
1311
  exports.createFunctionParameters = createFunctionParameters;
1161
1312
  exports.createObjectBindingParameter = createObjectBindingParameter;
@@ -1165,8 +1316,11 @@ exports.createProperty = createProperty;
1165
1316
  exports.createResponse = createResponse;
1166
1317
  exports.createRoot = createRoot;
1167
1318
  exports.createSchema = createSchema;
1319
+ exports.defineFunctionPrinter = defineFunctionPrinter;
1168
1320
  exports.definePrinter = definePrinter;
1321
+ exports.enumPropName = enumPropName;
1169
1322
  exports.extractRefName = extractRefName;
1323
+ exports.findDiscriminator = findDiscriminator;
1170
1324
  exports.functionPrinter = functionPrinter;
1171
1325
  exports.httpMethods = httpMethods;
1172
1326
  exports.isFunctionParameterNode = isFunctionParameterNode;
@@ -1174,20 +1328,23 @@ exports.isFunctionParametersNode = isFunctionParametersNode;
1174
1328
  exports.isObjectBindingParameterNode = isObjectBindingParameterNode;
1175
1329
  exports.isOperationNode = isOperationNode;
1176
1330
  exports.isParameterNode = isParameterNode;
1177
- exports.isPlainStringType = isPlainStringType;
1178
1331
  exports.isPropertyNode = isPropertyNode;
1179
1332
  exports.isResponseNode = isResponseNode;
1180
1333
  exports.isRootNode = isRootNode;
1181
1334
  exports.isSchemaNode = isSchemaNode;
1335
+ exports.isStringType = isStringType;
1182
1336
  exports.mediaTypes = mediaTypes;
1183
- exports.mergeAdjacentAnonymousObjects = mergeAdjacentAnonymousObjects;
1337
+ exports.mergeAdjacentObjects = mergeAdjacentObjects;
1184
1338
  exports.narrowSchema = narrowSchema;
1185
1339
  exports.nodeKinds = nodeKinds;
1186
1340
  exports.refMapToObject = refMapToObject;
1341
+ exports.resolveNames = resolveNames;
1187
1342
  exports.resolveRef = resolveRef;
1188
1343
  exports.schemaTypes = schemaTypes;
1189
- exports.simplifyUnionMembers = simplifyUnionMembers;
1190
- exports.syncPropertySchema = syncPropertySchema;
1344
+ exports.setDiscriminatorEnum = setDiscriminatorEnum;
1345
+ exports.setEnumName = setEnumName;
1346
+ exports.simplifyUnion = simplifyUnion;
1347
+ exports.syncOptionality = syncOptionality;
1191
1348
  exports.transform = transform;
1192
1349
  exports.walk = walk;
1193
1350