@kubb/ast 5.0.0-alpha.21 → 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.js CHANGED
@@ -4,17 +4,6 @@ const visitorDepths = {
4
4
  shallow: "shallow",
5
5
  deep: "deep"
6
6
  };
7
- const nodeKinds = {
8
- root: "Root",
9
- operation: "Operation",
10
- schema: "Schema",
11
- property: "Property",
12
- parameter: "Parameter",
13
- response: "Response",
14
- functionParameter: "FunctionParameter",
15
- objectBindingParameter: "ObjectBindingParameter",
16
- functionParameters: "FunctionParameters"
17
- };
18
7
  /**
19
8
  * Canonical schema type strings used by AST schema nodes.
20
9
  *
@@ -62,6 +51,12 @@ const SCALAR_PRIMITIVE_TYPES = new Set([
62
51
  "bigint",
63
52
  "boolean"
64
53
  ]);
54
+ /**
55
+ * Returns `true` when `type` is a scalar primitive schema type.
56
+ */
57
+ function isScalarPrimitive(type) {
58
+ return SCALAR_PRIMITIVE_TYPES.has(type);
59
+ }
65
60
  const httpMethods = {
66
61
  get: "GET",
67
62
  post: "POST",
@@ -94,218 +89,14 @@ const mediaTypes = {
94
89
  videoMp4: "video/mp4"
95
90
  };
96
91
  //#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
- }
92
+ //#region src/factory.ts
302
93
  /**
303
94
  * Syncs property/parameter schema optionality flags from `required` and `schema.nullable`.
304
95
  *
305
96
  * - `optional` is set for non-required, non-nullable schemas.
306
97
  * - `nullish` is set for non-required, nullable schemas.
307
98
  */
