@kubb/plugin-ts 5.0.0-beta.95 → 5.0.0-beta.99

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
@@ -106,31 +106,6 @@ function screamingSnakeCase(text, { prefix = "", suffix = "" } = {}) {
106
106
  }).toUpperCase();
107
107
  }
108
108
  //#endregion
109
- //#region ../../internals/utils/src/fs.ts
110
- /**
111
- * Builds a nested file path from a dotted name. Splits on dots that precede a letter
112
- * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
113
- * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
114
- *
115
- * Empty segments are dropped before joining. They arise when the name starts with a dot
116
- * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
117
- * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
118
- * absolute path, letting generated files escape the configured output directory.
119
- *
120
- * @example Nested path from a dotted name
121
- * `toFilePath('pet.petId') // 'pet/petId'`
122
- *
123
- * @example PascalCase the final segment
124
- * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
125
- *
126
- * @example Suffix applied to the final segment only
127
- * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
128
- */
129
- function toFilePath(name, caseLast = camelCase) {
130
- const parts = name.split(/\.(?=[a-zA-Z])/);
131
- return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
132
- }
133
- //#endregion
134
109
  //#region ../../internals/utils/src/reserved.ts
135
110
  /**
136
111
  * JavaScript and Java reserved words.
@@ -331,6 +306,31 @@ function jsStringEscape(input) {
331
306
  });
332
307
  }
333
308
  //#endregion
309
+ //#region ../../internals/utils/src/fs.ts
310
+ /**
311
+ * Builds a nested file path from a dotted name. Splits on dots that precede a letter
312
+ * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
313
+ * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
314
+ *
315
+ * Empty segments are dropped before joining. They arise when the name starts with a dot
316
+ * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
317
+ * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
318
+ * absolute path, letting generated files escape the configured output directory.
319
+ *
320
+ * @example Nested path from a dotted name
321
+ * `toFilePath('pet.petId') // 'pet/petId'`
322
+ *
323
+ * @example PascalCase the final segment
324
+ * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
325
+ *
326
+ * @example Suffix applied to the final segment only
327
+ * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
328
+ */
329
+ function toFilePath(name, caseLast = camelCase) {
330
+ const parts = name.split(/\.(?=[a-zA-Z])/);
331
+ return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
332
+ }
333
+ //#endregion
334
334
  //#region src/constants.ts
335
335
  /**
336
336
  * `optionalType` values that cause a property's type to include `| undefined`.
@@ -951,39 +951,19 @@ function Enum({ node, enum: enumOptions, resolver }) {
951
951
  }
952
952
  //#endregion
953
953
  //#region ../../internals/shared/src/params.ts
954
- const caseParamsCache = /* @__PURE__ */ new WeakMap();
955
954
  /**
956
- * Applies camelCase to parameter names and returns a new array without mutating the input.
955
+ * Drops parameters that share the same name, keeping the first.
957
956
  *
958
- * Run it before handing parameters to schema builders so output property keys get the right casing
959
- * while `OperationNode.parameters` stays intact for other consumers. When `casing` is unset, the
960
- * original array is returned unchanged. Results are cached per input array.
957
+ * A malformed spec can declare the same parameter name twice within one `in` location. Both would
958
+ * resolve to the same output property, so emitting both would yield an object type with a duplicate
959
+ * member, which TypeScript rejects. This is a defensive guard against that case, not a casing guard:
960
+ * parameter names flow through unchanged, so no two distinct names ever collide here anymore.
961
961
  */
