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

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,330 @@ 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, use `this.transform(node)` to dispatch to the `nodes` map
596
+ * - This keeps recursion safe and avoids self-calls
597
+ *
598
+ * When no `print` override is provided, `printer.print` falls back to `printer.transform` (the node-level dispatcher).
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.transform(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
+ transform: (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
+ transform: context.transform,
648
+ print: printOverride ? printOverride.bind(context) : context.transform
649
+ };
650
+ };
651
+ };
652
+ }
171
653
  //#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
- * },
654
+ //#region src/printers/functionPrinter.ts
655
+ const kindToHandlerKey = {
656
+ FunctionParameter: "functionParameter",
657
+ ObjectBindingParameter: "objectBindingParameter",
658
+ FunctionParameters: "functionParameters"
659
+ };
660
+ /**
661
+ * Creates a function-parameter printer factory.
662
+ *
663
+ * This wrapper uses `createPrinterFactory` and dispatches handlers by `node.kind`
664
+ * (for function nodes) rather than by `node.type` (for schema nodes).
665
+ *
666
+ * @example
667
+ * ```ts
668
+ * type MyPrinter = PrinterFactoryOptions<'my', { mode: 'declaration' | 'call' }, string>
669
+ *
670
+ * export const myPrinter = defineFunctionPrinter<MyPrinter>((options) => ({
671
+ * name: 'my',
672
+ * options,
673
+ * nodes: {
674
+ * functionParameter(node) {
675
+ * return options.mode === 'declaration' && node.type ? `${node.name}: ${node.type}` : node.name
676
+ * },
677
+ * objectBindingParameter(node) {
678
+ * const inner = node.properties.map(p => this.transform(p)).filter(Boolean).join(', ')
679
+ * return `{ ${inner} }`
680
+ * },
681
+ * functionParameters(node) {
682
+ * return node.params.map(p => this.transform(p)).filter(Boolean).join(', ')
197
683
  * },
198
- * }
684
+ * },
685
+ * }))
686
+ * ```
687
+ */
688
+ const defineFunctionPrinter = createPrinterFactory((node) => kindToHandlerKey[node.kind]);
689
+ function rank(param) {
690
+ if (param.kind === "ObjectBindingParameter") {
691
+ if (param.default) return 2;
692
+ return param.optional ?? param.properties.every((p) => p.optional || p.default !== void 0) ? 1 : 0;
693
+ }
694
+ if (param.rest) return 3;
695
+ if (param.default) return 2;
696
+ return param.optional ? 1 : 0;
697
+ }
698
+ function sortParams(params) {
699
+ return [...params].sort((a, b) => rank(a) - rank(b));
700
+ }
701
+ function sortChildParams(params) {
702
+ return [...params].sort((a, b) => rank(a) - rank(b));
703
+ }
704
+ /**
705
+ * Default function-signature printer.
706
+ * Covers the four standard output modes used across Kubb plugins.
707
+ *
708
+ * @example
709
+ * ```ts
710
+ * const printer = functionPrinter({ mode: 'declaration' })
711
+ *
712
+ * const sig = createFunctionParameters({
713
+ * params: [
714
+ * createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
715
+ * createFunctionParameter({ name: 'config', type: 'Config', optional: false, default: '{}' }),
716
+ * ],
199
717
  * })
200
718
  *
201
- * const printer = zodPrinter({ strict: false })
202
- * printer.name // 'zod'
203
- * printer.options // { strict: false }
204
- * printer.print(node) // 'z.string()'
719
+ * printer.print(sig) // "petId: string, config: Config = {}"
205
720
  * ```
206
721
  */
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;
722
+ const functionPrinter = defineFunctionPrinter((options) => ({
723
+ name: "functionParameters",
724
+ options,
725
+ nodes: {
726
+ functionParameter(node) {
727
+ const { mode, transformName, transformType } = this.options;
728
+ const name = transformName ? transformName(node.name) : node.name;
729
+ const type = node.type && transformType ? transformType(node.type) : node.type;
730
+ if (mode === "keys" || mode === "values") return node.rest ? `...${name}` : name;
731
+ if (mode === "call") return node.rest ? `...${name}` : name;
732
+ if (node.rest) return type ? `...${name}: ${type}` : `...${name}`;
733
+ if (type) {
734
+ if (node.optional) return `${name}?: ${type}`;
735
+ return node.default ? `${name}: ${type} = ${node.default}` : `${name}: ${type}`;
215
736
  }
216
- };
217
- return {
218
- name,
219
- options: resolvedOptions,
220
- print: context.print,
221
- for: (nodes) => nodes.map(context.print)
222
- };
223
- };
224
- }
737
+ return node.default ? `${name} = ${node.default}` : name;
738
+ },
739
+ objectBindingParameter(node) {
740
+ const { mode, transformName, transformType } = this.options;
741
+ const sorted = sortChildParams(node.properties);
742
+ const isOptional = node.optional ?? sorted.every((p) => p.optional || p.default !== void 0);
743
+ if (node.inline) return sorted.map((p) => this.transform(p)).filter(Boolean).join(", ");
744
+ if (mode === "keys" || mode === "values") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
745
+ if (mode === "call") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
746
+ const names = sorted.map((p) => {
747
+ return transformName ? transformName(p.name) : p.name;
748
+ });
749
+ const nameStr = names.length ? `{ ${names.join(", ")} }` : void 0;
750
+ if (!nameStr) return null;
751
+ let typeAnnotation = node.type;
752
+ if (!typeAnnotation) {
753
+ const typeParts = sorted.filter((p) => p.type).map((p) => {
754
+ const t = transformType && p.type ? transformType(p.type) : p.type;
755
+ return p.optional || p.default !== void 0 ? `${p.name}?: ${t}` : `${p.name}: ${t}`;
756
+ });
757
+ typeAnnotation = typeParts.length ? `{ ${typeParts.join("; ")} }` : void 0;
758
+ }
759
+ if (typeAnnotation) {
760
+ if (isOptional) return `${nameStr}: ${typeAnnotation} = ${node.default ?? "{}"}`;
761
+ return node.default ? `${nameStr}: ${typeAnnotation} = ${node.default}` : `${nameStr}: ${typeAnnotation}`;
762
+ }
763
+ return node.default ? `${nameStr} = ${node.default}` : nameStr;
764
+ },
765
+ functionParameters(node) {
766
+ return sortParams(node.params).map((p) => this.transform(p)).filter(Boolean).join(", ");
767
+ }
768
+ }
769
+ }));
225
770
  //#endregion
