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

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,55 @@ 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
+ }
367
433
  function buildOperationComments(node, options = {}) {
368
434
  const { link = "pathTemplate", linkPosition = "afterDeprecated", splitLines = false } = options;
369
435
  const linkComment = getOperationLink(node, link);
@@ -384,10 +450,10 @@ function buildOperationComments(node, options = {}) {
384
450
  function getOperationParameters(node, options = {}) {
385
451
  const params = caseParams(node.parameters, options.paramsCasing === "original" ? void 0 : "camelcase");
386
452
  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")
453
+ path: dedupeByCasedName(params.filter((param) => param.in === "path")),
454
+ query: dedupeByCasedName(params.filter((param) => param.in === "query")),
455
+ header: dedupeByCasedName(params.filter((param) => param.in === "header")),
456
+ cookie: dedupeByCasedName(params.filter((param) => param.in === "cookie"))
391
457
  };
392
458
  }
393
459
  function getStatusCodeNumber(statusCode) {
@@ -398,6 +464,15 @@ function isSuccessStatusCode(statusCode) {
398
464
  const code = getStatusCodeNumber(statusCode);
399
465
  return code !== null && code >= 200 && code < 300;
400
466
  }
467
+ function getSuccessResponses(responses) {
468
+ return responses.filter((response) => isSuccessStatusCode(response.statusCode));
469
+ }
470
+ function getOperationSuccessResponses(node) {
471
+ return getSuccessResponses(node.responses);
472
+ }
473
+ function getPrimarySuccessResponse(node) {
474
+ return getOperationSuccessResponses(node)[0] ?? null;
475
+ }
401
476
  //#endregion
402
477
  //#region ../../internals/shared/src/group.ts
403
478
  /**
@@ -430,39 +505,39 @@ function createGroupConfig(group) {
430
505
  };
431
506
  }
432
507
  //#endregion
433
- //#region ../../internals/client/src/builders/parser.ts
508
+ //#region ../../internals/client/src/builders/validatorOptions.ts
434
509
  /**
435
- * Returns `true` when any direction of the parser uses zod (used for dependency checks).
510
+ * Returns `true` when any direction of the validator uses zod (used for dependency checks).
436
511
  */
437
- function isParserEnabled(parser) {
438
- if (!parser) return false;
439
- if (parser === "zod") return true;
440
- return Boolean(parser.request || parser.response);
512
+ function isValidatorEnabled(validator) {
513
+ if (!validator) return false;
514
+ if (validator === "zod") return true;
515
+ return Boolean(validator.request || validator.response);
441
516
  }
442
517
  /**
443
518
  * Returns `'zod'` when request body parsing is enabled, `null` otherwise. The string shorthand
444
519
  * `'zod'` validates the response only, so it does not enable request parsing.
445
520
  */
446
- function resolveRequestParser(parser) {
447
- if (!parser || parser === "zod") return null;
448
- return parser.request ?? null;
521
+ function resolveRequestValidator(validator) {
522
+ if (!validator || validator === "zod") return null;
523
+ return validator.request ?? null;
449
524
  }
450
525
  /**
451
526
  * Returns `'zod'` when query-parameters parsing is enabled, `null` otherwise. Only the object form
452
527
  * `{ request: 'zod' }` enables it.
453
528
  */
454
- function resolveQueryParamsParser(parser) {
455
- if (!parser || parser === "zod") return null;
456
- return parser.request ?? null;
529
+ function resolveQueryParamsValidator(validator) {
530
+ if (!validator || validator === "zod") return null;
531
+ return validator.request ?? null;
457
532
  }
458
533
  /**
459
534
  * Returns `'zod'` when response parsing is enabled, `null` otherwise. The string shorthand `'zod'`
460
535
  * maps to response parsing.
461
536
  */
462
- function resolveResponseParser(parser) {
463
- if (!parser) return null;
464
- if (parser === "zod") return "zod";
465
- return parser.response ?? null;
537
+ function resolveResponseValidator(validator) {
538
+ if (!validator) return null;
539
+ if (validator === "zod") return "zod";
540
+ return validator.response ?? null;
466
541
  }
467
542
  /**
468
543
  * Resolves the zod expression a generated client validates a success response with. Only success
@@ -616,23 +691,74 @@ function buildGroupedOptionsSignature({ node, tsResolver }) {
616
691
  };
617
692
  }
618
693
  //#endregion
694
+ //#region ../../internals/client/src/builders/styles.ts
695
+ /**
696
+ * Renders a parameter name as an object-literal key, quoting it when the camelCased name is not a
697
+ * bare identifier (for example a name that starts with a digit) so the emitted literal stays valid.
698
+ */
699
+ function toKey(name) {
700
+ const cased = camelCase(name);
701
+ return isValidVarName(cased) ? cased : JSON.stringify(cased);
702
+ }
703
+ /**
704
+ * Serializes one parameter's metadata into a `{ style, explode }` literal, or `null` when the
705
+ * parameter carries neither. Path and query carry the serialization `style`; header and cookie use a
706
+ * fixed style (`simple` and `form`), so only `explode` is emitted for them.
707
+ */
708
+ function serializeParameter(parameter) {
709
+ const parts = [];
710
+ if ((parameter.in === "path" || parameter.in === "query") && parameter.style) parts.push(`style: '${parameter.style}'`);
711
+ if (parameter.explode !== void 0) parts.push(`explode: ${parameter.explode}`);
712
+ return parts.length > 0 ? `{ ${parts.join(", ")} }` : null;
713
+ }
714
+ /**
715
+ * Builds the per-operation `styles` literal from the operation's parameters, grouped by location and
716
+ * keyed by the camelCased parameter name to match the generated `path` / `query` / `headers` keys.
717
+ * Only parameters whose source defines `style` or `explode` are emitted, so calls without
718
+ * serialization metadata keep the runtime defaults and existing output is unchanged. Returns `null`
719
+ * when no parameter carries metadata.
720
+ *
721
+ * @example
722
+ * ```ts
723
+ * // a path param with { style: 'matrix', explode: true } and a query param with { explode: false }
724
+ * buildStyles({ node }) // "{ path: { id: { style: 'matrix', explode: true } }, query: { tags: { explode: false } } }"
725
+ * ```
726
+ */
727
+ function buildStyles({ node }) {
728
+ if (!ast.isHttpOperationNode(node)) return null;
729
+ const groups = {
730
+ path: [],
731
+ query: [],
732
+ header: [],
733
+ cookie: []
734
+ };
735
+ for (const parameter of node.parameters) {
736
+ const literal = serializeParameter(parameter);
737
+ if (!literal) continue;
738
+ groups[parameter.in].push(`${toKey(parameter.name)}: ${literal}`);
739
+ }
740
+ const locations = Object.keys(groups).filter((location) => groups[location].length > 0);
741
+ if (locations.length === 0) return null;
742
+ return `{ ${locations.map((location) => `${location}: { ${groups[location].join(", ")} }`).join(", ")} }`;
743
+ }
744
+ //#endregion
619
745
  //#region ../../internals/client/src/builders/validator.ts
620
746
  /**
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.
747
+ * Builds the validator-hook references for one operation. Request validation runs before the send;
748
+ * response validation runs on the success body only. Returns `null` references when the matching
749
+ * direction is disabled or the schema is absent.
624
750
  */
625
- function buildParserHooks({ node, parser, zodResolver }) {
751
+ function buildValidatorHooks({ node, validator, zodResolver }) {
626
752
  const importedZodNames = [];
627
753
  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;
754
+ const zodRequestName = zodResolver && resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.resolveDataName?.(node) : null;
755
+ const request = zodRequestName ?? null;
630
756
  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;
757
+ const responseParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodResponseParse(node, zodResolver) : null;
758
+ const response = responseParse ? responseParse.expression : null;
633
759
  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;
760
+ const errorParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver) : null;
761
+ const error = errorParse ? errorParse.expression : null;
636
762
  if (errorParse) importedZodNames.push(...errorParse.importNames);
637
763
  return {
638
764
  request,
@@ -648,34 +774,49 @@ function buildParserHooks({ node, parser, zodResolver }) {
648
774
  * single `options` object to the resolved client and returns the `RequestResult`. The type, signature,
649
775
  * and call config are built with the AST factory, and only the jsx-renderer emits the source.
650
776
  */
651
- function Operation({ name, node, tsResolver, zodResolver, parser, security, isExportable = true, isIndexable = true }) {
777
+ function Operation({ name, node, tsResolver, zodResolver, validator, security, isExportable = true, isIndexable = true }) {
652
778
  if (!ast.isHttpOperationNode(node)) return null;
653
779
  const signature = buildGroupedOptionsSignature({
654
780
  node,
655
781
  tsResolver
656
782
  });
657
- const parsers = buildParserHooks({
783
+ const validators = buildValidatorHooks({
658
784
  node,
659
- parser,
785
+ validator,
660
786
  zodResolver
661
787
  });
662
788
  const securityLiteral = buildSecurityMetadata({ security });
789
+ const stylesLiteral = buildStyles({ node });
663
790
  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
791
+ const bakedRequestContentType = Boolean(node.requestBody?.content?.[0]?.schema) && defaultContentType !== "application/json" ? defaultContentType : null;
792
+ const mergeContentType = Boolean(bakedRequestContentType) && getResponseContentTypeInfo(node).isMultipleContentTypes;
793
+ const contentTypeLiteral = !bakedRequestContentType ? null : mergeContentType ? `contentType: { request: '${bakedRequestContentType}', ...(typeof contentType === 'string' ? { request: contentType } : contentType) }` : `contentType: { request: '${bakedRequestContentType}' }`;
794
+ const eventStream = isEventStream(node);
795
+ const responseType = getResponseType(node);
796
+ const responseTypeLiteral = responseType ? `responseType: '${responseType}'` : null;
797
+ const validatorEntries = [
798
+ validators.request ? `request: ${validators.request}` : null,
799
+ validators.response ? `response: ${validators.response}` : null,
800
+ validators.error ? `error: ${validators.error}` : null
669
801
  ].filter(Boolean);
670
- const parserLiteral = parserEntries.length ? `parser: { ${parserEntries.join(", ")} }` : null;
802
+ const validatorLiteral = validatorEntries.length ? `validator: { ${validatorEntries.join(", ")} }` : null;
671
803
  const callConfig = `{ ${[
672
804
  `method: '${node.method.toUpperCase()}'`,
673
805
  `url: '${Url.toCasedTemplate(node.path, { casing: "camelcase" })}'`,
674
806
  securityLiteral ? `security: ${securityLiteral}` : null,
675
- parserLiteral,
807
+ stylesLiteral ? `styles: ${stylesLiteral}` : null,
808
+ validatorLiteral,
676
809
  contentTypeLiteral,
810
+ responseTypeLiteral,
677
811
  "...config"
678
812
  ].filter(Boolean).join(", ")} }`;
813
+ const eventType = `SuccessOf<${tsResolver.resolveResponsesName(node)}>`;
814
+ const returnType = eventStream ? `Promise<EventStreamResult<${eventType}>>` : signature.returnType;
815
+ const returnStatement = eventStream ? `return toEventStream<${eventType}>(request(${callConfig}))` : buildReturnStatement({
816
+ node,
817
+ tsResolver,
818
+ callConfig
819
+ });
679
820
  return /* @__PURE__ */ jsx(File.Source, {
680
821
  name,
681
822
  isExportable,
@@ -685,20 +826,16 @@ function Operation({ name, node, tsResolver, zodResolver, parser, security, isEx
685
826
  export: isExportable,
686
827
  generics: signature.generics,
687
828
  params: signature.paramsSignature,
688
- returnType: signature.returnType,
829
+ returnType,
689
830
  JSDoc: { comments: buildOperationComments(node, {
690
831
  link: "urlPath",
691
832
  linkPosition: "beforeDeprecated",
692
833
  splitLines: true
693
834
  }) },
694
835
  children: [
695
- "const { client: request = client, ...config } = options",
836
+ mergeContentType ? "const { client: request = client, contentType, ...config } = options" : "const { client: request = client, ...config } = options",
696
837
  /* @__PURE__ */ jsx("br", {}),
697
- buildReturnStatement({
698
- node,
699
- tsResolver,
700
- callConfig
701
- })
838
+ returnStatement
702
839
  ]
703
840
  })
704
841
  });
@@ -707,23 +844,23 @@ function Operation({ name, node, tsResolver, zodResolver, parser, security, isEx
707
844
  //#region ../../internals/client/src/builders/sdkMethod.ts
708
845
  /**
709
846
  * 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
847
+ * component: `{ method, url, security?, validator?, ...config }`. The `...config` spread carries every
711
848
  * per-call field (including `throwOnError`), so the method stays a thin wrapper over the contract.
712
849
  */
713
- function buildCallConfig({ node, parser, zodResolver, security }) {
714
- const parsers = buildParserHooks({
850
+ function buildCallConfig({ node, validator, zodResolver, security }) {
851
+ const validators = buildValidatorHooks({
715
852
  node,
716
- parser,
853
+ validator,
717
854
  zodResolver
718
855
  });
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;
856
+ const validatorEntries = [validators.request ? `request: ${validators.request}` : null, validators.response ? `response: ${validators.response}` : null].filter(Boolean);
857
+ const validatorLiteral = validatorEntries.length ? `validator: { ${validatorEntries.join(", ")} }` : null;
721
858
  const securityLiteral = buildSecurityMetadata({ security });
722
859
  return `{ ${[
723
860
  `method: '${node.method.toUpperCase()}'`,
724
861
  `url: '${Url.toCasedTemplate(node.path, { casing: "camelcase" })}'`,
725
862
  securityLiteral ? `security: ${securityLiteral}` : null,
726
- parserLiteral,
863
+ validatorLiteral,
727
864
  "...config"
728
865
  ].filter(Boolean).join(", ")} }`;
729
866
  }
@@ -733,7 +870,7 @@ function buildCallConfig({ node, parser, zodResolver, security }) {
733
870
  * returns the `RequestResult`. A per-call `options.client` still overrides the instance client, so
734
871
  * one operation can be routed to a different environment without a new instance.
735
872
  */
736
- function buildSdkMethod({ node, name, tsResolver, zodResolver, parser, security }) {
873
+ function buildSdkMethod({ node, name, tsResolver, zodResolver, validator, security }) {
737
874
  if (!ast.isHttpOperationNode(node)) return "";
738
875
  const signature = buildGroupedOptionsSignature({
739
876
  node,
@@ -744,7 +881,7 @@ function buildSdkMethod({ node, name, tsResolver, zodResolver, parser, security
744
881
  tsResolver,
745
882
  callConfig: buildCallConfig({
746
883
  node,
747
- parser,
884
+ validator,
748
885
  zodResolver,
749
886
  security
750
887
  })
@@ -770,13 +907,13 @@ function buildSdkMethod({ node, name, tsResolver, zodResolver, parser, security
770
907
  * instance: `const api = new PetClient({ baseURL }); api.getPetById(...)`. A per-call `client` option
771
908
  * still overrides the instance client for a one-off call.
772
909
  */
773
- function SdkClient({ name, isExportable = true, isIndexable = true, operations, parser, children }) {
910
+ function SdkClient({ name, isExportable = true, isIndexable = true, operations, validator, children }) {
774
911
  const methods = operations.map(({ node, name: methodName, tsResolver, zodResolver, security }) => buildSdkMethod({
775
912
  node,
776
913
  name: methodName,
777
914
  tsResolver,
778
915
  zodResolver,
779
- parser,
916
+ validator,
780
917
  security
781
918
  }));
782
919
  const classCode = `export class ${name} {\n${[
@@ -822,13 +959,13 @@ function SdkFacade({ name, isExportable = true, isIndexable = true, members, chi
822
959
  function resolveTypeImportNames(node, tsResolver) {
823
960
  return [tsResolver.resolveRequestConfigName(node), tsResolver.resolveResponsesName(node)];
824
961
  }
825
- function resolveZodImportNames(node, zodResolver, parser) {
962
+ function resolveZodImportNames(node, zodResolver, validator) {
826
963
  const { query: queryParams } = getOperationParameters(node, { paramsCasing: "original" });
827
964
  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
965
+ resolveResponseValidator(validator) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
966
+ resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
967
+ resolveRequestValidator(validator) === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
968
+ resolveQueryParamsValidator(validator) === "zod" && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]) : null
832
969
  ].filter((n) => Boolean(n));
833
970
  }
834
971
  /**
@@ -837,11 +974,11 @@ function resolveZodImportNames(node, zodResolver, parser) {
837
974
  */
838
975
  function buildControllers(nodes, ctx) {
839
976
  const { driver, resolver, root } = ctx;
840
- const { output, group, parser } = ctx.options;
977
+ const { output, group, validator } = ctx.options;
841
978
  const pluginTs = driver.getPlugin(pluginTsName);
842
979
  const tsResolver = driver.getResolver(pluginTsName);
843
980
  const tsPluginOptions = pluginTs.options;
844
- const pluginZod = isParserEnabled(parser) ? driver.getPlugin(pluginZodName) : null;
981
+ const pluginZod = isValidatorEnabled(validator) ? driver.getPlugin(pluginZodName) : null;
845
982
  const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null;
846
983
  const document = ctx.adapter.document;
847
984
  function buildOperationData(node) {
@@ -928,7 +1065,7 @@ function createSdkGenerator() {
928
1065
  renderer: jsxRenderer,
929
1066
  operations(nodes, ctx) {
930
1067
  const { config, resolver, root } = ctx;
931
- const { output, group, parser, sdk } = ctx.options;
1068
+ const { output, group, validator, sdk } = ctx.options;
932
1069
  if (!ctx.driver.getPlugin(pluginTsName) || !sdk) return null;
933
1070
  const controllers = buildControllers(nodes, ctx);
934
1071
  const clientPath = path.resolve(root, ".kubb/client.ts");
@@ -953,9 +1090,9 @@ function createSdkGenerator() {
953
1090
  file: op.typeFile,
954
1091
  names: resolveTypeImportNames(op.node, op.tsResolver)
955
1092
  }));
956
- const { namesByPath: zodNamesByPath, filesByPath: zodFilesByPath } = isParserEnabled(parser) ? collectImportsByFile(ops, (op) => ({
1093
+ const { namesByPath: zodNamesByPath, filesByPath: zodFilesByPath } = isValidatorEnabled(validator) ? collectImportsByFile(ops, (op) => ({
957
1094
  file: op.zodFile,
958
- names: op.zodResolver ? resolveZodImportNames(op.node, op.zodResolver, parser) : []
1095
+ names: op.zodResolver ? resolveZodImportNames(op.node, op.zodResolver, validator) : []
959
1096
  })) : {
960
1097
  namesByPath: /* @__PURE__ */ new Map(),
961
1098
  filesByPath: /* @__PURE__ */ new Map()
@@ -983,7 +1120,7 @@ function createSdkGenerator() {
983
1120
  path: clientPath,
984
1121
  isTypeOnly: true
985
1122
  }),
986
- parser === "zod" && ops.some((op) => op.node.requestBody?.content?.[0]?.schema != null) && /* @__PURE__ */ jsx(File.Import, {
1123
+ validator === "zod" && ops.some((op) => op.node.requestBody?.content?.[0]?.schema != null) && /* @__PURE__ */ jsx(File.Import, {
987
1124
  name: ["z"],
988
1125
  path: "zod",
989
1126
  isTypeOnly: true
@@ -994,7 +1131,7 @@ function createSdkGenerator() {
994
1131
  path: typeFilesByPath.get(filePath).path,
995
1132
  isTypeOnly: true
996
1133
  }, filePath)),
997
- isParserEnabled(parser) && Array.from(zodNamesByPath.entries()).map(([filePath, set]) => /* @__PURE__ */ jsx(File.Import, {
1134
+ isValidatorEnabled(validator) && Array.from(zodNamesByPath.entries()).map(([filePath, set]) => /* @__PURE__ */ jsx(File.Import, {
998
1135
  name: Array.from(set),
999
1136
  root: file.path,
1000
1137
  path: zodFilesByPath.get(filePath).path
@@ -1002,7 +1139,7 @@ function createSdkGenerator() {
1002
1139
  /* @__PURE__ */ jsx(SdkClient, {
1003
1140
  name: className,
1004
1141
  operations: ops,
1005
- parser
1142
+ validator
1006
1143
  })
1007
1144
  ]
1008
1145
  }, file.path);
@@ -1113,18 +1250,18 @@ const clientGenerator = defineGenerator({
1113
1250
  operation(node, ctx) {
1114
1251
  if (!ast.isHttpOperationNode(node)) return null;
1115
1252
  const { config, driver, resolver, root } = ctx;
1116
- const { output, parser, group } = ctx.options;
1253
+ const { output, validator, group } = ctx.options;
1117
1254
  const pluginTs = driver.getPlugin(pluginTsName);
1118
1255
  if (!pluginTs) return null;
1119
1256
  const tsResolver = driver.getResolver(pluginTsName);
1120
- const pluginZod = resolveResponseParser(parser) === "zod" || resolveRequestParser(parser) === "zod" ? driver.getPlugin(pluginZodName) : null;
1257
+ const pluginZod = resolveResponseValidator(validator) === "zod" || resolveRequestValidator(validator) === "zod" ? driver.getPlugin(pluginZodName) : null;
1121
1258
  const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null;
1122
1259
  const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
1123
1260
  const importedTypeNames = [tsResolver.resolveRequestConfigName(node), tsResolver.resolveResponsesName(node)];
1124
1261
  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
1262
+ resolveResponseValidator(validator) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
1263
+ resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
1264
+ resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.resolveDataName?.(node) : null
1128
1265
  ].filter((name) => Boolean(name)) : [];
1129
1266
  const meta = {
1130
1267
  name: resolver.resolveName(node.operationId),
@@ -1150,6 +1287,7 @@ const clientGenerator = defineGenerator({
1150
1287
  path: node.path
1151
1288
  });
1152
1289
  const clientPath = path.resolve(root, ".kubb/client.ts");
1290
+ const eventStream = isEventStream(node);
1153
1291
  return /* @__PURE__ */ jsxs(File, {
1154
1292
  baseName: meta.file.baseName,
1155
1293
  path: meta.file.path,
@@ -1172,12 +1310,16 @@ const clientGenerator = defineGenerator({
1172
1310
  }),
1173
1311
  children: [
1174
1312
  /* @__PURE__ */ jsx(File.Import, {
1175
- name: ["client"],
1313
+ name: eventStream ? ["client", "toEventStream"] : ["client"],
1176
1314
  root: meta.file.path,
1177
1315
  path: clientPath
1178
1316
  }),
1179
1317
  /* @__PURE__ */ jsx(File.Import, {
1180
- name: ["Options", "RequestResult"],
1318
+ name: eventStream ? [
1319
+ "Options",
1320
+ "EventStreamResult",
1321
+ "SuccessOf"
1322
+ ] : ["Options", "RequestResult"],
1181
1323
  root: meta.file.path,
1182
1324
  path: clientPath,
1183
1325
  isTypeOnly: true
@@ -1198,7 +1340,7 @@ const clientGenerator = defineGenerator({
1198
1340
  node,
1199
1341
  tsResolver,
1200
1342
  zodResolver,
1201
- parser,
1343
+ validator,
1202
1344
  security
1203
1345
  })
1204
1346
  ]
@@ -1207,12 +1349,15 @@ const clientGenerator = defineGenerator({
1207
1349
  });
1208
1350
  //#endregion
1209
1351
  //#region src/templates.ts
1352
+ /** Absolute path to the fetch client template, copied into `.kubb/client.ts`. */
1353
+ const fetchClientTemplatePath = fileURLToPath(new URL("../templates/fetch.ts", import.meta.url));
1354
+ /** Absolute path to the fetch serializers template, copied into `.kubb/serializers.ts`. */
1355
+ const fetchSerializersTemplatePath = fileURLToPath(new URL("../templates/serializers.ts", import.meta.url));
1210
1356
  /**
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.
1357
+ * Absolute path to the Standard Schema runtime template. Pass it to a file node's `copy` field to
1358
+ * emit the helper into the generated `.kubb/standardSchema.ts` verbatim.
1214
1359
  */
1215
- const fetchClientTemplatePath = fileURLToPath(new URL("../templates/fetch.ts", import.meta.url));
1360
+ const standardSchemaTemplatePath = fileURLToPath(new URL("../templates/standardSchema.ts", import.meta.url));
1216
1361
  //#endregion
1217
1362
  //#region src/plugin.ts
1218
1363
  /**
@@ -1246,7 +1391,7 @@ const pluginFetch = definePlugin((options) => {
1246
1391
  const { output = {
1247
1392
  path: "clients",
1248
1393
  barrel: { type: "named" }
1249
- }, exclude = [], include, override = [], baseURL, parser = false, group, sdk, resolver: userResolver } = options;
1394
+ }, exclude = [], include, override = [], baseURL, validator = false, group, sdk, resolver: userResolver } = options;
1250
1395
  const resolved = {
1251
1396
  output,
1252
1397
  exclude,
@@ -1254,7 +1399,7 @@ const pluginFetch = definePlugin((options) => {
1254
1399
  override,
1255
1400
  group: createGroupConfig(group),
1256
1401
  baseURL,
1257
- parser,
1402
+ validator,
1258
1403
  sdk: sdk ? {
1259
1404
  mode: sdk.mode ?? "tag",
1260
1405
  name: sdk.name
@@ -1268,19 +1413,29 @@ const pluginFetch = definePlugin((options) => {
1268
1413
  return {
1269
1414
  name: pluginFetchName,
1270
1415
  options,
1271
- dependencies: [pluginTsName, isParserEnabled(resolved.parser) ? pluginZodName : null].filter((dependency) => Boolean(dependency)),
1416
+ dependencies: [pluginTsName, isValidatorEnabled(resolved.validator) ? pluginZodName : null].filter((dependency) => Boolean(dependency)),
1272
1417
  hooks: { "kubb:plugin:setup"(ctx) {
1273
1418
  ctx.setOptions(resolved);
1274
1419
  ctx.setResolver(resolved.resolver);
1275
1420
  ctx.setMacros([...defaultMacros, ...options.macros ?? []]);
1276
1421
  ctx.addGenerator(...selectedGenerators);
1277
1422
  const root = path.resolve(ctx.config.root, ctx.config.output.path);
1423
+ ctx.injectFile({
1424
+ baseName: "serializers.ts",
1425
+ path: path.resolve(root, ".kubb/serializers.ts"),
1426
+ copy: fetchSerializersTemplatePath
1427
+ });
1278
1428
  ctx.injectFile({
1279
1429
  baseName: "client.ts",
1280
1430
  path: path.resolve(root, ".kubb/client.ts"),
1281
1431
  copy: fetchClientTemplatePath,
1282
1432
  footer: baseURL ? `client.setConfig({ baseURL: ${JSON.stringify(baseURL)} })` : void 0
1283
1433
  });
1434
+ ctx.injectFile({
1435
+ baseName: "standardSchema.ts",
1436
+ path: path.resolve(root, ".kubb/standardSchema.ts"),
1437
+ copy: standardSchemaTemplatePath
1438
+ });
1284
1439
  } }
1285
1440
  };
1286
1441
  });