@kubb/plugin-client 5.5.11 → 5.5.12

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,28 +1,10 @@
1
1
  import "./rolldown-runtime-C0LytTxp.js";
2
- import { Resolver, Url, addGenerator, ast, createResolver, defineExtension, defineGenerator, macroSimplifyUnion, setMacros, setResolver } from "kubb/kit";
2
+ import { Diagnostics, Resolver, Url, addGenerator, ast, createResolver, defineExtension, defineGenerator, macroSimplifyUnion, setMacros, setResolver } from "kubb/kit";
3
3
  import path from "node:path";
4
4
  import { createFunctionParameter, createFunctionParameters, functionPrinter, pluginTsName } from "@kubb/plugin-ts";
5
5
  import { pluginZodName } from "@kubb/plugin-zod";
6
6
  import { File, Function, jsxRenderer } from "kubb/jsx";
7
7
  import { jsx, jsxs } from "kubb/jsx/jsx-runtime";
8
- //#region ../../internals/shared/src/params.ts
9
- /**
10
- * Drops parameters that share the same name, keeping the first.
11
- *
12
- * A malformed spec can declare the same parameter name twice within one `in` location. Both would
13
- * resolve to the same output property, so emitting both would yield an object type with a duplicate
14
- * member, which TypeScript rejects. This is a defensive guard against that case, not a casing guard:
15
- * parameter names flow through unchanged, so no two distinct names ever collide here anymore.
16
- */
17
- function dedupeParams(params) {
18
- const seen = /* @__PURE__ */ new Set();
19
- return params.filter((param) => {
20
- if (seen.has(param.name)) return false;
21
- seen.add(param.name);
22
- return true;
23
- });
24
- }
25
- //#endregion
26
8
  //#region ../../internals/shared/src/operation.ts
27
9
  /**
28
10
  * Builds the `ResolverFileParams` every operation generator passes to
@@ -184,11 +166,19 @@ const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
184
166
  function getOperationParameters(node) {
185
167
  const cached = operationParameterGroupsByNode.get(node);
186
168
  if (cached) return cached;
169
+ const group = (location) => {
170
+ const seen = /* @__PURE__ */ new Set();
171
+ return node.parameters.filter((param) => {
172
+ if (param.in !== location || seen.has(param.name)) return false;
173
+ seen.add(param.name);
174
+ return true;
175
+ });
176
+ };
187
177
  const groups = {
188
- path: dedupeParams(node.parameters.filter((param) => param.in === "path")),
189
- query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
190
- header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
191
- cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
178
+ path: group("path"),
179
+ query: group("query"),
180
+ header: group("header"),
181
+ cookie: group("cookie")
192
182
  };
193
183
  operationParameterGroupsByNode.set(node, groups);
194
184
  return groups;
@@ -427,7 +417,135 @@ var compatibility = {
427
417
  };
428
418
  //#endregion
429
419
  //#region package.json
