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

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
@@ -34,121 +34,6 @@ let _kubb_plugin_zod = require("@kubb/plugin-zod");
34
34
  let _kubb_ast_utils = require("@kubb/ast/utils");
35
35
  let _kubb_ast_macros = require("@kubb/ast/macros");
36
36
  let node_url = require("node:url");
37
- //#region ../../internals/client/src/builders/parser.ts
38
- /**
39
- * Returns `true` when any direction of the parser uses zod (used for dependency checks).
40
- */
41
- function isParserEnabled(parser) {
42
- if (!parser) return false;
43
- if (parser === "zod") return true;
44
- return Boolean(parser.request || parser.response);
45
- }
46
- /**
47
- * Returns `'zod'` when request body parsing is enabled, `null` otherwise. The string shorthand
48
- * `'zod'` validates the response only, so it does not enable request parsing.
49
- */
50
- function resolveRequestParser(parser) {
51
- if (!parser || parser === "zod") return null;
52
- return parser.request ?? null;
53
- }
54
- /**
55
- * Returns `'zod'` when query-parameters parsing is enabled, `null` otherwise. Only the object form
56
- * `{ request: 'zod' }` enables it.
57
- */
58
- function resolveQueryParamsParser(parser) {
59
- if (!parser || parser === "zod") return null;
60
- return parser.request ?? null;
61
- }
62
- /**
63
- * Returns `'zod'` when response parsing is enabled, `null` otherwise. The string shorthand `'zod'`
64
- * maps to response parsing.
65
- */
66
- function resolveResponseParser(parser) {
67
- if (!parser) return null;
68
- if (parser === "zod") return "zod";
69
- return parser.response ?? null;
70
- }
71
- /**
72
- * Resolves the zod expression a generated client validates a success response with. Only success
73
- * (2xx) bodies reach the parse under the throw-on-error contract, so the success-only
74
- * `<operation>ResponseSchema` is used; error bodies are never zod-parsed.
75
- */
76
- function buildZodResponseParse(node, zodResolver) {
77
- const name = zodResolver.resolveResponseName?.(node);
78
- return name ? {
79
- expression: name,
80
- importNames: [name]
81
- } : null;
82
- }
83
- //#endregion
84
- //#region ../../internals/client/src/builders/security.ts
85
- function serializeAuth(auth) {
86
- const parts = [`type: '${auth.type}'`];
87
- if (auth.scheme) parts.push(`scheme: '${auth.scheme}'`);
88
- if (auth.name) parts.push(`name: '${auth.name}'`);
89
- if (auth.in) parts.push(`in: '${auth.in}'`);
90
- return `{ ${parts.join(", ")} }`;
91
- }
92
- /**
93
- * Maps an OpenAPI security scheme to the inline `Auth` object, or `null` when the runtime cannot
94
- * place it (an unresolved `$ref`, or an `apiKey` without a name or outside `header` / `query` /
95
- * `cookie`). `http` schemes other than `basic` are treated as bearer.
96
- */
97
- function resolveSecurityScheme(scheme) {
98
- if (!scheme || "$ref" in scheme) return null;
99
- if (scheme.type === "apiKey") {
100
- if (!scheme.name || scheme.in !== "header" && scheme.in !== "query" && scheme.in !== "cookie") return null;
101
- return {
102
- type: "apiKey",
103
- name: scheme.name,
104
- in: scheme.in
105
- };
106
- }
107
- if (scheme.type === "http") return {
108
- type: "http",
109
- scheme: scheme.scheme?.toLowerCase() === "basic" ? "basic" : "bearer"
110
- };
111
- if (scheme.type === "oauth2") return { type: "oauth2" };
112
- if (scheme.type === "openIdConnect") return { type: "openIdConnect" };
113
- return null;
114
- }
115
- /**
116
- * Derives the per-operation security metadata from the OpenAPI document. The operation's own
117
- * `security` overrides the global `security` (an explicit empty array disables auth), and every
118
- * referenced scheme is resolved from `components.securitySchemes` into a flat, de-duplicated list of
119
- * `Auth` objects the runtime walks in order.
120
- *
121
- * @example
122
- * `getOperationSecurity({ document, method: 'POST', path: '/pet' })`
123
- * `// [{ type: 'http', scheme: 'bearer' }]`
124
- */
125
- function getOperationSecurity({ document, method, path }) {
126
- if (!document) return void 0;
127
- const requirements = (document.paths?.[path]?.[method.toLowerCase()])?.security ?? document.security;
128
- if (!requirements?.length) return void 0;
129
- const definitions = document.components?.securitySchemes ?? {};
130
- const security = [];
131
- const seen = /* @__PURE__ */ new Set();
132
- for (const requirement of requirements) for (const schemeName of Object.keys(requirement)) {
133
- if (seen.has(schemeName)) continue;
134
- seen.add(schemeName);
135
- const auth = resolveSecurityScheme(definitions[schemeName]);
136
- if (auth) security.push(auth);
137
- }
138
- return security.length ? security : void 0;
139
- }
140
- /**
141
- * Serializes the per-operation security into the literal emitted on each generated call's `security`
142
- * field. The runtime `resolveAuth` helper walks it, calling the configured `auth` resolver per entry.
143
- *
144
- * @example
145
- * `buildSecurityMetadata({ security: [{ type: 'http', scheme: 'bearer' }] }) // "[{ type: 'http', scheme: 'bearer' }]"`
146
- */
147
- function buildSecurityMetadata({ security }) {
148
- if (!security?.length) return null;
149
- return `[${security.map(serializeAuth).join(", ")}]`;
150
- }
151
- //#endregion
152
37
  //#region ../../internals/utils/src/casing.ts
