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