962
- function caseParams(params, casing) {
963
- if (!casing) return params;
964
- const cached = caseParamsCache.get(params);
965
- if (cached) return cached;
966
- const result = params.map((param) => ({
967
- ...param,
968
- name: camelCase(param.name)
969
- }));
970
- caseParamsCache.set(params, result);
971
- return result;
972
- }
973
- /**
974
- * Drops parameters that collapse to the same property identity once camelCased, keeping the first.
975
- *
976
- * Some specs declare the same parameter twice under different casings (for example AWS S3 lists both
977
- * `max-uploads` and `MaxUploads`). Both resolve to one output property, so emitting both would yield
978
- * an object type with a duplicate member, which TypeScript rejects. De-duplicate by the camelCased
979
- * identity so the resulting group is collision-free regardless of the names each caller carries.
980
- */
981
- function dedupeByCasedName(params) {
962
+ function dedupeParams(params) {
982
963
  const seen = /* @__PURE__ */ new Set();
983
964
  return params.filter((param) => {
984
- const key = camelCase(param.name);
985
- if (seen.has(key)) return false;
986
- seen.add(key);
965
+ if (seen.has(param.name)) return false;
966
+ seen.add(param.name);
987
967
  return true;
988
968
  });
989
969
  }
@@ -1037,13 +1017,15 @@ function resolveContentTypeVariants(entries, baseName) {
1037
1017
  };
1038
1018
  });
1039
1019
  }
1040
- function getOperationParameters(node, options = {}) {
1041
- const params = caseParams(node.parameters, options.paramsCasing === "original" ? void 0 : "camelcase");
1020
+ function getOperationParameters(node) {
1042
1021
  return {
1043
- path: dedupeByCasedName(params.filter((param) => param.in === "path")),
1044
- query: dedupeByCasedName(params.filter((param) => param.in === "query")),
1045
- header: dedupeByCasedName(params.filter((param) => param.in === "header")),
1046
- cookie: dedupeByCasedName(params.filter((param) => param.in === "cookie"))
1022
+ path: dedupeParams(node.parameters.filter((param) => param.in === "path").map((param) => isValidVarName(param.name) ? param : {
1023
+ ...param,
1024
+ name: camelCase(param.name)
1025
+ })),
1026
+ query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
1027
+ header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
1028
+ cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
1047
1029
  };
1048
1030
  }
1049
1031
  //#endregion
@@ -1167,6 +1149,35 @@ function createGroupConfig(group) {
1167
1149
  };
1168
1150
  }
1169
1151
  //#endregion
1152
+ //#region ../../internals/shared/src/schemaTraversal.ts
1153
+ /**
1154
+ * Maps each property of an object schema to its transformed output. Pairs every result with the
1155
+ * original property so the printer keeps full control over modifiers, getters, and key syntax.
1156
+ *
1157
+ * @example
1158
+ * ```ts
1159
+ * const entries = mapSchemaProperties(node, (schema) => this.transform(schema))
1160
+ * // entries: [{ name: 'id', property, output: 'z.number()' }, ...]
1161
+ * ```
1162
+ */
1163
+ function mapSchemaProperties(node, transform) {
1164
+ return node.properties.map((property) => ({
1165
+ name: property.name,
1166
+ property,
1167
+ output: transform(property.schema)
1168
+ }));
1169
+ }
1170
+ /**
1171
+ * Maps each item of an array or tuple schema to its transformed output, pairing every result with
1172
+ * the original item.
1173
+ */
1174
+ function mapSchemaItems(node, transform) {
1175
+ return (node.items ?? []).map((schema) => ({
1176
+ schema,
1177
+ output: transform(schema)
1178
+ }));
1179
+ }
1180
+ //#endregion
1170
1181
  //#region src/utils.ts
