@kubb/plugin-fetch 5.6.0 → 5.6.2

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
@@ -27,29 +27,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  let kubb_kit = require("kubb/kit");
28
28
  let node_path = require("node:path");
29
29
  node_path = __toESM(node_path, 1);
30
- let _kubb_plugin_ts = require("@kubb/plugin-ts");
30
+ let _kubb_plugin_zod = require("@kubb/plugin-zod");
31
31
  let kubb_jsx = require("kubb/jsx");
32
+ let _kubb_plugin_ts = require("@kubb/plugin-ts");
32
33
  let kubb_jsx_jsx_runtime = require("kubb/jsx/jsx-runtime");
33
- let _kubb_plugin_zod = require("@kubb/plugin-zod");
34
34
  let node_url = require("node:url");
35
- //#region ../../internals/shared/src/params.ts
36
- /**
37
- * Drops parameters that share the same name, keeping the first.
38
- *
39
- * A malformed spec can declare the same parameter name twice within one `in` location. Both would
40
- * resolve to the same output property, so emitting both would yield an object type with a duplicate
41
- * member, which TypeScript rejects. This is a defensive guard against that case, not a casing guard:
42
- * parameter names flow through unchanged, so no two distinct names ever collide here anymore.
43
- */
44
- function dedupeParams(params) {
45
- const seen = /* @__PURE__ */ new Set();
46
- return params.filter((param) => {
47
- if (seen.has(param.name)) return false;
48
- seen.add(param.name);
49
- return true;
50
- });
51
- }
52
- //#endregion
53
35
  //#region ../../internals/shared/src/operation.ts
54
36
  /**
55
37
  * Builds the `ResolverFileParams` every operation generator passes to
@@ -211,11 +193,19 @@ const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
211
193
  function getOperationParameters(node) {
212
194
  const cached = operationParameterGroupsByNode.get(node);
213
195
  if (cached) return cached;
196
+ const group = (location) => {
197
+ const seen = /* @__PURE__ */ new Set();
198
+ return node.parameters.filter((param) => {
199
+ if (param.in !== location || seen.has(param.name)) return false;
200
+ seen.add(param.name);
201
+ return true;
202
+ });
203
+ };
214
204
  const groups = {
215
- path: dedupeParams(node.parameters.filter((param) => param.in === "path")),
216
- query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
217
- header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
218
- cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
205
+ path: group("path"),
206
+ query: group("query"),
207
+ header: group("header"),
208
+ cookie: group("cookie")
219
209
  };
220
210
  operationParameterGroupsByNode.set(node, groups);
221
211
  return groups;
@@ -464,6 +454,134 @@ function createGroupConfig(group) {
464
454
  };
465
455
  }
466
456
  //#endregion
