@kubb/plugin-ts 5.0.0-beta.99 → 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.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { n as __name, t as __exportAll } from "./rolldown-runtime-CNktS9qV.js";
2
2
  import { parserTs } from "@kubb/parser-ts";
3
3
  import { File, jsxRenderer } from "kubb/jsx";
4
- import { Resolver, ast, createResolver, defineGenerator, definePlugin } from "kubb/kit";
4
+ import { Resolver, ast, createResolver, defineGenerator, definePlugin, isStringType, syncSchemaRef } from "kubb/kit";
5
5
  import ts from "typescript";
6
6
  import { Fragment, jsx, jsxs } from "kubb/jsx/jsx-runtime";
7
7
  //#region ../../internals/utils/src/casing.ts
@@ -817,7 +817,7 @@ function buildTupleNode(node, print) {
817
817
  */
818
818
  function buildPropertyType(schema, baseType, optionalType, optional) {
819
819
  const addsUndefined = OPTIONAL_ADDS_UNDEFINED.has(optionalType);
820
- const meta = ast.syncSchemaRef(schema);
820
+ const meta = syncSchemaRef(schema);
821
821
  let type = baseType;
822
822
  if (meta.nullable) type = createUnionDeclaration({ nodes: [type, keywordTypeNodes.null] });
823
823
  if ((optional || meta.nullish || meta.optional) && addsUndefined) type = createUnionDeclaration({ nodes: [type, keywordTypeNodes.undefined] });
@@ -978,16 +978,81 @@ function resolveContentTypeVariants(entries, baseName) {
978
978
  };
979
979
  });
980
980
  }
