@kubb/ast 5.0.0-alpha.22 → 5.0.0-alpha.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -5,17 +5,6 @@ const visitorDepths = {
5
5
  shallow: "shallow",
6
6
  deep: "deep"
7
7
  };
8
- const nodeKinds = {
9
- root: "Root",
10
- operation: "Operation",
11
- schema: "Schema",
12
- property: "Property",
13
- parameter: "Parameter",
14
- response: "Response",
15
- functionParameter: "FunctionParameter",
16
- objectBindingParameter: "ObjectBindingParameter",
17
- functionParameters: "FunctionParameters"
18
- };
19
8
  /**
20
9
  * Canonical schema type strings used by AST schema nodes.
21
10
  *
@@ -63,6 +52,12 @@ const SCALAR_PRIMITIVE_TYPES = new Set([
63
52
  "bigint",
64
53
  "boolean"
65
54
  ]);
55
+ /**
56
+ * Returns `true` when `type` is a scalar primitive schema type.
57
+ */
58
+ function isScalarPrimitive(type) {
59
+ return SCALAR_PRIMITIVE_TYPES.has(type);
60
+ }
66
61
  const httpMethods = {
67
62
  get: "GET",
68
63
  post: "POST",
@@ -95,218 +90,14 @@ const mediaTypes = {
95
90
  videoMp4: "video/mp4"
96
91
  };
97
92
  //#endregion
98
- //#region ../../internals/utils/src/casing.ts
99
- /**
100
- * Shared implementation for camelCase and PascalCase conversion.
101
- * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
102
- * and capitalizes each word according to `pascal`.
103
- *
104
- * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
105
- */
106
- function toCamelOrPascal(text, pascal) {
107
- 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) => {
108
- if (word.length > 1 && word === word.toUpperCase()) return word;
109
- if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
110
- return word.charAt(0).toUpperCase() + word.slice(1);
111
- }).join("").replace(/[^a-zA-Z0-9]/g, "");
112
- }
113
- /**
114
- * Splits `text` on `.` and applies `transformPart` to each segment.
115
- * The last segment receives `isLast = true`, all earlier segments receive `false`.
116
- * Segments are joined with `/` to form a file path.
117
- *
118
- * Only splits on dots followed by a letter so that version numbers
119
- * embedded in operationIds (e.g. `v2025.0`) are kept intact.
120
- */
121
- function applyToFileParts(text, transformPart) {
122
- const parts = text.split(/\.(?=[a-zA-Z])/);
123
- return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
124
- }
125
- /**
126
- * Converts `text` to camelCase.
127
- * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
128
- *
129
- * @example
130
- * camelCase('hello-world') // 'helloWorld'
131
- * camelCase('pet.petId', { isFile: true }) // 'pet/petId'
132
- */
133
- function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
134
- if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
135
- prefix,
136
- suffix
137
- } : {}));
138
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
139
- }
140
- /**
141
- * Converts `text` to PascalCase.
142
- * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
143
- *
144
- * @example
145
- * pascalCase('hello-world') // 'HelloWorld'
146
- * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
147
- */
148
- function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
149
- if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
150
- prefix,
151
- suffix
152
- }) : camelCase(part));
153
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
154
- }
155
- //#endregion
156
- //#region ../../internals/utils/src/reserved.ts
157
- /**
158
- * Returns `true` when `name` is a syntactically valid JavaScript variable name.
159
- *
160
- * @example
161
- * ```ts
162
- * isValidVarName('status') // true
163
- * isValidVarName('class') // false (reserved word)
164
- * isValidVarName('42foo') // false (starts with digit)
165
- * ```
166
- */
167
- function isValidVarName(name) {
168
- try {
169
- new Function(`var ${name}`);
170
- } catch {
171
- return false;
172
- }
173
- return true;
174
- }
175
- //#endregion
176
- //#region src/guards.ts
177
- /**
178
- * Narrows a `SchemaNode` to the variant that matches `type`.
179
- *
180
- * @example
181
- * ```ts
182
- * const schema = createSchema({ type: 'string' })
183
- * const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | undefined
184
- * ```
185
- */
186
- function narrowSchema(node, type) {
187
- return node?.type === type ? node : void 0;
188
- }
189
- function isKind(kind) {
190
- return (node) => node.kind === kind;
191
- }
192
- /**
193
- * Returns `true` when the input is a `RootNode`.
194
- *
195
- * @example
196
- * ```ts
197
- * if (isRootNode(node)) {
198
- * console.log(node.schemas.length)
199
- * }
200
- * ```
201
- */
202
- const isRootNode = isKind("Root");
203
- /**
204
- * Returns `true` when the input is an `OperationNode`.
205
- *
206
- * @example
207
- * ```ts
208
- * if (isOperationNode(node)) {
209
- * console.log(node.operationId)
210
- * }
211
- * ```
212
- */
213
- const isOperationNode = isKind("Operation");
214
- /**
215
- * Returns `true` when the input is a `SchemaNode`.
216
- *
217
- * @example
218
- * ```ts
219
- * if (isSchemaNode(node)) {
220
- * console.log(node.type)
221
- * }
222
- * ```
223
- */
224
- const isSchemaNode = isKind("Schema");
225
- /**
226
- * Returns `true` when the input is a `PropertyNode`.
227
- */
228
- const isPropertyNode = isKind("Property");
229
- /**
230
- * Returns `true` when the input is a `ParameterNode`.
231
- */
232
- const isParameterNode = isKind("Parameter");
233
- /**
234
- * Returns `true` when the input is a `ResponseNode`.
235
- */
236
- const isResponseNode = isKind("Response");
237
- /**
238
- * Returns `true` when the input is a `FunctionParameterNode`.
239
- */
240
- const isFunctionParameterNode = isKind("FunctionParameter");
241
- /**
242
- * Returns `true` when the input is an `ObjectBindingParameterNode`.
243
- */
244
- const isObjectBindingParameterNode = isKind("ObjectBindingParameter");
245
- /**
246
- * Returns `true` when the input is a `FunctionParametersNode`.
247
- */
248
- const isFunctionParametersNode = isKind("FunctionParameters");
249
- //#endregion
250
- //#region src/utils.ts
251
- const plainStringTypes = new Set([
252
- "string",
253
- "uuid",
254
- "email",
255
- "url",
256
- "datetime"
257
- ]);
258
- /**
259
- * Returns `true` when a schema is emitted as a plain TypeScript `string`.
260
- *
261
- * - `string`, `uuid`, `email`, `url`, `datetime` are always plain strings.
262
- * - `date` and `time` are plain strings when their `representation` is `'string'` rather than `'date'`.
263
- *
264
- * @example
265
- * ```ts
266
- * isStringType(createSchema({ type: 'uuid' })) // true
267
- * isStringType(createSchema({ type: 'date', representation: 'date' })) // false
268
- * ```
269
- */
270
- function isStringType(node) {
271
- if (plainStringTypes.has(node.type)) return true;
272
- const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
273
- if (temporal) return temporal.representation !== "date";
274
- return false;
275
- }
276
- /**
277
- * Applies casing rules to parameter names and returns a new parameter array.
278
- *
279
- * The input array is not mutated.
280
- * If `casing` is not set, the original array is returned unchanged.
281
- *
282
- * Use this before passing parameters to schema builders so that property keys
283
- * in generated output match the desired casing while preserving
284
- * `OperationNode.parameters` for other consumers.
285
- *
286
- * @example
287
- * ```ts
288
- * const params = [createParameter({ name: 'pet_id', in: 'query', schema: createSchema({ type: 'string' }) })]
289
- * const cased = caseParams(params, 'camelcase')
290
- * // cased[0].name === 'petId'
291
- * ```
292
- */
293
- function caseParams(params, casing) {
294
- if (!casing) return params;
295
- return params.map((param) => {
296
- const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
297
- return {
298
- ...param,
299
- name: transformed
300
- };
301
- });
302
- }
93
+ //#region src/factory.ts
303
94
  /**
304
95
  * Syncs property/parameter schema optionality flags from `required` and `schema.nullable`.
305
96
  *
306
97
  * - `optional` is set for non-required, non-nullable schemas.
307
98
  * - `nullish` is set for non-required, nullable schemas.
308
99
  */