430
- var version = "5.5.11";
420
+ var version = "5.5.12";
421
+ //#endregion
422
+ //#region ../../internals/client/src/builders/validatorOptions.ts
423
+ /**
424
+ * Returns `true` when any direction of the validator uses zod (used for dependency checks).
425
+ */
426
+ function isValidatorEnabled(validator) {
427
+ if (!validator) return false;
428
+ if (validator === "zod") return true;
429
+ return Boolean(validator.request || validator.response);
430
+ }
431
+ /**
432
+ * Returns `'zod'` when request body parsing is enabled, `null` otherwise. The string shorthand
433
+ * `'zod'` validates the response only, so it does not enable request parsing.
434
+ */
435
+ function resolveRequestValidator(validator) {
436
+ if (!validator || validator === "zod") return null;
437
+ return validator.request ?? null;
438
+ }
439
+ /**
440
+ * Returns `'zod'` when response parsing is enabled, `null` otherwise. The string shorthand `'zod'`
441
+ * maps to response parsing.
442
+ */
443
+ function resolveResponseValidator(validator) {
444
+ if (!validator) return null;
445
+ if (validator === "zod") return "zod";
446
+ return validator.response ?? null;
447
+ }
448
+ /**
449
+ * Resolves the zod schema name a generated client validates an error body with on the non-throw path.
450
+ * Uses the error-only `<operation>ErrorSchema` (the union of non-2xx statuses); returns `null` when the
451
+ * operation documents no error responses with a schema.
452
+ */
453
+ function buildZodErrorParse(node, zodResolver) {
454
+ if (!node.responses.some((res) => !isSuccessStatusCode(res.statusCode) && res.content?.some((entry) => entry.schema))) return null;
455
+ return zodResolver.response.error?.(node) || null;
456
+ }
457
+ //#endregion
458
+ //#region ../../internals/client/src/builders/validator.ts
459
+ /**
460
+ * Builds the validator-hook references for one operation. Request validation runs before the send;
461
+ * response validation runs on the success body only. Returns `null` references when the matching
462
+ * direction is disabled or the schema is absent.
463
+ */
464
+ function buildValidatorHooks({ node, validator, zodResolver }) {
465
+ const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
466
+ const parsesResponse = zodResolver && resolveResponseValidator(validator) === "zod";
467
+ const response = parsesResponse ? zodResolver.response.response(node) || null : null;
468
+ const error = parsesResponse ? buildZodErrorParse(node, zodResolver) : null;
469
+ const request = zodResolver && resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.response.body(node) || null : null;
470
+ return {
471
+ request,
472
+ response,
473
+ error,
474
+ importedZodNames: [
475
+ response,
476
+ error,
477
+ request
478
+ ].filter((name) => Boolean(name))
479
+ };
480
+ }
481
+ //#endregion
482
+ //#region ../../internals/client/src/builders/security.ts
483
+ function serializeAuth(auth) {
484
+ const parts = [`type: '${auth.type}'`];
485
+ if (auth.scheme) parts.push(`scheme: '${auth.scheme}'`);
486
+ if (auth.name) parts.push(`name: '${auth.name}'`);
487
+ if (auth.in) parts.push(`in: '${auth.in}'`);
488
+ return `{ ${parts.join(", ")} }`;
489
+ }
490
+ /**
491
+ * Maps an OpenAPI security scheme to the inline `Auth` object, or `null` when the runtime cannot
492
+ * place it (an unresolved `$ref`, or an `apiKey` without a name or outside `header` / `query` /
493
+ * `cookie`). `http` schemes other than `basic` are treated as bearer.
494
+ */
495
+ function resolveSecurityScheme(scheme) {
496
+ if (!scheme || "$ref" in scheme) return null;
497
+ if (scheme.type === "apiKey") {
498
+ if (!scheme.name || scheme.in !== "header" && scheme.in !== "query" && scheme.in !== "cookie") return null;
499
+ return {
500
+ type: "apiKey",
501
+ name: scheme.name,
502
+ in: scheme.in
503
+ };
504
+ }
505
+ if (scheme.type === "http") return {
506
+ type: "http",
507
+ scheme: scheme.scheme?.toLowerCase() === "basic" ? "basic" : "bearer"
508
+ };
509
+ if (scheme.type === "oauth2") return { type: "oauth2" };
510
+ if (scheme.type === "openIdConnect") return { type: "openIdConnect" };
511
+ return null;
512
+ }
513
+ /**
514
+ * Derives the per-operation security metadata from the OpenAPI document. The operation's own
515
+ * `security` overrides the global `security` (an explicit empty array disables auth), and every
516
+ * referenced scheme is resolved from `components.securitySchemes` into a flat, de-duplicated list of
517
+ * `Auth` objects the runtime walks in order.
518
+ *
519
+ * @example
520
+ * `getOperationSecurity({ document, method: 'POST', path: '/pet' })`
521
+ * `// [{ type: 'http', scheme: 'bearer' }]`
522
+ */
523
+ function getOperationSecurity({ document, method, path }) {
524
+ if (!document) return void 0;
525
+ const requirements = (document.paths?.[path]?.[method.toLowerCase()])?.security ?? document.security;
526
+ if (!requirements?.length) return void 0;
527
+ const definitions = document.components?.securitySchemes ?? {};
528
+ const security = [];
529
+ const seen = /* @__PURE__ */ new Set();
530
+ for (const requirement of requirements) for (const schemeName of Object.keys(requirement)) {
531
+ if (seen.has(schemeName)) continue;
532
+ seen.add(schemeName);
533
+ const auth = resolveSecurityScheme(definitions[schemeName]);
534
+ if (auth) security.push(auth);
535
+ }
536
+ return security.length ? security : void 0;
537
+ }
538
+ /**
539
+ * Serializes the per-operation security into the literal emitted on each generated call's `security`
540
+ * field. The runtime `resolveAuth` helper walks it, calling the configured `auth` resolver per entry.
541
+ *
542
+ * @example
543
+ * `buildSecurityMetadata({ security: [{ type: 'http', scheme: 'bearer' }] }) // "[{ type: 'http', scheme: 'bearer' }]"`
544
+ */
545
+ function buildSecurityMetadata({ security }) {
546
+ if (!security?.length) return null;
547
+ return `[${security.map(serializeAuth).join(", ")}]`;
548
+ }
431
549
  //#endregion
