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

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;
@@ -167,6 +171,129 @@ function stringify(value) {
167
171
  return JSON.stringify(trimQuotes(value.toString()));
168
172
  }
169
173
  //#endregion
174
+ //#region ../../internals/utils/src/reserved.ts
175
+ /**
176
+ * JavaScript and Java reserved words.
177
+ * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
178
+ */
179
+ const reservedWords = new Set([
180
+ "abstract",
181
+ "arguments",
182
+ "boolean",
183
+ "break",
184
+ "byte",
185
+ "case",
186
+ "catch",
187
+ "char",
188
+ "class",
189
+ "const",
190
+ "continue",
191
+ "debugger",
192
+ "default",
193
+ "delete",
194
+ "do",
195
+ "double",
196
+ "else",
197
+ "enum",
198
+ "eval",
199
+ "export",
200
+ "extends",
201
+ "false",
202
+ "final",
203
+ "finally",
204
+ "float",
205
+ "for",
206
+ "function",
207
+ "goto",
208
+ "if",
209
+ "implements",
210
+ "import",
211
+ "in",
212
+ "instanceof",
213
+ "int",
214
+ "interface",
215
+ "let",
216
+ "long",
217
+ "native",
218
+ "new",
219
+ "null",
220
+ "package",
221
+ "private",
222
+ "protected",
223
+ "public",
224
+ "return",
225
+ "short",
226
+ "static",
227
+ "super",
228
+ "switch",
229
+ "synchronized",
230
+ "this",
231
+ "throw",
232
+ "throws",
233
+ "transient",
234
+ "true",
235
+ "try",
236
+ "typeof",
237
+ "var",
238
+ "void",
239
+ "volatile",
240
+ "while",
241
+ "with",
242
+ "yield",
243
+ "Array",
244
+ "Date",
245
+ "hasOwnProperty",
246
+ "Infinity",
247
+ "isFinite",
248
+ "isNaN",
249
+ "isPrototypeOf",
250
+ "length",
251
+ "Math",
252
+ "name",
253
+ "NaN",
254
+ "Number",
255
+ "Object",
256
+ "prototype",
257
+ "String",
258
+ "toString",
259
+ "undefined",
260
+ "valueOf"
261
+ ]);
262
+ /**
263
+ * Returns `true` when `name` is a syntactically valid JavaScript variable name.
264
+ *
265
+ * @example
266
+ * ```ts
267
+ * isValidVarName('status') // true
268
+ * isValidVarName('class') // false (reserved word)
269
+ * isValidVarName('42foo') // false (starts with digit)
270
+ * ```
271
+ */
272
+ function isValidVarName(name) {
273
+ if (!name || reservedWords.has(name)) return false;
274
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
275
+ }
276
+ /**
277
+ * Returns `name` when it's a syntactically valid JavaScript variable name,
278
+ * otherwise prefixes it with `_` so the result is a valid identifier.
279
+ *
280
+ * Useful for sanitizing OpenAPI schema names or operation IDs that start with
281
+ * a digit (e.g. `409`, `504AccountCancel`) before using them as exported
282
+ * variable, type, or function names.
283
+ *
284
+ * @example
285
+ * ```ts
286
+ * ensureValidVarName('409') // '_409'
287
+ * ensureValidVarName('504AccountCancel') // '_504AccountCancel'
288
+ * ensureValidVarName('Pet') // 'Pet'
289
+ * ensureValidVarName('class') // '_class'
290
+ * ```
291
+ */
292
+ function ensureValidVarName(name) {
293
+ if (!name || isValidVarName(name)) return name;
294
+ return `_${name}`;
295
+ }
296
+ //#endregion
170
297
  //#region src/constants.ts
171
298
  /**
172
299
  * `optionalType` values that cause a property's type to include `| undefined`.
@@ -229,6 +356,10 @@ const syntaxKind = {
229
356
  literalType: SyntaxKind.LiteralType,
230
357
  stringLiteral: SyntaxKind.StringLiteral
231
358
  };
359
+ function isNonNullable$1(value) {
360
+ return value !== null && value !== void 0;
361
+ }
362
+ __name(isNonNullable$1, "isNonNullable");
232
363
  function isValidIdentifier(str) {
233
364
  if (!str.length || str.trim() !== str) return false;
234
365
  const node = typescript.default.parseIsolatedEntityName(str, typescript.default.ScriptTarget.Latest);
@@ -298,7 +429,7 @@ function createUnionDeclaration({ nodes, withParentheses }) {
298
429
  * Supports optional markers, readonly modifiers, and type annotations.
299
430
  */
300
431
  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);
