@kubb/plugin-ts 5.0.0-beta.3 → 5.0.0-beta.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import "./chunk--u3MIqq1.js";
2
- import { safePrint } from "@kubb/parser-ts";
3
- import { File, jsxRenderer } from "@kubb/renderer-jsx";
1
+ import { t as __name } from "./chunk--u3MIqq1.js";
2
+ import { parserTs } from "@kubb/parser-ts";
3
+ import { File, jsxRendererSync } from "@kubb/renderer-jsx";
4
4
  import { ast, defineGenerator, definePlugin, defineResolver } from "@kubb/core";
5
5
  import { isNumber } from "remeda";
6
6
  import ts from "typescript";
@@ -141,6 +141,129 @@ function stringify(value) {
141
141
  return JSON.stringify(trimQuotes(value.toString()));
142
142
  }
143
143
  //#endregion
144
+ //#region ../../internals/utils/src/reserved.ts
145
+ /**
146
+ * JavaScript and Java reserved words.
147
+ * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
148
+ */
149
+ const reservedWords = new Set([
150
+ "abstract",
151
+ "arguments",
152
+ "boolean",
153
+ "break",
154
+ "byte",
155
+ "case",
156
+ "catch",
157
+ "char",
158
+ "class",
159
+ "const",
160
+ "continue",
161
+ "debugger",
162
+ "default",
163
+ "delete",
164
+ "do",
165
+ "double",
166
+ "else",
167
+ "enum",
168
+ "eval",
169
+ "export",
170
+ "extends",
171
+ "false",
172
+ "final",
173
+ "finally",
174
+ "float",
175
+ "for",
176
+ "function",
177
+ "goto",
178
+ "if",
179
+ "implements",
180
+ "import",
181
+ "in",
182
+ "instanceof",
183
+ "int",
184
+ "interface",
185
+ "let",
186
+ "long",
187
+ "native",
188
+ "new",
189
+ "null",
190
+ "package",
191
+ "private",
192
+ "protected",
193
+ "public",
194
+ "return",
195
+ "short",
196
+ "static",
197
+ "super",
198
+ "switch",
199
+ "synchronized",
200
+ "this",
201
+ "throw",
202
+ "throws",
203
+ "transient",
204
+ "true",
205
+ "try",
206
+ "typeof",
207
+ "var",
208
+ "void",
209
+ "volatile",
210
+ "while",
211
+ "with",
212
+ "yield",
213
+ "Array",
214
+ "Date",
215
+ "hasOwnProperty",
216
+ "Infinity",
217
+ "isFinite",
218
+ "isNaN",
219
+ "isPrototypeOf",
220
+ "length",
221
+ "Math",
222
+ "name",
223
+ "NaN",
224
+ "Number",
225
+ "Object",
226
+ "prototype",
227
+ "String",
228
+ "toString",
229
+ "undefined",
230
+ "valueOf"
231
+ ]);
232
+ /**
233
+ * Returns `true` when `name` is a syntactically valid JavaScript variable name.
234
+ *
235
+ * @example
236
+ * ```ts
237
+ * isValidVarName('status') // true
238
+ * isValidVarName('class') // false (reserved word)
239
+ * isValidVarName('42foo') // false (starts with digit)
240
+ * ```
241
+ */
242
+ function isValidVarName(name) {
243
+ if (!name || reservedWords.has(name)) return false;
244
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
245
+ }
246
+ /**
247
+ * Returns `name` when it's a syntactically valid JavaScript variable name,
248
+ * otherwise prefixes it with `_` so the result is a valid identifier.
249
+ *
250
+ * Useful for sanitizing OpenAPI schema names or operation IDs that start with
251
+ * a digit (e.g. `409`, `504AccountCancel`) before using them as exported
252
+ * variable, type, or function names.
253
+ *
254
+ * @example
255
+ * ```ts
256
+ * ensureValidVarName('409') // '_409'
257
+ * ensureValidVarName('504AccountCancel') // '_504AccountCancel'
258
+ * ensureValidVarName('Pet') // 'Pet'
259
+ * ensureValidVarName('class') // '_class'
260
+ * ```
261
+ */
262
+ function ensureValidVarName(name) {
263
+ if (!name || isValidVarName(name)) return name;
264
+ return `_${name}`;
265
+ }
266
+ //#endregion
144
267
  //#region src/constants.ts
145
268
  /**
146
269
  * `optionalType` values that cause a property's type to include `| undefined`.
@@ -203,6 +326,10 @@ const syntaxKind = {
203
326
  literalType: SyntaxKind.LiteralType,
204
327
  stringLiteral: SyntaxKind.StringLiteral
205
328
  };
329
+ function isNonNullable$1(value) {
330
+ return value !== null && value !== void 0;
331
+ }
332
+ __name(isNonNullable$1, "isNonNullable");
206
333
  function isValidIdentifier(str) {
207
334
  if (!str.length || str.trim() !== str) return false;
208
335
  const node = ts.parseIsolatedEntityName(str, ts.ScriptTarget.Latest);
@@ -272,7 +399,7 @@ function createUnionDeclaration({ nodes, withParentheses }) {
272
399
  * Supports optional markers, readonly modifiers, and type annotations.
273
400
  */
