@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.js CHANGED
@@ -77,7 +77,7 @@ function toFilePath(name, caseLast = camelCase) {
77
77
  * JavaScript and Java reserved words.
78
78
  * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
79
79
  */
80
- const reservedWords = new Set([
80
+ const reservedWords = /* @__PURE__ */ new Set([
81
81
  "abstract",
82
82
  "arguments",
83
83
  "boolean",
@@ -326,6 +326,23 @@ function caseParams(params, casing) {
326
326
  caseParamsCache.set(params, result);
327
327
  return result;
328
328
  }
329
+ /**
330
+ * Drops parameters that collapse to the same property identity once camelCased, keeping the first.
331
+ *
332
+ * Some specs declare the same parameter twice under different casings (for example AWS S3 lists both
333
+ * `max-uploads` and `MaxUploads`). Both resolve to one output property, so emitting both would yield
334
+ * an object type with a duplicate member, which TypeScript rejects. De-duplicate by the camelCased
335
+ * identity so the resulting group is collision-free regardless of the names each caller carries.
336
+ */
337
+ function dedupeByCasedName(params) {
338
+ const seen = /* @__PURE__ */ new Set();
339
+ return params.filter((param) => {
340
+ const key = camelCase(param.name);
341
+ if (seen.has(key)) return false;
342
+ seen.add(key);
343
+ return true;
344
+ });
345
+ }
329
346
  //#endregion
330
347
  //#region ../../internals/shared/src/operation.ts
331
348
  /**
@@ -364,6 +381,86 @@ function getContentTypeInfo(node) {
364
381
  hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
365
382
  };
366
383
  }
384
+ /**
385
+ * The request-body counterpart for the primary success response: the content types it documents and
386
+ * whether several are present, so the client can let a caller pick which one to accept.
387
+ */
388
+ function getResponseContentTypeInfo(node) {
389
+ const contentTypes = getPrimarySuccessResponse(node)?.content?.map((e) => e.contentType) ?? [];
390
+ const isMultipleContentTypes = contentTypes.length > 1;
391
+ return {
392
+ contentTypes,
393
+ isMultipleContentTypes,
394
+ contentTypeUnion: isMultipleContentTypes ? contentTypes.map((ct) => JSON.stringify(ct)).join(" | ") : "",
395
+ defaultContentType: contentTypes[0] ?? "application/json",
396
+ hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
397
+ };
398
+ }
399
+ /**
400
+ * Reads the single base content type of an operation's primary success response, lowercased and
401
+ * stripped of any `; charset=...` suffix. Returns `undefined` when the response declares zero or
402
+ * more than one content type, since neither case has a single type to act on.
403
+ */
404
+ function getPrimarySuccessContentType(node) {
405
+ const contentTypes = getPrimarySuccessResponse(node)?.content?.map((entry) => entry.contentType) ?? [];
406
+ if (contentTypes.length !== 1) return void 0;
407
+ return contentTypes[0].split(";")[0].trim().toLowerCase();
408
+ }
409
+ /**
410
+ * Whether an operation streams its primary success response as Server-Sent Events
411
+ * (`text/event-stream`). The client generator uses this to return a typed event stream instead of a
412
+ * one-shot `RequestResult`.
413
+ */
414
+ function isEventStream(node) {
415
+ return getPrimarySuccessContentType(node) === "text/event-stream";
416
+ }
417
+ /**
418
+ * Derives the default `responseType` for an operation from its primary success response.
419
+ *
420
+ * Returns a value only when that response declares a single non-JSON content type. `text/event-stream`
421
+ * and other binary types (`application/octet-stream`, `application/pdf`, `image/*`, `audio/*`,
422
+ * `video/*`) map to a stream or `'blob'`, and other `text/*` maps to `'text'`. Otherwise `undefined`,
423
+ * leaving the runtime client's `Content-Type` auto-detection in charge.
424
+ */
425
+ function getResponseType(node) {
426
+ const baseType = getPrimarySuccessContentType(node);
427
+ if (!baseType) return void 0;
428
+ if (baseType === "application/json" || baseType.endsWith("+json") || baseType === "text/json") return void 0;
429
+ if (baseType === "text/event-stream") return "stream";
430
+ if (baseType.startsWith("text/")) return "text";
431
+ if (baseType === "application/octet-stream" || baseType === "application/pdf" || /^(image|audio|video)\//.test(baseType)) return "blob";
432
+ }
433
+ /**
434
+ * Which of the grouped request options an operation carries.
435
+ */
436
+ function getRequestGroups(node) {
437
+ const { path, query, header } = getOperationParameters(node);
438
+ return {
439
+ path: path.length > 0,
440
+ query: query.length > 0,
441
+ body: Boolean(node.requestBody?.content?.[0]?.schema),
442
+ headers: header.length > 0
443
+ };
444
+ }
445
+ /**
446
+ * Resolves which grouped request options an operation carries together with whether each group
447
+ * holds a required member. The grouped parameter stays optional only when nothing inside it is
448
+ * required, matching the generated `RequestConfig` type.
449
+ */
450
+ function getRequestGroupOptionality(node) {
451
+ const groups = getRequestGroups(node);
452
+ const { path, query, header } = getOperationParameters(node);
453
+ const hasRequiredPath = path.some((param) => param.required);
454
+ const hasRequiredQuery = query.some((param) => param.required);
455
+ const hasRequiredHeader = header.some((param) => param.required);
456
+ return {
457
+ groups,
458
+ hasRequiredPath,
459
+ hasRequiredQuery,
460
+ hasRequiredHeader,
461
+ isOptional: !hasRequiredPath && !hasRequiredQuery && !hasRequiredHeader && !groups.body
462
+ };
463
+ }
367
464
  function buildOperationComments(node, options = {}) {
368
465
  const { link = "pathTemplate", linkPosition = "afterDeprecated", splitLines = false } = options;
369
466
  const linkComment = getOperationLink(node, link);
@@ -384,10 +481,10 @@ function buildOperationComments(node, options = {}) {
384
481
  function getOperationParameters(node, options = {}) {
385
482
  const params = caseParams(node.parameters, options.paramsCasing === "original" ? void 0 : "camelcase");
386
483
  return {
387
- path: params.filter((param) => param.in === "path"),
388
- query: params.filter((param) => param.in === "query"),
389
- header: params.filter((param) => param.in === "header"),
390
- cookie: params.filter((param) => param.in === "cookie")
484
+ path: dedupeByCasedName(params.filter((param) => param.in === "path")),
485
+ query: dedupeByCasedName(params.filter((param) => param.in === "query")),
486
+ header: dedupeByCasedName(params.filter((param) => param.in === "header")),
487
+ cookie: dedupeByCasedName(params.filter((param) => param.in === "cookie"))
391
488
  };
392
489
  }
393
490
  function getStatusCodeNumber(statusCode) {
@@ -398,6 +495,15 @@ function isSuccessStatusCode(statusCode) {
398
495
  const code = getStatusCodeNumber(statusCode);
399
496
  return code !== null && code >= 200 && code < 300;
400
497
  }
498
+ function getSuccessResponses(responses) {
499
+ return responses.filter((response) => isSuccessStatusCode(response.statusCode));
500
+ }
501
+ function getOperationSuccessResponses(node) {
502
+ return getSuccessResponses(node.responses);
503
+ }
504
+ function getPrimarySuccessResponse(node) {
505
+ return getOperationSuccessResponses(node)[0] ?? null;
506
+ }
401
507
  //#endregion
402
508
  //#region ../../internals/shared/src/group.ts
403
509
  /**
@@ -430,39 +536,39 @@ function createGroupConfig(group) {
430
536
  };
431
537
  }
432
538
  //#endregion
433
- //#region ../../internals/client/src/builders/parser.ts
539
+ //#region ../../internals/client/src/builders/validatorOptions.ts
434
540
  /**
435
- * Returns `true` when any direction of the parser uses zod (used for dependency checks).
541
+ * Returns `true` when any direction of the validator uses zod (used for dependency checks).
436
542
  */
437
- function isParserEnabled(parser) {
438
- if (!parser) return false;
439
- if (parser === "zod") return true;
440
- return Boolean(parser.request || parser.response);
543
+ function isValidatorEnabled(validator) {
544
+ if (!validator) return false;
545
+ if (validator === "zod") return true;
546
+ return Boolean(validator.request || validator.response);
441
547
  }
442
548
  /**
443
549
  * Returns `'zod'` when request body parsing is enabled, `null` otherwise. The string shorthand
444
550
  * `'zod'` validates the response only, so it does not enable request parsing.
445
551
  */
446
- function resolveRequestParser(parser) {
447
- if (!parser || parser === "zod") return null;
448
- return parser.request ?? null;
552
+ function resolveRequestValidator(validator) {
553
+ if (!validator || validator === "zod") return null;
554
+ return validator.request ?? null;
449
555
  }
450
556
  /**
451
557
  * Returns `'zod'` when query-parameters parsing is enabled, `null` otherwise. Only the object form
452
558
  * `{ request: 'zod' }` enables it.
453
559
  */
454
- function resolveQueryParamsParser(parser) {
455
- if (!parser || parser === "zod") return null;
456
- return parser.request ?? null;
560
+ function resolveQueryParamsValidator(validator) {
561
+ if (!validator || validator === "zod") return null;
562
+ return validator.request ?? null;
457
563
  }
458
564
  /**
459
565
  * Returns `'zod'` when response parsing is enabled, `null` otherwise. The string shorthand `'zod'`
460
566
  * maps to response parsing.
461
567
  */
462
- function resolveResponseParser(parser) {
463
- if (!parser) return null;
464
- if (parser === "zod") return "zod";
465
- return parser.response ?? null;
568
+ function resolveResponseValidator(validator) {
569
+ if (!validator) return null;
570
+ if (validator === "zod") return "zod";
571
+ return validator.response ?? null;
466
572
  }
467
573
  /**
468
574
  * Resolves the zod expression a generated client validates a success response with. Only success
@@ -604,11 +710,13 @@ function buildGroupedOptionsSignature({ node, tsResolver }) {
604
710
  node,
605
711
  tsResolver
606
712
  });
713
+ const { isOptional } = getRequestGroupOptionality(node);
607
714
  return {
608
715
  dataTypeName: requestConfigName,
609
716
  paramsSignature: declarationPrinter.print(createFunctionParameters({ params: [createFunctionParameter({
610
717
  name: "options",
611
- type: `Options<${requestConfigName}, ThrowOnError>`
718
+ type: `Options<${requestConfigName}, ThrowOnError>`,
719
+ ...isOptional ? { default: "{}" } : {}
612
720
  })] })) ?? "",
613
721
  returnType: `Promise<RequestResult<${resultGenerics}>>`,
614
722
  generics: ["ThrowOnError extends boolean = true"],
@@ -616,23 +724,74 @@ function buildGroupedOptionsSignature({ node, tsResolver }) {
616
724
  };
617
725
  }
618
726
  //#endregion
727
+ //#region ../../internals/client/src/builders/styles.ts
728
+ /**
729
+ * Renders a parameter name as an object-literal key, quoting it when the camelCased name is not a
730
+ * bare identifier (for example a name that starts with a digit) so the emitted literal stays valid.
731
+ */
732
+ function toKey(name) {
733
+ const cased = camelCase(name);
734
+ return isValidVarName(cased) ? cased : JSON.stringify(cased);
735
+ }
736
+ /**
737
+ * Serializes one parameter's metadata into a `{ style, explode }` literal, or `null` when the
738
+ * parameter carries neither. Path and query carry the serialization `style`; header and cookie use a
739
+ * fixed style (`simple` and `form`), so only `explode` is emitted for them.
740
+ */
741
+ function serializeParameter(parameter) {
742
+ const parts = [];
743
+ if ((parameter.in === "path" || parameter.in === "query") && parameter.style) parts.push(`style: '${parameter.style}'`);
744
+ if (parameter.explode !== void 0) parts.push(`explode: ${parameter.explode}`);
745
+ return parts.length > 0 ? `{ ${parts.join(", ")} }` : null;
746
+ }
747
+ /**
748
+ * Builds the per-operation `styles` literal from the operation's parameters, grouped by location and
749
+ * keyed by the camelCased parameter name to match the generated `path` / `query` / `headers` keys.
750
+ * Only parameters whose source defines `style` or `explode` are emitted, so calls without
751
+ * serialization metadata keep the runtime defaults and existing output is unchanged. Returns `null`
752
+ * when no parameter carries metadata.
753
+ *
754
+ * @example
755
+ * ```ts
756
+ * // a path param with { style: 'matrix', explode: true } and a query param with { explode: false }
757
+ * buildStyles({ node }) // "{ path: { id: { style: 'matrix', explode: true } }, query: { tags: { explode: false } } }"
758
+ * ```
759
+ */
760
+ function buildStyles({ node }) {
761
+ if (!ast.isHttpOperationNode(node)) return null;
762
+ const groups = {
763
+ path: [],
764
+ query: [],
765
+ header: [],
766
+ cookie: []
767
+ };
768
+ for (const parameter of node.parameters) {
769
+ const literal = serializeParameter(parameter);
770
+ if (!literal) continue;
771
+ groups[parameter.in].push(`${toKey(parameter.name)}: ${literal}`);
772
+ }
773
+ const locations = Object.keys(groups).filter((location) => groups[location].length > 0);
774
+ if (locations.length === 0) return null;
775
+ return `{ ${locations.map((location) => `${location}: { ${groups[location].join(", ")} }`).join(", ")} }`;
776
+ }
777
+ //#endregion
619
778
  //#region ../../internals/client/src/builders/validator.ts
620
779
  /**
621
- * Builds the parser-hook expressions for one operation. Request parsing runs before the send;
622
- * response parsing runs on the success body only. Returns `null` expressions when the matching
623
- * parser direction is disabled or the schema is absent.
780
+ * Builds the validator-hook references for one operation. Request validation runs before the send;
781
+ * response validation runs on the success body only. Returns `null` references when the matching
782
+ * direction is disabled or the schema is absent.
624
783
  */
625
- function buildParserHooks({ node, parser, zodResolver }) {
784
+ function buildValidatorHooks({ node, validator, zodResolver }) {
626
785
  const importedZodNames = [];
627
786
  const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
628
- const zodRequestName = zodResolver && resolveRequestParser(parser) === "zod" && hasRequestBody ? zodResolver.resolveDataName?.(node) : null;
629
- const request = zodRequestName ? `(data: unknown) => ${zodRequestName}.parse(data)` : null;
787
+ const zodRequestName = zodResolver && resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.resolveDataName?.(node) : null;
788
+ const request = zodRequestName ?? null;
630
789
  if (zodRequestName) importedZodNames.push(zodRequestName);
631
- const responseParse = zodResolver && resolveResponseParser(parser) === "zod" ? buildZodResponseParse(node, zodResolver) : null;
632
- const response = responseParse ? `(data: unknown) => ${responseParse.expression}.parse(data)` : null;
790
+ const responseParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodResponseParse(node, zodResolver) : null;
791
+ const response = responseParse ? responseParse.expression : null;
633
792
  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;
793
+ const errorParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver) : null;
794
+ const error = errorParse ? errorParse.expression : null;
636
795
  if (errorParse) importedZodNames.push(...errorParse.importNames);
637
796
  return {
638
797
  request,
@@ -648,34 +807,49 @@ function buildParserHooks({ node, parser, zodResolver }) {
648
807
  * single `options` object to the resolved client and returns the `RequestResult`. The type, signature,
649
808
  * and call config are built with the AST factory, and only the jsx-renderer emits the source.
650
809
  */
651
- function Operation({ name, node, tsResolver, zodResolver, parser, security, isExportable = true, isIndexable = true }) {
810
+ function Operation({ name, node, tsResolver, zodResolver, validator, security, isExportable = true, isIndexable = true }) {
652
811
  if (!ast.isHttpOperationNode(node)) return null;
653
812
  const signature = buildGroupedOptionsSignature({
654
813
  node,
655
814
  tsResolver
656
815
  });
657
- const parsers = buildParserHooks({
816
+ const validators = buildValidatorHooks({
658
817
  node,
659
- parser,
818
+ validator,
660
819
  zodResolver
661
820
  });
662
821
  const securityLiteral = buildSecurityMetadata({ security });
822
+ const stylesLiteral = buildStyles({ node });
663
823
  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
824
+ const bakedRequestContentType = Boolean(node.requestBody?.content?.[0]?.schema) && defaultContentType !== "application/json" ? defaultContentType : null;
825
+ const mergeContentType = Boolean(bakedRequestContentType) && getResponseContentTypeInfo(node).isMultipleContentTypes;
826
+ const contentTypeLiteral = !bakedRequestContentType ? null : mergeContentType ? `contentType: { request: '${bakedRequestContentType}', ...(typeof contentType === 'string' ? { request: contentType } : contentType) }` : `contentType: { request: '${bakedRequestContentType}' }`;
827
+ const eventStream = isEventStream(node);
828
+ const responseType = getResponseType(node);
829
+ const responseTypeLiteral = responseType ? `responseType: '${responseType}'` : null;
830
+ const validatorEntries = [
831
+ validators.request ? `request: ${validators.request}` : null,
832
+ validators.response ? `response: ${validators.response}` : null,
833
+ validators.error ? `error: ${validators.error}` : null
669
834
  ].filter(Boolean);
670
- const parserLiteral = parserEntries.length ? `parser: { ${parserEntries.join(", ")} }` : null;
835
+ const validatorLiteral = validatorEntries.length ? `validator: { ${validatorEntries.join(", ")} }` : null;
671
836
  const callConfig = `{ ${[
672
837
  `method: '${node.method.toUpperCase()}'`,
673
838
  `url: '${Url.toCasedTemplate(node.path, { casing: "camelcase" })}'`,
674
839
  securityLiteral ? `security: ${securityLiteral}` : null,
675
- parserLiteral,
840
+ stylesLiteral ? `styles: ${stylesLiteral}` : null,
841
+ validatorLiteral,
676
842
  contentTypeLiteral,
843
+ responseTypeLiteral,
677
844
  "...config"
678
845
  ].filter(Boolean).join(", ")} }`;
846
+ const eventType = `SuccessOf<${tsResolver.resolveResponsesName(node)}>`;
847
+ const returnType = eventStream ? `Promise<EventStreamResult<${eventType}>>` : signature.returnType;
848
+ const returnStatement = eventStream ? `return toEventStream<${eventType}>(request(${callConfig}))` : buildReturnStatement({
849
+ node,
850
+ tsResolver,
851
+ callConfig
852
+ });
679
853
  return /* @__PURE__ */ jsx(File.Source, {
680
854
  name,
681
855
  isExportable,
@@ -685,20 +859,16 @@ function Operation({ name, node, tsResolver, zodResolver, parser, security, isEx
685
859
  export: isExportable,
686
860
  generics: signature.generics,
687
861
  params: signature.paramsSignature,
688
- returnType: signature.returnType,
862
+ returnType,
689
863
  JSDoc: { comments: buildOperationComments(node, {
690
864
  link: "urlPath",
691
865
  linkPosition: "beforeDeprecated",
692
866
  splitLines: true
693
867
  }) },
694
868
  children: [
695
- "const { client: request = client, ...config } = options",
869
+ mergeContentType ? "const { client: request = client, contentType, ...config } = options" : "const { client: request = client, ...config } = options",
696
870
  /* @__PURE__ */ jsx("br", {}),
697
- buildReturnStatement({
698
- node,
699
- tsResolver,
700
- callConfig
701
- })
871
+ returnStatement
702
872
  ]
703
873
  })
704
874
  });
@@ -707,23 +877,23 @@ function Operation({ name, node, tsResolver, zodResolver, parser, security, isEx
707
877
  //#region ../../internals/client/src/builders/sdkMethod.ts
708
878
  /**
709
879
  * Builds the call config literal forwarded to the contract client, mirroring the shared `Operation`
710
- * component: `{ method, url, security?, parser?, ...config }`. The `...config` spread carries every
880
+ * component: `{ method, url, security?, validator?, ...config }`. The `...config` spread carries every
711
881
  * per-call field (including `throwOnError`), so the method stays a thin wrapper over the contract.
712
882
  */
713
- function buildCallConfig({ node, parser, zodResolver, security }) {
714
- const parsers = buildParserHooks({
883
+ function buildCallConfig({ node, validator, zodResolver, security }) {
884
+ const validators = buildValidatorHooks({
715
885
  node,
716
- parser,
886
+ validator,
717
887
  zodResolver
718
888
  });
719
- const parserEntries = [parsers.request ? `request: ${parsers.request}` : null, parsers.response ? `response: ${parsers.response}` : null].filter(Boolean);
720
- const parserLiteral = parserEntries.length ? `parser: { ${parserEntries.join(", ")} }` : null;
889
+ const validatorEntries = [validators.request ? `request: ${validators.request}` : null, validators.response ? `response: ${validators.response}` : null].filter(Boolean);
890
+ const validatorLiteral = validatorEntries.length ? `validator: { ${validatorEntries.join(", ")} }` : null;
721
891
  const securityLiteral = buildSecurityMetadata({ security });
722
892
  return `{ ${[
723
893
  `method: '${node.method.toUpperCase()}'`,
724
894
  `url: '${Url.toCasedTemplate(node.path, { casing: "camelcase" })}'`,
725
895
  securityLiteral ? `security: ${securityLiteral}` : null,
726
- parserLiteral,
896
+ validatorLiteral,
727
897
  "...config"
728
898
  ].filter(Boolean).join(", ")} }`;
729
899
  }
@@ -733,7 +903,7 @@ function buildCallConfig({ node, parser, zodResolver, security }) {
733
903
  * returns the `RequestResult`. A per-call `options.client` still overrides the instance client, so
734
904
  * one operation can be routed to a different environment without a new instance.
735
905
  */
736
- function buildSdkMethod({ node, name, tsResolver, zodResolver, parser, security }) {
906
+ function buildSdkMethod({ node, name, tsResolver, zodResolver, validator, security }) {
737
907
  if (!ast.isHttpOperationNode(node)) return "";
738
908
  const signature = buildGroupedOptionsSignature({
739
909
  node,
@@ -744,7 +914,7 @@ function buildSdkMethod({ node, name, tsResolver, zodResolver, parser, security
744
914
  tsResolver,
745
915
  callConfig: buildCallConfig({
746
916
  node,
747
- parser,
917
+ validator,
748
918
  zodResolver,
749
919
  security
750
920
  })
@@ -770,13 +940,13 @@ function buildSdkMethod({ node, name, tsResolver, zodResolver, parser, security
770
940
  * instance: `const api = new PetClient({ baseURL }); api.getPetById(...)`. A per-call `client` option
771
941
  * still overrides the instance client for a one-off call.
772
942
  */
773
- function SdkClient({ name, isExportable = true, isIndexable = true, operations, parser, children }) {
943
+ function SdkClient({ name, isExportable = true, isIndexable = true, operations, validator, children }) {
774
944
  const methods = operations.map(({ node, name: methodName, tsResolver, zodResolver, security }) => buildSdkMethod({
775
945
  node,
776
946
  name: methodName,
777
947
  tsResolver,
778
948
  zodResolver,
779
- parser,
949
+ validator,
780
950
  security
781
951
  }));
782
952
  const classCode = `export class ${name} {\n${[
@@ -822,13 +992,13 @@ function SdkFacade({ name, isExportable = true, isIndexable = true, members, chi
822
992
  function resolveTypeImportNames(node, tsResolver) {
823
993
  return [tsResolver.resolveRequestConfigName(node), tsResolver.resolveResponsesName(node)];
824
994
  }
825
- function resolveZodImportNames(node, zodResolver, parser) {
995
+ function resolveZodImportNames(node, zodResolver, validator) {
826
996
  const { query: queryParams } = getOperationParameters(node, { paramsCasing: "original" });
827
997
  return [
828
- resolveResponseParser(parser) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
829
- resolveResponseParser(parser) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
830
- resolveRequestParser(parser) === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
831
- resolveQueryParamsParser(parser) === "zod" && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]) : null
998
+ resolveResponseValidator(validator) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
999
+ resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
1000
+ resolveRequestValidator(validator) === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
1001
+ resolveQueryParamsValidator(validator) === "zod" && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]) : null
832
1002
  ].filter((n) => Boolean(n));
833
1003
  }
834
1004
  /**
@@ -837,11 +1007,11 @@ function resolveZodImportNames(node, zodResolver, parser) {
837
1007
  */
838
1008
  function buildControllers(nodes, ctx) {
839
1009
  const { driver, resolver, root } = ctx;
840
- const { output, group, parser } = ctx.options;
1010
+ const { output, group, validator } = ctx.options;
841
1011
  const pluginTs = driver.getPlugin(pluginTsName);
842
1012
  const tsResolver = driver.getResolver(pluginTsName);
843
1013
  const tsPluginOptions = pluginTs.options;
844
- const pluginZod = isParserEnabled(parser) ? driver.getPlugin(pluginZodName) : null;
1014
+ const pluginZod = isValidatorEnabled(validator) ? driver.getPlugin(pluginZodName) : null;
845
1015
  const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null;
846
1016
  const document = ctx.adapter.document;
847
1017
  function buildOperationData(node) {
@@ -928,7 +1098,7 @@ function createSdkGenerator() {
928
1098
  renderer: jsxRenderer,
929
1099
  operations(nodes, ctx) {
930
1100
  const { config, resolver, root } = ctx;
931
- const { output, group, parser, sdk } = ctx.options;
1101
+ const { output, group, validator, sdk } = ctx.options;
932
1102
  if (!ctx.driver.getPlugin(pluginTsName) || !sdk) return null;
933
1103
  const controllers = buildControllers(nodes, ctx);
934
1104
  const clientPath = path.resolve(root, ".kubb/client.ts");
@@ -953,9 +1123,9 @@ function createSdkGenerator() {
953
1123
  file: op.typeFile,
954
1124
  names: resolveTypeImportNames(op.node, op.tsResolver)
955
1125
  }));
956
- const { namesByPath: zodNamesByPath, filesByPath: zodFilesByPath } = isParserEnabled(parser) ? collectImportsByFile(ops, (op) => ({
1126
+ const { namesByPath: zodNamesByPath, filesByPath: zodFilesByPath } = isValidatorEnabled(validator) ? collectImportsByFile(ops, (op) => ({
957
1127
  file: op.zodFile,
958
- names: op.zodResolver ? resolveZodImportNames(op.node, op.zodResolver, parser) : []
1128
+ names: op.zodResolver ? resolveZodImportNames(op.node, op.zodResolver, validator) : []
959
1129
  })) : {
960
1130
  namesByPath: /* @__PURE__ */ new Map(),
961
1131
  filesByPath: /* @__PURE__ */ new Map()
@@ -983,7 +1153,7 @@ function createSdkGenerator() {
983
1153
  path: clientPath,
984
1154
  isTypeOnly: true
985
1155
  }),
986
- parser === "zod" && ops.some((op) => op.node.requestBody?.content?.[0]?.schema != null) && /* @__PURE__ */ jsx(File.Import, {
1156
+ validator === "zod" && ops.some((op) => op.node.requestBody?.content?.[0]?.schema != null) && /* @__PURE__ */ jsx(File.Import, {
987
1157
  name: ["z"],
988
1158
  path: "zod",
989
1159
  isTypeOnly: true
@@ -994,7 +1164,7 @@ function createSdkGenerator() {
994
1164
  path: typeFilesByPath.get(filePath).path,
995
1165
  isTypeOnly: true
996
1166
  }, filePath)),
997
- isParserEnabled(parser) && Array.from(zodNamesByPath.entries()).map(([filePath, set]) => /* @__PURE__ */ jsx(File.Import, {
1167
+ isValidatorEnabled(validator) && Array.from(zodNamesByPath.entries()).map(([filePath, set]) => /* @__PURE__ */ jsx(File.Import, {
998
1168
  name: Array.from(set),
999
1169
  root: file.path,
1000
1170
  path: zodFilesByPath.get(filePath).path
@@ -1002,7 +1172,7 @@ function createSdkGenerator() {
1002
1172
  /* @__PURE__ */ jsx(SdkClient, {
1003
1173
  name: className,
1004
1174
  operations: ops,
1005
- parser
1175
+ validator
1006
1176
  })
1007
1177
  ]
1008
1178
  }, file.path);
@@ -1113,18 +1283,18 @@ const clientGenerator = defineGenerator({
1113
1283
  operation(node, ctx) {
1114
1284
  if (!ast.isHttpOperationNode(node)) return null;
1115
1285
  const { config, driver, resolver, root } = ctx;
1116
- const { output, parser, group } = ctx.options;
1286
+ const { output, validator, group } = ctx.options;
1117
1287
  const pluginTs = driver.getPlugin(pluginTsName);
1118
1288
  if (!pluginTs) return null;
1119
1289
  const tsResolver = driver.getResolver(pluginTsName);
1120
- const pluginZod = resolveResponseParser(parser) === "zod" || resolveRequestParser(parser) === "zod" ? driver.getPlugin(pluginZodName) : null;
1290
+ const pluginZod = resolveResponseValidator(validator) === "zod" || resolveRequestValidator(validator) === "zod" ? driver.getPlugin(pluginZodName) : null;
1121
1291
  const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null;
1122
1292
  const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
1123
1293
  const importedTypeNames = [tsResolver.resolveRequestConfigName(node), tsResolver.resolveResponsesName(node)];
1124
1294
  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
1295
+ resolveResponseValidator(validator) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
1296
+ resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
1297
+ resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.resolveDataName?.(node) : null
1128
1298
  ].filter((name) => Boolean(name)) : [];
1129
1299
  const meta = {
1130
1300
  name: resolver.resolveName(node.operationId),
@@ -1150,6 +1320,7 @@ const clientGenerator = defineGenerator({
1150
1320
  path: node.path
1151
1321
  });
1152
1322
  const clientPath = path.resolve(root, ".kubb/client.ts");
1323
+ const eventStream = isEventStream(node);
1153
1324
  return /* @__PURE__ */ jsxs(File, {
1154
1325
  baseName: meta.file.baseName,
1155
1326
  path: meta.file.path,
@@ -1172,12 +1343,16 @@ const clientGenerator = defineGenerator({
1172
1343
  }),
1173
1344
  children: [
1174
1345
  /* @__PURE__ */ jsx(File.Import, {
1175
- name: ["client"],
1346
+ name: eventStream ? ["client", "toEventStream"] : ["client"],
1176
1347
  root: meta.file.path,
1177
1348
  path: clientPath
1178
1349
  }),
1179
1350
  /* @__PURE__ */ jsx(File.Import, {
1180
- name: ["Options", "RequestResult"],
1351
+ name: eventStream ? [
1352
+ "Options",
1353
+ "EventStreamResult",
1354
+ "SuccessOf"
1355
+ ] : ["Options", "RequestResult"],
1181
1356
  root: meta.file.path,
1182
1357
  path: clientPath,
1183
1358
  isTypeOnly: true
@@ -1198,7 +1373,7 @@ const clientGenerator = defineGenerator({
1198
1373
  node,
1199
1374
  tsResolver,
1200
1375
  zodResolver,
1201
- parser,
1376
+ validator,
1202
1377
  security
1203
1378
  })
1204
1379
  ]
@@ -1207,12 +1382,15 @@ const clientGenerator = defineGenerator({
1207
1382
  });
1208
1383
  //#endregion
1209
1384
  //#region src/templates.ts
1385
+ /** Absolute path to the fetch client template, copied into `.kubb/client.ts`. */
1386
+ const fetchClientTemplatePath = fileURLToPath(new URL("../templates/fetch.ts", import.meta.url));
1387
+ /** Absolute path to the fetch serializers template, copied into `.kubb/serializers.ts`. */
1388
+ const fetchSerializersTemplatePath = fileURLToPath(new URL("../templates/serializers.ts", import.meta.url));
1210
1389
  /**
1211
- * Absolute path to the fetch client runtime template, resolved relative to this package's own
1212
- * location so it stays correct no matter which package imports it. Pass it to a file node's `copy`
1213
- * field to emit the runtime into the generated `.kubb/client.ts` verbatim.
1390
+ * Absolute path to the Standard Schema runtime template. Pass it to a file node's `copy` field to
1391
+ * emit the helper into the generated `.kubb/standardSchema.ts` verbatim.
1214
1392
  */
1215
- const fetchClientTemplatePath = fileURLToPath(new URL("../templates/fetch.ts", import.meta.url));
1393
+ const standardSchemaTemplatePath = fileURLToPath(new URL("../templates/standardSchema.ts", import.meta.url));
1216
1394
  //#endregion
1217
1395
  //#region src/plugin.ts
1218
1396
  /**
@@ -1246,7 +1424,7 @@ const pluginFetch = definePlugin((options) => {
1246
1424
  const { output = {
1247
1425
  path: "clients",
1248
1426
  barrel: { type: "named" }
1249
- }, exclude = [], include, override = [], baseURL, parser = false, group, sdk, resolver: userResolver } = options;
1427
+ }, exclude = [], include, override = [], baseURL, validator = false, group, sdk, resolver: userResolver } = options;
1250
1428
  const resolved = {
1251
1429
  output,
1252
1430
  exclude,
@@ -1254,7 +1432,7 @@ const pluginFetch = definePlugin((options) => {
1254
1432
  override,
1255
1433
  group: createGroupConfig(group),
1256
1434
  baseURL,
1257
- parser,
1435
+ validator,
1258
1436
  sdk: sdk ? {
1259
1437
  mode: sdk.mode ?? "tag",
1260
1438
  name: sdk.name
@@ -1268,19 +1446,29 @@ const pluginFetch = definePlugin((options) => {
1268
1446
  return {
1269
1447
  name: pluginFetchName,
1270
1448
  options,
1271
- dependencies: [pluginTsName, isParserEnabled(resolved.parser) ? pluginZodName : null].filter((dependency) => Boolean(dependency)),
1449
+ dependencies: [pluginTsName, isValidatorEnabled(resolved.validator) ? pluginZodName : null].filter((dependency) => Boolean(dependency)),
1272
1450
  hooks: { "kubb:plugin:setup"(ctx) {
1273
1451
  ctx.setOptions(resolved);
1274
1452
  ctx.setResolver(resolved.resolver);
1275
1453
  ctx.setMacros([...defaultMacros, ...options.macros ?? []]);
1276
1454
  ctx.addGenerator(...selectedGenerators);
1277
1455
  const root = path.resolve(ctx.config.root, ctx.config.output.path);
1456
+ ctx.injectFile({
1457
+ baseName: "serializers.ts",
1458
+ path: path.resolve(root, ".kubb/serializers.ts"),
1459
+ copy: fetchSerializersTemplatePath
1460
+ });
1278
1461
  ctx.injectFile({
1279
1462
  baseName: "client.ts",
1280
1463
  path: path.resolve(root, ".kubb/client.ts"),
1281
1464
  copy: fetchClientTemplatePath,
1282
1465
  footer: baseURL ? `client.setConfig({ baseURL: ${JSON.stringify(baseURL)} })` : void 0
1283
1466
  });
1467
+ ctx.injectFile({
1468
+ baseName: "standardSchema.ts",
1469
+ path: path.resolve(root, ".kubb/standardSchema.ts"),
1470
+ copy: standardSchemaTemplatePath
1471
+ });
1284
1472
  } }
1285
1473
  };
1286
1474
  });