@kubb/ast 5.0.0-alpha.2 → 5.0.0-alpha.20

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
@@ -12,8 +12,22 @@ const nodeKinds = {
12
12
  schema: "Schema",
13
13
  property: "Property",
14
14
  parameter: "Parameter",
15
- response: "Response"
15
+ response: "Response",
16
+ functionParameter: "FunctionParameter",
17
+ objectBindingParameter: "ObjectBindingParameter",
18
+ functionParameters: "FunctionParameters"
16
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
+ */
17
31
  const schemaTypes = {
18
32
  string: "string",
19
33
  number: "number",
@@ -37,8 +51,19 @@ const schemaTypes = {
37
51
  uuid: "uuid",
38
52
  email: "email",
39
53
  url: "url",
40
- blob: "blob"
54
+ blob: "blob",
55
+ never: "never"
41
56
  };
57
+ /**
58
+ * Primitive scalar schema types used when simplifying union members.
59
+ */
60
+ const SCALAR_PRIMITIVE_TYPES = new Set([
61
+ "string",
62
+ "number",
63
+ "integer",
64
+ "bigint",
65
+ "boolean"
66
+ ]);
42
67
  const httpMethods = {
43
68
  get: "GET",
44
69
  post: "POST",
@@ -71,9 +96,241 @@ const mediaTypes = {
71
96
  videoMp4: "video/mp4"
72
97
  };
73
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
74
319
  //#region src/factory.ts
75
320
  /**
76
- * 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
+ * ```
77
334
  */
78
335
  function createRoot(overrides = {}) {
79
336
  return {
@@ -84,7 +341,27 @@ function createRoot(overrides = {}) {
84
341
  };
85
342
  }
86
343
  /**
87
- * 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
+ * ```
88
365
  */
89
366
  function createOperation(props) {
90
367
  return {
@@ -107,27 +384,85 @@ function createSchema(props) {
107
384
  };
108
385
  }
109
386
  /**
110
- * Creates a `PropertyNode`. `required` defaults to `false`.
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
+ * ```
111
410
  */
112
411
  function createProperty(props) {
412
+ const required = props.required ?? false;
113
413
  return {
114
- required: false,
115
414
  ...props,
116
- kind: "Property"
415
+ kind: "Property",
416
+ required,
417
+ schema: syncOptionality(required, props.schema)
117
418
  };
118
419
  }
119
420
  /**
120
- * Creates a `ParameterNode`. `required` defaults to `false`.
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
+ * ```
121
445
  */
122
446
  function createParameter(props) {
447
+ const required = props.required ?? false;
123
448
  return {
124
- required: false,
125
449
  ...props,
126
- kind: "Parameter"
450
+ kind: "Parameter",
451
+ required,
452
+ schema: syncOptionality(required, props.schema)
127
453
  };
128
454
  }
129
455
  /**
130
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
+ * ```
131
466
  */
132
467
  function createResponse(props) {
133
468
  return {
@@ -135,99 +470,329 @@ function createResponse(props) {
135
470
  kind: "Response"
136
471
  };
137
472
  }
138
- //#endregion
139
- //#region src/guards.ts
140
473
  /**
141
- * Narrows a `SchemaNode` to the specific variant matching `type`.
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
+ * ```
142
481
  */
143
- function narrowSchema(node, type) {
144
- return node?.type === type ? node : void 0;
145
- }
146
- function isKind(kind) {
147
- return (node) => node.kind === kind;
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
+ });
148
496
  }
149
497
  /**
150
- * Type guard for `RootNode`.
151
- */
152
- const isRootNode = isKind("Root");
153
- /**
154
- * Type guard for `OperationNode`.
498
+ * Creates a `FunctionParameterNode`.
499
+ *
500
+ * `optional` defaults to `false`.
501
+ *
502
+ * @example Required typed param
503
+ * ```ts
504
+ * createFunctionParameter({ name: 'petId', type: 'string' })
505
+ * // → petId: string
506
+ * ```
507
+ *
508
+ * @example Optional param
509
+ * ```ts
510
+ * createFunctionParameter({ name: 'params', type: 'QueryParams', optional: true })
511
+ * // → params?: QueryParams
512
+ * ```
513
+ *
514
+ * @example Param with default (implicitly optional; cannot combine with `optional: true`)
515
+ * ```ts
516
+ * createFunctionParameter({ name: 'config', type: 'RequestConfig', default: '{}' })
517
+ * // → config: RequestConfig = {}
518
+ * ```
155
519
  */
156
- const isOperationNode = isKind("Operation");
520
+ function createFunctionParameter(props) {
521
+ return {
522
+ optional: false,
523
+ ...props,
524
+ kind: "FunctionParameter"
525
+ };
526
+ }
157
527
  /**
158
- * Type guard for `SchemaNode`.
528
+ * Creates an `ObjectBindingParameterNode` for object-destructured parameter groups.
529
+ *
530
+ * @example Destructured object param
531
+ * ```ts
532
+ * createObjectBindingParameter({
533
+ * properties: [
534
+ * createFunctionParameter({ name: 'id', type: 'string', optional: false }),
535
+ * createFunctionParameter({ name: 'name', type: 'string', optional: true }),
536
+ * ],
537
+ * default: '{}',
538
+ * })
539
+ * // declaration → { id, name? }: { id: string; name?: string } = {}
540
+ * // call → { id, name }
541
+ * ```
542
+ *
543
+ * @example Inline mode — children emitted as individual top-level parameters
544
+ * ```ts
545
+ * createObjectBindingParameter({
546
+ * properties: [createFunctionParameter({ name: 'petId', type: 'string', optional: false })],
547
+ * inline: true,
548
+ * })
549
+ * // declaration → petId: string
550
+ * // call → petId
551
+ * ```
159
552
  */
160
- const isSchemaNode = isKind("Schema");
553
+ function createObjectBindingParameter(props) {
554
+ return {
555
+ ...props,
556
+ kind: "ObjectBindingParameter"
557
+ };
558
+ }
161
559
  /**
162
- * Type guard for `PropertyNode`.
560
+ * Creates a `FunctionParametersNode` from an ordered list of parameters.
561
+ *
562
+ * @example
563
+ * ```ts
564
+ * createFunctionParameters({
565
+ * params: [
566
+ * createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
567
+ * createFunctionParameter({ name: 'config', type: 'RequestConfig', optional: false, default: '{}' }),
568
+ * ],
569
+ * })
570
+ * ```
571
+ *
572
+ * @example
573
+ * ```ts
574
+ * const empty = createFunctionParameters()
575
+ * // { kind: 'FunctionParameters', params: [] }
576
+ * ```
163
577
  */
164
- const isPropertyNode = isKind("Property");
578
+ function createFunctionParameters(props = {}) {
579
+ return {
580
+ params: [],
581
+ ...props,
582
+ kind: "FunctionParameters"
583
+ };
584
+ }
585
+ //#endregion
586
+ //#region src/printers/printer.ts
165
587
  /**
166
- * Type guard for `ParameterNode`.
588
+ * Creates a schema printer factory.
589
+ *
590
+ * This function wraps a builder and makes options optional at call sites.
591
+ *
592
+ * The builder receives resolved options and returns:
593
+ * - `name` — a unique identifier for the printer
594
+ * - `options` — options stored on the returned printer instance
595
+ * - `nodes` — a map of `SchemaType` → handler functions that convert a `SchemaNode` to `TOutput`
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
599
+ *
600
+ * When no `print` override is provided, `printer.print` is the node-level dispatcher directly.
601
+ *
602
+ * @example Basic usage — Zod schema printer
603
+ * ```ts
604
+ * type ZodPrinter = PrinterFactoryOptions<'zod', { strict?: boolean }, string>
605
+ *
606
+ * export const zodPrinter = definePrinter<ZodPrinter>((options) => ({
607
+ * name: 'zod',
608
+ * options: { strict: options.strict ?? true },
609
+ * nodes: {
610
+ * string: () => 'z.string()',
611
+ * object(node) {
612
+ * const props = node.properties.map(p => `${p.name}: ${this.print(p.schema)}`).join(', ')
613
+ * return `z.object({ ${props} })`
614
+ * },
615
+ * },
616
+ * }))
617
+ * ```
167
618
  */
168
- const isParameterNode = isKind("Parameter");
619
+ function definePrinter(build) {
620
+ return createPrinterFactory((node) => node.type)(build);
621
+ }
169
622
  /**
170
- * Type guard for `ResponseNode`.
623
+ * Generic printer-factory function used by `definePrinter` and `defineFunctionPrinter`.
624
+ **
625
+ * @example
626
+ * ```ts
627
+ * export const defineFunctionPrinter = createPrinterFactory<FunctionNode, FunctionNodeType, FunctionNodeByType>(
628
+ * (node) => kindToHandlerKey[node.kind],
629
+ * )
630
+ * ```
171
631
  */
172
- const isResponseNode = isKind("Response");
632
+ function createPrinterFactory(getKey) {
633
+ return function(build) {
634
+ return (options) => {
635
+ const { name, options: resolvedOptions, nodes, print: printOverride } = build(options ?? {});
636
+ const context = {
637
+ options: resolvedOptions,
638
+ print: (node) => {
639
+ const key = getKey(node);
640
+ if (key === void 0) return null;
641
+ const handler = nodes[key];
642
+ if (!handler) return null;
643
+ return handler.call(context, node);
644
+ }
645
+ };
646
+ return {
647
+ name,
648
+ options: resolvedOptions,
649
+ print: printOverride ? printOverride.bind(context) : context.print
650
+ };
651
+ };
652
+ };
653
+ }
173
654
  //#endregion
174
- //#region src/printer.ts
175
- /**
176
- * Creates a named printer factory. Mirrors the `definePlugin` / `defineAdapter` pattern
177
- * from `@kubb/core` — wraps a builder to make options optional and separates raw options
178
- * from resolved options.
179
- *
180
- * @example
181
- * ```ts
182
- * type ZodPrinter = PrinterFactoryOptions<'zod', { strict?: boolean }, { strict: boolean }, string>
183
- *
184
- * export const zodPrinter = definePrinter<ZodPrinter>((options) => {
185
- * const { strict = true } = options
186
- * return {
187
- * name: 'zod',
188
- * options: { strict },
189
- * nodes: {
190
- * string(node) {
191
- * return `z.string()`
192
- * },
193
- * object(node) {
194
- * const props = node.properties
195
- * ?.map(p => `${p.name}: ${this.print(p)}`)
196
- * .join(', ') ?? ''
197
- * return `z.object({ ${props} })`
198
- * },
655
+ //#region src/printers/functionPrinter.ts
656
+ const kindToHandlerKey = {
657
+ FunctionParameter: "functionParameter",
658
+ ObjectBindingParameter: "objectBindingParameter",
659
+ FunctionParameters: "functionParameters"
660
+ };
661
+ /**
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).
666
+ *
667
+ * @example
668
+ * ```ts
669
+ * type MyPrinter = PrinterFactoryOptions<'my', { mode: 'declaration' | 'call' }, string>
670
+ *
671
+ * export const myPrinter = defineFunctionPrinter<MyPrinter>((options) => ({
672
+ * name: 'my',
673
+ * options,
674
+ * nodes: {
675
+ * functionParameter(node) {
676
+ * return options.mode === 'declaration' && node.type ? `${node.name}: ${node.type}` : node.name
677
+ * },
678
+ * objectBindingParameter(node) {
679
+ * const inner = node.properties.map(p => this.print(p)).filter(Boolean).join(', ')
680
+ * return `{ ${inner} }`
681
+ * },
682
+ * functionParameters(node) {
683
+ * return node.params.map(p => this.print(p)).filter(Boolean).join(', ')
199
684
  * },
200
- * }
685
+ * },
686
+ * }))
687
+ * ```
688
+ */
689
+ const defineFunctionPrinter = createPrinterFactory((node) => kindToHandlerKey[node.kind]);
690
+ function rank(param) {
691
+ if (param.kind === "ObjectBindingParameter") {
692
+ if (param.default) return 2;
693
+ return param.optional ?? param.properties.every((p) => p.optional || p.default !== void 0) ? 1 : 0;
694
+ }
695
+ if (param.rest) return 3;
696
+ if (param.default) return 2;
697
+ return param.optional ? 1 : 0;
698
+ }
699
+ function sortParams(params) {
700
+ return [...params].sort((a, b) => rank(a) - rank(b));
701
+ }
702
+ function sortChildParams(params) {
703
+ return [...params].sort((a, b) => rank(a) - rank(b));
704
+ }
705
+ /**
706
+ * Default function-signature printer.
707
+ * Covers the four standard output modes used across Kubb plugins.
708
+ *
709
+ * @example
710
+ * ```ts
711
+ * const printer = functionPrinter({ mode: 'declaration' })
712
+ *
713
+ * const sig = createFunctionParameters({
714
+ * params: [
715
+ * createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
716
+ * createFunctionParameter({ name: 'config', type: 'Config', optional: false, default: '{}' }),
717
+ * ],
201
718
  * })
202
719
  *
203
- * const printer = zodPrinter({ strict: false })
204
- * printer.name // 'zod'
205
- * printer.options // { strict: false }
206
- * printer.print(node) // 'z.string()'
720
+ * printer.print(sig) // "petId: string, config: Config = {}"
207
721
  * ```
208
722
  */
209
- function definePrinter(build) {
210
- return (options) => {
211
- const { name, options: resolvedOptions, nodes } = build(options ?? {});
212
- const context = {
213
- options: resolvedOptions,
214
- print: (node) => {
215
- const handler = nodes[node.type];
216
- return handler ? handler.call(context, node) : void 0;
723
+ const functionPrinter = defineFunctionPrinter((options) => ({
724
+ name: "functionParameters",
725
+ options,
726
+ nodes: {
727
+ functionParameter(node) {
728
+ const { mode, transformName, transformType } = this.options;
729
+ const name = transformName ? transformName(node.name) : node.name;
730
+ const type = node.type && transformType ? transformType(node.type) : node.type;
731
+ if (mode === "keys" || mode === "values") return node.rest ? `...${name}` : name;
732
+ if (mode === "call") return node.rest ? `...${name}` : name;
733
+ if (node.rest) return type ? `...${name}: ${type}` : `...${name}`;
734
+ if (type) {
735
+ if (node.optional) return `${name}?: ${type}`;
736
+ return node.default ? `${name}: ${type} = ${node.default}` : `${name}: ${type}`;
217
737
  }
218
- };
219
- return {
220
- name,
221
- options: resolvedOptions,
222
- print: context.print,
223
- for: (nodes) => nodes.map(context.print)
224
- };
225
- };
226
- }
738
+ return node.default ? `${name} = ${node.default}` : name;
739
+ },
740
+ objectBindingParameter(node) {
741
+ const { mode, transformName, transformType } = this.options;
742
+ const sorted = sortChildParams(node.properties);
743
+ const isOptional = node.optional ?? sorted.every((p) => p.optional || p.default !== void 0);
744
+ if (node.inline) return sorted.map((p) => this.print(p)).filter(Boolean).join(", ");
745
+ if (mode === "keys" || mode === "values") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
746
+ if (mode === "call") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
747
+ const names = sorted.map((p) => {
748
+ return transformName ? transformName(p.name) : p.name;
749
+ });
750
+ const nameStr = names.length ? `{ ${names.join(", ")} }` : void 0;
751
+ if (!nameStr) return null;
752
+ let typeAnnotation = node.type;
753
+ if (!typeAnnotation) {
754
+ const typeParts = sorted.filter((p) => p.type).map((p) => {
755
+ const t = transformType && p.type ? transformType(p.type) : p.type;
756
+ return p.optional || p.default !== void 0 ? `${p.name}?: ${t}` : `${p.name}: ${t}`;
757
+ });
758
+ typeAnnotation = typeParts.length ? `{ ${typeParts.join("; ")} }` : void 0;
759
+ }
760
+ if (typeAnnotation) {
761
+ if (isOptional) return `${nameStr}: ${typeAnnotation} = ${node.default ?? "{}"}`;
762
+ return node.default ? `${nameStr}: ${typeAnnotation} = ${node.default}` : `${nameStr}: ${typeAnnotation}`;
763
+ }
764
+ return node.default ? `${nameStr} = ${node.default}` : nameStr;
765
+ },
766
+ functionParameters(node) {
767
+ return sortParams(node.params).map((p) => this.print(p)).filter(Boolean).join(", ");
768
+ }
769
+ }
770
+ }));
227
771
  //#endregion
228
772
  //#region src/refs.ts
229
773
  /**
230
- * Indexes named schemas from `root.schemas` by name. Unnamed schemas are skipped.
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
+ * ```
782
+ */
783
+ function extractRefName(ref) {
784
+ return ref.split("/").at(-1) ?? ref;
785
+ }
786
+ /**
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
+ * ```
231
796
  */
232
797
  function buildRefMap(root) {
233
798
  const map = /* @__PURE__ */ new Map();
@@ -235,19 +800,45 @@ function buildRefMap(root) {
235
800
  return map;
236
801
  }
237
802
  /**
238
- * 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
+ * ```
239
809
  */
240
810
  function resolveRef(refMap, ref) {
241
811
  return refMap.get(ref);
242
812
  }
243
813
  /**
244
- * 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
+ * ```
245
820
  */
246
821
  function refMapToObject(refMap) {
247
822
  return Object.fromEntries(refMap);
248
823
  }
249
824
  //#endregion
250
825
  //#region src/visitor.ts
826
+ /**
827
+ * Creates a small async concurrency limiter.
828
+ *
829
+ * At most `concurrency` tasks are in flight at once. Extra tasks are queued.
830
+ *
831
+ * @example
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
+ * ```
841
+ */
251
842
  function createLimit(concurrency) {
252
843
  let active = 0;
253
844
  const queue = [];
@@ -270,14 +861,24 @@ function createLimit(concurrency) {
270
861
  };
271
862
  }
272
863
  /**
273
- * Traversable children of `node`, respecting `recurse` for schema nodes.
864
+ * Returns the immediate traversable children of `node`.
865
+ *
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
+ * ```
274
875
  */
275
876
  function getChildren(node, recurse) {
276
877
  switch (node.kind) {
277
878
  case "Root": return [...node.schemas, ...node.operations];
278
879
  case "Operation": return [
279
880
  ...node.parameters,
280
- ...node.requestBody ? [node.requestBody] : [],
881
+ ...node.requestBody?.schema ? [node.requestBody.schema] : [],
281
882
  ...node.responses
282
883
  ];
283
884
  case "Schema": {
@@ -286,162 +887,464 @@ function getChildren(node, recurse) {
286
887
  if ("properties" in node && node.properties.length > 0) children.push(...node.properties);
287
888
  if ("items" in node && node.items) children.push(...node.items);
288
889
  if ("members" in node && node.members) children.push(...node.members);
890
+ if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
289
891
  return children;
290
892
  }
291
893
  case "Property": return [node.schema];
292
894
  case "Parameter": return [node.schema];
293
895
  case "Response": return node.schema ? [node.schema] : [];
896
+ case "FunctionParameter":
897
+ case "ObjectBindingParameter":
898
+ case "FunctionParameters": return [];
294
899
  }
295
900
  }
296
901
  /**
297
902
  * Depth-first traversal for side effects. Visitor return values are ignored.
298
- * 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
+ * ```
299
920
  */
300
- async function walk(node, visitor, options = {}) {
301
- return _walk(node, visitor, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30));
921
+ async function walk(node, options) {
922
+ return _walk(node, options, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30), void 0);
302
923
  }
303
- async function _walk(node, visitor, recurse, limit) {
924
+ async function _walk(node, visitor, recurse, limit, parent) {
304
925
  switch (node.kind) {
305
926
  case "Root":
306
- await limit(() => visitor.root?.(node));
927
+ await limit(() => visitor.root?.(node, { parent }));
307
928
  break;
308
929
  case "Operation":
309
- await limit(() => visitor.operation?.(node));
930
+ await limit(() => visitor.operation?.(node, { parent }));
310
931
  break;
311
932
  case "Schema":
312
- await limit(() => visitor.schema?.(node));
933
+ await limit(() => visitor.schema?.(node, { parent }));
313
934
  break;
314
935
  case "Property":
315
- await limit(() => visitor.property?.(node));
936
+ await limit(() => visitor.property?.(node, { parent }));
316
937
  break;
317
938
  case "Parameter":
318
- await limit(() => visitor.parameter?.(node));
939
+ await limit(() => visitor.parameter?.(node, { parent }));
319
940
  break;
320
941
  case "Response":
321
- await limit(() => visitor.response?.(node));
942
+ await limit(() => visitor.response?.(node, { parent }));
322
943
  break;
944
+ case "FunctionParameter":
945
+ case "ObjectBindingParameter":
946
+ case "FunctionParameters": break;
323
947
  }
324
948
  const children = getChildren(node, recurse);
325
- await Promise.all(children.map((child) => _walk(child, visitor, recurse, limit)));
949
+ await Promise.all(children.map((child) => _walk(child, visitor, recurse, limit, node)));
326
950
  }
327
- function transform(node, visitor, options = {}) {
328
- const recurse = (options.depth ?? visitorDepths.deep) === visitorDepths.deep;
951
+ function transform(node, options) {
952
+ const { depth, parent, ...visitor } = options;
953
+ const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
329
954
  switch (node.kind) {
330
955
  case "Root": {
331
956
  let root = node;
332
- const replaced = visitor.root?.(root);
957
+ const replaced = visitor.root?.(root, { parent });
333
958
  if (replaced) root = replaced;
334
959
  return {
335
960
  ...root,
336
- schemas: root.schemas.map((s) => transform(s, visitor, options)),
337
- operations: root.operations.map((op) => transform(op, visitor, options))
961
+ schemas: root.schemas.map((s) => transform(s, {
962
+ ...options,
963
+ parent: root
964
+ })),
965
+ operations: root.operations.map((op) => transform(op, {
966
+ ...options,
967
+ parent: root
968
+ }))
338
969
  };
339
970
  }
340
971
  case "Operation": {
341
972
  let op = node;
342
- const replaced = visitor.operation?.(op);
973
+ const replaced = visitor.operation?.(op, { parent });
343
974
  if (replaced) op = replaced;
344
975
  return {
345
976
  ...op,
346
- parameters: op.parameters.map((p) => transform(p, visitor, options)),
347
- requestBody: op.requestBody ? transform(op.requestBody, visitor, options) : void 0,
348
- responses: op.responses.map((r) => transform(r, visitor, options))
977
+ parameters: op.parameters.map((p) => transform(p, {
978
+ ...options,
979
+ parent: op
980
+ })),
981
+ requestBody: op.requestBody ? {
982
+ ...op.requestBody,
983
+ schema: op.requestBody.schema ? transform(op.requestBody.schema, {
984
+ ...options,
985
+ parent: op
986
+ }) : void 0
987
+ } : void 0,
988
+ responses: op.responses.map((r) => transform(r, {
989
+ ...options,
990
+ parent: op
991
+ }))
349
992
  };
350
993
  }
351
994
  case "Schema": {
352
995
  let schema = node;
353
- const replaced = visitor.schema?.(schema);
996
+ const replaced = visitor.schema?.(schema, { parent });
354
997
  if (replaced) schema = replaced;
998
+ const childOptions = {
999
+ ...options,
1000
+ parent: schema
1001
+ };
355
1002
  return {
356
1003
  ...schema,
357
- ..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p, visitor, options)) } : {},
358
- ..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i, visitor, options)) } : {},
359
- ..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, visitor, options)) } : {}
1004
+ ..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p, childOptions)) } : {},
1005
+ ..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i, childOptions)) } : {},
1006
+ ..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, childOptions)) } : {},
1007
+ ..."additionalProperties" in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true ? { additionalProperties: transform(schema.additionalProperties, childOptions) } : {}
360
1008
  };