432
+ return factory.createPropertySignature([...modifiers, readOnly ? factory.createToken(typescript.default.SyntaxKind.ReadonlyKeyword) : void 0].filter((modifier) => modifier !== void 0), propertyName(name), createQuestionToken(questionToken), type);
302
433
  }
303
434
  /**
304
435
  * Creates a function parameter declaration with optional markers, rest parameters, and type annotations.
@@ -396,8 +527,8 @@ function createEnumDeclaration({ type = "enum", name, typeName, enums, enumKeyCa
396
527
  }
397
528
  if (typeof value === "boolean") return factory.createLiteralTypeNode(value ? factory.createTrue() : factory.createFalse());
398
529
  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]) => {
530
+ }).filter((node) => node !== void 0)))];
531
+ 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
532
  let initializer = factory.createStringLiteral(value?.toString());
402
533
  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)));
403
534
  else initializer = factory.createNumericLiteral(value);
@@ -410,7 +541,7 @@ function createEnumDeclaration({ type = "enum", name, typeName, enums, enumKeyCa
410
541
  const casingKey = applyEnumKeyCasing(key.toString(), enumKeyCasing);
411
542
  return factory.createEnumMember(propertyName(casingKey), initializer);
412
543
  }
413
- }).filter(Boolean))];
544
+ }).filter((member) => member !== void 0))];
414
545
  const identifierName = name;
415
546
  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
547
  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]) => {
@@ -422,7 +553,7 @@ function createEnumDeclaration({ type = "enum", name, typeName, enums, enumKeyCa
422
553
  const casingKey = applyEnumKeyCasing(key.toString(), enumKeyCasing);
423
554
  return factory.createPropertyAssignment(propertyName(casingKey), initializer);
424
555
  }
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))))];
556
+ }).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
557
  }
427
558
  /**
428
559
  * Creates a TypeScript `Omit<T, Keys>` type reference node.
@@ -562,14 +693,14 @@ function dateOrStringNode(node) {
562
693
  * Maps an array of `SchemaNode`s through the printer, filtering out `null` and `undefined` results.
563
694
  */
564
695
  function buildMemberNodes(members, print) {
565
- return (members ?? []).map(print).filter(Boolean);
696
+ return (members ?? []).map(print).filter(isNonNullable$1);
566
697
  }
567
698
  /**
568
699
  * Builds a TypeScript tuple type node from an array schema's `items`,
569
700
  * applying min/max slice and optional/rest element rules.
570
701
  */
571
702
  function buildTupleNode(node, print) {
572
- let items = (node.items ?? []).map(print).filter(Boolean);
703
+ let items = (node.items ?? []).map(print).filter(isNonNullable$1);
573
704
  const restNode = node.rest ? print(node.rest) ?? void 0 : void 0;
574
705
  const { min, max } = node;
575
706
  if (max !== void 0) {
@@ -656,13 +787,13 @@ function Enum({ node, enumType, enumTypeSuffix, enumKeyCasing, resolver }) {
656
787
  isExportable: true,
657
788
  isIndexable: true,
658
789
  isTypeOnly: false,
659
- children: (0, _kubb_parser_ts.safePrint)(nameNode)
790
+ children: _kubb_parser_ts.parserTs.print(nameNode)
660
791
  }), /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
661
792
  name: typeName,
662
793
  isIndexable: true,
663
794
  isExportable: ENUM_TYPES_WITH_RUNTIME_VALUE.has(enumType),
664
795
  isTypeOnly: ENUM_TYPES_WITH_TYPE_ONLY.has(enumType),
665
- children: (0, _kubb_parser_ts.safePrint)(typeNode)
796
+ children: _kubb_parser_ts.parserTs.print(typeNode)
666
797
  })] });
667
798
  }
668
799
  //#endregion
@@ -702,6 +833,98 @@ function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCasing, re
702
833
  })] });
703
834
  }
704
835
  //#endregion
