@kubb/plugin-fetch 5.13.0-canary.20261105T105941 → 5.13.0

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
@@ -1,4 +1,5 @@
1
- import { Diagnostics, Resolver, Url, addGenerator, ast, camelCase, createResolver, defineExtension, defineGenerator, injectFile, macroSimplifyUnion, pascalCase, setMacros, setResolver, useConfig } from "@kubb/kit";
1
+ import { createRequire } from "node:module";
2
+ import { Diagnostics, Resolver, Url, addGenerator, ast, camelCase, createResolver, defineExtension, defineGenerator, injectFile, macroDedupeUnion, pascalCase, setMacros, setResolver, useConfig } from "@kubb/kit";
2
3
  import path from "node:path";
3
4
  import { pluginZodName } from "@kubb/plugin-zod";
4
5
  import { File, Function, jsxRenderer } from "@kubb/renderer-jsx";
@@ -85,20 +86,33 @@ function getPrimarySuccessContentType(node) {
85
86
  return contentTypes[0].split(";")[0].trim().toLowerCase();
86
87
  }
87
88
  /**
88
- * Whether an operation streams its primary success response as Server-Sent Events
89
- * (`text/event-stream`). The client generator uses this to return a typed event stream instead of a
90
- * one-shot `RequestResult`.
89
+ * The `text/event-stream` content of the primary success response, when it is the only content type. A response that also
90
+ * declares another type stays a normal call with its query hooks.
91
+ */
92
+ function getEventStreamContent(node) {
93
+ return getPrimarySuccessContentType(node) === "text/event-stream" ? getPrimarySuccessResponse(node)?.content?.[0] : void 0;
94
+ }
95
+ /**
96
+ * Whether an operation streams its primary success response as Server-Sent Events (`text/event-stream`). The client
97
+ * generator uses this to return a typed event stream instead of a one-shot `RequestResult`.
91
98
  */
92
99
  function isEventStream(node) {
93
- return getPrimarySuccessContentType(node) === "text/event-stream";
100
+ return getEventStreamContent(node) !== void 0;
101
+ }
102
+ /**
103
+ * How the document types a `text/event-stream` response, as the adapter read it from `itemSchema`: `event` when each item is
104
+ * the parsed event with its own `event` and `data`, `data` when it is only the payload. Returns `null` without an event stream.
105
+ */
106
+ function getEventStream(node) {
107
+ const content = getEventStreamContent(node);
108
+ return content ? content.stream?.shape ?? "data" : null;
94
109
  }
95
110
  /**
96
111
  * Derives the default `responseType` for an operation from its primary success response.
97
112
  *
98
- * Returns a value only when that response declares a single non-JSON content type. `text/event-stream`
99
- * and other binary types (`application/octet-stream`, `application/pdf`, `image/*`, `audio/*`,
100
- * `video/*`) map to a stream or `'blob'`, and other `text/*` maps to `'text'`. Otherwise `undefined`,
101
- * leaving the runtime client's `Content-Type` auto-detection in charge.
113
+ * Returns a value only when that response declares a single non-JSON content type. `text/event-stream` maps to
114
+ * `'stream'`, `application/octet-stream`, `application/pdf`, `image/*`, `audio/*` and `video/*` map to `'blob'`, and
115
+ * other `text/*` to `'text'`. Otherwise `undefined`, leaving the runtime client's `Content-Type` auto-detection in charge.
102
116
  */