309
- function syncOptionality(required, schema) {
100
+ function syncOptionality(schema, required) {
310
101
  const nullable = schema.nullable ?? false;
311
102
  return {
312
103
  ...schema,
@@ -314,8 +105,6 @@ function syncOptionality(required, schema) {
314
105
  nullish: !required && nullable ? true : void 0
315
106
  };
316
107
  }
317
- //#endregion
318
- //#region src/factory.ts
319
108
  /**
320
109
  * Creates a `RootNode` with stable defaults for `schemas` and `operations`.
321
110
  *
@@ -413,7 +202,7 @@ function createProperty(props) {
413
202
  ...props,
414
203
  kind: "Property",
415
204
  required,
416
- schema: syncOptionality(required, props.schema)
205
+ schema: syncOptionality(props.schema, required)
417
206
  };
418
207
  }
419
208
  /**
@@ -448,7 +237,7 @@ function createParameter(props) {
448
237
  ...props,
449
238
  kind: "Parameter",
450
239
  required,
451
- schema: syncOptionality(required, props.schema)
240
+ schema: syncOptionality(props.schema, required)
452
241
  };
453
242
  }
454
243
  /**
@@ -470,49 +259,25 @@ function createResponse(props) {
470
259
  };
471
260
  }
472
261
  /**
473
- * Creates a single-property object schema used as a discriminator literal.
474
- *
475
- * @example
476
- * ```ts
477
- * createDiscriminantNode({ propertyName: 'type', value: 'dog' })
478
- * // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
479
- * ```
480
- */
481
- function createDiscriminantNode({ propertyName, value }) {
482
- return createSchema({
483
- type: "object",
484
- primitive: "object",
485
- properties: [createProperty({
486
- name: propertyName,
487
- schema: createSchema({
488
- type: "enum",
489
- primitive: "string",
490
- enumValues: [value]
491
- }),
492
- required: true
493
- })]
494
- });
495
- }
496
- /**
497
262
  * Creates a `FunctionParameterNode`.
498
263
  *
499
264
  * `optional` defaults to `false`.
500
265
  *
501
266
  * @example Required typed param
502
267
  * ```ts
503
- * createFunctionParameter({ name: 'petId', type: 'string' })
268
+ * createFunctionParameter({ name: 'petId', type: createTypeNode({ variant: 'reference', name: 'string' }) })
504
269
  * // → petId: string
505
270
  * ```
506
271
  *
507
272
  * @example Optional param
508
273
  * ```ts
509
- * createFunctionParameter({ name: 'params', type: 'QueryParams', optional: true })
274
+ * createFunctionParameter({ name: 'params', type: createTypeNode({ variant: 'reference', name: 'QueryParams' }), optional: true })
510
275
  * // → params?: QueryParams
511
276
  * ```
512
277
  *
513
278
  * @example Param with default (implicitly optional; cannot combine with `optional: true`)
514
279
  * ```ts
515
- * createFunctionParameter({ name: 'config', type: 'RequestConfig', default: '{}' })
280
+ * createFunctionParameter({ name: 'config', type: createTypeNode({ variant: 'reference', name: 'RequestConfig' }), default: '{}' })
516
281
  * // → config: RequestConfig = {}
517
282
  * ```
518
283
  */
@@ -524,14 +289,42 @@ function createFunctionParameter(props) {
524
289
  };
525
290
  }
526
291
  /**
527
- * Creates an `ObjectBindingParameterNode` for object-destructured parameter groups.
292
+ * Creates a {@link TypeNode} representing a language-agnostic structured type expression.
293
+ *
294
+ * Use `variant: 'struct'` for inline anonymous types and `variant: 'member'` for a single
295
+ * named field accessed from a group type. Each language's printer renders the variant
296
+ * into its own syntax (TypeScript, Python, C#, Kotlin, …).
297
+ *
298
+ * @example Reference type (TypeScript: `QueryParams`)
299
+ * ```ts
300
+ * createTypeNode({ variant: 'reference', name: 'QueryParams' })
301
+ * ```
302
+ *
303
+ * @example Struct type (TypeScript: `{ petId: string }`)
304
+ * ```ts
305
+ * createTypeNode({ variant: 'struct', properties: [{ name: 'petId', optional: false, type: createTypeNode({ variant: 'reference', name: 'string' }) }] })
306
+ * ```
307
+ *
308
+ * @example Member type (TypeScript: `DeletePetPathParams['petId']`)
309
+ * ```ts
310
+ * createTypeNode({ variant: 'member', base: 'DeletePetPathParams', key: 'petId' })
311
+ * ```
312
+ */
313
+ function createTypeNode(props) {
314
+ return {
315
+ ...props,
316
+ kind: "Type"
317
+ };
318
+ }
319
+ /**
320
+ * Creates a `ParameterGroupNode` representing a group of related parameters treated as a unit.
528
321
  *
529
- * @example Destructured object param
322
+ * @example Grouped param (TypeScript declaration)
530
323
  * ```ts
531
- * createObjectBindingParameter({
324
+ * createParameterGroup({
532
325
  * properties: [
533
- * createFunctionParameter({ name: 'id', type: 'string', optional: false }),
534
- * createFunctionParameter({ name: 'name', type: 'string', optional: true }),
326
+ * createFunctionParameter({ name: 'id', type: createTypeNode({ variant: 'reference', name: 'string' }), optional: false }),
327
+ * createFunctionParameter({ name: 'name', type: createTypeNode({ variant: 'reference', name: 'string' }), optional: true }),
535
328
  * ],
536
329
  * default: '{}',
537
330
  * })
@@ -539,20 +332,20 @@ function createFunctionParameter(props) {
539
332
  * // call → { id, name }
540
333
  * ```
541
334
  *
542
- * @example Inline mode — children emitted as individual top-level parameters
335
+ * @example Inline (spread) — children emitted as individual top-level parameters
543
336
  * ```ts
544
- * createObjectBindingParameter({
545
- * properties: [createFunctionParameter({ name: 'petId', type: 'string', optional: false })],
337
+ * createParameterGroup({
338
+ * properties: [createFunctionParameter({ name: 'petId', type: createTypeNode({ variant: 'reference', name: 'string' }), optional: false })],
546
339
  * inline: true,
547
340
  * })
548
341
  * // declaration → petId: string
549
342
  * // call → petId
550
343
  * ```
551
344
  */
552
- function createObjectBindingParameter(props) {
345
+ function createParameterGroup(props) {
553
346
  return {
554
347
  ...props,
555
- kind: "ObjectBindingParameter"
348
+ kind: "ParameterGroup"
556
349
  };
557
350
  }
558
351
  /**
@@ -562,8 +355,8 @@ function createObjectBindingParameter(props) {
562
355
  * ```ts
563
356
  * createFunctionParameters({
564
357
  * params: [
565
- * createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
566
- * createFunctionParameter({ name: 'config', type: 'RequestConfig', optional: false, default: '{}' }),
358
+ * createFunctionParameter({ name: 'petId', type: createTypeNode({ variant: 'reference', name: 'string' }), optional: false }),
359
+ * createFunctionParameter({ name: 'config', type: createTypeNode({ variant: 'reference', name: 'RequestConfig' }), optional: false, default: '{}' }),
567
360
  * ],
568
361
  * })
569
362
  * ```
@@ -582,7 +375,53 @@ function createFunctionParameters(props = {}) {
582
375
  };
583
376
  }
584
377
  //#endregion
585
- //#region src/printers/printer.ts
378
+ //#region src/guards.ts
379
+ /**
380
+ * Narrows a `SchemaNode` to the variant that matches `type`.
381
+ *
382
+ * @example
383
+ * ```ts
384
+ * const schema = createSchema({ type: 'string' })
385
+ * const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | undefined
386
+ * ```
387
+ */
388
+ function narrowSchema(node, type) {
389
+ return node?.type === type ? node : void 0;
390
+ }
391
+ function isKind(kind) {
392
+ return (node) => node.kind === kind;
393
+ }
394
+ isKind("Root");
395
+ /**
396
+ * Returns `true` when the input is an `OperationNode`.
397
+ *
398
+ * @example
399
+ * ```ts
400
+ * if (isOperationNode(node)) {
401
+ * console.log(node.operationId)
402
+ * }
403
+ * ```
404
+ */
405
+ const isOperationNode = isKind("Operation");
406
+ /**
407
+ * Returns `true` when the input is a `SchemaNode`.
408
+ *
409
+ * @example
410
+ * ```ts
411
+ * if (isSchemaNode(node)) {
412
+ * console.log(node.type)
413
+ * }
414
+ * ```
415
+ */
416
+ const isSchemaNode = isKind("Schema");
417
+ isKind("Property");
418
+ isKind("Parameter");
419
+ isKind("Response");
420
+ isKind("FunctionParameter");
421
+ isKind("ParameterGroup");
422
+ isKind("FunctionParameters");
423
+ //#endregion
424
+ //#region src/printer.ts
586
425
  /**
587
426
  * Creates a schema printer factory.
588
427
  *
@@ -652,123 +491,6 @@ function createPrinterFactory(getKey) {
652
491
  };
653
492
  }
654
493
  //#endregion
655
- //#region src/printers/functionPrinter.ts
656
- const kindToHandlerKey = {
657
- FunctionParameter: "functionParameter",
658
- ObjectBindingParameter: "objectBindingParameter",
659
- FunctionParameters: "functionParameters"
660
- };
661
- /**
662
- * Creates a function-parameter printer factory.
663
- *
664
- * This wrapper uses `createPrinterFactory` and dispatches handlers by `node.kind`
665
- * (for function nodes) rather than by `node.type` (for schema nodes).
666
- *
667
- * @example
668
- * ```ts
669
- * type MyPrinter = PrinterFactoryOptions<'my', { mode: 'declaration' | 'call' }, string>
670
- *
671
- * export const myPrinter = defineFunctionPrinter<MyPrinter>((options) => ({
672
- * name: 'my',
673
- * options,
674
- * nodes: {
675
- * functionParameter(node) {
676
- * return options.mode === 'declaration' && node.type ? `${node.name}: ${node.type}` : node.name
677
- * },
678
- * objectBindingParameter(node) {
679
- * const inner = node.properties.map(p => this.transform(p)).filter(Boolean).join(', ')
680
- * return `{ ${inner} }`
681
- * },
682
- * functionParameters(node) {
683
- * return node.params.map(p => this.transform(p)).filter(Boolean).join(', ')
684
- * },
685
- * },
686
- * }))
687
- * ```
688
- */
689
- const defineFunctionPrinter = createPrinterFactory((node) => kindToHandlerKey[node.kind]);
690
- function rank(param) {
691
- if (param.kind === "ObjectBindingParameter") {
692
- if (param.default) return 2;
693
- return param.optional ?? param.properties.every((p) => p.optional || p.default !== void 0) ? 1 : 0;
694
- }
695
- if (param.rest) return 3;
696
- if (param.default) return 2;
697
- return param.optional ? 1 : 0;
698
- }
699
- function sortParams(params) {
700
- return [...params].sort((a, b) => rank(a) - rank(b));
701
- }
702
- function sortChildParams(params) {
703
- return [...params].sort((a, b) => rank(a) - rank(b));
704
- }
705
- /**
706
- * Default function-signature printer.
707
- * Covers the four standard output modes used across Kubb plugins.
708
- *
709
- * @example
710
- * ```ts
711
- * const printer = functionPrinter({ mode: 'declaration' })
712
- *
713
- * const sig = createFunctionParameters({
714
- * params: [
715
- * createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
716
- * createFunctionParameter({ name: 'config', type: 'Config', optional: false, default: '{}' }),
717
- * ],
718
- * })
719
- *
720
- * printer.print(sig) // → "petId: string, config: Config = {}"
721
- * ```
722
- */
723
- const functionPrinter = defineFunctionPrinter((options) => ({
724
- name: "functionParameters",
725
- options,
726
- nodes: {
727
- functionParameter(node) {
728
- const { mode, transformName, transformType } = this.options;
729
- const name = transformName ? transformName(node.name) : node.name;
730
- const type = node.type && transformType ? transformType(node.type) : node.type;
731
- if (mode === "keys" || mode === "values") return node.rest ? `...${name}` : name;
732
- if (mode === "call") return node.rest ? `...${name}` : name;
733
- if (node.rest) return type ? `...${name}: ${type}` : `...${name}`;
734
- if (type) {
735
- if (node.optional) return `${name}?: ${type}`;
736
- return node.default ? `${name}: ${type} = ${node.default}` : `${name}: ${type}`;
737
- }
738
- return node.default ? `${name} = ${node.default}` : name;
739
- },
740
- objectBindingParameter(node) {
741
- const { mode, transformName, transformType } = this.options;
742
- const sorted = sortChildParams(node.properties);
743
- const isOptional = node.optional ?? sorted.every((p) => p.optional || p.default !== void 0);
744
- if (node.inline) return sorted.map((p) => this.transform(p)).filter(Boolean).join(", ");
745
- if (mode === "keys" || mode === "values") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
746
- if (mode === "call") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
747
- const names = sorted.map((p) => {
748
- return transformName ? transformName(p.name) : p.name;
749
- });
750
- const nameStr = names.length ? `{ ${names.join(", ")} }` : void 0;
751
- if (!nameStr) return null;
752
- let typeAnnotation = node.type;
753
- if (!typeAnnotation) {
754
- const typeParts = sorted.filter((p) => p.type).map((p) => {
755
- const t = transformType && p.type ? transformType(p.type) : p.type;
756
- return p.optional || p.default !== void 0 ? `${p.name}?: ${t}` : `${p.name}: ${t}`;
757
- });
758
- typeAnnotation = typeParts.length ? `{ ${typeParts.join("; ")} }` : void 0;
759
- }
760
- if (typeAnnotation) {
761
- if (isOptional) return `${nameStr}: ${typeAnnotation} = ${node.default ?? "{}"}`;
762
- return node.default ? `${nameStr}: ${typeAnnotation} = ${node.default}` : `${nameStr}: ${typeAnnotation}`;
763
- }
764
- return node.default ? `${nameStr} = ${node.default}` : nameStr;
765
- },
766
- functionParameters(node) {
767
- return sortParams(node.params).map((p) => this.transform(p)).filter(Boolean).join(", ");
768
- }
769
- }
770
- }));
771
- //#endregion
772
494
  //#region src/refs.ts
773
495
  /**
774
496
  * Returns the last path segment of a reference string.
@@ -783,43 +505,83 @@ const functionPrinter = defineFunctionPrinter((options) => ({
783
505
  function extractRefName(ref) {
784
506
  return ref.split("/").at(-1) ?? ref;
785
507
  }
508
+ //#endregion
509
+ //#region ../../internals/utils/src/casing.ts
786
510
  /**
787
- * Builds a `RefMap` from `root.schemas` using each schema's `name`.
511
+ * Shared implementation for camelCase and PascalCase conversion.
512
+ * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
513
+ * and capitalizes each word according to `pascal`.
788
514
  *
789
- * Unnamed schemas are skipped.
515
+ * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
516
+ */
517
+ function toCamelOrPascal(text, pascal) {
518
+ 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) => {
519
+ if (word.length > 1 && word === word.toUpperCase()) return word;
520
+ if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
521
+ return word.charAt(0).toUpperCase() + word.slice(1);
522
+ }).join("").replace(/[^a-zA-Z0-9]/g, "");
523
+ }
524
+ /**
525
+ * Splits `text` on `.` and applies `transformPart` to each segment.
526
+ * The last segment receives `isLast = true`, all earlier segments receive `false`.
527
+ * Segments are joined with `/` to form a file path.
528
+ *
529
+ * Only splits on dots followed by a letter so that version numbers
530
+ * embedded in operationIds (e.g. `v2025.0`) are kept intact.
531
+ */
532
+ function applyToFileParts(text, transformPart) {
533
+ const parts = text.split(/\.(?=[a-zA-Z])/);
534
+ return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
535
+ }
536
+ /**
537
+ * Converts `text` to camelCase.
538
+ * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
790
539
  *
791
540
  * @example
792
- * ```ts
793
- * const refMap = buildRefMap(root)
794
- * const pet = refMap.get('Pet')
795
- * ```
541
+ * camelCase('hello-world') // 'helloWorld'
542
+ * camelCase('pet.petId', { isFile: true }) // 'pet/petId'
796
543
  */
