@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.js CHANGED
@@ -1,29 +1,11 @@
1
1
  import "./rolldown-runtime-C0LytTxp.js";
2
- import { Diagnostics, Resolver, Url, addGenerator, ast, createResolver, defineExtension, defineGenerator, injectFile, macroSimplifyUnion, setMacros, setResolver, useConfig } from "kubb/kit";
2
+ import { Diagnostics, Resolver, Url, addGenerator, assertPluginOption, ast, createResolver, defineExtension, defineGenerator, injectFile, macroSimplifyUnion, setMacros, setResolver, useConfig } from "kubb/kit";
3
3
  import path from "node:path";
4
- import { createFunctionParameter, createFunctionParameters, functionPrinter, pluginTsName } from "@kubb/plugin-ts";
4
+ import { pluginZodName } from "@kubb/plugin-zod";
5
5
  import { File, Function, jsxRenderer } from "kubb/jsx";
6
+ import { createFunctionParameter, createFunctionParameters, functionPrinter, pluginTsName } from "@kubb/plugin-ts";
6
7
  import { Fragment, jsx, jsxs } from "kubb/jsx/jsx-runtime";
7
- import { pluginZodName } from "@kubb/plugin-zod";
8
8
  import { fileURLToPath } from "node:url";
9
- //#region ../../internals/shared/src/params.ts
10
- /**
11
- * Drops parameters that share the same name, keeping the first.
12
- *
13
- * A malformed spec can declare the same parameter name twice within one `in` location. Both would
14
- * resolve to the same output property, so emitting both would yield an object type with a duplicate
15
- * member, which TypeScript rejects. This is a defensive guard against that case, not a casing guard:
16
- * parameter names flow through unchanged, so no two distinct names ever collide here anymore.
17
- */
18
- function dedupeParams(params) {
19
- const seen = /* @__PURE__ */ new Set();
20
- return params.filter((param) => {
21
- if (seen.has(param.name)) return false;
22
- seen.add(param.name);
23
- return true;
24
- });
25
- }
26
- //#endregion
27
9
  //#region ../../internals/shared/src/operation.ts
28
10
  /**
29
11
  * Builds the `ResolverFileParams` every operation generator passes to
@@ -185,11 +167,19 @@ const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
185
167
  function getOperationParameters(node) {
186
168
  const cached = operationParameterGroupsByNode.get(node);
187
169
  if (cached) return cached;
170
+ const group = (location) => {
171
+ const seen = /* @__PURE__ */ new Set();
172
+ return node.parameters.filter((param) => {
173
+ if (param.in !== location || seen.has(param.name)) return false;
174
+ seen.add(param.name);
175
+ return true;
176
+ });
177
+ };
188
178
  const groups = {
189
- path: dedupeParams(node.parameters.filter((param) => param.in === "path")),
190
- query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
191
- header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
192
- cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
179
+ path: group("path"),
180
+ query: group("query"),
181
+ header: group("header"),
182
+ cookie: group("cookie")
193
183
  };
194
184
  operationParameterGroupsByNode.set(node, groups);
195
185
  return groups;
@@ -438,6 +428,134 @@ function createGroupConfig(group) {
438
428
  };
439
429
  }
440
430
  //#endregion