226
771
  //#region src/refs.ts
227
772
  /**
228
- * Indexes named schemas from `root.schemas` by name. Unnamed schemas are skipped.
773
+ * Returns the last path segment of a reference string.
774
+ *
775
+ * Example: `#/components/schemas/Pet` becomes `Pet`.
776
+ *
777
+ * @example
778
+ * ```ts
779
+ * extractRefName('#/components/schemas/Pet') // 'Pet'
780
+ * ```
781
+ */
782
+ function extractRefName(ref) {
783
+ return ref.split("/").at(-1) ?? ref;
784
+ }
785
+ /**
786
+ * Builds a `RefMap` from `root.schemas` using each schema's `name`.
787
+ *
788
+ * Unnamed schemas are skipped.
789
+ *
790
+ * @example
791
+ * ```ts
792
+ * const refMap = buildRefMap(root)
793
+ * const pet = refMap.get('Pet')
794
+ * ```
229
795
  */
230
796
  function buildRefMap(root) {
231
797
  const map = /* @__PURE__ */ new Map();
@@ -233,19 +799,45 @@ function buildRefMap(root) {
233
799
  return map;
234
800
  }
235
801
  /**
236
- * Looks up a schema by name. Prefer over `RefMap.get()` to keep the resolution strategy swappable.
802
+ * Resolves a schema by name from a `RefMap`.
803
+ *
804
+ * @example
805
+ * ```ts
806
+ * const petSchema = resolveRef(refMap, 'Pet')
807
+ * ```
237
808
  */
238
809
  function resolveRef(refMap, ref) {
239
810
  return refMap.get(ref);
240
811
  }
241
812
  /**
242
- * Converts a `RefMap` to a plain object.
813
+ * Converts a `RefMap` into a plain object.
814
+ *
815
+ * @example
816
+ * ```ts
817
+ * const refsObject = refMapToObject(refMap)
818
+ * ```
243
819
  */
244
820
  function refMapToObject(refMap) {
245
821
  return Object.fromEntries(refMap);
246
822
  }
247
823
  //#endregion
248
824
  //#region src/visitor.ts
825
+ /**
826
+ * Creates a small async concurrency limiter.
827
+ *
828
+ * At most `concurrency` tasks are in flight at once. Extra tasks are queued.
829
+ *
830
+ * @example
831
+ * ```ts
832
+ * const limit = createLimit(2)
833
+ * await Promise.all([
834
+ * limit(() => taskA()),
835
+ * limit(() => taskB()),
836
+ * limit(() => taskC()),
837
+ * ])
838
+ * // only 2 tasks run at the same time
839
+ * ```
840
+ */
249
841
  function createLimit(concurrency) {
250
842
  let active = 0;
251
843
  const queue = [];
@@ -268,14 +860,24 @@ function createLimit(concurrency) {
268
860
  };
269
861
  }
270
862
  /**
271
- * Traversable children of `node`, respecting `recurse` for schema nodes.
863
+ * Returns the immediate traversable children of `node`.
864
+ *
865
+ * For `Schema` nodes, children (`properties`, `items`, `members`, and non-boolean
866
+ * `additionalProperties`) are only included
867
+ * when `recurse` is `true`; shallow mode skips them.
868
+ *
869
+ * @example
870
+ * ```ts
871
+ * const children = getChildren(operationNode, true)
872
+ * // returns parameters, requestBody schema (if present), and responses
873
+ * ```
272
874
  */
273
875
  function getChildren(node, recurse) {
274
876
  switch (node.kind) {
275
877
  case "Root": return [...node.schemas, ...node.operations];
276
878
  case "Operation": return [
277
879
  ...node.parameters,
278
- ...node.requestBody ? [node.requestBody] : [],
880
+ ...node.requestBody?.schema ? [node.requestBody.schema] : [],
279
881
  ...node.responses
280
882
  ];
281
883
  case "Schema": {
@@ -284,140 +886,418 @@ function getChildren(node, recurse) {
284
886
  if ("properties" in node && node.properties.length > 0) children.push(...node.properties);
285
887
  if ("items" in node && node.items) children.push(...node.items);
286
888
  if ("members" in node && node.members) children.push(...node.members);
889
+ if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
287
890
  return children;
288
891
  }
289
892
  case "Property": return [node.schema];
290
893
  case "Parameter": return [node.schema];
291
894
  case "Response": return node.schema ? [node.schema] : [];
895
+ case "FunctionParameter":
896
+ case "ObjectBindingParameter":
897
+ case "FunctionParameters": return [];
292
898
  }
293
899
  }
294
900
  /**
295
901
  * 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).
902
+ * Sibling nodes at each level are visited concurrently up to `options.concurrency`
903
+ * (default: `WALK_CONCURRENCY`).
904
+ *
905
+ * @example
906
+ * ```ts
907
+ * await walk(root, {
908
+ * operation(node) {
909
+ * console.log(node.operationId)
910
+ * },
911
+ * })
912
+ * ```
913
+ *
914
+ * @example
915
+ * ```ts
916
+ * // Visit only the current node
917
+ * await walk(root, { depth: 'shallow', root: () => {} })
918
+ * ```
297
919
  */
298
- async function walk(node, visitor, options = {}) {
299
- return _walk(node, visitor, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30));
920
+ async function walk(node, options) {
921
+ return _walk(node, options, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30), void 0);
300
922
  }
301
- async function _walk(node, visitor, recurse, limit) {
923
+ async function _walk(node, visitor, recurse, limit, parent) {
302
924
  switch (node.kind) {
303
925
  case "Root":
304
- await limit(() => visitor.root?.(node));
926
+ await limit(() => visitor.root?.(node, { parent }));
305
927
  break;
306
928
  case "Operation":
307
- await limit(() => visitor.operation?.(node));
929
+ await limit(() => visitor.operation?.(node, { parent }));
308
930
  break;
309
931
  case "Schema":
310
- await limit(() => visitor.schema?.(node));
932
+ await limit(() => visitor.schema?.(node, { parent }));
311
933
  break;
312
934
  case "Property":
313
- await limit(() => visitor.property?.(node));
935
+ await limit(() => visitor.property?.(node, { parent }));
314
936
  break;
315
937
  case "Parameter":
316
- await limit(() => visitor.parameter?.(node));
938
+ await limit(() => visitor.parameter?.(node, { parent }));
317
939
  break;
318
940
  case "Response":
319
- await limit(() => visitor.response?.(node));
941
+ await limit(() => visitor.response?.(node, { parent }));
320
942
  break;
943
+ case "FunctionParameter":
944
+ case "ObjectBindingParameter":
945
+ case "FunctionParameters": break;
321
946
  }
322
947
  const children = getChildren(node, recurse);
323
- await Promise.all(children.map((child) => _walk(child, visitor, recurse, limit)));
948
+ await Promise.all(children.map((child) => _walk(child, visitor, recurse, limit, node)));
324
949
  }
325
- function transform(node, visitor, options = {}) {
326
- const recurse = (options.depth ?? visitorDepths.deep) === visitorDepths.deep;
950
+ function transform(node, options) {
951
+ const { depth, parent, ...visitor } = options;
952
+ const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
327
953
  switch (node.kind) {
328
954
  case "Root": {
329
955
  let root = node;
330
- const replaced = visitor.root?.(root);
956
+ const replaced = visitor.root?.(root, { parent });
331
957
  if (replaced) root = replaced;
332
958
  return {
333
959
  ...root,
334
- schemas: root.schemas.map((s) => transform(s, visitor, options)),
335
- operations: root.operations.map((op) => transform(op, visitor, options))
960
+ schemas: root.schemas.map((s) => transform(s, {
961
+ ...options,
962
+ parent: root
963
+ })),
964
+ operations: root.operations.map((op) => transform(op, {
965
+ ...options,
966
+ parent: root
967
+ }))
336
968
  };
337
969
  }
338
970
  case "Operation": {
339
971
  let op = node;
340
- const replaced = visitor.operation?.(op);
972
+ const replaced = visitor.operation?.(op, { parent });
341
973
  if (replaced) op = replaced;
342
974
  return {
343
975
  ...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))
976
+ parameters: op.parameters.map((p) => transform(p, {
977
+ ...options,
978
+ parent: op
979
+ })),
980
+ requestBody: op.requestBody ? {
981
+ ...op.requestBody,
982
+ schema: op.requestBody.schema ? transform(op.requestBody.schema, {
983
+ ...options,
984
+ parent: op
985
+ }) : void 0
986
+ } : void 0,
987
+ responses: op.responses.map((r) => transform(r, {
988
+ ...options,
989
+ parent: op
990
+ }))
347
991
  };
348
992
  }
349
993
  case "Schema": {
350
994
  let schema = node;
351
- const replaced = visitor.schema?.(schema);
995
+ const replaced = visitor.schema?.(schema, { parent });
352
996
  if (replaced) schema = replaced;
997
+ const childOptions = {
998
+ ...options,
999
+ parent: schema
1000
+ };
353
1001
  return {
354
1002
  ...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)) } : {}
1003
+ ..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p, childOptions)) } : {},
1004
+ ..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i, childOptions)) } : {},
1005
+ ..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, childOptions)) } : {},
1006
+ ..."additionalProperties" in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true ? { additionalProperties: transform(schema.additionalProperties, childOptions) } : {}
358
1007
  };
