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