@kubb/plugin-fetch 5.0.0-beta.75 → 5.0.0-beta.76

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
@@ -8,121 +8,6 @@ import { pluginZodName } from "@kubb/plugin-zod";
8
8
  import { buildJSDoc } from "@kubb/ast/utils";
9
9
  import { macroSimplifyUnion } from "@kubb/ast/macros";
10
10
  import { fileURLToPath } from "node:url";
11
- //#region ../../internals/client/src/builders/parser.ts
12
- /**
13
- * Returns `true` when any direction of the parser uses zod (used for dependency checks).
14
- */
15
- function isParserEnabled(parser) {
16
- if (!parser) return false;
17
- if (parser === "zod") return true;
18
- return Boolean(parser.request || parser.response);
19
- }
20
- /**
21
- * Returns `'zod'` when request body parsing is enabled, `null` otherwise. The string shorthand
22
- * `'zod'` validates the response only, so it does not enable request parsing.
23
- */
24
- function resolveRequestParser(parser) {
25
- if (!parser || parser === "zod") return null;
26
- return parser.request ?? null;
27
- }
28
- /**
29
- * Returns `'zod'` when query-parameters parsing is enabled, `null` otherwise. Only the object form
30
- * `{ request: 'zod' }` enables it.
31
- */
32
- function resolveQueryParamsParser(parser) {
33
- if (!parser || parser === "zod") return null;
34
- return parser.request ?? null;
35
- }
36
- /**
37
- * Returns `'zod'` when response parsing is enabled, `null` otherwise. The string shorthand `'zod'`
38
- * maps to response parsing.
39
- */
40
- function resolveResponseParser(parser) {
41
- if (!parser) return null;
42
- if (parser === "zod") return "zod";
43
- return parser.response ?? null;
44
- }
45
- /**
46
- * Resolves the zod expression a generated client validates a success response with. Only success
47
- * (2xx) bodies reach the parse under the throw-on-error contract, so the success-only
48
- * `<operation>ResponseSchema` is used; error bodies are never zod-parsed.
49
- */
50
- function buildZodResponseParse(node, zodResolver) {
51
- const name = zodResolver.resolveResponseName?.(node);
52
- return name ? {
53
- expression: name,
54
- importNames: [name]
55
- } : null;
56
- }
57
- //#endregion
58
- //#region ../../internals/client/src/builders/security.ts
59
- function serializeAuth(auth) {
60
- const parts = [`type: '${auth.type}'`];
61
- if (auth.scheme) parts.push(`scheme: '${auth.scheme}'`);
62
- if (auth.name) parts.push(`name: '${auth.name}'`);
63
- if (auth.in) parts.push(`in: '${auth.in}'`);
64
- return `{ ${parts.join(", ")} }`;
65
- }
66
- /**
67
- * Maps an OpenAPI security scheme to the inline `Auth` object, or `null` when the runtime cannot
68
- * place it (an unresolved `$ref`, or an `apiKey` without a name or outside `header` / `query` /
69
- * `cookie`). `http` schemes other than `basic` are treated as bearer.
70
- */
71
- function resolveSecurityScheme(scheme) {
72
- if (!scheme || "$ref" in scheme) return null;
73
- if (scheme.type === "apiKey") {
74
- if (!scheme.name || scheme.in !== "header" && scheme.in !== "query" && scheme.in !== "cookie") return null;
75
- return {
76
- type: "apiKey",
77
- name: scheme.name,
78
- in: scheme.in
79
- };
80
- }
81
- if (scheme.type === "http") return {
82
- type: "http",
83
- scheme: scheme.scheme?.toLowerCase() === "basic" ? "basic" : "bearer"
84
- };
85
- if (scheme.type === "oauth2") return { type: "oauth2" };
86
- if (scheme.type === "openIdConnect") return { type: "openIdConnect" };
87
- return null;
88
- }
89
- /**
90
- * Derives the per-operation security metadata from the OpenAPI document. The operation's own
91
- * `security` overrides the global `security` (an explicit empty array disables auth), and every
92
- * referenced scheme is resolved from `components.securitySchemes` into a flat, de-duplicated list of
93
- * `Auth` objects the runtime walks in order.
94
- *
95
- * @example
96
- * `getOperationSecurity({ document, method: 'POST', path: '/pet' })`
97
- * `// [{ type: 'http', scheme: 'bearer' }]`
98
- */
99
- function getOperationSecurity({ document, method, path }) {
100
- if (!document) return void 0;
101
- const requirements = (document.paths?.[path]?.[method.toLowerCase()])?.security ?? document.security;
102
- if (!requirements?.length) return void 0;
103
- const definitions = document.components?.securitySchemes ?? {};
104
- const security = [];
105
- const seen = /* @__PURE__ */ new Set();
106
- for (const requirement of requirements) for (const schemeName of Object.keys(requirement)) {
107
- if (seen.has(schemeName)) continue;
108
- seen.add(schemeName);
109
- const auth = resolveSecurityScheme(definitions[schemeName]);
110
- if (auth) security.push(auth);
111
- }
112
- return security.length ? security : void 0;
113
- }
114
- /**
115
- * Serializes the per-operation security into the literal emitted on each generated call's `security`
116
- * field. The runtime `resolveAuth` helper walks it, calling the configured `auth` resolver per entry.
117
- *
118
- * @example
119
- * `buildSecurityMetadata({ security: [{ type: 'http', scheme: 'bearer' }] }) // "[{ type: 'http', scheme: 'bearer' }]"`
120
- */
121
- function buildSecurityMetadata({ security }) {
122
- if (!security?.length) return null;
123
- return `[${security.map(serializeAuth).join(", ")}]`;
124
- }
125
- //#endregion
126
11
  //#region ../../internals/utils/src/casing.ts
