@kubb/plugin-ts 5.0.0-beta.98 → 5.0.0

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
@@ -32,7 +32,7 @@ var __copyProps = (to, from, except, desc) => {
32
32
  }
33
33
  return to;
34
34
  };
35
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
35
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
36
36
  value: mod,
37
37
  enumerable: true
38
38
  }) : target, mod));
@@ -856,7 +856,7 @@ function buildTupleNode(node, print) {
856
856
  */
857
857
  function buildPropertyType(schema, baseType, optionalType, optional) {
858
858
  const addsUndefined = OPTIONAL_ADDS_UNDEFINED.has(optionalType);
859
- const meta = kubb_kit.ast.syncSchemaRef(schema);
859
+ const meta = (0, kubb_kit.syncSchemaRef)(schema);
860
860
  let type = baseType;
861
861
  if (meta.nullable) type = createUnionDeclaration({ nodes: [type, keywordTypeNodes.null] });
862
862
  if ((optional || meta.nullish || meta.optional) && addsUndefined) type = createUnionDeclaration({ nodes: [type, keywordTypeNodes.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,14 +1017,81 @@ 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");
1042
- 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"))
1020
+ const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
1021
+ /**
1022
+ * Groups an operation's parameters by location (`path`/`query`/`header`/`cookie`), deduping each
1023
+ * group by name. Every plugin generator visiting the same `OperationNode` shares one AST instance
1024
+ * (see `KubbDriver`), so the result is cached per node to avoid re-filtering and re-deduping the
1025
+ * same parameters once per plugin.
1026
+ */
1027
+ function getOperationParameters(node) {
1028
+ const cached = operationParameterGroupsByNode.get(node);
1029
+ if (cached) return cached;
1030
+ const groups = {
1031
+ path: dedupeParams(node.parameters.filter((param) => param.in === "path")),
1032
+ query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
1033
+ header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
1034
+ cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
1047
1035
  };
1036
+ operationParameterGroupsByNode.set(node, groups);
1037
+ return groups;
1038
+ }
1039
+ /**
1040
+ * Builds the combined `{ body, path, query, headers }` options object schema for an operation,
1041
+ * referencing the already-resolved body and grouped param names. Shared by `@kubb/plugin-ts`'s
1042
+ * `Options` type and `@kubb/plugin-zod`'s inferred options schema, so both printers emit the same
1043
+ * shape from the same inputs. `primitive: 'object'` is a no-op for the TS printer and tells the Zod
1044
+ * printer to emit `z.object(…)` rather than a record.
1045
+ */
1046
+ function buildOptionsSchema(node, resolver) {
1047
+ const { path, query, header } = getOperationParameters(node);
1048
+ const hasBody = Boolean(node.requestBody?.content?.[0]?.schema);
1049
+ const createNever = () => kubb_kit.ast.factory.createSchema({
1050
+ type: "never",
1051
+ primitive: void 0,
1052
+ optional: true
1053
+ });
1054
+ const groups = [
1055
+ {
1056
+ name: "path",
1057
+ params: path,
1058
+ resolve: resolver.param.path
1059
+ },
1060
+ {
1061
+ name: "query",
1062
+ params: query,
1063
+ resolve: resolver.param.query
1064
+ },
1065
+ {
1066
+ name: "headers",
1067
+ params: header,
1068
+ resolve: resolver.param.headers
1069
+ }
1070
+ ];
1071
+ return kubb_kit.ast.factory.createSchema({
1072
+ type: "object",
1073
+ primitive: "object",
1074
+ deprecated: node.deprecated,
1075
+ properties: [kubb_kit.ast.factory.createProperty({
1076
+ name: "body",
1077
+ required: hasBody,
1078
+ schema: hasBody ? kubb_kit.ast.factory.createSchema({
1079
+ type: "ref",
1080
+ name: resolver.response.body(node)
1081
+ }) : createNever()
1082
+ }), ...groups.map(({ name, params, resolve }) => {
1083
+ const required = params.some((param) => param.required);
1084
+ return kubb_kit.ast.factory.createProperty({
1085
+ name,
1086
+ required,
1087
+ schema: params.length > 0 ? kubb_kit.ast.factory.createSchema({
1088
+ type: "ref",
1089
+ name: resolve.call(resolver.param, node, params[0]),
1090
+ optional: !required
1091
+ }) : createNever()
1092
+ });
1093
+ })]
1094
+ });
1048
1095
  }
1049
1096
  //#endregion
1050
1097
  //#region ../../internals/shared/src/resolver.ts
@@ -1129,7 +1176,7 @@ function createCasedFile(caseLast) {
1129
1176
  * `resolver.imports` would resolve file paths that are then discarded.
1130
1177
  */
1131
1178
  function collectRefNames(schema) {
1132
- return kubb_kit.ast.collect(schema, { schema: (node) => {
1179
+ return kubb_kit.ast.collectSync(schema, { schema: (node) => {
1133
1180
  const refNode = kubb_kit.ast.narrowSchema(node, "ref");
1134
1181
  if (!refNode?.ref) return null;
1135
1182
  return kubb_kit.ast.resolveRefName(refNode);
@@ -1167,6 +1214,35 @@ function createGroupConfig(group) {
1167
1214
  };
1168
1215
  }
1169
1216
  //#endregion
1217
+ //#region ../../internals/shared/src/schemaTraversal.ts
1218
+ /**
1219
+ * Maps each property of an object schema to its transformed output. Pairs every result with the
1220
+ * original property so the printer keeps full control over modifiers, getters, and key syntax.
1221
+ *
1222
+ * @example
1223
+ * ```ts
1224
+ * const entries = mapSchemaProperties(node, (schema) => this.transform(schema))
1225
+ * // entries: [{ name: 'id', property, output: 'z.number()' }, ...]
1226
+ * ```
1227
+ */
1228
+ function mapSchemaProperties(node, transform) {
1229
+ return node.properties.map((property) => ({
1230
+ name: property.name,
1231
+ property,
1232
+ output: transform(property.schema)
1233
+ }));
1234
+ }
1235
+ /**
1236
+ * Maps each item of an array or tuple schema to its transformed output, pairing every result with
1237
+ * the original item.
1238
+ */
1239
+ function mapSchemaItems(node, transform) {
1240
+ return (node.items ?? []).map((schema) => ({
1241
+ schema,
1242
+ output: transform(schema)
1243
+ }));
1244
+ }
1245
+ //#endregion
1170
1246
  //#region src/utils.ts
1171
1247
  /**
1172
1248
  * Tells whether a `const` (single-value enum) should render as a bare literal type (`'active'`)
@@ -1195,12 +1271,12 @@ function formatExample(value) {
1195
1271
  return (JSON.stringify(value) ?? String(value)).replaceAll("*/", "*\\/");
1196
1272
  }
1197
1273
  function buildPropertyJSDocComments(schema, optional) {
1198
- const meta = kubb_kit.ast.syncSchemaRef(schema);
1274
+ const meta = (0, kubb_kit.syncSchemaRef)(schema);
1199
1275
  const isArray = meta?.primitive === "array";
1200
1276
  const hasDescription = meta && "description" in meta && meta.description;
1201
1277
  const formatComment = meta && "format" in meta && meta.format ? hasDescription ? [" ", `Format: \`${meta.format}\``] : ["@description", `Format: \`${meta.format}\``] : [];
1202
1278
  const exampleValues = meta?.examples ?? [];
1203
- return [
1279
+ const comments = [
1204
1280
  hasDescription ? `@description ${jsStringEscape(meta.description)}` : null,
1205
1281
  ...formatComment,
1206
1282
  meta && "deprecated" in meta && meta.deprecated ? "@deprecated" : null,
@@ -1208,9 +1284,10 @@ function buildPropertyJSDocComments(schema, optional) {
1208
1284
  !isArray && meta && "max" in meta && meta.max !== void 0 ? `@maxLength ${meta.max}` : null,
1209
1285
  meta && "pattern" in meta && meta.pattern ? `@pattern ${meta.pattern}` : null,
1210
1286
  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,
1211
- ...exampleValues.map((example) => `@example ${formatExample(example)}`),
1212
- meta && "primitive" in meta && meta.primitive ? [`@type ${meta.primitive}`, optional ?? isSchemaOptional(schema) ? " | undefined" : null].filter(Boolean).join("") : null
1287
+ ...exampleValues.map((example) => `@example ${formatExample(example)}`)
1213
1288
  ].filter(Boolean);
1289
+ const typeTag = comments.length && meta && "primitive" in meta && meta.primitive ? [`@type ${meta.primitive}`, optional ?? isSchemaOptional(schema) ? " | undefined" : null].filter(Boolean).join("") : null;
1290
+ return [...comments, typeTag].filter(Boolean);
1214
1291
  }
1215
1292
  /**
1216
1293
  * Builds the object schema for a group of parameters sharing one `in` location (path, query, or
@@ -1230,70 +1307,6 @@ function buildParams({ params }) {
1230
1307
  }))
1231
1308
  });
1232
1309
  }
1233
- function buildOptions(node, { resolver }) {
1234
- const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node, { paramsCasing: "original" });
1235
- const hasBody = Boolean(node.requestBody?.content?.[0]?.schema);
1236
- const hasRequiredPath = pathParams.some((param) => param.required);
1237
- const hasRequiredQuery = queryParams.some((param) => param.required);
1238
- const hasRequiredHeader = headerParams.some((param) => param.required);
1239
- return kubb_kit.ast.factory.createSchema({
1240
- type: "object",
1241
- deprecated: node.deprecated,
1242
- properties: [
1243
- kubb_kit.ast.factory.createProperty({
1244
- name: "body",
1245
- required: hasBody,
1246
- schema: hasBody ? kubb_kit.ast.factory.createSchema({
1247
- type: "ref",
1248
- name: resolver.response.body(node)
1249
- }) : kubb_kit.ast.factory.createSchema({
1250
- type: "never",
1251
- primitive: void 0,
1252
- optional: true
1253
- })
1254
- }),
1255
- kubb_kit.ast.factory.createProperty({
1256
- name: "path",
1257
- required: hasRequiredPath,
1258
- schema: pathParams.length > 0 ? kubb_kit.ast.factory.createSchema({
1259
- type: "ref",
1260
- name: resolver.param.path(node, pathParams[0]),
1261
- optional: !hasRequiredPath
1262
- }) : kubb_kit.ast.factory.createSchema({
1263
- type: "never",
1264
- primitive: void 0,
1265
- optional: true
1266
- })
1267
- }),
1268
- kubb_kit.ast.factory.createProperty({
1269
- name: "query",
1270
- required: hasRequiredQuery,
1271
- schema: queryParams.length > 0 ? kubb_kit.ast.factory.createSchema({
1272
- type: "ref",
1273
- name: resolver.param.query(node, queryParams[0]),
1274
- optional: !hasRequiredQuery
1275
- }) : kubb_kit.ast.factory.createSchema({
1276
- type: "never",
1277
- primitive: void 0,
1278
- optional: true
1279
- })
1280
- }),
1281
- kubb_kit.ast.factory.createProperty({
1282
- name: "headers",
1283
- required: hasRequiredHeader,
1284
- schema: headerParams.length > 0 ? kubb_kit.ast.factory.createSchema({
1285
- type: "ref",
1286
- name: resolver.param.headers(node, headerParams[0]),
1287
- optional: !hasRequiredHeader
1288
- }) : kubb_kit.ast.factory.createSchema({
1289
- type: "never",
1290
- primitive: void 0,
1291
- optional: true
1292
- })
1293
- })
1294
- ]
1295
- });
1296
- }
1297
1310
  /**
1298
1311
  * The schema a status occupies in the `<Name>Responses` record. A status that documents several
1299
1312
  * content types becomes a `{ contentType; data }` union so the runtime can surface the negotiated type
@@ -1353,7 +1366,7 @@ function buildResponseUnion(node, { resolver }) {
1353
1366
  //#endregion
1354
1367
  //#region src/components/Type.tsx
1355
1368
  function Type({ name, node, printer, enum: enumOptions, resolver }) {
1356
- const enumSchemaNodes = kubb_kit.ast.collect(node, { schema(n) {
1369
+ const enumSchemaNodes = kubb_kit.ast.collectSync(node, { schema(n) {
1357
1370
  const enumNode = kubb_kit.ast.narrowSchema(n, kubb_kit.ast.schemaTypes.enum);
1358
1371
  if (enumNode?.name && !isInlineConstEnum(enumNode, printer.options.enumSchemaNames)) return enumNode;
1359
1372
  } });
@@ -1458,11 +1471,11 @@ const printerTs = kubb_kit.ast.createPrinter((options) => {
1458
1471
  const hasStringLiteral = members.some((m) => {
1459
1472
  return kubb_kit.ast.narrowSchema(m, kubb_kit.ast.schemaTypes.enum)?.primitive === "string";
1460
1473
  });
1461
- const hasPlainString = members.some((m) => kubb_kit.ast.isStringType(m));
1474
+ const hasPlainString = members.some((m) => (0, kubb_kit.isStringType)(m));
1462
1475
  if (hasStringLiteral && hasPlainString) return createUnionDeclaration({
1463
1476
  withParentheses: true,
1464
1477
  nodes: members.map((m) => {
1465
- if (kubb_kit.ast.isStringType(m)) return createIntersectionDeclaration({
1478
+ if ((0, kubb_kit.isStringType)(m)) return createIntersectionDeclaration({
1466
1479
  nodes: [keywordTypeNodes.string, createTypeLiteralNode([])],
1467
1480
  withParentheses: true
1468
1481
  });
@@ -1482,7 +1495,7 @@ const printerTs = kubb_kit.ast.createPrinter((options) => {
1482
1495
  },
1483
1496
  array(node) {
1484
1497
  return createArrayDeclaration({
1485
- nodes: kubb_kit.ast.mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(isNonNullable),
1498
+ nodes: mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(isNonNullable),
1486
1499
  arrayType: this.options.arrayType
1487
1500
  }) ?? null;
1488
1501
  },
@@ -1492,11 +1505,11 @@ const printerTs = kubb_kit.ast.createPrinter((options) => {
1492
1505
  object(node) {
1493
1506
  const { transform, options } = this;
1494
1507
  const addsQuestionToken = OPTIONAL_ADDS_QUESTION_TOKEN.has(options.optionalType);
1495
- const propertyNodes = kubb_kit.ast.mapSchemaProperties(node, (schema) => transform(schema)).map(({ name, property, output }) => {
1508
+ const propertyNodes = mapSchemaProperties(node, (schema) => transform(schema)).map(({ name, property, output }) => {
1496
1509
  const baseType = output ?? keywordTypeNodes.unknown;
1497
1510
  const optional = !property.required || !!property.schema.optional || !!property.schema.nullish;
1498
1511
  const type = buildPropertyType(property.schema, baseType, options.optionalType, optional);
1499
- const propMeta = kubb_kit.ast.syncSchemaRef(property.schema);
1512
+ const propMeta = (0, kubb_kit.syncSchemaRef)(property.schema);
1500
1513
  return appendJSDocToNode({
1501
1514
  node: createPropertySignature({
1502
1515
  questionToken: optional ? addsQuestionToken : false,
@@ -1517,7 +1530,7 @@ const printerTs = kubb_kit.ast.createPrinter((options) => {
1517
1530
  const { name, syntaxType = "type", description, keysToOmit } = this.options;
1518
1531
  const transformed = this.transform(node);
1519
1532
  if (!transformed) return null;
1520
- const meta = kubb_kit.ast.syncSchemaRef(node);
1533
+ const meta = (0, kubb_kit.syncSchemaRef)(node);
1521
1534
  if (!name) {
1522
1535
  const withNullable = meta.nullable ? createUnionDeclaration({ nodes: [transformed, keywordTypeNodes.null] }) : transformed;
1523
1536
  const result = (meta.nullish || meta.optional) && addsUndefined ? createUnionDeclaration({ nodes: [withNullable, keywordTypeNodes.undefined] }) : withNullable;
@@ -1648,7 +1661,6 @@ const typeGenerator = (0, kubb_kit.defineGenerator)({
1648
1661
  operation(node, ctx) {
1649
1662
  const { enum: enumOptions, optionalType, arrayType, syntaxType, group, output, printer } = ctx.options;
1650
1663
  const { config, resolver, root } = ctx;
1651
- const params = caseParams(node.parameters, "camelcase");
1652
1664
  const meta = { file: resolver.file({
1653
1665
  name: node.operationId,
1654
1666
  extname: ".ts",
@@ -1727,10 +1739,7 @@ const typeGenerator = (0, kubb_kit.defineGenerator)({
1727
1739
  name: baseName
1728
1740
  })] });
1729
1741
  }
1730
- const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters({
1731
- ...node,
1732
- parameters: params
1733
- }, { paramsCasing: "original" });
1742
+ const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node);
1734
1743
  const paramGroupTypes = [
1735
1744
  pathParams.length > 0 && renderSchemaType({
1736
1745
  schema: buildParams({ params: pathParams }),
@@ -1777,10 +1786,7 @@ const typeGenerator = (0, kubb_kit.defineGenerator)({
1777
1786
  });
1778
1787
  });
1779
1788
  const optionsType = renderSchemaType({
1780
- schema: buildOptions({
1781
- ...node,
1782
- parameters: params
1783
- }, { resolver }),
1789
+ schema: buildOptionsSchema(node, resolver),
1784
1790
  name: resolver.response.options(node)
1785
1791
  });
1786
1792
  const responsesType = renderSchemaType({
@@ -2230,17 +2236,14 @@ function resolveGroupType({ node, params, group, resolver }) {
2230
2236
  * name resolution and `extraParams` for plugin-specific trailing parameters such as an `options` object.
2231
2237
  */
2232
2238
  function createOperationParams(node, options) {
2233
- const { paramsType, pathParamsType, paramsCasing, resolver, pathParamsDefault, extraParams = [], paramNames, typeWrapper } = options;
2239
+ const { paramsType, pathParamsType, resolver, pathParamsDefault, extraParams = [], paramNames, typeWrapper } = options;
2234
2240
  const dataName = paramNames?.data ?? "data";
2235
2241
  const paramsName = paramNames?.params ?? "params";
2236
2242
  const headersName = paramNames?.headers ?? "headers";
2237
2243
  const pathName = paramNames?.path ?? "pathParams";
2238
2244
  const wrapType = (type) => typeWrapper ? typeWrapper(type) : type;
2239
2245
  const wrapTypeExpression = (type) => typeof type === "string" ? wrapType(type) : type;
2240
- const casedParams = caseParams(node.parameters, paramsCasing);
2241
- const pathParams = casedParams.filter((p) => p.in === "path");
2242
- const queryParams = casedParams.filter((p) => p.in === "query");
2243
- const headerParams = casedParams.filter((p) => p.in === "header");
2246
+ const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node);
2244
2247
  const toProperty = (param) => ({
2245
2248
  name: param.name,
2246
2249
  type: wrapTypeExpression(resolveParamType({