@kubb/plugin-ts 5.0.0-beta.4 → 5.0.0-beta.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -5,6 +5,10 @@ Object.defineProperties(exports, {
5
5
  //#region \0rolldown/runtime.js
6
6
  var __create = Object.create;
7
7
  var __defProp = Object.defineProperty;
8
+ var __name = (target, value) => __defProp(target, "name", {
9
+ value,
10
+ configurable: true
11
+ });
8
12
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9
13
  var __getOwnPropNames = Object.getOwnPropertyNames;
10
14
  var __getProtoOf = Object.getPrototypeOf;
@@ -27,7 +31,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
31
  let _kubb_parser_ts = require("@kubb/parser-ts");
28
32
  let _kubb_renderer_jsx = require("@kubb/renderer-jsx");
29
33
  let _kubb_core = require("@kubb/core");
30
- let remeda = require("remeda");
31
34
  let typescript = require("typescript");
32
35
  typescript = __toESM(typescript, 1);
33
36
  let _kubb_renderer_jsx_jsx_runtime = require("@kubb/renderer-jsx/jsx-runtime");
@@ -167,6 +170,129 @@ function stringify(value) {
167
170
  return JSON.stringify(trimQuotes(value.toString()));
168
171
  }
169
172
  //#endregion
173
+ //#region ../../internals/utils/src/reserved.ts
174
+ /**
175
+ * JavaScript and Java reserved words.
176
+ * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
177
+ */
178
+ const reservedWords = new Set([
179
+ "abstract",
180
+ "arguments",
181
+ "boolean",
182
+ "break",
183
+ "byte",
184
+ "case",
185
+ "catch",
186
+ "char",
187
+ "class",
188
+ "const",
189
+ "continue",
190
+ "debugger",
191
+ "default",
192
+ "delete",
193
+ "do",
194
+ "double",
195
+ "else",
196
+ "enum",
197
+ "eval",
198
+ "export",
199
+ "extends",
200
+ "false",
201
+ "final",
202
+ "finally",
203
+ "float",
204
+ "for",
205
+ "function",
206
+ "goto",
207
+ "if",
208
+ "implements",
209
+ "import",
210
+ "in",
211
+ "instanceof",
212
+ "int",
213
+ "interface",
214
+ "let",
215
+ "long",
216
+ "native",
217
+ "new",
218
+ "null",
219
+ "package",
220
+ "private",
221
+ "protected",
222
+ "public",
223
+ "return",
224
+ "short",
225
+ "static",
226
+ "super",
227
+ "switch",
228
+ "synchronized",
229
+ "this",
230
+ "throw",
231
+ "throws",
232
+ "transient",
233
+ "true",
234
+ "try",
235
+ "typeof",
236
+ "var",
237
+ "void",
238
+ "volatile",
239
+ "while",
240
+ "with",
241
+ "yield",
242
+ "Array",
243
+ "Date",
244
+ "hasOwnProperty",
245
+ "Infinity",
246
+ "isFinite",
247
+ "isNaN",
248
+ "isPrototypeOf",
249
+ "length",
250
+ "Math",
251
+ "name",
252
+ "NaN",
253
+ "Number",
254
+ "Object",
255
+ "prototype",
256
+ "String",
257
+ "toString",
258
+ "undefined",
259
+ "valueOf"
260
+ ]);
261
+ /**
262
+ * Returns `true` when `name` is a syntactically valid JavaScript variable name.
263
+ *
264
+ * @example
265
+ * ```ts
266
+ * isValidVarName('status') // true
267
+ * isValidVarName('class') // false (reserved word)
268
+ * isValidVarName('42foo') // false (starts with digit)
269
+ * ```
270
+ */
271
+ function isValidVarName(name) {
272
+ if (!name || reservedWords.has(name)) return false;
273
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
274
+ }
275
+ /**
276
+ * Returns `name` when it's a syntactically valid JavaScript variable name,
277
+ * otherwise prefixes it with `_` so the result is a valid identifier.
278
+ *
279
+ * Useful for sanitizing OpenAPI schema names or operation IDs that start with
280
+ * a digit (e.g. `409`, `504AccountCancel`) before using them as exported
281
+ * variable, type, or function names.
282
+ *
283
+ * @example
284
+ * ```ts
285
+ * ensureValidVarName('409') // '_409'
286
+ * ensureValidVarName('504AccountCancel') // '_504AccountCancel'
287
+ * ensureValidVarName('Pet') // 'Pet'
288
+ * ensureValidVarName('class') // '_class'
289
+ * ```
290
+ */
291
+ function ensureValidVarName(name) {
292
+ if (!name || isValidVarName(name)) return name;
293
+ return `_${name}`;
294
+ }
295
+ //#endregion
170
296
  //#region src/constants.ts
171
297
  /**
172
298
  * `optionalType` values that cause a property's type to include `| undefined`.
@@ -212,6 +338,9 @@ const PARAM_RANK = {
212
338
  //#endregion
213
339
  //#region src/factory.ts
214
340
  const { SyntaxKind, factory } = typescript.default;
341
+ function isNumber(value) {
342
+ return typeof value === "number" && !Number.isNaN(value);
343
+ }
215
344
  /**
216
345
  * TypeScript AST modifiers for common keywords (async, export, const, static).
217
346
  */
@@ -229,10 +358,19 @@ const syntaxKind = {
229
358
  literalType: SyntaxKind.LiteralType,
230
359
  stringLiteral: SyntaxKind.StringLiteral
231
360
  };
361
+ function isNonNullable$1(value) {
362
+ return value !== null && value !== void 0;
363
+ }
364
+ __name(isNonNullable$1, "isNonNullable");
232
365
  function isValidIdentifier(str) {
233
366
  if (!str.length || str.trim() !== str) return false;
234
- const node = typescript.default.parseIsolatedEntityName(str, typescript.default.ScriptTarget.Latest);
235
- return !!node && node.kind === typescript.default.SyntaxKind.Identifier && typescript.default.identifierToKeywordKind(node.kind) === void 0;
367
+ let ch = str.codePointAt(0);
368
+ if (!typescript.default.isIdentifierStart(ch, typescript.default.ScriptTarget.Latest)) return false;
369
+ for (let i = ch > 65535 ? 2 : 1; i < str.length; i += ch > 65535 ? 2 : 1) {
370
+ ch = str.codePointAt(i);
371
+ if (!typescript.default.isIdentifierPart(ch, typescript.default.ScriptTarget.Latest)) return false;
372
+ }
373
+ return true;
236
374
  }
237
375
  function propertyName(name) {
238
376
  if (typeof name === "string") return isValidIdentifier(name) ? factory.createIdentifier(name) : factory.createStringLiteral(name);
@@ -298,7 +436,7 @@ function createUnionDeclaration({ nodes, withParentheses }) {
298
436
  * Supports optional markers, readonly modifiers, and type annotations.
299
437
  */
300
438
  function createPropertySignature({ readOnly, modifiers = [], name, questionToken, type }) {
301
- return factory.createPropertySignature([...modifiers, readOnly ? factory.createToken(typescript.default.SyntaxKind.ReadonlyKeyword) : void 0].filter(Boolean), propertyName(name), createQuestionToken(questionToken), type);
439
+ return factory.createPropertySignature([...modifiers, readOnly ? factory.createToken(typescript.default.SyntaxKind.ReadonlyKeyword) : void 0].filter((modifier) => modifier !== void 0), propertyName(name), createQuestionToken(questionToken), type);
302
440
  }
303
441
  /**
304
442
  * Creates a function parameter declaration with optional markers, rest parameters, and type annotations.
@@ -390,19 +528,19 @@ function applyEnumKeyCasing(key, casing = "none") {
390
528
  */
391
529
  function createEnumDeclaration({ type = "enum", name, typeName, enums, enumKeyCasing = "none" }) {
392
530
  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]) => {
393
- if ((0, remeda.isNumber)(value)) {
531
+ if (isNumber(value)) {
394
532
  if (value < 0) return factory.createLiteralTypeNode(factory.createPrefixUnaryExpression(typescript.default.SyntaxKind.MinusToken, factory.createNumericLiteral(Math.abs(value))));
395
533
  return factory.createLiteralTypeNode(factory.createNumericLiteral(value?.toString()));
396
534
  }
397
535
  if (typeof value === "boolean") return factory.createLiteralTypeNode(value ? factory.createTrue() : factory.createFalse());
398
536
  if (value) return factory.createLiteralTypeNode(factory.createStringLiteral(value.toString()));
399
- }).filter(Boolean)))];
400
- 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(Boolean), factory.createIdentifier(typeName), enums.map(([key, value]) => {
537
+ }).filter((node) => node !== void 0)))];
538
+ 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]) => {
401
539
  let initializer = factory.createStringLiteral(value?.toString());
402
- 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)));
540
+ if (Number.parseInt(value.toString(), 10) === value && isNumber(Number.parseInt(value.toString(), 10))) if (value < 0) initializer = factory.createPrefixUnaryExpression(typescript.default.SyntaxKind.MinusToken, factory.createNumericLiteral(Math.abs(value)));
403
541
  else initializer = factory.createNumericLiteral(value);