153
38
  /**
154
39
  * Shared implementation for camelCase and PascalCase conversion.
@@ -374,6 +259,17 @@ var Url = class Url {
374
259
  return path.replace(/\{([^}]+)\}/g, ":$1");
375
260
  }
376
261
  /**
262
+ * Rewrites OpenAPI placeholder names while keeping the `{...}` braces, so the generated `url`
263
+ * literal aligns with the grouped `path` request option that the runtime client interpolates by
264
+ * key.
265
+ *
266
+ * @example
267
+ * Url.toCasedTemplate('/projects/{project_id}', { casing: 'camelcase' }) // '/projects/{projectId}'
268
+ */
269
+ static toCasedTemplate(path, { casing } = {}) {
270
+ return path.replace(/\{([^}]+)\}/g, (_, name) => `{${transformParam(name, casing)}}`);
271
+ }
272
+ /**
377
273
  * Converts an OpenAPI/Swagger path to a TypeScript template literal string.
378
274
  * `prefix` is prepended inside the literal, `replacer` transforms each parameter name,
379
275
  * and `casing` controls parameter identifier casing.
@@ -483,6 +379,17 @@ function getOperationLink(node, link) {
483
379
  if (link === "urlPath") return node.path ? `{@link ${Url.toPath(node.path)}}` : null;
484
380
  return node.path ? `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}` : null;
485
381
  }
382
+ function getContentTypeInfo(node) {
383
+ const contentTypes = node.requestBody?.content?.map((e) => e.contentType) ?? [];
384
+ const isMultipleContentTypes = contentTypes.length > 1;
385
+ return {
386
+ contentTypes,
387
+ isMultipleContentTypes,
388
+ contentTypeUnion: isMultipleContentTypes ? contentTypes.map((ct) => JSON.stringify(ct)).join(" | ") : "",
389
+ defaultContentType: contentTypes[0] ?? "application/json",
390
+ hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
391
+ };
392
+ }
486
393
  function buildOperationComments(node, options = {}) {
487
394
  const { link = "pathTemplate", linkPosition = "afterDeprecated", splitLines = false } = options;
488
395
  const linkComment = getOperationLink(node, link);
@@ -509,6 +416,14 @@ function getOperationParameters(node, options = {}) {
509
416
  cookie: params.filter((param) => param.in === "cookie")
510
417
  };
511
418
  }
419
+ function getStatusCodeNumber(statusCode) {
420
+ const code = Number(statusCode);
421
+ return Number.isNaN(code) ? null : code;
422
+ }
423
+ function isSuccessStatusCode(statusCode) {
424
+ const code = getStatusCodeNumber(statusCode);
425
+ return code !== null && code >= 200 && code < 300;
426
+ }
512
427
  //#endregion
513
428
  //#region ../../internals/shared/src/group.ts
514
429
  /**
@@ -541,6 +456,134 @@ function createGroupConfig(group) {
541
456
  };
542
457
  }
543
458
  //#endregion
459
+ //#region ../../internals/client/src/builders/parser.ts
460
+ /**
461
+ * Returns `true` when any direction of the parser uses zod (used for dependency checks).
462
+ */
463
+ function isParserEnabled(parser) {
464
+ if (!parser) return false;
465
+ if (parser === "zod") return true;
466
+ return Boolean(parser.request || parser.response);
467
+ }
468
+ /**
469
+ * Returns `'zod'` when request body parsing is enabled, `null` otherwise. The string shorthand
470
+ * `'zod'` validates the response only, so it does not enable request parsing.
471
+ */
472
+ function resolveRequestParser(parser) {
473
+ if (!parser || parser === "zod") return null;
474
+ return parser.request ?? null;
475
+ }
476
+ /**
477
+ * Returns `'zod'` when query-parameters parsing is enabled, `null` otherwise. Only the object form
478
+ * `{ request: 'zod' }` enables it.
479
+ */
480
+ function resolveQueryParamsParser(parser) {
481
+ if (!parser || parser === "zod") return null;
482
+ return parser.request ?? null;
483
+ }
484
+ /**
485
+ * Returns `'zod'` when response parsing is enabled, `null` otherwise. The string shorthand `'zod'`
486
+ * maps to response parsing.
487
+ */
488
+ function resolveResponseParser(parser) {
489
+ if (!parser) return null;
490
+ if (parser === "zod") return "zod";
491
+ return parser.response ?? null;
492
+ }
493
+ /**
494
+ * Resolves the zod expression a generated client validates a success response with. Only success
495
+ * (2xx) bodies reach the parse under the throw-on-error contract, so the success-only
496
+ * `<operation>ResponseSchema` is used; error bodies are never zod-parsed.
497
+ */
498
+ function buildZodResponseParse(node, zodResolver) {
499
+ const name = zodResolver.resolveResponseName?.(node);
500
+ return name ? {
501
+ expression: name,
502
+ importNames: [name]
503
+ } : null;
504
+ }
505
+ /**
506
+ * Resolves the zod expression a generated client validates an error body with on the non-throw path.
507
+ * Uses the error-only `<operation>ErrorSchema` (the union of non-2xx statuses); returns `null` when the
508
+ * operation documents no error responses with a schema.
509
+ */
510
+ function buildZodErrorParse(node, zodResolver) {
511
+ if (!node.responses.some((res) => !isSuccessStatusCode(res.statusCode) && res.content?.some((entry) => entry.schema))) return null;
512
+ const name = zodResolver.resolveErrorName?.(node);
513
+ return name ? {
514
+ expression: name,
515
+ importNames: [name]
516
+ } : null;
517
+ }
518
+ //#endregion
519
+ //#region ../../internals/client/src/builders/security.ts
520
+ function serializeAuth(auth) {
521
+ const parts = [`type: '${auth.type}'`];
522
+ if (auth.scheme) parts.push(`scheme: '${auth.scheme}'`);
523
+ if (auth.name) parts.push(`name: '${auth.name}'`);
524
+ if (auth.in) parts.push(`in: '${auth.in}'`);
525
+ return `{ ${parts.join(", ")} }`;
526
+ }
527
+ /**
528
+ * Maps an OpenAPI security scheme to the inline `Auth` object, or `null` when the runtime cannot
529
+ * place it (an unresolved `$ref`, or an `apiKey` without a name or outside `header` / `query` /
530
+ * `cookie`). `http` schemes other than `basic` are treated as bearer.
531
+ */
532
+ function resolveSecurityScheme(scheme) {
533
+ if (!scheme || "$ref" in scheme) return null;
534
+ if (scheme.type === "apiKey") {
535
+ if (!scheme.name || scheme.in !== "header" && scheme.in !== "query" && scheme.in !== "cookie") return null;
536
+ return {
537
+ type: "apiKey",
538
+ name: scheme.name,
539
+ in: scheme.in
540
+ };
541
+ }
542
+ if (scheme.type === "http") return {
543
+ type: "http",
544
+ scheme: scheme.scheme?.toLowerCase() === "basic" ? "basic" : "bearer"
545
+ };
546
+ if (scheme.type === "oauth2") return { type: "oauth2" };
547
+ if (scheme.type === "openIdConnect") return { type: "openIdConnect" };
548
+ return null;
549
+ }
550
+ /**
551
+ * Derives the per-operation security metadata from the OpenAPI document. The operation's own
552
+ * `security` overrides the global `security` (an explicit empty array disables auth), and every
553
+ * referenced scheme is resolved from `components.securitySchemes` into a flat, de-duplicated list of
554
+ * `Auth` objects the runtime walks in order.
555
+ *
556
+ * @example
557
+ * `getOperationSecurity({ document, method: 'POST', path: '/pet' })`
558
+ * `// [{ type: 'http', scheme: 'bearer' }]`
559
+ */
560
+ function getOperationSecurity({ document, method, path }) {
561
+ if (!document) return void 0;
562
+ const requirements = (document.paths?.[path]?.[method.toLowerCase()])?.security ?? document.security;
563
+ if (!requirements?.length) return void 0;
564
+ const definitions = document.components?.securitySchemes ?? {};
565
+ const security = [];
566
+ const seen = /* @__PURE__ */ new Set();
567
+ for (const requirement of requirements) for (const schemeName of Object.keys(requirement)) {
568
+ if (seen.has(schemeName)) continue;
569
+ seen.add(schemeName);
570
+ const auth = resolveSecurityScheme(definitions[schemeName]);
571
+ if (auth) security.push(auth);
572
+ }
573
+ return security.length ? security : void 0;
574
+ }
575
+ /**
576
+ * Serializes the per-operation security into the literal emitted on each generated call's `security`
577
+ * field. The runtime `resolveAuth` helper walks it, calling the configured `auth` resolver per entry.
578
+ *
579
+ * @example
580
+ * `buildSecurityMetadata({ security: [{ type: 'http', scheme: 'bearer' }] }) // "[{ type: 'http', scheme: 'bearer' }]"`
581
+ */
582
+ function buildSecurityMetadata({ security }) {
583
+ if (!security?.length) return null;
584
+ return `[${security.map(serializeAuth).join(", ")}]`;
585
+ }
586
+ //#endregion
544
587
  //#region ../../internals/client/src/builders/generics.ts