431
+ //#region ../../internals/client/src/builders/validatorOptions.ts
432
+ /**
433
+ * Returns `true` when any direction of the validator uses zod (used for dependency checks).
434
+ */
435
+ function isValidatorEnabled(validator) {
436
+ if (!validator) return false;
437
+ if (validator === "zod") return true;
438
+ return Boolean(validator.request || validator.response);
439
+ }
440
+ /**
441
+ * Returns `'zod'` when request body parsing is enabled, `null` otherwise. The string shorthand
442
+ * `'zod'` validates the response only, so it does not enable request parsing.
443
+ */
444
+ function resolveRequestValidator(validator) {
445
+ if (!validator || validator === "zod") return null;
446
+ return validator.request ?? null;
447
+ }
448
+ /**
449
+ * Returns `'zod'` when response parsing is enabled, `null` otherwise. The string shorthand `'zod'`
450
+ * maps to response parsing.
451
+ */
452
+ function resolveResponseValidator(validator) {
453
+ if (!validator) return null;
454
+ if (validator === "zod") return "zod";
455
+ return validator.response ?? null;
456
+ }
457
+ /**
458
+ * Resolves the zod schema name a generated client validates an error body with on the non-throw path.
459
+ * Uses the error-only `<operation>ErrorSchema` (the union of non-2xx statuses); returns `null` when the
460
+ * operation documents no error responses with a schema.
461
+ */
462
+ function buildZodErrorParse(node, zodResolver) {
463
+ if (!node.responses.some((res) => !isSuccessStatusCode(res.statusCode) && res.content?.some((entry) => entry.schema))) return null;
464
+ return zodResolver.response.error?.(node) || null;
465
+ }
466
+ //#endregion
467
+ //#region ../../internals/client/src/builders/validator.ts
468
+ /**
469
+ * Builds the validator-hook references for one operation. Request validation runs before the send;
470
+ * response validation runs on the success body only. Returns `null` references when the matching
471
+ * direction is disabled or the schema is absent.
472
+ */
473
+ function buildValidatorHooks({ node, validator, zodResolver }) {
474
+ const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
475
+ const parsesResponse = zodResolver && resolveResponseValidator(validator) === "zod";
476
+ const response = parsesResponse ? zodResolver.response.response(node) || null : null;
477
+ const error = parsesResponse ? buildZodErrorParse(node, zodResolver) : null;
478
+ const request = zodResolver && resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.response.body(node) || null : null;
479
+ return {
480
+ request,
481
+ response,
482
+ error,
483
+ importedZodNames: [
484
+ response,
485
+ error,
486
+ request
487
+ ].filter((name) => Boolean(name))
488
+ };
489
+ }
490
+ //#endregion
491
+ //#region ../../internals/client/src/builders/security.ts
492
+ function serializeAuth(auth) {
493
+ const parts = [`type: '${auth.type}'`];
494
+ if (auth.scheme) parts.push(`scheme: '${auth.scheme}'`);
495
+ if (auth.name) parts.push(`name: '${auth.name}'`);
496
+ if (auth.in) parts.push(`in: '${auth.in}'`);
497
+ return `{ ${parts.join(", ")} }`;
498
+ }
499
+ /**
500
+ * Maps an OpenAPI security scheme to the inline `Auth` object, or `null` when the runtime cannot
501
+ * place it (an unresolved `$ref`, or an `apiKey` without a name or outside `header` / `query` /
502
+ * `cookie`). `http` schemes other than `basic` are treated as bearer.
503
+ */
504
+ function resolveSecurityScheme(scheme) {
505
+ if (!scheme || "$ref" in scheme) return null;
506
+ if (scheme.type === "apiKey") {
507
+ if (!scheme.name || scheme.in !== "header" && scheme.in !== "query" && scheme.in !== "cookie") return null;
508
+ return {
509
+ type: "apiKey",
510
+ name: scheme.name,
511
+ in: scheme.in
512
+ };
513
+ }
514
+ if (scheme.type === "http") return {
515
+ type: "http",
516
+ scheme: scheme.scheme?.toLowerCase() === "basic" ? "basic" : "bearer"
517
+ };
518
+ if (scheme.type === "oauth2") return { type: "oauth2" };
519
+ if (scheme.type === "openIdConnect") return { type: "openIdConnect" };
520
+ return null;
521
+ }
522
+ /**
523
+ * Derives the per-operation security metadata from the OpenAPI document. The operation's own
524
+ * `security` overrides the global `security` (an explicit empty array disables auth), and every
525
+ * referenced scheme is resolved from `components.securitySchemes` into a flat, de-duplicated list of
526
+ * `Auth` objects the runtime walks in order.
527
+ *
528
+ * @example
529
+ * `getOperationSecurity({ document, method: 'POST', path: '/pet' })`
530
+ * `// [{ type: 'http', scheme: 'bearer' }]`
531
+ */
532
+ function getOperationSecurity({ document, method, path }) {
533
+ if (!document) return void 0;
534
+ const requirements = (document.paths?.[path]?.[method.toLowerCase()])?.security ?? document.security;
535
+ if (!requirements?.length) return void 0;
536
+ const definitions = document.components?.securitySchemes ?? {};
537
+ const security = [];
538
+ const seen = /* @__PURE__ */ new Set();
539
+ for (const requirement of requirements) for (const schemeName of Object.keys(requirement)) {
540
+ if (seen.has(schemeName)) continue;
541
+ seen.add(schemeName);
542
+ const auth = resolveSecurityScheme(definitions[schemeName]);
543
+ if (auth) security.push(auth);
544
+ }
545
+ return security.length ? security : void 0;
546
+ }
547
+ /**
548
+ * Serializes the per-operation security into the literal emitted on each generated call's `security`
549
+ * field. The runtime `resolveAuth` helper walks it, calling the configured `auth` resolver per entry.
550
+ *
551
+ * @example
552
+ * `buildSecurityMetadata({ security: [{ type: 'http', scheme: 'bearer' }] }) // "[{ type: 'http', scheme: 'bearer' }]"`
553
+ */
554
+ function buildSecurityMetadata({ security }) {
555
+ if (!security?.length) return null;
556
+ return `[${security.map(serializeAuth).join(", ")}]`;
557
+ }
558
+ //#endregion
441
559
  //#region ../../internals/client/src/builders/contentType.ts