361
1009
  }
362
1010
  case "Property": {
363
1011
  let prop = node;
364
- const replaced = visitor.property?.(prop);
1012
+ const replaced = visitor.property?.(prop, { parent });
365
1013
  if (replaced) prop = replaced;
366
- return {
1014
+ return createProperty({
367
1015
  ...prop,
368
- schema: transform(prop.schema, visitor, options)
369
- };
1016
+ schema: transform(prop.schema, {
1017
+ ...options,
1018
+ parent: prop
1019
+ })
1020
+ });
370
1021
  }
371
1022
  case "Parameter": {
372
1023
  let param = node;
373
- const replaced = visitor.parameter?.(param);
1024
+ const replaced = visitor.parameter?.(param, { parent });
374
1025
  if (replaced) param = replaced;
375
- return {
1026
+ return createParameter({
376
1027
  ...param,
377
- schema: transform(param.schema, visitor, options)
378
- };
1028
+ schema: transform(param.schema, {
1029
+ ...options,
1030
+ parent: param
1031
+ })
1032
+ });
379
1033
  }
380
1034
  case "Response": {
381
1035
  let response = node;
382
- const replaced = visitor.response?.(response);
1036
+ const replaced = visitor.response?.(response, { parent });
383
1037
  if (replaced) response = replaced;
384
1038
  return {
385
1039
  ...response,
386
- schema: response.schema ? transform(response.schema, visitor, options) : void 0
1040
+ schema: transform(response.schema, {
1041
+ ...options,
1042
+ parent: response
1043
+ })
387
1044
  };
388
1045
  }