457
+ //#region ../../internals/client/src/builders/validatorOptions.ts
458
+ /**
459
+ * Returns `true` when any direction of the validator uses zod (used for dependency checks).
460
+ */
461
+ function isValidatorEnabled(validator) {
462
+ if (!validator) return false;
463
+ if (validator === "zod") return true;
464
+ return Boolean(validator.request || validator.response);
465
+ }
466
+ /**
467
+ * Returns `'zod'` when request body parsing is enabled, `null` otherwise. The string shorthand
468
+ * `'zod'` validates the response only, so it does not enable request parsing.
469
+ */
470
+ function resolveRequestValidator(validator) {
471
+ if (!validator || validator === "zod") return null;
472
+ return validator.request ?? null;
473
+ }
474
+ /**
475
+ * Returns `'zod'` when response parsing is enabled, `null` otherwise. The string shorthand `'zod'`
476
+ * maps to response parsing.
477
+ */
478
+ function resolveResponseValidator(validator) {
479
+ if (!validator) return null;
480
+ if (validator === "zod") return "zod";
481
+ return validator.response ?? null;
482
+ }
483
+ /**
484
+ * Resolves the zod schema name a generated client validates an error body with on the non-throw path.
485
+ * Uses the error-only `<operation>ErrorSchema` (the union of non-2xx statuses); returns `null` when the
486
+ * operation documents no error responses with a schema.
487
+ */
488
+ function buildZodErrorParse(node, zodResolver) {
489
+ if (!node.responses.some((res) => !isSuccessStatusCode(res.statusCode) && res.content?.some((entry) => entry.schema))) return null;
490
+ return zodResolver.response.error?.(node) || null;
491
+ }
492
+ //#endregion
493
+ //#region ../../internals/client/src/builders/validator.ts
494
+ /**
495
+ * Builds the validator-hook references for one operation. Request validation runs before the send;
496
+ * response validation runs on the success body only. Returns `null` references when the matching
497
+ * direction is disabled or the schema is absent.
498
+ */
499
+ function buildValidatorHooks({ node, validator, zodResolver }) {
500
+ const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
501
+ const parsesResponse = zodResolver && resolveResponseValidator(validator) === "zod";
502
+ const response = parsesResponse ? zodResolver.response.response(node) || null : null;
503
+ const error = parsesResponse ? buildZodErrorParse(node, zodResolver) : null;
504
+ const request = zodResolver && resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.response.body(node) || null : null;
505
+ return {
506
+ request,
507
+ response,
508
+ error,
509
+ importedZodNames: [
510
+ response,
511
+ error,
512
+ request
513
+ ].filter((name) => Boolean(name))
514
+ };
515
+ }
516
+ //#endregion
517
+ //#region ../../internals/client/src/builders/security.ts
518
+ function serializeAuth(auth) {
519
+ const parts = [`type: '${auth.type}'`];
520
+ if (auth.scheme) parts.push(`scheme: '${auth.scheme}'`);
521
+ if (auth.name) parts.push(`name: '${auth.name}'`);
522
+ if (auth.in) parts.push(`in: '${auth.in}'`);
523
+ return `{ ${parts.join(", ")} }`;
524
+ }
525
+ /**
526
+ * Maps an OpenAPI security scheme to the inline `Auth` object, or `null` when the runtime cannot
527
+ * place it (an unresolved `$ref`, or an `apiKey` without a name or outside `header` / `query` /
528
+ * `cookie`). `http` schemes other than `basic` are treated as bearer.
529
+ */
530
+ function resolveSecurityScheme(scheme) {
531
+ if (!scheme || "$ref" in scheme) return null;
532
+ if (scheme.type === "apiKey") {
533
+ if (!scheme.name || scheme.in !== "header" && scheme.in !== "query" && scheme.in !== "cookie") return null;
534
+ return {
535
+ type: "apiKey",
536
+ name: scheme.name,
537
+ in: scheme.in
538
+ };
539
+ }
540
+ if (scheme.type === "http") return {
541
+ type: "http",
542
+ scheme: scheme.scheme?.toLowerCase() === "basic" ? "basic" : "bearer"
543
+ };
544
+ if (scheme.type === "oauth2") return { type: "oauth2" };
545
+ if (scheme.type === "openIdConnect") return { type: "openIdConnect" };
546
+ return null;
547
+ }
548
+ /**
549
+ * Derives the per-operation security metadata from the OpenAPI document. The operation's own
550
+ * `security` overrides the global `security` (an explicit empty array disables auth), and every
551
+ * referenced scheme is resolved from `components.securitySchemes` into a flat, de-duplicated list of
552
+ * `Auth` objects the runtime walks in order.
553
+ *
554
+ * @example
555
+ * `getOperationSecurity({ document, method: 'POST', path: '/pet' })`
556
+ * `// [{ type: 'http', scheme: 'bearer' }]`
557
+ */
558
+ function getOperationSecurity({ document, method, path }) {
559
+ if (!document) return void 0;
560
+ const requirements = (document.paths?.[path]?.[method.toLowerCase()])?.security ?? document.security;
561
+ if (!requirements?.length) return void 0;
562
+ const definitions = document.components?.securitySchemes ?? {};
563
+ const security = [];
564
+ const seen = /* @__PURE__ */ new Set();
565
+ for (const requirement of requirements) for (const schemeName of Object.keys(requirement)) {
566
+ if (seen.has(schemeName)) continue;
567
+ seen.add(schemeName);
568
+ const auth = resolveSecurityScheme(definitions[schemeName]);
569
+ if (auth) security.push(auth);
570
+ }
571
+ return security.length ? security : void 0;
572
+ }
573
+ /**
574
+ * Serializes the per-operation security into the literal emitted on each generated call's `security`
575
+ * field. The runtime `resolveAuth` helper walks it, calling the configured `auth` resolver per entry.
576
+ *
577
+ * @example
578
+ * `buildSecurityMetadata({ security: [{ type: 'http', scheme: 'bearer' }] }) // "[{ type: 'http', scheme: 'bearer' }]"`
579
+ */
580
+ function buildSecurityMetadata({ security }) {
581
+ if (!security?.length) return null;
582
+ return `[${security.map(serializeAuth).join(", ")}]`;
583
+ }
584
+ //#endregion
467
585
  //#region ../../internals/client/src/builders/contentType.ts