404
542
  if (typeof value === "boolean") initializer = value ? factory.createTrue() : factory.createFalse();
405
- if ((0, remeda.isNumber)(Number.parseInt(key.toString(), 10))) {
543
+ if (isNumber(Number.parseInt(key.toString(), 10))) {
406
544
  const casingKey = applyEnumKeyCasing(`${typeName}_${key}`, enumKeyCasing);
407
545
  return factory.createEnumMember(propertyName(casingKey), initializer);
408
546
  }
@@ -410,19 +548,19 @@ function createEnumDeclaration({ type = "enum", name, typeName, enums, enumKeyCa
410
548
  const casingKey = applyEnumKeyCasing(key.toString(), enumKeyCasing);
411
549
  return factory.createEnumMember(propertyName(casingKey), initializer);
412
550
  }
413
- }).filter(Boolean))];
551
+ }).filter((member) => member !== void 0))];
414
552
  const identifierName = name;
415
553
  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))];
416
554
  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]) => {
417
555
  let initializer = factory.createStringLiteral(value?.toString());
418
- if ((0, remeda.isNumber)(value)) if (value < 0) initializer = factory.createPrefixUnaryExpression(typescript.default.SyntaxKind.MinusToken, factory.createNumericLiteral(Math.abs(value)));
556
+ if (isNumber(value)) if (value < 0) initializer = factory.createPrefixUnaryExpression(typescript.default.SyntaxKind.MinusToken, factory.createNumericLiteral(Math.abs(value)));
419
557
  else initializer = factory.createNumericLiteral(value);
420
558
  if (typeof value === "boolean") initializer = value ? factory.createTrue() : factory.createFalse();
421
559
  if (key) {
422
560
  const casingKey = applyEnumKeyCasing(key.toString(), enumKeyCasing);
423
561
  return factory.createPropertyAssignment(propertyName(casingKey), initializer);
424
562
  }
425
- }).filter(Boolean), 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))))];
563
+ }).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))))];
426
564
  }
427
565
  /**
428
566
  * Creates a TypeScript `Omit<T, Keys>` type reference node.
@@ -562,14 +700,14 @@ function dateOrStringNode(node) {
562
700
  * Maps an array of `SchemaNode`s through the printer, filtering out `null` and `undefined` results.
563
701
  */
564
702
  function buildMemberNodes(members, print) {
565
- return (members ?? []).map(print).filter(Boolean);
703
+ return (members ?? []).map(print).filter(isNonNullable$1);
566
704
  }
567
705
  /**
568
706
  * Builds a TypeScript tuple type node from an array schema's `items`,
569
707
  * applying min/max slice and optional/rest element rules.
570
708
  */
