@kubb/plugin-client 5.10.0-canary.20261105T162357 → 5.10.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.cjs CHANGED
@@ -110,20 +110,33 @@ function getPrimarySuccessContentType(node) {
110
110
  return contentTypes[0].split(";")[0].trim().toLowerCase();
111
111
  }
112
112
  /**
113
- * Whether an operation streams its primary success response as Server-Sent Events
114
- * (`text/event-stream`). The client generator uses this to return a typed event stream instead of a
115
- * one-shot `RequestResult`.
113
+ * The `text/event-stream` content of the primary success response, when it is the only content type. A response that also
114
+ * declares another type stays a normal call with its query hooks.
115
+ */
116
+ function getEventStreamContent(node) {
117
+ return getPrimarySuccessContentType(node) === "text/event-stream" ? getPrimarySuccessResponse(node)?.content?.[0] : void 0;
118
+ }
119
+ /**
120
+ * Whether an operation streams its primary success response as Server-Sent Events (`text/event-stream`). The client
121
+ * generator uses this to return a typed event stream instead of a one-shot `RequestResult`.
116
122
  */
117
123
  function isEventStream(node) {
118
- return getPrimarySuccessContentType(node) === "text/event-stream";
124
+ return getEventStreamContent(node) !== void 0;
125
+ }
126
+ /**
127
+ * How the document types a `text/event-stream` response, as the adapter read it from `itemSchema`: `event` when each item is
128
+ * the parsed event with its own `event` and `data`, `data` when it is only the payload. Returns `null` without an event stream.
129
+ */
130
+ function getEventStream(node) {
131
+ const content = getEventStreamContent(node);
132
+ return content ? content.stream?.shape ?? "data" : null;
119
133
  }
120
134
  /**
121
135
  * Derives the default `responseType` for an operation from its primary success response.
122
136
  *
123
- * Returns a value only when that response declares a single non-JSON content type. `text/event-stream`
124
- * and other binary types (`application/octet-stream`, `application/pdf`, `image/*`, `audio/*`,
125
- * `video/*`) map to a stream or `'blob'`, and other `text/*` maps to `'text'`. Otherwise `undefined`,
126
- * leaving the runtime client's `Content-Type` auto-detection in charge.
137
+ * Returns a value only when that response declares a single non-JSON content type. `text/event-stream` maps to
138
+ * `'stream'`, `application/octet-stream`, `application/pdf`, `image/*`, `audio/*` and `video/*` map to `'blob'`, and
139
+ * other `text/*` to `'text'`. Otherwise `undefined`, leaving the runtime client's `Content-Type` auto-detection in charge.
127
140
  */