127
12
  /**
128
13
  * Shared implementation for camelCase and PascalCase conversion.
@@ -348,6 +233,17 @@ var Url = class Url {
348
233
  return path.replace(/\{([^}]+)\}/g, ":$1");
349
234
  }
350
235
  /**
236
+ * Rewrites OpenAPI placeholder names while keeping the `{...}` braces, so the generated `url`
237
+ * literal aligns with the grouped `path` request option that the runtime client interpolates by
238
+ * key.
239
+ *
240
+ * @example
241
+ * Url.toCasedTemplate('/projects/{project_id}', { casing: 'camelcase' }) // '/projects/{projectId}'
242
+ */
243
+ static toCasedTemplate(path, { casing } = {}) {
244
+ return path.replace(/\{([^}]+)\}/g, (_, name) => `{${transformParam(name, casing)}}`);
245
+ }
246
+ /**
351
247
  * Converts an OpenAPI/Swagger path to a TypeScript template literal string.
352
248
  * `prefix` is prepended inside the literal, `replacer` transforms each parameter name,
353
249
  * and `casing` controls parameter identifier casing.
@@ -457,6 +353,17 @@ function getOperationLink(node, link) {
457
353
  if (link === "urlPath") return node.path ? `{@link ${Url.toPath(node.path)}}` : null;
458
354
  return node.path ? `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}` : null;
459
355
  }
356
+ function getContentTypeInfo(node) {
357
+ const contentTypes = node.requestBody?.content?.map((e) => e.contentType) ?? [];
358
+ const isMultipleContentTypes = contentTypes.length > 1;
359
+ return {
360
+ contentTypes,
361
+ isMultipleContentTypes,
362
+ contentTypeUnion: isMultipleContentTypes ? contentTypes.map((ct) => JSON.stringify(ct)).join(" | ") : "",
363
+ defaultContentType: contentTypes[0] ?? "application/json",
364
+ hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
365
+ };
366
+ }
460
367
  function buildOperationComments(node, options = {}) {
461
368
  const { link = "pathTemplate", linkPosition = "afterDeprecated", splitLines = false } = options;
462
369
  const linkComment = getOperationLink(node, link);
@@ -483,6 +390,14 @@ function getOperationParameters(node, options = {}) {
483
390
  cookie: params.filter((param) => param.in === "cookie")
484
391
  };
485
392
  }
393
+ function getStatusCodeNumber(statusCode) {
394
+ const code = Number(statusCode);
395
+ return Number.isNaN(code) ? null : code;
396
+ }
397
+ function isSuccessStatusCode(statusCode) {
398
+ const code = getStatusCodeNumber(statusCode);
399
+ return code !== null && code >= 200 && code < 300;
400
+ }
486
401
  //#endregion
487
402
  //#region ../../internals/shared/src/group.ts
488
403
  /**
@@ -515,6 +430,134 @@ function createGroupConfig(group) {
515
430
  };
516
431
  }
517
432
  //#endregion
433
+ //#region ../../internals/client/src/builders/parser.ts
434
+ /**
435
+ * Returns `true` when any direction of the parser uses zod (used for dependency checks).
436
+ */
437
+ function isParserEnabled(parser) {
438
+ if (!parser) return false;
439
+ if (parser === "zod") return true;
440
+ return Boolean(parser.request || parser.response);
441
+ }
442
+ /**
443
+ * Returns `'zod'` when request body parsing is enabled, `null` otherwise. The string shorthand
444
+ * `'zod'` validates the response only, so it does not enable request parsing.
445
+ */
446
+ function resolveRequestParser(parser) {
447
+ if (!parser || parser === "zod") return null;
448
+ return parser.request ?? null;
449
+ }
450
+ /**
451
+ * Returns `'zod'` when query-parameters parsing is enabled, `null` otherwise. Only the object form
452
+ * `{ request: 'zod' }` enables it.
453
+ */
454
+ function resolveQueryParamsParser(parser) {
455
+ if (!parser || parser === "zod") return null;
456
+ return parser.request ?? null;
457
+ }
458
+ /**
459
+ * Returns `'zod'` when response parsing is enabled, `null` otherwise. The string shorthand `'zod'`
460
+ * maps to response parsing.
461
+ */
462
+ function resolveResponseParser(parser) {
463
+ if (!parser) return null;
464
+ if (parser === "zod") return "zod";
465
+ return parser.response ?? null;
466
+ }
467
+ /**
468
+ * Resolves the zod expression a generated client validates a success response with. Only success
469
+ * (2xx) bodies reach the parse under the throw-on-error contract, so the success-only
470
+ * `<operation>ResponseSchema` is used; error bodies are never zod-parsed.
471
+ */
472
+ function buildZodResponseParse(node, zodResolver) {
473
+ const name = zodResolver.resolveResponseName?.(node);
474
+ return name ? {
475
+ expression: name,
476
+ importNames: [name]
477
+ } : null;
478
+ }
479
+ /**
480
+ * Resolves the zod expression a generated client validates an error body with on the non-throw path.
481
+ * Uses the error-only `<operation>ErrorSchema` (the union of non-2xx statuses); returns `null` when the
482
+ * operation documents no error responses with a schema.
483
+ */
484
+ function buildZodErrorParse(node, zodResolver) {
485
+ if (!node.responses.some((res) => !isSuccessStatusCode(res.statusCode) && res.content?.some((entry) => entry.schema))) return null;
486
+ const name = zodResolver.resolveErrorName?.(node);
487
+ return name ? {
488
+ expression: name,
489
+ importNames: [name]
490
+ } : null;
491
+ }
492
+ //#endregion
493
+ //#region ../../internals/client/src/builders/security.ts
494
+ function serializeAuth(auth) {
495
+ const parts = [`type: '${auth.type}'`];
496
+ if (auth.scheme) parts.push(`scheme: '${auth.scheme}'`);
497
+ if (auth.name) parts.push(`name: '${auth.name}'`);
498
+ if (auth.in) parts.push(`in: '${auth.in}'`);
499
+ return `{ ${parts.join(", ")} }`;
500
+ }
501
+ /**
502
+ * Maps an OpenAPI security scheme to the inline `Auth` object, or `null` when the runtime cannot
503
+ * place it (an unresolved `$ref`, or an `apiKey` without a name or outside `header` / `query` /
504
+ * `cookie`). `http` schemes other than `basic` are treated as bearer.
505
+ */
506
+ function resolveSecurityScheme(scheme) {
507
+ if (!scheme || "$ref" in scheme) return null;
508
+ if (scheme.type === "apiKey") {
509
+ if (!scheme.name || scheme.in !== "header" && scheme.in !== "query" && scheme.in !== "cookie") return null;
510
+ return {
511
+ type: "apiKey",
512
+ name: scheme.name,
513
+ in: scheme.in
514
+ };
515
+ }
516
+ if (scheme.type === "http") return {
517
+ type: "http",
518
+ scheme: scheme.scheme?.toLowerCase() === "basic" ? "basic" : "bearer"
519
+ };
520
+ if (scheme.type === "oauth2") return { type: "oauth2" };
521
+ if (scheme.type === "openIdConnect") return { type: "openIdConnect" };
522
+ return null;
523
+ }
524
+ /**
525
+ * Derives the per-operation security metadata from the OpenAPI document. The operation's own
526
+ * `security` overrides the global `security` (an explicit empty array disables auth), and every
527
+ * referenced scheme is resolved from `components.securitySchemes` into a flat, de-duplicated list of
528
+ * `Auth` objects the runtime walks in order.
529
+ *
530
+ * @example
531
+ * `getOperationSecurity({ document, method: 'POST', path: '/pet' })`
532
+ * `// [{ type: 'http', scheme: 'bearer' }]`
533
+ */
534
+ function getOperationSecurity({ document, method, path }) {
535
+ if (!document) return void 0;
536
+ const requirements = (document.paths?.[path]?.[method.toLowerCase()])?.security ?? document.security;
537
+ if (!requirements?.length) return void 0;
538
+ const definitions = document.components?.securitySchemes ?? {};
539
+ const security = [];
540
+ const seen = /* @__PURE__ */ new Set();
541
+ for (const requirement of requirements) for (const schemeName of Object.keys(requirement)) {
542
+ if (seen.has(schemeName)) continue;
543
+ seen.add(schemeName);
544
+ const auth = resolveSecurityScheme(definitions[schemeName]);
545
+ if (auth) security.push(auth);
546
+ }
547
+ return security.length ? security : void 0;
548
+ }
549
+ /**
550
+ * Serializes the per-operation security into the literal emitted on each generated call's `security`
551
+ * field. The runtime `resolveAuth` helper walks it, calling the configured `auth` resolver per entry.
552
+ *
553
+ * @example
554
+ * `buildSecurityMetadata({ security: [{ type: 'http', scheme: 'bearer' }] }) // "[{ type: 'http', scheme: 'bearer' }]"`
555
+ */
556
+ function buildSecurityMetadata({ security }) {
557
+ if (!security?.length) return null;
558
+ return `[${security.map(serializeAuth).join(", ")}]`;
559
+ }
560
+ //#endregion
518
561
  //#region ../../internals/client/src/builders/generics.ts