308
- function syncOptionality(required, schema) {
99
+ function syncOptionality(schema, required) {
309
100
  const nullable = schema.nullable ?? false;
310
101
  return {
311
102
  ...schema,
@@ -313,8 +104,6 @@ function syncOptionality(required, schema) {
313
104
  nullish: !required && nullable ? true : void 0
314
105
  };
315
106
  }
316
- //#endregion
317
- //#region src/factory.ts
318
107
  /**
319
108
  * Creates a `RootNode` with stable defaults for `schemas` and `operations`.
320
109
  *
@@ -412,7 +201,7 @@ function createProperty(props) {
412
201
  ...props,
413
202
  kind: "Property",
414
203
  required,
415
- schema: syncOptionality(required, props.schema)
204
+ schema: syncOptionality(props.schema, required)
416
205
  };
417
206
  }
418
207
  /**
@@ -447,7 +236,7 @@ function createParameter(props) {
447
236
  ...props,
448
237
  kind: "Parameter",
449
238
  required,
450
- schema: syncOptionality(required, props.schema)
239
+ schema: syncOptionality(props.schema, required)
451
240
  };
452
241
  }
453
242
  /**
@@ -469,49 +258,25 @@ function createResponse(props) {
469
258
  };
470
259
  }
471
260
  /**
472
- * Creates a single-property object schema used as a discriminator literal.
473
- *
474
- * @example
475
- * ```ts
476
- * createDiscriminantNode({ propertyName: 'type', value: 'dog' })
477
- * // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
478
- * ```
479
- */
480
- function createDiscriminantNode({ propertyName, value }) {
481
- return createSchema({
482
- type: "object",
483
- primitive: "object",
484
- properties: [createProperty({
485
- name: propertyName,
486
- schema: createSchema({
487
- type: "enum",
488
- primitive: "string",
489
- enumValues: [value]
490
- }),
491
- required: true
492
- })]
493
- });
494
- }
495
- /**
496
261
  * Creates a `FunctionParameterNode`.
497
262
  *
498
263
  * `optional` defaults to `false`.
499
264
  *
500
265
  * @example Required typed param
501
266
  * ```ts
502
- * createFunctionParameter({ name: 'petId', type: 'string' })
267
+ * createFunctionParameter({ name: 'petId', type: createTypeNode({ variant: 'reference', name: 'string' }) })
503
268
  * // → petId: string
504
269
  * ```
505
270
  *
506
271
  * @example Optional param
507
272
  * ```ts
508
- * createFunctionParameter({ name: 'params', type: 'QueryParams', optional: true })
273
+ * createFunctionParameter({ name: 'params', type: createTypeNode({ variant: 'reference', name: 'QueryParams' }), optional: true })
509
274
  * // → params?: QueryParams
510
275
  * ```
511
276
  *
512
277
  * @example Param with default (implicitly optional; cannot combine with `optional: true`)
513
278
  * ```ts
514
- * createFunctionParameter({ name: 'config', type: 'RequestConfig', default: '{}' })
279
+ * createFunctionParameter({ name: 'config', type: createTypeNode({ variant: 'reference', name: 'RequestConfig' }), default: '{}' })
515
280
  * // → config: RequestConfig = {}
516
281
  * ```
517
282
  */
@@ -523,14 +288,42 @@ function createFunctionParameter(props) {
523
288
  };
524
289
  }
525
290
  /**
526
- * Creates an `ObjectBindingParameterNode` for object-destructured parameter groups.
291
+ * Creates a {@link TypeNode} representing a language-agnostic structured type expression.
292
+ *
293
+ * Use `variant: 'struct'` for inline anonymous types and `variant: 'member'` for a single
294
+ * named field accessed from a group type. Each language's printer renders the variant
295
+ * into its own syntax (TypeScript, Python, C#, Kotlin, …).
296
+ *
297
+ * @example Reference type (TypeScript: `QueryParams`)
298
+ * ```ts
299
+ * createTypeNode({ variant: 'reference', name: 'QueryParams' })
300
+ * ```
301
+ *
302
+ * @example Struct type (TypeScript: `{ petId: string }`)
303
+ * ```ts
304
+ * createTypeNode({ variant: 'struct', properties: [{ name: 'petId', optional: false, type: createTypeNode({ variant: 'reference', name: 'string' }) }] })
305
+ * ```
306
+ *
307
+ * @example Member type (TypeScript: `DeletePetPathParams['petId']`)
308
+ * ```ts
309
+ * createTypeNode({ variant: 'member', base: 'DeletePetPathParams', key: 'petId' })
310
+ * ```
311
+ */
312
+ function createTypeNode(props) {
313
+ return {
314
+ ...props,
315
+ kind: "Type"
316
+ };
317
+ }
318
+ /**
319
+ * Creates a `ParameterGroupNode` representing a group of related parameters treated as a unit.
527
320
  *
528
- * @example Destructured object param
321
+ * @example Grouped param (TypeScript declaration)
529
322
  * ```ts
530
- * createObjectBindingParameter({
323
+ * createParameterGroup({
531
324
  * properties: [
532
- * createFunctionParameter({ name: 'id', type: 'string', optional: false }),
533
- * createFunctionParameter({ name: 'name', type: 'string', optional: true }),
325
+ * createFunctionParameter({ name: 'id', type: createTypeNode({ variant: 'reference', name: 'string' }), optional: false }),
326
+ * createFunctionParameter({ name: 'name', type: createTypeNode({ variant: 'reference', name: 'string' }), optional: true }),
534
327
  * ],
535
328
  * default: '{}',
536
329
  * })
@@ -538,20 +331,20 @@ function createFunctionParameter(props) {
538
331
  * // call → { id, name }
539
332
  * ```
540
333
  *
541
- * @example Inline mode — children emitted as individual top-level parameters
334
+ * @example Inline (spread) — children emitted as individual top-level parameters
542
335
  * ```ts
543
- * createObjectBindingParameter({
544
- * properties: [createFunctionParameter({ name: 'petId', type: 'string', optional: false })],
336
+ * createParameterGroup({
337
+ * properties: [createFunctionParameter({ name: 'petId', type: createTypeNode({ variant: 'reference', name: 'string' }), optional: false })],
545
338
  * inline: true,
546
339
  * })
547
340
  * // declaration → petId: string
548
341
  * // call → petId
549
342
  * ```
550
343
  */
551
- function createObjectBindingParameter(props) {
344
+ function createParameterGroup(props) {
552
345
  return {
553
346
  ...props,
554
- kind: "ObjectBindingParameter"
347
+ kind: "ParameterGroup"
555
348
  };
556
349
  }
557
350
  /**
@@ -561,8 +354,8 @@ function createObjectBindingParameter(props) {
561
354
  * ```ts
562
355
  * createFunctionParameters({
563
356
  * params: [
564
- * createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
565
- * createFunctionParameter({ name: 'config', type: 'RequestConfig', optional: false, default: '{}' }),
357
+ * createFunctionParameter({ name: 'petId', type: createTypeNode({ variant: 'reference', name: 'string' }), optional: false }),
358
+ * createFunctionParameter({ name: 'config', type: createTypeNode({ variant: 'reference', name: 'RequestConfig' }), optional: false, default: '{}' }),
566
359
  * ],
567
360
  * })
568
361
  * ```
@@ -581,7 +374,53 @@ function createFunctionParameters(props = {}) {
581
374
  };
582
375
  }
583
376
  //#endregion
584
- //#region src/printers/printer.ts
377
+ //#region src/guards.ts
378
+ /**
379
+ * Narrows a `SchemaNode` to the variant that matches `type`.
380
+ *
381
+ * @example
382
+ * ```ts
383
+ * const schema = createSchema({ type: 'string' })
384
+ * const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | undefined
385
+ * ```
386
+ */
387
+ function narrowSchema(node, type) {
388
+ return node?.type === type ? node : void 0;
389
+ }
390
+ function isKind(kind) {
391
+ return (node) => node.kind === kind;
392
+ }
393
+ isKind("Root");
394
+ /**
395
+ * Returns `true` when the input is an `OperationNode`.
396
+ *
397
+ * @example
398
+ * ```ts
399
+ * if (isOperationNode(node)) {
400
+ * console.log(node.operationId)
401
+ * }
402
+ * ```
403
+ */
404
+ const isOperationNode = isKind("Operation");
405
+ /**
406
+ * Returns `true` when the input is a `SchemaNode`.
407
+ *
408
+ * @example
409
+ * ```ts
410
+ * if (isSchemaNode(node)) {
411
+ * console.log(node.type)
412
+ * }
413
+ * ```
414
+ */
415
+ const isSchemaNode = isKind("Schema");
416
+ isKind("Property");
417
+ isKind("Parameter");
418
+ isKind("Response");
419
+ isKind("FunctionParameter");
420
+ isKind("ParameterGroup");
421
+ isKind("FunctionParameters");
422
+ //#endregion
423
+ //#region src/printer.ts
585
424
  /**
586
425
  * Creates a schema printer factory.
587
426
  *
@@ -651,123 +490,6 @@ function createPrinterFactory(getKey) {
651
490
  };
652
491
  }
653
492
  //#endregion
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(', ')
683
- * },
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
- * ],
717
- * })
718
- *
719
- * printer.print(sig) // → "petId: string, config: Config = {}"
720
- * ```
721
- */
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}`;
736
- }
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
- }));
770
- //#endregion
771
493
  //#region src/refs.ts
772
494
  /**
773
495
  * Returns the last path segment of a reference string.
@@ -782,43 +504,83 @@ const functionPrinter = defineFunctionPrinter((options) => ({
782
504
  function extractRefName(ref) {
783
505
  return ref.split("/").at(-1) ?? ref;
784
506
  }
507
+ //#endregion
508
+ //#region ../../internals/utils/src/casing.ts
785
509
  /**
786
- * Builds a `RefMap` from `root.schemas` using each schema's `name`.
510
+ * Shared implementation for camelCase and PascalCase conversion.
511
+ * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
512
+ * and capitalizes each word according to `pascal`.
787
513
  *
788
- * Unnamed schemas are skipped.
514
+ * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
515
+ */
516
+ function toCamelOrPascal(text, pascal) {
517
+ 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) => {
518
+ if (word.length > 1 && word === word.toUpperCase()) return word;
519
+ if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
520
+ return word.charAt(0).toUpperCase() + word.slice(1);
521
+ }).join("").replace(/[^a-zA-Z0-9]/g, "");
522
+ }
523
+ /**
524
+ * Splits `text` on `.` and applies `transformPart` to each segment.
525
+ * The last segment receives `isLast = true`, all earlier segments receive `false`.
526
+ * Segments are joined with `/` to form a file path.
527
+ *
528
+ * Only splits on dots followed by a letter so that version numbers
529
+ * embedded in operationIds (e.g. `v2025.0`) are kept intact.
530
+ */
531
+ function applyToFileParts(text, transformPart) {
532
+ const parts = text.split(/\.(?=[a-zA-Z])/);
533
+ return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
534
+ }
535
+ /**
536
+ * Converts `text` to camelCase.
537
+ * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
789
538
  *
790
539
  * @example
791
- * ```ts
792
- * const refMap = buildRefMap(root)
793
- * const pet = refMap.get('Pet')
794
- * ```
540
+ * camelCase('hello-world') // 'helloWorld'
541
+ * camelCase('pet.petId', { isFile: true }) // 'pet/petId'
795
542
  */
796
- function buildRefMap(root) {
797
- const map = /* @__PURE__ */ new Map();
798
- for (const schema of root.schemas) if (schema.name) map.set(schema.name, schema);
799
- return map;
543
+ function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
544
+ if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
545
+ prefix,
546
+ suffix
547
+ } : {}));
548
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
800
549
  }
801
550
  /**
802
- * Resolves a schema by name from a `RefMap`.
551
+ * Converts `text` to PascalCase.
552
+ * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
803
553
  *
804
554
  * @example
805
- * ```ts
806
- * const petSchema = resolveRef(refMap, 'Pet')
807
- * ```
555
+ * pascalCase('hello-world') // 'HelloWorld'
556
+ * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
808
557
  */
809
- function resolveRef(refMap, ref) {
810
- return refMap.get(ref);
558
+ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
559
+ if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
560
+ prefix,
561
+ suffix
562
+ }) : camelCase(part));
563
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
811
564
  }
565
+ //#endregion
566
+ //#region ../../internals/utils/src/reserved.ts
812
567
  /**
813
- * Converts a `RefMap` into a plain object.
568
+ * Returns `true` when `name` is a syntactically valid JavaScript variable name.
814
569
  *
815
570
  * @example
816
571
  * ```ts
817
- * const refsObject = refMapToObject(refMap)
572
+ * isValidVarName('status') // true
573
+ * isValidVarName('class') // false (reserved word)
574
+ * isValidVarName('42foo') // false (starts with digit)
818
575
  * ```
819
576
  */
820
- function refMapToObject(refMap) {
821
- return Object.fromEntries(refMap);
577
+ function isValidVarName(name) {
578
+ try {
579
+ new Function(`var ${name}`);
580
+ } catch {
581
+ return false;
582
+ }
583
+ return true;
822
584
  }
823
585
  //#endregion
824
586
  //#region src/visitor.ts
@@ -893,8 +655,9 @@ function getChildren(node, recurse) {
893
655
  case "Parameter": return [node.schema];
894
656
  case "Response": return node.schema ? [node.schema] : [];
895
657
  case "FunctionParameter":
896
- case "ObjectBindingParameter":
897
- case "FunctionParameters": return [];
658
+ case "ParameterGroup":
659
+ case "FunctionParameters":
660
+ case "Type": return [];
898
661
  }
899
662
  }
900
663
  /**
@@ -941,7 +704,7 @@ async function _walk(node, visitor, recurse, limit, parent) {
941
704
  await limit(() => visitor.response?.(node, { parent }));
942
705
  break;
943
706
  case "FunctionParameter":
944
- case "ObjectBindingParameter":
707
+ case "ParameterGroup":
945
708
  case "FunctionParameters": break;
946
709
  }
947
710
  const children = getChildren(node, recurse);
@@ -1043,8 +806,9 @@ function transform(node, options) {
1043
806
  };
1044
807
  }
1045
808
  case "FunctionParameter":
1046
- case "ObjectBindingParameter":
1047
- case "FunctionParameters": return node;
809
+ case "ParameterGroup":
810
+ case "FunctionParameters":
811
+ case "Type": return node;
1048
812
  }
1049
813
  }
1050
814
  /**
@@ -1128,7 +892,7 @@ function collect(node, options) {
1128
892
  v = visitor.response?.(node, { parent });
1129
893
  break;
1130
894
  case "FunctionParameter":
1131
- case "ObjectBindingParameter":
895
+ case "ParameterGroup":
1132
896
  case "FunctionParameters": break;
1133
897
  }
1134
898
  if (v !== void 0) results.push(v);
@@ -1248,7 +1012,7 @@ function mergeAdjacentObjects(members) {
1248
1012
  * ```
1249
1013
  */
1250
1014
  function simplifyUnion(members) {
1251
- const scalarPrimitives = new Set(members.filter((member) => SCALAR_PRIMITIVE_TYPES.has(member.type)).map((m) => m.type));
1015
+ const scalarPrimitives = new Set(members.filter((member) => isScalarPrimitive(member.type)).map((m) => m.type));
1252
1016
  if (!scalarPrimitives.size) return members;
1253
1017
  return members.filter((member) => {
1254
1018
  const enumNode = narrowSchema(member, "enum");
@@ -1273,31 +1037,308 @@ function setEnumName(propNode, parentName, propName, enumSuffix) {
1273
1037
  };
1274
1038
  return propNode;
1275
1039
  }
1040
+ //#endregion
1041
+ //#region src/utils.ts
1042
+ const plainStringTypes = new Set([
1043
+ "string",
1044
+ "uuid",
1045
+ "email",
1046
+ "url",
1047
+ "datetime"
1048
+ ]);
1276
1049
  /**
1277
- * Walks a schema tree and resolves `ref`/`enum` names through callbacks.
1050
+ * Returns `true` when a schema is emitted as a plain `string` type.
1051
+ *
1052
+ * - `string`, `uuid`, `email`, `url`, `datetime` are always plain strings.
1053
+ * - `date` and `time` are plain strings when their `representation` is `'string'` rather than `'date'`.
1054
+ *
1055
+ * @example
1056
+ * ```ts
1057
+ * isStringType(createSchema({ type: 'uuid' })) // true
1058
+ * isStringType(createSchema({ type: 'date', representation: 'date' })) // false
1059
+ * ```
1278
1060
  */
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
- };
1061
+ function isStringType(node) {
1062
+ if (plainStringTypes.has(node.type)) return true;
1063
+ const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
1064
+ if (temporal) return temporal.representation !== "date";
1065
+ return false;
1066
+ }
1067
+ /**
1068
+ * Applies casing rules to parameter names and returns a new parameter array.
1069
+ *
1070
+ * The input array is not mutated.
1071
+ * If `casing` is not set, the original array is returned unchanged.
1072
+ *
1073
+ * Use this before passing parameters to schema builders so that property keys
1074
+ * in generated output match the desired casing while preserving
1075
+ * `OperationNode.parameters` for other consumers.
1076
+ *
1077
+ * @example
1078
+ * ```ts
1079
+ * const params = [createParameter({ name: 'pet_id', in: 'query', schema: createSchema({ type: 'string' }) })]
1080
+ * const cased = caseParams(params, 'camelcase')
1081
+ * // cased[0].name === 'petId'
1082
+ * ```
1083
+ */
1084
+ function caseParams(params, casing) {
1085
+ if (!casing) return params;
1086
+ return params.map((param) => {
1087
+ const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
1088
+ return {
1089
+ ...param,
1090
+ name: transformed
1091
+ };
1092
+ });
1093
+ }
1094
+ /**
1095
+ * Creates a single-property object schema used as a discriminator literal.
1096
+ *
1097
+ * @example
1098
+ * ```ts
1099
+ * createDiscriminantNode({ propertyName: 'type', value: 'dog' })
1100
+ * // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
1101
+ * ```
1102
+ */
1103
+ function createDiscriminantNode({ propertyName, value }) {
1104
+ return createSchema({
1105
+ type: "object",
1106
+ primitive: "object",
1107
+ properties: [createProperty({
1108
+ name: propertyName,
1109
+ schema: createSchema({
1110
+ type: "enum",
1111
+ primitive: "string",
1112
+ enumValues: [value]
1113
+ }),
1114
+ required: true
1115
+ })]
1116
+ });
1117
+ }
1118
+ function resolveType({ node, param, resolver }) {
1119
+ if (!resolver) return createTypeNode({
1120
+ variant: "reference",
1121
+ name: param.schema.primitive ?? "unknown"
1122
+ });
1123
+ const individualName = resolver.resolveParamName(node, param);
1124
+ const groupLocation = param.in === "path" || param.in === "query" || param.in === "header" ? param.in : void 0;
1125
+ const groupResolvers = {
1126
+ path: resolver.resolvePathParamsName,
1127
+ query: resolver.resolveQueryParamsName,
1128
+ header: resolver.resolveHeaderParamsName
1129
+ };
1130
+ const groupName = groupLocation ? groupResolvers[groupLocation].call(resolver, node, param) : void 0;
1131
+ if (groupName && groupName !== individualName) return createTypeNode({
1132
+ variant: "member",
1133
+ base: groupName,
1134
+ key: param.name
1135
+ });
1136
+ return createTypeNode({
1137
+ variant: "reference",
1138
+ name: individualName
1139
+ });
1140
+ }
1141
+ /**
1142
+ * Converts an {@link OperationNode} into a {@link FunctionParametersNode}.
1143
+ *
1144
+ * Centralizes the per-plugin `getParams()` pattern. Provide a `resolver` for
1145
+ * type resolution and `extraParams` for plugin-specific trailing parameters.
1146
+ *
1147
+ * @example
1148
+ * ```ts
1149
+ * const params = createOperationParams(node, {
1150
+ * paramsType: 'inline',
1151
+ * pathParamsType: 'inline',
1152
+ * resolver: tsResolver,
1153
+ * extraParams: [createFunctionParameter({ name: 'options', type: createTypeNode({ variant: 'reference', name: 'Partial<RequestOptions>' }), default: '{}' })],
1154
+ * })
1155
+ * ```
1156
+ */
1157
+ function createOperationParams(node, options) {
1158
+ const { paramsType, pathParamsType, paramsCasing, resolver, pathParamsDefault, extraParams = [], paramNames, typeWrapper } = options;
1159
+ const dataName = paramNames?.data ?? "data";
1160
+ const paramsName = paramNames?.params ?? "params";
1161
+ const headersName = paramNames?.headers ?? "headers";
1162
+ const pathName = paramNames?.path ?? "pathParams";
1163
+ const wrapType = (type) => createTypeNode({
1164
+ variant: "reference",
1165
+ name: typeWrapper ? typeWrapper(type) : type
1166
+ });
1167
+ const wrapTypeNode = (type) => type.variant === "reference" ? wrapType(type.name) : type;
1168
+ const casedParams = caseParams(node.parameters, paramsCasing);
1169
+ const pathParams = casedParams.filter((p) => p.in === "path");
1170
+ const queryParams = casedParams.filter((p) => p.in === "query");
1171
+ const headerParams = casedParams.filter((p) => p.in === "header");
1172
+ const bodyType = node.requestBody?.schema ? wrapType(resolver?.resolveDataName(node) ?? "unknown") : void 0;
1173
+ const bodyRequired = node.requestBody?.required ?? false;
1174
+ const queryGroupType = resolver ? resolveGroupType({
1175
+ node,
1176
+ params: queryParams,
1177
+ groupMethod: resolver.resolveQueryParamsName,
1178
+ resolver
1179
+ }) : void 0;
1180
+ const headerGroupType = resolver ? resolveGroupType({
1181
+ node,
1182
+ params: headerParams,
1183
+ groupMethod: resolver.resolveHeaderParamsName,
1184
+ resolver
1185
+ }) : void 0;
1186
+ const params = [];
1187
+ if (paramsType === "object") {
1188
+ const children = [
1189
+ ...pathParams.map((p) => {
1190
+ const type = resolveType({
1191
+ node,
1192
+ param: p,
1193
+ resolver
1194
+ });
1195
+ return createFunctionParameter({
1196
+ name: p.name,
1197
+ type: wrapTypeNode(type),
1198
+ optional: !p.required
1199
+ });
1200
+ }),
1201
+ ...bodyType ? [createFunctionParameter({
1202
+ name: dataName,
1203
+ type: bodyType,
1204
+ optional: !bodyRequired
1205
+ })] : [],
1206
+ ...buildGroupParam({
1207
+ name: paramsName,
1208
+ node,
1209
+ params: queryParams,
1210
+ groupType: queryGroupType,
1211
+ resolver,
1212
+ wrapType
1213
+ }),
1214
+ ...buildGroupParam({
1215
+ name: headersName,
1216
+ node,
1217
+ params: headerParams,
1218
+ groupType: headerGroupType,
1219
+ resolver,
1220
+ wrapType
1221
+ })
1222
+ ];
1223
+ if (children.length) params.push(createParameterGroup({
1224
+ properties: children,
1225
+ default: children.every((c) => c.optional) ? "{}" : void 0
1226
+ }));
1227
+ } else {
1228
+ if (pathParams.length) if (pathParamsType === "inlineSpread") {
1229
+ const spreadType = resolver?.resolvePathParamsName(node, pathParams[0]) ?? void 0;
1230
+ params.push(createFunctionParameter({
1231
+ name: pathName,
1232
+ type: spreadType ? wrapType(spreadType) : void 0,
1233
+ rest: true
1234
+ }));
1235
+ } else {
1236
+ const pathChildren = pathParams.map((p) => {
1237
+ const type = resolveType({
1238
+ node,
1239
+ param: p,
1240
+ resolver
1241
+ });
1242
+ return createFunctionParameter({
1243
+ name: p.name,
1244
+ type: wrapTypeNode(type),
1245
+ optional: !p.required
1246
+ });
1247
+ });
1248
+ params.push(createParameterGroup({
1249
+ properties: pathChildren,
1250
+ inline: pathParamsType === "inline",
1251
+ default: pathParamsDefault ?? (pathChildren.every((c) => c.optional) ? "{}" : void 0)
1252
+ }));
1297
1253
  }
1298
- } });
1254
+ if (bodyType) params.push(createFunctionParameter({
1255
+ name: dataName,
1256
+ type: bodyType,
1257
+ optional: !bodyRequired
1258
+ }));
1259
+ params.push(...buildGroupParam({
1260
+ name: paramsName,
1261
+ node,
1262
+ params: queryParams,
1263
+ groupType: queryGroupType,
1264
+ resolver,
1265
+ wrapType
1266
+ }));
1267
+ params.push(...buildGroupParam({
1268
+ name: headersName,
1269
+ node,
1270
+ params: headerParams,
1271
+ groupType: headerGroupType,
1272
+ resolver,
1273
+ wrapType
1274
+ }));
1275
+ }
1276
+ params.push(...extraParams);
1277
+ return createFunctionParameters({ params });
1278
+ }
1279
+ /**
1280
+ * Builds a single {@link FunctionParameterNode} for a query or header group.
1281
+ * Returns an empty array when there are no params to emit.
1282
+ *
1283
+ * If a pre-resolved `groupType` is provided it emits `name: GroupType`.
1284
+ * Otherwise, it builds an inline struct from the individual params.
1285
+ */
1286
+ function buildGroupParam({ name, node, params, groupType, resolver, wrapType }) {
1287
+ if (groupType) return [createFunctionParameter({
1288
+ name,
1289
+ type: groupType.type.variant === "reference" ? wrapType(groupType.type.name) : groupType.type,
1290
+ optional: groupType.optional
1291
+ })];
1292
+ if (params.length) return [createFunctionParameter({
1293
+ name,
1294
+ type: toStructType({
1295
+ node,
1296
+ params,
1297
+ resolver
1298
+ }),
1299
+ optional: params.every((p) => !p.required)
1300
+ })];
1301
+ return [];
1302
+ }
1303
+ /**
1304
+ * Derives a {@link ParamGroupType} from the resolver's group method.
1305
+ * Returns `undefined` when the group name equals the individual param name (no real group).
1306
+ */
1307
+ function resolveGroupType({ node, params, groupMethod, resolver }) {
1308
+ if (!params.length) return;
1309
+ const firstParam = params[0];
1310
+ const groupName = groupMethod.call(resolver, node, firstParam);
1311
+ if (groupName === resolver.resolveParamName(node, firstParam)) return;
1312
+ const allOptional = params.every((p) => !p.required);
1313
+ return {
1314
+ type: createTypeNode({
1315
+ variant: "reference",
1316
+ name: groupName
1317
+ }),
1318
+ optional: allOptional
1319
+ };
1320
+ }
1321
+ /**
1322
+ * Builds a {@link TypeNode} with `variant: 'struct'` for an inline anonymous type grouping named fields.
1323
+ *
1324
+ * Used when query or header parameters have no dedicated group type name.
1325
+ * Each language printer renders this appropriately (TypeScript: `{ petId: string; name?: string }`).
1326
+ */
1327
+ function toStructType({ node, params, resolver }) {
1328
+ return createTypeNode({
1329
+ variant: "struct",
1330
+ properties: params.map((p) => ({
1331
+ name: p.name,
1332
+ optional: !p.required,
1333
+ type: resolveType({
1334
+ node,
1335
+ param: p,
1336
+ resolver
1337
+ })
1338
+ }))
1339
+ });
1299
1340
  }
1300
1341
  //#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 };
1342
+ export { caseParams, childName, collect, collectImports, composeTransformers, createDiscriminantNode, createFunctionParameter, createFunctionParameters, createOperation, createOperationParams, createParameter, createParameterGroup, createPrinterFactory, createProperty, createResponse, createRoot, createSchema, createTypeNode, definePrinter, enumPropName, extractRefName, findDiscriminator, httpMethods, isOperationNode, isScalarPrimitive, isSchemaNode, isStringType, mediaTypes, mergeAdjacentObjects, narrowSchema, schemaTypes, setDiscriminatorEnum, setEnumName, simplifyUnion, syncOptionality, transform, walk };
1302
1343
 
1303
1344
  //# sourceMappingURL=index.js.map