836
+ //#region ../../internals/shared/src/operation.ts
837
+ /**
838
+ * Maps a content type to the PascalCase suffix used to name per-content-type variants
839
+ * (e.g. `application/json` → `Json`, `application/xml` → `Xml`, `multipart/form-data` → `FormData`).
840
+ */
841
+ function getContentTypeSuffix(contentType) {
842
+ const baseType = contentType.split(";")[0].trim();
843
+ if (baseType === "application/json") return "Json";
844
+ if (baseType === "multipart/form-data") return "FormData";
845
+ if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
846
+ const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
847
+ if (parts.length === 0) return "Unknown";
848
+ return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
849
+ }
850
+ /**
851
+ * Appends a content-type suffix to a base name, keeping a trailing `Data` segment last
852
+ * (e.g. `AddPetData` + `Json` → `AddPetJsonData`, `AddPetStatus200` + `Xml` → `AddPetStatus200Xml`).
853
+ */
854
+ function getPerContentTypeName(baseName, suffix) {
855
+ if (baseName.endsWith("Data")) return suffix.endsWith("Data") ? baseName.slice(0, -4) + suffix : `${baseName.slice(0, -4)}${suffix}Data`;
856
+ return baseName + suffix;
857
+ }
858
+ /**
859
+ * Resolves per-content-type variant names for a set of content entries, deduplicating suffix
860
+ * collisions with a numeric counter. Entries without a schema are skipped. The returned `suffix` is
861
+ * the final (possibly counter-augmented) value, so callers can derive parallel names in another
862
+ * namespace (e.g. plugin-faker deriving the matching plugin-ts type name).
863
+ */
864
+ function resolveContentTypeVariants(entries, baseName) {
865
+ const usedNames = /* @__PURE__ */ new Set();
866
+ return entries.filter((entry) => entry.schema).map((entry) => {
867
+ const baseSuffix = getContentTypeSuffix(entry.contentType);
868
+ let suffix = baseSuffix;
869
+ let name = getPerContentTypeName(baseName, suffix);
870
+ let counter = 2;
871
+ while (usedNames.has(name)) {
872
+ suffix = `${baseSuffix}${counter++}`;
873
+ name = getPerContentTypeName(baseName, suffix);
874
+ }
875
+ usedNames.add(name);
876
+ return {
877
+ name,
878
+ suffix,
879
+ schema: entry.schema,
880
+ keysToOmit: entry.keysToOmit,
881
+ contentType: entry.contentType
882
+ };
883
+ });
884
+ }
885
+ function getOperationParameters(node, options = {}) {
886
+ const params = _kubb_core.ast.caseParams(node.parameters, options.paramsCasing);
887
+ return {
888
+ path: params.filter((param) => param.in === "path"),
889
+ query: params.filter((param) => param.in === "query"),
890
+ header: params.filter((param) => param.in === "header"),
891
+ cookie: params.filter((param) => param.in === "cookie")
892
+ };
893
+ }
894
+ //#endregion
895
+ //#region ../../internals/shared/src/group.ts
896
+ /**
897
+ * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
898
+ * shared default naming so every plugin groups output consistently:
899
+ *
900
+ * - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
901
+ * - other groups use `${camelCase(group)}${suffix}` (e.g. `petController`).
902
+ *
903
+ * Returns `null` when grouping is disabled, matching the per-plugin convention.
904
+ *
905
+ * @param group - The user-supplied group option, or `undefined` to disable grouping.
906
+ * @param options.suffix - Appended to non-`path` group names, e.g. `'Controller'` or `'Requests'`.
907
+ * @param options.honorName - When `true`, a user-provided `group.name` overrides the default namer.
908
+ *
909
+ * @example
910
+ * ```ts
911
+ * createGroupConfig(group, { suffix: 'Controller' }) // plugin-ts, plugin-zod
912
+ * createGroupConfig(group, { suffix: 'Controller', honorName: true }) // plugin-faker, plugin-client, …
913
+ * createGroupConfig(group, { suffix: 'Requests', honorName: true }) // plugin-cypress, plugin-mcp
914
+ * ```
915
+ */
916
+ function createGroupConfig(group, options) {
917
+ if (!group) return null;
918
+ const defaultName = (ctx) => {
919
+ if (group.type === "path") return `${ctx.group.split("/")[1]}`;
920
+ return `${camelCase(ctx.group)}${options.suffix}`;
921
+ };
922
+ return {
923
+ ...group,
924
+ name: options.honorName && group.name ? group.name : defaultName
925
+ };
926
+ }
927
+ //#endregion
705
928
  //#region src/utils.ts