1171
1182
  /**
1172
1183
  * Tells whether a `const` (single-value enum) should render as a bare literal type (`'active'`)
@@ -1190,6 +1201,10 @@ function isInlineConstEnum(node, enumSchemaNames) {
1190
1201
  function isSchemaOptional(schema) {
1191
1202
  return Boolean("optional" in schema && schema.optional || "nullish" in schema && schema.nullish);
1192
1203
  }
1204
+ function formatExample(value) {
1205
+ if (value === null || typeof value !== "object") return String(value);
1206
+ return (JSON.stringify(value) ?? String(value)).replaceAll("*/", "*\\/");
1207
+ }
1193
1208
  function buildPropertyJSDocComments(schema, optional) {
1194
1209
  const meta = kubb_kit.ast.syncSchemaRef(schema);
1195
1210
  const isArray = meta?.primitive === "array";
@@ -1204,7 +1219,7 @@ function buildPropertyJSDocComments(schema, optional) {
1204
1219
  !isArray && meta && "max" in meta && meta.max !== void 0 ? `@maxLength ${meta.max}` : null,
1205
1220
  meta && "pattern" in meta && meta.pattern ? `@pattern ${meta.pattern}` : null,
1206
1221
  meta && "default" in meta && meta.default !== void 0 ? `@default ${"primitive" in meta && meta.primitive === "string" && typeof meta.default === "string" ? stringify(meta.default) : meta.default}` : null,
1207
- ...exampleValues.map((example) => `@example ${example}`),
1222
+ ...exampleValues.map((example) => `@example ${formatExample(example)}`),
1208
1223
  meta && "primitive" in meta && meta.primitive ? [`@type ${meta.primitive}`, optional ?? isSchemaOptional(schema) ? " | undefined" : null].filter(Boolean).join("") : null
1209
1224
  ].filter(Boolean);
1210
1225
  }
@@ -1227,7 +1242,7 @@ function buildParams({ params }) {
1227
1242
  });
1228
1243
  }
1229
1244
  function buildOptions(node, { resolver }) {
1230
- const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node, { paramsCasing: "original" });
1245
+ const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node);
1231
1246
  const hasBody = Boolean(node.requestBody?.content?.[0]?.schema);
1232
1247
  const hasRequiredPath = pathParams.some((param) => param.required);
1233
1248
  const hasRequiredQuery = queryParams.some((param) => param.required);
@@ -1437,7 +1452,8 @@ const printerTs = kubb_kit.ast.createPrinter((options) => {
1437
1452
  if (!node.name) return null;
1438
1453
  const refName = kubb_kit.ast.resolveRefName(node);
1439
1454
  if (!refName) return null;
1440
- return createTypeReferenceNode(node.ref && ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enum.type) && this.options.enum.typeSuffix && this.options.enumSchemaNames?.has(refName) ? this.options.resolver.enum.keyName({ name: refName }, this.options.enum.typeSuffix) : node.ref ? this.options.resolver.name(refName) : refName, void 0);
1455
+ const name = node.ref && ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enum.type) && this.options.enum.typeSuffix && this.options.enumSchemaNames?.has(refName) ? this.options.resolver.enum.keyName({ name: refName }, this.options.enum.typeSuffix) : node.ref ? this.options.resolver.name(refName) : refName;
1456
+ return createTypeReferenceNode(name, void 0);
1441
1457
  },
1442
1458
  enum(node) {
1443
1459
  const values = node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? [];
@@ -1445,7 +1461,8 @@ const printerTs = kubb_kit.ast.createPrinter((options) => {
1445
1461
  withParentheses: true,
1446
1462
  nodes: values.filter((v) => v !== null && v !== void 0).map((value) => constToTypeNode(value, typeof value)).filter(isNonNullable)
1447
1463
  }) ?? void 0;
1448
- return createTypeReferenceNode(ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enum.type) && this.options.enum.typeSuffix ? this.options.resolver.enum.keyName(node, this.options.enum.typeSuffix) : this.options.resolver.name(node.name), void 0);
1464
+ const resolvedName = ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enum.type) && this.options.enum.typeSuffix ? this.options.resolver.enum.keyName(node, this.options.enum.typeSuffix) : this.options.resolver.name(node.name);
1465
+ return createTypeReferenceNode(resolvedName, void 0);
1449
1466
  },