468
586
  /**
469
587
  * Builds the `contentType` entry baked into the call config, or `null` for the JSON default.
@@ -556,74 +674,6 @@ function buildReturnStatement({ node, types, callConfig, returnType, throwOnErro
556
674
  })}>>)`;
557
675
  }
558
676
  //#endregion
559
- //#region ../../internals/client/src/builders/security.ts
560
- function serializeAuth(auth) {
561
- const parts = [`type: '${auth.type}'`];
562
- if (auth.scheme) parts.push(`scheme: '${auth.scheme}'`);
563
- if (auth.name) parts.push(`name: '${auth.name}'`);
564
- if (auth.in) parts.push(`in: '${auth.in}'`);
565
- return `{ ${parts.join(", ")} }`;
566
- }
567
- /**
568
- * Maps an OpenAPI security scheme to the inline `Auth` object, or `null` when the runtime cannot
569
- * place it (an unresolved `$ref`, or an `apiKey` without a name or outside `header` / `query` /
570
- * `cookie`). `http` schemes other than `basic` are treated as bearer.
571
- */
572
- function resolveSecurityScheme(scheme) {
573
- if (!scheme || "$ref" in scheme) return null;
574
- if (scheme.type === "apiKey") {
575
- if (!scheme.name || scheme.in !== "header" && scheme.in !== "query" && scheme.in !== "cookie") return null;
576
- return {
577
- type: "apiKey",
578
- name: scheme.name,
579
- in: scheme.in
580
- };
581
- }
582
- if (scheme.type === "http") return {
583
- type: "http",
584
- scheme: scheme.scheme?.toLowerCase() === "basic" ? "basic" : "bearer"
585
- };
586
- if (scheme.type === "oauth2") return { type: "oauth2" };
587
- if (scheme.type === "openIdConnect") return { type: "openIdConnect" };
588
- return null;
589
- }
590
- /**
591
- * Derives the per-operation security metadata from the OpenAPI document. The operation's own
592
- * `security` overrides the global `security` (an explicit empty array disables auth), and every
593
- * referenced scheme is resolved from `components.securitySchemes` into a flat, de-duplicated list of
594
- * `Auth` objects the runtime walks in order.
595
- *
596
- * @example
597
- * `getOperationSecurity({ document, method: 'POST', path: '/pet' })`
598
- * `// [{ type: 'http', scheme: 'bearer' }]`
599
- */
600
- function getOperationSecurity({ document, method, path }) {
601
- if (!document) return void 0;
602
- const requirements = (document.paths?.[path]?.[method.toLowerCase()])?.security ?? document.security;
603
- if (!requirements?.length) return void 0;
604
- const definitions = document.components?.securitySchemes ?? {};
605
- const security = [];
606
- const seen = /* @__PURE__ */ new Set();
607
- for (const requirement of requirements) for (const schemeName of Object.keys(requirement)) {
608
- if (seen.has(schemeName)) continue;
609
- seen.add(schemeName);
610
- const auth = resolveSecurityScheme(definitions[schemeName]);
611
- if (auth) security.push(auth);
612
- }
613
- return security.length ? security : void 0;
614
- }
615
- /**
616
- * Serializes the per-operation security into the literal emitted on each generated call's `security`
617
- * field. The runtime `resolveAuth` helper walks it, calling the configured `auth` resolver per entry.
618
- *
619
- * @example
620
- * `buildSecurityMetadata({ security: [{ type: 'http', scheme: 'bearer' }] }) // "[{ type: 'http', scheme: 'bearer' }]"`
621
- */
622
- function buildSecurityMetadata({ security }) {
623
- if (!security?.length) return null;
624
- return `[${security.map(serializeAuth).join(", ")}]`;
625
- }
626
- //#endregion
627
677
  //#region ../../internals/client/src/builders/signature.ts
628
678
  const declarationPrinter = (0, _kubb_plugin_ts.functionPrinter)({ mode: "declaration" });