706
929
  /**
707
930
  * Collects JSDoc annotation strings for a schema node.
@@ -713,15 +936,18 @@ function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCasing, re
713
936
  function buildPropertyJSDocComments(schema) {
714
937
  const meta = _kubb_core.ast.syncSchemaRef(schema);
715
938
  const isArray = meta?.primitive === "array";
939
+ const hasDescription = meta && "description" in meta && meta.description;
940
+ const formatComment = meta && "format" in meta && meta.format ? hasDescription ? [" ", `Format: \`${meta.format}\``] : ["@description", `Format: \`${meta.format}\``] : [];
716
941
  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
942
+ hasDescription ? `@description ${jsStringEscape(meta.description)}` : null,
943
+ ...formatComment,
944
+ meta && "deprecated" in meta && meta.deprecated ? "@deprecated" : null,
945
+ !isArray && meta && "min" in meta && meta.min !== void 0 ? `@minLength ${meta.min}` : null,
946
+ !isArray && meta && "max" in meta && meta.max !== void 0 ? `@maxLength ${meta.max}` : null,
947
+ meta && "pattern" in meta && meta.pattern ? `@pattern ${meta.pattern}` : null,
948
+ meta && "default" in meta && meta.default !== void 0 ? `@default ${"primitive" in meta && meta.primitive === "string" ? stringify(meta.default) : meta.default}` : null,
949
+ meta && "example" in meta && meta.example !== void 0 ? `@example ${meta.example}` : null,
950
+ meta && "primitive" in meta && meta.primitive ? [`@type ${meta.primitive}`, "optional" in schema && schema.optional ? " | undefined" : null].filter(Boolean).join("") : null
725
951
  ].filter(Boolean);
726
952
  }
727
953
  function buildParams(node, { params, resolver }) {
@@ -738,9 +964,7 @@ function buildParams(node, { params, resolver }) {
738
964
  });
739
965
  }
740
966
  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");
967
+ const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node);
744
968
  return _kubb_core.ast.createSchema({
745
969
  type: "object",
746
970
  deprecated: node.deprecated,
@@ -822,7 +1046,7 @@ function buildResponses(node, { resolver }) {
822
1046
  });
823
1047
  }
824
1048
  function buildResponseUnion(node, { resolver }) {
825
- const responsesWithSchema = node.responses.filter((res) => res.schema);
1049
+ const responsesWithSchema = node.responses.filter((res) => res.content?.some((entry) => entry.schema));
826
1050
  if (responsesWithSchema.length === 0) return null;
827
1051
  return _kubb_core.ast.createSchema({
828
1052
  type: "union",
@@ -834,6 +1058,9 @@ function buildResponseUnion(node, { resolver }) {
834
1058
  }
835
1059
  //#endregion
836
1060
  //#region src/printers/printerTs.ts
1061
+ function isNonNullable(value) {
1062
+ return value !== null && value !== void 0;
1063
+ }
837
1064
  /**
838
1065
  * TypeScript type printer built with `definePrinter`.
839
1066
  *
@@ -886,7 +1113,7 @@ const printerTs = _kubb_core.ast.definePrinter((options) => {
886
1113
  date: dateOrStringNode,
887
1114
  time: dateOrStringNode,
888
1115
  ref(node) {
889
- if (!node.name) return;
1116
+ if (!node.name) return null;
890
1117
  const refName = node.ref ? _kubb_core.ast.extractRefName(node.ref) ?? node.name : node.name;
891
1118
  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
1119
  },
@@ -894,7 +1121,7 @@ const printerTs = _kubb_core.ast.definePrinter((options) => {
894
1121
  const values = node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? [];
895
1122
  if (this.options.enumType === "inlineLiteral" || !node.name) return createUnionDeclaration({
896
1123
  withParentheses: true,
897
- nodes: values.filter((v) => v !== null && v !== void 0).map((value) => constToTypeNode(value, typeof value)).filter(Boolean)
1124
+ nodes: values.filter((v) => v !== null && v !== void 0).map((value) => constToTypeNode(value, typeof value)).filter(isNonNullable)
898
1125
  }) ?? void 0;
899
1126
  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
1127
  },
@@ -912,7 +1139,7 @@ const printerTs = _kubb_core.ast.definePrinter((options) => {
912
1139
  withParentheses: true
913
1140
  });
914
1141
  return this.transform(m);
915
- }).filter(Boolean)
1142
+ }).filter(isNonNullable)
916
1143
  }) ?? void 0;
917
1144
  return createUnionDeclaration({
918
1145
  withParentheses: true,
@@ -923,16 +1150,16 @@ const printerTs = _kubb_core.ast.definePrinter((options) => {
923
1150
  return createIntersectionDeclaration({
924
1151
  withParentheses: true,
925
1152
  nodes: buildMemberNodes(node.members, this.transform)
926
- }) ?? void 0;
1153
+ }) ?? null;
927
1154
  },
928
1155
  array(node) {
929
1156
  return createArrayDeclaration({
930
- nodes: (node.items ?? []).map((item) => this.transform(item)).filter(Boolean),
1157
+ nodes: (node.items ?? []).map((item) => this.transform(item)).filter(isNonNullable),
931
1158
  arrayType: this.options.arrayType
932
- }) ?? void 0;
1159
+ }) ?? null;
933
1160
  },
934
1161
  tuple(node) {
935
- return buildTupleNode(node, this.transform);
1162
+ return buildTupleNode(node, this.transform) ?? null;
936
1163
  },
937
1164
  object(node) {
938
1165
  const { transform, options } = this;
@@ -959,46 +1186,54 @@ const printerTs = _kubb_core.ast.definePrinter((options) => {
959
1186
  },
960
1187
  print(node) {
961
1188
  const { name, syntaxType = "type", description, keysToOmit } = this.options;
962
- let base = this.transform(node);
963
- if (!base) return null;
1189
+ const transformed = this.transform(node);
1190
+ if (!transformed) return null;
964
1191
  const meta = _kubb_core.ast.syncSchemaRef(node);
965
1192
  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);
1193
+ const withNullable = meta.nullable ? createUnionDeclaration({ nodes: [transformed, keywordTypeNodes.null] }) : transformed;
1194
+ const result = (meta.nullish || meta.optional) && addsUndefined ? createUnionDeclaration({ nodes: [withNullable, keywordTypeNodes.undefined] }) : withNullable;
1195
+ return _kubb_parser_ts.parserTs.print(result);
969
1196
  }
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({
1197
+ const inner = (() => {
1198
+ const omitted = keysToOmit?.length ? createOmitDeclaration({
1199
+ keys: keysToOmit,
1200
+ type: transformed,
1201
+ nonNullable: true
1202
+ }) : transformed;
1203
+ const withNullable = meta.nullable ? createUnionDeclaration({ nodes: [omitted, keywordTypeNodes.null] }) : omitted;
1204
+ return meta.nullish || meta.optional ? createUnionDeclaration({ nodes: [withNullable, keywordTypeNodes.undefined] }) : withNullable;
1205
+ })();
1206
+ const typeNode = createTypeDeclaration({
979
1207
  name,
980
1208
  isExportable: true,
981
1209
  type: inner,
982
- syntax: useTypeGeneration ? "type" : "interface",
1210
+ syntax: syntaxType === "type" || inner.kind === syntaxKind.union || !!keysToOmit?.length ? "type" : "interface",
983
1211
  comments: buildPropertyJSDocComments({
984
1212
  ...meta,
985
1213
  description
986
1214
  })
987
- }));
1215
+ });
1216
+ return _kubb_parser_ts.parserTs.print(typeNode);
988
1217
  }
989
1218
  };
990
1219
  });
991
1220
  //#endregion
992
1221
  //#region src/generators/typeGenerator.tsx
1222
+ /**
1223
+ * Built-in generator for `@kubb/plugin-ts`. Emits one TypeScript file per
1224
+ * schema in the spec plus per-operation request, response, and parameter
1225
+ * types. Drop-replace with a custom `Generator<PluginTs>` to change how
1226
+ * TypeScript output is produced.
1227
+ */
993
1228
  const typeGenerator = (0, _kubb_core.defineGenerator)({
994
1229
  name: "typescript",
995
- renderer: _kubb_renderer_jsx.jsxRenderer,
1230
+ renderer: _kubb_renderer_jsx.jsxRendererSync,
996
1231
  schema(node, ctx) {
997
1232
  const { enumType, enumTypeSuffix, enumKeyCasing, syntaxType, optionalType, arrayType, output, group, printer } = ctx.options;
998
1233
  const { adapter, config, resolver, root } = ctx;
999
1234
  if (!node.name) return;
1000
1235
  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));
1236
+ const enumSchemaNames = new Set(ctx.meta.enumNames);
1002
1237
  function resolveImportName(schemaName) {
1003
1238
  if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && enumTypeSuffix && enumSchemaNames.has(schemaName)) return resolver.resolveEnumKeyName({ name: schemaName }, enumTypeSuffix);
1004
1239
  return resolver.resolveTypeName(schemaName);
@@ -1011,7 +1246,7 @@ const typeGenerator = (0, _kubb_core.defineGenerator)({
1011
1246
  }, {
1012
1247
  root,
1013
1248
  output,
1014
- group
1249
+ group: group ?? void 0
1015
1250
  }).path
1016
1251
  }));
