@kubb/plugin-client 6.1.0-canary.20261109T175349 → 6.1.0-canary.20261109T191216

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.
@@ -203,7 +203,7 @@ type RequestConfig<TBody = unknown, TRequest = NativeRequest, TResponse = Native
203
203
  transport?: Transport<TRequest, TResponse>;
204
204
  serializer?: Serializers;
205
205
  codecs?: Record<string, Codec>;
206
- /** `errorStatus` lists the statuses `error` covers, such as `[404, '5XX']`. Without it, `error` checks every non-2xx body. */
206
+ /** `error` takes one validator for every non-2xx body, or a map by status such as `{ 404: schema, '5XX': schema, default: schema }`. */
207
207
  validator?: {
208
208
  request?: Validator;
209
209
  path?: Validator;
@@ -211,8 +211,7 @@ type RequestConfig<TBody = unknown, TRequest = NativeRequest, TResponse = Native
211
211
  headers?: Validator;
212
212
  cookies?: Validator;
213
213
  response?: Validator;
214
- error?: Validator;
215
- errorStatus?: Array<number | string>;
214
+ error?: Validator | Record<string, Validator>;
216
215
  };
217
216
  onValidationError?: ValidationErrorHandler;
218
217
  validate?: ValidateOption;
package/dist/index.cjs CHANGED
@@ -547,7 +547,7 @@ var compatibility = {
547
547
  "node": ">=22.22"
548
548
  };
549
549
  // package.json
550
- var version = "6.1.0-canary.20261109T175349";
550
+ var version = "6.1.0-canary.20261109T191216";
551
551
  // ../../internals/client/src/builders/validatorOptions.ts
552
552
  /**
553
553
  * Returns `true` when any direction of the validator uses zod (used for dependency checks).
@@ -583,22 +583,19 @@ function resolveResponseValidator(validator) {
583
583
  return validator.response ?? null;
584
584
  }
585
585
  /**
586
- * Resolves the zod schema name a generated client validates an error body with on the non-throw path.
587
- * Uses the error-only `<operation>ErrorSchema` (the union of non-2xx statuses); returns `null` when the
588
- * operation documents no error responses with a schema.
586
+ * Builds the `validator.error` map of a call, one plugin-zod `<operation>StatusNNNSchema` per non-2xx response with a
587
+ * schema, such as `{ 404: getPetStatus404Schema, '5XX': getPetStatus5XXSchema }`. Returns `null` without one.
589
588
  */
590
589
  function buildZodErrorParse(node, zodResolver) {
591
- if (!getErrorStatusCodes(node).length) return null;
592
- return zodResolver.response.error?.(node) || null;
593
- }
594
- /** Prints the statuses `<operation>ErrorSchema` covers, such as `[404, '5XX']`, or `null` when a `default` response covers them all. */
595
- function buildErrorStatusLiteral(node) {
596
- const statusCodes = getErrorStatusCodes(node);
597
- if (!statusCodes.length || statusCodes.includes("default")) return null;
598
- return `[${statusCodes.map((statusCode) => /^\d+$/.test(statusCode) ? statusCode : stringify(statusCode)).join(", ")}]`;
599
- }
600
- function getErrorStatusCodes(node) {
601
- return node.responses.filter((res) => !isSuccessStatusCode(res.statusCode) && res.content?.some((entry) => entry.schema)).map((res) => String(res.statusCode));
590
+ const entries = node.responses.filter((res) => !isSuccessStatusCode(res.statusCode) && res.content?.some((entry) => entry.schema)).map((res) => ({
591
+ key: String(res.statusCode),
592
+ name: zodResolver.response.status(node, res.statusCode)
593
+ }));
594
+ if (!entries.length) return null;
595
+ return {
596
+ literal: `{ ${entries.map(({ key, name }) => `${/^\d+$/.test(key) ? key : stringify(key)}: ${name}`).join(", ")} }`,
597
+ names: entries.map(({ name }) => name)
598
+ };
602
599
  }
603
600
  // ../../internals/client/src/builders/generics.ts