629
679
  /**
@@ -653,161 +703,6 @@ function buildGroupedOptionsSignature({ node, types, returnType, throwOnErrorDef
653
703
  };
654
704
  }
655
705
  //#endregion
656
- //#region ../../internals/client/src/builders/validatorOptions.ts
657
- /**
658
- * Returns `true` when any direction of the validator uses zod (used for dependency checks).
659
- */
660
- function isValidatorEnabled(validator) {
661
- if (!validator) return false;
662
- if (validator === "zod") return true;
663
- return Boolean(validator.request || validator.response);
664
- }
665
- /**
666
- * Returns `'zod'` when request body parsing is enabled, `null` otherwise. The string shorthand
667
- * `'zod'` validates the response only, so it does not enable request parsing.
668
- */
669
- function resolveRequestValidator(validator) {
670
- if (!validator || validator === "zod") return null;
671
- return validator.request ?? null;
672
- }
673
- /**
674
- * Returns `'zod'` when query-parameters parsing is enabled, `null` otherwise. Only the object form
675
- * `{ request: 'zod' }` enables it.
676
- */
677
- function resolveQueryParamsValidator(validator) {
678
- if (!validator || validator === "zod") return null;
679
- return validator.request ?? null;
680
- }
681
- /**
682
- * Returns `'zod'` when response parsing is enabled, `null` otherwise. The string shorthand `'zod'`
683
- * maps to response parsing.
684
- */
685
- function resolveResponseValidator(validator) {
686
- if (!validator) return null;
687
- if (validator === "zod") return "zod";
688
- return validator.response ?? null;
689
- }
690
- /**
691
- * Resolves the zod expression a generated client validates a success response with. Only success
692
- * (2xx) bodies reach the parse under the throw-on-error contract, so the success-only
693
- * `<operation>ResponseSchema` is used; error bodies are never zod-parsed.
694
- */
695
- function buildZodResponseParse(node, zodResolver) {
696
- const name = zodResolver.response.response(node);
697
- return name ? {
698
- expression: name,
699
- importNames: [name]
700
- } : null;
701
- }
702
- /**
703
- * Resolves the zod expression a generated client validates an error body with on the non-throw path.
704
- * Uses the error-only `<operation>ErrorSchema` (the union of non-2xx statuses); returns `null` when the
705
- * operation documents no error responses with a schema.
706
- */
707
- function buildZodErrorParse(node, zodResolver) {
708
- if (!node.responses.some((res) => !isSuccessStatusCode(res.statusCode) && res.content?.some((entry) => entry.schema))) return null;
709
- const name = zodResolver.response.error?.(node);
710
- return name ? {
711
- expression: name,
712
- importNames: [name]
713
- } : null;
714
- }
715
- //#endregion
716
- //#region ../../internals/client/src/builders/validator.ts
717
- /**
718
- * Builds the validator-hook references for one operation. Request validation runs before the send;
719
- * response validation runs on the success body only. Returns `null` references when the matching
720
- * direction is disabled or the schema is absent.
721
- */
722
- function buildValidatorHooks({ node, validator, zodResolver }) {
723
- const importedZodNames = [];
724
- const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
725
- const zodRequestName = zodResolver && resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.response.body(node) : null;
726
- const request = zodRequestName ?? null;
727
- if (zodRequestName) importedZodNames.push(zodRequestName);
728
- const responseParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodResponseParse(node, zodResolver) : null;
729
- const response = responseParse ? responseParse.expression : null;
730
- if (responseParse) importedZodNames.push(...responseParse.importNames);
731
- const errorParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver) : null;
732
- const error = errorParse ? errorParse.expression : null;
733
- if (errorParse) importedZodNames.push(...errorParse.importNames);
734
- return {
735
- request,
736
- response,
737
- error,
738
- importedZodNames
739
- };
740
- }
741
- //#endregion
742
- //#region ../../internals/client/src/builders/sdkMethod.ts
743
- /**
744
- * Builds the call config literal forwarded to the contract client, mirroring the shared `Operation`
745
- * component: `{ method, url, security?, validator?, ...config }`. The `...config` spread carries every
746
- * per-call field. The plugin's `throwOnError` fallback is applied after the spread so it also wins
747
- * over a client instance's runtime config when the call omits an override.
748
- */
749
- function buildCallConfig({ node, validator, zodResolver, security, throwOnErrorDefault }) {
750
- const validators = buildValidatorHooks({
751
- node,
752
- validator,
753
- zodResolver
754
- });
755
- const validatorEntries = [validators.request ? `request: ${validators.request}` : null, validators.response ? `response: ${validators.response}` : null].filter(Boolean);
756
- const validatorLiteral = validatorEntries.length ? `validator: { ${validatorEntries.join(", ")} }` : null;
757
- const securityLiteral = buildSecurityMetadata({ security });
758
- const { literal: contentTypeLiteral } = buildContentType({ node });
759
- return `{ ${[
760
- `method: '${node.method.toUpperCase()}'`,
761
- `url: '${node.path}'`,
762
- securityLiteral ? `security: ${securityLiteral}` : null,
763
- validatorLiteral,
764
- contentTypeLiteral,
765
- "...config",
766
- `throwOnError: config.throwOnError ?? ${throwOnErrorDefault}`
767
- ].filter(Boolean).join(", ")} }`;
768
- }
769
- /**
770
- * Builds a single instance method for a generated SDK class. The body forwards the single grouped
771
- * `options` object to the instance's own client (`this.client`, built once in the constructor) and
772
- * returns the `Unwrappable<RequestResult>`. A per-call `options.client` still overrides the
773
- * instance client, so one operation can be routed to a different environment without a new
774
- * instance.
775
- */
776
- function buildSdkMethod({ node, name, types, zodResolver, validator, security, returnType, throwOnErrorDefault }) {
777
- if (!kubb_kit.ast.isHttpOperationNode(node)) return "";
778
- const signature = buildGroupedOptionsSignature({
779
- node,
780
- types,
781
- returnType,
782
- throwOnErrorDefault
783
- });
784
- const returnStatement = buildReturnStatement({
785
- node,
786
- types,
787
- callConfig: buildCallConfig({
788
- node,
789
- validator,
790
- zodResolver,
791
- security,
792
- throwOnErrorDefault
793
- }),
794
- returnType,
795
- throwOnErrorDefault
796
- });
797
- const generics = signature.generics.length ? `<${signature.generics.join(", ")}>` : "";
798
- const jsdoc = buildJSDoc(buildOperationComments(node, {
799
- link: "urlPath",
800
- linkPosition: "beforeDeprecated",
801
- splitLines: true
802
- }));
803
- const methodBody = [
804
- buildContentType({ node }).merge ? "const { client: request = this.client, contentType, ...config } = options" : "const { client: request = this.client, ...config } = options",
805
- "",
806
- returnStatement
807
- ].map((line) => line ? ` ${line}` : "").join("\n");
808
- return `${jsdoc} public ${name}${generics}(${signature.paramsSignature}): ${signature.returnType} {\n${methodBody}\n }`;
809
- }
810
- //#endregion
811
706
  //#region ../../internals/client/src/builders/styles.ts