442
560
  /**
443
561
  * Builds the `contentType` entry baked into the call config, or `null` for the JSON default.
@@ -530,74 +648,6 @@ function buildReturnStatement({ node, types, callConfig, returnType, throwOnErro
530
648
  })}>>)`;
531
649
  }
532
650
  //#endregion
533
- //#region ../../internals/client/src/builders/security.ts
534
- function serializeAuth(auth) {
535
- const parts = [`type: '${auth.type}'`];
536
- if (auth.scheme) parts.push(`scheme: '${auth.scheme}'`);
537
- if (auth.name) parts.push(`name: '${auth.name}'`);
538
- if (auth.in) parts.push(`in: '${auth.in}'`);
539
- return `{ ${parts.join(", ")} }`;
540
- }
541
- /**
542
- * Maps an OpenAPI security scheme to the inline `Auth` object, or `null` when the runtime cannot
543
- * place it (an unresolved `$ref`, or an `apiKey` without a name or outside `header` / `query` /
544
- * `cookie`). `http` schemes other than `basic` are treated as bearer.
545
- */
546
- function resolveSecurityScheme(scheme) {
547
- if (!scheme || "$ref" in scheme) return null;
548
- if (scheme.type === "apiKey") {
549
- if (!scheme.name || scheme.in !== "header" && scheme.in !== "query" && scheme.in !== "cookie") return null;
550
- return {
551
- type: "apiKey",
552
- name: scheme.name,
553
- in: scheme.in
554
- };
555
- }
556
- if (scheme.type === "http") return {
557
- type: "http",
558
- scheme: scheme.scheme?.toLowerCase() === "basic" ? "basic" : "bearer"
559
- };
560
- if (scheme.type === "oauth2") return { type: "oauth2" };
561
- if (scheme.type === "openIdConnect") return { type: "openIdConnect" };
562
- return null;
563
- }
564
- /**
565
- * Derives the per-operation security metadata from the OpenAPI document. The operation's own
566
- * `security` overrides the global `security` (an explicit empty array disables auth), and every
567
- * referenced scheme is resolved from `components.securitySchemes` into a flat, de-duplicated list of
568
- * `Auth` objects the runtime walks in order.
569
- *
570
- * @example
571
- * `getOperationSecurity({ document, method: 'POST', path: '/pet' })`
572
- * `// [{ type: 'http', scheme: 'bearer' }]`
573
- */
574
- function getOperationSecurity({ document, method, path }) {
575
- if (!document) return void 0;
576
- const requirements = (document.paths?.[path]?.[method.toLowerCase()])?.security ?? document.security;
577
- if (!requirements?.length) return void 0;
578
- const definitions = document.components?.securitySchemes ?? {};
579
- const security = [];
580
- const seen = /* @__PURE__ */ new Set();
581
- for (const requirement of requirements) for (const schemeName of Object.keys(requirement)) {
582
- if (seen.has(schemeName)) continue;
583
- seen.add(schemeName);
584
- const auth = resolveSecurityScheme(definitions[schemeName]);
585
- if (auth) security.push(auth);
586
- }
587
- return security.length ? security : void 0;
588
- }
589
- /**
590
- * Serializes the per-operation security into the literal emitted on each generated call's `security`
591
- * field. The runtime `resolveAuth` helper walks it, calling the configured `auth` resolver per entry.
592
- *
593
- * @example
594
- * `buildSecurityMetadata({ security: [{ type: 'http', scheme: 'bearer' }] }) // "[{ type: 'http', scheme: 'bearer' }]"`
595
- */
596
- function buildSecurityMetadata({ security }) {
597
- if (!security?.length) return null;
598
- return `[${security.map(serializeAuth).join(", ")}]`;
599
- }
600
- //#endregion
601
651
  //#region ../../internals/client/src/builders/signature.ts
602
652
  const declarationPrinter = functionPrinter({ mode: "declaration" });