571
709
  function buildTupleNode(node, print) {
572
- let items = (node.items ?? []).map(print).filter(Boolean);
710
+ let items = (node.items ?? []).map(print).filter(isNonNullable$1);
573
711
  const restNode = node.rest ? print(node.rest) ?? void 0 : void 0;
574
712
  const { min, max } = node;
575
713
  if (max !== void 0) {
@@ -656,13 +794,13 @@ function Enum({ node, enumType, enumTypeSuffix, enumKeyCasing, resolver }) {
656
794
  isExportable: true,
657
795
  isIndexable: true,
658
796
  isTypeOnly: false,
659
- children: (0, _kubb_parser_ts.safePrint)(nameNode)
797
+ children: _kubb_parser_ts.parserTs.print(nameNode)
660
798
  }), /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
661
799
  name: typeName,
662
800
  isIndexable: true,
663
801
  isExportable: ENUM_TYPES_WITH_RUNTIME_VALUE.has(enumType),
664
802
  isTypeOnly: ENUM_TYPES_WITH_TYPE_ONLY.has(enumType),
665
- children: (0, _kubb_parser_ts.safePrint)(typeNode)
803
+ children: _kubb_parser_ts.parserTs.print(typeNode)
666
804
  })] });
667
805
  }
668
806
  //#endregion
@@ -702,6 +840,98 @@ function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCasing, re
702
840
  })] });
703
841
  }
704
842
  //#endregion
843
+ //#region ../../internals/shared/src/operation.ts
844
+ /**
845
+ * Maps a content type to the PascalCase suffix used to name per-content-type variants
846
+ * (e.g. `application/json` → `Json`, `application/xml` → `Xml`, `multipart/form-data` → `FormData`).
847
+ */
848
+ function getContentTypeSuffix(contentType) {
849
+ const baseType = contentType.split(";")[0].trim();
850
+ if (baseType === "application/json") return "Json";
851
+ if (baseType === "multipart/form-data") return "FormData";
852
+ if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
853
+ const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
854
+ if (parts.length === 0) return "Unknown";
855
+ return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
856
+ }
857
+ /**
858
+ * Appends a content-type suffix to a base name, keeping a trailing `Data` segment last
859
+ * (e.g. `AddPetData` + `Json` → `AddPetJsonData`, `AddPetStatus200` + `Xml` → `AddPetStatus200Xml`).
860
+ */
861
+ function getPerContentTypeName(baseName, suffix) {
862
+ if (baseName.endsWith("Data")) return suffix.endsWith("Data") ? baseName.slice(0, -4) + suffix : `${baseName.slice(0, -4)}${suffix}Data`;
863
+ return baseName + suffix;
864
+ }
865
+ /**
866
+ * Resolves per-content-type variant names for a set of content entries, deduplicating suffix
867
+ * collisions with a numeric counter. Entries without a schema are skipped. The returned `suffix` is
868
+ * the final (possibly counter-augmented) value, so callers can derive parallel names in another
869
+ * namespace (e.g. plugin-faker deriving the matching plugin-ts type name).
870
+ */
871
+ function resolveContentTypeVariants(entries, baseName) {
872
+ const usedNames = /* @__PURE__ */ new Set();
873
+ return entries.filter((entry) => entry.schema).map((entry) => {
874
+ const baseSuffix = getContentTypeSuffix(entry.contentType);
875
+ let suffix = baseSuffix;
876
+ let name = getPerContentTypeName(baseName, suffix);
877
+ let counter = 2;
878
+ while (usedNames.has(name)) {
879
+ suffix = `${baseSuffix}${counter++}`;
880
+ name = getPerContentTypeName(baseName, suffix);
881
+ }
882
+ usedNames.add(name);
883
+ return {
884
+ name,
885
+ suffix,
886
+ schema: entry.schema,
887
+ keysToOmit: entry.keysToOmit,
888
+ contentType: entry.contentType
889
+ };
890
+ });
891
+ }
892
+ function getOperationParameters(node, options = {}) {
893
+ const params = _kubb_core.ast.caseParams(node.parameters, options.paramsCasing);
894
+ return {
895
+ path: params.filter((param) => param.in === "path"),
896
+ query: params.filter((param) => param.in === "query"),
897
+ header: params.filter((param) => param.in === "header"),
898
+ cookie: params.filter((param) => param.in === "cookie")
899
+ };
900
+ }
901
+ //#endregion
902
+ //#region ../../internals/shared/src/group.ts
903
+ /**
904
+ * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
905
+ * shared default naming so every plugin groups output consistently:
906
+ *
907
+ * - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
908
+ * - other groups use `${camelCase(group)}${suffix}` (e.g. `petController`).
909
+ *
910
+ * A user-provided `group.name` always wins over the default namer, so callers stay in
911
+ * control of their output folders. Returns `null` when grouping is disabled, matching the
912
+ * per-plugin convention.
913
+ *
914
+ * @param group - The user-supplied group option, or `undefined` to disable grouping.
915
+ * @param options.suffix - Appended to non-`path` group names, e.g. `'Controller'` or `'Requests'`.
916
+ *
917
+ * @example
918
+ * ```ts
919
+ * createGroupConfig(group, { suffix: 'Controller' }) // plugin-ts, plugin-client, …
920
+ * createGroupConfig(group, { suffix: 'Requests' }) // plugin-cypress, plugin-mcp
921
+ * ```
922
+ */
923
+ function createGroupConfig(group, options) {
924
+ if (!group) return null;
925
+ const defaultName = (ctx) => {
926
+ if (group.type === "path") return `${ctx.group.split("/")[1]}`;
927
+ return `${camelCase(ctx.group)}${options.suffix}`;
928
+ };
929
+ return {
930
+ ...group,
931
+ name: group.name ? group.name : defaultName
932
+ };
933
+ }
934
+ //#endregion
705
935
  //#region src/utils.ts
