@kubb/plugin-ts 5.0.0-beta.10 → 5.0.0-beta.100

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
@@ -1,10 +1,9 @@
1
- import { t as __name } from "./chunk--u3MIqq1.js";
2
- import { safePrint } from "@kubb/parser-ts";
3
- import { File, jsxRenderer } from "@kubb/renderer-jsx";
4
- import { ast, defineGenerator, definePlugin, defineResolver } from "@kubb/core";
5
- import { isNumber } from "remeda";
1
+ import { n as __name, t as __exportAll } from "./rolldown-runtime-CNktS9qV.js";
2
+ import { parserTs } from "@kubb/parser-ts";
3
+ import { File, jsxRenderer } from "kubb/jsx";
4
+ import { Resolver, ast, createResolver, defineGenerator, definePlugin, isStringType, syncSchemaRef } from "kubb/kit";
6
5
  import ts from "typescript";
7
- import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
6
+ import { Fragment, jsx, jsxs } from "kubb/jsx/jsx-runtime";
8
7
  //#region ../../internals/utils/src/casing.ts
9
8
  /**
10
9
  * Shared implementation for camelCase and PascalCase conversion.
@@ -16,58 +15,41 @@ import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
16
15
  function toCamelOrPascal(text, pascal) {
17
16
  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) => {
18
17
  if (word.length > 1 && word === word.toUpperCase()) return word;
19
- if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
20
- return word.charAt(0).toUpperCase() + word.slice(1);
18
+ return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
21
19
  }).join("").replace(/[^a-zA-Z0-9]/g, "");
22
20
  }
23
21
  /**
24
- * Splits `text` on `.` and applies `transformPart` to each segment.
25
- * The last segment receives `isLast = true`, all earlier segments receive `false`.
26
- * Segments are joined with `/` to form a file path.
27
- *
28
- * Only splits on dots followed by a letter so that version numbers
29
- * embedded in operationIds (e.g. `v2025.0`) are kept intact.
30
- */
31
- function applyToFileParts(text, transformPart) {
32
- const parts = text.split(/\.(?=[a-zA-Z])/);
33
- return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
34
- }
35
- /**
36
22
  * Converts `text` to camelCase.
37
- * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
38
23
  *
39
- * @example
40
- * camelCase('hello-world') // 'helloWorld'
41
- * camelCase('pet.petId', { isFile: true }) // 'pet/petId'
24
+ * @example Word boundaries
25
+ * `camelCase('hello-world') // 'helloWorld'`
26
+ *
27
+ * @example With a prefix
28
+ * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
42
29
  */
43
- function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
44
- if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
45
- prefix,
46
- suffix
47
- } : {}));
30
+ function camelCase(text, { prefix = "", suffix = "" } = {}) {
48
31
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
49
32
  }
50
33
  /**
51
34
  * Converts `text` to PascalCase.
52
- * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
53
35
  *
54
- * @example
55
- * pascalCase('hello-world') // 'HelloWorld'
56
- * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
36
+ * @example Word boundaries
37
+ * `pascalCase('hello-world') // 'HelloWorld'`
38
+ *
39
+ * @example With a suffix
40
+ * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`
57
41
  */
58
- function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
59
- if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
60
- prefix,
61
- suffix
62
- }) : camelCase(part));
42
+ function pascalCase(text, { prefix = "", suffix = "" } = {}) {
63
43
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
64
44
  }
65
45
  /**
66
46
  * Converts `text` to snake_case.
67
47
  *
68
- * @example
69
- * snakeCase('helloWorld') // 'hello_world'
70
- * snakeCase('Hello-World') // 'hello_world'
48
+ * @example From camelCase
49
+ * `snakeCase('helloWorld') // 'hello_world'`
50
+ *
51
+ * @example From mixed separators
52
+ * `snakeCase('Hello-World') // 'hello_world'`
71
53
  */
72
54
  function snakeCase(text, { prefix = "", suffix = "" } = {}) {
73
55
  return `${prefix} ${text} ${suffix}`.trim().replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s\-.]+/g, "_").replace(/[^a-zA-Z0-9_]/g, "").toLowerCase().split("_").filter(Boolean).join("_");