797
- function buildRefMap(root) {
798
- const map = /* @__PURE__ */ new Map();
799
- for (const schema of root.schemas) if (schema.name) map.set(schema.name, schema);
800
- return map;
544
+ function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
545
+ if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
546
+ prefix,
547
+ suffix
548
+ } : {}));
549
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
801
550
  }
802
551
  /**
803
- * Resolves a schema by name from a `RefMap`.
552
+ * Converts `text` to PascalCase.
553
+ * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
804
554
  *
805
555
  * @example
806
- * ```ts
807
- * const petSchema = resolveRef(refMap, 'Pet')
808
- * ```
556
+ * pascalCase('hello-world') // 'HelloWorld'
557
+ * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
809
558
  */
810
- function resolveRef(refMap, ref) {
811
- return refMap.get(ref);
559
+ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
560
+ if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
561
+ prefix,
562
+ suffix
563
+ }) : camelCase(part));
564
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
812
565
  }
566
+ //#endregion
567
+ //#region ../../internals/utils/src/reserved.ts
813
568
  /**
814
- * Converts a `RefMap` into a plain object.
569
+ * Returns `true` when `name` is a syntactically valid JavaScript variable name.
815
570
  *
816
571
  * @example
817
572
  * ```ts
818
- * const refsObject = refMapToObject(refMap)
573
+ * isValidVarName('status') // true
574
+ * isValidVarName('class') // false (reserved word)
575
+ * isValidVarName('42foo') // false (starts with digit)
819
576
  * ```
820
577
  */