274
401
  function createPropertySignature({ readOnly, modifiers = [], name, questionToken, type }) {
275
- return factory.createPropertySignature([...modifiers, readOnly ? factory.createToken(ts.SyntaxKind.ReadonlyKeyword) : void 0].filter(Boolean), propertyName(name), createQuestionToken(questionToken), type);
402
+ return factory.createPropertySignature([...modifiers, readOnly ? factory.createToken(ts.SyntaxKind.ReadonlyKeyword) : void 0].filter((modifier) => modifier !== void 0), propertyName(name), createQuestionToken(questionToken), type);
276
403
  }
277
404
  /**
278
405
  * Creates a function parameter declaration with optional markers, rest parameters, and type annotations.
@@ -370,8 +497,8 @@ function createEnumDeclaration({ type = "enum", name, typeName, enums, enumKeyCa
370
497
  }
371
498
  if (typeof value === "boolean") return factory.createLiteralTypeNode(value ? factory.createTrue() : factory.createFalse());
372
499
  if (value) return factory.createLiteralTypeNode(factory.createStringLiteral(value.toString()));
373
- }).filter(Boolean)))];
374
- if (type === "enum" || type === "constEnum") return [void 0, factory.createEnumDeclaration([factory.createToken(ts.SyntaxKind.ExportKeyword), type === "constEnum" ? factory.createToken(ts.SyntaxKind.ConstKeyword) : void 0].filter(Boolean), factory.createIdentifier(typeName), enums.map(([key, value]) => {
500
+ }).filter((node) => node !== void 0)))];
501
+ if (type === "enum" || type === "constEnum") return [void 0, factory.createEnumDeclaration([factory.createToken(ts.SyntaxKind.ExportKeyword), type === "constEnum" ? factory.createToken(ts.SyntaxKind.ConstKeyword) : void 0].filter((modifier) => modifier !== void 0), factory.createIdentifier(typeName), enums.map(([key, value]) => {
375
502
  let initializer = factory.createStringLiteral(value?.toString());
376
503
  if (Number.parseInt(value.toString(), 10) === value && isNumber(Number.parseInt(value.toString(), 10))) if (value < 0) initializer = factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, factory.createNumericLiteral(Math.abs(value)));
377
504
  else initializer = factory.createNumericLiteral(value);
@@ -384,7 +511,7 @@ function createEnumDeclaration({ type = "enum", name, typeName, enums, enumKeyCa
384
511
  const casingKey = applyEnumKeyCasing(key.toString(), enumKeyCasing);
385
512
  return factory.createEnumMember(propertyName(casingKey), initializer);
386
513
  }
387
- }).filter(Boolean))];
514
+ }).filter((member) => member !== void 0))];
388
515
  const identifierName = name;
389
516
  if (enums.length === 0) return [void 0, factory.createTypeAliasDeclaration([factory.createToken(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier(typeName), void 0, factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword))];
390
517
  return [factory.createVariableStatement([factory.createToken(ts.SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList([factory.createVariableDeclaration(factory.createIdentifier(identifierName), void 0, void 0, factory.createAsExpression(factory.createObjectLiteralExpression(enums.map(([key, value]) => {
@@ -396,7 +523,7 @@ function createEnumDeclaration({ type = "enum", name, typeName, enums, enumKeyCa
396
523
  const casingKey = applyEnumKeyCasing(key.toString(), enumKeyCasing);
397
524
  return factory.createPropertyAssignment(propertyName(casingKey), initializer);
398
525
  }
399
- }).filter(Boolean), true), factory.createTypeReferenceNode(factory.createIdentifier("const"), void 0)))], ts.NodeFlags.Const)), factory.createTypeAliasDeclaration([factory.createToken(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier(typeName), void 0, factory.createIndexedAccessTypeNode(factory.createParenthesizedType(factory.createTypeQueryNode(factory.createIdentifier(identifierName), void 0)), factory.createTypeOperatorNode(ts.SyntaxKind.KeyOfKeyword, factory.createTypeQueryNode(factory.createIdentifier(identifierName), void 0))))];
526
+ }).filter((property) => property !== void 0), true), factory.createTypeReferenceNode(factory.createIdentifier("const"), void 0)))], ts.NodeFlags.Const)), factory.createTypeAliasDeclaration([factory.createToken(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier(typeName), void 0, factory.createIndexedAccessTypeNode(factory.createParenthesizedType(factory.createTypeQueryNode(factory.createIdentifier(identifierName), void 0)), factory.createTypeOperatorNode(ts.SyntaxKind.KeyOfKeyword, factory.createTypeQueryNode(factory.createIdentifier(identifierName), void 0))))];
400
527
  }
401
528
  /**
402
529
  * Creates a TypeScript `Omit<T, Keys>` type reference node.
@@ -536,14 +663,14 @@ function dateOrStringNode(node) {
536
663
  * Maps an array of `SchemaNode`s through the printer, filtering out `null` and `undefined` results.
537
664
  */