1017
1252
  const isEnumSchema = !!_kubb_core.ast.narrowSchema(node, _kubb_core.ast.schemaTypes.enum);
@@ -1023,7 +1258,7 @@ const typeGenerator = (0, _kubb_core.defineGenerator)({
1023
1258
  }, {
1024
1259
  root,
1025
1260
  output,
1026
- group
1261
+ group: group ?? void 0
1027
1262
  })
1028
1263
  };
1029
1264
  const schemaPrinter = printerTs({
@@ -1042,13 +1277,21 @@ const typeGenerator = (0, _kubb_core.defineGenerator)({
1042
1277
  baseName: meta.file.baseName,
1043
1278
  path: meta.file.path,
1044
1279
  meta: meta.file.meta,
1045
- banner: resolver.resolveBanner(adapter.inputNode, {
1280
+ banner: resolver.resolveBanner(ctx.meta, {
1046
1281
  output,
1047
- config
1282
+ config,
1283
+ file: {
1284
+ path: meta.file.path,
1285
+ baseName: meta.file.baseName
1286
+ }
1048
1287
  }),
1049
- footer: resolver.resolveFooter(adapter.inputNode, {
1288
+ footer: resolver.resolveFooter(ctx.meta, {
1050
1289
  output,
1051
- config
1290
+ config,
1291
+ file: {
1292
+ path: meta.file.path,
1293
+ baseName: meta.file.baseName
1294
+ }
1052
1295
  }),
1053
1296
  children: [mode === "split" && imports.map((imp) => /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1054
1297
  root: meta.file.path,
@@ -1083,9 +1326,9 @@ const typeGenerator = (0, _kubb_core.defineGenerator)({
1083
1326
  }, {
1084
1327
  root,
1085
1328
  output,
1086
- group
1329
+ group: group ?? void 0
1087
1330
  }) };
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));
1331
+ const enumSchemaNames = new Set(ctx.meta.enumNames);
1089
1332
  function resolveImportName(schemaName) {
1090
1333
  if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && enumTypeSuffix && enumSchemaNames.has(schemaName)) return resolver.resolveEnumKeyName({ name: schemaName }, enumTypeSuffix);
1091
1334
  return resolver.resolveTypeName(schemaName);
@@ -1100,7 +1343,7 @@ const typeGenerator = (0, _kubb_core.defineGenerator)({
1100
1343
  }, {
1101
1344
  root,
1102
1345
  output,
1103
- group
1346
+ group: group ?? void 0
1104
1347
  }).path
1105
1348
  }));
1106
1349
  const schemaPrinter = printerTs({
@@ -1135,23 +1378,63 @@ const typeGenerator = (0, _kubb_core.defineGenerator)({
1135
1378
  printer: schemaPrinter
1136
1379
  })] });
1137
1380
  }
1381
+ /**
1382
+ * Emits an individual type per content type plus a union alias under `baseName`.
1383
+ * Shared by the request body and multi-content-type responses.
1384
+ */
1385
+ function buildContentTypeVariants(entries, baseName, decorate) {
1386
+ const variants = resolveContentTypeVariants(entries, baseName);
1387
+ const unionSchema = _kubb_core.ast.createSchema({
1388
+ type: "union",
1389
+ members: variants.map((variant) => _kubb_core.ast.createSchema({
1390
+ type: "ref",
1391
+ name: variant.name
1392
+ }))
1393
+ });
1394
+ return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [variants.map((variant) => renderSchemaType({
1395
+ schema: decorate ? decorate(variant.schema) : variant.schema,
1396
+ name: variant.name,
1397
+ keysToOmit: variant.keysToOmit
1398
+ })), renderSchemaType({
1399
+ schema: unionSchema,
1400
+ name: baseName
1401
+ })] });
1402
+ }
1138
1403
  const paramTypes = params.map((param) => renderSchemaType({
1139
1404
  schema: param.schema,
1140
1405
  name: resolver.resolveParamName(node, param)
1141
1406
  }));
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
- }));
1407
+ const requestBodyContent = node.requestBody?.content ?? [];
1408
+ function buildRequestType() {
1409
+ if (requestBodyContent.length === 0) return null;
1410
+ if (requestBodyContent.length === 1) {
1411
+ const entry = requestBodyContent[0];
1412
+ if (!entry.schema) return null;
1413
+ return renderSchemaType({
1414
+ schema: {
1415
+ ...entry.schema,
1416
+ description: node.requestBody.description ?? entry.schema.description
1417
+ },
1418
+ name: resolver.resolveDataName(node),
1419
+ keysToOmit: entry.keysToOmit
1420
+ });
1421
+ }
1422
+ return buildContentTypeVariants(requestBodyContent, resolver.resolveDataName(node), (schema) => ({
1423
+ ...schema,
1424
+ description: node.requestBody.description ?? schema.description
1425
+ }));
1426
+ }
1427
+ const requestType = buildRequestType();
1428
+ const responseTypes = node.responses.map((res) => {
1429
+ const variants = (res.content ?? []).filter((entry) => entry.schema);
1430
+ if (variants.length > 1) return buildContentTypeVariants(variants, resolver.resolveResponseStatusName(node, res.statusCode));
1431
+ const primary = variants[0] ?? res.content?.[0];
1432
+ return renderSchemaType({
1433
+ schema: primary?.schema ?? null,
1434
+ name: resolver.resolveResponseStatusName(node, res.statusCode),
1435
+ keysToOmit: primary?.keysToOmit
1436
+ });
1437
+ });
1155
1438
  const dataType = renderSchemaType({
1156
1439
  schema: buildData({
1157
1440
  ...node,
@@ -1163,14 +1446,15 @@ const typeGenerator = (0, _kubb_core.defineGenerator)({
1163
1446
  schema: buildResponses(node, { resolver }),
1164
1447
  name: resolver.resolveResponsesName(node)
1165
1448
  });
1166
- const responseType = (() => {
1167
- if (!node.responses.some((res) => res.schema)) return null;
1449
+ function buildResponseType() {
1450
+ const hasSchema = (res) => (res.content ?? []).some((entry) => entry.schema);
1451
+ if (!node.responses.some(hasSchema)) return null;
1168
1452
  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) => ({
1453
+ const responsesWithSchema = node.responses.filter(hasSchema);
1454
+ if (new Set(responsesWithSchema.flatMap((res) => (res.content ?? []).flatMap((entry) => entry.schema ? adapter.getImports(entry.schema, (schemaName) => ({
1171
1455
  name: resolveImportName(schemaName),
1172
1456
  path: ""
1173
- })).flatMap((imp) => Array.isArray(imp.name) ? imp.name : [imp.name]) : [])).has(responseName)) return null;
1457
+ })).flatMap((imp) => Array.isArray(imp.name) ? imp.name : [imp.name]) : []))).has(responseName)) return null;
1174
1458
  return renderSchemaType({
1175
1459
  schema: {
1176
1460
  ...buildResponseUnion(node, { resolver }),
@@ -1178,18 +1462,27 @@ const typeGenerator = (0, _kubb_core.defineGenerator)({
1178
1462
  },
1179
1463
  name: responseName
1180
1464
  });
1181
- })();
1465
+ }
1466
+ const responseType = buildResponseType();
1182
1467
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
1183
1468
  baseName: meta.file.baseName,
1184
1469
  path: meta.file.path,
1185
1470
  meta: meta.file.meta,
1186
- banner: resolver.resolveBanner(adapter.inputNode, {
1471
+ banner: resolver.resolveBanner(ctx.meta, {
1187
1472
  output,
1188
- config
1473
+ config,
1474
+ file: {
1475
+ path: meta.file.path,
1476
+ baseName: meta.file.baseName
1477
+ }
1189
1478
  }),
1190
- footer: resolver.resolveFooter(adapter.inputNode, {
1479
+ footer: resolver.resolveFooter(ctx.meta, {
1191
1480
  output,
1192
- config
1481
+ config,
1482
+ file: {
1483
+ path: meta.file.path,
1484
+ baseName: meta.file.baseName
1485
+ }
1193
1486
  }),
1194
1487
  children: [
1195
1488
  paramTypes,
@@ -1205,86 +1498,98 @@ const typeGenerator = (0, _kubb_core.defineGenerator)({
1205
1498
  //#endregion
1206
1499
  //#region src/resolvers/resolverTs.ts
1207
1500
  /**
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.
1501
+ * Default resolver used by `@kubb/plugin-ts`. Decides the names and file paths
1502
+ * for every generated TypeScript type. Import this in other plugins that need
1503
+ * to reference the exact names `plugin-ts` produces without duplicating the
1504
+ * casing/file-layout rules.
1211
1505
  *
1212
- * The `default` method is automatically injected by `defineResolver` it uses `camelCase`
1213
- * for identifiers/files and `pascalCase` for type names.
1506
+ * The `default` method is supplied by `defineResolver`. It uses PascalCase for
1507
+ * type names and PascalCase-with-isFile for files.
1214
1508
  *
1215
- * @example
1509
+ * @example Resolve a type and file name
1216
1510
  * ```ts
1217
- * import { resolver } from '@kubb/plugin-ts'
1511
+ * import { resolverTs } from '@kubb/plugin-ts'
1218
1512
  *
1219
- * resolver.default('list pets', 'type') // 'ListPets'
1220
- * resolver.resolveName('list pets status 200') // 'ListPetsStatus200'
1221
- * resolver.resolvePathName('list pets', 'file') // 'listPets'
1513
+ * resolverTs.default('list pets', 'type') // 'ListPets'
1514
+ * resolverTs.resolvePathName('list pets', 'file') // 'ListPets'
1515
+ * resolverTs.resolveResponseStatusName(node, 200) // 'ListPetsStatus200'
1222
1516
  * ```
1223
1517
  */
1224
- const resolverTs = (0, _kubb_core.defineResolver)((ctx) => {
1518
+ const resolverTs = (0, _kubb_core.defineResolver)(() => {
1225
1519
  return {
1226
1520
  name: "default",
1227
1521
  pluginName: "plugin-ts",
1228
1522
  default(name, type) {
1229
- return pascalCase(name, { isFile: type === "file" });
1523
+ const resolved = pascalCase(name, { isFile: type === "file" });
1524
+ return type === "file" ? resolved : ensureValidVarName(resolved);
1230
1525
  },
1231
1526
  resolveTypeName(name) {
1232
- return pascalCase(name);
1527
+ return ensureValidVarName(pascalCase(name));
1233
1528
  },
1234
1529
  resolvePathName(name, type) {
1235
- return pascalCase(name, { isFile: type === "file" });
1530
+ const resolved = pascalCase(name, { isFile: type === "file" });
1531
+ return type === "file" ? resolved : ensureValidVarName(resolved);
1236
1532
  },
1237
1533
  resolveParamName(node, param) {
1238
- return ctx.resolveTypeName(`${node.operationId} ${param.in} ${param.name}`);
1534
+ return this.resolveTypeName(`${node.operationId} ${param.in} ${param.name}`);
1239
1535
  },
1240
1536
  resolveResponseStatusName(node, statusCode) {
1241
- return ctx.resolveTypeName(`${node.operationId} Status ${statusCode}`);
1537
+ return this.resolveTypeName(`${node.operationId} Status ${statusCode}`);
1242
1538
  },
1243
1539
  resolveDataName(node) {
1244
- return ctx.resolveTypeName(`${node.operationId} Data`);
1540
+ return this.resolveTypeName(`${node.operationId} Data`);
1245
1541
  },
1246
1542
  resolveRequestConfigName(node) {
1247
- return ctx.resolveTypeName(`${node.operationId} RequestConfig`);
1543
+ return this.resolveTypeName(`${node.operationId} RequestConfig`);
1248
1544
  },
1249
1545
  resolveResponsesName(node) {
1250
- return ctx.resolveTypeName(`${node.operationId} Responses`);
1546
+ return this.resolveTypeName(`${node.operationId} Responses`);
1251
1547
  },
1252
1548
  resolveResponseName(node) {
1253
- return ctx.resolveTypeName(`${node.operationId} Response`);
1549
+ return this.resolveTypeName(`${node.operationId} Response`);
1254
1550
  },
1255
1551
  resolveEnumKeyName(node, enumTypeSuffix = "key") {
1256
- return `${ctx.resolveTypeName(node.name ?? "")}${enumTypeSuffix}`;
1552
+ return `${this.resolveTypeName(node.name ?? "")}${enumTypeSuffix}`;
1257
1553
  },
1258
1554
  resolvePathParamsName(node, param) {
1259
- return ctx.resolveParamName(node, param);
1555
+ return this.resolveParamName(node, param);
1260
1556
  },
1261
1557
  resolveQueryParamsName(node, param) {
1262
- return ctx.resolveParamName(node, param);
1558
+ return this.resolveParamName(node, param);
1263
1559
  },
1264
1560
  resolveHeaderParamsName(node, param) {
1265
- return ctx.resolveParamName(node, param);
1561
+ return this.resolveParamName(node, param);
1266
1562
  }
1267
1563
  };
1268
1564
  });
1269
1565
  //#endregion
1270
1566
  //#region src/plugin.ts
1271
1567
  /**
1272
- * Canonical plugin name for `@kubb/plugin-ts`, used to identify the plugin in driver lookups and warnings.
1568
+ * Canonical plugin name for `@kubb/plugin-ts`. Used for driver lookups and
1569
+ * cross-plugin dependency references.
1273
1570
  */
1274
1571
  const pluginTsName = "plugin-ts";
1275
1572
  /**
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`.
1573
+ * Generates TypeScript `type` aliases and `interface` declarations from an
1574
+ * OpenAPI spec. The foundation that every other Kubb plugin builds on:
1575
+ * clients, query hooks, mocks, and validators all reference the names this
1576
+ * plugin produces.
1281
1577
  *
1282
1578
  * @example
1283
1579
  * ```ts
1284
- * import pluginTs from '@kubb/plugin-ts'
1580
+ * import { defineConfig } from 'kubb'
1581
+ * import { pluginTs } from '@kubb/plugin-ts'
1285
1582
  *
1286
1583
  * export default defineConfig({
1287
- * plugins: [pluginTs({ output: { path: 'types' }, enumType: 'asConst' })],
1584
+ * input: { path: './petStore.yaml' },
1585
+ * output: { path: './src/gen' },
1586
+ * plugins: [
1587
+ * pluginTs({
1588
+ * output: { path: './types' },
1589
+ * enumType: 'asConst',
1590
+ * optionalType: 'questionTokenAndUndefined',
1591
+ * }),
1592
+ * ],
1288
1593
  * })
1289
1594
  * ```
1290
1595
  */
@@ -1293,13 +1598,7 @@ const pluginTs = (0, _kubb_core.definePlugin)((options) => {
1293
1598
  path: "types",
1294
1599
  barrelType: "named"
1295
1600
  }, 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;
1601
+ const groupConfig = createGroupConfig(group, { suffix: "Controller" });
1303
1602
  return {
1304
1603
  name: pluginTsName,
1305
1604
  options,