519
562
  /**
520
563
  * Builds the `RequestResult` generic arguments for one operation: the plugin-ts per-status responses
@@ -588,9 +631,13 @@ function buildParserHooks({ node, parser, zodResolver }) {
588
631
  const responseParse = zodResolver && resolveResponseParser(parser) === "zod" ? buildZodResponseParse(node, zodResolver) : null;
589
632
  const response = responseParse ? `(data: unknown) => ${responseParse.expression}.parse(data)` : null;
590
633
  if (responseParse) importedZodNames.push(...responseParse.importNames);
634
+ const errorParse = zodResolver && resolveResponseParser(parser) === "zod" ? buildZodErrorParse(node, zodResolver) : null;
635
+ const error = errorParse ? `(data: unknown) => ${errorParse.expression}.parse(data)` : null;
636
+ if (errorParse) importedZodNames.push(...errorParse.importNames);
591
637
  return {
592
638
  request,
593
639
  response,
640
+ error,
594
641
  importedZodNames
595
642
  };
596
643
  }
@@ -613,13 +660,20 @@ function Operation({ name, node, tsResolver, zodResolver, parser, security, isEx
613
660
  zodResolver
614
661
  });
615
662
  const securityLiteral = buildSecurityMetadata({ security });
616
- const parserEntries = [parsers.request ? `request: ${parsers.request}` : null, parsers.response ? `response: ${parsers.response}` : null].filter(Boolean);
663
+ const { defaultContentType } = getContentTypeInfo(node);
664
+ const contentTypeLiteral = Boolean(node.requestBody?.content?.[0]?.schema) && defaultContentType !== "application/json" ? `contentType: '${defaultContentType}'` : null;
665
+ const parserEntries = [
666
+ parsers.request ? `request: ${parsers.request}` : null,
667
+ parsers.response ? `response: ${parsers.response}` : null,
668
+ parsers.error ? `error: ${parsers.error}` : null
669
+ ].filter(Boolean);
617
670
  const parserLiteral = parserEntries.length ? `parser: { ${parserEntries.join(", ")} }` : null;
618
671
  const callConfig = `{ ${[
619
672
  `method: '${node.method.toUpperCase()}'`,
620
- `url: '${node.path}'`,
673
+ `url: '${Url.toCasedTemplate(node.path, { casing: "camelcase" })}'`,
621
674
  securityLiteral ? `security: ${securityLiteral}` : null,
622
675
  parserLiteral,
676
+ contentTypeLiteral,
623
677
  "...config"
624
678
  ].filter(Boolean).join(", ")} }`;
625
679
  return /* @__PURE__ */ jsx(File.Source, {
@@ -667,7 +721,7 @@ function buildCallConfig({ node, parser, zodResolver, security }) {
667
721
  const securityLiteral = buildSecurityMetadata({ security });
668
722
  return `{ ${[
669
723
  `method: '${node.method.toUpperCase()}'`,
670
- `url: '${node.path}'`,
724
+ `url: '${Url.toCasedTemplate(node.path, { casing: "camelcase" })}'`,
671
725
  securityLiteral ? `security: ${securityLiteral}` : null,
672
726
  parserLiteral,
673
727
  "...config"
@@ -772,6 +826,7 @@ function resolveZodImportNames(node, zodResolver, parser) {
772
826
  const { query: queryParams } = getOperationParameters(node, { paramsCasing: "original" });
773
827
  return [
774
828
  resolveResponseParser(parser) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
829
+ resolveResponseParser(parser) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
775
830
  resolveRequestParser(parser) === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
776
831
  resolveQueryParamsParser(parser) === "zod" && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]) : null
777
832
  ].filter((n) => Boolean(n));
@@ -1066,7 +1121,11 @@ const clientGenerator = defineGenerator({
1066
1121
  const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null;
1067
1122
  const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
1068
1123
  const importedTypeNames = [tsResolver.resolveRequestConfigName(node), tsResolver.resolveResponsesName(node)];
1069
- const importedZodNames = zodResolver ? [resolveResponseParser(parser) === "zod" ? zodResolver.resolveResponseName?.(node) : null, resolveRequestParser(parser) === "zod" && hasRequestBody ? zodResolver.resolveDataName?.(node) : null].filter((name) => Boolean(name)) : [];
1124
+ const importedZodNames = zodResolver ? [
1125
+ resolveResponseParser(parser) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
1126
+ resolveResponseParser(parser) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
1127
+ resolveRequestParser(parser) === "zod" && hasRequestBody ? zodResolver.resolveDataName?.(node) : null
1128
+ ].filter((name) => Boolean(name)) : [];
1070
1129
  const meta = {
1071
1130
  name: resolver.resolveName(node.operationId),
1072
1131
  file: resolver.resolveFile(operationFileEntry(node, node.operationId), {