538
665
  function buildMemberNodes(members, print) {
539
- return (members ?? []).map(print).filter(Boolean);
666
+ return (members ?? []).map(print).filter(isNonNullable$1);
540
667
  }
541
668
  /**
542
669
  * Builds a TypeScript tuple type node from an array schema's `items`,
543
670
  * applying min/max slice and optional/rest element rules.
544
671
  */
545
672
  function buildTupleNode(node, print) {
546
- let items = (node.items ?? []).map(print).filter(Boolean);
673
+ let items = (node.items ?? []).map(print).filter(isNonNullable$1);
547
674
  const restNode = node.rest ? print(node.rest) ?? void 0 : void 0;
548
675
  const { min, max } = node;
549
676
  if (max !== void 0) {
@@ -630,13 +757,13 @@ function Enum({ node, enumType, enumTypeSuffix, enumKeyCasing, resolver }) {
630
757
  isExportable: true,
631
758
  isIndexable: true,
632
759
  isTypeOnly: false,
633
- children: safePrint(nameNode)
760
+ children: parserTs.print(nameNode)
634
761
  }), /* @__PURE__ */ jsx(File.Source, {
635
762
  name: typeName,
636
763
  isIndexable: true,
637
764
  isExportable: ENUM_TYPES_WITH_RUNTIME_VALUE.has(enumType),
638
765
  isTypeOnly: ENUM_TYPES_WITH_TYPE_ONLY.has(enumType),
639
- children: safePrint(typeNode)
766
+ children: parserTs.print(typeNode)
640
767
  })] });
641
768
  }
642
769
  //#endregion
@@ -676,6 +803,17 @@ function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCasing, re
676
803
  })] });
677
804
  }
678
805
  //#endregion
806
+ //#region ../../internals/shared/src/operation.ts
807
+ function getOperationParameters(node, options = {}) {
808
+ const params = ast.caseParams(node.parameters, options.paramsCasing);
809
+ return {
810
+ path: params.filter((param) => param.in === "path"),
811
+ query: params.filter((param) => param.in === "query"),
812
+ header: params.filter((param) => param.in === "header"),
813
+ cookie: params.filter((param) => param.in === "cookie")
814
+ };
815
+ }
816
+ //#endregion
679
817
  //#region src/utils.ts