706
936
  /**
707
937
  * Collects JSDoc annotation strings for a schema node.
@@ -713,15 +943,18 @@ function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCasing, re
713
943
  function buildPropertyJSDocComments(schema) {
714
944
  const meta = _kubb_core.ast.syncSchemaRef(schema);
715
945
  const isArray = meta?.primitive === "array";
946
+ const hasDescription = meta && "description" in meta && meta.description;
947
+ const formatComment = meta && "format" in meta && meta.format ? hasDescription ? [" ", `Format: \`${meta.format}\``] : ["@description", `Format: \`${meta.format}\``] : [];
716
948
  return [
717
- meta && "description" in meta && meta.description ? `@description ${jsStringEscape(meta.description)}` : void 0,
718
- meta && "deprecated" in meta && meta.deprecated ? "@deprecated" : void 0,
719
- !isArray && meta && "min" in meta && meta.min !== void 0 ? `@minLength ${meta.min}` : void 0,
720
- !isArray && meta && "max" in meta && meta.max !== void 0 ? `@maxLength ${meta.max}` : void 0,
721
- meta && "pattern" in meta && meta.pattern ? `@pattern ${meta.pattern}` : void 0,
722
- meta && "default" in meta && meta.default !== void 0 ? `@default ${"primitive" in meta && meta.primitive === "string" ? stringify(meta.default) : meta.default}` : void 0,
723
- meta && "example" in meta && meta.example !== void 0 ? `@example ${meta.example}` : void 0,
724
- meta && "primitive" in meta && meta.primitive ? [`@type ${meta.primitive}`, "optional" in schema && schema.optional ? " | undefined" : void 0].filter(Boolean).join("") : void 0
949
+ hasDescription ? `@description ${jsStringEscape(meta.description)}` : null,
950
+ ...formatComment,
951
+ meta && "deprecated" in meta && meta.deprecated ? "@deprecated" : null,
952
+ !isArray && meta && "min" in meta && meta.min !== void 0 ? `@minLength ${meta.min}` : null,
953
+ !isArray && meta && "max" in meta && meta.max !== void 0 ? `@maxLength ${meta.max}` : null,
954
+ meta && "pattern" in meta && meta.pattern ? `@pattern ${meta.pattern}` : null,
955
+ meta && "default" in meta && meta.default !== void 0 ? `@default ${"primitive" in meta && meta.primitive === "string" ? stringify(meta.default) : meta.default}` : null,
956
+ meta && "example" in meta && meta.example !== void 0 ? `@example ${meta.example}` : null,
957
+ meta && "primitive" in meta && meta.primitive ? [`@type ${meta.primitive}`, "optional" in schema && schema.optional ? " | undefined" : null].filter(Boolean).join("") : null
725
958
  ].filter(Boolean);
726
959
  }
727
960
  function buildParams(node, { params, resolver }) {
@@ -738,9 +971,7 @@ function buildParams(node, { params, resolver }) {
738
971
  });
739
972
  }
740
973
  function buildData(node, { resolver }) {
741
- const pathParams = node.parameters.filter((p) => p.in === "path");
742
- const queryParams = node.parameters.filter((p) => p.in === "query");
743
- const headerParams = node.parameters.filter((p) => p.in === "header");
974
+ const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node);
744
975
  return _kubb_core.ast.createSchema({
745
976
  type: "object",
746
977
  deprecated: node.deprecated,
@@ -822,7 +1053,7 @@ function buildResponses(node, { resolver }) {
822
1053
  });
823
1054
  }
824
1055
  function buildResponseUnion(node, { resolver }) {
825
- const responsesWithSchema = node.responses.filter((res) => res.schema);
1056
+ const responsesWithSchema = node.responses.filter((res) => res.content?.some((entry) => entry.schema));
826
1057
  if (responsesWithSchema.length === 0) return null;
827
1058
  return _kubb_core.ast.createSchema({
828
1059
  type: "union",
@@ -834,6 +1065,9 @@ function buildResponseUnion(node, { resolver }) {
834
1065
  }
835
1066
  //#endregion
836
1067
  //#region src/printers/printerTs.ts
1068
+ function isNonNullable(value) {
1069
+ return value !== null && value !== void 0;
1070
+ }
837
1071
  /**
838
1072
  * TypeScript type printer built with `definePrinter`.
839
1073
  *
@@ -886,7 +1120,7 @@ const printerTs = _kubb_core.ast.definePrinter((options) => {
886
1120
  date: dateOrStringNode,
887
1121
  time: dateOrStringNode,
888
1122
  ref(node) {
889
- if (!node.name) return;
1123
+ if (!node.name) return null;
890
1124
  const refName = node.ref ? _kubb_core.ast.extractRefName(node.ref) ?? node.name : node.name;
891
1125
  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);
892
1126
  },
@@ -894,7 +1128,7 @@ const printerTs = _kubb_core.ast.definePrinter((options) => {
894
1128
  const values = node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? [];
895
1129
  if (this.options.enumType === "inlineLiteral" || !node.name) return createUnionDeclaration({
896
1130
  withParentheses: true,
897
- nodes: values.filter((v) => v !== null && v !== void 0).map((value) => constToTypeNode(value, typeof value)).filter(Boolean)
1131
+ nodes: values.filter((v) => v !== null && v !== void 0).map((value) => constToTypeNode(value, typeof value)).filter(isNonNullable)
898
1132
  }) ?? void 0;
899
1133
  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);
900
1134
  },
@@ -912,7 +1146,7 @@ const printerTs = _kubb_core.ast.definePrinter((options) => {
912
1146
  withParentheses: true
913
1147
  });
914
1148
  return this.transform(m);
915
- }).filter(Boolean)
1149
+ }).filter(isNonNullable)
916
1150
  }) ?? void 0;
917
1151
  return createUnionDeclaration({
918
1152
  withParentheses: true,
@@ -923,16 +1157,16 @@ const printerTs = _kubb_core.ast.definePrinter((options) => {
923
1157
  return createIntersectionDeclaration({
924
1158
  withParentheses: true,
925
1159
  nodes: buildMemberNodes(node.members, this.transform)
926
- }) ?? void 0;
1160
+ }) ?? null;
927
1161
  },
928
1162
  array(node) {
929
1163
  return createArrayDeclaration({
930
- nodes: (node.items ?? []).map((item) => this.transform(item)).filter(Boolean),
1164
+ nodes: (node.items ?? []).map((item) => this.transform(item)).filter(isNonNullable),
931
1165
  arrayType: this.options.arrayType
932
- }) ?? void 0;
1166
+ }) ?? null;
933
1167
  },
934
1168
  tuple(node) {
935
- return buildTupleNode(node, this.transform);
1169
+ return buildTupleNode(node, this.transform) ?? null;
936
1170
  },
937
1171
  object(node) {
938
1172
  const { transform, options } = this;
@@ -959,46 +1193,54 @@ const printerTs = _kubb_core.ast.definePrinter((options) => {
959
1193
  },
960
1194
  print(node) {
961
1195
  const { name, syntaxType = "type", description, keysToOmit } = this.options;
962
- let base = this.transform(node);
963
- if (!base) return null;
1196
+ const transformed = this.transform(node);
1197
+ if (!transformed) return null;
964
1198
  const meta = _kubb_core.ast.syncSchemaRef(node);
965
1199
  if (!name) {
966
- if (meta.nullable) base = createUnionDeclaration({ nodes: [base, keywordTypeNodes.null] });
967
- if ((meta.nullish || meta.optional) && addsUndefined) base = createUnionDeclaration({ nodes: [base, keywordTypeNodes.undefined] });
968
- return (0, _kubb_parser_ts.safePrint)(base);
1200
+ const withNullable = meta.nullable ? createUnionDeclaration({ nodes: [transformed, keywordTypeNodes.null] }) : transformed;
1201
+ const result = (meta.nullish || meta.optional) && addsUndefined ? createUnionDeclaration({ nodes: [withNullable, keywordTypeNodes.undefined] }) : withNullable;
1202
+ return _kubb_parser_ts.parserTs.print(result);
969
1203
  }
970
- let inner = keysToOmit?.length ? createOmitDeclaration({
971
- keys: keysToOmit,
972
- type: base,
973
- nonNullable: true
974
- }) : base;
975
- if (meta.nullable) inner = createUnionDeclaration({ nodes: [inner, keywordTypeNodes.null] });
976
- if (meta.nullish || meta.optional) inner = createUnionDeclaration({ nodes: [inner, keywordTypeNodes.undefined] });
977
- const useTypeGeneration = syntaxType === "type" || inner.kind === syntaxKind.union || !!keysToOmit?.length;
978
- return (0, _kubb_parser_ts.safePrint)(createTypeDeclaration({
1204
+ const inner = (() => {
1205
+ const omitted = keysToOmit?.length ? createOmitDeclaration({
1206
+ keys: keysToOmit,
1207
+ type: transformed,
1208
+ nonNullable: true
1209
+ }) : transformed;
1210
+ const withNullable = meta.nullable ? createUnionDeclaration({ nodes: [omitted, keywordTypeNodes.null] }) : omitted;
1211
+ return meta.nullish || meta.optional ? createUnionDeclaration({ nodes: [withNullable, keywordTypeNodes.undefined] }) : withNullable;
1212
+ })();
1213
+ const typeNode = createTypeDeclaration({
979
1214
  name,
980
1215
  isExportable: true,
981
1216
  type: inner,
982
- syntax: useTypeGeneration ? "type" : "interface",
1217
+ syntax: syntaxType === "type" || inner.kind === syntaxKind.union || !!keysToOmit?.length ? "type" : "interface",
983
1218
  comments: buildPropertyJSDocComments({
984
1219
  ...meta,
985
1220
  description
986
1221
  })
987
- }));
1222
+ });
1223
+ return _kubb_parser_ts.parserTs.print(typeNode);
988
1224
  }
989
1225
  };
990
1226
  });
991
1227
  //#endregion
992
1228
  //#region src/generators/typeGenerator.tsx
1229
+ /**
1230
+ * Built-in generator for `@kubb/plugin-ts`. Emits one TypeScript file per
1231
+ * schema in the spec plus per-operation request, response, and parameter
1232
+ * types. Drop-replace with a custom `Generator<PluginTs>` to change how
1233
+ * TypeScript output is produced.
1234
+ */
993
1235
  const typeGenerator = (0, _kubb_core.defineGenerator)({
994
1236
  name: "typescript",
995
- renderer: _kubb_renderer_jsx.jsxRenderer,
1237
+ renderer: _kubb_renderer_jsx.jsxRendererSync,
996
1238
  schema(node, ctx) {
997
1239
  const { enumType, enumTypeSuffix, enumKeyCasing, syntaxType, optionalType, arrayType, output, group, printer } = ctx.options;
998
1240
  const { adapter, config, resolver, root } = ctx;
999
1241
  if (!node.name) return;
1000
1242
  const mode = ctx.getMode(output);
1001
- 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));
1243
+ const enumSchemaNames = new Set(ctx.meta.enumNames);
1002
1244
  function resolveImportName(schemaName) {
1003
1245
  if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && enumTypeSuffix && enumSchemaNames.has(schemaName)) return resolver.resolveEnumKeyName({ name: schemaName }, enumTypeSuffix);
1004
1246
  return resolver.resolveTypeName(schemaName);
@@ -1011,7 +1253,7 @@ const typeGenerator = (0, _kubb_core.defineGenerator)({
1011
1253
  }, {
1012
1254
  root,
1013
1255
  output,
1014
- group
1256
+ group: group ?? void 0
1015
1257
  }).path
1016
1258
  }));