603
653
  /**
@@ -627,161 +677,6 @@ function buildGroupedOptionsSignature({ node, types, returnType, throwOnErrorDef
627
677
  };
628
678
  }
629
679
  //#endregion
630
- //#region ../../internals/client/src/builders/validatorOptions.ts
631
- /**
632
- * Returns `true` when any direction of the validator uses zod (used for dependency checks).
633
- */
634
- function isValidatorEnabled(validator) {
635
- if (!validator) return false;
636
- if (validator === "zod") return true;
637
- return Boolean(validator.request || validator.response);
638
- }
639
- /**
640
- * Returns `'zod'` when request body parsing is enabled, `null` otherwise. The string shorthand
641
- * `'zod'` validates the response only, so it does not enable request parsing.
642
- */
643
- function resolveRequestValidator(validator) {
644
- if (!validator || validator === "zod") return null;
645
- return validator.request ?? null;
646
- }
647
- /**
648
- * Returns `'zod'` when query-parameters parsing is enabled, `null` otherwise. Only the object form
649
- * `{ request: 'zod' }` enables it.
650
- */
651
- function resolveQueryParamsValidator(validator) {
652
- if (!validator || validator === "zod") return null;
653
- return validator.request ?? null;
654
- }
655
- /**
656
- * Returns `'zod'` when response parsing is enabled, `null` otherwise. The string shorthand `'zod'`
657
- * maps to response parsing.
658
- */
659
- function resolveResponseValidator(validator) {
660
- if (!validator) return null;
661
- if (validator === "zod") return "zod";
662
- return validator.response ?? null;
663
- }
664
- /**
665
- * Resolves the zod expression a generated client validates a success response with. Only success
666
- * (2xx) bodies reach the parse under the throw-on-error contract, so the success-only
667
- * `<operation>ResponseSchema` is used; error bodies are never zod-parsed.
668
- */
669
- function buildZodResponseParse(node, zodResolver) {
670
- const name = zodResolver.response.response(node);
671
- return name ? {
672
- expression: name,
673
- importNames: [name]
674
- } : null;
675
- }
676
- /**
677
- * Resolves the zod expression a generated client validates an error body with on the non-throw path.
678
- * Uses the error-only `<operation>ErrorSchema` (the union of non-2xx statuses); returns `null` when the
679
- * operation documents no error responses with a schema.
680
- */
681
- function buildZodErrorParse(node, zodResolver) {
682
- if (!node.responses.some((res) => !isSuccessStatusCode(res.statusCode) && res.content?.some((entry) => entry.schema))) return null;
683
- const name = zodResolver.response.error?.(node);
684
- return name ? {
685
- expression: name,
686
- importNames: [name]
687
- } : null;
688
- }
689
- //#endregion
690
- //#region ../../internals/client/src/builders/validator.ts
691
- /**
692
- * Builds the validator-hook references for one operation. Request validation runs before the send;
693
- * response validation runs on the success body only. Returns `null` references when the matching
694
- * direction is disabled or the schema is absent.
695
- */
696
- function buildValidatorHooks({ node, validator, zodResolver }) {
697
- const importedZodNames = [];
698
- const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
699
- const zodRequestName = zodResolver && resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.response.body(node) : null;
700
- const request = zodRequestName ?? null;
701
- if (zodRequestName) importedZodNames.push(zodRequestName);
702
- const responseParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodResponseParse(node, zodResolver) : null;
703
- const response = responseParse ? responseParse.expression : null;
704
- if (responseParse) importedZodNames.push(...responseParse.importNames);
705
- const errorParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver) : null;
706
- const error = errorParse ? errorParse.expression : null;
707
- if (errorParse) importedZodNames.push(...errorParse.importNames);
708
- return {
709
- request,
710
- response,
711
- error,
712
- importedZodNames
713
- };
714
- }
715
- //#endregion
716
- //#region ../../internals/client/src/builders/sdkMethod.ts
717
- /**
718
- * Builds the call config literal forwarded to the contract client, mirroring the shared `Operation`
719
- * component: `{ method, url, security?, validator?, ...config }`. The `...config` spread carries every
720
- * per-call field. The plugin's `throwOnError` fallback is applied after the spread so it also wins
721
- * over a client instance's runtime config when the call omits an override.
722
- */
723
- function buildCallConfig({ node, validator, zodResolver, security, throwOnErrorDefault }) {
724
- const validators = buildValidatorHooks({
725
- node,
726
- validator,
727
- zodResolver
728
- });
729
- const validatorEntries = [validators.request ? `request: ${validators.request}` : null, validators.response ? `response: ${validators.response}` : null].filter(Boolean);
730
- const validatorLiteral = validatorEntries.length ? `validator: { ${validatorEntries.join(", ")} }` : null;
731
- const securityLiteral = buildSecurityMetadata({ security });
732
- const { literal: contentTypeLiteral } = buildContentType({ node });
733
- return `{ ${[
734
- `method: '${node.method.toUpperCase()}'`,
735
- `url: '${node.path}'`,
736
- securityLiteral ? `security: ${securityLiteral}` : null,
737
- validatorLiteral,
738
- contentTypeLiteral,
739
- "...config",
740
- `throwOnError: config.throwOnError ?? ${throwOnErrorDefault}`
741
- ].filter(Boolean).join(", ")} }`;
742
- }
743
- /**
744
- * Builds a single instance method for a generated SDK class. The body forwards the single grouped
745
- * `options` object to the instance's own client (`this.client`, built once in the constructor) and
746
- * returns the `Unwrappable<RequestResult>`. A per-call `options.client` still overrides the
747
- * instance client, so one operation can be routed to a different environment without a new
748
- * instance.
749
- */
750
- function buildSdkMethod({ node, name, types, zodResolver, validator, security, returnType, throwOnErrorDefault }) {
751
- if (!ast.isHttpOperationNode(node)) return "";
752
- const signature = buildGroupedOptionsSignature({
753
- node,
754
- types,
755
- returnType,
756
- throwOnErrorDefault
757
- });
758
- const returnStatement = buildReturnStatement({
759
- node,
760
- types,
761
- callConfig: buildCallConfig({
762
- node,
763
- validator,
764
- zodResolver,
765
- security,
766
- throwOnErrorDefault
767
- }),
768
- returnType,
769
- throwOnErrorDefault
770
- });
771
- const generics = signature.generics.length ? `<${signature.generics.join(", ")}>` : "";
772
- const jsdoc = buildJSDoc(buildOperationComments(node, {
773
- link: "urlPath",
774
- linkPosition: "beforeDeprecated",
775
- splitLines: true
776
- }));
777
- const methodBody = [
778
- buildContentType({ node }).merge ? "const { client: request = this.client, contentType, ...config } = options" : "const { client: request = this.client, ...config } = options",
779
- "",
780
- returnStatement
781
- ].map((line) => line ? ` ${line}` : "").join("\n");
782
- return `${jsdoc} public ${name}${generics}(${signature.paramsSignature}): ${signature.returnType} {\n${methodBody}\n }`;
783
- }
784
- //#endregion
785
680
  //#region ../../internals/client/src/builders/styles.ts