128
141
  function getResponseType(node) {
129
142
  const baseType = getPrimarySuccessContentType(node);
@@ -183,6 +196,22 @@ function buildOperationComments(node, options = {}) {
183
196
  }
184
197
  const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
185
198
  /**
199
+ * An OpenAPI 3.2 `querystring` parameter is the whole query string, so a form-encoded object becomes one query parameter
200
+ * per property, typed and serialized like any other.
201
+ * Another content type, such as JSON, stays one parameter named after it, and the runtime sends its value as the whole query string.
202
+ */
203
+ function spreadQuerystring(param) {
204
+ const schema = param.schema.type === "ref" ? param.schema.schema : param.schema;
205
+ const formEncoded = !param.contentType || param.contentType.split(";")[0].trim().toLowerCase() === "application/x-www-form-urlencoded";
206
+ if (schema?.type !== "object" || !formEncoded) return [param];
207
+ return schema.properties.map((property) => _kubb_kit.ast.factory.createParameter({
208
+ name: property.name,
209
+ in: "query",
210
+ schema: property.schema,
211
+ required: param.required && property.required
212
+ }));
213
+ }
214
+ /**
186
215
  * Groups an operation's parameters by location (`path`/`query`/`header`/`cookie`), deduping each
187
216
  * group by name. Every plugin generator visiting the same `OperationNode` shares one AST instance
188
217
  * (see `KubbDriver`), so the result is cached per node to avoid re-filtering and re-deduping the
@@ -191,17 +220,18 @@ const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
191
220
  function getOperationParameters(node) {
192
221
  const cached = operationParameterGroupsByNode.get(node);
193
222
  if (cached) return cached;
194
- const group = (location) => {
223
+ const dedupe = (params) => {
195
224
  const seen = /* @__PURE__ */ new Set();
196
- return node.parameters.filter((param) => {
197
- if (param.in !== location || seen.has(param.name)) return false;
225
+ return params.filter((param) => {
226
+ if (seen.has(param.name)) return false;
198
227
  seen.add(param.name);
199
228
  return true;
200
229
  });
201
230
  };
231
+ const group = (location) => dedupe(node.parameters.filter((param) => param.in === location));
202
232
  const groups = {
203
233
  path: group("path"),
204
- query: group("query"),
234
+ query: dedupe([...group("query"), ...group("querystring").flatMap(spreadQuerystring)]),
205
235
  header: group("header"),
206
236
  cookie: group("cookie")
207
237
  };
@@ -431,11 +461,11 @@ function createGroupConfig(group) {
431
461
  }
432
462
  // kubb-extension.json
433
463
  var compatibility = {
434
- "kubb": ">=5.14.6",
464
+ "kubb": ">=5.16.0",
435
465
  "node": ">=22.22"
436
466
  };
437
467
  // package.json
438
- var version = "5.10.0-canary.20261105T162357";
468
+ var version = "5.10.0";
439
469
  // ../../internals/client/src/builders/validatorOptions.ts
440
470
  /**
441
471
  * Returns `true` when any direction of the validator uses zod (used for dependency checks).
@@ -479,6 +509,114 @@ function buildZodErrorParse(node, zodResolver) {
479
509
  if (!node.responses.some((res) => !isSuccessStatusCode(res.statusCode) && res.content?.some((entry) => entry.schema))) return null;
480
510
  return zodResolver.response.error?.(node) || null;
481
511
  }
512
+ // ../../internals/client/src/builders/generics.ts
513
+ /**
514
+ * Builds the `RequestResult` generic arguments for one operation: the per-status responses record
515
+ * plus the per-call `ThrowOnError` flag. `SuccessOf` / `ErrorOf` split the record inside the
516
+ * runtime, so this only names the record and threads `ThrowOnError`.
517
+ *
518
+ * @example
519
+ * `buildRequestResultGenerics({ node, types }) // 'AddPetResponses, ThrowOnError'`
520
+ */
521
+ function buildRequestResultGenerics({ node, types }) {
522
+ return `${types.response.responses(node)}, ThrowOnError`;
523
+ }
524
+ /**
525
+ * Builds the full return type an operation's function signature uses: `Unwrappable<RequestResult>`
526
+ * for the default `returnType: 'full'`, already a promise so no further wrapping is needed, a plain
527
+ * `Promise<RequestResult>` for the internal `'plain'` shape, or a
528
+ * `Promise` of the runtime's `UnwrappedResult` when `returnType: 'data'` narrows a resolved call
529
+ * down to the bare success body.
530
+ *
531
+ * @example
532
+ * `buildResultType({ node, types, returnType: 'data' }) // 'Promise<UnwrappedResult<AddPetResponses, ThrowOnError>>'`
533
+ */
534
+ function buildResultType({ node, types, returnType }) {
535
+ const generics = buildRequestResultGenerics({
536
+ node,
537
+ types
538
+ });
539
+ if (returnType === "data") return `Promise<UnwrappedResult<${generics}>>`;
540
+ if (returnType === "plain") return `Promise<RequestResult<${generics}>>`;
541
+ return `Unwrappable<RequestResult<${generics}>>`;
542
+ }
543
+ // ../../internals/client/src/builders/returnStatement.ts
544
+ /**
545
+ * Builds the return statement of a generated operation function. With the default
546
+ * `returnType: 'full'` the runtime call already resolves to `{ data, error, request, response }`.
547
+ * The generated code casts that result to the operation's `RequestResult`, then wraps it with
548
+ * `withUnwrap`, so the caller can `await` it directly or call `.unwrap()` for the bare success
549
+ * body. With `returnType: 'data'` it instead routes the call through the runtime's `unwrapResult`,
550
+ * which narrows the resolved value down to the bare success body the same way `RequestResult`
551
+ * already does, using the plugin default when the call leaves `throwOnError` unset.
552
+ * With `returnType: 'plain'` it returns the runtime call as is, cast to `RequestResult`, with no helper.
553
+ *
554
+ * Cast first, wrap second, for the `'full'` path. That order keeps `withUnwrap`'s generic inferred
555
+ * as `RequestResult` instead of the runtime's own internal result type. Casting an `Unwrappable<A>`
556
+ * straight to `Unwrappable<B>` does not work: the `.then` overload stays pinned to `A`, and `as`
557
+ * rejects that two-generic swap even where `A` and `B` on their own would satisfy it.
558
+ *
559
+ * @example
560
+ * `return withUnwrap(request({ method: 'POST', url: '/pet', ...config }) as Promise<RequestResult<AddPetResponses, ThrowOnError>>)`
561
+ * @example
562
+ * `return unwrapResult(request({ method: 'POST', url: '/pet', ...config }), config.throwOnError ?? false) as Promise<UnwrappedResult<AddPetResponses, ThrowOnError>>`
563
+ * @example
564
+ * `return request({ method: 'POST', url: '/pet', ...config }) as Promise<RequestResult<AddPetResponses, ThrowOnError>>`
565
+ */
566
+ function buildReturnStatement({ node, types, callConfig, returnType, throwOnErrorDefault }) {
567
+ const request = returnType !== "plain" && hasRequestContentTypeChoice(node) ? `request<${types.response.options(node)}['body']>` : "request";
568
+ if (returnType === "data") return `return unwrapResult(${request}(${callConfig}), config.throwOnError ?? ${throwOnErrorDefault}) as ${buildResultType({
569
+ node,
570
+ types,
571
+ returnType
572
+ })}`;
573
+ if (returnType === "plain") return `return ${request}(${callConfig}) as Promise<RequestResult<${buildRequestResultGenerics({
574
+ node,
575
+ types
576
+ })}>>`;
577
+ return `return withUnwrap(${request}(${callConfig}) as Promise<RequestResult<${buildRequestResultGenerics({
578
+ node,
579
+ types
580
+ })}>>)`;
581
+ }
582
+ /**
583
+ * Builds the return type and statement of a `text/event-stream` operation. The item type is `TypedEvent<...>` when each item
584
+ * declares its own `event` and `data`, and otherwise the payload type, which `EventStreamResult` wraps in `ServerSentEvent`. The call's
585
+ * promise goes through `toEventStream` with the `client` for its defaults, the call's `config`, the shape and the per-event
586
+ * validator. A `returnType: 'plain'` client receives only the promise,
587
+ * `toEventStream(request(config))`, and keeps its response validator in the call config.
588
+ */
589
+ function buildEventStreamReturn({ node, types, callConfig, returnType, validatorHooks, throwOnErrorDefault }) {
590
+ const shape = getEventStream(node) ?? "data";
591
+ const responses = types.response.responses(node);
592
+ const success = `SuccessOf<${responses}>`;
593
+ const eventType = shape === "event" ? `TypedEvent<${success}>` : success;
594
+ if (returnType === "plain") return {
595
+ returnType: `Promise<EventStreamResult<${eventType}>>`,
596
+ statement: `return toEventStream<${eventType}>(request(${callConfig}))`
597
+ };
598
+ const generics = `${responses}, ThrowOnError${shape === "event" ? `, ${eventType}` : ""}`;
599
+ const call = `toEventStream<${eventType}>(request(${callConfig}), { client: request, config, ${[`shape: '${shape}'`, validatorHooks?.response ? `validator: ${validatorHooks.response}` : null].filter(Boolean).join(", ")} })`;
600
+ if (returnType === "data") {
601
+ const result = `Promise<UnwrappedStreamResult<${generics}>>`;
602
+ return {
603
+ returnType: result,
604
+ statement: `return unwrapResult(${call}, config.throwOnError ?? ${throwOnErrorDefault}) as ${result}`
605
+ };
606
+ }
607
+ return {
608
+ returnType: `Unwrappable<StreamResult<${generics}>>`,
609
+ statement: `return withUnwrap(${call} as Promise<StreamResult<${generics}>>)`
610
+ };
611
+ }
612
+ /** The stream types an operation file imports: `StreamResult` and `StreamOptions`, `EventStreamResult` for a plain client, and `TypedEvent` for named events. */
613
+ function buildEventStreamImports({ returnType, typed }) {
614
+ return [...{
615
+ plain: ["EventStreamResult"],
616
+ full: ["StreamResult", "StreamOptions"],
617
+ data: ["UnwrappedStreamResult", "StreamOptions"]
618
+ }[returnType], ...typed ? ["TypedEvent"] : []];
619
+ }
482
620
  // ../../internals/client/src/builders/security.ts
483
621
  function serializeAuth(auth) {
484
622
  const parts = [`type: '${auth.type}'`];
@@ -634,6 +772,7 @@ function serializeParameter(parameter) {
634
772
  if (parameter.explode !== void 0) parts.push(`explode: ${parameter.explode}`);
635
773
  if (parameter.in === "query" && parameter.allowReserved !== void 0) parts.push(`allowReserved: ${parameter.allowReserved}`);
636
774
  if (parameter.in !== "path" && parameter.contentType) parts.push(`contentType: '${parameter.contentType}'`);
775
+ if (parameter.in === "querystring") parts.push("querystring: true");
637
776
  return parts.length > 0 ? `{ ${parts.join(", ")} }` : null;
638
777
  }
639
778
  /**
@@ -676,10 +815,9 @@ function buildStyles({ node }) {
676
815
  header: [],
677
816
  cookie: []
678
817
  };
679
- for (const parameter of node.parameters) {
818
+ for (const [location, parameters] of Object.entries(getOperationParameters(node))) for (const parameter of parameters) {
680
819
  const literal = serializeParameter(parameter);
681
- if (!literal) continue;
682
- groups[parameter.in].push(`${toKey(parameter.name)}: ${literal}`);
820
+ if (literal) groups[location].push(`${toKey(parameter.name)}: ${literal}`);
683
821
  }
684
822
  const entries = [...Object.keys(groups).filter((location) => groups[location].length > 0).map((location) => `${location}: { ${groups[location].join(", ")} }`), serializeBodyEncoding(node)].filter(Boolean);
685
823
  return entries.length > 0 ? `{ ${entries.join(", ")} }` : null;
@@ -690,12 +828,20 @@ function buildStyles({ node }) {
690
828
  * functions and the SDK methods. The `throwOnError` fallback is applied after the `...config` spread.
691
829
  *
692
830
  * `mergeContentType` is `true` when the caller must destructure `contentType` from `options`, and
693
- * `eventStream` is `true` when the operation returns `text/event-stream`.
831
+ * `eventStream` is `true` when the operation returns `text/event-stream`. Unless `returnType` is `'plain'`, a
832
+ * stream's literal leaves the `response` validator to `toEventStream`, which runs it per event.
694
833
  */
695
- function buildCallConfig({ node, validatorHooks, security, throwOnErrorDefault }) {
834
+ function buildCallConfig({ node, validatorHooks, security, throwOnErrorDefault, returnType }) {
835
+ const eventStream = isEventStream(node);
696
836
  const securityLiteral = buildSecurityMetadata({ security });
697
837
  const stylesLiteral = buildStyles({ node });
698
- const validatorLiteral = buildValidatorLiteral(validatorHooks, [
838
+ const validatorLiteral = buildValidatorLiteral(validatorHooks, eventStream && returnType !== "plain" ? [
839
+ "request",
840
+ "path",
841
+ "query",
842
+ "headers",
843
+ "error"
844
+ ] : [
699
845
  "request",
700
846
  "path",
701
847
  "query",
@@ -718,88 +864,7 @@ function buildCallConfig({ node, validatorHooks, security, throwOnErrorDefault }
718
864
  `throwOnError: config.throwOnError ?? ${throwOnErrorDefault}`
719
865
  ].filter(Boolean).join(", ")} }`,
720
866
  mergeContentType,
721
- eventStream: isEventStream(node)
722
- };
723
- }
724
- // ../../internals/client/src/builders/generics.ts
725
- /**
726
- * Builds the `RequestResult` generic arguments for one operation: the per-status responses record
727
- * plus the per-call `ThrowOnError` flag. `SuccessOf` / `ErrorOf` split the record inside the
728
- * runtime, so this only names the record and threads `ThrowOnError`.
729
- *
730
- * @example
731
- * `buildRequestResultGenerics({ node, types }) // 'AddPetResponses, ThrowOnError'`
732
- */
733
- function buildRequestResultGenerics({ node, types }) {
734
- return `${types.response.responses(node)}, ThrowOnError`;
735
- }
736
- /**
737
- * Builds the full return type an operation's function signature uses: `Unwrappable<RequestResult>`
738
- * for the default `returnType: 'full'`, already a promise so no further wrapping is needed, a plain
739
- * `Promise<RequestResult>` for the internal `'plain'` shape, or a
740
- * `Promise` of the runtime's `UnwrappedResult` when `returnType: 'data'` narrows a resolved call
741
- * down to the bare success body.
742
- *
743
- * @example
744
- * `buildResultType({ node, types, returnType: 'data' }) // 'Promise<UnwrappedResult<AddPetResponses, ThrowOnError>>'`
745
- */
746
- function buildResultType({ node, types, returnType }) {
747
- const generics = buildRequestResultGenerics({
748
- node,
749
- types
750
- });
751
- if (returnType === "data") return `Promise<UnwrappedResult<${generics}>>`;
752
- if (returnType === "plain") return `Promise<RequestResult<${generics}>>`;
753
- return `Unwrappable<RequestResult<${generics}>>`;
754
- }
755
- // ../../internals/client/src/builders/returnStatement.ts
756
- /**
757
- * Builds the return statement of a generated operation function. With the default
758
- * `returnType: 'full'` the runtime call already resolves to `{ data, error, request, response }`.
759
- * The generated code casts that result to the operation's `RequestResult`, then wraps it with
760
- * `withUnwrap`, so the caller can `await` it directly or call `.unwrap()` for the bare success
761
- * body. With `returnType: 'data'` it instead routes the call through the runtime's `unwrapResult`,
762
- * which narrows the resolved value down to the bare success body the same way `RequestResult`
763
- * already does, using the plugin default when the call leaves `throwOnError` unset.
764
- * With `returnType: 'plain'` it returns the runtime call as is, cast to `RequestResult`, with no helper.
765
- *
766
- * Cast first, wrap second, for the `'full'` path. That order keeps `withUnwrap`'s generic inferred
767
- * as `RequestResult` instead of the runtime's own internal result type. Casting an `Unwrappable<A>`
768
- * straight to `Unwrappable<B>` does not work: the `.then` overload stays pinned to `A`, and `as`
769
- * rejects that two-generic swap even where `A` and `B` on their own would satisfy it.
770
- *
771
- * @example
772
- * `return withUnwrap(request({ method: 'POST', url: '/pet', ...config }) as Promise<RequestResult<AddPetResponses, ThrowOnError>>)`
773
- * @example
774
- * `return unwrapResult(request({ method: 'POST', url: '/pet', ...config }), config.throwOnError ?? false) as Promise<UnwrappedResult<AddPetResponses, ThrowOnError>>`
775
- * @example
776
- * `return request({ method: 'POST', url: '/pet', ...config }) as Promise<RequestResult<AddPetResponses, ThrowOnError>>`
777
- */
778
- function buildReturnStatement({ node, types, callConfig, returnType, throwOnErrorDefault }) {
779
- const request = returnType !== "plain" && hasRequestContentTypeChoice(node) ? `request<${types.response.options(node)}['body']>` : "request";
780
- if (returnType === "data") return `return unwrapResult(${request}(${callConfig}), config.throwOnError ?? ${throwOnErrorDefault}) as ${buildResultType({
781
- node,
782
- types,
783
- returnType
784
- })}`;
785
- if (returnType === "plain") return `return ${request}(${callConfig}) as Promise<RequestResult<${buildRequestResultGenerics({
786
- node,
787
- types
788
- })}>>`;
789
- return `return withUnwrap(${request}(${callConfig}) as Promise<RequestResult<${buildRequestResultGenerics({
790
- node,
791
- types
792
- })}>>)`;
793
- }
794
- /**
795
- * Builds the return type and statement of an operation that returns `text/event-stream`: the call
796
- * is wrapped in `toEventStream`, so the caller iterates the typed success events.
797
- */
798
- function buildEventStreamReturn({ node, types, callConfig }) {
799
- const eventType = `SuccessOf<${types.response.responses(node)}>`;
800
- return {
801
- returnType: `Promise<EventStreamResult<${eventType}>>`,
802
- statement: `return toEventStream<${eventType}>(request(${callConfig}))`
867
+ eventStream
803
868
  };
804
869
  }
805
870
  // ../../internals/client/src/builders/signature.ts
@@ -813,13 +878,14 @@ const declarationPrinter = (0, _kubb_plugin_ts.functionPrinter)({ mode: "declara
813
878
  * per-operation input type has to be emitted. Both names come from `types`, which is `plugin-ts` or
814
879
  * `plugin-zod`'s inferred types (see `resolveOperationTypes`).
815
880
  */
816
- function buildGroupedOptionsSignature({ node, types, returnType, throwOnErrorDefault }) {
881
+ function buildGroupedOptionsSignature({ node, types, returnType, throwOnErrorDefault, eventStream = false }) {
817
882
  const optionsName = types.response.options(node);
883
+ const optionsType = `${eventStream && returnType !== "plain" ? "StreamOptions" : "Options"}<${optionsName}, ThrowOnError>`;
818
884
  const { isOptional } = getRequestGroupOptionality(node);
819
885
  return {
820
886
  paramsSignature: declarationPrinter.print((0, _kubb_plugin_ts.createFunctionParameters)({ params: [(0, _kubb_plugin_ts.createFunctionParameter)({
821
887
  name: "options",
822
- type: `Options<${optionsName}, ThrowOnError>`,
888
+ type: optionsType,
823
889
  ...isOptional ? { default: "{}" } : {}
824
890
  })] })) ?? "",
825
891
  returnType: buildResultType({
@@ -830,37 +896,63 @@ function buildGroupedOptionsSignature({ node, types, returnType, throwOnErrorDef
830
896
  generics: [`ThrowOnError extends boolean = ${throwOnErrorDefault}`]
831
897
  };
832
898
  }
833
- // ../../internals/client/src/components/Operation.tsx
899
+ // ../../internals/client/src/builders/operationBody.ts
834
900
  /**
835
- * Renders one client operation: the grouped `<Name>Request` type and the function that forwards a
836
- * single `options` object to the resolved client and returns the `Unwrappable<RequestResult>`. The
837
- * type, signature, and call config are built with the AST factory, and only the jsx-renderer emits
838
- * the source.
901
+ * The pieces of a generated operation: its generics, `options` parameter, return type, return statement, and whether the
902
+ * body destructures `contentType` from `options`. Shared by the standalone functions and the SDK methods, so a
903
+ * `text/event-stream` operation is wired to `toEventStream` in one place.
839
904
  */
840
- function Operation({ name, node, types, validatorHooks, returnType, throwOnErrorDefault, security }) {
841
- if (!_kubb_kit.ast.isHttpOperationNode(node)) return null;
905
+ function buildOperationBody({ node, types, validatorHooks, security, returnType, throwOnErrorDefault }) {
906
+ const { callConfig, mergeContentType, eventStream } = buildCallConfig({
907
+ node,
908
+ validatorHooks,
909
+ security,
910
+ throwOnErrorDefault,
911
+ returnType
912
+ });
842
913
  const signature = buildGroupedOptionsSignature({
843
914
  node,
844
915
  types,
845
916
  returnType,
846
- throwOnErrorDefault
917
+ throwOnErrorDefault,
918
+ eventStream
847
919
  });
848
- const { callConfig, mergeContentType, eventStream } = buildCallConfig({
920
+ const stream = eventStream ? buildEventStreamReturn({
849
921
  node,
922
+ types,
923
+ callConfig,
924
+ returnType,
850
925
  validatorHooks,
851
- security,
852
926
  throwOnErrorDefault
853
- });
854
- const eventStreamReturn = eventStream ? buildEventStreamReturn({
855
- node,
856
- types,
857
- callConfig
858
927
  }) : null;
859
- const functionReturnType = eventStreamReturn?.returnType ?? signature.returnType;
860
- const returnStatement = eventStreamReturn?.statement ?? buildReturnStatement({
928
+ return {
929
+ generics: signature.generics,
930
+ paramsSignature: signature.paramsSignature,
931
+ returnType: stream?.returnType ?? signature.returnType,
932
+ statement: stream?.statement ?? buildReturnStatement({
933
+ node,
934
+ types,
935
+ callConfig,
936
+ returnType,
937
+ throwOnErrorDefault
938
+ }),
939
+ mergeContentType
940
+ };
941
+ }
942
+ // ../../internals/client/src/components/Operation.tsx
943
+ /**
944
+ * Renders one client operation: the grouped `<Name>Request` type and the function that forwards a
945
+ * single `options` object to the resolved client and returns the `Unwrappable<RequestResult>`. The
946
+ * type, signature, and call config are built with the AST factory, and only the jsx-renderer emits
947
+ * the source.
948
+ */
949
+ function Operation({ name, node, types, validatorHooks, returnType, throwOnErrorDefault, security }) {
950
+ if (!_kubb_kit.ast.isHttpOperationNode(node)) return null;
951
+ const body = buildOperationBody({
861
952
  node,
862
953
  types,
863
- callConfig,
954
+ validatorHooks,
955
+ security,
864
956
  returnType,
865
957
  throwOnErrorDefault
866
958
  });
@@ -871,22 +963,53 @@ function Operation({ name, node, types, validatorHooks, returnType, throwOnError
871
963
  children: /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.Function, {
872
964
  name,
873
965
  export: true,
874
- generics: signature.generics,
875
- params: signature.paramsSignature,
876
- returnType: functionReturnType,
966
+ generics: body.generics,
967
+ params: body.paramsSignature,
968
+ returnType: body.returnType,
877
969
  JSDoc: { comments: buildOperationComments(node, {
878
970
  link: "urlPath",
879
971
  linkPosition: "beforeDeprecated",
880
972
  splitLines: true
881
973
  }) },
882
974
  children: [
883
- mergeContentType ? "const { client: request = client, contentType, ...config } = options" : "const { client: request = client, ...config } = options",
975
+ body.mergeContentType ? "const { client: request = client, contentType, ...config } = options" : "const { client: request = client, ...config } = options",
884
976
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
885
- returnStatement
977
+ body.statement
886
978
  ]
887
979
  })
888
980
  });
889
981
  }
982
+ // ../../internals/client/src/components/StreamRuntime.tsx
983
+ /**
984
+ * The `footer` of `.kubb/client.ts`: the `baseURL` set at generation time, and the `sse.ts` re-export when the document
985
+ * streams events, so `ServerSentEvent` and `toEventStream` stay importable from the client as in earlier releases.
986
+ */
987
+ function buildClientFooter({ baseURL, sse }) {
988
+ const baseURLExpression = baseURL ? baseURL.includes("${") ? `\`${baseURL.replaceAll("`", "\\`")}\`` : JSON.stringify(baseURL) : void 0;
989
+ return [baseURLExpression ? `client.setConfig({ baseURL: ${baseURLExpression} })` : null, sse ? `export * from './sse.ts'` : null].filter(Boolean).join("\n") || void 0;
990
+ }
991
+ /**
992
+ * Writes the footer of the `.kubb/client.ts` the plugin injected, the one place it is built, and copies `sse.ts` into
993
+ * `.kubb/` only for a document with a `text/event-stream` operation, so a document without streams ships no stream runtime.
994
+ * `sse` is set by the plugins that ship the runtime, so a custom client from `@kubb/plugin-client` gets neither file.
995
+ */
996
+ function StreamRuntime({ nodes, root, sse, baseURL }) {
997
+ if (!sse) return null;
998
+ const streams = nodes.some(isEventStream);
999
+ const footer = buildClientFooter({
1000
+ baseURL,
1001
+ sse: streams
1002
+ });
1003
+ return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [streams && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File, {
1004
+ baseName: "sse.ts",
1005
+ path: node_path.default.resolve(root, ".kubb/sse.ts"),
1006
+ copy: sse
1007
+ }), footer && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File, {
1008
+ baseName: "client.ts",
1009
+ path: node_path.default.resolve(root, ".kubb/client.ts"),
1010
+ footer
1011
+ })] });
1012
+ }
890
1013
  // ../../internals/client/src/resolveOperationTypes.ts
891
1014
  /**
892
1015
  * A `plugin-ts` shaped resolver whose operation type names point at the types `pluginZod({ inferred })`
@@ -962,13 +1085,24 @@ function resolveOperationTypes(driver, pluginName) {
962
1085
  * `@kubb/plugin-axios`). Emits one async function per OpenAPI operation using the shared
963
1086
  * `Operation` component: a grouped `<Name>Request` type and a function that forwards a single
964
1087
  * `options` object to the bundled `client` and returns the `Unwrappable<RequestResult>`. Only the
965
- * generator `name` differs between plugins. Every other resolution, import, and rendering step is
966
- * identical.
1088
+ * generator `name` and the runtime templates differ between plugins. Every other resolution, import,
1089
+ * and rendering step is identical.
1090
+ *
1091
+ * `sse` is the Server-Sent Events runtime template, copied into `.kubb/sse.ts` only for a document with a
1092
+ * `text/event-stream` operation.
967
1093
  */
968
- function createClientGenerator(name) {
1094
+ function createClientGenerator(name, { sse } = {}) {
969
1095
  return (0, _kubb_kit.defineGenerator)({
970
1096
  name,
971
1097
  renderer: _kubb_renderer_jsx.jsxRenderer,
1098
+ operations(nodes, ctx) {
1099
+ return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(StreamRuntime, {
1100
+ nodes,
1101
+ root: ctx.root,
1102
+ sse,
1103
+ baseURL: ctx.options.baseURL
1104
+ });
1105
+ },
972
1106
  operation(node, ctx) {
973
1107
  if (!_kubb_kit.ast.isHttpOperationNode(node)) return null;
974
1108
  const { config, driver, resolver, root } = ctx;
@@ -1018,19 +1152,19 @@ function createClientGenerator(name) {
1018
1152
  const clientPath = importPath ?? node_path.default.resolve(root, ".kubb/client.ts");
1019
1153
  const clientRoot = importPath === void 0 ? meta.file.path : void 0;
1020
1154
  const standardSchemaPath = importPath ?? node_path.default.resolve(root, ".kubb/standardSchema.ts");
1021
- const eventStream = isEventStream(node);
1155
+ const ssePath = importPath ?? node_path.default.resolve(root, ".kubb/sse.ts");
1156
+ const stream = getEventStream(node);
1022
1157
  const clientValueImports = (() => {
1023
- if (eventStream) return ["client", "toEventStream"];
1024
1158
  if (returnType === "plain") return ["client"];
1025
1159
  if (returnType === "data") return ["client", "unwrapResult"];
1026
1160
  return ["client", "withUnwrap"];
1027
1161
  })();
1028
1162
  const clientTypeImports = (() => {
1029
- if (eventStream) return [
1030
- "Options",
1031
- "EventStreamResult",
1032
- "SuccessOf"
1033
- ];
1163
+ if (stream) return {
1164
+ plain: ["Options", "SuccessOf"],
1165
+ data: ["SuccessOf"],
1166
+ full: ["SuccessOf", "Unwrappable"]
1167
+ }[returnType];
1034
1168
  if (returnType === "plain") return ["Options", "RequestResult"];
1035
1169
  if (returnType === "data") return ["Options", "UnwrappedResult"];
1036
1170
  return [
@@ -1071,6 +1205,20 @@ function createClientGenerator(name) {
1071
1205
  path: clientPath,
1072
1206
  isTypeOnly: true
1073
1207
  }),
1208
+ stream && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1209
+ name: ["toEventStream"],
1210
+ root: clientRoot,
1211
+ path: ssePath
1212
+ }),
1213
+ stream && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1214
+ name: buildEventStreamImports({
1215
+ returnType,
1216
+ typed: stream === "event"
1217
+ }),
1218
+ root: clientRoot,
1219
+ path: ssePath,
1220
+ isTypeOnly: true
1221
+ }),
1074
1222
  meta.fileTypes && importedTypeNames.length > 0 && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1075
1223
  name: Array.from(new Set(importedTypeNames)),
1076
1224
  root: meta.file.path,
@@ -1103,11 +1251,11 @@ function createClientGenerator(name) {
1103
1251
  }
1104
1252
  // ../../internals/client/src/macros.ts
1105
1253
  /**
1106
- * Macros the client plugins apply by default, ahead of any user macros. `macroSimplifyUnion`
1107
- * drops union members a broader scalar already covers, keeping the generated response and error
1254
+ * Macros the client plugins apply by default, ahead of any user macros. `macroDedupeUnion`
1255
+ * drops repeated union members, keeping the generated response and error
1108
1256
  * unions tidy. A plugin wires them with `ctx.setMacros([...defaultMacros, ...userMacros])`.
1109
1257
  */
1110
- const defaultMacros = [_kubb_kit.macroSimplifyUnion];
1258
+ const defaultMacros = [_kubb_kit.macroDedupeUnion];
1111
1259
  // ../../internals/client/src/resolver.ts
1112
1260
  /**
1113
1261
  * Default resolver shared by the client plugins. Functions use safe camelCase identifiers and