821
- function refMapToObject(refMap) {
822
- return Object.fromEntries(refMap);
578
+ function isValidVarName(name) {
579
+ try {
580
+ new Function(`var ${name}`);
581
+ } catch {
582
+ return false;
583
+ }
584
+ return true;
823
585
  }
824
586
  //#endregion
825
587
  //#region src/visitor.ts
@@ -894,8 +656,9 @@ function getChildren(node, recurse) {
894
656
  case "Parameter": return [node.schema];
895
657
  case "Response": return node.schema ? [node.schema] : [];
896
658
  case "FunctionParameter":
897
- case "ObjectBindingParameter":
898
- case "FunctionParameters": return [];
659
+ case "ParameterGroup":
660
+ case "FunctionParameters":
661
+ case "Type": return [];
899
662
  }
900
663
  }
901
664
  /**
@@ -942,7 +705,7 @@ async function _walk(node, visitor, recurse, limit, parent) {
942
705
  await limit(() => visitor.response?.(node, { parent }));
943
706
  break;
944
707
  case "FunctionParameter":
945
- case "ObjectBindingParameter":
708
+ case "ParameterGroup":
946
709
  case "FunctionParameters": break;
947
710
  }
948
711
  const children = getChildren(node, recurse);
@@ -1044,8 +807,9 @@ function transform(node, options) {
1044
807
  };
1045
808
  }
1046
809
  case "FunctionParameter":
1047
- case "ObjectBindingParameter":
1048
- case "FunctionParameters": return node;
810
+ case "ParameterGroup":
811
+ case "FunctionParameters":
812
+ case "Type": return node;
1049
813
  }
1050
814
  }
1051
815
  /**
@@ -1129,7 +893,7 @@ function collect(node, options) {
1129
893
  v = visitor.response?.(node, { parent });
1130
894
  break;
1131
895
  case "FunctionParameter":
1132
- case "ObjectBindingParameter":
896
+ case "ParameterGroup":
1133
897
  case "FunctionParameters": break;
1134
898
  }
1135
899
  if (v !== void 0) results.push(v);
@@ -1249,7 +1013,7 @@ function mergeAdjacentObjects(members) {
1249
1013
  * ```
1250
1014
  */