@@ -75,8 +57,8 @@ function snakeCase(text, { prefix = "", suffix = "" } = {}) {
75
57
  /**
76
58
  * Converts `text` to SCREAMING_SNAKE_CASE.
77
59
  *
78
- * @example
79
- * screamingSnakeCase('helloWorld') // 'HELLO_WORLD'
60
+ * @example From camelCase
61
+ * `screamingSnakeCase('helloWorld') // 'HELLO_WORLD'`
80
62
  */
81
63
  function screamingSnakeCase(text, { prefix = "", suffix = "" } = {}) {
82
64
  return snakeCase(text, {
@@ -85,14 +67,155 @@ function screamingSnakeCase(text, { prefix = "", suffix = "" } = {}) {
85
67
  }).toUpperCase();
86
68
  }
87
69
  //#endregion
88
- //#region ../../internals/utils/src/string.ts
70
+ //#region ../../internals/utils/src/reserved.ts
71
+ /**
72
+ * JavaScript and Java reserved words.
73
+ * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
74
+ */
75
+ const reservedWords = /* @__PURE__ */ new Set([
76
+ "abstract",
77
+ "arguments",
78
+ "boolean",
79
+ "break",
80
+ "byte",
81
+ "case",
82
+ "catch",
83
+ "char",
84
+ "class",
85
+ "const",
86
+ "continue",
87
+ "debugger",
88
+ "default",
89
+ "delete",
90
+ "do",
91
+ "double",
92
+ "else",
93
+ "enum",
94
+ "eval",
95
+ "export",
96
+ "extends",
97
+ "false",
98
+ "final",
99
+ "finally",
100
+ "float",
101
+ "for",
102
+ "function",
103
+ "goto",
104
+ "if",
105
+ "implements",
106
+ "import",
107
+ "in",
108
+ "instanceof",
109
+ "int",
110
+ "interface",
111
+ "let",
112
+ "long",
113
+ "native",
114
+ "new",
115
+ "null",
116
+ "package",
117
+ "private",
118
+ "protected",
119
+ "public",
120
+ "return",
121
+ "short",
122
+ "static",
123
+ "super",
124
+ "switch",
125
+ "synchronized",
126
+ "this",
127
+ "throw",
128
+ "throws",
129
+ "transient",
130
+ "true",
131
+ "try",
132
+ "typeof",
133
+ "var",
134
+ "void",
135
+ "volatile",
136
+ "while",
137
+ "with",
138
+ "yield",
139
+ "Array",
140
+ "Date",
141
+ "hasOwnProperty",
142
+ "Infinity",
143
+ "isFinite",
144
+ "isNaN",
145
+ "isPrototypeOf",
146
+ "length",
147
+ "Math",
148
+ "name",
149
+ "NaN",
150
+ "Number",
151
+ "Object",
152
+ "prototype",
153
+ "String",
154
+ "toString",
155
+ "undefined",
156
+ "valueOf"
157
+ ]);
158
+ /**
159
+ * Returns `true` when `name` is a syntactically valid JavaScript variable name.
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * isValidVarName('status') // true
164
+ * isValidVarName('class') // false (reserved word)
165
+ * isValidVarName('42foo') // false (starts with digit)
166
+ * ```
167
+ */
168
+ function isValidVarName(name) {
169
+ if (!name || reservedWords.has(name)) return false;
170
+ return isIdentifier(name);
171
+ }
172
+ /**
173
+ * Returns `name` when it's a syntactically valid JavaScript variable name,
174
+ * otherwise prefixes it with `_` so the result is a valid identifier.
175
+ *
176
+ * Useful for sanitizing OpenAPI schema names or operation IDs that start with
177
+ * a digit (e.g. `409`, `504AccountCancel`) before using them as exported
178
+ * variable, type, or function names.
179
+ *
180
+ * @example
181
+ * ```ts
182
+ * ensureValidVarName('409') // '_409'
183
+ * ensureValidVarName('504AccountCancel') // '_504AccountCancel'
184
+ * ensureValidVarName('Pet') // 'Pet'
185
+ * ensureValidVarName('class') // '_class'
186
+ * ```
187
+ */
188
+ function ensureValidVarName(name) {
189
+ if (!name || isValidVarName(name)) return name;
190
+ return `_${name}`;
191
+ }
192
+ /**
193
+ * Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
194
+ *
195
+ * Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
196
+ * even though they are not valid variable names, so use this (not {@link isValidVarName}) when
197
+ * deciding whether an object key needs quoting.
198
+ *
199
+ * @example
200
+ * ```ts
201
+ * isIdentifier('name') // true
202
+ * isIdentifier('x-total')// false
203
+ * ```
204
+ */
205
+ function isIdentifier(name) {
206
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
207
+ }
208
+ //#endregion
209
+ //#region ../../internals/utils/src/strings.ts
89
210
  /**
90
211
  * Strips a single matching pair of `"..."`, `'...'`, or `` `...` `` from both ends of `text`.
91
212
  * Returns the string unchanged when no balanced quote pair is found.
92
213
  *
93
214
  * @example
215
+ * ```ts
94
216
  * trimQuotes('"hello"') // 'hello'
95
217
  * trimQuotes('hello') // 'hello'
218
+ * ```
96
219
  */
97
220
  function trimQuotes(text) {
98
221
  if (text.length >= 2) {
@@ -103,8 +226,24 @@ function trimQuotes(text) {
103
226
  return text;
104
227
  }
105
228
  /**
106
- * Escapes characters that are not allowed inside JS string literals.
107
- * Handles quotes, backslashes, and Unicode line terminators (U+2028 / U+2029).
229
+ * Serializes a primitive to a single-quoted string literal, stripping any surrounding quotes first.
230
+ *
231
+ * Escaping runs through `JSON.stringify`, then the result switches to single quotes so the generated
232
+ * code matches the repo style without a formatter.
233
+ *
234
+ * @example
235
+ * ```ts
236
+ * stringify('hello') // "'hello'"
237
+ * stringify('"hello"') // "'hello'"
238
+ * ```
239
+ */
240
+ function stringify(value) {
241
+ if (value === void 0 || value === null) return "''";
242
+ return `'${JSON.stringify(trimQuotes(value.toString())).slice(1, -1).replace(/\\"/g, "\"").replace(/'/g, "\\'")}'`;
243
+ }
244
+ /**
245
+ * Escapes characters that are not allowed inside JS string literals, covering quotes, backslashes,
246
+ * and the Unicode line terminators U+2028 and U+2029.
108
247
  *
109
248
  * @see http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4
110
249
  *
@@ -128,49 +267,59 @@ function jsStringEscape(input) {
128
267
  });
129
268
  }
130
269
  //#endregion
131
- //#region ../../internals/utils/src/object.ts
270
+ //#region ../../internals/utils/src/fs.ts
132
271
  /**
133
- * Serializes a primitive value to a JSON string literal, stripping any surrounding quote characters first.
272
+ * Builds a nested file path from a dotted name. Splits on dots that precede a letter
273
+ * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
274
+ * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
134
275
  *
135
- * @example
136
- * stringify('hello') // '"hello"'
137
- * stringify('"hello"') // '"hello"'
276
+ * Empty segments are dropped before joining. They arise when the name starts with a dot
277
+ * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
278
+ * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
279
+ * absolute path, letting generated files escape the configured output directory.
280
+ *
281
+ * @example Nested path from a dotted name
282
+ * `toFilePath('pet.petId') // 'pet/petId'`
283
+ *
284
+ * @example PascalCase the final segment
285
+ * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
286
+ *
287
+ * @example Suffix applied to the final segment only
288
+ * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
138
289
  */
139
- function stringify(value) {
140
- if (value === void 0 || value === null) return "\"\"";
141
- return JSON.stringify(trimQuotes(value.toString()));
290
+ function toFilePath(name, caseLast = camelCase) {
291
+ const parts = name.split(/\.(?=[a-zA-Z])/);
292
+ return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
142
293
  }
143
294
  //#endregion
144
295
  //#region src/constants.ts
145
296
  /**
146
297
  * `optionalType` values that cause a property's type to include `| undefined`.
147
298
  */
148
- const OPTIONAL_ADDS_UNDEFINED = new Set(["undefined", "questionTokenAndUndefined"]);
299
+ const OPTIONAL_ADDS_UNDEFINED = /* @__PURE__ */ new Set(["undefined", "questionTokenAndUndefined"]);
149
300
  /**
150
301
  * `optionalType` values that render the property key with a `?` token.
151
302
  */
152
- const OPTIONAL_ADDS_QUESTION_TOKEN = new Set(["questionToken", "questionTokenAndUndefined"]);
303
+ const OPTIONAL_ADDS_QUESTION_TOKEN = /* @__PURE__ */ new Set(["questionToken", "questionTokenAndUndefined"]);
153
304
  /**
154
- * `enumType` values that append a `Key` suffix to the generated enum type alias.
305
+ * `enum.type` values that append a `typeSuffix` to the generated enum type alias.
155
306
  */
156
- const ENUM_TYPES_WITH_KEY_SUFFIX = new Set(["asConst", "asPascalConst"]);
307
+ const ENUM_TYPES_WITH_KEY_SUFFIX = /* @__PURE__ */ new Set(["asConst"]);
157
308
  /**
158
- * `enumType` values that require a runtime value declaration (object, enum, or literal).
309
+ * `enum.type` values that require a runtime value declaration (object, enum, or literal).
159
310
  */
160
- const ENUM_TYPES_WITH_RUNTIME_VALUE = new Set([
311
+ const ENUM_TYPES_WITH_RUNTIME_VALUE = /* @__PURE__ */ new Set([
161
312
  "enum",
162
313
  "asConst",
163
- "asPascalConst",
164
314
  "constEnum",
165
315
  "literal",
166
316
  void 0
167
317
  ]);
168
318
  /**
169
- * `enumType` values whose type declaration is type-only (no runtime value emitted for the type alias).
319
+ * `enum.type` values whose type declaration is type-only (no runtime value emitted for the type alias).
170
320
  */
171
- const ENUM_TYPES_WITH_TYPE_ONLY = new Set([
321
+ const ENUM_TYPES_WITH_TYPE_ONLY = /* @__PURE__ */ new Set([
172
322
  "asConst",
173
- "asPascalConst",
174
323
  "literal",
175
324
  void 0
176
325
  ]);
@@ -185,8 +334,48 @@ const PARAM_RANK = {
185
334
  };
186
335
  //#endregion
187
336
  //#region src/factory.ts
337
+ var factory_exports = /* @__PURE__ */ __exportAll({
338
+ appendJSDocToNode: () => appendJSDocToNode,
339
+ buildIndexSignatures: () => buildIndexSignatures,
340
+ buildMemberNodes: () => buildMemberNodes,
341
+ buildPropertyType: () => buildPropertyType,
342
+ buildTupleNode: () => buildTupleNode,
343
+ constToTypeNode: () => constToTypeNode,
344
+ createArrayDeclaration: () => createArrayDeclaration,
345
+ createEnumDeclaration: () => createEnumDeclaration,
346
+ createExportDeclaration: () => createExportDeclaration,
347
+ createImportDeclaration: () => createImportDeclaration,
348
+ createIntersectionDeclaration: () => createIntersectionDeclaration,
349
+ createJSDoc: () => createJSDoc,
350
+ createOmitDeclaration: () => createOmitDeclaration,
351
+ createParameterSignature: () => createParameterSignature,
352
+ createPropertySignature: () => createPropertySignature,
353
+ createQuestionToken: () => createQuestionToken,
354
+ createTypeDeclaration: () => createTypeDeclaration,
355
+ createTypeLiteralNode: () => createTypeLiteralNode,
356
+ createTypeReferenceNode: () => createTypeReferenceNode,
357
+ createUnionDeclaration: () => createUnionDeclaration,
358
+ createUrlTemplateType: () => createUrlTemplateType,
359
+ dateOrStringNode: () => dateOrStringNode,
360
+ isNonNullable: () => isNonNullable$1,
361
+ keywordTypeNodes: () => keywordTypeNodes,
362
+ modifiers: () => modifiers,
363
+ syntaxKind: () => syntaxKind
364
+ });
188
365
  const { SyntaxKind, factory } = ts;
189
366
  /**
367
+ * Compares two strings by UTF-16 code unit, keeping sorted output identical across platforms
368
+ * regardless of locale.
369
+ */
370
+ function compareStrings(a, b) {
371
+ if (a < b) return -1;
372
+ if (a > b) return 1;
373
+ return 0;
374
+ }
375
+ function isNumber(value) {
376
+ return typeof value === "number" && !Number.isNaN(value);
377
+ }
378
+ /**
190
379
  * TypeScript AST modifiers for common keywords (async, export, const, static).
191
380
  */
192
381
  const modifiers = {
@@ -209,8 +398,13 @@ function isNonNullable$1(value) {
209
398
  __name(isNonNullable$1, "isNonNullable");
210
399
  function isValidIdentifier(str) {
211
400
  if (!str.length || str.trim() !== str) return false;
212
- const node = ts.parseIsolatedEntityName(str, ts.ScriptTarget.Latest);
213
- return !!node && node.kind === ts.SyntaxKind.Identifier && ts.identifierToKeywordKind(node.kind) === void 0;
401
+ let ch = str.codePointAt(0);
402
+ if (!ts.isIdentifierStart(ch, ts.ScriptTarget.Latest)) return false;
403
+ for (let i = ch > 65535 ? 2 : 1; i < str.length; i += ch > 65535 ? 2 : 1) {
404
+ ch = str.codePointAt(i);
405
+ if (!ts.isIdentifierPart(ch, ts.ScriptTarget.Latest)) return false;
406
+ }
407
+ return true;
214
408
  }
215
409
  function propertyName(name) {
216
410
  if (typeof name === "string") return isValidIdentifier(name) ? factory.createIdentifier(name) : factory.createStringLiteral(name);
@@ -285,6 +479,17 @@ function createParameterSignature(name, { modifiers, dotDotDotToken, questionTok
285
479
  return factory.createParameterDeclaration(modifiers, dotDotDotToken, name, createQuestionToken(questionToken), type, initializer);
286
480
  }
287
481
  /**
482
+ * Creates a JSDoc comment node from an array of comment strings.
483
+ * Returns null if no comments are provided.
484
+ */
485
+ function createJSDoc({ comments }) {
486
+ if (!comments.length) return null;
487
+ return factory.createJSDocComment(factory.createNodeArray(comments.map((comment, i) => {
488
+ if (i === comments.length - 1) return factory.createJSDocText(comment);
489
+ return factory.createJSDocText(`${comment}\n`);
490
+ })));
491
+ }
492
+ /**
288
493
  * Attaches JSDoc comments to an AST node as synthetic leading comments.
289
494
  * Filters out undefined comments before attaching.
290
495
  *
@@ -322,7 +527,7 @@ function createInterfaceDeclaration({ modifiers, name, typeParameters, members }
322
527
  * Intelligently selects the syntax based on the type structure and attaches JSDoc comments.
323
528
  */
324
529
  function createTypeDeclaration({ syntax, isExportable, comments, name, type }) {
325
- if (syntax === "interface" && "members" in type) return appendJSDocToNode({
530
+ if (syntax === "interface" && ts.isTypeLiteralNode(type)) return appendJSDocToNode({
326
531
  node: createInterfaceDeclaration({
327
532
  members: [...type.members],
328
533
  modifiers: isExportable ? [modifiers.export] : [],
@@ -342,6 +547,50 @@ function createTypeDeclaration({ syntax, isExportable, comments, name, type }) {
342
547
  });
343
548
  }
344
549
  /**
550
+ * Creates an import declaration with support for default imports, named imports, namespace imports, and type-only imports.
551
+ * Optionally rename imported members with `propertyName` and `name` pairs.
552
+ *
553
+ * @example Default import
554
+ * `import Pet from './Pet'`
555
+ *
556
+ * @example Named imports with rename
557
+ * `import { Pet as Cat } from './Pet'`
558
+ *
559
+ * @example Namespace import
560
+ * `import * as Pet from './Pet'`
561
+ */
562
+ function createImportDeclaration({ name, path, isTypeOnly = false, isNameSpace = false }) {
563
+ if (!Array.isArray(name)) {
564
+ const importPropertyName = isNameSpace ? void 0 : factory.createIdentifier(name);
565
+ const importName = isNameSpace ? factory.createNamespaceImport(factory.createIdentifier(name)) : void 0;
566
+ return factory.createImportDeclaration(void 0, factory.createImportClause(isTypeOnly, importPropertyName, importName), factory.createStringLiteral(path), void 0);
567
+ }
568
+ const sortedName = name.toSorted((a, b) => compareStrings(typeof a === "object" ? a.propertyName : a, typeof b === "object" ? b.propertyName : b));
569
+ return factory.createImportDeclaration(void 0, factory.createImportClause(isTypeOnly, void 0, factory.createNamedImports(sortedName.map((item) => {
570
+ if (typeof item === "object") {
571
+ const obj = item;
572
+ if (obj.name) return factory.createImportSpecifier(false, factory.createIdentifier(obj.propertyName), factory.createIdentifier(obj.name));
573
+ return factory.createImportSpecifier(false, void 0, factory.createIdentifier(obj.propertyName));
574
+ }
575
+ return factory.createImportSpecifier(false, void 0, factory.createIdentifier(item));
576
+ }))), factory.createStringLiteral(path), void 0);
577
+ }
578
+ /**
579
+ * Creates an export declaration with support for named exports, namespace exports, and type-only exports.
580
+ * Sorts export names alphabetically for consistent output across platforms.
581
+ */
582
+ function createExportDeclaration({ path, asAlias, isTypeOnly = false, name }) {
583
+ if (name && !Array.isArray(name) && !asAlias) console.warn(`When using name as string, asAlias should be true ${name}`);
584
+ if (!Array.isArray(name)) {
585
+ const parsedName = name?.match(/^\d/) ? `_${name?.slice(1)}` : name;
586
+ return factory.createExportDeclaration(void 0, isTypeOnly, asAlias && parsedName ? factory.createNamespaceExport(factory.createIdentifier(parsedName)) : void 0, factory.createStringLiteral(path), void 0);
587
+ }
588
+ const sortedName = name.toSorted((a, b) => compareStrings(typeof a === "string" ? a : a.text, typeof b === "string" ? b : b.text));
589
+ return factory.createExportDeclaration(void 0, isTypeOnly, factory.createNamedExports(sortedName.map((propertyName) => {
590
+ return factory.createExportSpecifier(false, void 0, typeof propertyName === "string" ? factory.createIdentifier(propertyName) : propertyName);
591
+ })), factory.createStringLiteral(path), void 0);
592
+ }
593
+ /**
345
594
  * Apply casing transformation to enum keys
346
595
  */
347
596
  function applyEnumKeyCasing(key, casing = "none") {
@@ -362,7 +611,7 @@ function applyEnumKeyCasing(key, casing = "none") {
362
611
  * type: 'enum',
363
612
  * name: 'petType',
364
613
  * typeName: 'PetType',
365
- * enums: [['cat', 'cat'], ['dog', 'dog']],
614
+ * enums: [['cat', 'cat', 'A cat'], ['dog', 'dog']],
366
615
  * })
367
616
  * ```
368
617
  */
@@ -373,32 +622,41 @@ function createEnumDeclaration({ type = "enum", name, typeName, enums, enumKeyCa
373
622
  return factory.createLiteralTypeNode(factory.createNumericLiteral(value?.toString()));
374
623
  }
375
624
  if (typeof value === "boolean") return factory.createLiteralTypeNode(value ? factory.createTrue() : factory.createFalse());
376
- if (value) return factory.createLiteralTypeNode(factory.createStringLiteral(value.toString()));
625
+ if (value !== null && value !== void 0) return factory.createLiteralTypeNode(factory.createStringLiteral(value.toString()));
377
626
  }).filter((node) => node !== void 0)))];
378
- if (type === "enum" || type === "constEnum") return [void 0, factory.createEnumDeclaration([factory.createToken(ts.SyntaxKind.ExportKeyword), type === "constEnum" ? factory.createToken(ts.SyntaxKind.ConstKeyword) : void 0].filter((modifier) => modifier !== void 0), factory.createIdentifier(typeName), enums.map(([key, value]) => {
627
+ if (type === "enum" || type === "constEnum") return [void 0, factory.createEnumDeclaration([factory.createToken(ts.SyntaxKind.ExportKeyword), type === "constEnum" ? factory.createToken(ts.SyntaxKind.ConstKeyword) : void 0].filter((modifier) => modifier !== void 0), factory.createIdentifier(typeName), enums.map(([key, value, description]) => {
379
628
  let initializer = factory.createStringLiteral(value?.toString());
380
- if (Number.parseInt(value.toString(), 10) === value && isNumber(Number.parseInt(value.toString(), 10))) if (value < 0) initializer = factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, factory.createNumericLiteral(Math.abs(value)));
629
+ if (Number.parseInt(value.toString(), 10) === value && isNumber(value)) if (value < 0) initializer = factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, factory.createNumericLiteral(Math.abs(value)));
381
630
  else initializer = factory.createNumericLiteral(value);
382
631
  if (typeof value === "boolean") initializer = value ? factory.createTrue() : factory.createFalse();
383
632
  if (isNumber(Number.parseInt(key.toString(), 10))) {
384
633
  const casingKey = applyEnumKeyCasing(`${typeName}_${key}`, enumKeyCasing);
385
- return factory.createEnumMember(propertyName(casingKey), initializer);
634
+ return appendJSDocToNode({
635
+ node: factory.createEnumMember(propertyName(casingKey), initializer),
636
+ comments: [description]
637
+ });
386
638
  }
387
- if (key) {
639
+ if (key !== null && key !== void 0) {
388
640
  const casingKey = applyEnumKeyCasing(key.toString(), enumKeyCasing);
389
- return factory.createEnumMember(propertyName(casingKey), initializer);
641
+ return appendJSDocToNode({
642
+ node: factory.createEnumMember(propertyName(casingKey), initializer),
643
+ comments: [description]
644
+ });
390
645
  }
391
646
  }).filter((member) => member !== void 0))];
392
647
  const identifierName = name;
393
648
  if (enums.length === 0) return [void 0, factory.createTypeAliasDeclaration([factory.createToken(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier(typeName), void 0, factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword))];
394
- return [factory.createVariableStatement([factory.createToken(ts.SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList([factory.createVariableDeclaration(factory.createIdentifier(identifierName), void 0, void 0, factory.createAsExpression(factory.createObjectLiteralExpression(enums.map(([key, value]) => {
649
+ return [factory.createVariableStatement([factory.createToken(ts.SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList([factory.createVariableDeclaration(factory.createIdentifier(identifierName), void 0, void 0, factory.createAsExpression(factory.createObjectLiteralExpression(enums.map(([key, value, description]) => {
395
650
  let initializer = factory.createStringLiteral(value?.toString());
396
651
  if (isNumber(value)) if (value < 0) initializer = factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, factory.createNumericLiteral(Math.abs(value)));
397
652
  else initializer = factory.createNumericLiteral(value);
398
653
  if (typeof value === "boolean") initializer = value ? factory.createTrue() : factory.createFalse();
399
- if (key) {
654
+ if (key !== null && key !== void 0) {
400
655
  const casingKey = applyEnumKeyCasing(key.toString(), enumKeyCasing);
401
- return factory.createPropertyAssignment(propertyName(casingKey), initializer);
656
+ return appendJSDocToNode({
657
+ node: factory.createPropertyAssignment(propertyName(casingKey), initializer),
658
+ comments: [description]
659
+ });
402
660
  }
403
661
  }).filter((property) => property !== void 0), true), factory.createTypeReferenceNode(factory.createIdentifier("const"), void 0)))], ts.NodeFlags.Const)), factory.createTypeAliasDeclaration([factory.createToken(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier(typeName), void 0, factory.createIndexedAccessTypeNode(factory.createParenthesizedType(factory.createTypeQueryNode(factory.createIdentifier(identifierName), void 0)), factory.createTypeOperatorNode(ts.SyntaxKind.KeyOfKeyword, factory.createTypeQueryNode(factory.createIdentifier(identifierName), void 0))))];
404
662
  }
@@ -482,12 +740,10 @@ const createStringLiteral = factory.createStringLiteral;
482
740
  * Creates an array type node (e.g., `T[]`).
483
741
  */
484
742
  const createArrayTypeNode = factory.createArrayTypeNode;
485
- factory.createParenthesizedType;
486
743
  /**
487
744
  * Creates a literal type node (e.g., `'hello'`, `42`, `true`).
488
745
  */
489
746
  const createLiteralTypeNode = factory.createLiteralTypeNode;
490
- factory.createNull;
491
747
  /**
492
748
  * Creates an identifier node.
493
749
  */
@@ -512,8 +768,6 @@ const createTrue = factory.createTrue;
512
768
  * Creates a boolean false literal type node.
513
769
  */
514
770
  const createFalse = factory.createFalse;
515
- factory.createIndexedAccessTypeNode;
516
- factory.createTypeOperatorNode;
517
771
  /**
518
772
  * Creates a prefix unary expression (e.g., negative numbers, logical not).
519
773
  */
@@ -561,32 +815,38 @@ function buildTupleNode(node, print) {
561
815
  /**
562
816
  * Applies `nullable` and optional/nullish `| undefined` union modifiers to a property's resolved base type.
563
817
  */
564
- function buildPropertyType(schema, baseType, optionalType) {
818
+ function buildPropertyType(schema, baseType, optionalType, optional) {
565
819
  const addsUndefined = OPTIONAL_ADDS_UNDEFINED.has(optionalType);
566
- const meta = ast.syncSchemaRef(schema);
820
+ const meta = syncSchemaRef(schema);
567
821
  let type = baseType;
568
822
  if (meta.nullable) type = createUnionDeclaration({ nodes: [type, keywordTypeNodes.null] });
569
- if ((meta.nullish || meta.optional) && addsUndefined) type = createUnionDeclaration({ nodes: [type, keywordTypeNodes.undefined] });
823
+ if ((optional || meta.nullish || meta.optional) && addsUndefined) type = createUnionDeclaration({ nodes: [type, keywordTypeNodes.undefined] });
570
824
  return type;
571
825
  }
826
+ const indexSignaturePrinter = ts.createPrinter();
827
+ const indexSignatureSource = ts.createSourceFile("", "", ts.ScriptTarget.Latest);
572
828
  /**
573
- * Creates TypeScript index signatures for `additionalProperties` and `patternProperties` on an object schema node.
829
+ * Creates a TypeScript index signature for `additionalProperties` and `patternProperties` on an
830
+ * object schema node. TypeScript allows only one string index signature and its value must be
831
+ * assignable from every named property, so both keywords collapse into a single signature: the
832
+ * union of all value types (deduplicated), or `unknown` when the object also has fixed properties.
833
+ * The key regex of `patternProperties` cannot be expressed by an index signature and is dropped.
574
834
  */
575
835
  function buildIndexSignatures(node, propertyCount, print) {
576
- const elements = [];
577
- if (node.additionalProperties && node.additionalProperties !== true) {
578
- const additionalType = print(node.additionalProperties) ?? keywordTypeNodes.unknown;
579
- elements.push(createIndexSignature(propertyCount > 0 ? keywordTypeNodes.unknown : additionalType));
580
- } else if (node.additionalProperties === true) elements.push(createIndexSignature(keywordTypeNodes.unknown));
581
- if (node.patternProperties) {
582
- const first = Object.values(node.patternProperties)[0];
583
- if (first) {
584
- let patternType = print(first) ?? keywordTypeNodes.unknown;
585
- if (first.nullable) patternType = createUnionDeclaration({ nodes: [patternType, keywordTypeNodes.null] });
586
- elements.push(createIndexSignature(patternType));
587
- }
836
+ const valueTypes = [];
837
+ if (node.additionalProperties && node.additionalProperties !== true) valueTypes.push(print(node.additionalProperties) ?? keywordTypeNodes.unknown);
838
+ else if (node.additionalProperties === true) valueTypes.push(keywordTypeNodes.unknown);
839
+ for (const schema of Object.values(node.patternProperties ?? {})) {
840
+ const patternType = print(schema) ?? keywordTypeNodes.unknown;
841
+ valueTypes.push(schema.nullable ? createUnionDeclaration({ nodes: [patternType, keywordTypeNodes.null] }) : patternType);
588
842
  }
589
- return elements;
843
+ if (valueTypes.length === 0) return [];
844
+ const seen = /* @__PURE__ */ new Set();
845
+ const distinct = valueTypes.filter((type) => {
846
+ const key = indexSignaturePrinter.printNode(ts.EmitHint.Unspecified, type, indexSignatureSource);
847
+ return seen.has(key) ? false : (seen.add(key), true);
848
+ });
849
+ return [createIndexSignature(propertyCount > 0 ? keywordTypeNodes.unknown : createUnionDeclaration({ nodes: distinct }))];
590
850
  }
591
851
  //#endregion
592
852
  //#region src/components/Enum.tsx
@@ -594,236 +854,501 @@ function buildIndexSignatures(node, propertyCount, print) {
594
854
  * Resolves the runtime identifier name and the TypeScript type name for an enum schema node.
595
855
  *
596
856
  * The raw `node.name` may be a YAML key such as `"enumNames.Type"` which is not a
597
- * valid TypeScript identifier. The resolver normalizes it; for inline enum
598
- * properties the adapter already emits a PascalCase+suffix name so resolution is typically a no-op.
857
+ * valid TypeScript identifier. The resolver normalizes it. For inline enum properties the adapter
858
+ * already emits a PascalCase+suffix name, so resolution is typically a no-op.
859
+ *
860
+ * When `constCasing` is `'pascalCase'` and `typeSuffix` is empty, the const and the type
861
+ * resolve to the same name, which TypeScript merges into a single value+type declaration.
599
862
  */
600
- function getEnumNames({ node, enumType, enumTypeSuffix, resolver }) {
601
- const resolved = resolver.default(node.name, "type");
863
+ function getEnumNames({ node, enum: enumOptions, resolver }) {
864
+ const resolved = resolver.name(node.name);
602
865
  return {
603
- enumName: enumType === "asPascalConst" ? resolved : camelCase(node.name),
604
- typeName: ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) ? resolver.resolveEnumKeyName(node, enumTypeSuffix) : resolved
866
+ enumName: enumOptions.constCasing === "pascalCase" ? resolved : camelCase(node.name),
867
+ typeName: ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) ? resolver.enum.keyName(node, enumOptions.typeSuffix) : resolved
605
868
  };
606
869
  }
607
870
  /**
608
871
  * Renders the enum declaration(s) for a single named `EnumSchemaNode`.
609
872
  *
610
- * Depending on `enumType` this may emit:
611
- * - A runtime object (`asConst` / `asPascalConst`) plus a `typeof` type alias
873
+ * Depending on `enum.type` this may emit:
874
+ * - A runtime object (`asConst`) plus a `typeof` type alias
612
875
  * - A `const enum` or plain `enum` declaration (`constEnum` / `enum`)
613
876
  * - A union literal type alias (`literal`)
614
877
  *
615
878
  * The emitted `File.Source` nodes carry the resolved names so that the barrel
616
879
  * index picks up the correct export identifiers.
617
880
  */
618
- function Enum({ node, enumType, enumTypeSuffix, enumKeyCasing, resolver }) {
881
+ function Enum({ node, enum: enumOptions, resolver }) {
619
882
  const { enumName, typeName } = getEnumNames({
620
883
  node,
621
- enumType,
622
- enumTypeSuffix,
884
+ enum: enumOptions,
623
885
  resolver
624
886
  });
625
887
  const [nameNode, typeNode] = createEnumDeclaration({
626
888
  name: enumName,
627
889
  typeName,
628
- enums: node.namedEnumValues?.map((v) => [trimQuotes(v.name.toString()), v.value]) ?? node.enumValues?.filter((v) => v !== null && v !== void 0).map((v) => [trimQuotes(v.toString()), v]) ?? [],
629
- type: enumType,
630
- enumKeyCasing
890
+ enums: node.namedEnumValues?.map((v) => [
891
+ trimQuotes(v.name.toString()),
892
+ v.value,
893
+ v.description
894
+ ]) ?? node.enumValues?.filter((v) => v !== null && v !== void 0).map((v) => [trimQuotes(v.toString()), v]) ?? [],
895
+ type: enumOptions.type,
896
+ enumKeyCasing: enumOptions.keyCasing
631
897
  });
898
+ const namesMerge = !!nameNode && enumName === typeName;
632
899
  return /* @__PURE__ */ jsxs(Fragment, { children: [nameNode && /* @__PURE__ */ jsx(File.Source, {
633
900
  name: enumName,
634
901
  isExportable: true,
635
902
  isIndexable: true,
636
903
  isTypeOnly: false,
637
- children: safePrint(nameNode)
904
+ children: parserTs().print(nameNode)
638
905
  }), /* @__PURE__ */ jsx(File.Source, {
639
906
  name: typeName,
640
- isIndexable: true,
641
- isExportable: ENUM_TYPES_WITH_RUNTIME_VALUE.has(enumType),
642
- isTypeOnly: ENUM_TYPES_WITH_TYPE_ONLY.has(enumType),
643
- children: safePrint(typeNode)
907
+ isIndexable: !namesMerge,
908
+ isExportable: !namesMerge && ENUM_TYPES_WITH_RUNTIME_VALUE.has(enumOptions.type),
909
+ isTypeOnly: ENUM_TYPES_WITH_TYPE_ONLY.has(enumOptions.type),
910
+ children: parserTs().print(typeNode)
644
911
  })] });
645
912
  }
646
913
  //#endregion
647
- //#region src/components/Type.tsx
648
- function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCasing, resolver }) {
649
- const enumSchemaNodes = ast.collect(node, { schema(n) {
650
- const enumNode = ast.narrowSchema(n, ast.schemaTypes.enum);
651
- if (enumNode?.name) return enumNode;
652
- } });
653
- const output = printer.print(node);
654
- if (!output) return;
655
- const enums = [...new Map(enumSchemaNodes.map((n) => [n.name, n])).values()].map((node) => {
914
+ //#region ../../internals/shared/src/params.ts
915
+ /**
916
+ * Drops parameters that share the same name, keeping the first.
917
+ *
918
+ * A malformed spec can declare the same parameter name twice within one `in` location. Both would
919
+ * resolve to the same output property, so emitting both would yield an object type with a duplicate
920
+ * member, which TypeScript rejects. This is a defensive guard against that case, not a casing guard:
921
+ * parameter names flow through unchanged, so no two distinct names ever collide here anymore.
922
+ */
923
+ function dedupeParams(params) {
924
+ const seen = /* @__PURE__ */ new Set();
925
+ return params.filter((param) => {
926
+ if (seen.has(param.name)) return false;
927
+ seen.add(param.name);
928
+ return true;
929
+ });
930
+ }
931
+ //#endregion
932
+ //#region ../../internals/shared/src/operation.ts
933
+ /**
934
+ * Maps a content type to the PascalCase suffix used to name per-content-type variants
935
+ * (e.g. `application/json` → `Json`, `application/xml` → `Xml`, `multipart/form-data` → `FormData`).
936
+ */
937
+ function getContentTypeSuffix(contentType) {
938
+ const baseType = contentType.split(";")[0].trim();
939
+ if (baseType === "application/json") return "Json";
940
+ if (baseType === "multipart/form-data") return "FormData";
941
+ if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
942
+ const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
943
+ if (parts.length === 0) return "Unknown";
944
+ return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
945
+ }
946
+ /**
947
+ * Appends a content-type suffix to a base name, keeping a trailing `Data` segment last
948
+ * (e.g. `AddPetData` + `Json` → `AddPetJsonData`, `AddPetStatus200` + `Xml` → `AddPetStatus200Xml`).
949
+ */
950
+ function getPerContentTypeName(baseName, suffix) {
951
+ if (baseName.endsWith("Data")) return suffix.endsWith("Data") ? baseName.slice(0, -4) + suffix : `${baseName.slice(0, -4)}${suffix}Data`;
952
+ return baseName + suffix;
953
+ }
954
+ /**
955
+ * Resolves per-content-type variant names for a set of content entries, deduplicating suffix
956
+ * collisions with a numeric counter. Entries without a schema are skipped. The returned `suffix` is
957
+ * the final (possibly counter-augmented) value, so callers can derive parallel names in another
958
+ * namespace (e.g. plugin-faker deriving the matching plugin-ts type name).
959
+ */
960
+ function resolveContentTypeVariants(entries, baseName) {
961
+ const usedNames = /* @__PURE__ */ new Set();
962
+ return entries.filter((entry) => entry.schema).map((entry) => {
963
+ const baseSuffix = getContentTypeSuffix(entry.contentType);
964
+ let suffix = baseSuffix;
965
+ let name = getPerContentTypeName(baseName, suffix);
966
+ let counter = 2;
967
+ while (usedNames.has(name)) {
968
+ suffix = `${baseSuffix}${counter++}`;
969
+ name = getPerContentTypeName(baseName, suffix);
970
+ }
971
+ usedNames.add(name);
656
972
  return {
657
- node,
658
- ...getEnumNames({
659
- node,
660
- enumType,
661
- enumTypeSuffix,
662
- resolver
663
- })
973
+ name,
974
+ suffix,
975
+ schema: entry.schema,
976
+ keysToOmit: entry.keysToOmit,
977
+ contentType: entry.contentType
664
978
  };
665
979
  });
666
- const shouldExportEnums = enumType !== "inlineLiteral";
667
- const shouldExportType = enumType === "inlineLiteral" || enums.every((item) => item.typeName !== name);
668
- return /* @__PURE__ */ jsxs(Fragment, { children: [shouldExportEnums && enums.map(({ node }) => /* @__PURE__ */ jsx(Enum, {
669
- node,
670
- enumType,
671
- enumTypeSuffix,
672
- enumKeyCasing,
673
- resolver
674
- }, node.name)), shouldExportType && /* @__PURE__ */ jsx(File.Source, {
675
- name,
676
- isTypeOnly: true,
677
- isExportable: true,
678
- isIndexable: true,
679
- children: output
680
- })] });
980
+ }
981
+ function getOperationParameters(node) {
982
+ return {
983
+ path: dedupeParams(node.parameters.filter((param) => param.in === "path")),
984
+ query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
985
+ header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
986
+ cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
987
+ };
988
+ }
989
+ /**
990
+ * Builds the combined `{ body, path, query, headers }` options object schema for an operation,
991
+ * referencing the already-resolved body and grouped param names. Shared by `@kubb/plugin-ts`'s
992
+ * `Options` type and `@kubb/plugin-zod`'s inferred options schema, so both printers emit the same
993
+ * shape from the same inputs. `primitive: 'object'` is a no-op for the TS printer and tells the Zod
994
+ * printer to emit `z.object(…)` rather than a record.
995
+ */
996
+ function buildOptionsSchema(node, resolver) {
997
+ const { path, query, header } = getOperationParameters(node);
998
+ const hasBody = Boolean(node.requestBody?.content?.[0]?.schema);
999
+ const createNever = () => ast.factory.createSchema({
1000
+ type: "never",
1001
+ primitive: void 0,
1002
+ optional: true
1003
+ });
1004
+ const groups = [
1005
+ {
1006
+ name: "path",
1007
+ params: path,
1008
+ resolve: resolver.param.path
1009
+ },
1010
+ {
1011
+ name: "query",
1012
+ params: query,
1013
+ resolve: resolver.param.query
1014
+ },
1015
+ {
1016
+ name: "headers",
1017
+ params: header,
1018
+ resolve: resolver.param.headers
1019
+ }
1020
+ ];
1021
+ return ast.factory.createSchema({
1022
+ type: "object",
1023
+ primitive: "object",
1024
+ deprecated: node.deprecated,
1025
+ properties: [ast.factory.createProperty({
1026
+ name: "body",
1027
+ required: hasBody,
1028
+ schema: hasBody ? ast.factory.createSchema({
1029
+ type: "ref",
1030
+ name: resolver.response.body(node)
1031
+ }) : createNever()
1032
+ }), ...groups.map(({ name, params, resolve }) => {
1033
+ const required = params.some((param) => param.required);
1034
+ return ast.factory.createProperty({
1035
+ name,
1036
+ required,
1037
+ schema: params.length > 0 ? ast.factory.createSchema({
1038
+ type: "ref",
1039
+ name: resolve.call(resolver.param, node, params[0]),
1040
+ optional: !required
1041
+ }) : createNever()
1042
+ });
1043
+ })]
1044
+ });
681
1045
  }
682
1046
  //#endregion
683
- //#region ../../internals/shared/src/operation.ts
684
- function getOperationParameters(node, options = {}) {
685
- const params = ast.caseParams(node.parameters, options.paramsCasing);
1047
+ //#region ../../internals/shared/src/resolver.ts
1048
+ /**
1049
+ * Resolves a single operation parameter name with the
1050
+ * `<operationId> <in> <name>` template.
1051
+ *
1052
+ * @example
1053
+ * `operationParamName.call(resolver, node, param) // → 'DeletePetPathPetId'`
1054
+ */
1055
+ function operationParamName(node, param) {
1056
+ return this.name(`${node.operationId} ${param.in} ${param.name}`);
1057
+ }
1058
+ /**
1059
+ * Builds the shared `param` namespace. Spread the result into `createResolver`
1060
+ * and override individual methods next to it when a plugin deviates.
1061
+ *
1062
+ * @example
1063
+ * ```ts
1064
+ * createResolver<PluginTs>({ param: createOperationParamResolver(), ... })
1065
+ * ```
1066
+ */
1067
+ function createOperationParamResolver() {
1068
+ return {
1069
+ name: operationParamName,
1070
+ path(node) {
1071
+ return this.name(`${node.operationId} Path`);
1072
+ },
1073
+ query(node) {
1074
+ return this.name(`${node.operationId} Query`);
1075
+ },
1076
+ headers(node) {
1077
+ return this.name(`${node.operationId} Headers`);
1078
+ }
1079
+ };
1080
+ }
1081
+ /**
1082
+ * Builds the shared `response` namespace. Spread the result into
1083
+ * `createResolver` and add plugin-specific methods (`options`, `error`) next
1084
+ * to it.
1085
+ *
1086
+ * @example
1087
+ * ```ts
1088
+ * createResolver<PluginTs>({ response: { ...createOperationResponseResolver(), options(node) {...} }, ... })
1089
+ * ```
1090
+ */
1091
+ function createOperationResponseResolver() {
686
1092
  return {
687
- path: params.filter((param) => param.in === "path"),
688
- query: params.filter((param) => param.in === "query"),
689
- header: params.filter((param) => param.in === "header"),
690
- cookie: params.filter((param) => param.in === "cookie")
1093
+ status(node, statusCode) {
1094
+ return this.name(`${node.operationId} Status ${statusCode}`);
1095
+ },
1096
+ body(node) {
1097
+ return this.name(`${node.operationId} Body`);
1098
+ },
1099
+ responses(node) {
1100
+ return this.name(`${node.operationId} Responses`);
1101
+ },
1102
+ response(node) {
1103
+ return this.name(`${node.operationId} Response`);
1104
+ }
691
1105
  };
692
1106
  }
1107
+ /**
1108
+ * Builds a resolver `file` override whose base name runs every path segment
1109
+ * through `toFilePath`, casing the final segment with `caseLast`.
1110
+ *
1111
+ * @example
1112
+ * ```ts
1113
+ * createResolver<PluginTs>({ file: createCasedFile(pascalCase), ... })
1114
+ * ```
1115
+ */
1116
+ function createCasedFile(caseLast) {
1117
+ return { baseName({ name, extname }) {
1118
+ return `${toFilePath(name, caseLast)}${extname}`;
1119
+ } };
1120
+ }
1121
+ //#endregion
1122
+ //#region ../../internals/shared/src/refs.ts
1123
+ /**
1124
+ * Collects the resolved target name of every pointer-carrying ref in a schema tree, in
1125
+ * first-occurrence order. Use this for name-only checks (e.g. redeclaration detection) where
1126
+ * `resolver.imports` would resolve file paths that are then discarded.
1127
+ */
1128
+ function collectRefNames(schema) {
1129
+ return ast.collectSync(schema, { schema: (node) => {
1130
+ const refNode = ast.narrowSchema(node, "ref");
1131
+ if (!refNode?.ref) return null;
1132
+ return ast.resolveRefName(refNode);
1133
+ } });
1134
+ }
1135
+ //#endregion
1136
+ //#region ../../internals/shared/src/group.ts
1137
+ /**
1138
+ * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
1139
+ * shared default naming so every plugin groups output consistently:
1140
+ *
1141
+ * - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
1142
+ * - other groups use the camelCased group (`pet store` → `petStore`).
1143
+ *
1144
+ * A user-provided `group.name` always wins over the default namer, so callers stay in
1145
+ * control of their output folders. Returns `null` when grouping is disabled, matching the
1146
+ * per-plugin convention.
1147
+ *
1148
+ * @param group - The user-supplied group option, or `undefined` to disable grouping.
1149
+ *
1150
+ * @example
1151
+ * ```ts
1152
+ * createGroupConfig(group) // shared across every plugin
1153
+ * ```
1154
+ */
1155
+ function createGroupConfig(group) {
1156
+ if (!group) return null;
1157
+ const defaultName = (ctx) => {
1158
+ if (group.type === "path") return `${ctx.group.split("/")[1]}`;
1159
+ return camelCase(ctx.group);
1160
+ };
1161
+ return {
1162
+ ...group,
1163
+ name: group.name ? group.name : defaultName
1164
+ };
1165
+ }
1166
+ //#endregion
1167
+ //#region ../../internals/shared/src/schemaTraversal.ts
1168
+ /**
1169
+ * Maps each property of an object schema to its transformed output. Pairs every result with the
1170
+ * original property so the printer keeps full control over modifiers, getters, and key syntax.
1171
+ *
1172
+ * @example
1173
+ * ```ts
1174
+ * const entries = mapSchemaProperties(node, (schema) => this.transform(schema))
1175
+ * // entries: [{ name: 'id', property, output: 'z.number()' }, ...]
1176
+ * ```
1177
+ */
1178
+ function mapSchemaProperties(node, transform) {
1179
+ return node.properties.map((property) => ({
1180
+ name: property.name,
1181
+ property,
1182
+ output: transform(property.schema)
1183
+ }));
1184
+ }
1185
+ /**
1186
+ * Maps each item of an array or tuple schema to its transformed output, pairing every result with
1187
+ * the original item.
1188
+ */
1189
+ function mapSchemaItems(node, transform) {
1190
+ return (node.items ?? []).map((schema) => ({
1191
+ schema,
1192
+ output: transform(schema)
1193
+ }));
1194
+ }
693
1195
  //#endregion
694
1196
  //#region src/utils.ts
695
1197
  /**
1198
+ * Tells whether a `const` (single-value enum) should render as a bare literal type (`'active'`)
1199
+ * rather than a named enum reference or a runtime enum declaration.
1200
+ *
1201
+ * The parser folds `const` into a single-value enum node. The adapter decides which schemas are
1202
+ * named enums and lists them in `enumSchemaNames`, and references to those names get suffixed (for
1203
+ * example `StatusKey`). A const renders as a literal only when the adapter has not registered it as
1204
+ * a named enum, which keeps the declaration and its references in sync across adapter versions.
1205
+ */
1206
+ function isInlineConstEnum(node, enumSchemaNames) {
1207
+ return (node.namedEnumValues ?? node.enumValues ?? []).length === 1 && !(node.name && enumSchemaNames?.has(node.name));
1208
+ }
1209
+ /**
696
1210
  * Collects JSDoc annotation strings for a schema node.
697
1211
  *
698
1212
  * Only uses official JSDoc tags from https://jsdoc.app/: `@description`, `@deprecated`, `@default`, `@example`, `@type`.
699
1213
  * Constraint metadata (min/max length, pattern, multipleOf, min/maxProperties) is emitted as plain-text lines.
700
1214
 
701
1215
  */
702
- function buildPropertyJSDocComments(schema) {
703
- const meta = ast.syncSchemaRef(schema);
1216
+ function isSchemaOptional(schema) {
1217
+ return Boolean("optional" in schema && schema.optional || "nullish" in schema && schema.nullish);
1218
+ }
1219
+ function formatExample(value) {
1220
+ if (value === null || typeof value !== "object") return String(value);
1221
+ return (JSON.stringify(value) ?? String(value)).replaceAll("*/", "*\\/");
1222
+ }
1223
+ function buildPropertyJSDocComments(schema, optional) {
1224
+ const meta = syncSchemaRef(schema);
704
1225
  const isArray = meta?.primitive === "array";
705
- return [
706
- meta && "description" in meta && meta.description ? `@description ${jsStringEscape(meta.description)}` : void 0,
707
- meta && "deprecated" in meta && meta.deprecated ? "@deprecated" : void 0,
708
- !isArray && meta && "min" in meta && meta.min !== void 0 ? `@minLength ${meta.min}` : void 0,
709
- !isArray && meta && "max" in meta && meta.max !== void 0 ? `@maxLength ${meta.max}` : void 0,
710
- meta && "pattern" in meta && meta.pattern ? `@pattern ${meta.pattern}` : void 0,
711
- meta && "default" in meta && meta.default !== void 0 ? `@default ${"primitive" in meta && meta.primitive === "string" ? stringify(meta.default) : meta.default}` : void 0,
712
- meta && "example" in meta && meta.example !== void 0 ? `@example ${meta.example}` : void 0,
713
- meta && "primitive" in meta && meta.primitive ? [`@type ${meta.primitive}`, "optional" in schema && schema.optional ? " | undefined" : void 0].filter(Boolean).join("") : void 0
1226
+ const hasDescription = meta && "description" in meta && meta.description;
1227
+ const formatComment = meta && "format" in meta && meta.format ? hasDescription ? [" ", `Format: \`${meta.format}\``] : ["@description", `Format: \`${meta.format}\``] : [];
1228
+ const exampleValues = meta?.examples ?? [];
1229
+ const comments = [
1230
+ hasDescription ? `@description ${jsStringEscape(meta.description)}` : null,
1231
+ ...formatComment,
1232
+ meta && "deprecated" in meta && meta.deprecated ? "@deprecated" : null,
1233
+ !isArray && meta && "min" in meta && meta.min !== void 0 ? `@minLength ${meta.min}` : null,
1234
+ !isArray && meta && "max" in meta && meta.max !== void 0 ? `@maxLength ${meta.max}` : null,
1235
+ meta && "pattern" in meta && meta.pattern ? `@pattern ${meta.pattern}` : null,
1236
+ meta && "default" in meta && meta.default !== void 0 ? `@default ${"primitive" in meta && meta.primitive === "string" && typeof meta.default === "string" ? stringify(meta.default) : meta.default}` : null,
1237
+ ...exampleValues.map((example) => `@example ${formatExample(example)}`)
714
1238
  ].filter(Boolean);
1239
+ const typeTag = comments.length && meta && "primitive" in meta && meta.primitive ? [`@type ${meta.primitive}`, optional ?? isSchemaOptional(schema) ? " | undefined" : null].filter(Boolean).join("") : null;
1240
+ return [...comments, typeTag].filter(Boolean);
715
1241
  }
716
- function buildParams(node, { params, resolver }) {
717
- return ast.createSchema({
1242
+ /**
1243
+ * Builds the object schema for a group of parameters sharing one `in` location (path, query, or
1244
+ * header), embedding each param's own schema (and JSDoc) directly rather than referencing a
1245
+ * separate per-param type — the group itself is the only type these params get exported as.
1246
+ */
1247
+ function buildParams({ params }) {
1248
+ return ast.factory.createSchema({
718
1249
  type: "object",
719
- properties: params.map((param) => ast.createProperty({
1250
+ properties: params.map((param) => ast.factory.createProperty({
720
1251
  name: param.name,
721
1252
  required: param.required,
722
- schema: ast.createSchema({
723
- type: "ref",
724
- name: resolver.resolveParamName(node, param)
1253
+ schema: ast.factory.createSchema({
1254
+ ...param.schema,
1255
+ optional: !param.required
725
1256
  })
726
1257
  }))
727
1258
  });
728
1259
  }
729
- function buildData(node, { resolver }) {
730
- const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node);
731
- return ast.createSchema({
732
- type: "object",
733
- deprecated: node.deprecated,
734
- properties: [
735
- ast.createProperty({
736
- name: "data",
737
- schema: node.requestBody?.content?.[0]?.schema ? ast.createSchema({
738
- type: "ref",
739
- name: resolver.resolveDataName(node),
740
- optional: true
741
- }) : ast.createSchema({
742
- type: "never",
743
- primitive: void 0,
744
- optional: true
745
- })
746
- }),
747
- ast.createProperty({
748
- name: "pathParams",
749
- required: pathParams.length > 0,
750
- schema: pathParams.length > 0 ? buildParams(node, {
751
- params: pathParams,
752
- resolver
753
- }) : ast.createSchema({
754
- type: "never",
755
- primitive: void 0
756
- })
757
- }),
758
- ast.createProperty({
759
- name: "queryParams",
760
- schema: queryParams.length > 0 ? ast.createSchema({
761
- ...buildParams(node, {
762
- params: queryParams,
763
- resolver
764
- }),
765
- optional: true
766
- }) : ast.createSchema({
767
- type: "never",
768
- primitive: void 0,
769
- optional: true
770
- })
771
- }),
772
- ast.createProperty({
773
- name: "headerParams",
774
- schema: headerParams.length > 0 ? ast.createSchema({
775
- ...buildParams(node, {
776
- params: headerParams,
777
- resolver
778
- }),
779
- optional: true
780
- }) : ast.createSchema({
781
- type: "never",
782
- primitive: void 0,
783
- optional: true
1260
+ /**
1261
+ * The schema a status occupies in the `<Name>Responses` record. A status that documents several
1262
+ * content types becomes a `{ contentType; data }` union so the runtime can surface the negotiated type
1263
+ * on `result.parsed`, while the standalone `<Name>StatusNNN` alias stays the plain body union that the
1264
+ * query hooks and `result.data` use.
1265
+ */
1266
+ function buildResponseRecordEntry(node, res, resolver) {
1267
+ const statusName = resolver.response.status(node, res.statusCode);
1268
+ const variants = (res.content ?? []).filter((entry) => entry.schema);
1269
+ if (variants.length <= 1) return ast.factory.createSchema({
1270
+ type: "ref",
1271
+ name: statusName
1272
+ });
1273
+ return ast.factory.createSchema({
1274
+ type: "union",
1275
+ members: resolveContentTypeVariants(variants, statusName).map((variant) => ast.factory.createSchema({
1276
+ type: "object",
1277
+ properties: [ast.factory.createProperty({
1278
+ name: "contentType",
1279
+ required: true,
1280
+ schema: ast.factory.createSchema({
1281
+ type: "enum",
1282
+ enumValues: [variant.contentType]
784
1283
  })
785
- }),
786
- ast.createProperty({
787
- name: "url",
1284
+ }), ast.factory.createProperty({
1285
+ name: "data",
788
1286
  required: true,
789
- schema: ast.createSchema({
790
- type: "url",
791
- path: node.path
1287
+ schema: ast.factory.createSchema({
1288
+ type: "ref",
1289
+ name: variant.name
792
1290
  })
793
- })
794
- ]
1291
+ })]
1292
+ }))
795
1293
  });
796
1294
  }
797
1295
  function buildResponses(node, { resolver }) {
798
- if (node.responses.length === 0) return null;
799
- return ast.createSchema({
1296
+ return ast.factory.createSchema({
800
1297
  type: "object",
801
- properties: node.responses.map((res) => ast.createProperty({
1298
+ properties: node.responses.map((res) => ast.factory.createProperty({
802
1299
  name: String(res.statusCode),
803
1300
  required: true,
804
- schema: ast.createSchema({
805
- type: "ref",
806
- name: resolver.resolveResponseStatusName(node, res.statusCode)
807
- })
1301
+ schema: buildResponseRecordEntry(node, res, resolver)
808
1302
  }))
809
1303
  });
810
1304
  }
811
1305
  function buildResponseUnion(node, { resolver }) {
812
- const responsesWithSchema = node.responses.filter((res) => res.schema);
1306
+ const responsesWithSchema = node.responses.filter((res) => res.content?.some((entry) => entry.schema));
813
1307
  if (responsesWithSchema.length === 0) return null;
814
- return ast.createSchema({
1308
+ return ast.factory.createSchema({
815
1309
  type: "union",
816
- members: responsesWithSchema.map((res) => ast.createSchema({
1310
+ members: responsesWithSchema.map((res) => ast.factory.createSchema({
817
1311
  type: "ref",
818
- name: resolver.resolveResponseStatusName(node, res.statusCode)
1312
+ name: resolver.response.status(node, res.statusCode)
819
1313
  }))
820
1314
  });
821
1315
  }
822
1316
  //#endregion
823
- //#region src/printers/printerTs.ts
824
- function isNonNullable(value) {
825
- return value !== null && value !== void 0;
1317
+ //#region src/components/Type.tsx
1318
+ function Type({ name, node, printer, enum: enumOptions, resolver }) {
1319
+ const enumSchemaNodes = ast.collectSync(node, { schema(n) {
1320
+ const enumNode = ast.narrowSchema(n, ast.schemaTypes.enum);
1321
+ if (enumNode?.name && !isInlineConstEnum(enumNode, printer.options.enumSchemaNames)) return enumNode;
1322
+ } });
1323
+ const output = printer.print(node);
1324
+ if (!output) return;
1325
+ const enums = [...new Map(enumSchemaNodes.map((n) => [n.name, n])).values()].map((node) => {
1326
+ return {
1327
+ node,
1328
+ ...getEnumNames({
1329
+ node,
1330
+ enum: enumOptions,
1331
+ resolver
1332
+ })
1333
+ };
1334
+ });
1335
+ const shouldExportEnums = enumOptions.type !== "inlineLiteral";
1336
+ const shouldExportType = enumOptions.type === "inlineLiteral" || enums.every((item) => item.typeName !== name);
1337
+ return /* @__PURE__ */ jsxs(Fragment, { children: [shouldExportEnums && enums.map(({ node }) => /* @__PURE__ */ jsx(Enum, {
1338
+ node,
1339
+ enum: enumOptions,
1340
+ resolver
1341
+ }, node.name)), shouldExportType && /* @__PURE__ */ jsx(File.Source, {
1342
+ name,
1343
+ isTypeOnly: true,
1344
+ isExportable: true,
1345
+ isIndexable: true,
1346
+ children: output
1347
+ })] });
826
1348
  }
1349
+ //#endregion
1350
+ //#region src/printers/printerTs.ts
1351
+ const { isNonNullable } = factory_exports;
827
1352
  /**
828
1353
  * TypeScript type printer built with `definePrinter`.
829
1354
  *
@@ -837,17 +1362,17 @@ function isNonNullable(value) {
837
1362
  *
838
1363
  * @example Raw type node (no `typeName`)
839
1364
  * ```ts
840
- * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enumType: 'inlineLiteral' })
1365
+ * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enum: { type: 'inlineLiteral' } })
841
1366
  * const typeNode = printer.print(schemaNode) // ts.TypeNode
842
1367
  * ```
843
1368
  *
844
1369
  * @example Full declaration (with `typeName`)
845
1370
  * ```ts
846
- * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enumType: 'inlineLiteral', typeName: 'MyType' })
1371
+ * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enum: { type: 'inlineLiteral' }, typeName: 'MyType' })
847
1372
  * const declaration = printer.print(schemaNode) // ts.TypeAliasDeclaration | ts.InterfaceDeclaration
848
1373
  * ```
849
1374
  */
850
- const printerTs = ast.definePrinter((options) => {
1375
+ const printerTs = ast.createPrinter((options) => {
851
1376
  const addsUndefined = OPTIONAL_ADDS_UNDEFINED.has(options.optionalType);
852
1377
  return {
853
1378
  name: "typescript",
@@ -876,28 +1401,31 @@ const printerTs = ast.definePrinter((options) => {
876
1401
  date: dateOrStringNode,
877
1402
  time: dateOrStringNode,
878
1403
  ref(node) {
879
- if (!node.name) return;
880
- const refName = node.ref ? ast.extractRefName(node.ref) ?? node.name : node.name;
881
- return createTypeReferenceNode(node.ref && ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enumType) && this.options.enumTypeSuffix && this.options.enumSchemaNames?.has(refName) ? this.options.resolver.resolveEnumKeyName({ name: refName }, this.options.enumTypeSuffix) : node.ref ? this.options.resolver.default(refName, "type") : refName, void 0);
1404
+ if (!node.name) return null;
1405
+ const refName = ast.resolveRefName(node);
1406
+ if (!refName) return null;
1407
+ const name = node.ref && ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enum.type) && this.options.enum.typeSuffix && this.options.enumSchemaNames?.has(refName) ? this.options.resolver.enum.keyName({ name: refName }, this.options.enum.typeSuffix) : node.ref ? this.options.resolver.name(refName) : refName;
1408
+ return createTypeReferenceNode(name, void 0);
882
1409
  },
883
1410
  enum(node) {
884
1411
  const values = node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? [];
885
- if (this.options.enumType === "inlineLiteral" || !node.name) return createUnionDeclaration({
1412
+ if (this.options.enum.type === "inlineLiteral" || !node.name || isInlineConstEnum(node, this.options.enumSchemaNames)) return createUnionDeclaration({
886
1413
  withParentheses: true,
887
1414
  nodes: values.filter((v) => v !== null && v !== void 0).map((value) => constToTypeNode(value, typeof value)).filter(isNonNullable)
888
1415
  }) ?? void 0;
889
- return createTypeReferenceNode(ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enumType) && this.options.enumTypeSuffix ? this.options.resolver.resolveEnumKeyName(node, this.options.enumTypeSuffix) : this.options.resolver.default(node.name, "type"), void 0);
1416
+ const resolvedName = ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enum.type) && this.options.enum.typeSuffix ? this.options.resolver.enum.keyName(node, this.options.enum.typeSuffix) : this.options.resolver.name(node.name);
1417
+ return createTypeReferenceNode(resolvedName, void 0);
890
1418
  },
891
1419
  union(node) {
892
1420
  const members = node.members ?? [];
893
1421
  const hasStringLiteral = members.some((m) => {
894
1422
  return ast.narrowSchema(m, ast.schemaTypes.enum)?.primitive === "string";
895
1423
  });
896
- const hasPlainString = members.some((m) => ast.isStringType(m));
1424
+ const hasPlainString = members.some((m) => isStringType(m));
897
1425
  if (hasStringLiteral && hasPlainString) return createUnionDeclaration({
898
1426
  withParentheses: true,
899
1427
  nodes: members.map((m) => {
900
- if (ast.isStringType(m)) return createIntersectionDeclaration({
1428
+ if (isStringType(m)) return createIntersectionDeclaration({
901
1429
  nodes: [keywordTypeNodes.string, createTypeLiteralNode([])],
902
1430
  withParentheses: true
903
1431
  });
@@ -913,32 +1441,33 @@ const printerTs = ast.definePrinter((options) => {
913
1441
  return createIntersectionDeclaration({
914
1442
  withParentheses: true,
915
1443
  nodes: buildMemberNodes(node.members, this.transform)
916
- }) ?? void 0;
1444
+ }) ?? null;
917
1445
  },
918
1446
  array(node) {
919
1447
  return createArrayDeclaration({
920
- nodes: (node.items ?? []).map((item) => this.transform(item)).filter(isNonNullable),
1448
+ nodes: mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(isNonNullable),
921
1449
  arrayType: this.options.arrayType
922
- }) ?? void 0;
1450
+ }) ?? null;
923
1451
  },
924
1452
  tuple(node) {
925
- return buildTupleNode(node, this.transform);
1453
+ return buildTupleNode(node, this.transform) ?? null;
926
1454
  },
927
1455
  object(node) {
928
1456
  const { transform, options } = this;
929
1457
  const addsQuestionToken = OPTIONAL_ADDS_QUESTION_TOKEN.has(options.optionalType);
930
- const propertyNodes = node.properties.map((prop) => {
931
- const baseType = transform(prop.schema) ?? keywordTypeNodes.unknown;
932
- const type = buildPropertyType(prop.schema, baseType, options.optionalType);
933
- const propMeta = ast.syncSchemaRef(prop.schema);
1458
+ const propertyNodes = mapSchemaProperties(node, (schema) => transform(schema)).map(({ name, property, output }) => {
1459
+ const baseType = output ?? keywordTypeNodes.unknown;
1460
+ const optional = !property.required || !!property.schema.optional || !!property.schema.nullish;
1461
+ const type = buildPropertyType(property.schema, baseType, options.optionalType, optional);
1462
+ const propMeta = syncSchemaRef(property.schema);
934
1463
  return appendJSDocToNode({
935
1464
  node: createPropertySignature({
936
- questionToken: prop.schema.optional || prop.schema.nullish ? addsQuestionToken : false,
937
- name: prop.name,
1465
+ questionToken: optional ? addsQuestionToken : false,
1466
+ name,
938
1467
  type,
939
1468
  readOnly: propMeta?.readOnly
940
1469
  }),
941
- comments: buildPropertyJSDocComments(prop.schema)
1470
+ comments: buildPropertyJSDocComments(property.schema, optional)
942
1471
  });
943
1472
  });
944
1473
  const allElements = [...propertyNodes, ...buildIndexSignatures(node, propertyNodes.length, transform)];
@@ -949,91 +1478,91 @@ const printerTs = ast.definePrinter((options) => {
949
1478
  },
950
1479
  print(node) {
951
1480
  const { name, syntaxType = "type", description, keysToOmit } = this.options;
952
- let base = this.transform(node);
953
- if (!base) return null;
954
- const meta = ast.syncSchemaRef(node);
1481
+ const transformed = this.transform(node);
1482
+ if (!transformed) return null;
1483
+ const meta = syncSchemaRef(node);
955
1484
  if (!name) {
956
- if (meta.nullable) base = createUnionDeclaration({ nodes: [base, keywordTypeNodes.null] });
957
- if ((meta.nullish || meta.optional) && addsUndefined) base = createUnionDeclaration({ nodes: [base, keywordTypeNodes.undefined] });
958
- return safePrint(base);
1485
+ const withNullable = meta.nullable ? createUnionDeclaration({ nodes: [transformed, keywordTypeNodes.null] }) : transformed;
1486
+ const result = (meta.nullish || meta.optional) && addsUndefined ? createUnionDeclaration({ nodes: [withNullable, keywordTypeNodes.undefined] }) : withNullable;
1487
+ return parserTs().print(result);
959
1488
  }
960
- let inner = keysToOmit?.length ? createOmitDeclaration({
961
- keys: keysToOmit,
962
- type: base,
963
- nonNullable: true
964
- }) : base;
965
- if (meta.nullable) inner = createUnionDeclaration({ nodes: [inner, keywordTypeNodes.null] });
966
- if (meta.nullish || meta.optional) inner = createUnionDeclaration({ nodes: [inner, keywordTypeNodes.undefined] });
967
- const useTypeGeneration = syntaxType === "type" || inner.kind === syntaxKind.union || !!keysToOmit?.length;
968
- return safePrint(createTypeDeclaration({
1489
+ const inner = (() => {
1490
+ const omitted = keysToOmit?.length ? createOmitDeclaration({
1491
+ keys: keysToOmit,
1492
+ type: transformed,
1493
+ nonNullable: true
1494
+ }) : transformed;
1495
+ const withNullable = meta.nullable ? createUnionDeclaration({ nodes: [omitted, keywordTypeNodes.null] }) : omitted;
1496
+ return meta.nullish || meta.optional ? createUnionDeclaration({ nodes: [withNullable, keywordTypeNodes.undefined] }) : withNullable;
1497
+ })();
1498
+ const typeNode = createTypeDeclaration({
969
1499
  name,
970
1500
  isExportable: true,
971
1501
  type: inner,
972
- syntax: useTypeGeneration ? "type" : "interface",
1502
+ syntax: syntaxType === "type" || inner.kind === syntaxKind.union || !!keysToOmit?.length ? "type" : "interface",
973
1503
  comments: buildPropertyJSDocComments({
974
1504
  ...meta,
975
1505
  description
976
1506
  })
977
- }));
1507
+ });
1508
+ return parserTs().print(typeNode);
978
1509
  }
979
1510
  };
980
1511
  });
981
1512
  //#endregion
982
1513
  //#region src/generators/typeGenerator.tsx
983
- function getContentTypeSuffix(contentType) {
984
- const baseType = contentType.split(";")[0].trim();
985
- if (baseType === "application/json") return "Json";
986
- if (baseType === "multipart/form-data") return "FormData";
987
- if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
988
- const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
989
- if (parts.length === 0) return "Unknown";
990
- return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
991
- }
992
- function getPerContentTypeName(dataName, suffix) {
993
- if (dataName.endsWith("Data")) return suffix.endsWith("Data") ? dataName.slice(0, -4) + suffix : `${dataName.slice(0, -4)}${suffix}Data`;
994
- return dataName + suffix;
1514
+ /**
1515
+ * Resolves the imported type name for a referenced schema. An enum schema emitted
1516
+ * as a `const` object imports its suffixed key alias (e.g. `StatusKey`); every other
1517
+ * schema imports its plain resolved name.
1518
+ */
1519
+ function resolveImportName({ schemaName, enumOptions, enumSchemaNames, resolver }) {
1520
+ if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && enumOptions.typeSuffix && enumSchemaNames.has(schemaName)) return resolver.enum.keyName({ name: schemaName }, enumOptions.typeSuffix);
1521
+ return resolver.name(schemaName);
995
1522
  }
1523
+ /**
1524
+ * Built-in generator for `@kubb/plugin-ts`. Emits one TypeScript file per
1525
+ * schema in the spec plus per-operation request, response, and parameter
1526
+ * types. Drop-replace with a custom `Generator<PluginTs>` to change how
1527
+ * TypeScript output is produced.
1528
+ */
996
1529
  const typeGenerator = defineGenerator({
997
1530
  name: "typescript",
998
1531
  renderer: jsxRenderer,
999
1532
  schema(node, ctx) {
1000
- const { enumType, enumTypeSuffix, enumKeyCasing, syntaxType, optionalType, arrayType, output, group, printer } = ctx.options;
1001
- const { adapter, config, resolver, root } = ctx;
1533
+ const { enum: enumOptions, syntaxType, optionalType, arrayType, output, group, printer } = ctx.options;
1534
+ const { config, resolver, root } = ctx;
1002
1535
  if (!node.name) return;
1003
- const mode = ctx.getMode(output);
1004
- const enumSchemaNames = new Set((adapter.inputNode?.schemas ?? []).filter((s) => ast.narrowSchema(s, ast.schemaTypes.enum) && s.name).map((s) => s.name));
1005
- function resolveImportName(schemaName) {
1006
- if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && enumTypeSuffix && enumSchemaNames.has(schemaName)) return resolver.resolveEnumKeyName({ name: schemaName }, enumTypeSuffix);
1007
- return resolver.resolveTypeName(schemaName);
1008
- }
1009
- const imports = adapter.getImports(node, (schemaName) => ({
1010
- name: resolveImportName(schemaName),
1011
- path: resolver.resolveFile({
1012
- name: schemaName,
1013
- extname: ".ts"
1014
- }, {
1015
- root,
1016
- output,
1017
- group
1018
- }).path
1019
- }));
1020
- const isEnumSchema = !!ast.narrowSchema(node, ast.schemaTypes.enum);
1536
+ const enumSchemaNames = new Set(ctx.meta.enumNames);
1537
+ const importName = (schemaName) => resolveImportName({
1538
+ schemaName,
1539
+ enumOptions,
1540
+ enumSchemaNames,
1541
+ resolver
1542
+ });
1543
+ const imports = resolver.imports({
1544
+ node,
1545
+ root,
1546
+ output,
1547
+ group: group ?? void 0,
1548
+ name: importName
1549
+ });
1550
+ const enumNode = ast.narrowSchema(node, ast.schemaTypes.enum);
1551
+ const isEnumSchema = !!enumNode && !isInlineConstEnum(enumNode, enumSchemaNames);
1021
1552
  const meta = {
1022
- name: ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && isEnumSchema ? resolver.resolveEnumKeyName(node, enumTypeSuffix) : resolver.resolveTypeName(node.name),
1023
- file: resolver.resolveFile({
1553
+ name: ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && isEnumSchema ? resolver.enum.keyName(node, enumOptions.typeSuffix) : resolver.name(node.name),
1554
+ file: resolver.file({
1024
1555
  name: node.name,
1025
- extname: ".ts"
1026
- }, {
1556
+ extname: ".ts",
1027
1557
  root,
1028
1558
  output,
1029
- group
1559
+ group: group ?? void 0
1030
1560
  })
1031
1561
  };
1032
1562
  const schemaPrinter = printerTs({
1033
1563
  optionalType,
1034
1564
  arrayType,
1035
- enumType,
1036
- enumTypeSuffix,
1565
+ enum: enumOptions,
1037
1566
  name: meta.name,
1038
1567
  syntaxType,
1039
1568
  description: node.description,
@@ -1045,15 +1574,23 @@ const typeGenerator = defineGenerator({
1045
1574
  baseName: meta.file.baseName,
1046
1575
  path: meta.file.path,
1047
1576
  meta: meta.file.meta,
1048
- banner: resolver.resolveBanner(adapter.inputNode, {
1577
+ banner: resolver.default.banner(ctx.meta, {
1049
1578
  output,
1050
- config
1579
+ config,
1580
+ file: {
1581
+ path: meta.file.path,
1582
+ baseName: meta.file.baseName
1583
+ }
1051
1584
  }),
1052
- footer: resolver.resolveFooter(adapter.inputNode, {
1585
+ footer: resolver.default.footer(ctx.meta, {
1053
1586
  output,
1054
- config
1587
+ config,
1588
+ file: {
1589
+ path: meta.file.path,
1590
+ baseName: meta.file.baseName
1591
+ }
1055
1592
  }),
1056
- children: [mode === "split" && imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
1593
+ children: [imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
1057
1594
  root: meta.file.path,
1058
1595
  path: imp.path,
1059
1596
  name: imp.name,
@@ -1065,52 +1602,44 @@ const typeGenerator = defineGenerator({
1065
1602
  ].join("-"))), /* @__PURE__ */ jsx(Type, {
1066
1603
  name: meta.name,
1067
1604
  node,
1068
- enumType,
1069
- enumTypeSuffix,
1070
- enumKeyCasing,
1605
+ enum: enumOptions,
1071
1606
  resolver,
1072
1607
  printer: schemaPrinter
1073
1608
  })]
1074
1609
  });
1075
1610
  },
1076
1611
  operation(node, ctx) {
1077
- const { enumType, enumTypeSuffix, enumKeyCasing, optionalType, arrayType, syntaxType, paramsCasing, group, output, printer } = ctx.options;
1078
- const { adapter, config, resolver, root } = ctx;
1079
- const mode = ctx.getMode(output);
1080
- const params = ast.caseParams(node.parameters, paramsCasing);
1081
- const meta = { file: resolver.resolveFile({
1612
+ const { enum: enumOptions, optionalType, arrayType, syntaxType, group, output, printer } = ctx.options;
1613
+ const { config, resolver, root } = ctx;
1614
+ const meta = { file: resolver.file({
1082
1615
  name: node.operationId,
1083
1616
  extname: ".ts",
1084
1617
  tag: node.tags[0] ?? "default",
1085
- path: node.path
1086
- }, {
1618
+ path: node.path,
1087
1619
  root,
1088
1620
  output,
1089
- group
1621
+ group: group ?? void 0
1090
1622
  }) };
1091
- const enumSchemaNames = new Set((adapter.inputNode?.schemas ?? []).filter((s) => ast.narrowSchema(s, ast.schemaTypes.enum) && s.name).map((s) => s.name));
1092
- function resolveImportName(schemaName) {
1093
- if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && enumTypeSuffix && enumSchemaNames.has(schemaName)) return resolver.resolveEnumKeyName({ name: schemaName }, enumTypeSuffix);
1094
- return resolver.resolveTypeName(schemaName);
1095
- }
1623
+ const enumSchemaNames = new Set(ctx.meta.enumNames);
1624
+ const importName = (schemaName) => resolveImportName({
1625
+ schemaName,
1626
+ enumOptions,
1627
+ enumSchemaNames,
1628
+ resolver
1629
+ });
1096
1630
  function renderSchemaType({ schema, name, keysToOmit }) {
1097
1631
  if (!schema) return null;
1098
- const imports = adapter.getImports(schema, (schemaName) => ({
1099
- name: resolveImportName(schemaName),
1100
- path: resolver.resolveFile({
1101
- name: schemaName,
1102
- extname: ".ts"
1103
- }, {
1104
- root,
1105
- output,
1106
- group
1107
- }).path
1108
- }));
1632
+ const imports = resolver.imports({
1633
+ node: schema,
1634
+ root,
1635
+ output,
1636
+ group: group ?? void 0,
1637
+ name: importName
1638
+ });
1109
1639
  const schemaPrinter = printerTs({
1110
1640
  optionalType,
1111
1641
  arrayType,
1112
- enumType,
1113
- enumTypeSuffix,
1642
+ enum: enumOptions,
1114
1643
  name,
1115
1644
  syntaxType,
1116
1645
  description: schema.description,
@@ -1119,7 +1648,7 @@ const typeGenerator = defineGenerator({
1119
1648
  enumSchemaNames,
1120
1649
  nodes: printer?.nodes
1121
1650
  });
1122
- return /* @__PURE__ */ jsxs(Fragment, { children: [mode === "split" && imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
1651
+ return /* @__PURE__ */ jsxs(Fragment, { children: [imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
1123
1652
  root: meta.file.path,
1124
1653
  path: imp.path,
1125
1654
  name: imp.name,
@@ -1131,17 +1660,50 @@ const typeGenerator = defineGenerator({
1131
1660
  ].join("-"))), /* @__PURE__ */ jsx(Type, {
1132
1661
  name,
1133
1662
  node: schema,
1134
- enumType,
1135
- enumTypeSuffix,
1136
- enumKeyCasing,
1663
+ enum: enumOptions,
1137
1664
  resolver,
1138
1665
  printer: schemaPrinter
1139
1666
  })] });
1140
1667
  }
1141
- const paramTypes = params.map((param) => renderSchemaType({
1142
- schema: param.schema,
1143
- name: resolver.resolveParamName(node, param)
1144
- }));
1668
+ /**
1669
+ * Emits an individual type per content type plus a plain union alias under `baseName`. Shared by
1670
+ * the request body and multi-content-type responses. The response record discriminates these
1671
+ * variants by content type in {@link buildResponses}; the standalone alias stays a plain union so
1672
+ * query hooks and `result.data` keep the bare body.
1673
+ */
1674
+ function buildContentTypeVariants(entries, baseName, decorate) {
1675
+ const variants = resolveContentTypeVariants(entries, baseName);
1676
+ const unionSchema = ast.factory.createSchema({
1677
+ type: "union",
1678
+ members: variants.map((variant) => ast.factory.createSchema({
1679
+ type: "ref",
1680
+ name: variant.name
1681
+ }))
1682
+ });
1683
+ return /* @__PURE__ */ jsxs(Fragment, { children: [variants.map((variant) => renderSchemaType({
1684
+ schema: decorate ? decorate(variant.schema) : variant.schema,
1685
+ name: variant.name,
1686
+ keysToOmit: variant.keysToOmit
1687
+ })), renderSchemaType({
1688
+ schema: unionSchema,
1689
+ name: baseName
1690
+ })] });
1691
+ }
1692
+ const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node);
1693
+ const paramGroupTypes = [
1694
+ pathParams.length > 0 && renderSchemaType({
1695
+ schema: buildParams({ params: pathParams }),
1696
+ name: resolver.param.path(node, pathParams[0])
1697
+ }),
1698
+ queryParams.length > 0 && renderSchemaType({
1699
+ schema: buildParams({ params: queryParams }),
1700
+ name: resolver.param.query(node, queryParams[0])
1701
+ }),
1702
+ headerParams.length > 0 && renderSchemaType({
1703
+ schema: buildParams({ params: headerParams }),
1704
+ name: resolver.param.headers(node, headerParams[0])
1705
+ })
1706
+ ];
1145
1707
  const requestBodyContent = node.requestBody?.content ?? [];
1146
1708
  function buildRequestType() {
1147
1709
  if (requestBodyContent.length === 0) return null;
@@ -1153,67 +1715,40 @@ const typeGenerator = defineGenerator({
1153
1715
  ...entry.schema,
1154
1716
  description: node.requestBody.description ?? entry.schema.description
1155
1717
  },
1156
- name: resolver.resolveDataName(node),
1718
+ name: resolver.response.body(node),
1157
1719
  keysToOmit: entry.keysToOmit
1158
1720
  });
1159
1721
  }
1160
- const dataName = resolver.resolveDataName(node);
1161
- const usedNames = /* @__PURE__ */ new Set();
1162
- const individualItems = requestBodyContent.filter((entry) => entry.schema).map((entry) => {
1163
- const baseSuffix = getContentTypeSuffix(entry.contentType);
1164
- let individualName = getPerContentTypeName(dataName, baseSuffix);
1165
- let counter = 2;
1166
- while (usedNames.has(individualName)) individualName = getPerContentTypeName(dataName, `${baseSuffix}${counter++}`);
1167
- usedNames.add(individualName);
1168
- return {
1169
- name: individualName,
1170
- rendered: renderSchemaType({
1171
- schema: {
1172
- ...entry.schema,
1173
- description: node.requestBody.description ?? entry.schema.description
1174
- },
1175
- name: individualName,
1176
- keysToOmit: entry.keysToOmit
1177
- })
1178
- };
1179
- });
1180
- const unionType = renderSchemaType({
1181
- schema: ast.createSchema({
1182
- type: "union",
1183
- members: individualItems.map((item) => ast.createSchema({
1184
- type: "ref",
1185
- name: item.name
1186
- }))
1187
- }),
1188
- name: dataName
1189
- });
1190
- return /* @__PURE__ */ jsxs(Fragment, { children: [individualItems.map((item) => item.rendered), unionType] });
1722
+ return buildContentTypeVariants(requestBodyContent, resolver.response.body(node), (schema) => ({
1723
+ ...schema,
1724
+ description: node.requestBody.description ?? schema.description
1725
+ }));
1191
1726
  }
1192
1727
  const requestType = buildRequestType();
1193
- const responseTypes = node.responses.map((res) => renderSchemaType({
1194
- schema: res.schema,
1195
- name: resolver.resolveResponseStatusName(node, res.statusCode),
1196
- keysToOmit: res.keysToOmit
1197
- }));
1198
- const dataType = renderSchemaType({
1199
- schema: buildData({
1200
- ...node,
1201
- parameters: params
1202
- }, { resolver }),
1203
- name: resolver.resolveRequestConfigName(node)
1728
+ const responseTypes = node.responses.map((res) => {
1729
+ const variants = (res.content ?? []).filter((entry) => entry.schema);
1730
+ if (variants.length > 1) return buildContentTypeVariants(variants, resolver.response.status(node, res.statusCode));
1731
+ const primary = variants[0] ?? res.content?.[0];
1732
+ return renderSchemaType({
1733
+ schema: primary?.schema ?? null,
1734
+ name: resolver.response.status(node, res.statusCode),
1735
+ keysToOmit: primary?.keysToOmit
1736
+ });
1737
+ });
1738
+ const optionsType = renderSchemaType({
1739
+ schema: buildOptionsSchema(node, resolver),
1740
+ name: resolver.response.options(node)
1204
1741
  });
1205
1742
  const responsesType = renderSchemaType({
1206
1743
  schema: buildResponses(node, { resolver }),
1207
- name: resolver.resolveResponsesName(node)
1744
+ name: resolver.response.responses(node)
1208
1745
  });
1209
1746
  function buildResponseType() {
1210
- if (!node.responses.some((res) => res.schema)) return null;
1211
- const responseName = resolver.resolveResponseName(node);
1212
- const responsesWithSchema = node.responses.filter((res) => res.schema);
1213
- if (new Set(responsesWithSchema.flatMap((res) => res.schema ? adapter.getImports(res.schema, (schemaName) => ({
1214
- name: resolveImportName(schemaName),
1215
- path: ""
1216
- })).flatMap((imp) => Array.isArray(imp.name) ? imp.name : [imp.name]) : [])).has(responseName)) return null;
1747
+ const hasSchema = (res) => (res.content ?? []).some((entry) => entry.schema);
1748
+ if (!node.responses.some(hasSchema)) return null;
1749
+ const responseName = resolver.response.response(node);
1750
+ const responsesWithSchema = node.responses.filter(hasSchema);
1751
+ if (new Set(responsesWithSchema.flatMap((res) => (res.content ?? []).flatMap((entry) => entry.schema ? collectRefNames(entry.schema).map(importName) : []))).has(responseName)) return null;
1217
1752
  return renderSchemaType({
1218
1753
  schema: {
1219
1754
  ...buildResponseUnion(node, { resolver }),
@@ -1227,19 +1762,27 @@ const typeGenerator = defineGenerator({
1227
1762
  baseName: meta.file.baseName,
1228
1763
  path: meta.file.path,
1229
1764
  meta: meta.file.meta,
1230
- banner: resolver.resolveBanner(adapter.inputNode, {
1765
+ banner: resolver.default.banner(ctx.meta, {
1231
1766
  output,
1232
- config
1767
+ config,
1768
+ file: {
1769
+ path: meta.file.path,
1770
+ baseName: meta.file.baseName
1771
+ }
1233
1772
  }),
1234
- footer: resolver.resolveFooter(adapter.inputNode, {
1773
+ footer: resolver.default.footer(ctx.meta, {
1235
1774
  output,
1236
- config
1775
+ config,
1776
+ file: {
1777
+ path: meta.file.path,
1778
+ baseName: meta.file.baseName
1779
+ }
1237
1780
  }),
1238
1781
  children: [
1239
- paramTypes,
1782
+ paramGroupTypes,
1240
1783
  responseTypes,
1241
1784
  requestType,
1242
- dataType,
1785
+ optionsType,
1243
1786
  responsesType,
1244
1787
  responseType
1245
1788
  ]
@@ -1249,101 +1792,84 @@ const typeGenerator = defineGenerator({
1249
1792
  //#endregion
1250
1793
  //#region src/resolvers/resolverTs.ts
1251
1794
  /**
1252
- * Resolver for `@kubb/plugin-ts` that provides the default naming and path-resolution
1253
- * helpers used by the plugin. Import this in other plugins to resolve the exact names and
1254
- * paths that `plugin-ts` generates without hardcoding the conventions.
1795
+ * Default resolver used by `@kubb/plugin-ts`. Decides the names and file paths
1796
+ * for every generated TypeScript type. Import this in other plugins that need
1797
+ * to reference the exact names `plugin-ts` produces without duplicating the
1798
+ * casing/file-layout rules.
1255
1799
  *
1256
- * The `default` method is automatically injected by `defineResolver` — it uses `camelCase`
1257
- * for identifiers/files and `pascalCase` for type names.
1800
+ * The `default` helpers are supplied by `createResolver`. This plugin overrides the top-level `name`
1801
+ * to use PascalCase for value and type names and `file` to write PascalCase file paths (dotted names
1802
+ * become `/`-joined), and groups the operation-specific naming under the `param`, `response`, and
1803
+ * `enum` namespaces.
1258
1804
  *
1259
- * @example
1805
+ * @example Resolve a type and file name
1260
1806
  * ```ts
1261
- * import { resolver } from '@kubb/plugin-ts'
1807
+ * import { resolverTs } from '@kubb/plugin-ts'
1262
1808
  *
1263
- * resolver.default('list pets', 'type') // → 'ListPets'
1264
- * resolver.resolveName('list pets status 200') // → 'ListPetsStatus200'
1265
- * resolver.resolvePathName('list pets', 'file') // → 'listPets'
1809
+ * resolverTs.name('list pets') // 'ListPets'
1810
+ * resolverTs.response.status(node, 200) // 'ListPetsStatus200'
1266
1811
  * ```
1267
1812
  */
1268
- const resolverTs = defineResolver(() => {
1269
- return {
1270
- name: "default",
1271
- pluginName: "plugin-ts",
1272
- default(name, type) {
1273
- return pascalCase(name, { isFile: type === "file" });
1274
- },
1275
- resolveTypeName(name) {
1276
- return pascalCase(name);
1277
- },
1278
- resolvePathName(name, type) {
1279
- return pascalCase(name, { isFile: type === "file" });
1280
- },
1281
- resolveParamName(node, param) {
1282
- return this.resolveTypeName(`${node.operationId} ${param.in} ${param.name}`);
1283
- },
1284
- resolveResponseStatusName(node, statusCode) {
1285
- return this.resolveTypeName(`${node.operationId} Status ${statusCode}`);
1286
- },
1287
- resolveDataName(node) {
1288
- return this.resolveTypeName(`${node.operationId} Data`);
1289
- },
1290
- resolveRequestConfigName(node) {
1291
- return this.resolveTypeName(`${node.operationId} RequestConfig`);
1292
- },
1293
- resolveResponsesName(node) {
1294
- return this.resolveTypeName(`${node.operationId} Responses`);
1295
- },
1296
- resolveResponseName(node) {
1297
- return this.resolveTypeName(`${node.operationId} Response`);
1298
- },
1299
- resolveEnumKeyName(node, enumTypeSuffix = "key") {
1300
- return `${this.resolveTypeName(node.name ?? "")}${enumTypeSuffix}`;
1301
- },
1302
- resolvePathParamsName(node, param) {
1303
- return this.resolveParamName(node, param);
1304
- },
1305
- resolveQueryParamsName(node, param) {
1306
- return this.resolveParamName(node, param);
1307
- },
1308
- resolveHeaderParamsName(node, param) {
1309
- return this.resolveParamName(node, param);
1813
+ const resolverTs = createResolver({
1814
+ pluginName: "plugin-ts",
1815
+ name(name) {
1816
+ return ensureValidVarName(pascalCase(name));
1817
+ },
1818
+ file: createCasedFile(pascalCase),
1819
+ param: createOperationParamResolver(),
1820
+ response: {
1821
+ ...createOperationResponseResolver(),
1822
+ options(node) {
1823
+ return this.name(`${node.operationId} Options`);
1310
1824
  }
1311
- };
1825
+ },
1826
+ enum: { keyName(node, enumTypeSuffix = "key") {
1827
+ return `${this.name(node.name ?? "")}${enumTypeSuffix}`;
1828
+ } }
1312
1829
  });
1313
1830
  //#endregion
1314
1831
  //#region src/plugin.ts
1315
1832
  /**
1316
- * Canonical plugin name for `@kubb/plugin-ts`, used to identify the plugin in driver lookups and warnings.
1833
+ * Canonical plugin name for `@kubb/plugin-ts`. Used for driver lookups and
1834
+ * cross-plugin dependency references.
1317
1835
  */
1318
1836
  const pluginTsName = "plugin-ts";
1319
1837
  /**
1320
- * The `@kubb/plugin-ts` plugin factory.
1321
- *
1322
- * Generates TypeScript type declarations from an OpenAPI/AST `RootNode`.
1323
- * Walks schemas and operations, delegates rendering to the active generators,
1324
- * and writes barrel files based on `output.barrelType`.
1838
+ * Generates TypeScript `type` aliases and `interface` declarations from an
1839
+ * OpenAPI spec. The foundation that every other Kubb plugin builds on:
1840
+ * clients, query hooks, mocks, and validators all reference the names this
1841
+ * plugin produces.
1325
1842
  *
1326
1843
  * @example
1327
1844
  * ```ts
1328
- * import pluginTs from '@kubb/plugin-ts'
1845
+ * import { defineConfig } from 'kubb/config'
1846
+ * import { pluginTs } from '@kubb/plugin-ts'
1329
1847
  *
1330
1848
  * export default defineConfig({
1331
- * plugins: [pluginTs({ output: { path: 'types' }, enumType: 'asConst' })],
1849
+ * input: './petStore.yaml',
1850
+ * output: { path: './src/gen' },
1851
+ * plugins: [
1852
+ * pluginTs({
1853
+ * output: { path: './types' },
1854
+ * enum: { type: 'asConst' },
1855
+ * optionalType: 'questionTokenAndUndefined',
1856
+ * }),
1857
+ * ],
1332
1858
  * })
1333
1859
  * ```
1334
1860
  */
1335
1861
  const pluginTs = definePlugin((options) => {
1336
1862
  const { output = {
1337
1863
  path: "types",
1338
- barrelType: "named"
1339
- }, group, exclude = [], include, override = [], enumType = "asConst", enumTypeSuffix = "Key", enumKeyCasing = "none", optionalType = "questionToken", arrayType = "array", syntaxType = "type", paramsCasing, printer, resolver: userResolver, transformer: userTransformer, generators: userGenerators = [] } = options;
1340
- const groupConfig = group ? {
1341
- ...group,
1342
- name: (ctx) => {
1343
- if (group.type === "path") return `${ctx.group.split("/")[1]}`;
1344
- return `${camelCase(ctx.group)}Controller`;
1345
- }
1346
- } : void 0;
1864
+ barrel: { type: "named" }
1865
+ }, group, exclude = [], include, override = [], enum: enumOptions = {}, optionalType = "questionToken", arrayType = "array", syntaxType = "type", printer, resolver: userResolver, macros: userMacros } = options;
1866
+ const groupConfig = createGroupConfig(group);
1867
+ const resolvedEnum = {
1868
+ type: enumOptions.type ?? "asConst",
1869
+ constCasing: enumOptions.constCasing ?? "camelCase",
1870
+ typeSuffix: enumOptions.typeSuffix ?? "Key",
1871
+ keyCasing: enumOptions.keyCasing ?? "none"
1872
+ };
1347
1873
  return {
1348
1874
  name: pluginTsName,
1349
1875
  options,
@@ -1356,56 +1882,236 @@ const pluginTs = definePlugin((options) => {
1356
1882
  optionalType,
1357
1883
  group: groupConfig,
1358
1884
  arrayType,
1359
- enumType,
1360
- enumTypeSuffix,
1361
- enumKeyCasing,
1885
+ enum: resolvedEnum,
1362
1886
  syntaxType,
1363
- paramsCasing,
1364
1887
  printer
1365
1888
  });
1366
- ctx.setResolver(userResolver ? {
1367
- ...resolverTs,
1368
- ...userResolver
1369
- } : resolverTs);
1370
- if (userTransformer) ctx.setTransformer(userTransformer);
1889
+ ctx.setResolver(userResolver ? Resolver.merge(resolverTs, userResolver) : resolverTs);
1890
+ if (userMacros?.length) ctx.setMacros(userMacros);
1371
1891
  ctx.addGenerator(typeGenerator);
1372
- for (const gen of userGenerators) ctx.addGenerator(gen);
1373
1892
  } }
1374
1893
  };
1375
1894
  });
1376
1895
  //#endregion
1896
+ //#region src/printers/functionParams.ts
1897
+ /**
1898
+ * Narrows a {@link TypeExpression} to a {@link TypeLiteralNode}.
1899
+ */
1900
+ function isTypeLiteral(type) {
1901
+ return typeof type !== "string" && type.kind === "TypeLiteral";
1902
+ }
1903
+ /**
1904
+ * Narrows a {@link TypeExpression} to an {@link IndexedAccessTypeNode}.
1905
+ */
1906
+ function isIndexedAccessType(type) {
1907
+ return typeof type !== "string" && type.kind === "IndexedAccessType";
1908
+ }
1909
+ /**
1910
+ * Creates a {@link TypeLiteralNode} representing an inline anonymous object type.
1911
+ *
1912
+ * @example
1913
+ * ```ts
1914
+ * createTypeLiteral({ members: [{ name: 'petId', type: 'string', optional: false }] })
1915
+ * // { petId: string }
1916
+ * ```
1917
+ */
1918
+ function createTypeLiteral(props) {
1919
+ return {
1920
+ kind: "TypeLiteral",
1921
+ ...props
1922
+ };
1923
+ }
1924
+ /**
1925
+ * Creates an {@link IndexedAccessTypeNode} representing a single field accessed from a named type.
1926
+ *
1927
+ * @example
1928
+ * ```ts
1929
+ * createIndexedAccessType({ target: 'DeletePetPathParams', key: 'petId' })
1930
+ * // DeletePetPathParams['petId']
1931
+ * ```
1932
+ */
1933
+ function createIndexedAccessType(props) {
1934
+ return {
1935
+ kind: "IndexedAccessType",
1936
+ ...props
1937
+ };
1938
+ }
1939
+ /**
1940
+ * Creates an {@link ObjectBindingPatternNode} for a destructured parameter binding.
1941
+ *
1942
+ * @example
1943
+ * ```ts
1944
+ * createObjectBindingPattern({ elements: [{ name: 'id' }, { name: 'name' }] })
1945
+ * // { id, name }
1946
+ * ```
1947
+ */
1948
+ function createObjectBindingPattern(props) {
1949
+ return {
1950
+ kind: "ObjectBindingPattern",
1951
+ ...props
1952
+ };
1953
+ }
1954
+ /**
1955
+ * Creates a {@link FunctionParameterNode}. `optional` defaults to `false`.
1956
+ * Passing `properties` builds a destructured group: an {@link ObjectBindingPatternNode} name
1957
+ * paired with a {@link TypeLiteralNode} type.
1958
+ *
1959
+ * @example Optional param
1960
+ * ```ts
1961
+ * createFunctionParameter({ name: 'params', type: 'QueryParams', optional: true })
1962
+ * // → params?: QueryParams
1963
+ * ```
1964
+ *
1965
+ * @example Destructured group
1966
+ * ```ts
1967
+ * createFunctionParameter({ properties: [{ name: 'id', type: 'string' }, { name: 'name', type: 'string', optional: true }], default: '{}' })
1968
+ * // → { id, name }: { id: string; name?: string } = {}
1969
+ * ```
1970
+ */
1971
+ function createFunctionParameter(input) {
1972
+ if ("properties" in input) return {
1973
+ kind: "FunctionParameter",
1974
+ name: createObjectBindingPattern({ elements: input.properties.map((p) => ({ name: p.name })) }),
1975
+ type: createTypeLiteral({ members: input.properties.map((p) => ({
1976
+ name: p.name,
1977
+ type: p.type,
1978
+ optional: p.optional ?? false
1979
+ })) }),
1980
+ optional: input.optional ?? false,
1981
+ ...input.default !== void 0 ? { default: input.default } : {}
1982
+ };
1983
+ return {
1984
+ kind: "FunctionParameter",
1985
+ optional: false,
1986
+ ...input
1987
+ };
1988
+ }
1989
+ /**
1990
+ * Creates a {@link FunctionParametersNode} from an ordered list of parameters.
1991
+ *
1992
+ * @example
1993
+ * ```ts
1994
+ * const empty = createFunctionParameters()
1995
+ * // { params: [] }
1996
+ * ```
1997
+ */
1998
+ function createFunctionParameters(props = {}) {
1999
+ return {
2000
+ kind: "FunctionParameters",
2001
+ params: [],
2002
+ ...props
2003
+ };
2004
+ }
2005
+ //#endregion
1377
2006
  //#region src/printers/functionPrinter.ts
1378
- const kindToHandlerKey = {
1379
- FunctionParameter: "functionParameter",
1380
- ParameterGroup: "parameterGroup",
1381
- FunctionParameters: "functionParameters",
1382
- ParamsType: "paramsType"
1383
- };
1384
2007
  /**
1385
- * Creates a function-parameter printer factory.
2008
+ * Renders a {@link TypeExpression} to its TypeScript source.
2009
+ *
2010
+ * - a `string` is a type reference, returned as-is
2011
+ * - an `IndexedAccessType` becomes `objectType['indexType']`
2012
+ * - a `TypeLiteral` becomes `{ key: Type; key?: Type }`
1386
2013
  *
1387
- * Uses `createPrinterFactory` and dispatches handlers by `node.kind`
1388
- * (for function nodes) rather than by `node.type` (for schema nodes).
2014
+ * `transformType` is applied once to the fully rendered type, matching how the
2015
+ * printer wrapped reference types before the `ts.factory` model.
2016
+ */
2017
+ function renderType(type, transformType) {
2018
+ const rendered = renderTypeExpression(type);
2019
+ return transformType ? transformType(rendered) : rendered;
2020
+ }
2021
+ /**
2022
+ * Renders an object-type key, quoting it as a string literal when it is not a valid
2023
+ * identifier so `{ 'content-type': string }` stays valid TypeScript.
1389
2024
  */
1390
- const defineFunctionPrinter = ast.createPrinterFactory((node) => kindToHandlerKey[node.kind]);
2025
+ function renderKey(name) {
2026
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
2027
+ }
2028
+ function renderTypeExpression(type) {
2029
+ if (typeof type === "string") return type;
2030
+ if (isIndexedAccessType(type)) return `${type.target}['${type.key}']`;
2031
+ return `{ ${type.members.map((member) => {
2032
+ const value = renderTypeExpression(member.type);
2033
+ return member.optional ? `${renderKey(member.name)}?: ${value}` : `${renderKey(member.name)}: ${value}`;
2034
+ }).join("; ")} }`;
2035
+ }
2036
+ function groupMembers(param) {
2037
+ if (typeof param.name === "string") return null;
2038
+ return param.type && isTypeLiteral(param.type) ? param.type.members : [];
2039
+ }
1391
2040
  function rank(param) {
1392
- if (param.kind === "ParameterGroup") {
1393
- if (param.default) return PARAM_RANK.withDefault;
1394
- return param.optional ?? param.properties.every((p) => p.optional || p.default !== void 0) ? PARAM_RANK.optional : PARAM_RANK.required;
1395
- }
1396
2041
  if (param.rest) return PARAM_RANK.rest;
1397
2042
  if (param.default) return PARAM_RANK.withDefault;
2043
+ const members = groupMembers(param);
2044
+ if (members) return members.every((m) => m.optional) ? PARAM_RANK.optional : PARAM_RANK.required;
1398
2045
  return param.optional ? PARAM_RANK.optional : PARAM_RANK.required;
1399
2046
  }
1400
2047
  function sortParams(params) {
1401
- return [...params].sort((a, b) => rank(a) - rank(b));
2048
+ return params.toSorted((a, b) => rank(a) - rank(b));
2049
+ }
2050
+ function sortedGroupMembers(name, type) {
2051
+ const members = type && isTypeLiteral(type) ? type.members : [];
2052
+ const memberRank = (optional) => optional ? PARAM_RANK.optional : PARAM_RANK.required;
2053
+ return name.elements.map((element, index) => ({
2054
+ name: element.name,
2055
+ propertyName: element.propertyName,
2056
+ type: members[index]?.type,
2057
+ optional: members[index]?.optional
2058
+ })).toSorted((a, b) => memberRank(a.optional) - memberRank(b.optional));
2059
+ }
2060
+ /**
2061
+ * Renders one binding element: the local name, prefixed with its source key when the
2062
+ * binding renames the property, as in `{ petId: id }`.
2063
+ */
2064
+ function renderBindingMember(member, transformName) {
2065
+ const local = transformName ? transformName(member.name) : member.name;
2066
+ return member.propertyName ? `${member.propertyName}: ${local}` : local;
1402
2067
  }
1403
- function sortChildParams(params) {
1404
- return [...params].sort((a, b) => rank(a) - rank(b));
2068
+ /**
2069
+ * Renders the type annotation of a destructured group. An inline object type is
2070
+ * rendered from the already-sorted members so its key order matches the binding;
2071
+ * a reference type is rendered as-is.
2072
+ */
2073
+ function renderGroupType(type, sorted, transformType) {
2074
+ if (!type) return void 0;
2075
+ if (!isTypeLiteral(type)) return renderType(type);
2076
+ const typed = sorted.filter((member) => member.type !== void 0);
2077
+ if (!typed.length) return void 0;
2078
+ return `{ ${typed.map((member) => {
2079
+ const key = renderKey(member.propertyName ?? member.name);
2080
+ const value = renderType(member.type, transformType);
2081
+ return member.optional ? `${key}?: ${value}` : `${key}: ${value}`;
2082
+ }).join("; ")} }`;
2083
+ }
2084
+ function printParameter(node, options) {
2085
+ const { mode, transformName, transformType } = options;
2086
+ const bindingName = node.name;
2087
+ if (mode === "call") {
2088
+ if (typeof bindingName !== "string") return `{ ${sortedGroupMembers(bindingName, node.type).map((member) => renderBindingMember(member)).join(", ")} }`;
2089
+ const name = transformName ? transformName(bindingName) : bindingName;
2090
+ return node.rest ? `...${name}` : name;
2091
+ }
2092
+ if (typeof bindingName !== "string") {
2093
+ const sorted = sortedGroupMembers(bindingName, node.type);
2094
+ const binding = `{ ${sorted.map((member) => renderBindingMember(member, transformName)).join(", ")} }`;
2095
+ const allOptional = sorted.every((member) => member.optional);
2096
+ const type = renderGroupType(node.type, sorted, transformType);
2097
+ if (type) {
2098
+ if (allOptional) return `${binding}: ${type} = ${node.default ?? "{}"}`;
2099
+ return node.default ? `${binding}: ${type} = ${node.default}` : `${binding}: ${type}`;
2100
+ }
2101
+ return node.default ? `${binding} = ${node.default}` : binding;
2102
+ }
2103
+ const name = transformName ? transformName(bindingName) : bindingName;
2104
+ const type = node.type ? renderType(node.type, transformType) : void 0;
2105
+ if (node.rest) return type ? `...${name}: ${type}` : `...${name}`;
2106
+ if (type) {
2107
+ if (node.optional) return `${name}?: ${type}`;
2108
+ return node.default ? `${name}: ${type} = ${node.default}` : `${name}: ${type}`;
2109
+ }
2110
+ return node.default ? `${name} = ${node.default}` : name;
1405
2111
  }
1406
2112
  /**
1407
- * Default function-signature printer.
1408
- * Covers the four standard output modes used across Kubb plugins.
2113
+ * Default function-signature printer. Renders a parameter list in one of two modes:
2114
+ * `declaration` for the function signature and `call` for the call arguments.
1409
2115
  *
1410
2116
  * @example
1411
2117
  * ```ts
@@ -1421,68 +2127,195 @@ function sortChildParams(params) {
1421
2127
  * printer.print(sig) // → "petId: string, config: Config = {}"
1422
2128
  * ```
1423
2129
  */
1424
- const functionPrinter = defineFunctionPrinter((options) => ({
1425
- name: "functionParameters",
1426
- options,
1427
- nodes: {
1428
- paramsType(node) {
1429
- if (node.kind !== "ParamsType") return null;
1430
- if (node.variant === "member") return `${node.base}['${node.key}']`;
1431
- if (node.variant === "struct") return `{ ${node.properties.map((p) => {
1432
- const typeStr = this.transform(p.type);
1433
- const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(p.name) ? p.name : JSON.stringify(p.name);
1434
- return p.optional ? `${key}?: ${typeStr}` : `${key}: ${typeStr}`;
1435
- }).join("; ")} }`;
1436
- if (node.variant === "reference") return node.name;
1437
- return null;
1438
- },
1439
- functionParameter(node) {
1440
- const { mode, transformName, transformType } = this.options;
1441
- const name = transformName ? transformName(node.name) : node.name;
1442
- const rawType = node.type ? this.transform(node.type) : void 0;
1443
- const type = rawType != null && transformType ? transformType(rawType) : rawType;
1444
- if (mode === "keys" || mode === "values") return node.rest ? `...${name}` : name;
1445
- if (mode === "call") return node.rest ? `...${name}` : name;
1446
- if (node.rest) return type ? `...${name}: ${type}` : `...${name}`;
1447
- if (type) {
1448
- if (node.optional) return `${name}?: ${type}`;
1449
- return node.default ? `${name}: ${type} = ${node.default}` : `${name}: ${type}`;
1450
- }
1451
- return node.default ? `${name} = ${node.default}` : name;
1452
- },
1453
- parameterGroup(node) {
1454
- const { mode, transformName, transformType } = this.options;
1455
- const sorted = sortChildParams(node.properties);
1456
- const isOptional = node.optional ?? sorted.every((p) => p.optional || p.default !== void 0);
1457
- if (node.inline) return sorted.map((p) => this.transform(p)).filter(Boolean).join(", ");
1458
- if (mode === "keys" || mode === "values") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
1459
- if (mode === "call") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
1460
- const names = sorted.map((p) => {
1461
- return transformName ? transformName(p.name) : p.name;
1462
- });
1463
- const nameStr = names.length ? `{ ${names.join(", ")} }` : void 0;
1464
- if (!nameStr) return null;
1465
- let typeAnnotation = node.type ? this.transform(node.type) ?? void 0 : void 0;
1466
- if (!typeAnnotation) {
1467
- const typeParts = sorted.filter((p) => p.type).map((p) => {
1468
- const rawT = p.type ? this.transform(p.type) : void 0;
1469
- const t = rawT != null && transformType ? transformType(rawT) : rawT;
1470
- return p.optional || p.default !== void 0 ? `${p.name}?: ${t}` : `${p.name}: ${t}`;
1471
- });
1472
- typeAnnotation = typeParts.length ? `{ ${typeParts.join("; ")} }` : void 0;
1473
- }
1474
- if (typeAnnotation) {
1475
- if (isOptional) return `${nameStr}: ${typeAnnotation} = ${node.default ?? "{}"}`;
1476
- return node.default ? `${nameStr}: ${typeAnnotation} = ${node.default}` : `${nameStr}: ${typeAnnotation}`;
1477
- }
1478
- return node.default ? `${nameStr} = ${node.default}` : nameStr;
1479
- },
1480
- functionParameters(node) {
1481
- return sortParams(node.params).map((p) => this.transform(p)).filter(Boolean).join(", ");
2130
+ function functionPrinter(options) {
2131
+ return {
2132
+ name: "functionParameters",
2133
+ options,
2134
+ print(node) {
2135
+ return sortParams(node.params).map((p) => printParameter(p, options)).filter(Boolean).join(", ");
2136
+ }
2137
+ };
2138
+ }
2139
+ //#endregion
2140
+ //#region src/printers/operationParams.ts
2141
+ /**
2142
+ * Resolves the {@link TypeExpression} for an individual parameter.
2143
+ *
2144
+ * Without a resolver, it falls back to the schema primitive (a plain type-name string). When the
2145
+ * parameter belongs to a named group, it emits an `IndexedAccessType` like `GroupParams['petId']`,
2146
+ * otherwise the resolved individual name.
2147
+ */
2148
+ function resolveParamType({ node, param, resolver }) {
2149
+ if (!resolver) return param.schema.primitive ?? "unknown";
2150
+ const individualName = resolver.param.name(node, param);
2151
+ const groupLocation = param.in === "path" || param.in === "query" || param.in === "header" ? param.in : void 0;
2152
+ const groupResolvers = {
2153
+ path: resolver.param.path,
2154
+ query: resolver.param.query,
2155
+ header: resolver.param.headers
2156
+ };
2157
+ const groupName = groupLocation ? groupResolvers[groupLocation].call(resolver, node, param) : void 0;
2158
+ if (groupName && groupName !== individualName) return createIndexedAccessType({
2159
+ target: groupName,
2160
+ key: param.name
2161
+ });
2162
+ return individualName;
2163
+ }
2164
+ /**
2165
+ * Derives a {@link ParamGroupType} for a query or header group from the resolver.
2166
+ *
2167
+ * Returns `null` when there is no resolver, no params, or the group name equals the
2168
+ * individual param name (so there is no real group to emit).
2169
+ */
2170
+ function resolveGroupType({ node, params, group, resolver }) {
2171
+ if (!resolver || !params.length) return null;
2172
+ const firstParam = params[0];
2173
+ const groupName = (group === "query" ? resolver.param.query : resolver.param.headers).call(resolver, node, firstParam);
2174
+ if (groupName === resolver.param.name(node, firstParam)) return null;
2175
+ return {
2176
+ type: groupName,
2177
+ optional: params.every((p) => !p.required)
2178
+ };
2179
+ }
2180
+ /**
2181
+ * Converts an `OperationNode` into function parameters for code generation.
2182
+ *
2183
+ * Centralizes parameter grouping logic for all plugins. `paramsType` chooses between one
2184
+ * destructured object parameter (`object`) and separate top-level parameters (`inline`), while
2185
+ * `pathParamsType` controls how path params render in inline mode. Provide a `resolver` for type
2186
+ * name resolution and `extraParams` for plugin-specific trailing parameters such as an `options` object.
2187
+ */
2188
+ function createOperationParams(node, options) {
2189
+ const { paramsType, pathParamsType, resolver, pathParamsDefault, extraParams = [], paramNames, typeWrapper } = options;
2190
+ const dataName = paramNames?.data ?? "data";
2191
+ const paramsName = paramNames?.params ?? "params";
2192
+ const headersName = paramNames?.headers ?? "headers";
2193
+ const pathName = paramNames?.path ?? "pathParams";
2194
+ const wrapType = (type) => typeWrapper ? typeWrapper(type) : type;
2195
+ const wrapTypeExpression = (type) => typeof type === "string" ? wrapType(type) : type;
2196
+ const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node);
2197
+ const toProperty = (param) => ({
2198
+ name: param.name,
2199
+ type: wrapTypeExpression(resolveParamType({
2200
+ node,
2201
+ param,
2202
+ resolver
2203
+ })),
2204
+ optional: !param.required
2205
+ });
2206
+ const emptyObjectDefault = (props) => props.every((p) => p.optional) ? "{}" : void 0;
2207
+ const bodyType = node.requestBody?.content?.[0]?.schema ? wrapType(resolver?.response.body(node) ?? "unknown") : void 0;
2208
+ const bodyProperty = bodyType ? [{
2209
+ name: dataName,
2210
+ type: bodyType,
2211
+ optional: !(node.requestBody?.required ?? false)
2212
+ }] : [];
2213
+ const trailingGroups = [{
2214
+ name: paramsName,
2215
+ node,
2216
+ params: queryParams,
2217
+ groupType: resolveGroupType({
2218
+ node,
2219
+ params: queryParams,
2220
+ group: "query",
2221
+ resolver
2222
+ }),
2223
+ resolver,
2224
+ wrapType
2225
+ }, {
2226
+ name: headersName,
2227
+ node,
2228
+ params: headerParams,
2229
+ groupType: resolveGroupType({
2230
+ node,
2231
+ params: headerParams,
2232
+ group: "header",
2233
+ resolver
2234
+ }),
2235
+ resolver,
2236
+ wrapType
2237
+ }];
2238
+ const params = [];
2239
+ if (paramsType === "object") {
2240
+ const children = [
2241
+ ...pathParams.map(toProperty),
2242
+ ...bodyProperty,
2243
+ ...trailingGroups.flatMap(buildGroupProperty)
2244
+ ];
2245
+ if (children.length) params.push(createFunctionParameter({
2246
+ properties: children,
2247
+ default: emptyObjectDefault(children)
2248
+ }));
2249
+ } else {
2250
+ if (pathParamsType === "inlineSpread" && pathParams.length) {
2251
+ const spreadType = resolver?.param.path(node, pathParams[0]);
2252
+ params.push(createFunctionParameter({
2253
+ name: pathName,
2254
+ type: spreadType ? wrapType(spreadType) : void 0,
2255
+ rest: true
2256
+ }));
2257
+ } else if (pathParamsType === "inline") params.push(...pathParams.map((p) => createFunctionParameter(toProperty(p))));
2258
+ else if (pathParams.length) {
2259
+ const pathChildren = pathParams.map(toProperty);
2260
+ params.push(createFunctionParameter({
2261
+ properties: pathChildren,
2262
+ default: pathParamsDefault ?? emptyObjectDefault(pathChildren)
2263
+ }));
1482
2264
  }
2265
+ params.push(...bodyProperty.map((p) => createFunctionParameter(p)));
2266
+ params.push(...trailingGroups.flatMap(buildGroupParam));
1483
2267
  }
1484
- }));
2268
+ params.push(...extraParams);
2269
+ return createFunctionParameters({ params });
2270
+ }
2271
+ /**
2272
+ * Builds the property descriptor for a query or header group.
2273
+ * Returns an empty array when there are no params to emit.
2274
+ *
2275
+ * A pre-resolved `groupType` emits `name: GroupType`. Otherwise it builds an inline
2276
+ * {@link TypeLiteralNode} from the individual params.
2277
+ */
2278
+ function buildGroupProperty({ name, node, params, groupType, resolver, wrapType }) {
2279
+ if (groupType) return [{
2280
+ name,
2281
+ type: typeof groupType.type === "string" ? wrapType(groupType.type) : groupType.type,
2282
+ optional: groupType.optional
2283
+ }];
2284
+ if (params.length) return [{
2285
+ name,
2286
+ type: buildTypeLiteral({
2287
+ node,
2288
+ params,
2289
+ resolver
2290
+ }),
2291
+ optional: params.every((p) => !p.required)
2292
+ }];
2293
+ return [];
2294
+ }
2295
+ /**
2296
+ * Builds a single {@link FunctionParameterNode} for a query or header group.
2297
+ * Returns an empty array when there are no params to emit.
2298
+ */
2299
+ function buildGroupParam(args) {
2300
+ return buildGroupProperty(args).map((p) => createFunctionParameter(p));
2301
+ }
2302
+ /**
2303
+ * Builds a {@link TypeLiteralNode} for an inline anonymous type grouping named fields.
2304
+ *
2305
+ * Used when query or header parameters have no dedicated group type name.
2306
+ */
2307
+ function buildTypeLiteral({ node, params, resolver }) {
2308
+ return createTypeLiteral({ members: params.map((p) => ({
2309
+ name: p.name,
2310
+ type: resolveParamType({
2311
+ node,
2312
+ param: p,
2313
+ resolver
2314
+ }),
2315
+ optional: !p.required
2316
+ })) });
2317
+ }
1485
2318
  //#endregion
1486
- export { Enum, Type, pluginTs as default, pluginTs, functionPrinter, pluginTsName, printerTs, resolverTs, typeGenerator };
2319
+ export { Enum, Type, buildParams, createFunctionParameter, createFunctionParameters, createIndexedAccessType, createObjectBindingPattern, createOperationParams, createTypeLiteral, pluginTs as default, pluginTs, functionPrinter, pluginTsName, printerTs, renderType, resolverTs, typeGenerator };
1487
2320
 
1488
2321
  //# sourceMappingURL=index.js.map