981
+ const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
982
+ /**
983
+ * Groups an operation's parameters by location (`path`/`query`/`header`/`cookie`), deduping each
984
+ * group by name. Every plugin generator visiting the same `OperationNode` shares one AST instance
985
+ * (see `KubbDriver`), so the result is cached per node to avoid re-filtering and re-deduping the
986
+ * same parameters once per plugin.
987
+ */
981
988
  function getOperationParameters(node) {
982
- return {
983
- path: dedupeParams(node.parameters.filter((param) => param.in === "path").map((param) => isValidVarName(param.name) ? param : {
984
- ...param,
985
- name: camelCase(param.name)
986
- })),
989
+ const cached = operationParameterGroupsByNode.get(node);
990
+ if (cached) return cached;
991
+ const groups = {
992
+ path: dedupeParams(node.parameters.filter((param) => param.in === "path")),
987
993
  query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
988
994
  header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
989
995
  cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
990
996
  };
997
+ operationParameterGroupsByNode.set(node, groups);
998
+ return groups;
999
+ }
1000
+ /**
1001
+ * Builds the combined `{ body, path, query, headers }` options object schema for an operation,
1002
+ * referencing the already-resolved body and grouped param names. Shared by `@kubb/plugin-ts`'s
1003
+ * `Options` type and `@kubb/plugin-zod`'s inferred options schema, so both printers emit the same
1004
+ * shape from the same inputs. `primitive: 'object'` is a no-op for the TS printer and tells the Zod
1005
+ * printer to emit `z.object(…)` rather than a record.
1006
+ */
1007
+ function buildOptionsSchema(node, resolver) {
1008
+ const { path, query, header } = getOperationParameters(node);
1009
+ const hasBody = Boolean(node.requestBody?.content?.[0]?.schema);
1010
+ const createNever = () => ast.factory.createSchema({
1011
+ type: "never",
1012
+ primitive: void 0,
1013
+ optional: true
1014
+ });
1015
+ const groups = [
1016
+ {
1017
+ name: "path",
1018
+ params: path,
1019
+ resolve: resolver.param.path
1020
+ },
1021
+ {
1022
+ name: "query",
1023
+ params: query,
1024
+ resolve: resolver.param.query
1025
+ },
1026
+ {
1027
+ name: "headers",
1028
+ params: header,
1029
+ resolve: resolver.param.headers
1030
+ }
1031
+ ];
1032
+ return ast.factory.createSchema({
1033
+ type: "object",
1034
+ primitive: "object",
1035
+ deprecated: node.deprecated,
1036
+ properties: [ast.factory.createProperty({
1037
+ name: "body",
1038
+ required: hasBody,
1039
+ schema: hasBody ? ast.factory.createSchema({
1040
+ type: "ref",
1041
+ name: resolver.response.body(node)
1042
+ }) : createNever()
1043
+ }), ...groups.map(({ name, params, resolve }) => {
1044
+ const required = params.some((param) => param.required);
1045
+ return ast.factory.createProperty({
1046
+ name,
1047
+ required,
1048
+ schema: params.length > 0 ? ast.factory.createSchema({
1049
+ type: "ref",
1050
+ name: resolve.call(resolver.param, node, params[0]),
1051
+ optional: !required
1052
+ }) : createNever()
1053
+ });
1054
+ })]
1055
+ });
991
1056
  }
992
1057
  //#endregion
993
1058
  //#region ../../internals/shared/src/resolver.ts
@@ -1072,7 +1137,7 @@ function createCasedFile(caseLast) {
1072
1137
  * `resolver.imports` would resolve file paths that are then discarded.
1073
1138
  */
1074
1139
  function collectRefNames(schema) {
1075
- return ast.collect(schema, { schema: (node) => {
1140
+ return ast.collectSync(schema, { schema: (node) => {
1076
1141
  const refNode = ast.narrowSchema(node, "ref");
1077
1142
  if (!refNode?.ref) return null;
1078
1143
  return ast.resolveRefName(refNode);
@@ -1167,12 +1232,12 @@ function formatExample(value) {
1167
1232
  return (JSON.stringify(value) ?? String(value)).replaceAll("*/", "*\\/");
1168
1233
  }
1169
1234
  function buildPropertyJSDocComments(schema, optional) {
1170
- const meta = ast.syncSchemaRef(schema);
1235
+ const meta = syncSchemaRef(schema);
1171
1236
  const isArray = meta?.primitive === "array";
1172
1237
  const hasDescription = meta && "description" in meta && meta.description;
1173
1238
  const formatComment = meta && "format" in meta && meta.format ? hasDescription ? [" ", `Format: \`${meta.format}\``] : ["@description", `Format: \`${meta.format}\``] : [];
1174
1239
  const exampleValues = meta?.examples ?? [];
1175
- return [
1240
+ const comments = [
1176
1241
  hasDescription ? `@description ${jsStringEscape(meta.description)}` : null,
1177
1242
  ...formatComment,
1178
1243
  meta && "deprecated" in meta && meta.deprecated ? "@deprecated" : null,
@@ -1180,9 +1245,10 @@ function buildPropertyJSDocComments(schema, optional) {
1180
1245
  !isArray && meta && "max" in meta && meta.max !== void 0 ? `@maxLength ${meta.max}` : null,
1181
1246
  meta && "pattern" in meta && meta.pattern ? `@pattern ${meta.pattern}` : null,
1182
1247
  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,
1183
- ...exampleValues.map((example) => `@example ${formatExample(example)}`),
1184
- meta && "primitive" in meta && meta.primitive ? [`@type ${meta.primitive}`, optional ?? isSchemaOptional(schema) ? " | undefined" : null].filter(Boolean).join("") : null
1248
+ ...exampleValues.map((example) => `@example ${formatExample(example)}`)
1185
1249
  ].filter(Boolean);
1250
+ const typeTag = comments.length && meta && "primitive" in meta && meta.primitive ? [`@type ${meta.primitive}`, optional ?? isSchemaOptional(schema) ? " | undefined" : null].filter(Boolean).join("") : null;
1251
+ return [...comments, typeTag].filter(Boolean);
1186
1252
  }
1187
1253
  /**
1188
1254
  * Builds the object schema for a group of parameters sharing one `in` location (path, query, or
@@ -1202,70 +1268,6 @@ function buildParams({ params }) {
1202
1268
  }))
1203
1269
  });
1204
1270
  }
1205
- function buildOptions(node, { resolver }) {
1206
- const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node);
1207
- const hasBody = Boolean(node.requestBody?.content?.[0]?.schema);
1208
- const hasRequiredPath = pathParams.some((param) => param.required);
1209
- const hasRequiredQuery = queryParams.some((param) => param.required);
1210
- const hasRequiredHeader = headerParams.some((param) => param.required);
1211
- return ast.factory.createSchema({
1212
- type: "object",
1213
- deprecated: node.deprecated,
1214
- properties: [
1215
- ast.factory.createProperty({
1216
- name: "body",
1217
- required: hasBody,
1218
- schema: hasBody ? ast.factory.createSchema({
1219
- type: "ref",
1220
- name: resolver.response.body(node)
1221
- }) : ast.factory.createSchema({
1222
- type: "never",
1223
- primitive: void 0,
1224
- optional: true
1225
- })
1226
- }),
1227
- ast.factory.createProperty({
1228
- name: "path",
1229
- required: hasRequiredPath,
1230
- schema: pathParams.length > 0 ? ast.factory.createSchema({
1231
- type: "ref",
1232
- name: resolver.param.path(node, pathParams[0]),
1233
- optional: !hasRequiredPath
1234
- }) : ast.factory.createSchema({
1235
- type: "never",
1236
- primitive: void 0,
1237
- optional: true
1238
- })
1239
- }),
1240
- ast.factory.createProperty({
1241
- name: "query",
1242
- required: hasRequiredQuery,
1243
- schema: queryParams.length > 0 ? ast.factory.createSchema({
1244
- type: "ref",
1245
- name: resolver.param.query(node, queryParams[0]),
1246
- optional: !hasRequiredQuery
1247
- }) : ast.factory.createSchema({
1248
- type: "never",
1249
- primitive: void 0,
1250
- optional: true
1251
- })
1252
- }),
1253
- ast.factory.createProperty({
1254
- name: "headers",
1255
- required: hasRequiredHeader,
1256
- schema: headerParams.length > 0 ? ast.factory.createSchema({
1257
- type: "ref",
1258
- name: resolver.param.headers(node, headerParams[0]),
1259
- optional: !hasRequiredHeader
1260
- }) : ast.factory.createSchema({
1261
- type: "never",
1262
- primitive: void 0,
1263
- optional: true
1264
- })
1265
- })
1266
- ]
1267
- });
1268
- }
1269
1271
  /**
1270
1272
  * The schema a status occupies in the `<Name>Responses` record. A status that documents several
1271
1273
  * content types becomes a `{ contentType; data }` union so the runtime can surface the negotiated type
@@ -1325,7 +1327,7 @@ function buildResponseUnion(node, { resolver }) {
1325
1327
  //#endregion
1326
1328
  //#region src/components/Type.tsx
1327
1329
  function Type({ name, node, printer, enum: enumOptions, resolver }) {
1328
- const enumSchemaNodes = ast.collect(node, { schema(n) {
1330
+ const enumSchemaNodes = ast.collectSync(node, { schema(n) {
1329
1331
  const enumNode = ast.narrowSchema(n, ast.schemaTypes.enum);
1330
1332
  if (enumNode?.name && !isInlineConstEnum(enumNode, printer.options.enumSchemaNames)) return enumNode;
1331
1333
  } });
@@ -1430,11 +1432,11 @@ const printerTs = ast.createPrinter((options) => {
1430
1432
  const hasStringLiteral = members.some((m) => {
1431
1433
  return ast.narrowSchema(m, ast.schemaTypes.enum)?.primitive === "string";
1432
1434
  });
1433
- const hasPlainString = members.some((m) => ast.isStringType(m));
1435
+ const hasPlainString = members.some((m) => isStringType(m));
1434
1436
  if (hasStringLiteral && hasPlainString) return createUnionDeclaration({
1435
1437
  withParentheses: true,
1436
1438
  nodes: members.map((m) => {
1437
- if (ast.isStringType(m)) return createIntersectionDeclaration({
1439
+ if (isStringType(m)) return createIntersectionDeclaration({
1438
1440
  nodes: [keywordTypeNodes.string, createTypeLiteralNode([])],
1439
1441
  withParentheses: true
1440
1442
  });
@@ -1468,7 +1470,7 @@ const printerTs = ast.createPrinter((options) => {
1468
1470
  const baseType = output ?? keywordTypeNodes.unknown;
1469
1471
  const optional = !property.required || !!property.schema.optional || !!property.schema.nullish;
1470
1472
  const type = buildPropertyType(property.schema, baseType, options.optionalType, optional);
1471
- const propMeta = ast.syncSchemaRef(property.schema);
1473
+ const propMeta = syncSchemaRef(property.schema);
1472
1474
  return appendJSDocToNode({
1473
1475
  node: createPropertySignature({
1474
1476
  questionToken: optional ? addsQuestionToken : false,
@@ -1489,7 +1491,7 @@ const printerTs = ast.createPrinter((options) => {
1489
1491
  const { name, syntaxType = "type", description, keysToOmit } = this.options;
1490
1492
  const transformed = this.transform(node);
1491
1493
  if (!transformed) return null;
1492
- const meta = ast.syncSchemaRef(node);
1494
+ const meta = syncSchemaRef(node);
1493
1495
  if (!name) {
1494
1496
  const withNullable = meta.nullable ? createUnionDeclaration({ nodes: [transformed, keywordTypeNodes.null] }) : transformed;
1495
1497
  const result = (meta.nullish || meta.optional) && addsUndefined ? createUnionDeclaration({ nodes: [withNullable, keywordTypeNodes.undefined] }) : withNullable;
@@ -1745,7 +1747,7 @@ const typeGenerator = defineGenerator({
1745
1747
  });
1746
1748
  });
1747
1749
  const optionsType = renderSchemaType({
1748
- schema: buildOptions(node, { resolver }),
1750
+ schema: buildOptionsSchema(node, resolver),
1749
1751
  name: resolver.response.options(node)
1750
1752
  });
1751
1753
  const responsesType = renderSchemaType({