103
117
  function getResponseType(node) {
104
118
  const baseType = getPrimarySuccessContentType(node);
@@ -158,6 +172,22 @@ function buildOperationComments(node, options = {}) {
158
172
  }
159
173
  const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
160
174
  /**
175
+ * An OpenAPI 3.2 `querystring` parameter is the whole query string, so a form-encoded object becomes one query parameter
176
+ * per property, typed and serialized like any other.
177
+ * Another content type, such as JSON, stays one parameter named after it, and the runtime sends its value as the whole query string.
178
+ */
179
+ function spreadQuerystring(param) {
180
+ const schema = param.schema.type === "ref" ? param.schema.schema : param.schema;
181
+ const formEncoded = !param.contentType || param.contentType.split(";")[0].trim().toLowerCase() === "application/x-www-form-urlencoded";
182
+ if (schema?.type !== "object" || !formEncoded) return [param];
183
+ return schema.properties.map((property) => ast.factory.createParameter({
184
+ name: property.name,
185
+ in: "query",
186
+ schema: property.schema,
187
+ required: param.required && property.required
188
+ }));
189
+ }
190
+ /**
161
191
  * Groups an operation's parameters by location (`path`/`query`/`header`/`cookie`), deduping each
162
192
  * group by name. Every plugin generator visiting the same `OperationNode` shares one AST instance
163
193
  * (see `KubbDriver`), so the result is cached per node to avoid re-filtering and re-deduping the
@@ -166,17 +196,18 @@ const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
166
196
  function getOperationParameters(node) {
167
197
  const cached = operationParameterGroupsByNode.get(node);
168
198
  if (cached) return cached;
169
- const group = (location) => {
199
+ const dedupe = (params) => {
170
200
  const seen = /* @__PURE__ */ new Set();
171
- return node.parameters.filter((param) => {
172
- if (param.in !== location || seen.has(param.name)) return false;
201
+ return params.filter((param) => {
202
+ if (seen.has(param.name)) return false;
173
203
  seen.add(param.name);
174
204
  return true;
175
205
  });
176
206
  };
207
+ const group = (location) => dedupe(node.parameters.filter((param) => param.in === location));
177
208
  const groups = {
178
209
  path: group("path"),
179
- query: group("query"),
210
+ query: dedupe([...group("query"), ...group("querystring").flatMap(spreadQuerystring)]),
180
211
  header: group("header"),
181
212
  cookie: group("cookie")
182
213
  };
@@ -463,6 +494,114 @@ function buildZodErrorParse(node, zodResolver) {
463
494
  if (!node.responses.some((res) => !isSuccessStatusCode(res.statusCode) && res.content?.some((entry) => entry.schema))) return null;
464
495
  return zodResolver.response.error?.(node) || null;
465
496
  }
497
+ // ../../internals/client/src/builders/generics.ts
498
+ /**
499
+ * Builds the `RequestResult` generic arguments for one operation: the per-status responses record
500
+ * plus the per-call `ThrowOnError` flag. `SuccessOf` / `ErrorOf` split the record inside the
501
+ * runtime, so this only names the record and threads `ThrowOnError`.
502
+ *
503
+ * @example
504
+ * `buildRequestResultGenerics({ node, types }) // 'AddPetResponses, ThrowOnError'`
505
+ */
506
+ function buildRequestResultGenerics({ node, types }) {
507
+ return `${types.response.responses(node)}, ThrowOnError`;
508
+ }
509
+ /**
510
+ * Builds the full return type an operation's function signature uses: `Unwrappable<RequestResult>`
511
+ * for the default `returnType: 'full'`, already a promise so no further wrapping is needed, a plain
512
+ * `Promise<RequestResult>` for the internal `'plain'` shape, or a
513
+ * `Promise` of the runtime's `UnwrappedResult` when `returnType: 'data'` narrows a resolved call
514
+ * down to the bare success body.
515
+ *
516
+ * @example
517
+ * `buildResultType({ node, types, returnType: 'data' }) // 'Promise<UnwrappedResult<AddPetResponses, ThrowOnError>>'`
518
+ */
519
+ function buildResultType({ node, types, returnType }) {
520
+ const generics = buildRequestResultGenerics({
521
+ node,
522
+ types
523
+ });
524
+ if (returnType === "data") return `Promise<UnwrappedResult<${generics}>>`;
525
+ if (returnType === "plain") return `Promise<RequestResult<${generics}>>`;
526
+ return `Unwrappable<RequestResult<${generics}>>`;
527
+ }
528
+ // ../../internals/client/src/builders/returnStatement.ts
529
+ /**
530
+ * Builds the return statement of a generated operation function. With the default
531
+ * `returnType: 'full'` the runtime call already resolves to `{ data, error, request, response }`.
532
+ * The generated code casts that result to the operation's `RequestResult`, then wraps it with
533
+ * `withUnwrap`, so the caller can `await` it directly or call `.unwrap()` for the bare success
534
+ * body. With `returnType: 'data'` it instead routes the call through the runtime's `unwrapResult`,
535
+ * which narrows the resolved value down to the bare success body the same way `RequestResult`
536
+ * already does, using the plugin default when the call leaves `throwOnError` unset.
537
+ * With `returnType: 'plain'` it returns the runtime call as is, cast to `RequestResult`, with no helper.
538
+ *
539
+ * Cast first, wrap second, for the `'full'` path. That order keeps `withUnwrap`'s generic inferred
540
+ * as `RequestResult` instead of the runtime's own internal result type. Casting an `Unwrappable<A>`
541
+ * straight to `Unwrappable<B>` does not work: the `.then` overload stays pinned to `A`, and `as`
542
+ * rejects that two-generic swap even where `A` and `B` on their own would satisfy it.
543
+ *
544
+ * @example
545
+ * `return withUnwrap(request({ method: 'POST', url: '/pet', ...config }) as Promise<RequestResult<AddPetResponses, ThrowOnError>>)`
546
+ * @example
547
+ * `return unwrapResult(request({ method: 'POST', url: '/pet', ...config }), config.throwOnError ?? false) as Promise<UnwrappedResult<AddPetResponses, ThrowOnError>>`
548
+ * @example
549
+ * `return request({ method: 'POST', url: '/pet', ...config }) as Promise<RequestResult<AddPetResponses, ThrowOnError>>`
550
+ */
551
+ function buildReturnStatement({ node, types, callConfig, returnType, throwOnErrorDefault }) {
552
+ const request = returnType !== "plain" && hasRequestContentTypeChoice(node) ? `request<${types.response.options(node)}['body']>` : "request";
553
+ if (returnType === "data") return `return unwrapResult(${request}(${callConfig}), config.throwOnError ?? ${throwOnErrorDefault}) as ${buildResultType({
554
+ node,
555
+ types,
556
+ returnType
557
+ })}`;
558
+ if (returnType === "plain") return `return ${request}(${callConfig}) as Promise<RequestResult<${buildRequestResultGenerics({
559
+ node,
560
+ types
561
+ })}>>`;
562
+ return `return withUnwrap(${request}(${callConfig}) as Promise<RequestResult<${buildRequestResultGenerics({
563
+ node,
564
+ types
565
+ })}>>)`;
566
+ }
567
+ /**
568
+ * Builds the return type and statement of a `text/event-stream` operation. The item type is `TypedEvent<...>` when each item
569
+ * declares its own `event` and `data`, and otherwise the payload type, which `EventStreamResult` wraps in `ServerSentEvent`. The call's
570
+ * promise goes through `toEventStream` with the `client` for its defaults, the call's `config`, the shape and the per-event
571
+ * validator. A `returnType: 'plain'` client receives only the promise,
572
+ * `toEventStream(request(config))`, and keeps its response validator in the call config.
573
+ */
574
+ function buildEventStreamReturn({ node, types, callConfig, returnType, validatorHooks, throwOnErrorDefault }) {
575
+ const shape = getEventStream(node) ?? "data";
576
+ const responses = types.response.responses(node);
577
+ const success = `SuccessOf<${responses}>`;
578
+ const eventType = shape === "event" ? `TypedEvent<${success}>` : success;
579
+ if (returnType === "plain") return {
580
+ returnType: `Promise<EventStreamResult<${eventType}>>`,
581
+ statement: `return toEventStream<${eventType}>(request(${callConfig}))`
582
+ };
583
+ const generics = `${responses}, ThrowOnError${shape === "event" ? `, ${eventType}` : ""}`;
584
+ const call = `toEventStream<${eventType}>(request(${callConfig}), { client: request, config, ${[`shape: '${shape}'`, validatorHooks?.response ? `validator: ${validatorHooks.response}` : null].filter(Boolean).join(", ")} })`;
585
+ if (returnType === "data") {
586
+ const result = `Promise<UnwrappedStreamResult<${generics}>>`;
587
+ return {
588
+ returnType: result,
589
+ statement: `return unwrapResult(${call}, config.throwOnError ?? ${throwOnErrorDefault}) as ${result}`
590
+ };
591
+ }
592
+ return {
593
+ returnType: `Unwrappable<StreamResult<${generics}>>`,
594
+ statement: `return withUnwrap(${call} as Promise<StreamResult<${generics}>>)`
595
+ };
596
+ }
597
+ /** The stream types an operation file imports: `StreamResult` and `StreamOptions`, `EventStreamResult` for a plain client, and `TypedEvent` for named events. */
598
+ function buildEventStreamImports({ returnType, typed }) {
599
+ return [...{
600
+ plain: ["EventStreamResult"],
601
+ full: ["StreamResult", "StreamOptions"],
602
+ data: ["UnwrappedStreamResult", "StreamOptions"]
603
+ }[returnType], ...typed ? ["TypedEvent"] : []];
604
+ }
466
605
  // ../../internals/client/src/builders/security.ts
467
606
  function serializeAuth(auth) {
468
607
  const parts = [`type: '${auth.type}'`];
@@ -618,6 +757,7 @@ function serializeParameter(parameter) {
618
757
  if (parameter.explode !== void 0) parts.push(`explode: ${parameter.explode}`);
619
758
  if (parameter.in === "query" && parameter.allowReserved !== void 0) parts.push(`allowReserved: ${parameter.allowReserved}`);
620
759
  if (parameter.in !== "path" && parameter.contentType) parts.push(`contentType: '${parameter.contentType}'`);
760
+ if (parameter.in === "querystring") parts.push("querystring: true");
621
761
  return parts.length > 0 ? `{ ${parts.join(", ")} }` : null;
622
762
  }
623
763
  /**
@@ -660,10 +800,9 @@ function buildStyles({ node }) {
660
800
  header: [],
661
801
  cookie: []
662
802
  };
663
- for (const parameter of node.parameters) {
803
+ for (const [location, parameters] of Object.entries(getOperationParameters(node))) for (const parameter of parameters) {
664
804
  const literal = serializeParameter(parameter);
665
- if (!literal) continue;
666
- groups[parameter.in].push(`${toKey(parameter.name)}: ${literal}`);
805
+ if (literal) groups[location].push(`${toKey(parameter.name)}: ${literal}`);
667
806
  }
668
807
  const entries = [...Object.keys(groups).filter((location) => groups[location].length > 0).map((location) => `${location}: { ${groups[location].join(", ")} }`), serializeBodyEncoding(node)].filter(Boolean);
669
808
  return entries.length > 0 ? `{ ${entries.join(", ")} }` : null;
@@ -674,12 +813,20 @@ function buildStyles({ node }) {
674
813
  * functions and the SDK methods. The `throwOnError` fallback is applied after the `...config` spread.
675
814
  *
676
815
  * `mergeContentType` is `true` when the caller must destructure `contentType` from `options`, and
677
- * `eventStream` is `true` when the operation returns `text/event-stream`.
816
+ * `eventStream` is `true` when the operation returns `text/event-stream`. Unless `returnType` is `'plain'`, a
817
+ * stream's literal leaves the `response` validator to `toEventStream`, which runs it per event.
678
818
  */
679
- function buildCallConfig({ node, validatorHooks, security, throwOnErrorDefault }) {
819
+ function buildCallConfig({ node, validatorHooks, security, throwOnErrorDefault, returnType }) {
820
+ const eventStream = isEventStream(node);
680
821
  const securityLiteral = buildSecurityMetadata({ security });
681
822
  const stylesLiteral = buildStyles({ node });
682
- const validatorLiteral = buildValidatorLiteral(validatorHooks, [
823
+ const validatorLiteral = buildValidatorLiteral(validatorHooks, eventStream && returnType !== "plain" ? [
824
+ "request",
825
+ "path",
826
+ "query",
827
+ "headers",
828
+ "error"
829
+ ] : [
683
830
  "request",
684
831
  "path",
685
832
  "query",
@@ -702,88 +849,7 @@ function buildCallConfig({ node, validatorHooks, security, throwOnErrorDefault }
702
849
  `throwOnError: config.throwOnError ?? ${throwOnErrorDefault}`
703
850
  ].filter(Boolean).join(", ")} }`,
704
851
  mergeContentType,
705
- eventStream: isEventStream(node)
706
- };
707
- }
708
- // ../../internals/client/src/builders/generics.ts
709
- /**
710
- * Builds the `RequestResult` generic arguments for one operation: the per-status responses record
711
- * plus the per-call `ThrowOnError` flag. `SuccessOf` / `ErrorOf` split the record inside the
712
- * runtime, so this only names the record and threads `ThrowOnError`.
713
- *
714
- * @example
715
- * `buildRequestResultGenerics({ node, types }) // 'AddPetResponses, ThrowOnError'`
716
- */
717
- function buildRequestResultGenerics({ node, types }) {
718
- return `${types.response.responses(node)}, ThrowOnError`;
719
- }
720
- /**
721
- * Builds the full return type an operation's function signature uses: `Unwrappable<RequestResult>`
722
- * for the default `returnType: 'full'`, already a promise so no further wrapping is needed, a plain
723
- * `Promise<RequestResult>` for the internal `'plain'` shape, or a
724
- * `Promise` of the runtime's `UnwrappedResult` when `returnType: 'data'` narrows a resolved call
725
- * down to the bare success body.
726
- *
727
- * @example
728
- * `buildResultType({ node, types, returnType: 'data' }) // 'Promise<UnwrappedResult<AddPetResponses, ThrowOnError>>'`
729
- */
730
- function buildResultType({ node, types, returnType }) {
731
- const generics = buildRequestResultGenerics({
732
- node,
733
- types
734
- });
735
- if (returnType === "data") return `Promise<UnwrappedResult<${generics}>>`;
736
- if (returnType === "plain") return `Promise<RequestResult<${generics}>>`;
737
- return `Unwrappable<RequestResult<${generics}>>`;
738
- }
739
- // ../../internals/client/src/builders/returnStatement.ts
740
- /**
741
- * Builds the return statement of a generated operation function. With the default
742
- * `returnType: 'full'` the runtime call already resolves to `{ data, error, request, response }`.
743
- * The generated code casts that result to the operation's `RequestResult`, then wraps it with
744
- * `withUnwrap`, so the caller can `await` it directly or call `.unwrap()` for the bare success
745
- * body. With `returnType: 'data'` it instead routes the call through the runtime's `unwrapResult`,
746
- * which narrows the resolved value down to the bare success body the same way `RequestResult`
747
- * already does, using the plugin default when the call leaves `throwOnError` unset.
748
- * With `returnType: 'plain'` it returns the runtime call as is, cast to `RequestResult`, with no helper.
749
- *
750
- * Cast first, wrap second, for the `'full'` path. That order keeps `withUnwrap`'s generic inferred
751
- * as `RequestResult` instead of the runtime's own internal result type. Casting an `Unwrappable<A>`
752
- * straight to `Unwrappable<B>` does not work: the `.then` overload stays pinned to `A`, and `as`
753
- * rejects that two-generic swap even where `A` and `B` on their own would satisfy it.
754
- *
755
- * @example
756
- * `return withUnwrap(request({ method: 'POST', url: '/pet', ...config }) as Promise<RequestResult<AddPetResponses, ThrowOnError>>)`
757
- * @example
758
- * `return unwrapResult(request({ method: 'POST', url: '/pet', ...config }), config.throwOnError ?? false) as Promise<UnwrappedResult<AddPetResponses, ThrowOnError>>`
759
- * @example
760
- * `return request({ method: 'POST', url: '/pet', ...config }) as Promise<RequestResult<AddPetResponses, ThrowOnError>>`
761
- */
762
- function buildReturnStatement({ node, types, callConfig, returnType, throwOnErrorDefault }) {
763
- const request = returnType !== "plain" && hasRequestContentTypeChoice(node) ? `request<${types.response.options(node)}['body']>` : "request";
764
- if (returnType === "data") return `return unwrapResult(${request}(${callConfig}), config.throwOnError ?? ${throwOnErrorDefault}) as ${buildResultType({
765
- node,
766
- types,
767
- returnType
768
- })}`;
769
- if (returnType === "plain") return `return ${request}(${callConfig}) as Promise<RequestResult<${buildRequestResultGenerics({
770
- node,
771
- types
772
- })}>>`;
773
- return `return withUnwrap(${request}(${callConfig}) as Promise<RequestResult<${buildRequestResultGenerics({
774
- node,
775
- types
776
- })}>>)`;
777
- }
778
- /**
779
- * Builds the return type and statement of an operation that returns `text/event-stream`: the call
780
- * is wrapped in `toEventStream`, so the caller iterates the typed success events.
781
- */
782
- function buildEventStreamReturn({ node, types, callConfig }) {
783
- const eventType = `SuccessOf<${types.response.responses(node)}>`;
784
- return {
785
- returnType: `Promise<EventStreamResult<${eventType}>>`,
786
- statement: `return toEventStream<${eventType}>(request(${callConfig}))`
852
+ eventStream
787
853
  };
788
854
  }
789
855
  // ../../internals/client/src/builders/signature.ts
@@ -797,13 +863,14 @@ const declarationPrinter = functionPrinter({ mode: "declaration" });
797
863
  * per-operation input type has to be emitted. Both names come from `types`, which is `plugin-ts` or
798
864
  * `plugin-zod`'s inferred types (see `resolveOperationTypes`).
799
865
  */
800
- function buildGroupedOptionsSignature({ node, types, returnType, throwOnErrorDefault }) {
866
+ function buildGroupedOptionsSignature({ node, types, returnType, throwOnErrorDefault, eventStream = false }) {
801
867
  const optionsName = types.response.options(node);
868
+ const optionsType = `${eventStream && returnType !== "plain" ? "StreamOptions" : "Options"}<${optionsName}, ThrowOnError>`;
802
869
  const { isOptional } = getRequestGroupOptionality(node);
803
870
  return {
804
871
  paramsSignature: declarationPrinter.print(createFunctionParameters({ params: [createFunctionParameter({
805
872
  name: "options",
806
- type: `Options<${optionsName}, ThrowOnError>`,
873
+ type: optionsType,
807
874
  ...isOptional ? { default: "{}" } : {}
808
875
  })] })) ?? "",
809
876
  returnType: buildResultType({
@@ -814,37 +881,63 @@ function buildGroupedOptionsSignature({ node, types, returnType, throwOnErrorDef
814
881
  generics: [`ThrowOnError extends boolean = ${throwOnErrorDefault}`]
815
882
  };
816
883
  }
817
- // ../../internals/client/src/components/Operation.tsx
884
+ // ../../internals/client/src/builders/operationBody.ts
818
885
  /**
819
- * Renders one client operation: the grouped `<Name>Request` type and the function that forwards a
820
- * single `options` object to the resolved client and returns the `Unwrappable<RequestResult>`. The
821
- * type, signature, and call config are built with the AST factory, and only the jsx-renderer emits
822
- * the source.
886
+ * The pieces of a generated operation: its generics, `options` parameter, return type, return statement, and whether the
887
+ * body destructures `contentType` from `options`. Shared by the standalone functions and the SDK methods, so a
888
+ * `text/event-stream` operation is wired to `toEventStream` in one place.
823
889
  */
824
- function Operation({ name, node, types, validatorHooks, returnType, throwOnErrorDefault, security }) {
825
- if (!ast.isHttpOperationNode(node)) return null;
890
+ function buildOperationBody({ node, types, validatorHooks, security, returnType, throwOnErrorDefault }) {
891
+ const { callConfig, mergeContentType, eventStream } = buildCallConfig({
892
+ node,
893
+ validatorHooks,
894
+ security,
895
+ throwOnErrorDefault,
896
+ returnType
897
+ });
826
898
  const signature = buildGroupedOptionsSignature({
827
899
  node,
828
900
  types,
829
901
  returnType,
830
- throwOnErrorDefault
902
+ throwOnErrorDefault,
903
+ eventStream
831
904
  });
832
- const { callConfig, mergeContentType, eventStream } = buildCallConfig({
905
+ const stream = eventStream ? buildEventStreamReturn({
833
906
  node,
907
+ types,
908
+ callConfig,
909
+ returnType,
834
910
  validatorHooks,
835
- security,
836
911
  throwOnErrorDefault
837
- });
838
- const eventStreamReturn = eventStream ? buildEventStreamReturn({
839
- node,
840
- types,
841
- callConfig
842
912
  }) : null;
843
- const functionReturnType = eventStreamReturn?.returnType ?? signature.returnType;
844
- const returnStatement = eventStreamReturn?.statement ?? buildReturnStatement({
913
+ return {
914
+ generics: signature.generics,
915
+ paramsSignature: signature.paramsSignature,
916
+ returnType: stream?.returnType ?? signature.returnType,
917
+ statement: stream?.statement ?? buildReturnStatement({
918
+ node,
919
+ types,
920
+ callConfig,
921
+ returnType,
922
+ throwOnErrorDefault
923
+ }),
924
+ mergeContentType
925
+ };
926
+ }
927
+ // ../../internals/client/src/components/Operation.tsx
928
+ /**
929
+ * Renders one client operation: the grouped `<Name>Request` type and the function that forwards a
930
+ * single `options` object to the resolved client and returns the `Unwrappable<RequestResult>`. The
931
+ * type, signature, and call config are built with the AST factory, and only the jsx-renderer emits
932
+ * the source.
933
+ */
934
+ function Operation({ name, node, types, validatorHooks, returnType, throwOnErrorDefault, security }) {
935
+ if (!ast.isHttpOperationNode(node)) return null;
936
+ const body = buildOperationBody({
845
937
  node,
846
938
  types,
847
- callConfig,
939
+ validatorHooks,
940
+ security,
848
941
  returnType,
849
942
  throwOnErrorDefault
850
943
  });
@@ -855,22 +948,53 @@ function Operation({ name, node, types, validatorHooks, returnType, throwOnError
855
948
  children: /* @__PURE__ */ jsxs(Function, {
856
949
  name,
857
950
  export: true,
858
- generics: signature.generics,
859
- params: signature.paramsSignature,
860
- returnType: functionReturnType,
951
+ generics: body.generics,
952
+ params: body.paramsSignature,
953
+ returnType: body.returnType,
861
954
  JSDoc: { comments: buildOperationComments(node, {
862
955
  link: "urlPath",
863
956
  linkPosition: "beforeDeprecated",
864
957
  splitLines: true
865
958
  }) },
866
959
  children: [
867
- mergeContentType ? "const { client: request = client, contentType, ...config } = options" : "const { client: request = client, ...config } = options",
960
+ body.mergeContentType ? "const { client: request = client, contentType, ...config } = options" : "const { client: request = client, ...config } = options",
868
961
  /* @__PURE__ */ jsx("br", {}),
869
- returnStatement
962
+ body.statement
870
963
  ]
871
964
  })
872
965
  });
873
966
  }
967
+ // ../../internals/client/src/components/StreamRuntime.tsx
968
+ /**
969
+ * The `footer` of `.kubb/client.ts`: the `baseURL` set at generation time, and the `sse.ts` re-export when the document
970
+ * streams events, so `ServerSentEvent` and `toEventStream` stay importable from the client as in earlier releases.
971
+ */
972
+ function buildClientFooter({ baseURL, sse }) {
973
+ const baseURLExpression = baseURL ? baseURL.includes("${") ? `\`${baseURL.replaceAll("`", "\\`")}\`` : JSON.stringify(baseURL) : void 0;
974
+ return [baseURLExpression ? `client.setConfig({ baseURL: ${baseURLExpression} })` : null, sse ? `export * from './sse.ts'` : null].filter(Boolean).join("\n") || void 0;
975
+ }
976
+ /**
977
+ * Writes the footer of the `.kubb/client.ts` the plugin injected, the one place it is built, and copies `sse.ts` into
978
+ * `.kubb/` only for a document with a `text/event-stream` operation, so a document without streams ships no stream runtime.
979
+ * `sse` is set by the plugins that ship the runtime, so a custom client from `@kubb/plugin-client` gets neither file.
980
+ */
981
+ function StreamRuntime({ nodes, root, sse, baseURL }) {
982
+ if (!sse) return null;
983
+ const streams = nodes.some(isEventStream);
984
+ const footer = buildClientFooter({
985
+ baseURL,
986
+ sse: streams
987
+ });
988
+ return /* @__PURE__ */ jsxs(Fragment, { children: [streams && /* @__PURE__ */ jsx(File, {
989
+ baseName: "sse.ts",
990
+ path: path.resolve(root, ".kubb/sse.ts"),
991
+ copy: sse
992
+ }), footer && /* @__PURE__ */ jsx(File, {
993
+ baseName: "client.ts",
994
+ path: path.resolve(root, ".kubb/client.ts"),
995
+ footer
996
+ })] });
997
+ }
874
998
  // ../../internals/client/src/resolveOperationTypes.ts
875
999
  /**
876
1000
  * A `plugin-ts` shaped resolver whose operation type names point at the types `pluginZod({ inferred })`
@@ -946,13 +1070,24 @@ function resolveOperationTypes(driver, pluginName) {
946
1070
  * `@kubb/plugin-axios`). Emits one async function per OpenAPI operation using the shared
947
1071
  * `Operation` component: a grouped `<Name>Request` type and a function that forwards a single
948
1072
  * `options` object to the bundled `client` and returns the `Unwrappable<RequestResult>`. Only the
949
- * generator `name` differs between plugins. Every other resolution, import, and rendering step is
950
- * identical.
1073
+ * generator `name` and the runtime templates differ between plugins. Every other resolution, import,
1074
+ * and rendering step is identical.
1075
+ *
1076
+ * `sse` is the Server-Sent Events runtime template, copied into `.kubb/sse.ts` only for a document with a
1077
+ * `text/event-stream` operation.
951
1078
  */
952
- function createClientGenerator(name) {
1079
+ function createClientGenerator(name, { sse } = {}) {
953
1080
  return defineGenerator({
954
1081
  name,
955
1082
  renderer: jsxRenderer,
1083
+ operations(nodes, ctx) {
1084
+ return /* @__PURE__ */ jsx(StreamRuntime, {
1085
+ nodes,
1086
+ root: ctx.root,
1087
+ sse,
1088
+ baseURL: ctx.options.baseURL
1089
+ });
1090
+ },
956
1091
  operation(node, ctx) {
957
1092
  if (!ast.isHttpOperationNode(node)) return null;
958
1093
  const { config, driver, resolver, root } = ctx;
@@ -1002,19 +1137,19 @@ function createClientGenerator(name) {
1002
1137
  const clientPath = importPath ?? path.resolve(root, ".kubb/client.ts");
1003
1138
  const clientRoot = importPath === void 0 ? meta.file.path : void 0;
1004
1139
  const standardSchemaPath = importPath ?? path.resolve(root, ".kubb/standardSchema.ts");
1005
- const eventStream = isEventStream(node);
1140
+ const ssePath = importPath ?? path.resolve(root, ".kubb/sse.ts");
1141
+ const stream = getEventStream(node);
1006
1142
  const clientValueImports = (() => {
1007
- if (eventStream) return ["client", "toEventStream"];
1008
1143
  if (returnType === "plain") return ["client"];
1009
1144
  if (returnType === "data") return ["client", "unwrapResult"];
1010
1145
  return ["client", "withUnwrap"];
1011
1146
  })();
1012
1147
  const clientTypeImports = (() => {
1013
- if (eventStream) return [
1014
- "Options",
1015
- "EventStreamResult",
1016
- "SuccessOf"
1017
- ];
1148
+ if (stream) return {
1149
+ plain: ["Options", "SuccessOf"],
1150
+ data: ["SuccessOf"],
1151
+ full: ["SuccessOf", "Unwrappable"]
1152
+ }[returnType];
1018
1153
  if (returnType === "plain") return ["Options", "RequestResult"];
1019
1154
  if (returnType === "data") return ["Options", "UnwrappedResult"];
1020
1155
  return [
@@ -1055,6 +1190,20 @@ function createClientGenerator(name) {
1055
1190
  path: clientPath,
1056
1191
  isTypeOnly: true
1057
1192
  }),
1193
+ stream && /* @__PURE__ */ jsx(File.Import, {
1194
+ name: ["toEventStream"],
1195
+ root: clientRoot,
1196
+ path: ssePath
1197
+ }),
1198
+ stream && /* @__PURE__ */ jsx(File.Import, {
1199
+ name: buildEventStreamImports({
1200
+ returnType,
1201
+ typed: stream === "event"
1202
+ }),
1203
+ root: clientRoot,
1204
+ path: ssePath,
1205
+ isTypeOnly: true
1206
+ }),
1058
1207
  meta.fileTypes && importedTypeNames.length > 0 && /* @__PURE__ */ jsx(File.Import, {
1059
1208
  name: Array.from(new Set(importedTypeNames)),
1060
1209
  root: meta.file.path,
@@ -1091,43 +1240,26 @@ function createClientGenerator(name) {
1091
1240
  */
1092
1241
  function buildSdkMethod({ node, name, types, validatorHooks, security, returnType, throwOnErrorDefault }) {
1093
1242
  if (!ast.isHttpOperationNode(node)) return "";
1094
- const signature = buildGroupedOptionsSignature({
1243
+ const body = buildOperationBody({
1095
1244
  node,
1096
1245
  types,
1097
- returnType,
1098
- throwOnErrorDefault
1099
- });
1100
- const { callConfig, mergeContentType, eventStream } = buildCallConfig({
1101
- node,
1102
1246
  validatorHooks,
1103
1247
  security,
1104
- throwOnErrorDefault
1105
- });
1106
- const eventStreamReturn = eventStream ? buildEventStreamReturn({
1107
- node,
1108
- types,
1109
- callConfig
1110
- }) : null;
1111
- const methodReturnType = eventStreamReturn?.returnType ?? signature.returnType;
1112
- const returnStatement = eventStreamReturn?.statement ?? buildReturnStatement({
1113
- node,
1114
- types,
1115
- callConfig,
1116
1248
  returnType,
1117
1249
  throwOnErrorDefault
1118
1250
  });
1119
- const generics = signature.generics.length ? `<${signature.generics.join(", ")}>` : "";
1251
+ const generics = body.generics.length ? `<${body.generics.join(", ")}>` : "";
1120
1252
  const jsdoc = buildJSDoc(buildOperationComments(node, {
1121
1253
  link: "urlPath",
1122
1254
  linkPosition: "beforeDeprecated",
1123
1255
  splitLines: true
1124
1256
  }));
1125
1257
  const methodBody = [
1126
- mergeContentType ? "const { client: request = this.client, contentType, ...config } = options" : "const { client: request = this.client, ...config } = options",
1258
+ body.mergeContentType ? "const { client: request = this.client, contentType, ...config } = options" : "const { client: request = this.client, ...config } = options",
1127
1259
  "",
1128
- returnStatement
1260
+ body.statement
1129
1261
  ].map((line) => line ? ` ${line}` : "").join("\n");
1130
- return `${jsdoc} public ${name}${generics}(${signature.paramsSignature}): ${methodReturnType} {\n${methodBody}\n }`;
1262
+ return `${jsdoc} public ${name}${generics}(${body.paramsSignature}): ${body.returnType} {\n${methodBody}\n }`;
1131
1263
  }
1132
1264
  /**
1133
1265
  * Renders one instance class per tag with one method per operation. The constructor takes a client
@@ -1283,8 +1415,11 @@ function collectImportsByFile(ops, pick) {
1283
1415
  * emits one class per tag and, when `sdk.name` is set, a composed root that instantiates every tag
1284
1416
  * client. With `sdk.mode: 'flat'` it emits one class named by `sdk.name`, with every operation as a
1285
1417
  * direct method.
1418
+ *
1419
+ * `sse` is the Server-Sent Events runtime template, copied into `.kubb/sse.ts` only for a document with a
1420
+ * `text/event-stream` operation.
1286
1421
  */
1287
- function createSdkGenerator() {
1422
+ function createSdkGenerator({ sse } = {}) {
1288
1423
  return defineGenerator({
1289
1424
  name: "sdk",
1290
1425
  renderer: jsxRenderer,
@@ -1305,6 +1440,7 @@ function createSdkGenerator() {
1305
1440
  const controllers = buildControllers(nodes, ctx, types, validatorEnabled);
1306
1441
  const clientPath = path.resolve(root, ".kubb/client.ts");
1307
1442
  const standardSchemaPath = path.resolve(root, ".kubb/standardSchema.ts");
1443
+ const ssePath = path.resolve(root, ".kubb/sse.ts");
1308
1444
  const banner = (file) => resolver.default.banner(ctx.meta, {
1309
1445
  output,
1310
1446
  config,
@@ -1333,7 +1469,8 @@ function createSdkGenerator() {
1333
1469
  namesByPath: /* @__PURE__ */ new Map(),
1334
1470
  filesByPath: /* @__PURE__ */ new Map()
1335
1471
  };
1336
- const hasEventStream = ops.some((op) => isEventStream(op.node));
1472
+ const shapes = new Set(ops.map((op) => getEventStream(op.node)).filter(Boolean));
1473
+ const hasEventStream = shapes.size > 0;
1337
1474
  const hasResult = ops.some((op) => !isEventStream(op.node));
1338
1475
  return /* @__PURE__ */ jsxs(File, {
1339
1476
  baseName: file.baseName,
@@ -1343,11 +1480,7 @@ function createSdkGenerator() {
1343
1480
  footer: footer(file),
1344
1481
  children: [
1345
1482
  /* @__PURE__ */ jsx(File.Import, {
1346
- name: [
1347
- "createClient",
1348
- ...hasResult ? [returnType === "data" ? "unwrapResult" : "withUnwrap"] : [],
1349
- ...hasEventStream ? ["toEventStream"] : []
1350
- ],
1483
+ name: ["createClient", returnType === "data" ? "unwrapResult" : "withUnwrap"],
1351
1484
  root: file.path,
1352
1485
  path: clientPath
1353
1486
  }),
@@ -1356,13 +1489,28 @@ function createSdkGenerator() {
1356
1489
  "ClientConfig",
1357
1490
  "ClientInstance",
1358
1491
  "Options",
1359
- ...hasResult ? returnType === "data" ? ["UnwrappedResult"] : ["Unwrappable", "RequestResult"] : [],
1360
- ...hasEventStream ? ["EventStreamResult", "SuccessOf"] : []
1492
+ ...returnType === "data" ? [] : ["Unwrappable"],
1493
+ ...hasResult ? [returnType === "data" ? "UnwrappedResult" : "RequestResult"] : [],
1494
+ ...hasEventStream ? ["SuccessOf"] : []
1361
1495
  ],
1362
1496
  root: file.path,
1363
1497
  path: clientPath,
1364
1498
  isTypeOnly: true
1365
1499
  }),
1500
+ hasEventStream && /* @__PURE__ */ jsx(File.Import, {
1501
+ name: ["toEventStream"],
1502
+ root: file.path,
1503
+ path: ssePath
1504
+ }),
1505
+ hasEventStream && /* @__PURE__ */ jsx(File.Import, {
1506
+ name: buildEventStreamImports({
1507
+ returnType,
1508
+ typed: shapes.has("event")
1509
+ }),
1510
+ root: file.path,
1511
+ path: ssePath,
1512
+ isTypeOnly: true
1513
+ }),
1366
1514
  validator === "zod" && ops.some((op) => op.node.requestBody?.content?.[0]?.schema != null) && /* @__PURE__ */ jsx(File.Import, {
1367
1515
  name: ["z"],
1368
1516
  path: "zod",
@@ -1393,61 +1541,66 @@ function createSdkGenerator() {
1393
1541
  ]
1394
1542
  }, file.path);
1395
1543
  };
1396
- if (sdk.mode === "flat") return renderClassFile(resolver.className(sdk.name ?? "sdk"), resolver.file({
1544
+ const renderFacadeFile = (name) => {
1545
+ const sdkFile = resolver.file({
1546
+ name,
1547
+ extname: ".ts",
1548
+ root,
1549
+ output,
1550
+ group: group ?? void 0
1551
+ });
1552
+ const members = controllers.map(({ name, tag }) => ({
1553
+ className: name,
1554
+ propName: resolver.propertyName(tag ?? name)
1555
+ }));
1556
+ return /* @__PURE__ */ jsxs(File, {
1557
+ baseName: sdkFile.baseName,
1558
+ path: sdkFile.path,
1559
+ meta: sdkFile.meta,
1560
+ banner: banner(sdkFile),
1561
+ footer: footer(sdkFile),
1562
+ children: [
1563
+ /* @__PURE__ */ jsx(File.Import, {
1564
+ name: ["ClientConfig"],
1565
+ root: sdkFile.path,
1566
+ path: clientPath,
1567
+ isTypeOnly: true
1568
+ }),
1569
+ controllers.map(({ name, file }) => /* @__PURE__ */ jsx(File.Import, {
1570
+ name: [name],
1571
+ root: sdkFile.path,
1572
+ path: file.path
1573
+ }, name)),
1574
+ /* @__PURE__ */ jsx(SdkFacade, {
1575
+ name: resolver.className(name),
1576
+ members
1577
+ })
1578
+ ]
1579
+ }, sdkFile.path);
1580
+ };
1581
+ const files = sdk.mode === "flat" ? renderClassFile(resolver.className(sdk.name ?? "sdk"), resolver.file({
1397
1582
  name: sdk.name ?? "sdk",
1398
1583
  extname: ".ts",
1399
1584
  root,
1400
1585
  output,
1401
1586
  group: group ?? void 0
1402
- }), controllers.flatMap((controller) => controller.operations));
1403
- const classFiles = controllers.map(({ name, file, operations: ops }) => renderClassFile(name, file, ops));
1404
- if (!sdk.name) return /* @__PURE__ */ jsx(Fragment, { children: classFiles });
1405
- const sdkFile = resolver.file({
1406
- name: sdk.name,
1407
- extname: ".ts",
1587
+ }), controllers.flatMap((controller) => controller.operations)) : [...controllers.map(({ name, file, operations: ops }) => renderClassFile(name, file, ops)), sdk.name && renderFacadeFile(sdk.name)];
1588
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(StreamRuntime, {
1589
+ nodes,
1408
1590
  root,
1409
- output,
1410
- group: group ?? void 0
1411
- });
1412
- const facadeName = resolver.className(sdk.name);
1413
- const members = controllers.map(({ name, tag }) => ({
1414
- className: name,
1415
- propName: resolver.propertyName(tag ?? name)
1416
- }));
1417
- return /* @__PURE__ */ jsxs(Fragment, { children: [classFiles, /* @__PURE__ */ jsxs(File, {
1418
- baseName: sdkFile.baseName,
1419
- path: sdkFile.path,
1420
- meta: sdkFile.meta,
1421
- banner: banner(sdkFile),
1422
- footer: footer(sdkFile),
1423
- children: [
1424
- /* @__PURE__ */ jsx(File.Import, {
1425
- name: ["ClientConfig"],
1426
- root: sdkFile.path,
1427
- path: clientPath,
1428
- isTypeOnly: true
1429
- }),
1430
- controllers.map(({ name, file }) => /* @__PURE__ */ jsx(File.Import, {
1431
- name: [name],
1432
- root: sdkFile.path,
1433
- path: file.path
1434
- }, name)),
1435
- /* @__PURE__ */ jsx(SdkFacade, {
1436
- name: facadeName,
1437
- members
1438
- })
1439
- ]
1440
- }, sdkFile.path)] });
1591
+ sse,
1592
+ baseURL: ctx.options.baseURL
1593
+ }), files] });
1441
1594
  }
1442
1595
  });
1443
1596
  }
1444
1597
  // ../../internals/client/src/macros.ts
1445
1598
  /**
1446
- * Macros the client plugins apply by default, ahead of any user macros. `macroSimplifyUnion`
1447
- * drops union members a broader scalar already covers, keeping the generated response and error
1599
+ * Macros the client plugins apply by default, ahead of any user macros. `macroDedupeUnion`
1600
+ * drops repeated union members, keeping the generated response and error
1448
1601
  * unions tidy. A plugin wires them with `ctx.setMacros([...defaultMacros, ...userMacros])`.
1449
1602
  */
1450
- const defaultMacros = [macroSimplifyUnion];
1603
+ const defaultMacros = [macroDedupeUnion];
1451
1604
  // ../../internals/client/src/resolver.ts
1452
1605
  /**
1453
1606
  * Default resolver shared by the client plugins. Functions use safe camelCase identifiers and
@@ -1472,30 +1625,32 @@ const resolverClient = createResolver({
1472
1625
  return ensureValidVarName(camelCase(name));
1473
1626
  }
1474
1627
  });
1628
+ // src/templates.ts
1629
+ const require = createRequire(import.meta.url);
1630
+ /** Absolute path to the fetch client template, copied into `.kubb/client.ts`. */
1631
+ const fetchClientTemplatePath = fileURLToPath(new URL("../templates/client.ts", import.meta.url));
1632
+ /**
1633
+ * Absolute paths to the runtime templates `@kubb/plugin-client` ships for every client plugin, copied into `.kubb/core.ts`,
1634
+ * `.kubb/serializers.ts`, `.kubb/standardSchema.ts`, and `.kubb/sse.ts`.
1635
+ */
1636
+ const coreTemplatePath = require.resolve("@kubb/plugin-client/templates/core.ts");
1637
+ const serializersTemplatePath = require.resolve("@kubb/plugin-client/templates/serializers.ts");
1638
+ const standardSchemaTemplatePath = require.resolve("@kubb/plugin-client/templates/standardSchema.ts");
1639
+ const sseTemplatePath = require.resolve("@kubb/plugin-client/templates/sse.ts");
1475
1640
  // src/generators/clientGenerator.tsx
1476
1641
  /**
1477
1642
  * Built-in operation generator for `@kubb/plugin-fetch`. Emits one async function per OpenAPI
1478
1643
  * operation using the shared `Operation` component: a grouped `<Name>Request` type and a function
1479
1644
  * that forwards a single `options` object to the bundled `client` and returns the `RequestResult`.
1480
1645
  */
1481
- const clientGenerator = createClientGenerator("fetch");
1646
+ const clientGenerator = createClientGenerator("fetch", { sse: sseTemplatePath });
1482
1647
  // kubb-extension.json
1483
1648
  var compatibility = {
1484
- "kubb": ">=5.14.6",
1649
+ "kubb": ">=5.16.0",
1485
1650
  "node": ">=22.22"
1486
1651
  };
1487
1652
  // package.json
1488
- var version = "5.13.0-canary.20261105T105941";
1489
- // src/templates.ts
1490
- /** Absolute path to the fetch client template, copied into `.kubb/client.ts`. */
1491
- const fetchClientTemplatePath = fileURLToPath(new URL("../templates/fetch.ts", import.meta.url));
1492
- /** Absolute path to the fetch serializers template, copied into `.kubb/serializers.ts`. */
1493
- const fetchSerializersTemplatePath = fileURLToPath(new URL("../templates/serializers.ts", import.meta.url));
1494
- /**
1495
- * Absolute path to the Standard Schema runtime template. Pass it to a file node's `copy` field to
1496
- * emit the helper into the generated `.kubb/standardSchema.ts` verbatim.
1497
- */
1498
- const standardSchemaTemplatePath = fileURLToPath(new URL("../templates/standardSchema.ts", import.meta.url));
1653
+ var version = "5.13.0";
1499
1654
  // src/plugin.ts
1500
1655
  /**
1501
1656
  * Canonical plugin name for `@kubb/plugin-fetch`. Used for driver lookups and cross-plugin
@@ -1559,19 +1714,22 @@ const pluginFetch = defineExtension({
1559
1714
  };
1560
1715
  setResolver(resolved.resolver);
1561
1716
  setMacros([...defaultMacros, ...options.macros ?? []]);
1562
- addGenerator(resolved.sdk ? createSdkGenerator() : clientGenerator);
1717
+ addGenerator(resolved.sdk ? createSdkGenerator({ sse: sseTemplatePath }) : clientGenerator);
1563
1718
  const root = path.resolve(config.root, config.output.path);
1564
- const baseURLExpression = baseURL ? baseURL.includes("${") ? `\`${baseURL.replaceAll("`", "\\`")}\`` : JSON.stringify(baseURL) : void 0;
1565
1719
  injectFile({
1566
1720
  baseName: "serializers.ts",
1567
1721
  path: path.resolve(root, ".kubb/serializers.ts"),
1568
- copy: fetchSerializersTemplatePath
1722
+ copy: serializersTemplatePath
1723
+ });
1724
+ injectFile({
1725
+ baseName: "core.ts",
1726
+ path: path.resolve(root, ".kubb/core.ts"),
1727
+ copy: coreTemplatePath
1569
1728
  });
1570
1729
  injectFile({
1571
1730
  baseName: "client.ts",
1572
1731
  path: path.resolve(root, ".kubb/client.ts"),
1573
- copy: fetchClientTemplatePath,
1574
- footer: baseURLExpression ? `client.setConfig({ baseURL: ${baseURLExpression} })` : void 0
1732
+ copy: fetchClientTemplatePath
1575
1733
  });
1576
1734
  injectFile({
1577
1735
  baseName: "standardSchema.ts",