786
681
  /**
787
682
  * Renders a parameter name as an object-literal key, quoted when it is not a bare identifier.
@@ -842,7 +737,7 @@ function buildStyles({ node }) {
842
737
  * type, signature, and call config are built with the AST factory, and only the jsx-renderer emits
843
738
  * the source.
844
739
  */
845
- function Operation({ name, node, types, zodResolver, validator, returnType, throwOnErrorDefault, security, isExportable = true, isIndexable = true }) {
740
+ function Operation({ name, node, types, zodResolver, validator, returnType, throwOnErrorDefault, security }) {
846
741
  if (!ast.isHttpOperationNode(node)) return null;
847
742
  const signature = buildGroupedOptionsSignature({
848
743
  node,
@@ -889,11 +784,11 @@ function Operation({ name, node, types, zodResolver, validator, returnType, thro
889
784
  });
890
785
  return /* @__PURE__ */ jsx(File.Source, {
891
786
  name,
892
- isExportable,
893
- isIndexable,
787
+ isExportable: true,
788
+ isIndexable: true,
894
789
  children: /* @__PURE__ */ jsxs(Function, {
895
790
  name,
896
- export: isExportable,
791
+ export: true,
897
792
  generics: signature.generics,
898
793
  params: signature.paramsSignature,
899
794
  returnType: functionReturnType,
@@ -911,62 +806,24 @@ function Operation({ name, node, types, zodResolver, validator, returnType, thro
911
806
  });
912
807
  }
913
808
  //#endregion
914
- //#region ../../internals/client/src/components/SdkClient.tsx
915
- /**
916
- * Renders one instance class per tag with one method per operation. The constructor takes a client
917
- * config object and builds its own client through `createClient`, so each environment is a separate
918
- * instance: `const api = new PetClient({ baseURL }); api.getPetById(...)`. A per-call `client` option
919
- * still overrides the instance client for a one-off call.
920
- */
921
- function SdkClient({ name, isExportable = true, isIndexable = true, operations, validator, returnType, throwOnErrorDefault, children }) {
922
- const methods = operations.map(({ node, name: methodName, types, zodResolver, security }) => buildSdkMethod({
923
- node,
924
- name: methodName,
925
- types,
926
- zodResolver,
927
- validator,
928
- security,
929
- returnType,
930
- throwOnErrorDefault
931
- }));
932
- const classCode = `export class ${name} {\n${[
933
- " private readonly client: ClientInstance",
934
- "",
935
- " constructor(config: ClientConfig = {}) {",
936
- " this.client = createClient(config)",
937
- " }"
938
- ].join("\n")}\n\n${methods.join("\n\n")}\n}`;
939
- return /* @__PURE__ */ jsxs(File.Source, {
940
- name,
941
- isExportable,
942
- isIndexable,
943
- children: [classCode, children]
944
- });
945
- }
946
- //#endregion
947
809
  //#region ../../internals/client/src/resolveOperationTypes.ts
948
810
  /**
949
- * Warning a client generator reports when neither plugin can supply the operation types, so the
950
- * pipeline says why nothing was generated instead of silently emitting no files.
951
- */
952
- const MISSING_OPERATION_TYPES_WARNING = "Skipped: no operation types available. Add `pluginTs()`, or `pluginZod({ inferred: true })` to type the client from its zod schemas.";
953
- /**
954
811
  * Picks the plugin a generated client takes its `Options` and `Responses` types from.
955
812
  *
956
813
  * `plugin-ts` wins whenever it is in the pipeline, so existing configs keep the types they generate
957
814
  * today. Without it, `pluginZod({ inferred: true })` takes over: its inferred options and responses
958
815
  * types are built from the same shared schemas as the `plugin-ts` ones, so a client typed off
959
816
  * `z.infer` carries what the codecs decode to (a `Temporal` instance, a `Date`) rather than the raw
960
- * wire type. Returns `null` when neither is available, leaving the caller nothing to type against.
817
+ * wire type. Throws `KUBB_MISSING_DEPENDENCY` for `pluginName` when neither is available, since the
818
+ * client has nothing to type against.
961
819
  *
962
820
  * @example
963
821
  * ```ts
964
- * const types = resolveOperationTypes(ctx.driver)
965
- * if (!types) return null
822
+ * const types = resolveOperationTypes(ctx.driver, ctx.plugin.name)
966
823
  * types.response.options(node) // 'GetPetByIdOptions', or 'GetPetByIdOptionsSchemaType' from zod
967
824
  * ```
968
825
  */
969
- function resolveOperationTypes(driver) {
826
+ function resolveOperationTypes(driver, pluginName) {
970
827
  const pluginTs = driver.getPlugin(pluginTsName);
971
828
  if (pluginTs) {
972
829
  const resolver = driver.getResolver(pluginTsName);
@@ -978,7 +835,14 @@ function resolveOperationTypes(driver) {
978
835
  };
979
836
  }
980
837
  const pluginZod = driver.getPlugin(pluginZodName);
981
- if (!pluginZod?.options?.inferred) return null;
838
+ if (!pluginZod?.options?.inferred) throw new Diagnostics.Error({
839
+ code: Diagnostics.code.missingDependency,
840
+ severity: "error",
841
+ message: `Plugin "${pluginName}" has no source for operation types.`,
842
+ help: `Add \`pluginTs()\` before "${pluginName}", or add \`pluginZod({ inferred: true })\`.`,
843
+ location: { kind: "config" },
844
+ plugin: pluginName
845
+ });
982
846
  const resolver = driver.getResolver(pluginZodName);
983
847
  return {
984
848
  response: {
@@ -1008,20 +872,15 @@ function createClientGenerator(name, resolveClientPath) {
1008
872
  if (!ast.isHttpOperationNode(node)) return null;
1009
873
  const { config, driver, resolver, root } = ctx;
1010
874
  const { output, validator, returnType, throwOnErrorDefault, group } = ctx.options;
1011
- const types = resolveOperationTypes(driver);
1012
- if (!types) {
1013
- ctx.warn(MISSING_OPERATION_TYPES_WARNING);
1014
- return null;
1015
- }
875
+ const types = resolveOperationTypes(driver, ctx.plugin.name);
1016
876
  const pluginZod = resolveResponseValidator(validator) === "zod" || resolveRequestValidator(validator) === "zod" ? driver.getPlugin(pluginZodName) : null;
1017
877
  const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null;
1018
- const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
1019
878
  const importedTypeNames = [types.response.options(node), types.response.responses(node)];
1020
- const importedZodNames = zodResolver ? [
1021
- resolveResponseValidator(validator) === "zod" ? zodResolver.response.response?.(node) : null,
1022
- resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
1023
- resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.response.body?.(node) : null
1024
- ].filter((name) => Boolean(name)) : [];
879
+ const importedZodNames = buildValidatorHooks({
880
+ node,
881
+ validator,
882
+ zodResolver
883
+ }).importedZodNames;
1025
884
  const meta = {
1026
885
  name: resolver.name(node.operationId),
1027
886
  file: resolver.file({
@@ -1133,13 +992,106 @@ function createClientGenerator(name, resolveClientPath) {
1133
992
  });
1134
993
  }
1135
994
  //#endregion
995
+ //#region ../../internals/client/src/components/SdkClient.tsx
996
+ /**
997
+ * Builds the call config literal forwarded to the contract client; the `throwOnError` fallback is applied after the `...config` spread.
998
+ */
999
+ function buildCallConfig({ node, validator, zodResolver, security, throwOnErrorDefault }) {
1000
+ const validators = buildValidatorHooks({
1001
+ node,
1002
+ validator,
1003
+ zodResolver
1004
+ });
1005
+ const validatorEntries = [validators.request ? `request: ${validators.request}` : null, validators.response ? `response: ${validators.response}` : null].filter(Boolean);
1006
+ const validatorLiteral = validatorEntries.length ? `validator: { ${validatorEntries.join(", ")} }` : null;
1007
+ const securityLiteral = buildSecurityMetadata({ security });
1008
+ const { literal: contentTypeLiteral } = buildContentType({ node });
1009
+ return `{ ${[
1010
+ `method: '${node.method.toUpperCase()}'`,
1011
+ `url: '${node.path}'`,
1012
+ securityLiteral ? `security: ${securityLiteral}` : null,
1013
+ validatorLiteral,
1014
+ contentTypeLiteral,
1015
+ "...config",
1016
+ `throwOnError: config.throwOnError ?? ${throwOnErrorDefault}`
1017
+ ].filter(Boolean).join(", ")} }`;
1018
+ }
1019
+ /**
1020
+ * Builds one instance method of a generated SDK class; a per-call `options.client` overrides `this.client`.
1021
+ */
1022
+ function buildSdkMethod({ node, name, types, zodResolver, validator, security, returnType, throwOnErrorDefault }) {
1023
+ if (!ast.isHttpOperationNode(node)) return "";
1024
+ const signature = buildGroupedOptionsSignature({
1025
+ node,
1026
+ types,
1027
+ returnType,
1028
+ throwOnErrorDefault
1029
+ });
1030
+ const returnStatement = buildReturnStatement({
1031
+ node,
1032
+ types,
1033
+ callConfig: buildCallConfig({
1034
+ node,
1035
+ validator,
1036
+ zodResolver,
1037
+ security,
1038
+ throwOnErrorDefault
1039
+ }),
1040
+ returnType,
1041
+ throwOnErrorDefault
1042
+ });
1043
+ const generics = signature.generics.length ? `<${signature.generics.join(", ")}>` : "";
1044
+ const jsdoc = buildJSDoc(buildOperationComments(node, {
1045
+ link: "urlPath",
1046
+ linkPosition: "beforeDeprecated",
1047
+ splitLines: true
1048
+ }));
1049
+ const methodBody = [
1050
+ buildContentType({ node }).merge ? "const { client: request = this.client, contentType, ...config } = options" : "const { client: request = this.client, ...config } = options",
1051
+ "",
1052
+ returnStatement
1053
+ ].map((line) => line ? ` ${line}` : "").join("\n");
1054
+ return `${jsdoc} public ${name}${generics}(${signature.paramsSignature}): ${signature.returnType} {\n${methodBody}\n }`;
1055
+ }
1056
+ /**
1057
+ * Renders one instance class per tag with one method per operation. The constructor takes a client
1058
+ * config object and builds its own client through `createClient`, so each environment is a separate
1059
+ * instance: `const api = new PetClient({ baseURL }); api.getPetById(...)`. A per-call `client` option
1060
+ * still overrides the instance client for a one-off call.
1061
+ */
1062
+ function SdkClient({ name, operations, validator, returnType, throwOnErrorDefault }) {
1063
+ const methods = operations.map(({ node, name: methodName, types, zodResolver, security }) => buildSdkMethod({
1064
+ node,
1065
+ name: methodName,
1066
+ types,
1067
+ zodResolver,
1068
+ validator,
1069
+ security,
1070
+ returnType,
1071
+ throwOnErrorDefault
1072
+ }));
1073
+ const classCode = `export class ${name} {\n${[
1074
+ " private readonly client: ClientInstance",
1075
+ "",
1076
+ " constructor(config: ClientConfig = {}) {",
1077
+ " this.client = createClient(config)",
1078
+ " }"
1079
+ ].join("\n")}\n\n${methods.join("\n\n")}\n}`;
1080
+ return /* @__PURE__ */ jsx(File.Source, {
1081
+ name,
1082
+ isExportable: true,
1083
+ isIndexable: true,
1084
+ children: classCode
1085
+ });
1086
+ }
1087
+ //#endregion
1136
1088
  //#region ../../internals/client/src/components/SdkFacade.tsx
1137
1089
  /**
1138
1090
  * Renders a composed root SDK class that instantiates every tag client from one shared config, so
1139
1091
  * `new PetStore({ baseURL }).petClient.getPetById(...)` reaches an operation through a single entry
1140
1092
  * point bound to one environment. The per-tag clients are read-only fields built in the constructor.
1141
1093
  */
1142
- function SdkFacade({ name, isExportable = true, isIndexable = true, members, children }) {
1094
+ function SdkFacade({ name, members }) {
1143
1095
  const fields = members.map((member) => ` readonly ${member.propName}: ${member.className}`);
1144
1096
  const assignments = members.map((member) => ` this.${member.propName} = new ${member.className}(config)`);
1145
1097
  const classCode = `export class ${name} {\n${[
@@ -1149,11 +1101,11 @@ function SdkFacade({ name, isExportable = true, isIndexable = true, members, chi
1149
1101
  ...assignments,
1150
1102
  " }"
1151
1103
  ].join("\n")}\n}`;
1152
- return /* @__PURE__ */ jsxs(File.Source, {
1104
+ return /* @__PURE__ */ jsx(File.Source, {
1153
1105
  name,
1154
- isExportable,
1155
- isIndexable,
1156
- children: [classCode, children]
1106
+ isExportable: true,
1107
+ isIndexable: true,
1108
+ children: classCode
1157
1109
  });
1158
1110
  }
1159
1111
  //#endregion
@@ -1162,13 +1114,7 @@ function resolveTypeImportNames(node, types) {
1162
1114
  return [types.response.options(node), types.response.responses(node)];
1163
1115
  }
1164
1116
  function resolveZodImportNames(node, zodResolver, validator) {
1165
- const { query: queryParams } = getOperationParameters(node);
1166
- return [
1167
- resolveResponseValidator(validator) === "zod" ? zodResolver.response.response(node) : null,
1168
- resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
1169
- resolveRequestValidator(validator) === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.response.body(node) : null,
1170
- resolveQueryParamsValidator(validator) === "zod" && queryParams.length > 0 ? zodResolver.param.query(node, queryParams[0]) : null
1171
- ].filter((n) => Boolean(n));
1117
+ 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));
1172
1118
  }
1173
1119
  /**
1174
1120
  * Groups operations into one controller per tag. Operations without a tag fall back to a single
@@ -1275,12 +1221,7 @@ function createSdkGenerator() {
1275
1221
  location: { kind: "config" },
1276
1222
  plugin: ctx.plugin.name
1277
1223
  });
1278
- const types = resolveOperationTypes(ctx.driver);
1279
- if (!types) {
1280
- ctx.warn(MISSING_OPERATION_TYPES_WARNING);
1281
- return null;
1282
- }
1283
- const controllers = buildControllers(nodes, ctx, types);
1224
+ const controllers = buildControllers(nodes, ctx, resolveOperationTypes(ctx.driver, ctx.plugin.name));
1284
1225
  const clientPath = path.resolve(root, ".kubb/client.ts");
1285
1226
  const banner = (file) => resolver.default.banner(ctx.meta, {
1286
1227
  output,
@@ -1461,7 +1402,7 @@ var compatibility = {
1461
1402
  };
1462
1403
  //#endregion
1463
1404
  //#region package.json
1464
- var version = "5.6.0";
1405
+ var version = "5.6.2";
1465
1406
  //#endregion
1466
1407
  //#region src/templates.ts
1467
1408
  /** Absolute path to the fetch client template, copied into `.kubb/client.ts`. */
@@ -1509,10 +1450,10 @@ const pluginFetch = defineExtension({
1509
1450
  version,
1510
1451
  compatibility
1511
1452
  },
1512
- dependencies: {
1453
+ dependencies: (options) => ({
1513
1454
  [pluginTsName]: { optional: true },
1514
- [pluginZodName]: { optional: true }
1515
- },
1455
+ [pluginZodName]: { optional: !isValidatorEnabled(options.validator) }
1456
+ }),
1516
1457
  setup(options) {
1517
1458
  const config = useConfig();
1518
1459
  const { output = {
@@ -1535,6 +1476,32 @@ const pluginFetch = defineExtension({
1535
1476
  } : void 0,
1536
1477
  resolver: userResolver ? Resolver.merge(resolverClient, userResolver) : resolverClient
1537
1478
  };
1479
+ const allowedReturnTypes = [
1480
+ "full",
1481
+ "data",
1482
+ "plain"
1483
+ ];
1484
+ assertPluginOption({
1485
+ plugin: pluginFetchName,
1486
+ option: "returnType",
1487
+ value: returnType,
1488
+ allowed: allowedReturnTypes
1489
+ });
1490
+ for (const [index, entry] of override.entries()) {
1491
+ if (entry.options.returnType === void 0) continue;
1492
+ assertPluginOption({
1493
+ plugin: pluginFetchName,
1494
+ option: `override[${index}].options.returnType`,
1495
+ value: entry.options.returnType,
1496
+ allowed: allowedReturnTypes
1497
+ });
1498
+ }
1499
+ if (resolved.sdk) assertPluginOption({
1500
+ plugin: pluginFetchName,
1501
+ option: "sdk.mode",
1502
+ value: resolved.sdk.mode,
1503
+ allowed: ["tag", "flat"]
1504
+ });
1538
1505
  setResolver(resolved.resolver);
1539
1506
  setMacros([...defaultMacros, ...options.macros ?? []]);
1540
1507
  addGenerator(resolved.sdk ? createSdkGenerator() : clientGenerator);