680
818
  /**
681
819
  * Collects JSDoc annotation strings for a schema node.
@@ -687,15 +825,18 @@ function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCasing, re
687
825
  function buildPropertyJSDocComments(schema) {
688
826
  const meta = ast.syncSchemaRef(schema);
689
827
  const isArray = meta?.primitive === "array";
828
+ const hasDescription = meta && "description" in meta && meta.description;
829
+ const formatComment = meta && "format" in meta && meta.format ? hasDescription ? [" ", `Format: \`${meta.format}\``] : ["@description", `Format: \`${meta.format}\``] : [];
690
830
  return [
691
- meta && "description" in meta && meta.description ? `@description ${jsStringEscape(meta.description)}` : void 0,
692
- meta && "deprecated" in meta && meta.deprecated ? "@deprecated" : void 0,
693
- !isArray && meta && "min" in meta && meta.min !== void 0 ? `@minLength ${meta.min}` : void 0,
694
- !isArray && meta && "max" in meta && meta.max !== void 0 ? `@maxLength ${meta.max}` : void 0,
695
- meta && "pattern" in meta && meta.pattern ? `@pattern ${meta.pattern}` : void 0,
696
- meta && "default" in meta && meta.default !== void 0 ? `@default ${"primitive" in meta && meta.primitive === "string" ? stringify(meta.default) : meta.default}` : void 0,
697
- meta && "example" in meta && meta.example !== void 0 ? `@example ${meta.example}` : void 0,
698
- meta && "primitive" in meta && meta.primitive ? [`@type ${meta.primitive}`, "optional" in schema && schema.optional ? " | undefined" : void 0].filter(Boolean).join("") : void 0
831
+ hasDescription ? `@description ${jsStringEscape(meta.description)}` : null,
832
+ ...formatComment,
833
+ meta && "deprecated" in meta && meta.deprecated ? "@deprecated" : null,
834
+ !isArray && meta && "min" in meta && meta.min !== void 0 ? `@minLength ${meta.min}` : null,
835
+ !isArray && meta && "max" in meta && meta.max !== void 0 ? `@maxLength ${meta.max}` : null,
836
+ meta && "pattern" in meta && meta.pattern ? `@pattern ${meta.pattern}` : null,
837
+ meta && "default" in meta && meta.default !== void 0 ? `@default ${"primitive" in meta && meta.primitive === "string" ? stringify(meta.default) : meta.default}` : null,
838
+ meta && "example" in meta && meta.example !== void 0 ? `@example ${meta.example}` : null,
839
+ meta && "primitive" in meta && meta.primitive ? [`@type ${meta.primitive}`, "optional" in schema && schema.optional ? " | undefined" : null].filter(Boolean).join("") : null
699
840
  ].filter(Boolean);
700
841
  }
701
842
  function buildParams(node, { params, resolver }) {
@@ -712,9 +853,7 @@ function buildParams(node, { params, resolver }) {
712
853
  });
713
854
  }
714
855
  function buildData(node, { resolver }) {
715
- const pathParams = node.parameters.filter((p) => p.in === "path");
716
- const queryParams = node.parameters.filter((p) => p.in === "query");
717
- const headerParams = node.parameters.filter((p) => p.in === "header");
856
+ const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node);
718
857
  return ast.createSchema({
719
858
  type: "object",
720
859
  deprecated: node.deprecated,
@@ -796,7 +935,7 @@ function buildResponses(node, { resolver }) {
796
935
  });
797
936
  }
798
937
  function buildResponseUnion(node, { resolver }) {
799
- const responsesWithSchema = node.responses.filter((res) => res.schema);
938
+ const responsesWithSchema = node.responses.filter((res) => res.content?.[0]?.schema);
800
939
  if (responsesWithSchema.length === 0) return null;
801
940
  return ast.createSchema({
802
941
  type: "union",
@@ -808,6 +947,9 @@ function buildResponseUnion(node, { resolver }) {
808
947
  }
809
948
  //#endregion
810
949
  //#region src/printers/printerTs.ts
950
+ function isNonNullable(value) {
951
+ return value !== null && value !== void 0;
952
+ }
811
953
  /**
812
954
  * TypeScript type printer built with `definePrinter`.
813
955
  *
@@ -860,7 +1002,7 @@ const printerTs = ast.definePrinter((options) => {
860
1002
  date: dateOrStringNode,
861
1003
  time: dateOrStringNode,
862
1004
  ref(node) {
863
- if (!node.name) return;
1005
+ if (!node.name) return null;
864
1006
  const refName = node.ref ? ast.extractRefName(node.ref) ?? node.name : node.name;
865
1007
  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);
866
1008
  },
@@ -868,7 +1010,7 @@ const printerTs = ast.definePrinter((options) => {
868
1010
  const values = node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? [];
869
1011
  if (this.options.enumType === "inlineLiteral" || !node.name) return createUnionDeclaration({
870
1012
  withParentheses: true,
871
- nodes: values.filter((v) => v !== null && v !== void 0).map((value) => constToTypeNode(value, typeof value)).filter(Boolean)
1013
+ nodes: values.filter((v) => v !== null && v !== void 0).map((value) => constToTypeNode(value, typeof value)).filter(isNonNullable)
872
1014
  }) ?? void 0;
873
1015
  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);
874
1016
  },
@@ -886,7 +1028,7 @@ const printerTs = ast.definePrinter((options) => {
886
1028
  withParentheses: true
887
1029
  });
888
1030
  return this.transform(m);
889
- }).filter(Boolean)
1031
+ }).filter(isNonNullable)
890
1032
  }) ?? void 0;
891
1033
  return createUnionDeclaration({
892
1034
  withParentheses: true,
@@ -897,16 +1039,16 @@ const printerTs = ast.definePrinter((options) => {
897
1039
  return createIntersectionDeclaration({
898
1040
  withParentheses: true,
899
1041
  nodes: buildMemberNodes(node.members, this.transform)
900
- }) ?? void 0;
1042
+ }) ?? null;
901
1043
  },
902
1044
  array(node) {
903
1045
  return createArrayDeclaration({
904
- nodes: (node.items ?? []).map((item) => this.transform(item)).filter(Boolean),
1046
+ nodes: (node.items ?? []).map((item) => this.transform(item)).filter(isNonNullable),
905
1047
  arrayType: this.options.arrayType
906
- }) ?? void 0;
1048
+ }) ?? null;
907
1049
  },
908
1050
  tuple(node) {
909
- return buildTupleNode(node, this.transform);
1051
+ return buildTupleNode(node, this.transform) ?? null;
910
1052
  },
911
1053
  object(node) {
912
1054
  const { transform, options } = this;
@@ -933,46 +1075,67 @@ const printerTs = ast.definePrinter((options) => {
933
1075
  },
934
1076
  print(node) {
935
1077
  const { name, syntaxType = "type", description, keysToOmit } = this.options;
936
- let base = this.transform(node);
937
- if (!base) return null;
1078
+ const transformed = this.transform(node);
1079
+ if (!transformed) return null;
938
1080
  const meta = ast.syncSchemaRef(node);
939
1081
  if (!name) {
940
- if (meta.nullable) base = createUnionDeclaration({ nodes: [base, keywordTypeNodes.null] });
941
- if ((meta.nullish || meta.optional) && addsUndefined) base = createUnionDeclaration({ nodes: [base, keywordTypeNodes.undefined] });
942
- return safePrint(base);
1082
+ const withNullable = meta.nullable ? createUnionDeclaration({ nodes: [transformed, keywordTypeNodes.null] }) : transformed;
1083
+ const result = (meta.nullish || meta.optional) && addsUndefined ? createUnionDeclaration({ nodes: [withNullable, keywordTypeNodes.undefined] }) : withNullable;
1084
+ return parserTs.print(result);
943
1085
  }
944
- let inner = keysToOmit?.length ? createOmitDeclaration({
945
- keys: keysToOmit,
946
- type: base,
947
- nonNullable: true
948
- }) : base;
949
- if (meta.nullable) inner = createUnionDeclaration({ nodes: [inner, keywordTypeNodes.null] });
950
- if (meta.nullish || meta.optional) inner = createUnionDeclaration({ nodes: [inner, keywordTypeNodes.undefined] });
951
- const useTypeGeneration = syntaxType === "type" || inner.kind === syntaxKind.union || !!keysToOmit?.length;
952
- return safePrint(createTypeDeclaration({
1086
+ const inner = (() => {
1087
+ const omitted = keysToOmit?.length ? createOmitDeclaration({
1088
+ keys: keysToOmit,
1089
+ type: transformed,
1090
+ nonNullable: true
1091
+ }) : transformed;
1092
+ const withNullable = meta.nullable ? createUnionDeclaration({ nodes: [omitted, keywordTypeNodes.null] }) : omitted;
1093
+ return meta.nullish || meta.optional ? createUnionDeclaration({ nodes: [withNullable, keywordTypeNodes.undefined] }) : withNullable;
1094
+ })();
1095
+ const typeNode = createTypeDeclaration({
953
1096
  name,
954
1097
  isExportable: true,
955
1098
  type: inner,
956
- syntax: useTypeGeneration ? "type" : "interface",
1099
+ syntax: syntaxType === "type" || inner.kind === syntaxKind.union || !!keysToOmit?.length ? "type" : "interface",
957
1100
  comments: buildPropertyJSDocComments({
958
1101
  ...meta,
959
1102
  description
960
1103
  })
961
- }));
1104
+ });
1105
+ return parserTs.print(typeNode);
962
1106
  }
963
1107
  };
964
1108
  });
965
1109
  //#endregion
966
1110
  //#region src/generators/typeGenerator.tsx
1111
+ function getContentTypeSuffix(contentType) {
1112
+ const baseType = contentType.split(";")[0].trim();
1113
+ if (baseType === "application/json") return "Json";
1114
+ if (baseType === "multipart/form-data") return "FormData";
1115
+ if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
1116
+ const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
1117
+ if (parts.length === 0) return "Unknown";
1118
+ return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
1119
+ }
1120
+ function getPerContentTypeName(dataName, suffix) {
1121
+ if (dataName.endsWith("Data")) return suffix.endsWith("Data") ? dataName.slice(0, -4) + suffix : `${dataName.slice(0, -4)}${suffix}Data`;
1122
+ return dataName + suffix;
1123
+ }
1124
+ /**
1125
+ * Built-in generator for `@kubb/plugin-ts`. Emits one TypeScript file per
1126
+ * schema in the spec plus per-operation request, response, and parameter
1127
+ * types. Drop-replace with a custom `Generator<PluginTs>` to change how
1128
+ * TypeScript output is produced.
1129
+ */
967
1130
  const typeGenerator = defineGenerator({
968
1131
  name: "typescript",
969
- renderer: jsxRenderer,
1132
+ renderer: jsxRendererSync,
970
1133
  schema(node, ctx) {
971
1134
  const { enumType, enumTypeSuffix, enumKeyCasing, syntaxType, optionalType, arrayType, output, group, printer } = ctx.options;
972
1135
  const { adapter, config, resolver, root } = ctx;
973
1136
  if (!node.name) return;
974
1137
  const mode = ctx.getMode(output);
975
- const enumSchemaNames = new Set((adapter.inputNode?.schemas ?? []).filter((s) => ast.narrowSchema(s, ast.schemaTypes.enum) && s.name).map((s) => s.name));
1138
+ const enumSchemaNames = new Set(ctx.meta.enumNames);
976
1139
  function resolveImportName(schemaName) {
977
1140
  if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && enumTypeSuffix && enumSchemaNames.has(schemaName)) return resolver.resolveEnumKeyName({ name: schemaName }, enumTypeSuffix);
978
1141
  return resolver.resolveTypeName(schemaName);
@@ -985,7 +1148,7 @@ const typeGenerator = defineGenerator({
985
1148
  }, {
986
1149
  root,
987
1150
  output,
988
- group
1151
+ group: group ?? void 0
989
1152
  }).path
990
1153
  }));