1450
1467
  union(node) {
1451
1468
  const members = node.members ?? [];
@@ -1476,7 +1493,7 @@ const printerTs = kubb_kit.ast.createPrinter((options) => {
1476
1493
  },
1477
1494
  array(node) {
1478
1495
  return createArrayDeclaration({
1479
- nodes: kubb_kit.ast.mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(isNonNullable),
1496
+ nodes: mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(isNonNullable),
1480
1497
  arrayType: this.options.arrayType
1481
1498
  }) ?? null;
1482
1499
  },
@@ -1486,7 +1503,7 @@ const printerTs = kubb_kit.ast.createPrinter((options) => {
1486
1503
  object(node) {
1487
1504
  const { transform, options } = this;
1488
1505
  const addsQuestionToken = OPTIONAL_ADDS_QUESTION_TOKEN.has(options.optionalType);
1489
- const propertyNodes = kubb_kit.ast.mapSchemaProperties(node, (schema) => transform(schema)).map(({ name, property, output }) => {
1506
+ const propertyNodes = mapSchemaProperties(node, (schema) => transform(schema)).map(({ name, property, output }) => {
1490
1507
  const baseType = output ?? keywordTypeNodes.unknown;
1491
1508
  const optional = !property.required || !!property.schema.optional || !!property.schema.nullish;
1492
1509
  const type = buildPropertyType(property.schema, baseType, options.optionalType, optional);
@@ -1642,7 +1659,6 @@ const typeGenerator = (0, kubb_kit.defineGenerator)({
1642
1659
  operation(node, ctx) {
1643
1660
  const { enum: enumOptions, optionalType, arrayType, syntaxType, group, output, printer } = ctx.options;
1644
1661
  const { config, resolver, root } = ctx;
1645
- const params = caseParams(node.parameters, "camelcase");
1646
1662
  const meta = { file: resolver.file({
1647
1663
  name: node.operationId,
1648
1664
  extname: ".ts",
@@ -1721,10 +1737,7 @@ const typeGenerator = (0, kubb_kit.defineGenerator)({
1721
1737
  name: baseName
1722
1738
  })] });
1723
1739
  }
1724
- const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters({
1725
- ...node,
1726
- parameters: params
1727
- }, { paramsCasing: "original" });
1740
+ const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node);
1728
1741
  const paramGroupTypes = [
1729
1742
  pathParams.length > 0 && renderSchemaType({
1730
1743
  schema: buildParams({ params: pathParams }),
@@ -1771,10 +1784,7 @@ const typeGenerator = (0, kubb_kit.defineGenerator)({
1771
1784
  });
1772
1785
  });
1773
1786
  const optionsType = renderSchemaType({
1774
- schema: buildOptions({
1775
- ...node,
1776
- parameters: params
1777
- }, { resolver }),
1787
+ schema: buildOptions(node, { resolver }),
1778
1788
  name: resolver.response.options(node)
1779
1789
  });
1780
1790
  const responsesType = renderSchemaType({
@@ -2224,17 +2234,14 @@ function resolveGroupType({ node, params, group, resolver }) {
2224
2234
  * name resolution and `extraParams` for plugin-specific trailing parameters such as an `options` object.
2225
2235
  */
2226
2236
  function createOperationParams(node, options) {
2227
- const { paramsType, pathParamsType, paramsCasing, resolver, pathParamsDefault, extraParams = [], paramNames, typeWrapper } = options;
2237
+ const { paramsType, pathParamsType, resolver, pathParamsDefault, extraParams = [], paramNames, typeWrapper } = options;
2228
2238
  const dataName = paramNames?.data ?? "data";
2229
2239
  const paramsName = paramNames?.params ?? "params";
2230
2240
  const headersName = paramNames?.headers ?? "headers";
2231
2241
  const pathName = paramNames?.path ?? "pathParams";
2232
2242
  const wrapType = (type) => typeWrapper ? typeWrapper(type) : type;
2233
2243
  const wrapTypeExpression = (type) => typeof type === "string" ? wrapType(type) : type;
2234
- const casedParams = caseParams(node.parameters, paramsCasing);
2235
- const pathParams = casedParams.filter((p) => p.in === "path");
2236
- const queryParams = casedParams.filter((p) => p.in === "query");
2237
- const headerParams = casedParams.filter((p) => p.in === "header");
2244
+ const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node);
2238
2245
  const toProperty = (param) => ({
2239
2246
  name: param.name,
2240
2247
  type: wrapTypeExpression(resolveParamType({