604
601
  /**
@@ -793,6 +790,18 @@ function buildSecurityMetadata({ security }) {
793
790
  if (!security?.length) return null;
794
791
  return `[${security.map(serializeAuth).join(", ")}]`;
795
792
  }
793
+ // ../../internals/client/src/builders/server.ts
794
+ /**
795
+ * Returns the URL of the operation's own `servers`, else its path item's, picked with `server.index` and falling back to the
796
+ * first entry. Returns `undefined` without `servers`, for a relative URL such as `/v2`, which would drop the client's host, or
797
+ * for a URL with `{variable}` placeholders, which are not supported, so the client's `baseURL` applies.
798
+ */
799
+ function getOperationBaseURL({ document, method, path, server: { index = 0 } = {} }) {
800
+ const pathItem = document?.paths?.[path];
801
+ const servers = [(pathItem?.[method.toLowerCase()])?.servers, pathItem?.servers].find((list) => list?.length);
802
+ const url = (servers?.[index] ?? servers?.[0])?.url;
803
+ return url && /^[a-z][a-z\d+.-]*:\/\/[^{}]*$/i.test(url) ? url : void 0;
804
+ }
796
805
  // ../../internals/client/src/builders/validator.ts
797
806
  /**
798
807
  * Builds the validator-hook references for one operation. Request body and params validation run before the send.
@@ -803,8 +812,7 @@ function buildValidatorHooks({ node, validator, zodResolver }) {
803
812
  const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
804
813
  const parsesResponse = zodResolver && resolveResponseValidator(validator) === "zod";
805
814
  const response = parsesResponse ? zodResolver.response.response(node) || null : null;
806
- const error = parsesResponse ? buildZodErrorParse(node, zodResolver) : null;
807
- const errorStatus = error ? buildErrorStatusLiteral(node) : null;
815
+ const errorParse = parsesResponse ? buildZodErrorParse(node, zodResolver) : null;
808
816
  const request = zodResolver && resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.response.body(node) || null : null;
809
817
  const paramGroups = getOperationParameters(node);
810
818
  const resolveParamsName = (kind, params) => zodResolver && resolveParamsValidator(validator) === "zod" && params.length > 0 ? zodResolver.param[kind](node, params[0]) : null;
@@ -814,7 +822,7 @@ function buildValidatorHooks({ node, validator, zodResolver }) {
814
822
  const cookies = resolveParamsName("cookies", paramGroups.cookie);
815
823
  const importedZodNames = [
816
824
  response,
817
- error,
825
+ ...errorParse?.names ?? [],
818
826
  request,
819
827
  path,
820
828
  query,
@@ -828,17 +836,17 @@ function buildValidatorHooks({ node, validator, zodResolver }) {
828
836
  headers: headers ? `toHeadersStandardSchema(${headers}, ${headers}.shape)` : null,
829
837
  cookies,
830
838
  response,
831
- error,
832
- errorStatus,
839
+ error: errorParse?.literal ?? null,
833
840
  importedZodNames
834
841
  };
835
842
  }
836
843
  /**
837
844
  * Prints the `validator: { … }` entry of a call config from the hooks named in `keys`, or `null` when none is set.
845
+ * The entry spreads `config.validator` last, so a per-call hook overrides the generated one for its key only.
838
846
  */
839
847
  function buildValidatorLiteral(hooks, keys) {
840
848
  const entries = keys.flatMap((key) => hooks?.[key] ? [`${key}: ${hooks[key]}`] : []);
841
- return entries.length ? `validator: { ${entries.join(", ")} }` : null;
849
+ return entries.length ? `validator: { ${entries.join(", ")}, ...config.validator }` : null;
842
850
  }
843
851
  // ../../internals/client/src/builders/contentType.ts
844
852
  /**
@@ -891,13 +899,13 @@ function serializeParameter(parameter) {
891
899
  return parts.length > 0 ? `{ ${parts.join(", ")} }` : null;
892
900
  }
893
901
  /**
894
- * Serializes the `encoding` of the request body's default content type into a `{ property: { ... } }`
895
- * literal, or `null` when it carries none. Only an operation with a single request content type
902
+ * Serializes the `encoding` of the request body's default content type into `property: { ... }`
903
+ * entries, empty when it carries none. Only an operation with a single request content type
896
904
  * emits it, because the runtime applies `styles.body` to whichever type the caller selects.
897
905
  */