812
707
  /**
813
708
  * Renders a parameter name as an object-literal key, quoted when it is not a bare identifier.
@@ -868,7 +763,7 @@ function buildStyles({ node }) {
868
763
  * type, signature, and call config are built with the AST factory, and only the jsx-renderer emits
869
764
  * the source.
870
765
  */
871
- function Operation({ name, node, types, zodResolver, validator, returnType, throwOnErrorDefault, security, isExportable = true, isIndexable = true }) {
766
+ function Operation({ name, node, types, zodResolver, validator, returnType, throwOnErrorDefault, security }) {
872
767
  if (!kubb_kit.ast.isHttpOperationNode(node)) return null;
873
768
  const signature = buildGroupedOptionsSignature({
874
769
  node,
@@ -915,11 +810,11 @@ function Operation({ name, node, types, zodResolver, validator, returnType, thro
915
810
  });
916
811
  return /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Source, {
917
812
  name,
918
- isExportable,
919
- isIndexable,
813
+ isExportable: true,
814
+ isIndexable: true,
920
815
  children: /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsxs)(kubb_jsx.Function, {
921
816
  name,
922
- export: isExportable,
817
+ export: true,
923
818
  generics: signature.generics,
924
819
  params: signature.paramsSignature,
925
820
  returnType: functionReturnType,
@@ -937,62 +832,24 @@ function Operation({ name, node, types, zodResolver, validator, returnType, thro
937
832
  });
938
833
  }
939
834
  //#endregion
940
- //#region ../../internals/client/src/components/SdkClient.tsx
941
- /**
942
- * Renders one instance class per tag with one method per operation. The constructor takes a client
943
- * config object and builds its own client through `createClient`, so each environment is a separate
944
- * instance: `const api = new PetClient({ baseURL }); api.getPetById(...)`. A per-call `client` option
945
- * still overrides the instance client for a one-off call.
946
- */
947
- function SdkClient({ name, isExportable = true, isIndexable = true, operations, validator, returnType, throwOnErrorDefault, children }) {
948
- const methods = operations.map(({ node, name: methodName, types, zodResolver, security }) => buildSdkMethod({
949
- node,
950
- name: methodName,
951
- types,
952
- zodResolver,
953
- validator,
954
- security,
955
- returnType,
956
- throwOnErrorDefault
957
- }));
958
- const classCode = `export class ${name} {\n${[
959
- " private readonly client: ClientInstance",
960
- "",
961
- " constructor(config: ClientConfig = {}) {",
962
- " this.client = createClient(config)",
963
- " }"
964
- ].join("\n")}\n\n${methods.join("\n\n")}\n}`;
965
- return /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsxs)(kubb_jsx.File.Source, {
966
- name,
967
- isExportable,
968
- isIndexable,
969
- children: [classCode, children]
970
- });
971
- }
972
- //#endregion
973
835
  //#region ../../internals/client/src/resolveOperationTypes.ts
974
836
  /**
975
- * Warning a client generator reports when neither plugin can supply the operation types, so the
976
- * pipeline says why nothing was generated instead of silently emitting no files.
977
- */
978
- const MISSING_OPERATION_TYPES_WARNING = "Skipped: no operation types available. Add `pluginTs()`, or `pluginZod({ inferred: true })` to type the client from its zod schemas.";
979
- /**
980
837
  * Picks the plugin a generated client takes its `Options` and `Responses` types from.
981
838
  *
982
839
  * `plugin-ts` wins whenever it is in the pipeline, so existing configs keep the types they generate
983
840
  * today. Without it, `pluginZod({ inferred: true })` takes over: its inferred options and responses
984
841
  * types are built from the same shared schemas as the `plugin-ts` ones, so a client typed off
985
842
  * `z.infer` carries what the codecs decode to (a `Temporal` instance, a `Date`) rather than the raw
986
- * wire type. Returns `null` when neither is available, leaving the caller nothing to type against.
843
+ * wire type. Throws `KUBB_MISSING_DEPENDENCY` for `pluginName` when neither is available, since the
844
+ * client has nothing to type against.
987
845
  *
988
846
  * @example
989
847
  * ```ts
990
- * const types = resolveOperationTypes(ctx.driver)
991
- * if (!types) return null
848
+ * const types = resolveOperationTypes(ctx.driver, ctx.plugin.name)
992
849
  * types.response.options(node) // 'GetPetByIdOptions', or 'GetPetByIdOptionsSchemaType' from zod
993
850
  * ```
994
851
  */
995
- function resolveOperationTypes(driver) {
852
+ function resolveOperationTypes(driver, pluginName) {
996
853
  const pluginTs = driver.getPlugin(_kubb_plugin_ts.pluginTsName);
997
854
  if (pluginTs) {
998
855
  const resolver = driver.getResolver(_kubb_plugin_ts.pluginTsName);
@@ -1004,7 +861,14 @@ function resolveOperationTypes(driver) {
1004
861
  };
1005
862
  }
1006
863
  const pluginZod = driver.getPlugin(_kubb_plugin_zod.pluginZodName);
1007
- if (!pluginZod?.options?.inferred) return null;
864
+ if (!pluginZod?.options?.inferred) throw new kubb_kit.Diagnostics.Error({
865
+ code: kubb_kit.Diagnostics.code.missingDependency,
866
+ severity: "error",
867
+ message: `Plugin "${pluginName}" has no source for operation types.`,
868
+ help: `Add \`pluginTs()\` before "${pluginName}", or add \`pluginZod({ inferred: true })\`.`,
869
+ location: { kind: "config" },
870
+ plugin: pluginName
871
+ });
1008
872
  const resolver = driver.getResolver(_kubb_plugin_zod.pluginZodName);
1009
873
  return {
1010
874
  response: {
@@ -1034,20 +898,15 @@ function createClientGenerator(name, resolveClientPath) {
1034
898
  if (!kubb_kit.ast.isHttpOperationNode(node)) return null;
1035
899
  const { config, driver, resolver, root } = ctx;
1036
900
  const { output, validator, returnType, throwOnErrorDefault, group } = ctx.options;
1037
- const types = resolveOperationTypes(driver);
1038
- if (!types) {
1039
- ctx.warn(MISSING_OPERATION_TYPES_WARNING);
1040
- return null;
1041
- }
901
+ const types = resolveOperationTypes(driver, ctx.plugin.name);
1042
902
  const pluginZod = resolveResponseValidator(validator) === "zod" || resolveRequestValidator(validator) === "zod" ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
1043
903
  const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : null;
1044
- const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
1045
904
  const importedTypeNames = [types.response.options(node), types.response.responses(node)];
1046
- const importedZodNames = zodResolver ? [
1047
- resolveResponseValidator(validator) === "zod" ? zodResolver.response.response?.(node) : null,
1048
- resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
1049
- resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.response.body?.(node) : null
1050
- ].filter((name) => Boolean(name)) : [];
905
+ const importedZodNames = buildValidatorHooks({
906
+ node,
907
+ validator,
908
+ zodResolver
909
+ }).importedZodNames;
1051
910
  const meta = {
1052
911
  name: resolver.name(node.operationId),
1053
912
  file: resolver.file({
@@ -1159,13 +1018,106 @@ function createClientGenerator(name, resolveClientPath) {
1159
1018
  });
1160
1019
  }
1161
1020
  //#endregion
1021
+ //#region ../../internals/client/src/components/SdkClient.tsx
1022
+ /**
1023
+ * Builds the call config literal forwarded to the contract client; the `throwOnError` fallback is applied after the `...config` spread.
1024
+ */
1025
+ function buildCallConfig({ node, validator, zodResolver, security, throwOnErrorDefault }) {
1026
+ const validators = buildValidatorHooks({
1027
+ node,
1028
+ validator,
1029
+ zodResolver
1030
+ });
1031
+ const validatorEntries = [validators.request ? `request: ${validators.request}` : null, validators.response ? `response: ${validators.response}` : null].filter(Boolean);
1032
+ const validatorLiteral = validatorEntries.length ? `validator: { ${validatorEntries.join(", ")} }` : null;
1033
+ const securityLiteral = buildSecurityMetadata({ security });
1034
+ const { literal: contentTypeLiteral } = buildContentType({ node });
1035
+ return `{ ${[
1036
+ `method: '${node.method.toUpperCase()}'`,
1037
+ `url: '${node.path}'`,
1038
+ securityLiteral ? `security: ${securityLiteral}` : null,
1039
+ validatorLiteral,
1040
+ contentTypeLiteral,
1041
+ "...config",
1042
+ `throwOnError: config.throwOnError ?? ${throwOnErrorDefault}`
1043
+ ].filter(Boolean).join(", ")} }`;
1044
+ }
1045
+ /**
1046
+ * Builds one instance method of a generated SDK class; a per-call `options.client` overrides `this.client`.
1047
+ */
1048
+ function buildSdkMethod({ node, name, types, zodResolver, validator, security, returnType, throwOnErrorDefault }) {
1049
+ if (!kubb_kit.ast.isHttpOperationNode(node)) return "";
1050
+ const signature = buildGroupedOptionsSignature({
1051
+ node,
1052
+ types,
1053
+ returnType,
1054
+ throwOnErrorDefault
1055
+ });
1056
+ const returnStatement = buildReturnStatement({
1057
+ node,
1058
+ types,
1059
+ callConfig: buildCallConfig({
1060
+ node,
1061
+ validator,
1062
+ zodResolver,
1063
+ security,
1064
+ throwOnErrorDefault
1065
+ }),
1066
+ returnType,
1067
+ throwOnErrorDefault
1068
+ });
1069
+ const generics = signature.generics.length ? `<${signature.generics.join(", ")}>` : "";
1070
+ const jsdoc = buildJSDoc(buildOperationComments(node, {
1071
+ link: "urlPath",
1072
+ linkPosition: "beforeDeprecated",
1073
+ splitLines: true
1074
+ }));
1075
+ const methodBody = [
1076
+ buildContentType({ node }).merge ? "const { client: request = this.client, contentType, ...config } = options" : "const { client: request = this.client, ...config } = options",
1077
+ "",
1078
+ returnStatement
1079
+ ].map((line) => line ? ` ${line}` : "").join("\n");
1080
+ return `${jsdoc} public ${name}${generics}(${signature.paramsSignature}): ${signature.returnType} {\n${methodBody}\n }`;
1081
+ }
1082
+ /**
1083
+ * Renders one instance class per tag with one method per operation. The constructor takes a client
1084
+ * config object and builds its own client through `createClient`, so each environment is a separate
1085
+ * instance: `const api = new PetClient({ baseURL }); api.getPetById(...)`. A per-call `client` option
1086
+ * still overrides the instance client for a one-off call.
1087
+ */
1088
+ function SdkClient({ name, operations, validator, returnType, throwOnErrorDefault }) {
1089
+ const methods = operations.map(({ node, name: methodName, types, zodResolver, security }) => buildSdkMethod({
1090
+ node,
1091
+ name: methodName,
1092
+ types,
1093
+ zodResolver,
1094
+ validator,
1095
+ security,
1096
+ returnType,
1097
+ throwOnErrorDefault
1098
+ }));
1099
+ const classCode = `export class ${name} {\n${[
1100
+ " private readonly client: ClientInstance",
1101
+ "",
1102
+ " constructor(config: ClientConfig = {}) {",
1103
+ " this.client = createClient(config)",
1104
+ " }"
1105
+ ].join("\n")}\n\n${methods.join("\n\n")}\n}`;
1106
+ return /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Source, {
1107
+ name,
1108
+ isExportable: true,
1109
+ isIndexable: true,
1110
+ children: classCode
1111
+ });
1112
+ }
1113
+ //#endregion
1162
1114
  //#region ../../internals/client/src/components/SdkFacade.tsx
1163
1115
  /**
1164
1116
  * Renders a composed root SDK class that instantiates every tag client from one shared config, so
1165
1117
  * `new PetStore({ baseURL }).petClient.getPetById(...)` reaches an operation through a single entry
1166
1118
  * point bound to one environment. The per-tag clients are read-only fields built in the constructor.
1167
1119
  */
1168
- function SdkFacade({ name, isExportable = true, isIndexable = true, members, children }) {
1120
+ function SdkFacade({ name, members }) {
1169
1121
  const fields = members.map((member) => ` readonly ${member.propName}: ${member.className}`);
1170
1122
  const assignments = members.map((member) => ` this.${member.propName} = new ${member.className}(config)`);
1171
1123
  const classCode = `export class ${name} {\n${[
@@ -1175,11 +1127,11 @@ function SdkFacade({ name, isExportable = true, isIndexable = true, members, chi
1175
1127
  ...assignments,
1176
1128
  " }"
1177
1129
  ].join("\n")}\n}`;
1178
- return /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsxs)(kubb_jsx.File.Source, {
1130
+ return /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Source, {
1179
1131
  name,
1180
- isExportable,
1181
- isIndexable,
1182
- children: [classCode, children]
1132
+ isExportable: true,
1133
+ isIndexable: true,
1134
+ children: classCode
1183
1135
  });
1184
1136
  }
1185
1137
  //#endregion
@@ -1188,13 +1140,7 @@ function resolveTypeImportNames(node, types) {
1188
1140
  return [types.response.options(node), types.response.responses(node)];
1189
1141
  }
1190
1142
  function resolveZodImportNames(node, zodResolver, validator) {
1191
- const { query: queryParams } = getOperationParameters(node);
1192
- return [
1193
- resolveResponseValidator(validator) === "zod" ? zodResolver.response.response(node) : null,
1194
- resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
1195
- resolveRequestValidator(validator) === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.response.body(node) : null,
1196
- resolveQueryParamsValidator(validator) === "zod" && queryParams.length > 0 ? zodResolver.param.query(node, queryParams[0]) : null
1197
- ].filter((n) => Boolean(n));
1143
+ return [resolveResponseValidator(validator) === "zod" ? zodResolver.response.response(node) : null, resolveRequestValidator(validator) === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.response.body(node) : null].filter((n) => Boolean(n));
1198
1144
  }
1199
1145
  /**
1200
1146
  * Groups operations into one controller per tag. Operations without a tag fall back to a single
@@ -1301,12 +1247,7 @@ function createSdkGenerator() {
1301
1247
  location: { kind: "config" },
1302
1248
  plugin: ctx.plugin.name
1303
1249
  });
1304
- const types = resolveOperationTypes(ctx.driver);
1305
- if (!types) {
1306
- ctx.warn(MISSING_OPERATION_TYPES_WARNING);
1307
- return null;
1308
- }
1309
- const controllers = buildControllers(nodes, ctx, types);
1250
+ const controllers = buildControllers(nodes, ctx, resolveOperationTypes(ctx.driver, ctx.plugin.name));
1310
1251
  const clientPath = node_path.default.resolve(root, ".kubb/client.ts");
1311
1252
  const banner = (file) => resolver.default.banner(ctx.meta, {
1312
1253
  output,
@@ -1487,7 +1428,7 @@ var compatibility = {
1487
1428
  };
1488
1429
  //#endregion
1489
1430
  //#region package.json
1490
- var version = "5.6.0";
1431
+ var version = "5.6.2";
1491
1432
  //#endregion
1492
1433
  //#region src/templates.ts
1493
1434
  /** Absolute path to the fetch client template, copied into `.kubb/client.ts`. */
@@ -1535,10 +1476,10 @@ const pluginFetch = (0, kubb_kit.defineExtension)({
1535
1476
  version,
1536
1477
  compatibility
1537
1478
  },
1538
- dependencies: {
1479
+ dependencies: (options) => ({
1539
1480
  [_kubb_plugin_ts.pluginTsName]: { optional: true },
1540
- [_kubb_plugin_zod.pluginZodName]: { optional: true }
1541
- },
1481
+ [_kubb_plugin_zod.pluginZodName]: { optional: !isValidatorEnabled(options.validator) }
1482
+ }),
1542
1483
  setup(options) {
1543
1484
  const config = (0, kubb_kit.useConfig)();
1544
1485
  const { output = {
@@ -1561,6 +1502,32 @@ const pluginFetch = (0, kubb_kit.defineExtension)({
1561
1502
  } : void 0,
1562
1503
  resolver: userResolver ? kubb_kit.Resolver.merge(resolverClient, userResolver) : resolverClient
1563
1504
  };
1505
+ const allowedReturnTypes = [
1506
+ "full",
1507
+ "data",
1508
+ "plain"
1509
+ ];
1510
+ (0, kubb_kit.assertPluginOption)({
1511
+ plugin: pluginFetchName,
1512
+ option: "returnType",
1513
+ value: returnType,
1514
+ allowed: allowedReturnTypes
1515
+ });
1516
+ for (const [index, entry] of override.entries()) {
1517
+ if (entry.options.returnType === void 0) continue;
1518
+ (0, kubb_kit.assertPluginOption)({
1519
+ plugin: pluginFetchName,
1520
+ option: `override[${index}].options.returnType`,
1521
+ value: entry.options.returnType,
1522
+ allowed: allowedReturnTypes
1523
+ });
1524
+ }
1525
+ if (resolved.sdk) (0, kubb_kit.assertPluginOption)({
1526
+ plugin: pluginFetchName,
1527
+ option: "sdk.mode",
1528
+ value: resolved.sdk.mode,
1529
+ allowed: ["tag", "flat"]
1530
+ });
1564
1531
  (0, kubb_kit.setResolver)(resolved.resolver);
1565
1532
  (0, kubb_kit.setMacros)([...defaultMacros, ...options.macros ?? []]);
1566
1533
  (0, kubb_kit.addGenerator)(resolved.sdk ? createSdkGenerator() : clientGenerator);