1046
+ case "FunctionParameter":
1047
+ case "ObjectBindingParameter":
1048
+ case "FunctionParameters": return node;
389
1049
  }
390
1050
  }
391
1051
  /**
392
- * Depth-first synchronous reduction. Collects non-`undefined` visitor return values into an array.
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
+ * ```
393
1064
  */
394
- function collect(node, visitor, options = {}) {
395
- const recurse = (options.depth ?? visitorDepths.deep) === visitorDepths.deep;
1065
+ function composeTransformers(...visitors) {
1066
+ return {
1067
+ root(node, context) {
1068
+ return visitors.reduce((acc, v) => v.root?.(acc, context) ?? acc, node);
1069
+ },
1070
+ operation(node, context) {
1071
+ return visitors.reduce((acc, v) => v.operation?.(acc, context) ?? acc, node);
1072
+ },
1073
+ schema(node, context) {
1074
+ return visitors.reduce((acc, v) => v.schema?.(acc, context) ?? acc, node);
1075
+ },
1076
+ property(node, context) {
1077
+ return visitors.reduce((acc, v) => v.property?.(acc, context) ?? acc, node);
1078
+ },
1079
+ parameter(node, context) {
1080
+ return visitors.reduce((acc, v) => v.parameter?.(acc, context) ?? acc, node);
1081
+ },
1082
+ response(node, context) {
1083
+ return visitors.reduce((acc, v) => v.response?.(acc, context) ?? acc, node);
1084
+ }
1085
+ };
1086
+ }
1087
+ /**
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
+ * ```
1106
+ */
1107
+ function collect(node, options) {
1108
+ const { depth, parent, ...visitor } = options;
1109
+ const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
396
1110
  const results = [];
397
1111
  let v;
398
1112
  switch (node.kind) {
399
1113
  case "Root":
400
- v = visitor.root?.(node);
1114
+ v = visitor.root?.(node, { parent });
401
1115
  break;
402
1116
  case "Operation":
403
- v = visitor.operation?.(node);
1117
+ v = visitor.operation?.(node, { parent });
404
1118
  break;
405
1119
  case "Schema":
406
- v = visitor.schema?.(node);
1120
+ v = visitor.schema?.(node, { parent });
407
1121
  break;
408
1122
  case "Property":
409
- v = visitor.property?.(node);
1123
+ v = visitor.property?.(node, { parent });
410
1124
  break;
411
1125
  case "Parameter":
412
- v = visitor.parameter?.(node);
1126
+ v = visitor.parameter?.(node, { parent });
413
1127
  break;
414
1128
  case "Response":
415
- v = visitor.response?.(node);
1129
+ v = visitor.response?.(node, { parent });
416
1130
  break;
1131
+ case "FunctionParameter":
1132
+ case "ObjectBindingParameter":
1133
+ case "FunctionParameters": break;
417
1134
  }
418
1135
  if (v !== void 0) results.push(v);
419
- for (const child of getChildren(node, recurse)) for (const item of collect(child, visitor, options)) results.push(item);
1136
+ for (const child of getChildren(node, recurse)) for (const item of collect(child, {
1137
+ ...options,
1138
+ parent: node
1139
+ })) results.push(item);
420
1140
  return results;
421
1141
  }
422
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
1302
+ exports.SCALAR_PRIMITIVE_TYPES = SCALAR_PRIMITIVE_TYPES;
423
1303
  exports.buildRefMap = buildRefMap;
1304
+ exports.caseParams = caseParams;
1305
+ exports.childName = childName;
424
1306
  exports.collect = collect;
1307
+ exports.collectImports = collectImports;
1308
+ exports.composeTransformers = composeTransformers;
1309
+ exports.createDiscriminantNode = createDiscriminantNode;
1310
+ exports.createFunctionParameter = createFunctionParameter;
1311
+ exports.createFunctionParameters = createFunctionParameters;
1312
+ exports.createObjectBindingParameter = createObjectBindingParameter;
425
1313
  exports.createOperation = createOperation;
426
1314
  exports.createParameter = createParameter;
427
1315
  exports.createProperty = createProperty;
428
1316
  exports.createResponse = createResponse;
429
1317
  exports.createRoot = createRoot;
430
1318
  exports.createSchema = createSchema;
1319
+ exports.defineFunctionPrinter = defineFunctionPrinter;
431
1320
  exports.definePrinter = definePrinter;
1321
+ exports.enumPropName = enumPropName;
1322
+ exports.extractRefName = extractRefName;
1323
+ exports.findDiscriminator = findDiscriminator;
1324
+ exports.functionPrinter = functionPrinter;
432
1325
  exports.httpMethods = httpMethods;
1326
+ exports.isFunctionParameterNode = isFunctionParameterNode;
1327
+ exports.isFunctionParametersNode = isFunctionParametersNode;
1328
+ exports.isObjectBindingParameterNode = isObjectBindingParameterNode;
433
1329
  exports.isOperationNode = isOperationNode;
434
1330
  exports.isParameterNode = isParameterNode;
435
1331
  exports.isPropertyNode = isPropertyNode;
436
1332
  exports.isResponseNode = isResponseNode;
437
1333
  exports.isRootNode = isRootNode;
438
1334
  exports.isSchemaNode = isSchemaNode;
1335
+ exports.isStringType = isStringType;
439
1336
  exports.mediaTypes = mediaTypes;
1337
+ exports.mergeAdjacentObjects = mergeAdjacentObjects;
440
1338
  exports.narrowSchema = narrowSchema;
441
1339
  exports.nodeKinds = nodeKinds;
442
1340
  exports.refMapToObject = refMapToObject;
1341
+ exports.resolveNames = resolveNames;
443
1342
  exports.resolveRef = resolveRef;
444
1343
  exports.schemaTypes = schemaTypes;
1344
+ exports.setDiscriminatorEnum = setDiscriminatorEnum;
1345
+ exports.setEnumName = setEnumName;
1346
+ exports.simplifyUnion = simplifyUnion;
1347
+ exports.syncOptionality = syncOptionality;
445
1348
  exports.transform = transform;
446
1349
  exports.walk = walk;
447
1350