1017
1259
  const isEnumSchema = !!_kubb_core.ast.narrowSchema(node, _kubb_core.ast.schemaTypes.enum);
@@ -1023,7 +1265,7 @@ const typeGenerator = (0, _kubb_core.defineGenerator)({
1023
1265
  }, {
1024
1266
  root,
1025
1267
  output,
1026
- group
1268
+ group: group ?? void 0
1027
1269
  })
1028
1270
  };
1029
1271
  const schemaPrinter = printerTs({
@@ -1042,13 +1284,21 @@ const typeGenerator = (0, _kubb_core.defineGenerator)({
1042
1284
  baseName: meta.file.baseName,
1043
1285
  path: meta.file.path,
1044
1286
  meta: meta.file.meta,
1045
- banner: resolver.resolveBanner(adapter.inputNode, {
1287
+ banner: resolver.resolveBanner(ctx.meta, {
1046
1288
  output,
1047
- config
1289
+ config,
1290
+ file: {
1291
+ path: meta.file.path,
1292
+ baseName: meta.file.baseName
1293
+ }
1048
1294
  }),
1049
- footer: resolver.resolveFooter(adapter.inputNode, {
1295
+ footer: resolver.resolveFooter(ctx.meta, {
1050
1296
  output,
1051
- config
1297
+ config,
1298
+ file: {
1299
+ path: meta.file.path,
1300
+ baseName: meta.file.baseName
1301
+ }
1052
1302
  }),
1053
1303
  children: [mode === "split" && imports.map((imp) => /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1054
1304
  root: meta.file.path,
@@ -1083,9 +1333,9 @@ const typeGenerator = (0, _kubb_core.defineGenerator)({
1083
1333
  }, {
1084
1334
  root,
1085
1335
  output,
1086
- group
1336
+ group: group ?? void 0
1087
1337
  }) };
1088
- 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));
1338
+ const enumSchemaNames = new Set(ctx.meta.enumNames);
1089
1339
  function resolveImportName(schemaName) {
1090
1340
  if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && enumTypeSuffix && enumSchemaNames.has(schemaName)) return resolver.resolveEnumKeyName({ name: schemaName }, enumTypeSuffix);
1091
1341
  return resolver.resolveTypeName(schemaName);
@@ -1100,7 +1350,7 @@ const typeGenerator = (0, _kubb_core.defineGenerator)({
1100
1350
  }, {
1101
1351
  root,
1102
1352
  output,
1103
- group
1353
+ group: group ?? void 0
1104
1354
  }).path
1105
1355
  }));
1106
1356
  const schemaPrinter = printerTs({
@@ -1135,23 +1385,63 @@ const typeGenerator = (0, _kubb_core.defineGenerator)({
1135
1385
  printer: schemaPrinter
1136
1386
  })] });
1137
1387
  }
1388
+ /**
1389
+ * Emits an individual type per content type plus a union alias under `baseName`.
1390
+ * Shared by the request body and multi-content-type responses.
1391
+ */
1392
+ function buildContentTypeVariants(entries, baseName, decorate) {
1393
+ const variants = resolveContentTypeVariants(entries, baseName);
1394
+ const unionSchema = _kubb_core.ast.createSchema({
1395
+ type: "union",
1396
+ members: variants.map((variant) => _kubb_core.ast.createSchema({
1397
+ type: "ref",
1398
+ name: variant.name
1399
+ }))
1400
+ });
1401
+ return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [variants.map((variant) => renderSchemaType({
1402
+ schema: decorate ? decorate(variant.schema) : variant.schema,
1403
+ name: variant.name,
1404
+ keysToOmit: variant.keysToOmit
1405
+ })), renderSchemaType({
1406
+ schema: unionSchema,
1407
+ name: baseName
1408
+ })] });
1409
+ }
1138
1410
  const paramTypes = params.map((param) => renderSchemaType({
1139
1411
  schema: param.schema,
1140
1412
  name: resolver.resolveParamName(node, param)
1141
1413
  }));