898
906
  function serializeBodyEncoding(node) {
899
907
  const content = node.requestBody?.content;
900
- const entries = Object.entries(content?.length === 1 ? content[0]?.encoding ?? {} : {}).map(([property, { contentType, style, explode, allowReserved }]) => {
908
+ return Object.entries(content?.length === 1 ? content[0]?.encoding ?? {} : {}).map(([property, { contentType, style, explode, allowReserved }]) => {
901
909
  const parts = [
902
910
  style && `style: ${JSON.stringify(style)}`,
903
911
  explode !== void 0 && `explode: ${explode}`,
@@ -906,7 +914,6 @@ function serializeBodyEncoding(node) {
906
914
  ].filter(Boolean);
907
915
  return `${isValidVarName(property) ? property : JSON.stringify(property)}: { ${parts.join(", ")} }`;
908
916
  });
909
- return entries.length > 0 ? `body: { ${entries.join(", ")} }` : null;
910
917
  }
911
918
  /**
912
919
  * Builds the per-operation `styles` literal from the operation's parameters, grouped by location.
@@ -916,26 +923,31 @@ function serializeBodyEncoding(node) {
916
923
  * is unchanged. A form request body adds a `body` group keyed by property name from its `encoding`.
917
924
  * Returns `null` when nothing carries metadata.
918
925
  *
926
+ * With `merge`, the literal spreads `config.styles` per parameter, so a per-call entry overrides the generated one for
927
+ * that parameter and leaves the others in place.
928
+ *
919
929
  * @example
920
930
  * ```ts
921
931
  * // a path param with { style: 'matrix', explode: true } and a query param with { explode: false }
922
932
  * buildStyles({ node }) // "{ path: { id: { style: 'matrix', explode: true } }, query: { tags: { explode: false } } }"
923
933
  * ```
924
934
  */
925
- function buildStyles({ node }) {
935
+ function buildStyles({ node, merge }) {
926
936
  if (!_kubb_kit.ast.isHttpOperationNode(node)) return null;
927
937
  const groups = {
928
938
  path: [],
929
939
  query: [],
930
940
  header: [],
931
- cookie: []
941
+ cookie: [],
942
+ body: serializeBodyEncoding(node)
932
943
  };
933
944
  for (const [location, parameters] of Object.entries(getOperationParameters(node))) for (const parameter of parameters) {
934
945
  const literal = serializeParameter(parameter);
935
946
  if (literal) groups[location].push(`${toKey(parameter.name)}: ${literal}`);
936
947
  }
937
- const entries = [...Object.keys(groups).filter((location) => groups[location].length > 0).map((location) => `${location}: { ${groups[location].join(", ")} }`), serializeBodyEncoding(node)].filter(Boolean);
938
- return entries.length > 0 ? `{ ${entries.join(", ")} }` : null;
948
+ const entries = Object.entries(groups).filter(([, items]) => items.length > 0).map(([location, items]) => `${location}: { ${(merge ? [...items, `...config.styles?.${location}`] : items).join(", ")} }`);
949
+ if (entries.length === 0) return null;
950
+ return `{ ${(merge ? ["...config.styles", ...entries] : entries).join(", ")} }`;
939
951
  }
940
952
  // ../../internals/client/src/builders/callConfig.ts
941
953
  /**
@@ -951,37 +963,45 @@ function buildResponseTypeLiteral(node) {
951
963
  }
952
964
  /**
953
965
  * Builds the call config literal a generated operation forwards to the client, shared by the standalone
954
- * functions and the SDK methods. The `throwOnError` fallback is applied after the `...config` spread.
966
+ * functions and the SDK methods. The operation's own `baseURL` comes before the `...config` spread, so a per-call one wins. The
967
+ * `validator` and `throwOnError` entries come after it, so a per-call `validator` is merged per key with the generated one instead
968
+ * of replacing it. `styles` is merged the same way, per parameter, unless `returnType` is `'plain'`.
955
969
  *
956
970
  * `mergeContentType` is `true` when the caller must destructure `contentType` from `options`, and
957
971
  * `eventStream` is `true` when the operation streams `text/event-stream` or JSON Lines. Unless `returnType` is `'plain'`, a
958
972
  * stream's literal leaves the `response` validator to `toEventStream`, which runs it per item.
959
973
  */
960
- function buildCallConfig({ node, validatorHooks, security, throwOnErrorDefault, returnType }) {
974
+ function buildCallConfig({ node, validatorHooks, security, baseURL, throwOnErrorDefault, returnType }) {
961
975
  const eventStream = isEventStream(node);
962
976
  const securityLiteral = buildSecurityMetadata({ security });
963
- const stylesLiteral = buildStyles({ node });
977
+ const ownRuntime = returnType !== "plain";
978
+ const stylesLiteral = buildStyles({
979
+ node,
980
+ merge: ownRuntime
981
+ });
982
+ const styles = stylesLiteral ? `styles: ${stylesLiteral}` : null;
964
983
  const validatorLiteral = buildValidatorLiteral(validatorHooks, [
965
984
  "request",
966
985
  "path",
967
986
  "query",
968
987
  "headers",
969
988
  "cookies",
970
- ...eventStream && returnType !== "plain" ? [] : ["response"],
971
- "error",
972
- "errorStatus"
989
+ ...eventStream && ownRuntime ? [] : ["response"],
990
+ "error"
973
991
  ]);
974
992
  const { literal: contentTypeLiteral, merge: mergeContentType } = buildContentType({ node });
975
993
  return {
976
994
  callConfig: `{ ${[
977
995
  `method: '${node.method.toUpperCase()}'`,
978
996
  `url: '${node.path}'`,
997
+ baseURL ? `baseURL: ${JSON.stringify(baseURL)}` : null,
979
998
  securityLiteral ? `security: ${securityLiteral}` : null,
980
- stylesLiteral ? `styles: ${stylesLiteral}` : null,
981
- validatorLiteral,
999
+ ownRuntime ? null : styles,
982
1000
  contentTypeLiteral,
983
1001
  buildResponseTypeLiteral(node),
984
1002
  "...config",
1003
+ ownRuntime ? styles : null,
1004
+ validatorLiteral,
985
1005
  `throwOnError: config.throwOnError ?? ${throwOnErrorDefault}`
986
1006
  ].filter(Boolean).join(", ")} }`,
987
1007
  mergeContentType,
@@ -1023,11 +1043,12 @@ function buildGroupedOptionsSignature({ node, types, returnType, throwOnErrorDef
1023
1043
  * body destructures `contentType` from `options`. Shared by the standalone functions and the SDK methods, so a
1024
1044
  * `text/event-stream` operation is wired to `toEventStream` in one place.
1025
1045
  */
1026
- function buildOperationBody({ node, types, validatorHooks, security, returnType, throwOnErrorDefault }) {
1046
+ function buildOperationBody({ node, types, validatorHooks, security, baseURL, returnType, throwOnErrorDefault }) {
1027
1047
  const { callConfig, mergeContentType, eventStream } = buildCallConfig({
1028
1048
  node,
1029
1049
  validatorHooks,
1030
1050
  security,
1051
+ baseURL,
1031
1052
  throwOnErrorDefault,
1032
1053
  returnType
1033
1054
  });
@@ -1067,13 +1088,14 @@ function buildOperationBody({ node, types, validatorHooks, security, returnType,
1067
1088
  * type, signature, and call config are built with the AST factory, and only the jsx-renderer emits
1068
1089
  * the source.
1069
1090
  */
1070
- function Operation({ name, node, types, validatorHooks, returnType, throwOnErrorDefault, security }) {
1091
+ function Operation({ name, node, types, validatorHooks, returnType, throwOnErrorDefault, security, baseURL }) {
1071
1092
  if (!_kubb_kit.ast.isHttpOperationNode(node)) return null;
1072
1093
  const body = buildOperationBody({
1073
1094
  node,
1074
1095
  types,
1075
1096
  validatorHooks,
1076
1097
  security,
1098
+ baseURL,
1077
1099
  returnType,
1078
1100
  throwOnErrorDefault
1079
1101
  });
@@ -1272,6 +1294,12 @@ function createClientGenerator(name, { sse } = {}) {
1272
1294
  method: node.method,
1273
1295
  path: node.path
1274
1296
  });
1297
+ const baseURL = getOperationBaseURL({
1298
+ document: ctx.adapter.document,
1299
+ method: node.method,
1300
+ path: node.path,
1301
+ server: ctx.adapter.options?.server
1302
+ });
1275
1303
  const clientPath = importPath ?? node_path.default.resolve(root, ".kubb/client.ts");
1276
1304
  const clientRoot = importPath === void 0 ? meta.file.path : void 0;
1277
1305
  const standardSchemaPath = importPath ?? node_path.default.resolve(root, ".kubb/standardSchema.ts");
@@ -1366,7 +1394,8 @@ function createClientGenerator(name, { sse } = {}) {
1366
1394
  validatorHooks,
1367
1395
  returnType,
1368
1396
  throwOnErrorDefault,
1369
- security
1397
+ security,
1398
+ baseURL
1370
1399
  })
1371
1400
  ]
1372
1401
  });