432
550
  //#region ../../internals/client/src/builders/contentType.ts
433
551
  /**
@@ -521,74 +639,6 @@ function buildReturnStatement({ node, types, callConfig, returnType, throwOnErro
521
639
  })}>>)`;
522
640
  }
523
641
  //#endregion
524
- //#region ../../internals/client/src/builders/security.ts
525
- function serializeAuth(auth) {
526
- const parts = [`type: '${auth.type}'`];
527
- if (auth.scheme) parts.push(`scheme: '${auth.scheme}'`);
528
- if (auth.name) parts.push(`name: '${auth.name}'`);
529
- if (auth.in) parts.push(`in: '${auth.in}'`);
530
- return `{ ${parts.join(", ")} }`;
531
- }
532
- /**
533
- * Maps an OpenAPI security scheme to the inline `Auth` object, or `null` when the runtime cannot
534
- * place it (an unresolved `$ref`, or an `apiKey` without a name or outside `header` / `query` /
535
- * `cookie`). `http` schemes other than `basic` are treated as bearer.
536
- */
537
- function resolveSecurityScheme(scheme) {
538
- if (!scheme || "$ref" in scheme) return null;
539
- if (scheme.type === "apiKey") {
540
- if (!scheme.name || scheme.in !== "header" && scheme.in !== "query" && scheme.in !== "cookie") return null;
541
- return {
542
- type: "apiKey",
543
- name: scheme.name,
544
- in: scheme.in
545
- };
546
- }
547
- if (scheme.type === "http") return {
548
- type: "http",
549
- scheme: scheme.scheme?.toLowerCase() === "basic" ? "basic" : "bearer"
550
- };
551
- if (scheme.type === "oauth2") return { type: "oauth2" };
552
- if (scheme.type === "openIdConnect") return { type: "openIdConnect" };
553
- return null;
554
- }
555
- /**
556
- * Derives the per-operation security metadata from the OpenAPI document. The operation's own
557
- * `security` overrides the global `security` (an explicit empty array disables auth), and every
558
- * referenced scheme is resolved from `components.securitySchemes` into a flat, de-duplicated list of
559
- * `Auth` objects the runtime walks in order.
560
- *
561
- * @example
562
- * `getOperationSecurity({ document, method: 'POST', path: '/pet' })`
563
- * `// [{ type: 'http', scheme: 'bearer' }]`
564
- */
565
- function getOperationSecurity({ document, method, path }) {
566
- if (!document) return void 0;
567
- const requirements = (document.paths?.[path]?.[method.toLowerCase()])?.security ?? document.security;
568
- if (!requirements?.length) return void 0;
569
- const definitions = document.components?.securitySchemes ?? {};
570
- const security = [];
571
- const seen = /* @__PURE__ */ new Set();
572
- for (const requirement of requirements) for (const schemeName of Object.keys(requirement)) {
573
- if (seen.has(schemeName)) continue;
574
- seen.add(schemeName);
575
- const auth = resolveSecurityScheme(definitions[schemeName]);
576
- if (auth) security.push(auth);
577
- }
578
- return security.length ? security : void 0;
579
- }
580
- /**
581
- * Serializes the per-operation security into the literal emitted on each generated call's `security`
582
- * field. The runtime `resolveAuth` helper walks it, calling the configured `auth` resolver per entry.
583
- *
584
- * @example
585
- * `buildSecurityMetadata({ security: [{ type: 'http', scheme: 'bearer' }] }) // "[{ type: 'http', scheme: 'bearer' }]"`
586
- */
587
- function buildSecurityMetadata({ security }) {
588
- if (!security?.length) return null;
589
- return `[${security.map(serializeAuth).join(", ")}]`;
590
- }
591
- //#endregion
592
642
  //#region ../../internals/client/src/builders/signature.ts
593
643
  const declarationPrinter = functionPrinter({ mode: "declaration" });