1142
- const requestType = node.requestBody?.content?.[0]?.schema ? renderSchemaType({
1143
- schema: {
1144
- ...node.requestBody.content[0].schema,
1145
- description: node.requestBody.description ?? node.requestBody.content[0].schema.description
1146
- },
1147
- name: resolver.resolveDataName(node),
1148
- keysToOmit: node.requestBody.content[0].keysToOmit
1149
- }) : null;
1150
- const responseTypes = node.responses.map((res) => renderSchemaType({
1151
- schema: res.schema,
1152
- name: resolver.resolveResponseStatusName(node, res.statusCode),
1153
- keysToOmit: res.keysToOmit
1154
- }));
1414
+ const requestBodyContent = node.requestBody?.content ?? [];
1415
+ function buildRequestType() {
1416
+ if (requestBodyContent.length === 0) return null;
1417
+ if (requestBodyContent.length === 1) {
1418
+ const entry = requestBodyContent[0];
1419
+ if (!entry.schema) return null;
1420
+ return renderSchemaType({
1421
+ schema: {
1422
+ ...entry.schema,
1423
+ description: node.requestBody.description ?? entry.schema.description
1424
+ },
1425
+ name: resolver.resolveDataName(node),
1426
+ keysToOmit: entry.keysToOmit
1427
+ });
1428
+ }
1429
+ return buildContentTypeVariants(requestBodyContent, resolver.resolveDataName(node), (schema) => ({
1430
+ ...schema,
1431
+ description: node.requestBody.description ?? schema.description
1432
+ }));
1433
+ }
1434
+ const requestType = buildRequestType();
1435
+ const responseTypes = node.responses.map((res) => {
1436
+ const variants = (res.content ?? []).filter((entry) => entry.schema);
1437
+ if (variants.length > 1) return buildContentTypeVariants(variants, resolver.resolveResponseStatusName(node, res.statusCode));
1438
+ const primary = variants[0] ?? res.content?.[0];
1439
+ return renderSchemaType({
1440
+ schema: primary?.schema ?? null,
1441
+ name: resolver.resolveResponseStatusName(node, res.statusCode),
1442
+ keysToOmit: primary?.keysToOmit
1443
+ });
1444
+ });
1155
1445
  const dataType = renderSchemaType({
1156
1446
  schema: buildData({
1157
1447
  ...node,
@@ -1163,14 +1453,15 @@ const typeGenerator = (0, _kubb_core.defineGenerator)({
1163
1453
  schema: buildResponses(node, { resolver }),
1164
1454
  name: resolver.resolveResponsesName(node)
1165
1455
  });
1166
- const responseType = (() => {
1167
- if (!node.responses.some((res) => res.schema)) return null;
1456
+ function buildResponseType() {
1457
+ const hasSchema = (res) => (res.content ?? []).some((entry) => entry.schema);
1458
+ if (!node.responses.some(hasSchema)) return null;
1168
1459
  const responseName = resolver.resolveResponseName(node);
1169
- const responsesWithSchema = node.responses.filter((res) => res.schema);
1170
- if (new Set(responsesWithSchema.flatMap((res) => res.schema ? adapter.getImports(res.schema, (schemaName) => ({
1460
+ const responsesWithSchema = node.responses.filter(hasSchema);
1461
+ if (new Set(responsesWithSchema.flatMap((res) => (res.content ?? []).flatMap((entry) => entry.schema ? adapter.getImports(entry.schema, (schemaName) => ({
1171
1462
  name: resolveImportName(schemaName),
1172
1463
  path: ""
1173
- })).flatMap((imp) => Array.isArray(imp.name) ? imp.name : [imp.name]) : [])).has(responseName)) return null;
1464
+ })).flatMap((imp) => Array.isArray(imp.name) ? imp.name : [imp.name]) : []))).has(responseName)) return null;
1174
1465
  return renderSchemaType({
1175
1466
  schema: {
1176
1467
  ...buildResponseUnion(node, { resolver }),
@@ -1178,18 +1469,27 @@ const typeGenerator = (0, _kubb_core.defineGenerator)({
1178
1469
  },
1179
1470
  name: responseName
1180
1471
  });
1181
- })();
1472
+ }
1473
+ const responseType = buildResponseType();
1182
1474
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
1183
1475
  baseName: meta.file.baseName,
1184
1476
  path: meta.file.path,
1185
1477
  meta: meta.file.meta,
1186
- banner: resolver.resolveBanner(adapter.inputNode, {
1478
+ banner: resolver.resolveBanner(ctx.meta, {
1187
1479
  output,
1188
- config
1480
+ config,
1481
+ file: {
1482
+ path: meta.file.path,
1483
+ baseName: meta.file.baseName
1484
+ }
1189
1485
  }),
1190
- footer: resolver.resolveFooter(adapter.inputNode, {
1486
+ footer: resolver.resolveFooter(ctx.meta, {
1191
1487
  output,
1192
- config
1488
+ config,
1489
+ file: {
1490
+ path: meta.file.path,
1491
+ baseName: meta.file.baseName
1492
+ }
1193
1493
  }),
1194
1494
  children: [
1195
1495
  paramTypes,
@@ -1205,86 +1505,98 @@ const typeGenerator = (0, _kubb_core.defineGenerator)({
1205
1505
  //#endregion
1206
1506
  //#region src/resolvers/resolverTs.ts
1207
1507
  /**
1208
- * Resolver for `@kubb/plugin-ts` that provides the default naming and path-resolution
1209
- * helpers used by the plugin. Import this in other plugins to resolve the exact names and
1210
- * paths that `plugin-ts` generates without hardcoding the conventions.
1508
+ * Default resolver used by `@kubb/plugin-ts`. Decides the names and file paths
1509
+ * for every generated TypeScript type. Import this in other plugins that need
1510
+ * to reference the exact names `plugin-ts` produces without duplicating the
1511
+ * casing/file-layout rules.
1211
1512
  *
1212
- * The `default` method is automatically injected by `defineResolver` it uses `camelCase`
1213
- * for identifiers/files and `pascalCase` for type names.
1513
+ * The `default` method is supplied by `defineResolver`. It uses PascalCase for
1514
+ * type names and PascalCase-with-isFile for files.
1214
1515
  *
1215
- * @example
1516
+ * @example Resolve a type and file name
1216
1517
  * ```ts
1217
- * import { resolver } from '@kubb/plugin-ts'
1518
+ * import { resolverTs } from '@kubb/plugin-ts'
1218
1519
  *
1219
- * resolver.default('list pets', 'type') // 'ListPets'
1220
- * resolver.resolveName('list pets status 200') // 'ListPetsStatus200'
1221
- * resolver.resolvePathName('list pets', 'file') // 'listPets'
1520
+ * resolverTs.default('list pets', 'type') // 'ListPets'
1521
+ * resolverTs.resolvePathName('list pets', 'file') // 'ListPets'
1522
+ * resolverTs.resolveResponseStatusName(node, 200) // 'ListPetsStatus200'
1222
1523
  * ```
1223
1524
  */
1224
- const resolverTs = (0, _kubb_core.defineResolver)((ctx) => {
1525
+ const resolverTs = (0, _kubb_core.defineResolver)(() => {
1225
1526
  return {
1226
1527
  name: "default",
1227
1528
  pluginName: "plugin-ts",
1228
1529
  default(name, type) {
1229
- return pascalCase(name, { isFile: type === "file" });
1530
+ const resolved = pascalCase(name, { isFile: type === "file" });
1531
+ return type === "file" ? resolved : ensureValidVarName(resolved);
1230
1532
  },
1231
1533
  resolveTypeName(name) {
1232
- return pascalCase(name);
1534
+ return ensureValidVarName(pascalCase(name));
1233
1535
  },
1234
1536
  resolvePathName(name, type) {
1235
- return pascalCase(name, { isFile: type === "file" });
1537
+ const resolved = pascalCase(name, { isFile: type === "file" });
1538
+ return type === "file" ? resolved : ensureValidVarName(resolved);
1236
1539
  },
1237
1540
  resolveParamName(node, param) {
1238
- return ctx.resolveTypeName(`${node.operationId} ${param.in} ${param.name}`);
1541
+ return this.resolveTypeName(`${node.operationId} ${param.in} ${param.name}`);
1239
1542
  },
1240
1543
  resolveResponseStatusName(node, statusCode) {
1241
- return ctx.resolveTypeName(`${node.operationId} Status ${statusCode}`);
1544
+ return this.resolveTypeName(`${node.operationId} Status ${statusCode}`);
1242
1545
  },
1243
1546
  resolveDataName(node) {
1244
- return ctx.resolveTypeName(`${node.operationId} Data`);
1547
+ return this.resolveTypeName(`${node.operationId} Data`);
1245
1548
  },
1246
1549
  resolveRequestConfigName(node) {
1247
- return ctx.resolveTypeName(`${node.operationId} RequestConfig`);
1550
+ return this.resolveTypeName(`${node.operationId} RequestConfig`);
1248
1551
  },
1249
1552
  resolveResponsesName(node) {
1250
- return ctx.resolveTypeName(`${node.operationId} Responses`);
1553
+ return this.resolveTypeName(`${node.operationId} Responses`);
1251
1554
  },
1252
1555
  resolveResponseName(node) {
1253
- return ctx.resolveTypeName(`${node.operationId} Response`);
1556
+ return this.resolveTypeName(`${node.operationId} Response`);
1254
1557
  },
1255
1558
  resolveEnumKeyName(node, enumTypeSuffix = "key") {
1256
- return `${ctx.resolveTypeName(node.name ?? "")}${enumTypeSuffix}`;
1559
+ return `${this.resolveTypeName(node.name ?? "")}${enumTypeSuffix}`;
1257
1560
  },
1258
1561
  resolvePathParamsName(node, param) {
1259
- return ctx.resolveParamName(node, param);
1562
+ return this.resolveParamName(node, param);
1260
1563
  },
1261
1564
  resolveQueryParamsName(node, param) {
1262
- return ctx.resolveParamName(node, param);
1565
+ return this.resolveParamName(node, param);
1263
1566
  },
1264
1567
  resolveHeaderParamsName(node, param) {
1265
- return ctx.resolveParamName(node, param);
1568
+ return this.resolveParamName(node, param);
1266
1569
  }
1267
1570
  };
1268
1571
  });
1269
1572
  //#endregion
1270
1573
  //#region src/plugin.ts
1271
1574
  /**
1272
- * Canonical plugin name for `@kubb/plugin-ts`, used to identify the plugin in driver lookups and warnings.
1575
+ * Canonical plugin name for `@kubb/plugin-ts`. Used for driver lookups and
1576
+ * cross-plugin dependency references.
1273
1577
  */
1274
1578
  const pluginTsName = "plugin-ts";
1275
1579
  /**
1276
- * The `@kubb/plugin-ts` plugin factory.
1277
- *
1278
- * Generates TypeScript type declarations from an OpenAPI/AST `RootNode`.
1279
- * Walks schemas and operations, delegates rendering to the active generators,
1280
- * and writes barrel files based on `output.barrelType`.
1580
+ * Generates TypeScript `type` aliases and `interface` declarations from an
1581
+ * OpenAPI spec. The foundation that every other Kubb plugin builds on:
1582
+ * clients, query hooks, mocks, and validators all reference the names this
1583
+ * plugin produces.
1281
1584
  *
1282
1585
  * @example
1283
1586
  * ```ts
1284
- * import pluginTs from '@kubb/plugin-ts'
1587
+ * import { defineConfig } from 'kubb'
1588
+ * import { pluginTs } from '@kubb/plugin-ts'
1285
1589
  *
1286
1590
  * export default defineConfig({
1287
- * plugins: [pluginTs({ output: { path: 'types' }, enumType: 'asConst' })],
1591
+ * input: { path: './petStore.yaml' },
1592
+ * output: { path: './src/gen' },
1593
+ * plugins: [
1594
+ * pluginTs({
1595
+ * output: { path: './types' },
1596
+ * enumType: 'asConst',
1597
+ * optionalType: 'questionTokenAndUndefined',
1598
+ * }),
1599
+ * ],
1288
1600
  * })
1289
1601
  * ```
1290
1602
  */
@@ -1293,13 +1605,7 @@ const pluginTs = (0, _kubb_core.definePlugin)((options) => {
1293
1605
  path: "types",
1294
1606
  barrelType: "named"
1295
1607
  }, 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;
1296
- const groupConfig = group ? {
1297
- ...group,
1298
- name: (ctx) => {
1299
- if (group.type === "path") return `${ctx.group.split("/")[1]}`;
1300
- return `${camelCase(ctx.group)}Controller`;
1301
- }
1302
- } : void 0;
1608
+ const groupConfig = createGroupConfig(group, { suffix: "Controller" });
1303
1609
  return {
1304
1610
  name: pluginTsName,
1305
1611
  options,
@@ -1354,10 +1660,10 @@ function rank(param) {
1354
1660
  return param.optional ? PARAM_RANK.optional : PARAM_RANK.required;
1355
1661
  }
1356
1662
  function sortParams(params) {
1357
- return [...params].sort((a, b) => rank(a) - rank(b));
1663
+ return params.toSorted((a, b) => rank(a) - rank(b));
1358
1664
  }
1359
1665
  function sortChildParams(params) {
1360
- return [...params].sort((a, b) => rank(a) - rank(b));
1666
+ return params.toSorted((a, b) => rank(a) - rank(b));
1361
1667
  }
1362
1668
  /**
1363
1669
  * Default function-signature printer.