1251
1015
  function simplifyUnion(members) {
1252
- const scalarPrimitives = new Set(members.filter((member) => SCALAR_PRIMITIVE_TYPES.has(member.type)).map((m) => m.type));
1016
+ const scalarPrimitives = new Set(members.filter((member) => isScalarPrimitive(member.type)).map((m) => m.type));
1253
1017
  if (!scalarPrimitives.size) return members;
1254
1018
  return members.filter((member) => {
1255
1019
  const enumNode = narrowSchema(member, "enum");
@@ -1274,33 +1038,308 @@ function setEnumName(propNode, parentName, propName, enumSuffix) {
1274
1038
  };
1275
1039
  return propNode;
1276
1040
  }
1041
+ //#endregion
1042
+ //#region src/utils.ts
1043
+ const plainStringTypes = new Set([
1044
+ "string",
1045
+ "uuid",
1046
+ "email",
1047
+ "url",
1048
+ "datetime"
1049
+ ]);
1277
1050
  /**
1278
- * Walks a schema tree and resolves `ref`/`enum` names through callbacks.
1051
+ * Returns `true` when a schema is emitted as a plain `string` type.
1052
+ *
1053
+ * - `string`, `uuid`, `email`, `url`, `datetime` are always plain strings.
1054
+ * - `date` and `time` are plain strings when their `representation` is `'string'` rather than `'date'`.
1055
+ *
1056
+ * @example
1057
+ * ```ts
1058
+ * isStringType(createSchema({ type: 'uuid' })) // true
1059
+ * isStringType(createSchema({ type: 'date', representation: 'date' })) // false
1060
+ * ```
1279
1061
  */
1280
- function resolveNames({ node, nameMapping, resolveName, resolveEnumName }) {
1281
- return transform(node, { schema(schemaNode) {
1282
- const schemaRef = narrowSchema(schemaNode, "ref");
1283
- if (schemaRef && (schemaRef.ref || schemaRef.name)) {
1284
- const rawRef = schemaRef.ref ?? schemaRef.name;
1285
- const resolved = resolveName(nameMapping.get(rawRef) ?? rawRef);
1286
- if (resolved) return {
1287
- ...schemaNode,
1288
- name: resolved
1289
- };
1290
- }
1291
- const schemaEnum = narrowSchema(schemaNode, "enum");
1292
- if (schemaEnum?.name) {
1293
- const resolved = (resolveEnumName ?? resolveName)(schemaEnum.name);
1294
- if (resolved) return {
1295
- ...schemaNode,
1296
- name: resolved
1297
- };
1062
+ function isStringType(node) {
1063
+ if (plainStringTypes.has(node.type)) return true;
1064
+ const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
1065
+ if (temporal) return temporal.representation !== "date";
1066
+ return false;
1067
+ }
1068
+ /**
1069
+ * Applies casing rules to parameter names and returns a new parameter array.
1070
+ *
1071
+ * The input array is not mutated.
1072
+ * If `casing` is not set, the original array is returned unchanged.
1073
+ *
1074
+ * Use this before passing parameters to schema builders so that property keys
1075
+ * in generated output match the desired casing while preserving
1076
+ * `OperationNode.parameters` for other consumers.
1077
+ *
1078
+ * @example
1079
+ * ```ts
1080
+ * const params = [createParameter({ name: 'pet_id', in: 'query', schema: createSchema({ type: 'string' }) })]
1081
+ * const cased = caseParams(params, 'camelcase')
1082
+ * // cased[0].name === 'petId'
1083
+ * ```
1084
+ */
1085
+ function caseParams(params, casing) {
1086
+ if (!casing) return params;
1087
+ return params.map((param) => {
1088
+ const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
1089
+ return {
1090
+ ...param,
1091
+ name: transformed
1092
+ };
1093
+ });
1094
+ }
1095
+ /**
1096
+ * Creates a single-property object schema used as a discriminator literal.
1097
+ *
1098
+ * @example
1099
+ * ```ts
1100
+ * createDiscriminantNode({ propertyName: 'type', value: 'dog' })
1101
+ * // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
1102
+ * ```
1103
+ */
1104
+ function createDiscriminantNode({ propertyName, value }) {
1105
+ return createSchema({
1106
+ type: "object",
1107
+ primitive: "object",
1108
+ properties: [createProperty({
1109
+ name: propertyName,
1110
+ schema: createSchema({
1111
+ type: "enum",
1112
+ primitive: "string",
1113
+ enumValues: [value]
1114
+ }),
1115
+ required: true
1116
+ })]
1117
+ });
1118
+ }
1119
+ function resolveType({ node, param, resolver }) {
1120
+ if (!resolver) return createTypeNode({
1121
+ variant: "reference",
1122
+ name: param.schema.primitive ?? "unknown"
1123
+ });
1124
+ const individualName = resolver.resolveParamName(node, param);
1125
+ const groupLocation = param.in === "path" || param.in === "query" || param.in === "header" ? param.in : void 0;
1126
+ const groupResolvers = {
1127
+ path: resolver.resolvePathParamsName,
1128
+ query: resolver.resolveQueryParamsName,
1129
+ header: resolver.resolveHeaderParamsName
1130
+ };
1131
+ const groupName = groupLocation ? groupResolvers[groupLocation].call(resolver, node, param) : void 0;
1132
+ if (groupName && groupName !== individualName) return createTypeNode({
1133
+ variant: "member",
1134
+ base: groupName,
1135
+ key: param.name
1136
+ });
1137
+ return createTypeNode({
1138
+ variant: "reference",
1139
+ name: individualName
1140
+ });
1141
+ }
1142
+ /**
1143
+ * Converts an {@link OperationNode} into a {@link FunctionParametersNode}.
1144
+ *
1145
+ * Centralizes the per-plugin `getParams()` pattern. Provide a `resolver` for
1146
+ * type resolution and `extraParams` for plugin-specific trailing parameters.
1147
+ *
1148
+ * @example
1149
+ * ```ts
1150
+ * const params = createOperationParams(node, {
1151
+ * paramsType: 'inline',
1152
+ * pathParamsType: 'inline',
1153
+ * resolver: tsResolver,
1154
+ * extraParams: [createFunctionParameter({ name: 'options', type: createTypeNode({ variant: 'reference', name: 'Partial<RequestOptions>' }), default: '{}' })],
1155
+ * })
1156
+ * ```
1157
+ */
1158
+ function createOperationParams(node, options) {
1159
+ const { paramsType, pathParamsType, paramsCasing, resolver, pathParamsDefault, extraParams = [], paramNames, typeWrapper } = options;
1160
+ const dataName = paramNames?.data ?? "data";
1161
+ const paramsName = paramNames?.params ?? "params";
1162
+ const headersName = paramNames?.headers ?? "headers";
1163
+ const pathName = paramNames?.path ?? "pathParams";
1164
+ const wrapType = (type) => createTypeNode({
1165
+ variant: "reference",
1166
+ name: typeWrapper ? typeWrapper(type) : type
1167
+ });
1168
+ const wrapTypeNode = (type) => type.variant === "reference" ? wrapType(type.name) : type;
1169
+ const casedParams = caseParams(node.parameters, paramsCasing);
1170
+ const pathParams = casedParams.filter((p) => p.in === "path");
1171
+ const queryParams = casedParams.filter((p) => p.in === "query");
1172
+ const headerParams = casedParams.filter((p) => p.in === "header");
1173
+ const bodyType = node.requestBody?.schema ? wrapType(resolver?.resolveDataName(node) ?? "unknown") : void 0;
1174
+ const bodyRequired = node.requestBody?.required ?? false;
1175
+ const queryGroupType = resolver ? resolveGroupType({
1176
+ node,
1177
+ params: queryParams,
1178
+ groupMethod: resolver.resolveQueryParamsName,
1179
+ resolver
1180
+ }) : void 0;
1181
+ const headerGroupType = resolver ? resolveGroupType({
1182
+ node,
1183
+ params: headerParams,
1184
+ groupMethod: resolver.resolveHeaderParamsName,
1185
+ resolver
1186
+ }) : void 0;
1187
+ const params = [];
1188
+ if (paramsType === "object") {
1189
+ const children = [
1190
+ ...pathParams.map((p) => {
1191
+ const type = resolveType({
1192
+ node,
1193
+ param: p,
1194
+ resolver
1195
+ });
1196
+ return createFunctionParameter({
1197
+ name: p.name,
1198
+ type: wrapTypeNode(type),
1199
+ optional: !p.required
1200
+ });
1201
+ }),
1202
+ ...bodyType ? [createFunctionParameter({
1203
+ name: dataName,
1204
+ type: bodyType,
1205
+ optional: !bodyRequired
1206
+ })] : [],
1207
+ ...buildGroupParam({
1208
+ name: paramsName,
1209
+ node,
1210
+ params: queryParams,
1211
+ groupType: queryGroupType,
1212
+ resolver,
1213
+ wrapType
1214
+ }),
1215
+ ...buildGroupParam({
1216
+ name: headersName,
1217
+ node,
1218
+ params: headerParams,
1219
+ groupType: headerGroupType,
1220
+ resolver,
1221
+ wrapType
1222
+ })
1223
+ ];
1224
+ if (children.length) params.push(createParameterGroup({
1225
+ properties: children,
1226
+ default: children.every((c) => c.optional) ? "{}" : void 0
1227
+ }));
1228
+ } else {
1229
+ if (pathParams.length) if (pathParamsType === "inlineSpread") {
1230
+ const spreadType = resolver?.resolvePathParamsName(node, pathParams[0]) ?? void 0;
1231
+ params.push(createFunctionParameter({
1232
+ name: pathName,
1233
+ type: spreadType ? wrapType(spreadType) : void 0,
1234
+ rest: true
1235
+ }));
1236
+ } else {
1237
+ const pathChildren = pathParams.map((p) => {
1238
+ const type = resolveType({
1239
+ node,
1240
+ param: p,
1241
+ resolver
1242
+ });
1243
+ return createFunctionParameter({
1244
+ name: p.name,
1245
+ type: wrapTypeNode(type),
1246
+ optional: !p.required
1247
+ });
1248
+ });
1249
+ params.push(createParameterGroup({
1250
+ properties: pathChildren,
1251
+ inline: pathParamsType === "inline",
1252
+ default: pathParamsDefault ?? (pathChildren.every((c) => c.optional) ? "{}" : void 0)
1253
+ }));
1298
1254
  }
1299
- } });
1255
+ if (bodyType) params.push(createFunctionParameter({
1256
+ name: dataName,
1257
+ type: bodyType,
1258
+ optional: !bodyRequired
1259
+ }));
1260
+ params.push(...buildGroupParam({
1261
+ name: paramsName,
1262
+ node,
1263
+ params: queryParams,
1264
+ groupType: queryGroupType,
1265
+ resolver,
1266
+ wrapType
1267
+ }));
1268
+ params.push(...buildGroupParam({
1269
+ name: headersName,
1270
+ node,
1271
+ params: headerParams,
1272
+ groupType: headerGroupType,
1273
+ resolver,
1274
+ wrapType
1275
+ }));
1276
+ }
1277
+ params.push(...extraParams);
1278
+ return createFunctionParameters({ params });
1279
+ }
1280
+ /**
1281
+ * Builds a single {@link FunctionParameterNode} for a query or header group.
1282
+ * Returns an empty array when there are no params to emit.
1283
+ *
1284
+ * If a pre-resolved `groupType` is provided it emits `name: GroupType`.
1285
+ * Otherwise, it builds an inline struct from the individual params.
1286
+ */
1287
+ function buildGroupParam({ name, node, params, groupType, resolver, wrapType }) {
1288
+ if (groupType) return [createFunctionParameter({
1289
+ name,
1290
+ type: groupType.type.variant === "reference" ? wrapType(groupType.type.name) : groupType.type,
1291
+ optional: groupType.optional
1292
+ })];
1293
+ if (params.length) return [createFunctionParameter({
1294
+ name,
1295
+ type: toStructType({
1296
+ node,
1297
+ params,
1298
+ resolver
1299
+ }),
1300
+ optional: params.every((p) => !p.required)
1301
+ })];
1302
+ return [];
1303
+ }
1304
+ /**
1305
+ * Derives a {@link ParamGroupType} from the resolver's group method.
1306
+ * Returns `undefined` when the group name equals the individual param name (no real group).
1307
+ */
1308
+ function resolveGroupType({ node, params, groupMethod, resolver }) {
1309
+ if (!params.length) return;
1310
+ const firstParam = params[0];
1311
+ const groupName = groupMethod.call(resolver, node, firstParam);
1312
+ if (groupName === resolver.resolveParamName(node, firstParam)) return;
1313
+ const allOptional = params.every((p) => !p.required);
1314
+ return {
1315
+ type: createTypeNode({
1316
+ variant: "reference",
1317
+ name: groupName
1318
+ }),
1319
+ optional: allOptional
1320
+ };
1321
+ }
1322
+ /**
1323
+ * Builds a {@link TypeNode} with `variant: 'struct'` for an inline anonymous type grouping named fields.
1324
+ *
1325
+ * Used when query or header parameters have no dedicated group type name.
1326
+ * Each language printer renders this appropriately (TypeScript: `{ petId: string; name?: string }`).
1327
+ */
1328
+ function toStructType({ node, params, resolver }) {
1329
+ return createTypeNode({
1330
+ variant: "struct",
1331
+ properties: params.map((p) => ({
1332
+ name: p.name,
1333
+ optional: !p.required,
1334
+ type: resolveType({
1335
+ node,
1336
+ param: p,
1337
+ resolver
1338
+ })
1339
+ }))
1340
+ });
1300
1341
  }
1301
1342
  //#endregion
1302
- exports.SCALAR_PRIMITIVE_TYPES = SCALAR_PRIMITIVE_TYPES;
1303
- exports.buildRefMap = buildRefMap;
1304
1343
  exports.caseParams = caseParams;
1305
1344
  exports.childName = childName;
1306
1345
  exports.collect = collect;
@@ -1309,37 +1348,28 @@ exports.composeTransformers = composeTransformers;
1309
1348
  exports.createDiscriminantNode = createDiscriminantNode;
1310
1349
  exports.createFunctionParameter = createFunctionParameter;
1311
1350
  exports.createFunctionParameters = createFunctionParameters;
1312
- exports.createObjectBindingParameter = createObjectBindingParameter;
1313
1351
  exports.createOperation = createOperation;
1352
+ exports.createOperationParams = createOperationParams;
1314
1353
  exports.createParameter = createParameter;
1354
+ exports.createParameterGroup = createParameterGroup;
1355
+ exports.createPrinterFactory = createPrinterFactory;
1315
1356
  exports.createProperty = createProperty;
1316
1357
  exports.createResponse = createResponse;
1317
1358
  exports.createRoot = createRoot;
1318
1359
  exports.createSchema = createSchema;
1319
- exports.defineFunctionPrinter = defineFunctionPrinter;
1360
+ exports.createTypeNode = createTypeNode;
1320
1361
  exports.definePrinter = definePrinter;
1321
1362
  exports.enumPropName = enumPropName;
1322
1363
  exports.extractRefName = extractRefName;
1323
1364
  exports.findDiscriminator = findDiscriminator;
1324
- exports.functionPrinter = functionPrinter;
1325
1365
  exports.httpMethods = httpMethods;
1326
- exports.isFunctionParameterNode = isFunctionParameterNode;
1327
- exports.isFunctionParametersNode = isFunctionParametersNode;
1328
- exports.isObjectBindingParameterNode = isObjectBindingParameterNode;
1329
1366
  exports.isOperationNode = isOperationNode;
1330
- exports.isParameterNode = isParameterNode;
1331
- exports.isPropertyNode = isPropertyNode;
1332
- exports.isResponseNode = isResponseNode;
1333
- exports.isRootNode = isRootNode;
1367
+ exports.isScalarPrimitive = isScalarPrimitive;
1334
1368
  exports.isSchemaNode = isSchemaNode;
1335
1369
  exports.isStringType = isStringType;
1336
1370
  exports.mediaTypes = mediaTypes;
1337
1371
  exports.mergeAdjacentObjects = mergeAdjacentObjects;
1338
1372
  exports.narrowSchema = narrowSchema;
1339
- exports.nodeKinds = nodeKinds;
1340
- exports.refMapToObject = refMapToObject;
1341
- exports.resolveNames = resolveNames;
1342
- exports.resolveRef = resolveRef;
1343
1373
  exports.schemaTypes = schemaTypes;
1344
1374
  exports.setDiscriminatorEnum = setDiscriminatorEnum;
1345
1375
  exports.setEnumName = setEnumName;