@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/README.md +1 -1
- package/dist/client-CiHb_Cfg.d.ts +345 -0
- package/dist/index.cjs +288 -143
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +290 -145
- package/dist/index.js.map +1 -1
- package/dist/serializers-Ds8zZwOO.d.ts +190 -0
- package/dist/standardSchema-BShJWz2i.d.ts +104 -0
- package/dist/templates/client.d.ts +2 -0
- package/dist/templates/core.d.ts +3 -0
- package/dist/templates/serializers.d.ts +2 -0
- package/dist/templates/sse.d.ts +124 -0
- package/dist/templates/standardSchema.d.ts +2 -0
- package/docs/guides/write-your-client.md +1 -1
- package/docs/overview.md +1 -1
- package/kubb-extension.json +1 -1
- package/package.json +17 -8
- package/templates/client.ts +5 -0
- package/templates/core.ts +694 -0
- package/templates/serializers.ts +493 -0
- package/templates/sse.ts +274 -0
- package/templates/standardSchema.ts +162 -0
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { Diagnostics, Resolver, Url, addGenerator, ast, camelCase, createResolver, defineExtension, defineGenerator,
|
|
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
|
-
*
|
|
88
|
-
*
|
|
89
|
-
|
|
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
|
|
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
|
-
*
|
|
99
|
-
*
|
|
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
|
|
197
|
+
const dedupe = (params) => {
|
|
169
198
|
const seen = /* @__PURE__ */ new Set();
|
|
170
|
-
return
|
|
171
|
-
if (
|
|
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.
|
|
438
|
+
"kubb": ">=5.16.0",
|
|
409
439
|
"node": ">=22.22"
|
|
410
440
|
};
|
|
411
441
|
// package.json
|
|
412
|
-
var version = "5.10.0
|
|
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
|
|
792
|
+
for (const [location, parameters] of Object.entries(getOperationParameters(node))) for (const parameter of parameters) {
|
|
654
793
|
const literal = serializeParameter(parameter);
|
|
655
|
-
if (
|
|
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
|
|
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:
|
|
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/
|
|
873
|
+
// ../../internals/client/src/builders/operationBody.ts
|
|
810
874
|
/**
|
|
811
|
-
*
|
|
812
|
-
*
|
|
813
|
-
*
|
|
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
|
|
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
|
|
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
|
-
|
|
837
|
-
|
|
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
|
-
|
|
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:
|
|
852
|
-
params:
|
|
853
|
-
returnType:
|
|
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
|
-
|
|
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`
|
|
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
|
|
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 (
|
|
1007
|
-
"Options",
|
|
1008
|
-
"
|
|
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. `
|
|
1084
|
-
* drops union members
|
|
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 = [
|
|
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
|