594
644
  /**
@@ -618,76 +668,6 @@ function buildGroupedOptionsSignature({ node, types, returnType, throwOnErrorDef
618
668
  };
619
669
  }
620
670
  //#endregion
621
- //#region ../../internals/client/src/builders/validatorOptions.ts
622
- /**
623
- * Returns `'zod'` when request body parsing is enabled, `null` otherwise. The string shorthand
624
- * `'zod'` validates the response only, so it does not enable request parsing.
625
- */
626
- function resolveRequestValidator(validator) {
627
- if (!validator || validator === "zod") return null;
628
- return validator.request ?? null;
629
- }
630
- /**
631
- * Returns `'zod'` when response parsing is enabled, `null` otherwise. The string shorthand `'zod'`
632
- * maps to response parsing.
633
- */
634
- function resolveResponseValidator(validator) {
635
- if (!validator) return null;
636
- if (validator === "zod") return "zod";
637
- return validator.response ?? null;
638
- }
639
- /**
640
- * Resolves the zod expression a generated client validates a success response with. Only success
641
- * (2xx) bodies reach the parse under the throw-on-error contract, so the success-only
642
- * `<operation>ResponseSchema` is used; error bodies are never zod-parsed.
643
- */
644
- function buildZodResponseParse(node, zodResolver) {
645
- const name = zodResolver.response.response(node);
646
- return name ? {
647
- expression: name,
648
- importNames: [name]
649
- } : null;
650
- }
651
- /**
652
- * Resolves the zod expression a generated client validates an error body with on the non-throw path.
653
- * Uses the error-only `<operation>ErrorSchema` (the union of non-2xx statuses); returns `null` when the
654
- * operation documents no error responses with a schema.
655
- */
656
- function buildZodErrorParse(node, zodResolver) {
657
- if (!node.responses.some((res) => !isSuccessStatusCode(res.statusCode) && res.content?.some((entry) => entry.schema))) return null;
658
- const name = zodResolver.response.error?.(node);
659
- return name ? {
660
- expression: name,
661
- importNames: [name]
662
- } : null;
663
- }
664
- //#endregion
665
- //#region ../../internals/client/src/builders/validator.ts
666
- /**
667
- * Builds the validator-hook references for one operation. Request validation runs before the send;
668
- * response validation runs on the success body only. Returns `null` references when the matching
669
- * direction is disabled or the schema is absent.
670
- */
671
- function buildValidatorHooks({ node, validator, zodResolver }) {
672
- const importedZodNames = [];
673
- const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
674
- const zodRequestName = zodResolver && resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.response.body(node) : null;
675
- const request = zodRequestName ?? null;
676
- if (zodRequestName) importedZodNames.push(zodRequestName);
677
- const responseParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodResponseParse(node, zodResolver) : null;
678
- const response = responseParse ? responseParse.expression : null;
679
- if (responseParse) importedZodNames.push(...responseParse.importNames);
680
- const errorParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver) : null;
681
- const error = errorParse ? errorParse.expression : null;
682
- if (errorParse) importedZodNames.push(...errorParse.importNames);
683
- return {
684
- request,
685
- response,
686
- error,
687
- importedZodNames
688
- };
689
- }
690
- //#endregion
691
671
  //#region ../../internals/client/src/builders/styles.ts
692
672
  /**
693
673
  * Renders a parameter name as an object-literal key, quoted when it is not a bare identifier.
@@ -748,7 +728,7 @@ function buildStyles({ node }) {
748
728
  * type, signature, and call config are built with the AST factory, and only the jsx-renderer emits
749
729
  * the source.
750
730
  */
