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