359
1008
  }
360
1009
  case "Property": {
361
1010
  let prop = node;
362
- const replaced = visitor.property?.(prop);
1011
+ const replaced = visitor.property?.(prop, { parent });
363
1012
  if (replaced) prop = replaced;
364
- return {
1013
+ return createProperty({
365
1014
  ...prop,
366
- schema: transform(prop.schema, visitor, options)
367
- };
1015
+ schema: transform(prop.schema, {
1016
+ ...options,
1017
+ parent: prop
1018
+ })
1019
+ });
368
1020
  }
369
1021
  case "Parameter": {
370
1022
  let param = node;
371
- const replaced = visitor.parameter?.(param);
1023
+ const replaced = visitor.parameter?.(param, { parent });
372
1024
  if (replaced) param = replaced;
373
- return {
1025
+ return createParameter({
374
1026
  ...param,
375
- schema: transform(param.schema, visitor, options)
376
- };
1027
+ schema: transform(param.schema, {
1028
+ ...options,
1029
+ parent: param
1030
+ })
1031
+ });
377
1032
  }
378
1033
  case "Response": {
379
1034
  let response = node;
380
- const replaced = visitor.response?.(response);
1035
+ const replaced = visitor.response?.(response, { parent });
381
1036
  if (replaced) response = replaced;
382
1037
  return {
383
1038
  ...response,
384
- schema: response.schema ? transform(response.schema, visitor, options) : void 0
1039
+ schema: transform(response.schema, {
1040
+ ...options,
1041
+ parent: response
1042
+ })
385
1043
  };
386
1044
  }