545
588
  /**
546
589
  * Builds the `RequestResult` generic arguments for one operation: the plugin-ts per-status responses
@@ -614,9 +657,13 @@ function buildParserHooks({ node, parser, zodResolver }) {
614
657
  const responseParse = zodResolver && resolveResponseParser(parser) === "zod" ? buildZodResponseParse(node, zodResolver) : null;
615
658
  const response = responseParse ? `(data: unknown) => ${responseParse.expression}.parse(data)` : null;
616
659
  if (responseParse) importedZodNames.push(...responseParse.importNames);
660
+ const errorParse = zodResolver && resolveResponseParser(parser) === "zod" ? buildZodErrorParse(node, zodResolver) : null;
661
+ const error = errorParse ? `(data: unknown) => ${errorParse.expression}.parse(data)` : null;
662
+ if (errorParse) importedZodNames.push(...errorParse.importNames);
617
663
  return {
618
664
  request,
619
665
  response,
666
+ error,
620
667
  importedZodNames
621
668
  };
622
669
  }
@@ -639,13 +686,20 @@ function Operation({ name, node, tsResolver, zodResolver, parser, security, isEx
639
686
  zodResolver
640
687
  });
641
688
  const securityLiteral = buildSecurityMetadata({ security });
642
- const parserEntries = [parsers.request ? `request: ${parsers.request}` : null, parsers.response ? `response: ${parsers.response}` : null].filter(Boolean);
689
+ const { defaultContentType } = getContentTypeInfo(node);
690
+ const contentTypeLiteral = Boolean(node.requestBody?.content?.[0]?.schema) && defaultContentType !== "application/json" ? `contentType: '${defaultContentType}'` : null;
691
+ const parserEntries = [
692
+ parsers.request ? `request: ${parsers.request}` : null,
693
+ parsers.response ? `response: ${parsers.response}` : null,
694
+ parsers.error ? `error: ${parsers.error}` : null
695
+ ].filter(Boolean);
643
696
  const parserLiteral = parserEntries.length ? `parser: { ${parserEntries.join(", ")} }` : null;
644
697
  const callConfig = `{ ${[
645
698
  `method: '${node.method.toUpperCase()}'`,
646
- `url: '${node.path}'`,
699
+ `url: '${Url.toCasedTemplate(node.path, { casing: "camelcase" })}'`,
647
700
  securityLiteral ? `security: ${securityLiteral}` : null,
648
701
  parserLiteral,
702
+ contentTypeLiteral,
649
703
  "...config"
650
704
  ].filter(Boolean).join(", ")} }`;
651
705
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
@@ -693,7 +747,7 @@ function buildCallConfig({ node, parser, zodResolver, security }) {
693
747
  const securityLiteral = buildSecurityMetadata({ security });
694
748
  return `{ ${[
695
749
  `method: '${node.method.toUpperCase()}'`,
696
- `url: '${node.path}'`,
750
+ `url: '${Url.toCasedTemplate(node.path, { casing: "camelcase" })}'`,
697
751
  securityLiteral ? `security: ${securityLiteral}` : null,
698
752
  parserLiteral,
699
753
  "...config"
@@ -798,6 +852,7 @@ function resolveZodImportNames(node, zodResolver, parser) {
798
852
  const { query: queryParams } = getOperationParameters(node, { paramsCasing: "original" });
799
853
  return [
800
854
  resolveResponseParser(parser) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
855
+ resolveResponseParser(parser) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
801
856
  resolveRequestParser(parser) === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
802
857
  resolveQueryParamsParser(parser) === "zod" && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]) : null
803
858
  ].filter((n) => Boolean(n));
@@ -1092,7 +1147,11 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
1092
1147
  const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : null;
1093
1148
  const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
1094
1149
  const importedTypeNames = [tsResolver.resolveRequestConfigName(node), tsResolver.resolveResponsesName(node)];
1095
- const importedZodNames = zodResolver ? [resolveResponseParser(parser) === "zod" ? zodResolver.resolveResponseName?.(node) : null, resolveRequestParser(parser) === "zod" && hasRequestBody ? zodResolver.resolveDataName?.(node) : null].filter((name) => Boolean(name)) : [];
1150
+ const importedZodNames = zodResolver ? [
1151
+ resolveResponseParser(parser) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
1152
+ resolveResponseParser(parser) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
1153
+ resolveRequestParser(parser) === "zod" && hasRequestBody ? zodResolver.resolveDataName?.(node) : null
1154
+ ].filter((name) => Boolean(name)) : [];
1096
1155
  const meta = {
1097
1156
  name: resolver.resolveName(node.operationId),
1098
1157
  file: resolver.resolveFile(operationFileEntry(node, node.operationId), {