@kubb/plugin-client 5.10.0-canary.20261105T164114 → 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.20261105T164114";
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,90 +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. Except with
797
- * `returnType: 'plain'`, `toEventStream` also receives `config`, so a per-call `maxEventSize` reaches the parser.
798
- */
799
- function buildEventStreamReturn({ node, types, callConfig, returnType }) {
800
- const eventType = `SuccessOf<${types.response.responses(node)}>`;
801
- const args = returnType === "plain" ? `request(${callConfig})` : `request(${callConfig}), config`;
802
- return {
803
- returnType: `Promise<EventStreamResult<${eventType}>>`,
804
- statement: `return toEventStream<${eventType}>(${args})`
867
+ eventStream
805
868
  };
806
869
  }
807
870
  // ../../internals/client/src/builders/signature.ts
@@ -815,13 +878,14 @@ const declarationPrinter = (0, _kubb_plugin_ts.functionPrinter)({ mode: "declara
815
878
  * per-operation input type has to be emitted. Both names come from `types`, which is `plugin-ts` or
816
879
  * `plugin-zod`'s inferred types (see `resolveOperationTypes`).
817
880
  */
818
- function buildGroupedOptionsSignature({ node, types, returnType, throwOnErrorDefault }) {
881
+ function buildGroupedOptionsSignature({ node, types, returnType, throwOnErrorDefault, eventStream = false }) {
819
882
  const optionsName = types.response.options(node);
883
+ const optionsType = `${eventStream && returnType !== "plain" ? "StreamOptions" : "Options"}<${optionsName}, ThrowOnError>`;
820
884
  const { isOptional } = getRequestGroupOptionality(node);
821
885
  return {
822
886
  paramsSignature: declarationPrinter.print((0, _kubb_plugin_ts.createFunctionParameters)({ params: [(0, _kubb_plugin_ts.createFunctionParameter)({
823
887
  name: "options",
824
- type: `Options<${optionsName}, ThrowOnError>`,
888
+ type: optionsType,
825
889
  ...isOptional ? { default: "{}" } : {}
826
890
  })] })) ?? "",
827
891
  returnType: buildResultType({
@@ -832,38 +896,63 @@ function buildGroupedOptionsSignature({ node, types, returnType, throwOnErrorDef
832
896
  generics: [`ThrowOnError extends boolean = ${throwOnErrorDefault}`]
833
897
  };
834
898
  }
835
- // ../../internals/client/src/components/Operation.tsx
899
+ // ../../internals/client/src/builders/operationBody.ts
836
900
  /**
837
- * Renders one client operation: the grouped `<Name>Request` type and the function that forwards a
838
- * single `options` object to the resolved client and returns the `Unwrappable<RequestResult>`. The
839
- * type, signature, and call config are built with the AST factory, and only the jsx-renderer emits
840
- * 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.
841
904
  */
842
- function Operation({ name, node, types, validatorHooks, returnType, throwOnErrorDefault, security }) {
843
- if (!_kubb_kit.ast.isHttpOperationNode(node)) return null;
844
- const signature = buildGroupedOptionsSignature({
845
- node,
846
- types,
847
- returnType,
848
- throwOnErrorDefault
849
- });
905
+ function buildOperationBody({ node, types, validatorHooks, security, returnType, throwOnErrorDefault }) {
850
906
  const { callConfig, mergeContentType, eventStream } = buildCallConfig({
851
907
  node,
852
908
  validatorHooks,
853
909
  security,
854
- throwOnErrorDefault
910
+ throwOnErrorDefault,
911
+ returnType
855
912
  });
856
- const eventStreamReturn = eventStream ? buildEventStreamReturn({
913
+ const signature = buildGroupedOptionsSignature({
914
+ node,
915
+ types,
916
+ returnType,
917
+ throwOnErrorDefault,
918
+ eventStream
919
+ });
920
+ const stream = eventStream ? buildEventStreamReturn({
857
921
  node,
858
922
  types,
859
923
  callConfig,
860
- returnType
924
+ returnType,
925
+ validatorHooks,
926
+ throwOnErrorDefault
861
927
  }) : null;
862
- const functionReturnType = eventStreamReturn?.returnType ?? signature.returnType;
863
- 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({
864
952
  node,
865
953
  types,
866
- callConfig,
954
+ validatorHooks,
955
+ security,
867
956
  returnType,
868
957
  throwOnErrorDefault
869
958
  });
@@ -874,22 +963,53 @@ function Operation({ name, node, types, validatorHooks, returnType, throwOnError
874
963
  children: /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.Function, {
875
964
  name,
876
965
  export: true,
877
- generics: signature.generics,
878
- params: signature.paramsSignature,
879
- returnType: functionReturnType,
966
+ generics: body.generics,
967
+ params: body.paramsSignature,
968
+ returnType: body.returnType,
880
969
  JSDoc: { comments: buildOperationComments(node, {
881
970
  link: "urlPath",
882
971
  linkPosition: "beforeDeprecated",
883
972
  splitLines: true
884
973
  }) },
885
974
  children: [
886
- 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",
887
976
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
888
- returnStatement
977
+ body.statement
889
978
  ]
890
979
  })
891
980
  });
892
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
+ }
893
1013
  // ../../internals/client/src/resolveOperationTypes.ts
894
1014
  /**
895
1015
  * A `plugin-ts` shaped resolver whose operation type names point at the types `pluginZod({ inferred })`
@@ -965,13 +1085,24 @@ function resolveOperationTypes(driver, pluginName) {
965
1085
  * `@kubb/plugin-axios`). Emits one async function per OpenAPI operation using the shared
966
1086
  * `Operation` component: a grouped `<Name>Request` type and a function that forwards a single
967
1087
  * `options` object to the bundled `client` and returns the `Unwrappable<RequestResult>`. Only the
968
- * generator `name` differs between plugins. Every other resolution, import, and rendering step is
969
- * 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.
970
1093
  */
971
- function createClientGenerator(name) {
1094
+ function createClientGenerator(name, { sse } = {}) {
972
1095
  return (0, _kubb_kit.defineGenerator)({
973
1096
  name,
974
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
+ },
975
1106
  operation(node, ctx) {
976
1107
  if (!_kubb_kit.ast.isHttpOperationNode(node)) return null;
977
1108
  const { config, driver, resolver, root } = ctx;
@@ -1021,19 +1152,19 @@ function createClientGenerator(name) {
1021
1152
  const clientPath = importPath ?? node_path.default.resolve(root, ".kubb/client.ts");
1022
1153
  const clientRoot = importPath === void 0 ? meta.file.path : void 0;
1023
1154
  const standardSchemaPath = importPath ?? node_path.default.resolve(root, ".kubb/standardSchema.ts");
1024
- const eventStream = isEventStream(node);
1155
+ const ssePath = importPath ?? node_path.default.resolve(root, ".kubb/sse.ts");
1156
+ const stream = getEventStream(node);
1025
1157
  const clientValueImports = (() => {
1026
- if (eventStream) return ["client", "toEventStream"];
1027
1158
  if (returnType === "plain") return ["client"];
1028
1159
  if (returnType === "data") return ["client", "unwrapResult"];
1029
1160
  return ["client", "withUnwrap"];
1030
1161
  })();
1031
1162
  const clientTypeImports = (() => {
1032
- if (eventStream) return [
1033
- "Options",
1034
- "EventStreamResult",
1035
- "SuccessOf"
1036
- ];
1163
+ if (stream) return {
1164
+ plain: ["Options", "SuccessOf"],
1165
+ data: ["SuccessOf"],
1166
+ full: ["SuccessOf", "Unwrappable"]
1167
+ }[returnType];
1037
1168
  if (returnType === "plain") return ["Options", "RequestResult"];
1038
1169
  if (returnType === "data") return ["Options", "UnwrappedResult"];
1039
1170
  return [
@@ -1074,6 +1205,20 @@ function createClientGenerator(name) {
1074
1205
  path: clientPath,
1075
1206
  isTypeOnly: true
1076
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
+ }),
1077
1222
  meta.fileTypes && importedTypeNames.length > 0 && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1078
1223
  name: Array.from(new Set(importedTypeNames)),
1079
1224
  root: meta.file.path,
@@ -1106,11 +1251,11 @@ function createClientGenerator(name) {
1106
1251
  }
1107
1252
  // ../../internals/client/src/macros.ts
1108
1253
  /**
1109
- * Macros the client plugins apply by default, ahead of any user macros. `macroSimplifyUnion`
1110
- * 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
1111
1256
  * unions tidy. A plugin wires them with `ctx.setMacros([...defaultMacros, ...userMacros])`.
1112
1257
  */
1113
- const defaultMacros = [_kubb_kit.macroSimplifyUnion];
1258
+ const defaultMacros = [_kubb_kit.macroDedupeUnion];
1114
1259
  // ../../internals/client/src/resolver.ts
1115
1260
  /**
1116
1261
  * Default resolver shared by the client plugins. Functions use safe camelCase identifiers and