991
1154
  const isEnumSchema = !!ast.narrowSchema(node, ast.schemaTypes.enum);
@@ -997,7 +1160,7 @@ const typeGenerator = defineGenerator({
997
1160
  }, {
998
1161
  root,
999
1162
  output,
1000
- group
1163
+ group: group ?? void 0
1001
1164
  })
1002
1165
  };
1003
1166
  const schemaPrinter = printerTs({
@@ -1016,13 +1179,21 @@ const typeGenerator = defineGenerator({
1016
1179
  baseName: meta.file.baseName,
1017
1180
  path: meta.file.path,
1018
1181
  meta: meta.file.meta,
1019
- banner: resolver.resolveBanner(adapter.inputNode, {
1182
+ banner: resolver.resolveBanner(ctx.meta, {
1020
1183
  output,
1021
- config
1184
+ config,
1185
+ file: {
1186
+ path: meta.file.path,
1187
+ baseName: meta.file.baseName
1188
+ }
1022
1189
  }),
1023
- footer: resolver.resolveFooter(adapter.inputNode, {
1190
+ footer: resolver.resolveFooter(ctx.meta, {
1024
1191
  output,
1025
- config
1192
+ config,
1193
+ file: {
1194
+ path: meta.file.path,
1195
+ baseName: meta.file.baseName
1196
+ }
1026
1197
  }),
1027
1198
  children: [mode === "split" && imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
1028
1199
  root: meta.file.path,
@@ -1057,9 +1228,9 @@ const typeGenerator = defineGenerator({
1057
1228
  }, {
1058
1229
  root,
1059
1230
  output,
1060
- group
1231
+ group: group ?? void 0
1061
1232
  }) };
1062
- const enumSchemaNames = new Set((adapter.inputNode?.schemas ?? []).filter((s) => ast.narrowSchema(s, ast.schemaTypes.enum) && s.name).map((s) => s.name));
1233
+ const enumSchemaNames = new Set(ctx.meta.enumNames);
1063
1234
  function resolveImportName(schemaName) {
1064
1235
  if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && enumTypeSuffix && enumSchemaNames.has(schemaName)) return resolver.resolveEnumKeyName({ name: schemaName }, enumTypeSuffix);
1065
1236
  return resolver.resolveTypeName(schemaName);
@@ -1074,7 +1245,7 @@ const typeGenerator = defineGenerator({
1074
1245
  }, {
1075
1246
  root,
1076
1247
  output,
1077
- group
1248
+ group: group ?? void 0
1078
1249
  }).path
1079
1250
  }));
1080
1251
  const schemaPrinter = printerTs({
@@ -1113,18 +1284,58 @@ const typeGenerator = defineGenerator({
1113
1284
  schema: param.schema,
1114
1285
  name: resolver.resolveParamName(node, param)
1115
1286
  }));
1116
- const requestType = node.requestBody?.content?.[0]?.schema ? renderSchemaType({
1117
- schema: {
1118
- ...node.requestBody.content[0].schema,
1119
- description: node.requestBody.description ?? node.requestBody.content[0].schema.description
1120
- },
1121
- name: resolver.resolveDataName(node),
1122
- keysToOmit: node.requestBody.content[0].keysToOmit
1123
- }) : null;
1287
+ const requestBodyContent = node.requestBody?.content ?? [];
1288
+ function buildRequestType() {
1289
+ if (requestBodyContent.length === 0) return null;
1290
+ if (requestBodyContent.length === 1) {
1291
+ const entry = requestBodyContent[0];
1292
+ if (!entry.schema) return null;
1293
+ return renderSchemaType({
1294
+ schema: {
1295
+ ...entry.schema,
1296
+ description: node.requestBody.description ?? entry.schema.description
1297
+ },
1298
+ name: resolver.resolveDataName(node),
1299
+ keysToOmit: entry.keysToOmit
1300
+ });
1301
+ }
1302
+ const dataName = resolver.resolveDataName(node);
1303
+ const usedNames = /* @__PURE__ */ new Set();
1304
+ const individualItems = requestBodyContent.filter((entry) => entry.schema).map((entry) => {
1305
+ const baseSuffix = getContentTypeSuffix(entry.contentType);
1306
+ let individualName = getPerContentTypeName(dataName, baseSuffix);
1307
+ let counter = 2;
1308
+ while (usedNames.has(individualName)) individualName = getPerContentTypeName(dataName, `${baseSuffix}${counter++}`);
1309
+ usedNames.add(individualName);
1310
+ return {
1311
+ name: individualName,
1312
+ rendered: renderSchemaType({
1313
+ schema: {
1314
+ ...entry.schema,
1315
+ description: node.requestBody.description ?? entry.schema.description
1316
+ },
1317
+ name: individualName,
1318
+ keysToOmit: entry.keysToOmit
1319
+ })
1320
+ };
1321
+ });
1322
+ const unionType = renderSchemaType({
1323
+ schema: ast.createSchema({
1324
+ type: "union",
1325
+ members: individualItems.map((item) => ast.createSchema({
1326
+ type: "ref",
1327
+ name: item.name
1328
+ }))
1329
+ }),
1330
+ name: dataName
1331
+ });
1332
+ return /* @__PURE__ */ jsxs(Fragment, { children: [individualItems.map((item) => item.rendered), unionType] });
1333
+ }
1334
+ const requestType = buildRequestType();
1124
1335
  const responseTypes = node.responses.map((res) => renderSchemaType({
1125
- schema: res.schema,
1336
+ schema: res.content?.[0]?.schema ?? null,
1126
1337
  name: resolver.resolveResponseStatusName(node, res.statusCode),
1127
- keysToOmit: res.keysToOmit
1338
+ keysToOmit: res.content?.[0]?.keysToOmit
1128
1339
  }));
1129
1340
  const dataType = renderSchemaType({
1130
1341
  schema: buildData({
@@ -1137,11 +1348,11 @@ const typeGenerator = defineGenerator({
1137
1348
  schema: buildResponses(node, { resolver }),
1138
1349
  name: resolver.resolveResponsesName(node)
1139
1350
  });
1140
- const responseType = (() => {
1141
- if (!node.responses.some((res) => res.schema)) return null;
1351
+ function buildResponseType() {
1352
+ if (!node.responses.some((res) => res.content?.[0]?.schema)) return null;
1142
1353
  const responseName = resolver.resolveResponseName(node);
1143
- const responsesWithSchema = node.responses.filter((res) => res.schema);
1144
- if (new Set(responsesWithSchema.flatMap((res) => res.schema ? adapter.getImports(res.schema, (schemaName) => ({
1354
+ const responsesWithSchema = node.responses.filter((res) => res.content?.[0]?.schema);
1355
+ if (new Set(responsesWithSchema.flatMap((res) => res.content?.[0]?.schema ? adapter.getImports(res.content[0].schema, (schemaName) => ({
1145
1356
  name: resolveImportName(schemaName),
1146
1357
  path: ""
1147
1358
  })).flatMap((imp) => Array.isArray(imp.name) ? imp.name : [imp.name]) : [])).has(responseName)) return null;
@@ -1152,18 +1363,27 @@ const typeGenerator = defineGenerator({
1152
1363
  },
1153
1364
  name: responseName
1154
1365
  });
1155
- })();
1366
+ }
1367
+ const responseType = buildResponseType();
1156
1368
  return /* @__PURE__ */ jsxs(File, {
1157
1369
  baseName: meta.file.baseName,
1158
1370
  path: meta.file.path,
1159
1371
  meta: meta.file.meta,
1160
- banner: resolver.resolveBanner(adapter.inputNode, {
1372
+ banner: resolver.resolveBanner(ctx.meta, {
1161
1373
  output,
1162
- config
1374
+ config,
1375
+ file: {
1376
+ path: meta.file.path,
1377
+ baseName: meta.file.baseName
1378
+ }
1163
1379
  }),
1164
- footer: resolver.resolveFooter(adapter.inputNode, {
1380
+ footer: resolver.resolveFooter(ctx.meta, {
1165
1381
  output,
1166
- config
1382
+ config,
1383
+ file: {
1384
+ path: meta.file.path,
1385
+ baseName: meta.file.baseName
1386
+ }
1167
1387
  }),
1168
1388
  children: [
1169
1389
  paramTypes,
@@ -1179,86 +1399,98 @@ const typeGenerator = defineGenerator({
1179
1399
  //#endregion
1180
1400
  //#region src/resolvers/resolverTs.ts
1181
1401
  /**
1182
- * Resolver for `@kubb/plugin-ts` that provides the default naming and path-resolution
1183
- * helpers used by the plugin. Import this in other plugins to resolve the exact names and
1184
- * paths that `plugin-ts` generates without hardcoding the conventions.
1402
+ * Default resolver used by `@kubb/plugin-ts`. Decides the names and file paths
1403
+ * for every generated TypeScript type. Import this in other plugins that need
1404
+ * to reference the exact names `plugin-ts` produces without duplicating the
1405
+ * casing/file-layout rules.
1185
1406
  *
1186
- * The `default` method is automatically injected by `defineResolver` it uses `camelCase`
1187
- * for identifiers/files and `pascalCase` for type names.
1407
+ * The `default` method is supplied by `defineResolver`. It uses PascalCase for
1408
+ * type names and PascalCase-with-isFile for files.
1188
1409
  *
1189
- * @example
1410
+ * @example Resolve a type and file name
1190
1411
  * ```ts
1191
- * import { resolver } from '@kubb/plugin-ts'
1412
+ * import { resolverTs } from '@kubb/plugin-ts'
1192
1413
  *
1193
- * resolver.default('list pets', 'type') // 'ListPets'
1194
- * resolver.resolveName('list pets status 200') // 'ListPetsStatus200'
1195
- * resolver.resolvePathName('list pets', 'file') // 'listPets'
1414
+ * resolverTs.default('list pets', 'type') // 'ListPets'
1415
+ * resolverTs.resolvePathName('list pets', 'file') // 'ListPets'
1416
+ * resolverTs.resolveResponseStatusName(node, 200) // 'ListPetsStatus200'
1196
1417
  * ```
1197
1418
  */
1198
- const resolverTs = defineResolver((ctx) => {
1419
+ const resolverTs = defineResolver(() => {
1199
1420
  return {
1200
1421
  name: "default",
1201
1422
  pluginName: "plugin-ts",
1202
1423
  default(name, type) {
1203
- return pascalCase(name, { isFile: type === "file" });
1424
+ const resolved = pascalCase(name, { isFile: type === "file" });
1425
+ return type === "file" ? resolved : ensureValidVarName(resolved);
1204
1426
  },
1205
1427
  resolveTypeName(name) {
1206
- return pascalCase(name);
1428
+ return ensureValidVarName(pascalCase(name));
1207
1429
  },
1208
1430
  resolvePathName(name, type) {
1209
- return pascalCase(name, { isFile: type === "file" });
1431
+ const resolved = pascalCase(name, { isFile: type === "file" });
1432
+ return type === "file" ? resolved : ensureValidVarName(resolved);
1210
1433
  },
1211
1434
  resolveParamName(node, param) {
1212
- return ctx.resolveTypeName(`${node.operationId} ${param.in} ${param.name}`);
1435
+ return this.resolveTypeName(`${node.operationId} ${param.in} ${param.name}`);
1213
1436
  },
1214
1437
  resolveResponseStatusName(node, statusCode) {
1215
- return ctx.resolveTypeName(`${node.operationId} Status ${statusCode}`);
1438
+ return this.resolveTypeName(`${node.operationId} Status ${statusCode}`);
1216
1439
  },
1217
1440
  resolveDataName(node) {
1218
- return ctx.resolveTypeName(`${node.operationId} Data`);
1441
+ return this.resolveTypeName(`${node.operationId} Data`);
1219
1442
  },
1220
1443
  resolveRequestConfigName(node) {
1221
- return ctx.resolveTypeName(`${node.operationId} RequestConfig`);
1444
+ return this.resolveTypeName(`${node.operationId} RequestConfig`);
1222
1445
  },
1223
1446
  resolveResponsesName(node) {
1224
- return ctx.resolveTypeName(`${node.operationId} Responses`);
1447
+ return this.resolveTypeName(`${node.operationId} Responses`);
1225
1448
  },
1226
1449
  resolveResponseName(node) {
1227
- return ctx.resolveTypeName(`${node.operationId} Response`);
1450
+ return this.resolveTypeName(`${node.operationId} Response`);
1228
1451
  },
1229
1452
  resolveEnumKeyName(node, enumTypeSuffix = "key") {
1230
- return `${ctx.resolveTypeName(node.name ?? "")}${enumTypeSuffix}`;
1453
+ return `${this.resolveTypeName(node.name ?? "")}${enumTypeSuffix}`;
1231
1454
  },
1232
1455
  resolvePathParamsName(node, param) {
1233
- return ctx.resolveParamName(node, param);
1456
+ return this.resolveParamName(node, param);
1234
1457
  },
1235
1458
  resolveQueryParamsName(node, param) {
1236
- return ctx.resolveParamName(node, param);
1459
+ return this.resolveParamName(node, param);
1237
1460
  },
1238
1461
  resolveHeaderParamsName(node, param) {
1239
- return ctx.resolveParamName(node, param);
1462
+ return this.resolveParamName(node, param);
1240
1463
  }
1241
1464
  };
1242
1465
  });
1243
1466
  //#endregion
1244
1467
  //#region src/plugin.ts
1245
1468
  /**
1246
- * Canonical plugin name for `@kubb/plugin-ts`, used to identify the plugin in driver lookups and warnings.
1469
+ * Canonical plugin name for `@kubb/plugin-ts`. Used for driver lookups and
1470
+ * cross-plugin dependency references.
1247
1471
  */
1248
1472
  const pluginTsName = "plugin-ts";
1249
1473
  /**
1250
- * The `@kubb/plugin-ts` plugin factory.
1251
- *
1252
- * Generates TypeScript type declarations from an OpenAPI/AST `RootNode`.
1253
- * Walks schemas and operations, delegates rendering to the active generators,
1254
- * and writes barrel files based on `output.barrelType`.
1474
+ * Generates TypeScript `type` aliases and `interface` declarations from an
1475
+ * OpenAPI spec. The foundation that every other Kubb plugin builds on:
1476
+ * clients, query hooks, mocks, and validators all reference the names this
1477
+ * plugin produces.
1255
1478
  *
1256
1479
  * @example
1257
1480
  * ```ts
1258
- * import pluginTs from '@kubb/plugin-ts'
1481
+ * import { defineConfig } from 'kubb'
1482
+ * import { pluginTs } from '@kubb/plugin-ts'
1259
1483
  *
1260
1484
  * export default defineConfig({
1261
- * plugins: [pluginTs({ output: { path: 'types' }, enumType: 'asConst' })],
1485
+ * input: { path: './petStore.yaml' },
1486
+ * output: { path: './src/gen' },
1487
+ * plugins: [
1488
+ * pluginTs({
1489
+ * output: { path: './types' },
1490
+ * enumType: 'asConst',
1491
+ * optionalType: 'questionTokenAndUndefined',
1492
+ * }),
1493
+ * ],
1262
1494
  * })
1263
1495
  * ```
1264
1496
  */
@@ -1273,7 +1505,7 @@ const pluginTs = definePlugin((options) => {
1273
1505
  if (group.type === "path") return `${ctx.group.split("/")[1]}`;
1274
1506
  return `${camelCase(ctx.group)}Controller`;
1275
1507
  }
1276
- } : void 0;
1508
+ } : null;
1277
1509
  return {
1278
1510
  name: pluginTsName,
1279
1511
  options,