@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.js CHANGED
@@ -1,9 +1,9 @@
1
- import { Diagnostics, Resolver, Url, addGenerator, ast, camelCase, createResolver, defineExtension, defineGenerator, macroSimplifyUnion, pascalCase, setMacros, setResolver } from "@kubb/kit";
1
+ import { Diagnostics, Resolver, Url, addGenerator, ast, camelCase, createResolver, defineExtension, defineGenerator, macroDedupeUnion, pascalCase, setMacros, setResolver } from "@kubb/kit";
2
2
  import path from "node:path";
3
3
  import { createFunctionParameter, createFunctionParameters, functionPrinter, pluginTsName } from "@kubb/plugin-ts";
4
4
  import { pluginZodName } from "@kubb/plugin-zod";
5
5
  import { File, Function, jsxRenderer } from "@kubb/renderer-jsx";
6
- import { jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
6
+ import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
7
7
  // ../../internals/shared/src/operation.ts
8
8
  /**
9
9
  * Builds the `ResolverFileParams` every operation generator passes to
@@ -84,20 +84,33 @@ function getPrimarySuccessContentType(node) {
84
84
  return contentTypes[0].split(";")[0].trim().toLowerCase();
85
85
  }
86
86
  /**
87
- * Whether an operation streams its primary success response as Server-Sent Events
88
- * (`text/event-stream`). The client generator uses this to return a typed event stream instead of a
89
- * one-shot `RequestResult`.
87
+ * The `text/event-stream` content of the primary success response, when it is the only content type. A response that also
88
+ * declares another type stays a normal call with its query hooks.
89
+ */
90
+ function getEventStreamContent(node) {
91
+ return getPrimarySuccessContentType(node) === "text/event-stream" ? getPrimarySuccessResponse(node)?.content?.[0] : void 0;
92
+ }
93
+ /**
94
+ * Whether an operation streams its primary success response as Server-Sent Events (`text/event-stream`). The client
95
+ * generator uses this to return a typed event stream instead of a one-shot `RequestResult`.
90
96
  */
91
97
  function isEventStream(node) {
92
- return getPrimarySuccessContentType(node) === "text/event-stream";
98
+ return getEventStreamContent(node) !== void 0;
99
+ }
100
+ /**
101
+ * How the document types a `text/event-stream` response, as the adapter read it from `itemSchema`: `event` when each item is
102
+ * the parsed event with its own `event` and `data`, `data` when it is only the payload. Returns `null` without an event stream.
103
+ */
104
+ function getEventStream(node) {
105
+ const content = getEventStreamContent(node);
106
+ return content ? content.stream?.shape ?? "data" : null;
93
107
  }
94
108
  /**
95
109
  * Derives the default `responseType` for an operation from its primary success response.
96
110
  *
97
- * Returns a value only when that response declares a single non-JSON content type. `text/event-stream`
98
- * and other binary types (`application/octet-stream`, `application/pdf`, `image/*`, `audio/*`,
99
- * `video/*`) map to a stream or `'blob'`, and other `text/*` maps to `'text'`. Otherwise `undefined`,
100
- * leaving the runtime client's `Content-Type` auto-detection in charge.
111
+ * Returns a value only when that response declares a single non-JSON content type. `text/event-stream` maps to
112
+ * `'stream'`, `application/octet-stream`, `application/pdf`, `image/*`, `audio/*` and `video/*` map to `'blob'`, and
113
+ * other `text/*` to `'text'`. Otherwise `undefined`, leaving the runtime client's `Content-Type` auto-detection in charge.
101
114
  */
102
115
  function getResponseType(node) {
103
116
  const baseType = getPrimarySuccessContentType(node);
@@ -157,6 +170,22 @@ function buildOperationComments(node, options = {}) {
157
170
  }
158
171
  const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
159
172
  /**
173
+ * An OpenAPI 3.2 `querystring` parameter is the whole query string, so a form-encoded object becomes one query parameter
174
+ * per property, typed and serialized like any other.
175
+ * Another content type, such as JSON, stays one parameter named after it, and the runtime sends its value as the whole query string.
176
+ */
177
+ function spreadQuerystring(param) {
178
+ const schema = param.schema.type === "ref" ? param.schema.schema : param.schema;
179
+ const formEncoded = !param.contentType || param.contentType.split(";")[0].trim().toLowerCase() === "application/x-www-form-urlencoded";
180
+ if (schema?.type !== "object" || !formEncoded) return [param];
181
+ return schema.properties.map((property) => ast.factory.createParameter({
182
+ name: property.name,
183
+ in: "query",
184
+ schema: property.schema,
185
+ required: param.required && property.required
186
+ }));
187
+ }
188
+ /**
160
189
  * Groups an operation's parameters by location (`path`/`query`/`header`/`cookie`), deduping each
161
190
  * group by name. Every plugin generator visiting the same `OperationNode` shares one AST instance
162
191
  * (see `KubbDriver`), so the result is cached per node to avoid re-filtering and re-deduping the
@@ -165,17 +194,18 @@ const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
165
194
  function getOperationParameters(node) {
166
195
  const cached = operationParameterGroupsByNode.get(node);
167
196
  if (cached) return cached;
168
- const group = (location) => {
197
+ const dedupe = (params) => {
169
198
  const seen = /* @__PURE__ */ new Set();
170
- return node.parameters.filter((param) => {
171
- if (param.in !== location || seen.has(param.name)) return false;
199
+ return params.filter((param) => {
200
+ if (seen.has(param.name)) return false;
172
201
  seen.add(param.name);
173
202
  return true;
174
203
  });
175
204
  };
205
+ const group = (location) => dedupe(node.parameters.filter((param) => param.in === location));
176
206
  const groups = {
177
207
  path: group("path"),
178
- query: group("query"),
208
+ query: dedupe([...group("query"), ...group("querystring").flatMap(spreadQuerystring)]),
179
209
  header: group("header"),
180
210
  cookie: group("cookie")
181
211
  };
@@ -405,11 +435,11 @@ function createGroupConfig(group) {
405
435
  }
406
436
  // kubb-extension.json
407
437
  var compatibility = {
408
- "kubb": ">=5.14.6",
438
+ "kubb": ">=5.16.0",
409
439
  "node": ">=22.22"
410
440
  };
411
441
  // package.json
412
- var version = "5.10.0-canary.20261105T164114";
442
+ var version = "5.10.0";
413
443
  // ../../internals/client/src/builders/validatorOptions.ts
414
444
  /**
415
445
  * Returns `true` when any direction of the validator uses zod (used for dependency checks).
@@ -453,6 +483,114 @@ function buildZodErrorParse(node, zodResolver) {
453
483
  if (!node.responses.some((res) => !isSuccessStatusCode(res.statusCode) && res.content?.some((entry) => entry.schema))) return null;
454
484
  return zodResolver.response.error?.(node) || null;
455
485
  }
486
+ // ../../internals/client/src/builders/generics.ts
487
+ /**
488
+ * Builds the `RequestResult` generic arguments for one operation: the per-status responses record
489
+ * plus the per-call `ThrowOnError` flag. `SuccessOf` / `ErrorOf` split the record inside the
490
+ * runtime, so this only names the record and threads `ThrowOnError`.
491
+ *
492
+ * @example
493
+ * `buildRequestResultGenerics({ node, types }) // 'AddPetResponses, ThrowOnError'`
494
+ */
495
+ function buildRequestResultGenerics({ node, types }) {
496
+ return `${types.response.responses(node)}, ThrowOnError`;
497
+ }
498
+ /**
499
+ * Builds the full return type an operation's function signature uses: `Unwrappable<RequestResult>`
500
+ * for the default `returnType: 'full'`, already a promise so no further wrapping is needed, a plain
501
+ * `Promise<RequestResult>` for the internal `'plain'` shape, or a
502
+ * `Promise` of the runtime's `UnwrappedResult` when `returnType: 'data'` narrows a resolved call
503
+ * down to the bare success body.
504
+ *
505
+ * @example
506
+ * `buildResultType({ node, types, returnType: 'data' }) // 'Promise<UnwrappedResult<AddPetResponses, ThrowOnError>>'`
507
+ */
508
+ function buildResultType({ node, types, returnType }) {
509
+ const generics = buildRequestResultGenerics({
510
+ node,
511
+ types
512
+ });
513
+ if (returnType === "data") return `Promise<UnwrappedResult<${generics}>>`;
514
+ if (returnType === "plain") return `Promise<RequestResult<${generics}>>`;
515
+ return `Unwrappable<RequestResult<${generics}>>`;
516
+ }
517
+ // ../../internals/client/src/builders/returnStatement.ts
518
+ /**
519
+ * Builds the return statement of a generated operation function. With the default
520
+ * `returnType: 'full'` the runtime call already resolves to `{ data, error, request, response }`.
521
+ * The generated code casts that result to the operation's `RequestResult`, then wraps it with
522
+ * `withUnwrap`, so the caller can `await` it directly or call `.unwrap()` for the bare success
523
+ * body. With `returnType: 'data'` it instead routes the call through the runtime's `unwrapResult`,
524
+ * which narrows the resolved value down to the bare success body the same way `RequestResult`
525
+ * already does, using the plugin default when the call leaves `throwOnError` unset.
526
+ * With `returnType: 'plain'` it returns the runtime call as is, cast to `RequestResult`, with no helper.
527
+ *
528
+ * Cast first, wrap second, for the `'full'` path. That order keeps `withUnwrap`'s generic inferred
529
+ * as `RequestResult` instead of the runtime's own internal result type. Casting an `Unwrappable<A>`
530
+ * straight to `Unwrappable<B>` does not work: the `.then` overload stays pinned to `A`, and `as`
531
+ * rejects that two-generic swap even where `A` and `B` on their own would satisfy it.
532
+ *
533
+ * @example
534
+ * `return withUnwrap(request({ method: 'POST', url: '/pet', ...config }) as Promise<RequestResult<AddPetResponses, ThrowOnError>>)`
535
+ * @example
536
+ * `return unwrapResult(request({ method: 'POST', url: '/pet', ...config }), config.throwOnError ?? false) as Promise<UnwrappedResult<AddPetResponses, ThrowOnError>>`
537
+ * @example
538
+ * `return request({ method: 'POST', url: '/pet', ...config }) as Promise<RequestResult<AddPetResponses, ThrowOnError>>`
539
+ */
540
+ function buildReturnStatement({ node, types, callConfig, returnType, throwOnErrorDefault }) {
541
+ const request = returnType !== "plain" && hasRequestContentTypeChoice(node) ? `request<${types.response.options(node)}['body']>` : "request";
542
+ if (returnType === "data") return `return unwrapResult(${request}(${callConfig}), config.throwOnError ?? ${throwOnErrorDefault}) as ${buildResultType({
543
+ node,
544
+ types,
545
+ returnType
546
+ })}`;
547
+ if (returnType === "plain") return `return ${request}(${callConfig}) as Promise<RequestResult<${buildRequestResultGenerics({
548
+ node,
549
+ types
550
+ })}>>`;
551
+ return `return withUnwrap(${request}(${callConfig}) as Promise<RequestResult<${buildRequestResultGenerics({
552
+ node,
553
+ types
554
+ })}>>)`;
555
+ }
556
+ /**
557
+ * Builds the return type and statement of a `text/event-stream` operation. The item type is `TypedEvent<...>` when each item
558
+ * declares its own `event` and `data`, and otherwise the payload type, which `EventStreamResult` wraps in `ServerSentEvent`. The call's
559
+ * promise goes through `toEventStream` with the `client` for its defaults, the call's `config`, the shape and the per-event
560
+ * validator. A `returnType: 'plain'` client receives only the promise,
561
+ * `toEventStream(request(config))`, and keeps its response validator in the call config.
562
+ */
563
+ function buildEventStreamReturn({ node, types, callConfig, returnType, validatorHooks, throwOnErrorDefault }) {
564
+ const shape = getEventStream(node) ?? "data";
565
+ const responses = types.response.responses(node);
566
+ const success = `SuccessOf<${responses}>`;
567
+ const eventType = shape === "event" ? `TypedEvent<${success}>` : success;
568
+ if (returnType === "plain") return {
569
+ returnType: `Promise<EventStreamResult<${eventType}>>`,
570
+ statement: `return toEventStream<${eventType}>(request(${callConfig}))`
571
+ };
572
+ const generics = `${responses}, ThrowOnError${shape === "event" ? `, ${eventType}` : ""}`;
573
+ const call = `toEventStream<${eventType}>(request(${callConfig}), { client: request, config, ${[`shape: '${shape}'`, validatorHooks?.response ? `validator: ${validatorHooks.response}` : null].filter(Boolean).join(", ")} })`;
574
+ if (returnType === "data") {
575
+ const result = `Promise<UnwrappedStreamResult<${generics}>>`;
576
+ return {
577
+ returnType: result,
578
+ statement: `return unwrapResult(${call}, config.throwOnError ?? ${throwOnErrorDefault}) as ${result}`
579
+ };
580
+ }
581
+ return {
582
+ returnType: `Unwrappable<StreamResult<${generics}>>`,
583
+ statement: `return withUnwrap(${call} as Promise<StreamResult<${generics}>>)`
584
+ };
585
+ }
586
+ /** The stream types an operation file imports: `StreamResult` and `StreamOptions`, `EventStreamResult` for a plain client, and `TypedEvent` for named events. */
587
+ function buildEventStreamImports({ returnType, typed }) {
588
+ return [...{
589
+ plain: ["EventStreamResult"],
590
+ full: ["StreamResult", "StreamOptions"],
591
+ data: ["UnwrappedStreamResult", "StreamOptions"]
592
+ }[returnType], ...typed ? ["TypedEvent"] : []];
593
+ }
456
594
  // ../../internals/client/src/builders/security.ts
457
595
  function serializeAuth(auth) {
458
596
  const parts = [`type: '${auth.type}'`];
@@ -608,6 +746,7 @@ function serializeParameter(parameter) {
608
746
  if (parameter.explode !== void 0) parts.push(`explode: ${parameter.explode}`);
609
747
  if (parameter.in === "query" && parameter.allowReserved !== void 0) parts.push(`allowReserved: ${parameter.allowReserved}`);
610
748
  if (parameter.in !== "path" && parameter.contentType) parts.push(`contentType: '${parameter.contentType}'`);
749
+ if (parameter.in === "querystring") parts.push("querystring: true");
611
750
  return parts.length > 0 ? `{ ${parts.join(", ")} }` : null;
612
751
  }
613
752
  /**
@@ -650,10 +789,9 @@ function buildStyles({ node }) {
650
789
  header: [],
651
790
  cookie: []
652
791
  };
653
- for (const parameter of node.parameters) {
792
+ for (const [location, parameters] of Object.entries(getOperationParameters(node))) for (const parameter of parameters) {
654
793
  const literal = serializeParameter(parameter);
655
- if (!literal) continue;
656
- groups[parameter.in].push(`${toKey(parameter.name)}: ${literal}`);
794
+ if (literal) groups[location].push(`${toKey(parameter.name)}: ${literal}`);
657
795
  }
658
796
  const entries = [...Object.keys(groups).filter((location) => groups[location].length > 0).map((location) => `${location}: { ${groups[location].join(", ")} }`), serializeBodyEncoding(node)].filter(Boolean);
659
797
  return entries.length > 0 ? `{ ${entries.join(", ")} }` : null;
@@ -664,12 +802,20 @@ function buildStyles({ node }) {
664
802
  * functions and the SDK methods. The `throwOnError` fallback is applied after the `...config` spread.
665
803
  *
666
804
  * `mergeContentType` is `true` when the caller must destructure `contentType` from `options`, and
667
- * `eventStream` is `true` when the operation returns `text/event-stream`.
805
+ * `eventStream` is `true` when the operation returns `text/event-stream`. Unless `returnType` is `'plain'`, a
806
+ * stream's literal leaves the `response` validator to `toEventStream`, which runs it per event.
668
807
  */
669
- function buildCallConfig({ node, validatorHooks, security, throwOnErrorDefault }) {
808
+ function buildCallConfig({ node, validatorHooks, security, throwOnErrorDefault, returnType }) {
809
+ const eventStream = isEventStream(node);
670
810
  const securityLiteral = buildSecurityMetadata({ security });
671
811
  const stylesLiteral = buildStyles({ node });
672
- const validatorLiteral = buildValidatorLiteral(validatorHooks, [
812
+ const validatorLiteral = buildValidatorLiteral(validatorHooks, eventStream && returnType !== "plain" ? [
813
+ "request",
814
+ "path",
815
+ "query",
816
+ "headers",
817
+ "error"
818
+ ] : [
673
819
  "request",
674
820
  "path",
675
821
  "query",
@@ -692,90 +838,7 @@ function buildCallConfig({ node, validatorHooks, security, throwOnErrorDefault }
692
838
  `throwOnError: config.throwOnError ?? ${throwOnErrorDefault}`
693
839
  ].filter(Boolean).join(", ")} }`,
694
840
  mergeContentType,
695
- eventStream: isEventStream(node)
696
- };
697
- }
698
- // ../../internals/client/src/builders/generics.ts
699
- /**
700
- * Builds the `RequestResult` generic arguments for one operation: the per-status responses record
701
- * plus the per-call `ThrowOnError` flag. `SuccessOf` / `ErrorOf` split the record inside the
702
- * runtime, so this only names the record and threads `ThrowOnError`.
703
- *
704
- * @example
705
- * `buildRequestResultGenerics({ node, types }) // 'AddPetResponses, ThrowOnError'`
706
- */
707
- function buildRequestResultGenerics({ node, types }) {
708
- return `${types.response.responses(node)}, ThrowOnError`;
709
- }
710
- /**
711
- * Builds the full return type an operation's function signature uses: `Unwrappable<RequestResult>`
712
- * for the default `returnType: 'full'`, already a promise so no further wrapping is needed, a plain
713
- * `Promise<RequestResult>` for the internal `'plain'` shape, or a
714
- * `Promise` of the runtime's `UnwrappedResult` when `returnType: 'data'` narrows a resolved call
715
- * down to the bare success body.
716
- *
717
- * @example
718
- * `buildResultType({ node, types, returnType: 'data' }) // 'Promise<UnwrappedResult<AddPetResponses, ThrowOnError>>'`
719
- */
720
- function buildResultType({ node, types, returnType }) {
721
- const generics = buildRequestResultGenerics({
722
- node,
723
- types
724
- });
725
- if (returnType === "data") return `Promise<UnwrappedResult<${generics}>>`;
726
- if (returnType === "plain") return `Promise<RequestResult<${generics}>>`;
727
- return `Unwrappable<RequestResult<${generics}>>`;
728
- }
729
- // ../../internals/client/src/builders/returnStatement.ts
730
- /**
731
- * Builds the return statement of a generated operation function. With the default
732
- * `returnType: 'full'` the runtime call already resolves to `{ data, error, request, response }`.
733
- * The generated code casts that result to the operation's `RequestResult`, then wraps it with
734
- * `withUnwrap`, so the caller can `await` it directly or call `.unwrap()` for the bare success
735
- * body. With `returnType: 'data'` it instead routes the call through the runtime's `unwrapResult`,
736
- * which narrows the resolved value down to the bare success body the same way `RequestResult`
737
- * already does, using the plugin default when the call leaves `throwOnError` unset.
738
- * With `returnType: 'plain'` it returns the runtime call as is, cast to `RequestResult`, with no helper.
739
- *
740
- * Cast first, wrap second, for the `'full'` path. That order keeps `withUnwrap`'s generic inferred
741
- * as `RequestResult` instead of the runtime's own internal result type. Casting an `Unwrappable<A>`
742
- * straight to `Unwrappable<B>` does not work: the `.then` overload stays pinned to `A`, and `as`
743
- * rejects that two-generic swap even where `A` and `B` on their own would satisfy it.
744
- *
745
- * @example
746
- * `return withUnwrap(request({ method: 'POST', url: '/pet', ...config }) as Promise<RequestResult<AddPetResponses, ThrowOnError>>)`
747
- * @example
748
- * `return unwrapResult(request({ method: 'POST', url: '/pet', ...config }), config.throwOnError ?? false) as Promise<UnwrappedResult<AddPetResponses, ThrowOnError>>`
749
- * @example
750
- * `return request({ method: 'POST', url: '/pet', ...config }) as Promise<RequestResult<AddPetResponses, ThrowOnError>>`
751
- */
752
- function buildReturnStatement({ node, types, callConfig, returnType, throwOnErrorDefault }) {
753
- const request = returnType !== "plain" && hasRequestContentTypeChoice(node) ? `request<${types.response.options(node)}['body']>` : "request";
754
- if (returnType === "data") return `return unwrapResult(${request}(${callConfig}), config.throwOnError ?? ${throwOnErrorDefault}) as ${buildResultType({
755
- node,
756
- types,
757
- returnType
758
- })}`;
759
- if (returnType === "plain") return `return ${request}(${callConfig}) as Promise<RequestResult<${buildRequestResultGenerics({
760
- node,
761
- types
762
- })}>>`;
763
- return `return withUnwrap(${request}(${callConfig}) as Promise<RequestResult<${buildRequestResultGenerics({
764
- node,
765
- types
766
- })}>>)`;
767
- }
768
- /**
769
- * Builds the return type and statement of an operation that returns `text/event-stream`: the call
770
- * is wrapped in `toEventStream`, so the caller iterates the typed success events. Except with
771
- * `returnType: 'plain'`, `toEventStream` also receives `config`, so a per-call `maxEventSize` reaches the parser.
772
- */
773
- function buildEventStreamReturn({ node, types, callConfig, returnType }) {
774
- const eventType = `SuccessOf<${types.response.responses(node)}>`;
775
- const args = returnType === "plain" ? `request(${callConfig})` : `request(${callConfig}), config`;
776
- return {
777
- returnType: `Promise<EventStreamResult<${eventType}>>`,
778
- statement: `return toEventStream<${eventType}>(${args})`
841
+ eventStream
779
842
  };
780
843
  }
781
844
  // ../../internals/client/src/builders/signature.ts
@@ -789,13 +852,14 @@ const declarationPrinter = functionPrinter({ mode: "declaration" });
789
852
  * per-operation input type has to be emitted. Both names come from `types`, which is `plugin-ts` or
790
853
  * `plugin-zod`'s inferred types (see `resolveOperationTypes`).
791
854
  */
792
- function buildGroupedOptionsSignature({ node, types, returnType, throwOnErrorDefault }) {
855
+ function buildGroupedOptionsSignature({ node, types, returnType, throwOnErrorDefault, eventStream = false }) {
793
856
  const optionsName = types.response.options(node);
857
+ const optionsType = `${eventStream && returnType !== "plain" ? "StreamOptions" : "Options"}<${optionsName}, ThrowOnError>`;
794
858
  const { isOptional } = getRequestGroupOptionality(node);
795
859
  return {
796
860
  paramsSignature: declarationPrinter.print(createFunctionParameters({ params: [createFunctionParameter({
797
861
  name: "options",
798
- type: `Options<${optionsName}, ThrowOnError>`,
862
+ type: optionsType,
799
863
  ...isOptional ? { default: "{}" } : {}
800
864
  })] })) ?? "",
801
865
  returnType: buildResultType({
@@ -806,38 +870,63 @@ function buildGroupedOptionsSignature({ node, types, returnType, throwOnErrorDef
806
870
  generics: [`ThrowOnError extends boolean = ${throwOnErrorDefault}`]
807
871
  };
808
872
  }
809
- // ../../internals/client/src/components/Operation.tsx
873
+ // ../../internals/client/src/builders/operationBody.ts
810
874
  /**
811
- * Renders one client operation: the grouped `<Name>Request` type and the function that forwards a
812
- * single `options` object to the resolved client and returns the `Unwrappable<RequestResult>`. The
813
- * type, signature, and call config are built with the AST factory, and only the jsx-renderer emits
814
- * the source.
875
+ * The pieces of a generated operation: its generics, `options` parameter, return type, return statement, and whether the
876
+ * body destructures `contentType` from `options`. Shared by the standalone functions and the SDK methods, so a
877
+ * `text/event-stream` operation is wired to `toEventStream` in one place.
815
878
  */
816
- function Operation({ name, node, types, validatorHooks, returnType, throwOnErrorDefault, security }) {
817
- if (!ast.isHttpOperationNode(node)) return null;
818
- const signature = buildGroupedOptionsSignature({
819
- node,
820
- types,
821
- returnType,
822
- throwOnErrorDefault
823
- });
879
+ function buildOperationBody({ node, types, validatorHooks, security, returnType, throwOnErrorDefault }) {
824
880
  const { callConfig, mergeContentType, eventStream } = buildCallConfig({
825
881
  node,
826
882
  validatorHooks,
827
883
  security,
828
- throwOnErrorDefault
884
+ throwOnErrorDefault,
885
+ returnType
829
886
  });
830
- const eventStreamReturn = eventStream ? buildEventStreamReturn({
887
+ const signature = buildGroupedOptionsSignature({
888
+ node,
889
+ types,
890
+ returnType,
891
+ throwOnErrorDefault,
892
+ eventStream
893
+ });
894
+ const stream = eventStream ? buildEventStreamReturn({
831
895
  node,
832
896
  types,
833
897
  callConfig,
834
- returnType
898
+ returnType,
899
+ validatorHooks,
900
+ throwOnErrorDefault
835
901
  }) : null;
836
- const functionReturnType = eventStreamReturn?.returnType ?? signature.returnType;
837
- const returnStatement = eventStreamReturn?.statement ?? buildReturnStatement({
902
+ return {
903
+ generics: signature.generics,
904
+ paramsSignature: signature.paramsSignature,
905
+ returnType: stream?.returnType ?? signature.returnType,
906
+ statement: stream?.statement ?? buildReturnStatement({
907
+ node,
908
+ types,
909
+ callConfig,
910
+ returnType,
911
+ throwOnErrorDefault
912
+ }),
913
+ mergeContentType
914
+ };
915
+ }
916
+ // ../../internals/client/src/components/Operation.tsx
917
+ /**
918
+ * Renders one client operation: the grouped `<Name>Request` type and the function that forwards a
919
+ * single `options` object to the resolved client and returns the `Unwrappable<RequestResult>`. The
920
+ * type, signature, and call config are built with the AST factory, and only the jsx-renderer emits
921
+ * the source.
922
+ */
923
+ function Operation({ name, node, types, validatorHooks, returnType, throwOnErrorDefault, security }) {
924
+ if (!ast.isHttpOperationNode(node)) return null;
925
+ const body = buildOperationBody({
838
926
  node,
839
927
  types,
840
- callConfig,
928
+ validatorHooks,
929
+ security,
841
930
  returnType,
842
931
  throwOnErrorDefault
843
932
  });
@@ -848,22 +937,53 @@ function Operation({ name, node, types, validatorHooks, returnType, throwOnError
848
937
  children: /* @__PURE__ */ jsxs(Function, {
849
938
  name,
850
939
  export: true,
851
- generics: signature.generics,
852
- params: signature.paramsSignature,
853
- returnType: functionReturnType,
940
+ generics: body.generics,
941
+ params: body.paramsSignature,
942
+ returnType: body.returnType,
854
943
  JSDoc: { comments: buildOperationComments(node, {
855
944
  link: "urlPath",
856
945
  linkPosition: "beforeDeprecated",
857
946
  splitLines: true
858
947
  }) },
859
948
  children: [
860
- mergeContentType ? "const { client: request = client, contentType, ...config } = options" : "const { client: request = client, ...config } = options",
949
+ body.mergeContentType ? "const { client: request = client, contentType, ...config } = options" : "const { client: request = client, ...config } = options",
861
950
  /* @__PURE__ */ jsx("br", {}),
862
- returnStatement
951
+ body.statement
863
952
  ]
864
953
  })
865
954
  });
866
955
  }
956
+ // ../../internals/client/src/components/StreamRuntime.tsx
957
+ /**
958
+ * The `footer` of `.kubb/client.ts`: the `baseURL` set at generation time, and the `sse.ts` re-export when the document
959
+ * streams events, so `ServerSentEvent` and `toEventStream` stay importable from the client as in earlier releases.
960
+ */
961
+ function buildClientFooter({ baseURL, sse }) {
962
+ const baseURLExpression = baseURL ? baseURL.includes("${") ? `\`${baseURL.replaceAll("`", "\\`")}\`` : JSON.stringify(baseURL) : void 0;
963
+ return [baseURLExpression ? `client.setConfig({ baseURL: ${baseURLExpression} })` : null, sse ? `export * from './sse.ts'` : null].filter(Boolean).join("\n") || void 0;
964
+ }
965
+ /**
966
+ * Writes the footer of the `.kubb/client.ts` the plugin injected, the one place it is built, and copies `sse.ts` into
967
+ * `.kubb/` only for a document with a `text/event-stream` operation, so a document without streams ships no stream runtime.
968
+ * `sse` is set by the plugins that ship the runtime, so a custom client from `@kubb/plugin-client` gets neither file.
969
+ */
970
+ function StreamRuntime({ nodes, root, sse, baseURL }) {
971
+ if (!sse) return null;
972
+ const streams = nodes.some(isEventStream);
973
+ const footer = buildClientFooter({
974
+ baseURL,
975
+ sse: streams
976
+ });
977
+ return /* @__PURE__ */ jsxs(Fragment, { children: [streams && /* @__PURE__ */ jsx(File, {
978
+ baseName: "sse.ts",
979
+ path: path.resolve(root, ".kubb/sse.ts"),
980
+ copy: sse
981
+ }), footer && /* @__PURE__ */ jsx(File, {
982
+ baseName: "client.ts",
983
+ path: path.resolve(root, ".kubb/client.ts"),
984
+ footer
985
+ })] });
986
+ }
867
987
  // ../../internals/client/src/resolveOperationTypes.ts
868
988
  /**
869
989
  * A `plugin-ts` shaped resolver whose operation type names point at the types `pluginZod({ inferred })`
@@ -939,13 +1059,24 @@ function resolveOperationTypes(driver, pluginName) {
939
1059
  * `@kubb/plugin-axios`). Emits one async function per OpenAPI operation using the shared
940
1060
  * `Operation` component: a grouped `<Name>Request` type and a function that forwards a single
941
1061
  * `options` object to the bundled `client` and returns the `Unwrappable<RequestResult>`. Only the
942
- * generator `name` differs between plugins. Every other resolution, import, and rendering step is
943
- * identical.
1062
+ * generator `name` and the runtime templates differ between plugins. Every other resolution, import,
1063
+ * and rendering step is identical.
1064
+ *
1065
+ * `sse` is the Server-Sent Events runtime template, copied into `.kubb/sse.ts` only for a document with a
1066
+ * `text/event-stream` operation.
944
1067
  */
945
- function createClientGenerator(name) {
1068
+ function createClientGenerator(name, { sse } = {}) {
946
1069
  return defineGenerator({
947
1070
  name,
948
1071
  renderer: jsxRenderer,
1072
+ operations(nodes, ctx) {
1073
+ return /* @__PURE__ */ jsx(StreamRuntime, {
1074
+ nodes,
1075
+ root: ctx.root,
1076
+ sse,
1077
+ baseURL: ctx.options.baseURL
1078
+ });
1079
+ },
949
1080
  operation(node, ctx) {
950
1081
  if (!ast.isHttpOperationNode(node)) return null;
951
1082
  const { config, driver, resolver, root } = ctx;
@@ -995,19 +1126,19 @@ function createClientGenerator(name) {
995
1126
  const clientPath = importPath ?? path.resolve(root, ".kubb/client.ts");
996
1127
  const clientRoot = importPath === void 0 ? meta.file.path : void 0;
997
1128
  const standardSchemaPath = importPath ?? path.resolve(root, ".kubb/standardSchema.ts");
998
- const eventStream = isEventStream(node);
1129
+ const ssePath = importPath ?? path.resolve(root, ".kubb/sse.ts");
1130
+ const stream = getEventStream(node);
999
1131
  const clientValueImports = (() => {
1000
- if (eventStream) return ["client", "toEventStream"];
1001
1132
  if (returnType === "plain") return ["client"];
1002
1133
  if (returnType === "data") return ["client", "unwrapResult"];
1003
1134
  return ["client", "withUnwrap"];
1004
1135
  })();
1005
1136
  const clientTypeImports = (() => {
1006
- if (eventStream) return [
1007
- "Options",
1008
- "EventStreamResult",
1009
- "SuccessOf"
1010
- ];
1137
+ if (stream) return {
1138
+ plain: ["Options", "SuccessOf"],
1139
+ data: ["SuccessOf"],
1140
+ full: ["SuccessOf", "Unwrappable"]
1141
+ }[returnType];
1011
1142
  if (returnType === "plain") return ["Options", "RequestResult"];
1012
1143
  if (returnType === "data") return ["Options", "UnwrappedResult"];
1013
1144
  return [
@@ -1048,6 +1179,20 @@ function createClientGenerator(name) {
1048
1179
  path: clientPath,
1049
1180
  isTypeOnly: true
1050
1181
  }),
1182
+ stream && /* @__PURE__ */ jsx(File.Import, {
1183
+ name: ["toEventStream"],
1184
+ root: clientRoot,
1185
+ path: ssePath
1186
+ }),
1187
+ stream && /* @__PURE__ */ jsx(File.Import, {
1188
+ name: buildEventStreamImports({
1189
+ returnType,
1190
+ typed: stream === "event"
1191
+ }),
1192
+ root: clientRoot,
1193
+ path: ssePath,
1194
+ isTypeOnly: true
1195
+ }),
1051
1196
  meta.fileTypes && importedTypeNames.length > 0 && /* @__PURE__ */ jsx(File.Import, {
1052
1197
  name: Array.from(new Set(importedTypeNames)),
1053
1198
  root: meta.file.path,
@@ -1080,11 +1225,11 @@ function createClientGenerator(name) {
1080
1225
  }
1081
1226
  // ../../internals/client/src/macros.ts
1082
1227
  /**
1083
- * Macros the client plugins apply by default, ahead of any user macros. `macroSimplifyUnion`
1084
- * drops union members a broader scalar already covers, keeping the generated response and error
1228
+ * Macros the client plugins apply by default, ahead of any user macros. `macroDedupeUnion`
1229
+ * drops repeated union members, keeping the generated response and error
1085
1230
  * unions tidy. A plugin wires them with `ctx.setMacros([...defaultMacros, ...userMacros])`.
1086
1231
  */
1087
- const defaultMacros = [macroSimplifyUnion];
1232
+ const defaultMacros = [macroDedupeUnion];
1088
1233
  // ../../internals/client/src/resolver.ts
1089
1234
  /**
1090
1235
  * Default resolver shared by the client plugins. Functions use safe camelCase identifiers and