1045
+ case "FunctionParameter":
1046
+ case "ObjectBindingParameter":
1047
+ case "FunctionParameters": return node;
387
1048
  }
388
1049
  }
389
1050
  /**
390
- * Depth-first synchronous reduction. Collects non-`undefined` visitor return values into an array.
1051
+ * Composes multiple visitors into one visitor, applied left to right.
1052
+ *
1053
+ * For each node kind, output from one visitor is input to the next.
1054
+ * If a visitor returns `undefined`, the previous node value is kept.
1055
+ *
1056
+ * @example
1057
+ * ```ts
1058
+ * const visitor = composeTransformers(
1059
+ * { operation: (node) => ({ ...node, operationId: `a_${node.operationId}` }) },
1060
+ * { operation: (node) => ({ ...node, operationId: `b_${node.operationId}` }) },
1061
+ * )
1062
+ * ```
391
1063
  */
392
- function collect(node, visitor, options = {}) {
393
- const recurse = (options.depth ?? visitorDepths.deep) === visitorDepths.deep;
1064
+ function composeTransformers(...visitors) {
1065
+ return {
1066
+ root(node, context) {
1067
+ return visitors.reduce((acc, v) => v.root?.(acc, context) ?? acc, node);
1068
+ },
1069
+ operation(node, context) {
1070
+ return visitors.reduce((acc, v) => v.operation?.(acc, context) ?? acc, node);
1071
+ },
1072
+ schema(node, context) {
1073
+ return visitors.reduce((acc, v) => v.schema?.(acc, context) ?? acc, node);
1074
+ },
1075
+ property(node, context) {
1076
+ return visitors.reduce((acc, v) => v.property?.(acc, context) ?? acc, node);
1077
+ },
1078
+ parameter(node, context) {
1079
+ return visitors.reduce((acc, v) => v.parameter?.(acc, context) ?? acc, node);
1080
+ },
1081
+ response(node, context) {
1082
+ return visitors.reduce((acc, v) => v.response?.(acc, context) ?? acc, node);
1083
+ }
1084
+ };
1085
+ }
1086
+ /**
1087
+ * Runs a depth-first synchronous collection pass.
1088
+ *
1089
+ * Non-`undefined` values returned by visitor callbacks are appended to the result.
1090
+ *
1091
+ * @example
1092
+ * ```ts
1093
+ * const ids = collect(root, {
1094
+ * operation(node) {
1095
+ * return node.operationId
1096
+ * },
1097
+ * })
1098
+ * ```
1099
+ *
1100
+ * @example
1101
+ * ```ts
1102
+ * // Collect from only the current node
1103
+ * const values = collect(root, { depth: 'shallow', root: () => 'root' })
1104
+ * ```
1105
+ */
1106
+ function collect(node, options) {
1107
+ const { depth, parent, ...visitor } = options;
1108
+ const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
394
1109
  const results = [];
395
1110
  let v;
396
1111
  switch (node.kind) {
397
1112
  case "Root":
398
- v = visitor.root?.(node);
1113
+ v = visitor.root?.(node, { parent });
399
1114
  break;
400
1115
  case "Operation":
401
- v = visitor.operation?.(node);
1116
+ v = visitor.operation?.(node, { parent });
402
1117
  break;
403
1118
  case "Schema":
404
- v = visitor.schema?.(node);
1119
+ v = visitor.schema?.(node, { parent });
405
1120
  break;
406
1121
  case "Property":
407
- v = visitor.property?.(node);
1122
+ v = visitor.property?.(node, { parent });
408
1123
  break;
409
1124
  case "Parameter":
410
- v = visitor.parameter?.(node);
1125
+ v = visitor.parameter?.(node, { parent });
411
1126
  break;
412
1127
  case "Response":
413
- v = visitor.response?.(node);
1128
+ v = visitor.response?.(node, { parent });
414
1129
  break;
1130
+ case "FunctionParameter":
1131
+ case "ObjectBindingParameter":
1132
+ case "FunctionParameters": break;
415
1133
  }
416
1134
  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);
1135
+ for (const child of getChildren(node, recurse)) for (const item of collect(child, {
1136
+ ...options,
1137
+ parent: node
1138
+ })) results.push(item);
418
1139
  return results;
419
1140
  }
