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

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
@@ -103,7 +103,7 @@ function toFilePath(name, caseLast = camelCase) {
103
103
  * JavaScript and Java reserved words.
104
104
  * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
105
105
  */
106
- const reservedWords = new Set([
106
+ const reservedWords = /* @__PURE__ */ new Set([
107
107
  "abstract",
108
108
  "arguments",
109
109
  "boolean",
@@ -352,6 +352,23 @@ function caseParams(params, casing) {
352
352
  caseParamsCache.set(params, result);
353
353
  return result;
354
354
  }
355
+ /**
356
+ * Drops parameters that collapse to the same property identity once camelCased, keeping the first.
357
+ *
358
+ * Some specs declare the same parameter twice under different casings (for example AWS S3 lists both
359
+ * `max-uploads` and `MaxUploads`). Both resolve to one output property, so emitting both would yield
360
+ * an object type with a duplicate member, which TypeScript rejects. De-duplicate by the camelCased
361
+ * identity so the resulting group is collision-free regardless of the names each caller carries.
362
+ */
363
+ function dedupeByCasedName(params) {
364
+ const seen = /* @__PURE__ */ new Set();
365
+ return params.filter((param) => {
366
+ const key = camelCase(param.name);
367
+ if (seen.has(key)) return false;
368
+ seen.add(key);
369
+ return true;
370
+ });
371
+ }
355
372
  //#endregion
356
373
  //#region ../../internals/shared/src/operation.ts
357
374
  /**
@@ -390,6 +407,86 @@ function getContentTypeInfo(node) {
390
407
  hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
391
408
  };
392
409
  }
410
+ /**
411
+ * The request-body counterpart for the primary success response: the content types it documents and
412
+ * whether several are present, so the client can let a caller pick which one to accept.
413
+ */
414
+ function getResponseContentTypeInfo(node) {
415
+ const contentTypes = getPrimarySuccessResponse(node)?.content?.map((e) => e.contentType) ?? [];
416
+ const isMultipleContentTypes = contentTypes.length > 1;
417
+ return {
418
+ contentTypes,
419
+ isMultipleContentTypes,
420
+ contentTypeUnion: isMultipleContentTypes ? contentTypes.map((ct) => JSON.stringify(ct)).join(" | ") : "",
421
+ defaultContentType: contentTypes[0] ?? "application/json",
422
+ hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
423
+ };
424
+ }
425
+ /**
426
+ * Reads the single base content type of an operation's primary success response, lowercased and
427
+ * stripped of any `; charset=...` suffix. Returns `undefined` when the response declares zero or
428
+ * more than one content type, since neither case has a single type to act on.
429
+ */
430
+ function getPrimarySuccessContentType(node) {
431
+ const contentTypes = getPrimarySuccessResponse(node)?.content?.map((entry) => entry.contentType) ?? [];
432
+ if (contentTypes.length !== 1) return void 0;
433
+ return contentTypes[0].split(";")[0].trim().toLowerCase();
434
+ }
435
+ /**
436
+ * Whether an operation streams its primary success response as Server-Sent Events
437
+ * (`text/event-stream`). The client generator uses this to return a typed event stream instead of a
438
+ * one-shot `RequestResult`.
439
+ */
440
+ function isEventStream(node) {
441
+ return getPrimarySuccessContentType(node) === "text/event-stream";
442
+ }
443
+ /**
444
+ * Derives the default `responseType` for an operation from its primary success response.
445
+ *
446
+ * Returns a value only when that response declares a single non-JSON content type. `text/event-stream`
447
+ * and other binary types (`application/octet-stream`, `application/pdf`, `image/*`, `audio/*`,
448
+ * `video/*`) map to a stream or `'blob'`, and other `text/*` maps to `'text'`. Otherwise `undefined`,
449
+ * leaving the runtime client's `Content-Type` auto-detection in charge.
450
+ */
451
+ function getResponseType(node) {
452
+ const baseType = getPrimarySuccessContentType(node);
453
+ if (!baseType) return void 0;
454
+ if (baseType === "application/json" || baseType.endsWith("+json") || baseType === "text/json") return void 0;
455
+ if (baseType === "text/event-stream") return "stream";
456
+ if (baseType.startsWith("text/")) return "text";
457
+ if (baseType === "application/octet-stream" || baseType === "application/pdf" || /^(image|audio|video)\//.test(baseType)) return "blob";
458
+ }
459
+ /**
460
+ * Which of the grouped request options an operation carries.
461
+ */
462
+ function getRequestGroups(node) {
463
+ const { path, query, header } = getOperationParameters(node);
464
+ return {
465
+ path: path.length > 0,
466
+ query: query.length > 0,
467
+ body: Boolean(node.requestBody?.content?.[0]?.schema),
468
+ headers: header.length > 0
469
+ };
470
+ }
471
+ /**
472
+ * Resolves which grouped request options an operation carries together with whether each group
473
+ * holds a required member. The grouped parameter stays optional only when nothing inside it is
474
+ * required, matching the generated `RequestConfig` type.
475
+ */
476
+ function getRequestGroupOptionality(node) {
477
+ const groups = getRequestGroups(node);
478
+ const { path, query, header } = getOperationParameters(node);
479
+ const hasRequiredPath = path.some((param) => param.required);
480
+ const hasRequiredQuery = query.some((param) => param.required);
481
+ const hasRequiredHeader = header.some((param) => param.required);
482
+ return {
483
+ groups,
484
+ hasRequiredPath,
485
+ hasRequiredQuery,
486
+ hasRequiredHeader,
487
+ isOptional: !hasRequiredPath && !hasRequiredQuery && !hasRequiredHeader && !groups.body
488
+ };
489
+ }
393
490
  function buildOperationComments(node, options = {}) {
394
491
  const { link = "pathTemplate", linkPosition = "afterDeprecated", splitLines = false } = options;
395
492
  const linkComment = getOperationLink(node, link);
@@ -410,10 +507,10 @@ function buildOperationComments(node, options = {}) {
410
507
  function getOperationParameters(node, options = {}) {
411
508
  const params = caseParams(node.parameters, options.paramsCasing === "original" ? void 0 : "camelcase");
412
509
  return {
413
- path: params.filter((param) => param.in === "path"),
414
- query: params.filter((param) => param.in === "query"),
415
- header: params.filter((param) => param.in === "header"),
416
- cookie: params.filter((param) => param.in === "cookie")
510
+ path: dedupeByCasedName(params.filter((param) => param.in === "path")),
511
+ query: dedupeByCasedName(params.filter((param) => param.in === "query")),
512
+ header: dedupeByCasedName(params.filter((param) => param.in === "header")),
513
+ cookie: dedupeByCasedName(params.filter((param) => param.in === "cookie"))
417
514
  };
418
515
  }
419
516
  function getStatusCodeNumber(statusCode) {
@@ -424,6 +521,15 @@ function isSuccessStatusCode(statusCode) {
424
521
  const code = getStatusCodeNumber(statusCode);
425
522
  return code !== null && code >= 200 && code < 300;
426
523
  }
524
+ function getSuccessResponses(responses) {
525
+ return responses.filter((response) => isSuccessStatusCode(response.statusCode));
526
+ }
527
+ function getOperationSuccessResponses(node) {
528
+ return getSuccessResponses(node.responses);
529
+ }
530
+ function getPrimarySuccessResponse(node) {
531
+ return getOperationSuccessResponses(node)[0] ?? null;
532
+ }
427
533
  //#endregion
428
534
  //#region ../../internals/shared/src/group.ts
429
535
  /**
@@ -456,39 +562,39 @@ function createGroupConfig(group) {
456
562
  };
457
563
  }
458
564
  //#endregion
459
- //#region ../../internals/client/src/builders/parser.ts
565
+ //#region ../../internals/client/src/builders/validatorOptions.ts
460
566
  /**
461
- * Returns `true` when any direction of the parser uses zod (used for dependency checks).
567
+ * Returns `true` when any direction of the validator uses zod (used for dependency checks).
462
568
  */
463
- function isParserEnabled(parser) {
464
- if (!parser) return false;
465
- if (parser === "zod") return true;
466
- return Boolean(parser.request || parser.response);
569
+ function isValidatorEnabled(validator) {
570
+ if (!validator) return false;
571
+ if (validator === "zod") return true;
572
+ return Boolean(validator.request || validator.response);
467
573
  }
468
574
  /**
469
575
  * Returns `'zod'` when request body parsing is enabled, `null` otherwise. The string shorthand
470
576
  * `'zod'` validates the response only, so it does not enable request parsing.
471
577
  */
472
- function resolveRequestParser(parser) {
473
- if (!parser || parser === "zod") return null;
474
- return parser.request ?? null;
578
+ function resolveRequestValidator(validator) {
579
+ if (!validator || validator === "zod") return null;
580
+ return validator.request ?? null;
475
581
  }
476
582
  /**
477
583
  * Returns `'zod'` when query-parameters parsing is enabled, `null` otherwise. Only the object form
478
584
  * `{ request: 'zod' }` enables it.
479
585
  */
480
- function resolveQueryParamsParser(parser) {
481
- if (!parser || parser === "zod") return null;
482
- return parser.request ?? null;
586
+ function resolveQueryParamsValidator(validator) {
587
+ if (!validator || validator === "zod") return null;
588
+ return validator.request ?? null;
483
589
  }
484
590
  /**
485
591
  * Returns `'zod'` when response parsing is enabled, `null` otherwise. The string shorthand `'zod'`
486
592
  * maps to response parsing.
487
593
  */
488
- function resolveResponseParser(parser) {
489
- if (!parser) return null;
490
- if (parser === "zod") return "zod";
491
- return parser.response ?? null;
594
+ function resolveResponseValidator(validator) {
595
+ if (!validator) return null;
596
+ if (validator === "zod") return "zod";
597
+ return validator.response ?? null;
492
598
  }
493
599
  /**
494
600
  * Resolves the zod expression a generated client validates a success response with. Only success
@@ -630,11 +736,13 @@ function buildGroupedOptionsSignature({ node, tsResolver }) {
630
736
  node,
631
737
  tsResolver
632
738
  });
739
+ const { isOptional } = getRequestGroupOptionality(node);
633
740
  return {
634
741
  dataTypeName: requestConfigName,
635
742
  paramsSignature: declarationPrinter.print((0, _kubb_plugin_ts.createFunctionParameters)({ params: [(0, _kubb_plugin_ts.createFunctionParameter)({
636
743
  name: "options",
637
- type: `Options<${requestConfigName}, ThrowOnError>`
744
+ type: `Options<${requestConfigName}, ThrowOnError>`,
745
+ ...isOptional ? { default: "{}" } : {}
638
746
  })] })) ?? "",
639
747
  returnType: `Promise<RequestResult<${resultGenerics}>>`,
640
748
  generics: ["ThrowOnError extends boolean = true"],
@@ -642,23 +750,74 @@ function buildGroupedOptionsSignature({ node, tsResolver }) {
642
750
  };
643
751
  }
644
752
  //#endregion
753
+ //#region ../../internals/client/src/builders/styles.ts
754
+ /**
755
+ * Renders a parameter name as an object-literal key, quoting it when the camelCased name is not a
756
+ * bare identifier (for example a name that starts with a digit) so the emitted literal stays valid.
757
+ */
758
+ function toKey(name) {
759
+ const cased = camelCase(name);
760
+ return isValidVarName(cased) ? cased : JSON.stringify(cased);
761
+ }
762
+ /**
763
+ * Serializes one parameter's metadata into a `{ style, explode }` literal, or `null` when the
764
+ * parameter carries neither. Path and query carry the serialization `style`; header and cookie use a
765
+ * fixed style (`simple` and `form`), so only `explode` is emitted for them.
766
+ */
767
+ function serializeParameter(parameter) {
768
+ const parts = [];
769
+ if ((parameter.in === "path" || parameter.in === "query") && parameter.style) parts.push(`style: '${parameter.style}'`);
770
+ if (parameter.explode !== void 0) parts.push(`explode: ${parameter.explode}`);
771
+ return parts.length > 0 ? `{ ${parts.join(", ")} }` : null;
772
+ }
773
+ /**
774
+ * Builds the per-operation `styles` literal from the operation's parameters, grouped by location and
775
+ * keyed by the camelCased parameter name to match the generated `path` / `query` / `headers` keys.
776
+ * Only parameters whose source defines `style` or `explode` are emitted, so calls without
777
+ * serialization metadata keep the runtime defaults and existing output is unchanged. Returns `null`
778
+ * when no parameter carries metadata.
779
+ *
780
+ * @example
781
+ * ```ts
782
+ * // a path param with { style: 'matrix', explode: true } and a query param with { explode: false }
783
+ * buildStyles({ node }) // "{ path: { id: { style: 'matrix', explode: true } }, query: { tags: { explode: false } } }"
784
+ * ```
785
+ */
786
+ function buildStyles({ node }) {
787
+ if (!_kubb_core.ast.isHttpOperationNode(node)) return null;
788
+ const groups = {
789
+ path: [],
790
+ query: [],
791
+ header: [],
792
+ cookie: []
793
+ };
794
+ for (const parameter of node.parameters) {
795
+ const literal = serializeParameter(parameter);
796
+ if (!literal) continue;
797
+ groups[parameter.in].push(`${toKey(parameter.name)}: ${literal}`);
798
+ }
799
+ const locations = Object.keys(groups).filter((location) => groups[location].length > 0);
800
+ if (locations.length === 0) return null;
801
+ return `{ ${locations.map((location) => `${location}: { ${groups[location].join(", ")} }`).join(", ")} }`;
802
+ }
803
+ //#endregion
645
804
  //#region ../../internals/client/src/builders/validator.ts
646
805
  /**
647
- * Builds the parser-hook expressions for one operation. Request parsing runs before the send;
648
- * response parsing runs on the success body only. Returns `null` expressions when the matching
649
- * parser direction is disabled or the schema is absent.
806
+ * Builds the validator-hook references for one operation. Request validation runs before the send;
807
+ * response validation runs on the success body only. Returns `null` references when the matching
808
+ * direction is disabled or the schema is absent.
650
809
  */
651
- function buildParserHooks({ node, parser, zodResolver }) {
810
+ function buildValidatorHooks({ node, validator, zodResolver }) {
652
811
  const importedZodNames = [];
653
812
  const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
654
- const zodRequestName = zodResolver && resolveRequestParser(parser) === "zod" && hasRequestBody ? zodResolver.resolveDataName?.(node) : null;
655
- const request = zodRequestName ? `(data: unknown) => ${zodRequestName}.parse(data)` : null;
813
+ const zodRequestName = zodResolver && resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.resolveDataName?.(node) : null;
814
+ const request = zodRequestName ?? null;
656
815
  if (zodRequestName) importedZodNames.push(zodRequestName);
657
- const responseParse = zodResolver && resolveResponseParser(parser) === "zod" ? buildZodResponseParse(node, zodResolver) : null;
658
- const response = responseParse ? `(data: unknown) => ${responseParse.expression}.parse(data)` : null;
816
+ const responseParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodResponseParse(node, zodResolver) : null;
817
+ const response = responseParse ? responseParse.expression : null;
659
818
  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;
819
+ const errorParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver) : null;
820
+ const error = errorParse ? errorParse.expression : null;
662
821
  if (errorParse) importedZodNames.push(...errorParse.importNames);
663
822
  return {
664
823
  request,
@@ -674,34 +833,49 @@ function buildParserHooks({ node, parser, zodResolver }) {
674
833
  * single `options` object to the resolved client and returns the `RequestResult`. The type, signature,
675
834
  * and call config are built with the AST factory, and only the jsx-renderer emits the source.
676
835
  */
677
- function Operation({ name, node, tsResolver, zodResolver, parser, security, isExportable = true, isIndexable = true }) {
836
+ function Operation({ name, node, tsResolver, zodResolver, validator, security, isExportable = true, isIndexable = true }) {
678
837
  if (!_kubb_core.ast.isHttpOperationNode(node)) return null;
679
838
  const signature = buildGroupedOptionsSignature({
680
839
  node,
681
840
  tsResolver
682
841
  });
683
- const parsers = buildParserHooks({
842
+ const validators = buildValidatorHooks({
684
843
  node,
685
- parser,
844
+ validator,
686
845
  zodResolver
687
846
  });
688
847
  const securityLiteral = buildSecurityMetadata({ security });
848
+ const stylesLiteral = buildStyles({ node });
689
849
  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
850
+ const bakedRequestContentType = Boolean(node.requestBody?.content?.[0]?.schema) && defaultContentType !== "application/json" ? defaultContentType : null;
851
+ const mergeContentType = Boolean(bakedRequestContentType) && getResponseContentTypeInfo(node).isMultipleContentTypes;
852
+ const contentTypeLiteral = !bakedRequestContentType ? null : mergeContentType ? `contentType: { request: '${bakedRequestContentType}', ...(typeof contentType === 'string' ? { request: contentType } : contentType) }` : `contentType: { request: '${bakedRequestContentType}' }`;
853
+ const eventStream = isEventStream(node);
854
+ const responseType = getResponseType(node);
855
+ const responseTypeLiteral = responseType ? `responseType: '${responseType}'` : null;
856
+ const validatorEntries = [
857
+ validators.request ? `request: ${validators.request}` : null,
858
+ validators.response ? `response: ${validators.response}` : null,
859
+ validators.error ? `error: ${validators.error}` : null
695
860
  ].filter(Boolean);
696
- const parserLiteral = parserEntries.length ? `parser: { ${parserEntries.join(", ")} }` : null;
861
+ const validatorLiteral = validatorEntries.length ? `validator: { ${validatorEntries.join(", ")} }` : null;
697
862
  const callConfig = `{ ${[
698
863
  `method: '${node.method.toUpperCase()}'`,
699
864
  `url: '${Url.toCasedTemplate(node.path, { casing: "camelcase" })}'`,
700
865
  securityLiteral ? `security: ${securityLiteral}` : null,
701
- parserLiteral,
866
+ stylesLiteral ? `styles: ${stylesLiteral}` : null,
867
+ validatorLiteral,
702
868
  contentTypeLiteral,
869
+ responseTypeLiteral,
703
870
  "...config"
704
871
  ].filter(Boolean).join(", ")} }`;
872
+ const eventType = `SuccessOf<${tsResolver.resolveResponsesName(node)}>`;
873
+ const returnType = eventStream ? `Promise<EventStreamResult<${eventType}>>` : signature.returnType;
874
+ const returnStatement = eventStream ? `return toEventStream<${eventType}>(request(${callConfig}))` : buildReturnStatement({
875
+ node,
876
+ tsResolver,
877
+ callConfig
878
+ });
705
879
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
706
880
  name,
707
881
  isExportable,
@@ -711,20 +885,16 @@ function Operation({ name, node, tsResolver, zodResolver, parser, security, isEx
711
885
  export: isExportable,
712
886
  generics: signature.generics,
713
887
  params: signature.paramsSignature,
714
- returnType: signature.returnType,
888
+ returnType,
715
889
  JSDoc: { comments: buildOperationComments(node, {
716
890
  link: "urlPath",
717
891
  linkPosition: "beforeDeprecated",
718
892
  splitLines: true
719
893
  }) },
720
894
  children: [
721
- "const { client: request = client, ...config } = options",
895
+ mergeContentType ? "const { client: request = client, contentType, ...config } = options" : "const { client: request = client, ...config } = options",
722
896
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
723
- buildReturnStatement({
724
- node,
725
- tsResolver,
726
- callConfig
727
- })
897
+ returnStatement
728
898
  ]
729
899
  })
730
900
  });
@@ -733,23 +903,23 @@ function Operation({ name, node, tsResolver, zodResolver, parser, security, isEx
733
903
  //#region ../../internals/client/src/builders/sdkMethod.ts
734
904
  /**
735
905
  * Builds the call config literal forwarded to the contract client, mirroring the shared `Operation`
736
- * component: `{ method, url, security?, parser?, ...config }`. The `...config` spread carries every
906
+ * component: `{ method, url, security?, validator?, ...config }`. The `...config` spread carries every
737
907
  * per-call field (including `throwOnError`), so the method stays a thin wrapper over the contract.
738
908
  */
739
- function buildCallConfig({ node, parser, zodResolver, security }) {
740
- const parsers = buildParserHooks({
909
+ function buildCallConfig({ node, validator, zodResolver, security }) {
910
+ const validators = buildValidatorHooks({
741
911
  node,
742
- parser,
912
+ validator,
743
913
  zodResolver
744
914
  });
745
- const parserEntries = [parsers.request ? `request: ${parsers.request}` : null, parsers.response ? `response: ${parsers.response}` : null].filter(Boolean);
746
- const parserLiteral = parserEntries.length ? `parser: { ${parserEntries.join(", ")} }` : null;
915
+ const validatorEntries = [validators.request ? `request: ${validators.request}` : null, validators.response ? `response: ${validators.response}` : null].filter(Boolean);
916
+ const validatorLiteral = validatorEntries.length ? `validator: { ${validatorEntries.join(", ")} }` : null;
747
917
  const securityLiteral = buildSecurityMetadata({ security });
748
918
  return `{ ${[
749
919
  `method: '${node.method.toUpperCase()}'`,
750
920
  `url: '${Url.toCasedTemplate(node.path, { casing: "camelcase" })}'`,
751
921
  securityLiteral ? `security: ${securityLiteral}` : null,
752
- parserLiteral,
922
+ validatorLiteral,
753
923
  "...config"
754
924
  ].filter(Boolean).join(", ")} }`;
755
925
  }
@@ -759,7 +929,7 @@ function buildCallConfig({ node, parser, zodResolver, security }) {
759
929
  * returns the `RequestResult`. A per-call `options.client` still overrides the instance client, so
760
930
  * one operation can be routed to a different environment without a new instance.
761
931
  */
762
- function buildSdkMethod({ node, name, tsResolver, zodResolver, parser, security }) {
932
+ function buildSdkMethod({ node, name, tsResolver, zodResolver, validator, security }) {
763
933
  if (!_kubb_core.ast.isHttpOperationNode(node)) return "";
764
934
  const signature = buildGroupedOptionsSignature({
765
935
  node,
@@ -770,7 +940,7 @@ function buildSdkMethod({ node, name, tsResolver, zodResolver, parser, security
770
940
  tsResolver,
771
941
  callConfig: buildCallConfig({
772
942
  node,
773
- parser,
943
+ validator,
774
944
  zodResolver,
775
945
  security
776
946
  })
@@ -796,13 +966,13 @@ function buildSdkMethod({ node, name, tsResolver, zodResolver, parser, security
796
966
  * instance: `const api = new PetClient({ baseURL }); api.getPetById(...)`. A per-call `client` option
797
967
  * still overrides the instance client for a one-off call.
798
968
  */
799
- function SdkClient({ name, isExportable = true, isIndexable = true, operations, parser, children }) {
969
+ function SdkClient({ name, isExportable = true, isIndexable = true, operations, validator, children }) {
800
970
  const methods = operations.map(({ node, name: methodName, tsResolver, zodResolver, security }) => buildSdkMethod({
801
971
  node,
802
972
  name: methodName,
803
973
  tsResolver,
804
974
  zodResolver,
805
- parser,
975
+ validator,
806
976
  security
807
977
  }));
808
978
  const classCode = `export class ${name} {\n${[
@@ -848,13 +1018,13 @@ function SdkFacade({ name, isExportable = true, isIndexable = true, members, chi
848
1018
  function resolveTypeImportNames(node, tsResolver) {
849
1019
  return [tsResolver.resolveRequestConfigName(node), tsResolver.resolveResponsesName(node)];
850
1020
  }
851
- function resolveZodImportNames(node, zodResolver, parser) {
1021
+ function resolveZodImportNames(node, zodResolver, validator) {
852
1022
  const { query: queryParams } = getOperationParameters(node, { paramsCasing: "original" });
853
1023
  return [
854
- resolveResponseParser(parser) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
855
- resolveResponseParser(parser) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
856
- resolveRequestParser(parser) === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
857
- resolveQueryParamsParser(parser) === "zod" && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]) : null
1024
+ resolveResponseValidator(validator) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
1025
+ resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
1026
+ resolveRequestValidator(validator) === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
1027
+ resolveQueryParamsValidator(validator) === "zod" && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]) : null
858
1028
  ].filter((n) => Boolean(n));
859
1029
  }
860
1030
  /**
@@ -863,11 +1033,11 @@ function resolveZodImportNames(node, zodResolver, parser) {
863
1033
  */
864
1034
  function buildControllers(nodes, ctx) {
865
1035
  const { driver, resolver, root } = ctx;
866
- const { output, group, parser } = ctx.options;
1036
+ const { output, group, validator } = ctx.options;
867
1037
  const pluginTs = driver.getPlugin(_kubb_plugin_ts.pluginTsName);
868
1038
  const tsResolver = driver.getResolver(_kubb_plugin_ts.pluginTsName);
869
1039
  const tsPluginOptions = pluginTs.options;
870
- const pluginZod = isParserEnabled(parser) ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
1040
+ const pluginZod = isValidatorEnabled(validator) ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
871
1041
  const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : null;
872
1042
  const document = ctx.adapter.document;
873
1043
  function buildOperationData(node) {
@@ -954,7 +1124,7 @@ function createSdkGenerator() {
954
1124
  renderer: _kubb_renderer_jsx.jsxRenderer,
955
1125
  operations(nodes, ctx) {
956
1126
  const { config, resolver, root } = ctx;
957
- const { output, group, parser, sdk } = ctx.options;
1127
+ const { output, group, validator, sdk } = ctx.options;
958
1128
  if (!ctx.driver.getPlugin(_kubb_plugin_ts.pluginTsName) || !sdk) return null;
959
1129
  const controllers = buildControllers(nodes, ctx);
960
1130
  const clientPath = node_path.default.resolve(root, ".kubb/client.ts");
@@ -979,9 +1149,9 @@ function createSdkGenerator() {
979
1149
  file: op.typeFile,
980
1150
  names: resolveTypeImportNames(op.node, op.tsResolver)
981
1151
  }));
982
- const { namesByPath: zodNamesByPath, filesByPath: zodFilesByPath } = isParserEnabled(parser) ? collectImportsByFile(ops, (op) => ({
1152
+ const { namesByPath: zodNamesByPath, filesByPath: zodFilesByPath } = isValidatorEnabled(validator) ? collectImportsByFile(ops, (op) => ({
983
1153
  file: op.zodFile,
984
- names: op.zodResolver ? resolveZodImportNames(op.node, op.zodResolver, parser) : []
1154
+ names: op.zodResolver ? resolveZodImportNames(op.node, op.zodResolver, validator) : []
985
1155
  })) : {
986
1156
  namesByPath: /* @__PURE__ */ new Map(),
987
1157
  filesByPath: /* @__PURE__ */ new Map()
@@ -1009,7 +1179,7 @@ function createSdkGenerator() {
1009
1179
  path: clientPath,
1010
1180
  isTypeOnly: true
1011
1181
  }),
1012
- parser === "zod" && ops.some((op) => op.node.requestBody?.content?.[0]?.schema != null) && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1182
+ validator === "zod" && ops.some((op) => op.node.requestBody?.content?.[0]?.schema != null) && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1013
1183
  name: ["z"],
1014
1184
  path: "zod",
1015
1185
  isTypeOnly: true
@@ -1020,7 +1190,7 @@ function createSdkGenerator() {
1020
1190
  path: typeFilesByPath.get(filePath).path,
1021
1191
  isTypeOnly: true
1022
1192
  }, filePath)),
1023
- isParserEnabled(parser) && Array.from(zodNamesByPath.entries()).map(([filePath, set]) => /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1193
+ isValidatorEnabled(validator) && Array.from(zodNamesByPath.entries()).map(([filePath, set]) => /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1024
1194
  name: Array.from(set),
1025
1195
  root: file.path,
1026
1196
  path: zodFilesByPath.get(filePath).path
@@ -1028,7 +1198,7 @@ function createSdkGenerator() {
1028
1198
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(SdkClient, {
1029
1199
  name: className,
1030
1200
  operations: ops,
1031
- parser
1201
+ validator
1032
1202
  })
1033
1203
  ]
1034
1204
  }, file.path);
@@ -1139,18 +1309,18 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
1139
1309
  operation(node, ctx) {
1140
1310
  if (!_kubb_core.ast.isHttpOperationNode(node)) return null;
1141
1311
  const { config, driver, resolver, root } = ctx;
1142
- const { output, parser, group } = ctx.options;
1312
+ const { output, validator, group } = ctx.options;
1143
1313
  const pluginTs = driver.getPlugin(_kubb_plugin_ts.pluginTsName);
1144
1314
  if (!pluginTs) return null;
1145
1315
  const tsResolver = driver.getResolver(_kubb_plugin_ts.pluginTsName);
1146
- const pluginZod = resolveResponseParser(parser) === "zod" || resolveRequestParser(parser) === "zod" ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
1316
+ const pluginZod = resolveResponseValidator(validator) === "zod" || resolveRequestValidator(validator) === "zod" ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
1147
1317
  const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : null;
1148
1318
  const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
1149
1319
  const importedTypeNames = [tsResolver.resolveRequestConfigName(node), tsResolver.resolveResponsesName(node)];
1150
1320
  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
1321
+ resolveResponseValidator(validator) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
1322
+ resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
1323
+ resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.resolveDataName?.(node) : null
1154
1324
  ].filter((name) => Boolean(name)) : [];
1155
1325
  const meta = {
1156
1326
  name: resolver.resolveName(node.operationId),
@@ -1176,6 +1346,7 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
1176
1346
  path: node.path
1177
1347
  });
1178
1348
  const clientPath = node_path.default.resolve(root, ".kubb/client.ts");
1349
+ const eventStream = isEventStream(node);
1179
1350
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
1180
1351
  baseName: meta.file.baseName,
1181
1352
  path: meta.file.path,
@@ -1198,12 +1369,16 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
1198
1369
  }),
1199
1370
  children: [
1200
1371
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1201
- name: ["client"],
1372
+ name: eventStream ? ["client", "toEventStream"] : ["client"],
1202
1373
  root: meta.file.path,
1203
1374
  path: clientPath
1204
1375
  }),
1205
1376
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1206
- name: ["Options", "RequestResult"],
1377
+ name: eventStream ? [
1378
+ "Options",
1379
+ "EventStreamResult",
1380
+ "SuccessOf"
1381
+ ] : ["Options", "RequestResult"],
1207
1382
  root: meta.file.path,
1208
1383
  path: clientPath,
1209
1384
  isTypeOnly: true
@@ -1224,7 +1399,7 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
1224
1399
  node,
1225
1400
  tsResolver,
1226
1401
  zodResolver,
1227
- parser,
1402
+ validator,
1228
1403
  security
1229
1404
  })
1230
1405
  ]
@@ -1233,12 +1408,15 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
1233
1408
  });
1234
1409
  //#endregion
1235
1410
  //#region src/templates.ts
1411
+ /** Absolute path to the fetch client template, copied into `.kubb/client.ts`. */
1412
+ const fetchClientTemplatePath = (0, node_url.fileURLToPath)(new URL("../templates/fetch.ts", require("url").pathToFileURL(__filename).href));
1413
+ /** Absolute path to the fetch serializers template, copied into `.kubb/serializers.ts`. */
1414
+ const fetchSerializersTemplatePath = (0, node_url.fileURLToPath)(new URL("../templates/serializers.ts", require("url").pathToFileURL(__filename).href));
1236
1415
  /**
1237
- * Absolute path to the fetch client runtime template, resolved relative to this package's own
1238
- * location so it stays correct no matter which package imports it. Pass it to a file node's `copy`
1239
- * field to emit the runtime into the generated `.kubb/client.ts` verbatim.
1416
+ * Absolute path to the Standard Schema runtime template. Pass it to a file node's `copy` field to
1417
+ * emit the helper into the generated `.kubb/standardSchema.ts` verbatim.
1240
1418
  */
1241
- const fetchClientTemplatePath = (0, node_url.fileURLToPath)(new URL("../templates/fetch.ts", require("url").pathToFileURL(__filename).href));
1419
+ const standardSchemaTemplatePath = (0, node_url.fileURLToPath)(new URL("../templates/standardSchema.ts", require("url").pathToFileURL(__filename).href));
1242
1420
  //#endregion
1243
1421
  //#region src/plugin.ts
1244
1422
  /**
@@ -1272,7 +1450,7 @@ const pluginFetch = (0, _kubb_core.definePlugin)((options) => {
1272
1450
  const { output = {
1273
1451
  path: "clients",
1274
1452
  barrel: { type: "named" }
1275
- }, exclude = [], include, override = [], baseURL, parser = false, group, sdk, resolver: userResolver } = options;
1453
+ }, exclude = [], include, override = [], baseURL, validator = false, group, sdk, resolver: userResolver } = options;
1276
1454
  const resolved = {
1277
1455
  output,
1278
1456
  exclude,
@@ -1280,7 +1458,7 @@ const pluginFetch = (0, _kubb_core.definePlugin)((options) => {
1280
1458
  override,
1281
1459
  group: createGroupConfig(group),
1282
1460
  baseURL,
1283
- parser,
1461
+ validator,
1284
1462
  sdk: sdk ? {
1285
1463
  mode: sdk.mode ?? "tag",
1286
1464
  name: sdk.name
@@ -1294,19 +1472,29 @@ const pluginFetch = (0, _kubb_core.definePlugin)((options) => {
1294
1472
  return {
1295
1473
  name: pluginFetchName,
1296
1474
  options,
1297
- dependencies: [_kubb_plugin_ts.pluginTsName, isParserEnabled(resolved.parser) ? _kubb_plugin_zod.pluginZodName : null].filter((dependency) => Boolean(dependency)),
1475
+ dependencies: [_kubb_plugin_ts.pluginTsName, isValidatorEnabled(resolved.validator) ? _kubb_plugin_zod.pluginZodName : null].filter((dependency) => Boolean(dependency)),
1298
1476
  hooks: { "kubb:plugin:setup"(ctx) {
1299
1477
  ctx.setOptions(resolved);
1300
1478
  ctx.setResolver(resolved.resolver);
1301
1479
  ctx.setMacros([...defaultMacros, ...options.macros ?? []]);
1302
1480
  ctx.addGenerator(...selectedGenerators);
1303
1481
  const root = node_path.default.resolve(ctx.config.root, ctx.config.output.path);
1482
+ ctx.injectFile({
1483
+ baseName: "serializers.ts",
1484
+ path: node_path.default.resolve(root, ".kubb/serializers.ts"),
1485
+ copy: fetchSerializersTemplatePath
1486
+ });
1304
1487
  ctx.injectFile({
1305
1488
  baseName: "client.ts",
1306
1489
  path: node_path.default.resolve(root, ".kubb/client.ts"),
1307
1490
  copy: fetchClientTemplatePath,
1308
1491
  footer: baseURL ? `client.setConfig({ baseURL: ${JSON.stringify(baseURL)} })` : void 0
1309
1492
  });
1493
+ ctx.injectFile({
1494
+ baseName: "standardSchema.ts",
1495
+ path: node_path.default.resolve(root, ".kubb/standardSchema.ts"),
1496
+ copy: standardSchemaTemplatePath
1497
+ });
1310
1498
  } }
1311
1499
  };
1312
1500
  });