751
- function Operation({ name, node, types, zodResolver, validator, returnType, throwOnErrorDefault, security, isExportable = true, isIndexable = true }) {
731
+ function Operation({ name, node, types, zodResolver, validator, returnType, throwOnErrorDefault, security }) {
752
732
  if (!ast.isHttpOperationNode(node)) return null;
753
733
  const signature = buildGroupedOptionsSignature({
754
734
  node,
@@ -795,11 +775,11 @@ function Operation({ name, node, types, zodResolver, validator, returnType, thro
795
775
  });
796
776
  return /* @__PURE__ */ jsx(File.Source, {
797
777
  name,
798
- isExportable,
799
- isIndexable,
778
+ isExportable: true,
779
+ isIndexable: true,
800
780
  children: /* @__PURE__ */ jsxs(Function, {
801
781
  name,
802
- export: isExportable,
782
+ export: true,
803
783
  generics: signature.generics,
804
784
  params: signature.paramsSignature,
805
785
  returnType: functionReturnType,
@@ -819,27 +799,22 @@ function Operation({ name, node, types, zodResolver, validator, returnType, thro
819
799
  //#endregion
820
800
  //#region ../../internals/client/src/resolveOperationTypes.ts
821
801
  /**
822
- * Warning a client generator reports when neither plugin can supply the operation types, so the
823
- * pipeline says why nothing was generated instead of silently emitting no files.
824
- */
825
- const MISSING_OPERATION_TYPES_WARNING = "Skipped: no operation types available. Add `pluginTs()`, or `pluginZod({ inferred: true })` to type the client from its zod schemas.";
826
- /**
827
802
  * Picks the plugin a generated client takes its `Options` and `Responses` types from.
828
803
  *
829
804
  * `plugin-ts` wins whenever it is in the pipeline, so existing configs keep the types they generate
830
805
  * today. Without it, `pluginZod({ inferred: true })` takes over: its inferred options and responses
831
806
  * types are built from the same shared schemas as the `plugin-ts` ones, so a client typed off
832
807
  * `z.infer` carries what the codecs decode to (a `Temporal` instance, a `Date`) rather than the raw
833
- * wire type. Returns `null` when neither is available, leaving the caller nothing to type against.
808
+ * wire type. Throws `KUBB_MISSING_DEPENDENCY` for `pluginName` when neither is available, since the
809
+ * client has nothing to type against.
834
810
  *
835
811
  * @example
836
812
  * ```ts
837
- * const types = resolveOperationTypes(ctx.driver)
838
- * if (!types) return null
813
+ * const types = resolveOperationTypes(ctx.driver, ctx.plugin.name)
839
814
  * types.response.options(node) // 'GetPetByIdOptions', or 'GetPetByIdOptionsSchemaType' from zod
840
815
  * ```
841
816
  */
842
- function resolveOperationTypes(driver) {
817
+ function resolveOperationTypes(driver, pluginName) {
843
818
  const pluginTs = driver.getPlugin(pluginTsName);
844
819
  if (pluginTs) {
845
820
  const resolver = driver.getResolver(pluginTsName);
@@ -851,7 +826,14 @@ function resolveOperationTypes(driver) {
851
826
  };
852
827
  }
853
828
  const pluginZod = driver.getPlugin(pluginZodName);
854
- if (!pluginZod?.options?.inferred) return null;
829
+ if (!pluginZod?.options?.inferred) throw new Diagnostics.Error({
830
+ code: Diagnostics.code.missingDependency,
831
+ severity: "error",
832
+ message: `Plugin "${pluginName}" has no source for operation types.`,
833
+ help: `Add \`pluginTs()\` before "${pluginName}", or add \`pluginZod({ inferred: true })\`.`,
834
+ location: { kind: "config" },
835
+ plugin: pluginName
836
+ });
855
837
  const resolver = driver.getResolver(pluginZodName);
856
838
  return {
857
839
  response: {
@@ -881,20 +863,15 @@ function createClientGenerator(name, resolveClientPath) {
881
863
  if (!ast.isHttpOperationNode(node)) return null;
882
864
  const { config, driver, resolver, root } = ctx;
883
865
  const { output, validator, returnType, throwOnErrorDefault, group } = ctx.options;
884
- const types = resolveOperationTypes(driver);
885
- if (!types) {
886
- ctx.warn(MISSING_OPERATION_TYPES_WARNING);
887
- return null;
888
- }
866
+ const types = resolveOperationTypes(driver, ctx.plugin.name);
889
867
  const pluginZod = resolveResponseValidator(validator) === "zod" || resolveRequestValidator(validator) === "zod" ? driver.getPlugin(pluginZodName) : null;
890
868
  const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null;
891
- const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
892
869
  const importedTypeNames = [types.response.options(node), types.response.responses(node)];
893
- const importedZodNames = zodResolver ? [
894
- resolveResponseValidator(validator) === "zod" ? zodResolver.response.response?.(node) : null,
895
- resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
896
- resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.response.body?.(node) : null
897
- ].filter((name) => Boolean(name)) : [];
870
+ const importedZodNames = buildValidatorHooks({
871
+ node,
872
+ validator,
873
+ zodResolver
874
+ }).importedZodNames;
898
875
  const meta = {
899
876
  name: resolver.name(node.operationId),
900
877
  file: resolver.file({
@@ -1070,7 +1047,7 @@ const pluginClient = defineExtension({
1070
1047
  },
1071
1048
  dependencies: (options) => ({
1072
1049
  [pluginTsName]: { optional: true },
1073
- [pluginZodName]: { optional: !options.validator }
1050
+ [pluginZodName]: { optional: !isValidatorEnabled(options.validator) }
1074
1051
  }),
1075
1052
  setup(options) {
1076
1053
  const { output = {