420
1141
  //#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 };
1142
+ //#region src/resolvers.ts
1143
+ function findDiscriminator(mapping, ref) {
1144
+ if (!mapping || !ref) return null;
1145
+ return Object.entries(mapping).find(([, value]) => value === ref)?.[0] ?? null;
1146
+ }
1147
+ function childName(parentName, propName) {
1148
+ return parentName ? pascalCase([parentName, propName].join(" ")) : null;
1149
+ }
1150
+ function enumPropName(parentName, propName, enumSuffix) {
1151
+ return pascalCase([
1152
+ parentName,
1153
+ propName,
1154
+ enumSuffix
1155
+ ].filter(Boolean).join(" "));
1156
+ }
1157
+ /**
1158
+ * Collects import entries for all `ref` schema nodes in `node`.
1159
+ */
1160
+ function collectImports({ node, nameMapping, resolve }) {
1161
+ return collect(node, { schema(schemaNode) {
1162
+ const schemaRef = narrowSchema(schemaNode, "ref");
1163
+ if (!schemaRef?.ref) return;
1164
+ const rawName = extractRefName(schemaRef.ref);
1165
+ const result = resolve(nameMapping.get(rawName) ?? rawName);
1166
+ if (!result) return;
1167
+ return result;
1168
+ } });
1169
+ }
1170
+ //#endregion
1171
+ //#region src/transformers.ts
1172
+ /**
1173
+ * Replaces a discriminator property's schema with a string enum of allowed values.
1174
+ *
1175
+ * If `node` is not an object schema, or if the property does not exist, the input
1176
+ * node is returned as-is.
1177
+ *
1178
+ * @example
1179
+ * ```ts
1180
+ * const schema = createSchema({
1181
+ * type: 'object',
1182
+ * properties: [createProperty({ name: 'type', required: true, schema: createSchema({ type: 'string' }) })],
1183
+ * })
1184
+ * const result = setDiscriminatorEnum({ node: schema, propertyName: 'type', values: ['dog', 'cat'] })
1185
+ * ```
1186
+ */
1187
+ function setDiscriminatorEnum({ node, propertyName, values, enumName }) {
1188
+ const objectNode = narrowSchema(node, "object");
1189
+ if (!objectNode?.properties?.length) return node;
1190
+ if (!objectNode.properties.some((prop) => prop.name === propertyName)) return node;
1191
+ return createSchema({
1192
+ ...objectNode,
1193
+ properties: objectNode.properties.map((prop) => {
1194
+ if (prop.name !== propertyName) return prop;
1195
+ return createProperty({
1196
+ ...prop,
1197
+ schema: createSchema({
1198
+ type: "enum",
1199
+ primitive: "string",
1200
+ enumValues: values,
1201
+ name: enumName,
1202
+ readOnly: prop.schema.readOnly,
1203
+ writeOnly: prop.schema.writeOnly
1204
+ })
1205
+ });
1206
+ })
1207
+ });
1208
+ }
1209
+ /**
1210
+ * Merges adjacent anonymous object members into a single anonymous object member.
1211
+ *
1212
+ * @example
1213
+ * ```ts
1214
+ * const merged = mergeAdjacentObjects([
1215
+ * createSchema({ type: 'object', properties: [createProperty({ name: 'a', schema: createSchema({ type: 'string' }) })] }),
1216
+ * createSchema({ type: 'object', properties: [createProperty({ name: 'b', schema: createSchema({ type: 'number' }) })] }),
1217
+ * ])
1218
+ * ```
1219
+ */
1220
+ function mergeAdjacentObjects(members) {
1221
+ return members.reduce((acc, member) => {
1222
+ const objectMember = narrowSchema(member, "object");
1223
+ if (objectMember && !objectMember.name) {
1224
+ const previous = acc.at(-1);
1225
+ const previousObject = previous ? narrowSchema(previous, "object") : void 0;
1226
+ if (previousObject && !previousObject.name) {
1227
+ acc[acc.length - 1] = createSchema({
1228
+ ...previousObject,
1229
+ properties: [...previousObject.properties ?? [], ...objectMember.properties ?? []]
1230
+ });
1231
+ return acc;
1232
+ }
1233
+ }
1234
+ acc.push(member);
1235
+ return acc;
1236
+ }, []);
1237
+ }
1238
+ /**
1239
+ * Removes enum members that are covered by broader scalar primitives in the same union.
1240
+ *
1241
+ * @example
1242
+ * ```ts
1243
+ * const simplified = simplifyUnion([
1244
+ * createSchema({ type: 'enum', primitive: 'string', enumValues: ['active'] }),
1245
+ * createSchema({ type: 'string' }),
1246
+ * ])
1247
+ * // keeps only string member
1248
+ * ```
1249
+ */
1250
+ function simplifyUnion(members) {
1251
+ const scalarPrimitives = new Set(members.filter((member) => SCALAR_PRIMITIVE_TYPES.has(member.type)).map((m) => m.type));
1252
+ if (!scalarPrimitives.size) return members;
1253
+ return members.filter((member) => {
1254
+ const enumNode = narrowSchema(member, "enum");
1255
+ if (!enumNode) return true;
1256
+ const primitive = enumNode.primitive;
1257
+ if (!primitive) return true;
1258
+ if ((enumNode.namedEnumValues?.length ?? enumNode.enumValues?.length ?? 0) <= 1) return true;
1259
+ if (scalarPrimitives.has(primitive)) return false;
1260
+ if ((primitive === "integer" || primitive === "number") && (scalarPrimitives.has("integer") || scalarPrimitives.has("number"))) return false;
1261
+ return true;
1262
+ });
1263
+ }
1264
+ function setEnumName(propNode, parentName, propName, enumSuffix) {
1265
+ const enumNode = narrowSchema(propNode, "enum");
1266
+ if (enumNode?.primitive === "boolean") return {
1267
+ ...propNode,
1268
+ name: void 0
1269
+ };
1270
+ if (enumNode) return {
1271
+ ...propNode,
1272
+ name: enumPropName(parentName, propName, enumSuffix)
1273
+ };
1274
+ return propNode;
1275
+ }
1276
+ /**
1277
+ * Walks a schema tree and resolves `ref`/`enum` names through callbacks.
1278
+ */
1279
+ function resolveNames({ node, nameMapping, resolveName, resolveEnumName }) {
1280
+ return transform(node, { schema(schemaNode) {
1281
+ const schemaRef = narrowSchema(schemaNode, "ref");
1282
+ if (schemaRef && (schemaRef.ref || schemaRef.name)) {
1283
+ const rawRef = schemaRef.ref ?? schemaRef.name;
1284
+ const resolved = resolveName(nameMapping.get(rawRef) ?? rawRef);
1285
+ if (resolved) return {
1286
+ ...schemaNode,
1287
+ name: resolved
1288
+ };
1289
+ }
1290
+ const schemaEnum = narrowSchema(schemaNode, "enum");
1291
+ if (schemaEnum?.name) {
1292
+ const resolved = (resolveEnumName ?? resolveName)(schemaEnum.name);
1293
+ if (resolved) return {
1294
+ ...schemaNode,
1295
+ name: resolved
1296
+ };
1297
+ }
1298
+ } });
1299
+ }
1300
+ //#endregion
1301
+ 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
1302
 
423
1303
  //# sourceMappingURL=index.js.map