@kubb/plugin-client 5.12.0 → 6.0.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-Dq0z61hu.d.ts +554 -0
- package/dist/index.cjs +1378 -784
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +114 -74
- package/dist/index.d.ts +114 -74
- package/dist/index.js +1376 -786
- package/dist/index.js.map +1 -1
- package/dist/serializers-BRgfPGr1.d.ts +119 -0
- package/dist/{standardSchema-BShJWz2i.d.ts → standardSchema-CGuiSK7F.d.ts} +15 -37
- package/dist/templates/client.d.ts +3 -2
- package/dist/templates/core.d.ts +3 -3
- package/dist/templates/mediaTypes/formData.d.ts +5 -0
- package/dist/templates/mediaTypes/urlencoded.d.ts +5 -0
- package/dist/templates/mediaTypes.d.ts +2 -0
- package/dist/templates/serializers/cookies.d.ts +5 -0
- package/dist/templates/serializers/headers.d.ts +5 -0
- package/dist/templates/serializers/path.d.ts +5 -0
- package/dist/templates/serializers/pathStyles.d.ts +5 -0
- package/dist/templates/serializers/query.d.ts +5 -0
- package/dist/templates/serializers/queryStyles.d.ts +14 -0
- package/dist/templates/serializers.d.ts +2 -2
- package/dist/templates/sse.d.ts +20 -113
- package/dist/templates/standardSchema.d.ts +1 -1
- package/docs/guides/authentication.md +9 -1
- package/docs/guides/migrate-to-v6.md +139 -0
- package/docs/guides/validate-requests-and-responses.md +8 -3
- package/docs/guides/write-your-client.md +117 -11
- package/docs/options.md +2 -2
- package/docs/overview.md +1 -1
- package/kubb-extension.json +1 -1
- package/package.json +6 -5
- package/src/builders/callConfig.ts +80 -0
- package/src/builders/contentType.ts +35 -0
- package/src/builders/generics.ts +29 -0
- package/src/builders/operationBody.ts +43 -0
- package/src/builders/operationData.ts +64 -0
- package/src/builders/returnStatement.ts +103 -0
- package/src/builders/security.ts +125 -0
- package/src/builders/signature.ts +71 -0
- package/src/builders/styles.ts +94 -0
- package/src/builders/validator.ts +84 -0
- package/src/builders/validatorOptions.ts +53 -0
- package/src/clientTypes.ts +195 -0
- package/src/components/ClientRuntime.tsx +97 -0
- package/src/components/Operation.tsx +72 -0
- package/src/components/SdkClient.tsx +100 -0
- package/src/components/SdkFacade.tsx +35 -0
- package/src/generators/clientGenerator.tsx +91 -6
- package/src/generators/sdkGenerator.tsx +209 -0
- package/src/index.ts +3 -1
- package/src/macros.ts +9 -0
- package/src/plugin.ts +7 -8
- package/src/resolver.ts +28 -0
- package/src/setup.ts +90 -0
- package/src/types.ts +4 -2
- package/templates/client.ts +2 -1
- package/templates/core.ts +735 -286
- package/templates/mediaTypes/formData.ts +59 -0
- package/templates/mediaTypes/urlencoded.ts +33 -0
- package/templates/mediaTypes.ts +147 -0
- package/templates/serializers/cookies.ts +58 -0
- package/templates/serializers/headers.ts +35 -0
- package/templates/serializers/path.ts +19 -0
- package/templates/serializers/pathStyles.ts +27 -0
- package/templates/serializers/query.ts +21 -0
- package/templates/serializers/queryStyles.ts +107 -0
- package/templates/serializers.ts +48 -364
- package/templates/sse.ts +87 -178
- package/templates/standardSchema.ts +16 -37
- package/dist/client-BxO6Dygu.d.ts +0 -355
- package/dist/serializers-Ds8zZwOO.d.ts +0 -190
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Migrate to v6
|
|
2
|
+
|
|
3
|
+
`@kubb/plugin-client` 6 calls your client with type arguments and streams through `client.stream`. Update the `client` export of your module, then regenerate. Find your code in the table, then follow its section.
|
|
4
|
+
|
|
5
|
+
## What to change
|
|
6
|
+
|
|
7
|
+
| Your code | What to do |
|
|
8
|
+
| ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
9
|
+
| Exports a `client` that takes no type arguments | Make it generic. See [A generic client call](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#a-generic-client-call). |
|
|
10
|
+
| Exports `toEventStream` or `EventStreamResult` | Add a `stream` method to `client`. See [A stream method](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#a-stream-method-instead-of-toeventstream). |
|
|
11
|
+
| Declares `responseType` on `RequestConfig`, or lacks `contentType` or `cookies` | Add the new fields. See [New fields on the request config](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#new-fields-on-the-request-config). |
|
|
12
|
+
| Reads `security` entries as single schemes | Handle arrays and the new fields. See [Security requirements](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#security-requirements). |
|
|
13
|
+
| Imports a `…Status2XX` type, or writes a `'2XX'` key | Write the range in lower case. See [Status ranges](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#status-ranges). |
|
|
14
|
+
| Groups by tag in an OpenAPI 3.2 document whose tags set `parent` | Update the import paths. See [Tag folders](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#tag-folders). |
|
|
15
|
+
| Has `servers` on a path or operation, or reads XML, YAML, CSV, or binary bodies | Check [Behavior changes](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#behavior-changes). It compiles. |
|
|
16
|
+
| Wants `.unwrap()` or a call that resolves to the body | Set `returnType`. See [Return type](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#return-type). This is optional. |
|
|
17
|
+
|
|
18
|
+
## Upgrade steps
|
|
19
|
+
|
|
20
|
+
1. Upgrade `kubb` and `@kubb/plugin-client` together. v6 needs Kubb 5.18.4 or later:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install --save-dev kubb@latest @kubb/plugin-client@6
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
2. Update your client module with the sections below.
|
|
27
|
+
|
|
28
|
+
3. Regenerate the operation files:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx kubb generate
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
4. Run the type checker. A missing export or field in your client module shows up as a compile error in the generated files:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx tsc --noEmit
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## A generic client call
|
|
41
|
+
|
|
42
|
+
A generated function now calls `client<TResponses, ThrowOnError>(config)`. Make `client` generic:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
export const client = <TResponses, ThrowOnError extends boolean = true>(config: RequestConfig) =>
|
|
46
|
+
send(config) as Promise<RequestResult<TResponses, ThrowOnError>>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## A stream method instead of toEventStream
|
|
50
|
+
|
|
51
|
+
A Server-Sent Events or JSON Lines operation calls `client.stream<TResponses, ThrowOnError, TItem>(config)`. Remove `toEventStream` and `EventStreamResult`, add `stream` to `client`, and export `StreamResult`, `SuccessOf`, `TypedEvent`, and `ServerSentEvent`:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
export type StreamResult<TResponses, ThrowOnError extends boolean = true, TEvent = ServerSentEvent<SuccessOf<TResponses>>> = {
|
|
55
|
+
stream: AsyncGenerator<TEvent>
|
|
56
|
+
response: Response
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function client<TResponses, ThrowOnError extends boolean = true>(config: RequestConfig) {
|
|
60
|
+
return send<TResponses, ThrowOnError>(config)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
client.stream = async <TResponses, ThrowOnError extends boolean = true, TEvent = ServerSentEvent<SuccessOf<TResponses>>>(
|
|
64
|
+
config: RequestConfig,
|
|
65
|
+
): Promise<StreamResult<TResponses, ThrowOnError, TEvent>> => {
|
|
66
|
+
// send the request and parse the body into TEvent values
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
A Server-Sent Events call also passes `eventShape: 'event'` or `'data'`. A JSON Lines call, such as `application/jsonl` or `application/x-ndjson`, passes the media type in `contentType.response` and expects one parsed item per line. See [Write your client](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/write-your-client).
|
|
71
|
+
|
|
72
|
+
## New fields on the request config
|
|
73
|
+
|
|
74
|
+
Add these fields to your `RequestConfig`, or the generated functions fail to compile:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
export type RequestConfig = {
|
|
78
|
+
// ...
|
|
79
|
+
contentType?: string | { request?: string; response?: string }
|
|
80
|
+
// Replaces `responseType`. Binary downloads pass 'blob'.
|
|
81
|
+
parseAs?: 'json' | 'text' | 'blob' | 'arrayBuffer' | 'stream'
|
|
82
|
+
eventShape?: 'event' | 'data'
|
|
83
|
+
// Name and value pairs for `in: cookie` parameters. Join them into a `Cookie` header on the server. Browsers ignore a `Cookie` header set by script and send their own cookies.
|
|
84
|
+
cookies?: object
|
|
85
|
+
// `style`, `explode`, `allowReserved`, and `contentType` per parameter, grouped by location.
|
|
86
|
+
styles?: object
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
A cookie parameter with the OpenAPI 3.2 `cookie` style passes `style: 'cookie'` in `styles`. Send its values without percent-encoding. See [Serialize parameters](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/write-your-client#serialize-parameters).
|
|
91
|
+
|
|
92
|
+
## Security requirements
|
|
93
|
+
|
|
94
|
+
A `security` entry is an array when one requirement needs several schemes together, such as an API key and a session cookie. Send all of them, or skip the entry.
|
|
95
|
+
|
|
96
|
+
An `oauth2` scheme now also passes `flows` and `metadataUrl`, and an `openIdConnect` scheme passes `url`. Accept them on your auth type:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
export type Auth = {
|
|
100
|
+
type: string
|
|
101
|
+
scheme?: string
|
|
102
|
+
name?: string
|
|
103
|
+
in?: string
|
|
104
|
+
scopes?: Array<string>
|
|
105
|
+
flows?: Record<string, Record<string, string>>
|
|
106
|
+
metadataUrl?: string
|
|
107
|
+
url?: string
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Status ranges
|
|
112
|
+
|
|
113
|
+
A status range is written in lower case in generated names and keys. Rename the imports, and the range keys in a responses record or `validator.error` map you wrote by hand. An upper-case key such as `'2XX'` is no longer read:
|
|
114
|
+
|
|
115
|
+
```diff
|
|
116
|
+
- import type { ListPetsStatus4XX } from './gen/types/ListPets'
|
|
117
|
+
+ import type { ListPetsStatus4xx } from './gen/types/ListPets'
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Tag folders
|
|
121
|
+
|
|
122
|
+
With `group: { type: 'tag' }`, a tag whose OpenAPI 3.2 `parent` is `store` now writes to `store/payment/` instead of `payment/`. `importPath` is written into every file as given, so one relative path cannot serve files at two depths, such as `store/` and `store/payment/`. Pass `group.name` to keep one folder per tag, or set `importPath` to a package name or alias that resolves from every folder:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
pluginClient({ importPath: '../../../client', group: { type: 'tag', name: ({ group }) => group } })
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Behavior changes
|
|
129
|
+
|
|
130
|
+
These compile without errors, so check them by hand:
|
|
131
|
+
|
|
132
|
+
- A path or operation with its own `servers` passes that server as `baseURL` on the call. Your client must use `config.baseURL` when it is set.
|
|
133
|
+
- A `2xx` response key is a success, and an exact code such as `200` wins over a range listed before it.
|
|
134
|
+
- An XML, YAML, or CSV response is typed `string`. An image, audio, video, PDF, or `application/octet-stream` response is typed `Blob`. When every success content type is binary, the call passes `parseAs: 'blob'`.
|
|
135
|
+
- An operation whose only success content type is JSON Lines returns a `StreamResult`, not the whole body as one string.
|
|
136
|
+
|
|
137
|
+
## Return type
|
|
138
|
+
|
|
139
|
+
`returnType` is new and defaults to `'plain'`, so existing code keeps working. With `'full'`, generated calls wrap the result in `withUnwrap`, so your module must also export `withUnwrap`, `resolveThrowOnError`, and the `Unwrappable` type. With `'data'`, calls resolve to the body when `throwOnError` is `true`, and to the full result when it is `false`. Your module must then export `unwrapResult`, `resolveThrowOnError`, and the `UnwrappedResult` type. A stream operation also imports `StreamOptions` with `StreamResult` for `'full'`, or with `UnwrappedStreamResult` for `'data'`. Both modes need a `StreamResult` with `data` and `error`, as the one in `@kubb/plugin-client/templates/core.ts` has, so the minimal type above is not enough. `@kubb/plugin-client/templates/core.ts` exports all of these. See [returnType](https://kubb.dev/extensions/kubb-labs/plugin-client/options#returntype).
|
|
@@ -29,11 +29,13 @@ Each generated call now includes the schemas:
|
|
|
29
29
|
request({
|
|
30
30
|
method: 'POST',
|
|
31
31
|
url: '/pet',
|
|
32
|
-
validator: { response: addPetResponseSchema, error: addPetErrorSchema },
|
|
33
32
|
...config,
|
|
33
|
+
validator: { response: addPetResponseSchema, error: { 405: addPetStatus405Schema }, ...config.validator },
|
|
34
34
|
})
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
A `validator` passed on one call is merged per key, so `validator: { request }` adds a request schema and keeps the generated response schema. A per-call `error` replaces the generated error map as a whole.
|
|
38
|
+
|
|
37
39
|
Add a `validator` field to your `RequestConfig`, then run the schemas in `client`. Zod schemas follow [Standard Schema](https://standardschema.dev), so `~standard.validate` works with any compatible library:
|
|
38
40
|
|
|
39
41
|
```typescript [src/client.ts]
|
|
@@ -42,7 +44,7 @@ type Schema = { '~standard': { validate: (value: unknown) => Result | Promise<Re
|
|
|
42
44
|
|
|
43
45
|
export type RequestConfig = {
|
|
44
46
|
// ...
|
|
45
|
-
validator?: { request?: Schema; response?: Schema; error?: Schema }
|
|
47
|
+
validator?: { request?: Schema; response?: Schema; error?: Schema | Record<string, Schema> }
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
// inside client(), after reading the body
|
|
@@ -53,6 +55,8 @@ const validatedBody = result && 'value' in result ? result.value : body
|
|
|
53
55
|
|
|
54
56
|
Return `validatedBody` as `data`, so a schema that transforms the value takes effect. Without a response schema, `validatedBody` is the original `body`.
|
|
55
57
|
|
|
58
|
+
`error` is keyed by status, such as `{ 404: schema, '5xx': schema, default: schema }`. Pick the exact status, then its range, then `default`, and skip validation when none matches.
|
|
59
|
+
|
|
56
60
|
Set `validator: { request: 'zod' }` or `{ response: 'zod' }` to pass one direction only. Without `pluginZod()` in the plugins list, generation stops with an error.
|
|
57
61
|
|
|
58
62
|
## Validate params
|
|
@@ -63,12 +67,13 @@ With `validator: { params: 'zod' }`, each call also passes the schemas for its p
|
|
|
63
67
|
request({
|
|
64
68
|
method: 'PUT',
|
|
65
69
|
url: '/events/{day}',
|
|
70
|
+
...config,
|
|
66
71
|
validator: {
|
|
67
72
|
path: putEventPathSchema,
|
|
68
73
|
query: putEventQuerySchema,
|
|
69
74
|
headers: toHeadersStandardSchema(putEventHeadersSchema, putEventHeadersSchema.shape),
|
|
75
|
+
...config.validator,
|
|
70
76
|
},
|
|
71
|
-
...config,
|
|
72
77
|
})
|
|
73
78
|
```
|
|
74
79
|
|
|
@@ -10,7 +10,9 @@ The generated functions import your module through [`importPath`](https://kubb.d
|
|
|
10
10
|
| `Options` | type | The grouped options object a generated function accepts. |
|
|
11
11
|
| `RequestResult` | type | What a generated function resolves to. |
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
A generated function calls `client<TResponses, ThrowOnError>(config)`, so type `client` to return `Promise<RequestResult<TResponses, ThrowOnError>>`.
|
|
14
|
+
|
|
15
|
+
If your spec has server-sent event or JSON Lines operations, `client` also needs a `stream` method, and the module exports the `StreamResult`, `SuccessOf`, `TypedEvent`, and `ServerSentEvent` types. A generated function calls `client.stream<TResponses, ThrowOnError, TItem>(config)` and returns `Promise<StreamResult<TResponses, ThrowOnError, TItem>>`. `TItem` is `TypedEvent<…>` when the document names each event and the success type for JSON Lines. When the document types only the payload, `TItem` is left out and defaults to `ServerSentEvent<SuccessOf<TResponses>>`. A Server-Sent Events call passes `eventShape: 'event'` or `'data'` to say whether `config.validator.response` checks the whole event or its `data`.
|
|
14
16
|
|
|
15
17
|
The query plugins (`@kubb/plugin-react-query`, `@kubb/plugin-vue-query`, `@kubb/plugin-swr`) and `@kubb/plugin-mcp` call the generated functions with `throwOnError: true` and read `data` off the result. The query plugins also import two more types from your module:
|
|
16
18
|
|
|
@@ -21,6 +23,23 @@ The query plugins (`@kubb/plugin-react-query`, `@kubb/plugin-vue-query`, `@kubb/
|
|
|
21
23
|
|
|
22
24
|
With a relative `importPath`, the hooks resolve it from the generated operation file, so one path serves both.
|
|
23
25
|
|
|
26
|
+
## Build on the shared runtime
|
|
27
|
+
|
|
28
|
+
To reuse the runtime of the fetch and axios clients instead of writing your own request pipeline:
|
|
29
|
+
|
|
30
|
+
1. Copy `core.ts`, `mediaTypes.ts`, `serializers.ts`, `standardSchema.ts`, `sse.ts`, and the `mediaTypes/` and `serializers/` files your spec needs from `@kubb/plugin-client/templates` next to your `client.ts`.
|
|
31
|
+
2. In `client.ts`, export the three types the core imports, listed below.
|
|
32
|
+
3. Write a `Transport` function that sends a `ResolvedRequest` and returns a web `Response`. If your library does not return one, wrap its bytes in `new Response(bytes, { status, headers })`.
|
|
33
|
+
4. Export `createLazyClient((config) => createClientCore({ defaultTransport, ...config }))` as `client`. Register the form and stream media types your spec needs with `client.setConfig({ mediaTypes: { 'multipart/form-data': multipartFormData, ...streamMediaTypes } })`, and add `export * from './core.ts'` and `export * from './sse.ts'`. The core has no serializers of its own, so also set `serializer: { query: serializeQuery, path: serializePath }` from `serializers/query.ts` and `serializers/path.ts`, or their styled versions, and `header` and `cookie` for a spec with such parameters.
|
|
34
|
+
|
|
35
|
+
| Export | Purpose | On fetch | On axios |
|
|
36
|
+
| ---------------- | ------------------------------------------------------- | ------------- | -------------------- |
|
|
37
|
+
| `NativeRequest` | The request object on a result and a `ResponseError`. | `Request` | `AxiosRequestConfig` |
|
|
38
|
+
| `NativeResponse` | The response object on a result and a `ResponseError`. | `Response` | `AxiosResponse` |
|
|
39
|
+
| `NativeOptions` | The `options` a call passes to your HTTP library as is. | `RequestInit` | `AxiosRequestConfig` |
|
|
40
|
+
|
|
41
|
+
[`@kubb/plugin-fetch/templates/client.ts`](https://github.com/kubb-labs/plugins/blob/main/packages/plugin-fetch/templates/client.ts) is a complete example.
|
|
42
|
+
|
|
24
43
|
## What a generated function looks like
|
|
25
44
|
|
|
26
45
|
```typescript
|
|
@@ -31,27 +50,100 @@ import { client } from '../../../client'
|
|
|
31
50
|
export function getPetById<ThrowOnError extends boolean = true>(
|
|
32
51
|
options: Options<GetPetByIdOptions, ThrowOnError>,
|
|
33
52
|
): Promise<RequestResult<GetPetByIdResponses, ThrowOnError>> {
|
|
34
|
-
const { client:
|
|
53
|
+
const { client: apiClient = client, ...config } = options
|
|
35
54
|
|
|
36
|
-
return
|
|
55
|
+
return apiClient<GetPetByIdResponses, ThrowOnError>({
|
|
37
56
|
method: 'GET',
|
|
38
57
|
url: '/pet/{petId}',
|
|
39
58
|
security: [{ type: 'apiKey', name: 'api_key', in: 'header' }, { type: 'oauth2' }],
|
|
40
59
|
...config,
|
|
41
60
|
throwOnError: config.throwOnError ?? true,
|
|
42
|
-
})
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Your `client` receives `method`, the `url` template with `{param}` placeholders, `security`, the grouped `path`, `query`, `headers`, `cookies`, and `body`, and `throwOnError`. It returns a promise. `cookies` holds name and value pairs, which your client joins into a `Cookie` header, as the example below does. A `security` entry is one scheme, or an array of schemes to send together.
|
|
66
|
+
|
|
67
|
+
Read the body by the response `Content-Type`. An operation with only binary success content also passes `parseAs: 'blob'`.
|
|
68
|
+
|
|
69
|
+
A call can also pass `contentType`: `request` is the body's media type, and `response` is the media type to ask for in `Accept`. A streamed operation always passes its media type, such as `contentType: { response: 'text/event-stream' }` or `contentType: { response: 'application/jsonl' }`. A call whose response offers `text/event-stream` next to another content type passes `text/event-stream` too, unless the caller picks another one. `RequestConfig` needs the field.
|
|
70
|
+
|
|
71
|
+
## Serialize parameters
|
|
72
|
+
|
|
73
|
+
When the spec sets `style`, `explode`, `allowReserved`, or `contentType` on a parameter, the generated call also passes `styles`, grouped by location and keyed by the parameter name:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
styles: {
|
|
77
|
+
path: { petId: { style: 'matrix', explode: true } },
|
|
78
|
+
query: { tags: { style: 'pipeDelimited', explode: false } },
|
|
43
79
|
}
|
|
44
80
|
```
|
|
45
81
|
|
|
46
|
-
|
|
82
|
+
A parameter without an entry uses the default for its location. The shared runtime's `serializers.ts` applies these styles. A client you write from scratch declares `styles` on `RequestConfig` and applies the styles itself, or ignores them when the spec only uses the defaults.
|
|
83
|
+
|
|
84
|
+
### Query
|
|
85
|
+
|
|
86
|
+
Query parameters default to the `form` style. Arrays explode into repeated keys unless the spec
|
|
87
|
+
says otherwise, and `spaceDelimited`, `pipeDelimited`, and `deepObject` change how arrays and
|
|
88
|
+
objects collapse.
|
|
89
|
+
|
|
90
|
+
| Style | `explode` | Input | Result |
|
|
91
|
+
| ---------------- | --------- | ------------------- | ---------------- |
|
|
92
|
+
| `form` (default) | `true` | `{ id: [3, 4, 5] }` | `id=3&id=4&id=5` |
|
|
93
|
+
| `form` | `false` | `{ id: [3, 4, 5] }` | `id=3,4,5` |
|
|
94
|
+
| `spaceDelimited` | `false` | `{ id: [3, 4, 5] }` | `id=3%204%205` |
|
|
95
|
+
| `pipeDelimited` | `false` | `{ id: [3, 4, 5] }` | `id=3\|4\|5` |
|
|
96
|
+
| `deepObject` | `n/a` | `{ a: { b: 1 } }` | `a%5Bb%5D=1` |
|
|
97
|
+
|
|
98
|
+
With `explode: true`, `spaceDelimited` and `pipeDelimited` fall back to repeated keys like `form`,
|
|
99
|
+
so the delimiter only shows with `explode: false`.
|
|
100
|
+
|
|
101
|
+
### Path
|
|
47
102
|
|
|
48
|
-
|
|
103
|
+
Path parameters default to the `simple` style, which emits the bare value. `label` prefixes a
|
|
104
|
+
`.` and `matrix` prefixes a `;name=` segment. The results below are the serialized segment for a
|
|
105
|
+
parameter named `id`.
|
|
106
|
+
|
|
107
|
+
| Style | `explode` | Input | Result |
|
|
108
|
+
| ------------------ | --------- | ---------------- | ----------------- |
|
|
109
|
+
| `simple` (default) | `false` | `[3, 4, 5]` | `3,4,5` |
|
|
110
|
+
| `label` | `true` | `[3, 4, 5]` | `.3.4.5` |
|
|
111
|
+
| `matrix` | `true` | `[3, 4, 5]` | `;id=3;id=4;id=5` |
|
|
112
|
+
| `simple` | `false` | `{ x: 1, y: 2 }` | `x,1,y,2` |
|
|
113
|
+
|
|
114
|
+
### Header and cookie
|
|
115
|
+
|
|
116
|
+
Header parameters use the `simple` style and cookie parameters use the `form` style, so the metadata
|
|
117
|
+
for these locations carries only `explode`, plus `style: 'cookie'` for a cookie parameter with the
|
|
118
|
+
OpenAPI 3.2 `cookie` style. Header values are sent as-is. Cookie values are URL-encoded into a single
|
|
119
|
+
`Cookie` header. The `cookie` style sends them unencoded, as RFC 6265 describes, defaults `explode` to
|
|
120
|
+
`true`, and throws on a value with a character such as `;` or a space, or on an exploded object key that is not a valid
|
|
121
|
+
cookie name, such as `a=b`.
|
|
122
|
+
|
|
123
|
+
| Location | `explode` | Input | Result |
|
|
124
|
+
| ---------------------- | --------- | --------------------------------- | ------------------------- |
|
|
125
|
+
| header | `false` | `[3, 4]` | `X-Ids: 3,4` |
|
|
126
|
+
| header | `true` | `{ role: 'admin' }` | `X-Filter: role=admin` |
|
|
127
|
+
| cookie | `false` | `{ session: 'abc', ids: [1, 2] }` | `session=abc; ids=1,2` |
|
|
128
|
+
| cookie | `true` | `{ ids: [1, 2] }` | `ids=1; ids=2` |
|
|
129
|
+
| cookie, `cookie` style | default | `{ sid: 'a%20b', ids: [1, 2] }` | `sid=a%20b; ids=1; ids=2` |
|
|
49
130
|
|
|
50
131
|
## A minimal client
|
|
51
132
|
|
|
52
133
|
The client below uses `fetch`. Replace the transport with the HTTP library you use.
|
|
53
134
|
|
|
54
135
|
```typescript [src/client.ts]
|
|
136
|
+
export type Auth = {
|
|
137
|
+
type: string
|
|
138
|
+
scheme?: string
|
|
139
|
+
name?: string
|
|
140
|
+
in?: string
|
|
141
|
+
scopes?: Array<string>
|
|
142
|
+
flows?: Record<string, Record<string, string>>
|
|
143
|
+
metadataUrl?: string
|
|
144
|
+
url?: string
|
|
145
|
+
}
|
|
146
|
+
|
|
55
147
|
export type RequestConfig = {
|
|
56
148
|
method: 'GET' | 'PUT' | 'PATCH' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD'
|
|
57
149
|
url: string
|
|
@@ -59,15 +151,18 @@ export type RequestConfig = {
|
|
|
59
151
|
headers?: object
|
|
60
152
|
path?: object
|
|
61
153
|
query?: object
|
|
154
|
+
cookies?: object
|
|
62
155
|
body?: unknown
|
|
63
156
|
signal?: AbortSignal
|
|
64
|
-
|
|
157
|
+
parseAs?: 'json' | 'text' | 'blob' | 'arrayBuffer' | 'stream'
|
|
158
|
+
contentType?: string | { request?: string; response?: string }
|
|
65
159
|
throwOnError?: boolean
|
|
66
|
-
security?: Array<
|
|
160
|
+
security?: Array<Auth | Array<Auth>>
|
|
161
|
+
styles?: object
|
|
67
162
|
client?: typeof client
|
|
68
163
|
}
|
|
69
164
|
|
|
70
|
-
type DataShape = { body?: unknown; headers?: unknown; path?: unknown; query?: unknown }
|
|
165
|
+
type DataShape = { body?: unknown; cookies?: unknown; headers?: unknown; path?: unknown; query?: unknown }
|
|
71
166
|
|
|
72
167
|
export type Options<TData extends DataShape, ThrowOnError extends boolean = true> = Omit<RequestConfig, keyof DataShape | 'url' | 'method'> &
|
|
73
168
|
TData & { throwOnError?: ThrowOnError }
|
|
@@ -82,13 +177,20 @@ export type ResponseErrorConfig<TError = unknown> = Error & { body?: TError }
|
|
|
82
177
|
// Set this to your API host. `new URL()` needs an absolute URL.
|
|
83
178
|
export const settings = { baseURL: 'https://petstore3.swagger.io/api/v3' }
|
|
84
179
|
|
|
85
|
-
|
|
180
|
+
async function send(config: RequestConfig) {
|
|
86
181
|
const path: Record<string, unknown> = { ...config.path }
|
|
87
182
|
const url = new URL((config.baseURL ?? settings.baseURL) + config.url.replace(/\{(\w+)\}/g, (_, key) => encodeURIComponent(String(path[key]))))
|
|
183
|
+
const cookie = Object.entries(config.cookies ?? {})
|
|
184
|
+
.filter(([, value]) => value != null)
|
|
185
|
+
.map(([name, value]) => `${name}=${encodeURIComponent(String(value))}`)
|
|
186
|
+
.join('; ')
|
|
88
187
|
|
|
89
188
|
const response = await fetch(url, {
|
|
90
189
|
method: config.method,
|
|
91
|
-
headers:
|
|
190
|
+
headers: {
|
|
191
|
+
...(config.headers as Record<string, string>),
|
|
192
|
+
...(cookie ? { Cookie: cookie } : {}),
|
|
193
|
+
},
|
|
92
194
|
body: config.body === undefined ? undefined : JSON.stringify(config.body),
|
|
93
195
|
signal: config.signal,
|
|
94
196
|
})
|
|
@@ -98,6 +200,10 @@ export async function client(config: RequestConfig) {
|
|
|
98
200
|
if (config.throwOnError ?? true) throw new Error(`Request failed with status ${response.status}`)
|
|
99
201
|
return { data: undefined, error: body ?? { status: response.status }, response }
|
|
100
202
|
}
|
|
203
|
+
|
|
204
|
+
// Generated functions pass their responses type and `ThrowOnError`.
|
|
205
|
+
export const client = <TResponses, ThrowOnError extends boolean = true>(config: RequestConfig) =>
|
|
206
|
+
send(config) as Promise<RequestResult<TResponses, ThrowOnError>>
|
|
101
207
|
```
|
|
102
208
|
|
|
103
209
|
The [example project](https://github.com/kubb-labs/plugins/tree/main/examples/client) has a fuller version with a base URL setter, query handling, and an async auth hook. The types in this sketch are minimal. Add status-aware response types when you need them.
|
package/docs/options.md
CHANGED
|
@@ -55,8 +55,8 @@ The setting applies to the whole plugin, so you cannot set it in `override`.
|
|
|
55
55
|
Shape of the value a generated operation resolves to.
|
|
56
56
|
|
|
57
57
|
- `'plain'`: returns what your `client` returns.
|
|
58
|
-
- `'full'`: wraps the call in `withUnwrap`, so the caller can `await` it or call `.unwrap()`. Your module must export `withUnwrap` and the `Unwrappable` type.
|
|
59
|
-
- `'data'`: passes the call through `unwrapResult` and resolves to the body. Your module must export `unwrapResult` and the `UnwrappedResult` type.
|
|
58
|
+
- `'full'`: wraps the call in `withUnwrap`, so the caller can `await` it or call `.unwrap()`. Your module must export `withUnwrap`, `resolveThrowOnError`, and the `Unwrappable` type.
|
|
59
|
+
- `'data'`: passes the call through `unwrapResult` and resolves to the body. Your module must export `unwrapResult`, `resolveThrowOnError`, and the `UnwrappedResult` type.
|
|
60
60
|
|
|
61
61
|
`@kubb/plugin-client/templates/core.ts` exports all of these, so your module can re-export them from there.
|
|
62
62
|
|
package/docs/overview.md
CHANGED
|
@@ -6,7 +6,7 @@ Each function takes grouped `path`, `query`, `headers`, and `body` parameters an
|
|
|
6
6
|
|
|
7
7
|
## Setup
|
|
8
8
|
|
|
9
|
-
- Set the required `importPath` option to your client module. The module must export `client` and the `Options` and `RequestResult` types. When the spec has server-sent event operations,
|
|
9
|
+
- Set the required `importPath` option to your client module. The module must export a generic `client` and the `Options` and `RequestResult` types. When the spec has server-sent event or JSON Lines operations, `client` also needs a `stream` method, and the module exports the `StreamResult`, `SuccessOf`, `TypedEvent`, and `ServerSentEvent` types.
|
|
10
10
|
- Add `@kubb/plugin-ts` for the operation types, or `@kubb/plugin-zod` with `inferred: true`.
|
|
11
11
|
- Add `@kubb/plugin-zod` when you set `validator`, and install `zod` v4. Generation fails without the plugin.
|
|
12
12
|
|
package/kubb-extension.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubb/plugin-client",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "Generate typed API operations against your own client module.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"api-client",
|
|
@@ -61,14 +61,15 @@
|
|
|
61
61
|
"registry": "https://registry.npmjs.org/"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@kubb/kit": "^5.
|
|
65
|
-
"@kubb/plugin-ts": "^5.
|
|
66
|
-
"@kubb/plugin-zod": "^5.
|
|
67
|
-
"@kubb/renderer-jsx": "^5.
|
|
64
|
+
"@kubb/kit": "^5.18.6",
|
|
65
|
+
"@kubb/plugin-ts": "^5.5.0",
|
|
66
|
+
"@kubb/plugin-zod": "^5.9.0",
|
|
67
|
+
"@kubb/renderer-jsx": "^5.18.6"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@internals/client": "0.0.0",
|
|
71
71
|
"@internals/shared": "0.0.0",
|
|
72
|
+
"@internals/utils": "0.0.0",
|
|
72
73
|
"typescript": "^7.0.2"
|
|
73
74
|
},
|
|
74
75
|
"peerDependencies": {},
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { toLiteral } from '@internals/utils'
|
|
2
|
+
import { getEventStream, hasTextEventData } from '@internals/shared'
|
|
3
|
+
|
|
4
|
+
import { ast, text } from '@kubb/kit'
|
|
5
|
+
import type { ReturnTypeOption } from '../types.ts'
|
|
6
|
+
import { buildContentType } from './contentType.ts'
|
|
7
|
+
import { buildSecurityMetadata, type SecurityRequirement } from './security.ts'
|
|
8
|
+
import { buildStyles } from './styles.ts'
|
|
9
|
+
import { buildValidatorLiteral, type ValidatorHooks } from './validator.ts'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* `parseAs: 'blob'` when every success content type is binary, so a download is a `Blob` whatever its media type, and
|
|
13
|
+
* also when the server sends no `Content-Type`. Any other body is decoded by its media type at runtime.
|
|
14
|
+
*/
|
|
15
|
+
function buildParseAsLiteral(content: Array<ast.ContentNode>): string | null {
|
|
16
|
+
return content.length > 0 && content.every((entry) => entry.schema?.type === 'blob') ? `parseAs: 'blob'` : null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Builds the call config literal a generated operation forwards to the client, shared by the standalone
|
|
21
|
+
* functions and the SDK methods. The operation's own `baseURL` comes before the `...config` spread, so a per-call one wins. The
|
|
22
|
+
* `validator` and `throwOnError` entries come after it, so a per-call `validator` is merged per key with the generated one instead
|
|
23
|
+
* of replacing it. `styles` is merged the same way, per parameter, unless `returnType` is `'plain'`.
|
|
24
|
+
*
|
|
25
|
+
* `mergeContentType` is `true` when the caller must destructure `contentType` from `options`, and
|
|
26
|
+
* `eventStream` is `true` when the operation streams `text/event-stream` or JSON Lines. A Server-Sent Events call
|
|
27
|
+
* carries its `eventShape`, which tells the runtime what the `response` validator checks on each event, and `rawData` when
|
|
28
|
+
* the document types each event's `data` as a string, so the runtime keeps it as text.
|
|
29
|
+
*/
|
|
30
|
+
export function buildCallConfig({
|
|
31
|
+
node,
|
|
32
|
+
validatorHooks,
|
|
33
|
+
security,
|
|
34
|
+
baseURL,
|
|
35
|
+
throwOnErrorDefault,
|
|
36
|
+
returnType,
|
|
37
|
+
}: {
|
|
38
|
+
node: ast.HttpOperationNode
|
|
39
|
+
validatorHooks: ValidatorHooks | null
|
|
40
|
+
security?: Array<SecurityRequirement>
|
|
41
|
+
baseURL?: string
|
|
42
|
+
throwOnErrorDefault: boolean
|
|
43
|
+
returnType: ReturnTypeOption
|
|
44
|
+
}): { callConfig: string; mergeContentType: boolean; eventStream: boolean } {
|
|
45
|
+
const stream = getEventStream(node)
|
|
46
|
+
const content = ast.http.responses.findPrimary(node)?.content ?? []
|
|
47
|
+
const securityLiteral = buildSecurityMetadata({ security })
|
|
48
|
+
// A `'plain'` client is the user's own, which may type `styles` as `unknown`, so it keeps the generated styles as a default.
|
|
49
|
+
const ownRuntime = returnType !== 'plain'
|
|
50
|
+
const stylesLiteral = buildStyles({ node, merge: ownRuntime })
|
|
51
|
+
const styles = stylesLiteral ? `styles: ${stylesLiteral}` : null
|
|
52
|
+
const validatorLiteral = buildValidatorLiteral(validatorHooks, ['request', 'path', 'query', 'headers', 'cookies', 'response', 'error'])
|
|
53
|
+
const { literal: contentTypeLiteral, merge: mergeContentType } = buildContentType({ node })
|
|
54
|
+
// The runtime's call types `throwOnError` as the operation's `ThrowOnError`, which the shared helper keeps. A `'plain'`
|
|
55
|
+
// client takes a boolean.
|
|
56
|
+
const throwOnError = ownRuntime
|
|
57
|
+
? `throwOnError: resolveThrowOnError(config.throwOnError, ${throwOnErrorDefault})`
|
|
58
|
+
: `throwOnError: config.throwOnError ?? ${throwOnErrorDefault}`
|
|
59
|
+
|
|
60
|
+
const rawData = hasTextEventData(node)
|
|
61
|
+
const callConfig = `{ ${[
|
|
62
|
+
`method: ${text.quote(node.method.toUpperCase())}`,
|
|
63
|
+
`url: ${text.quote(node.path)}`,
|
|
64
|
+
baseURL ? `baseURL: ${JSON.stringify(baseURL)}` : null,
|
|
65
|
+
securityLiteral ? `security: ${securityLiteral}` : null,
|
|
66
|
+
ownRuntime ? null : styles,
|
|
67
|
+
contentTypeLiteral,
|
|
68
|
+
buildParseAsLiteral(content),
|
|
69
|
+
stream === 'event' || stream === 'data' ? `eventShape: '${stream}'` : null,
|
|
70
|
+
rawData ? `rawData: ${toLiteral(rawData)}` : null,
|
|
71
|
+
'...config',
|
|
72
|
+
ownRuntime ? styles : null,
|
|
73
|
+
validatorLiteral,
|
|
74
|
+
throwOnError,
|
|
75
|
+
]
|
|
76
|
+
.filter(Boolean)
|
|
77
|
+
.join(', ')} }`
|
|
78
|
+
|
|
79
|
+
return { callConfig, mergeContentType, eventStream: stream !== null }
|
|
80
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { getContentTypeInfo, getResponseContentTypeInfo, hasEventStreamContent, isEventStream } from '@internals/shared'
|
|
2
|
+
import { ast, text } from '@kubb/kit'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Builds the `contentType` entry baked into the call config. It holds the request default when the operation has a body
|
|
6
|
+
* and `response` set to the streamed media type when it streams items, or `text/event-stream` when it offers events next to
|
|
7
|
+
* another type, and is `null` with neither.
|
|
8
|
+
*
|
|
9
|
+
* `merge` is `true` when the generated function must destructure `contentType` from `options` and merge it over the
|
|
10
|
+
* baked defaults, because the caller can also pick a response content type or a stream must keep its `Accept`.
|
|
11
|
+
*/
|
|
12
|
+
export function buildContentType({ node }: { node: ast.HttpOperationNode }): { literal: string | null; merge: boolean } {
|
|
13
|
+
const { defaultContentType } = getContentTypeInfo(node)
|
|
14
|
+
const hasRequestBody = Boolean(ast.http.requestBody.getSchema(node))
|
|
15
|
+
// The first declared type is the default; the caller overrides it on `contentType`.
|
|
16
|
+
const requestContentType = hasRequestBody ? defaultContentType : null
|
|
17
|
+
// A stream asks for its own media type, and a response that offers events next to JSON asks for the events, so a client whose
|
|
18
|
+
// default `Accept` prefers JSON, such as axios, gets the stream. The caller picks another type with `contentType.response`.
|
|
19
|
+
const streamType = isEventStream(node) ? ast.mediaType.getBase(getResponseContentTypeInfo(node).defaultContentType) : null
|
|
20
|
+
const response = streamType || hasEventStreamContent(node) ? `response: ${text.quote(streamType ?? 'text/event-stream')}` : null
|
|
21
|
+
|
|
22
|
+
if (!requestContentType) return { literal: response && `contentType: { ${response} }`, merge: false }
|
|
23
|
+
|
|
24
|
+
// When the caller can also pick a response content type, or a stream must keep its `Accept`, a partial `contentType` would
|
|
25
|
+
// replace the baked default through `...config`, so merge the caller's choice over it instead.
|
|
26
|
+
const merge = Boolean(response) || getResponseContentTypeInfo(node).isMultipleContentTypes
|
|
27
|
+
const defaults = [`request: ${text.quote(requestContentType)}`, response].filter(Boolean).join(', ')
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
merge,
|
|
31
|
+
literal: merge
|
|
32
|
+
? `contentType: { ${defaults}, ...(typeof contentType === 'string' ? { request: contentType } : contentType) }`
|
|
33
|
+
: `contentType: { ${defaults} }`,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ast } from '@kubb/kit'
|
|
2
|
+
import { buildResponseHeadersType, buildResultTypeRef, type OperationTypeNames } from '@internals/client'
|
|
3
|
+
import type { ReturnTypeOption } from '../types.ts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Builds the `RequestResult` type arguments of one operation: responses, `ThrowOnError` and the response headers, when a response declares any.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* `buildRequestResultGenerics({ node, types }) // "LoginUserResponses, ThrowOnError, { '200': LoginUserStatus200Headers }"`
|
|
10
|
+
*/
|
|
11
|
+
export function buildRequestResultGenerics({ node, types }: { node: ast.OperationNode; types: OperationTypeNames }): string {
|
|
12
|
+
return [types.response.responses(node), 'ThrowOnError', buildResponseHeadersType({ node, types })].filter(Boolean).join(', ')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Builds the full return type an operation's function signature uses: `Unwrappable<RequestResult>`
|
|
17
|
+
* for the default `returnType: 'full'`, already a promise so no further wrapping is needed, a plain
|
|
18
|
+
* `Promise<RequestResult>` for the internal `'plain'` shape, or a
|
|
19
|
+
* `Promise` of the runtime's `UnwrappedResult` when `returnType: 'data'` narrows a resolved call
|
|
20
|
+
* down to the bare success body.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* `buildResultType({ node, types, returnType: 'data' }) // 'Promise<UnwrappedResult<AddPetResponses, ThrowOnError>>'`
|
|
24
|
+
*/
|
|
25
|
+
export function buildResultType({ node, types, returnType }: { node: ast.OperationNode; types: OperationTypeNames; returnType: ReturnTypeOption }): string {
|
|
26
|
+
const name = returnType === 'data' ? 'UnwrappedResult' : 'RequestResult'
|
|
27
|
+
const result = buildResultTypeRef(name, types.response.responses(node), buildResponseHeadersType({ node, types }), ['ThrowOnError'])
|
|
28
|
+
return returnType === 'full' ? `Unwrappable<${result}>` : `Promise<${result}>`
|
|
29
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ast } from '@kubb/kit'
|
|
2
|
+
import type { OperationTypeNames } from '@internals/client'
|
|
3
|
+
import type { ReturnTypeOption } from '../types.ts'
|
|
4
|
+
import { buildCallConfig } from './callConfig.ts'
|
|
5
|
+
import { buildEventStreamReturn, buildReturnStatement } from './returnStatement.ts'
|
|
6
|
+
import type { SecurityRequirement } from './security.ts'
|
|
7
|
+
import { buildGroupedOptionsSignature } from './signature.ts'
|
|
8
|
+
import type { ValidatorHooks } from './validator.ts'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The pieces of a generated operation: its generics, `options` parameter, return type, return statement, and whether the
|
|
12
|
+
* body destructures `contentType` from `options`. Shared by the standalone functions and the SDK methods, so a
|
|
13
|
+
* streamed operation is wired to `apiClient.stream` in one place.
|
|
14
|
+
*/
|
|
15
|
+
export function buildOperationBody({
|
|
16
|
+
node,
|
|
17
|
+
types,
|
|
18
|
+
validatorHooks,
|
|
19
|
+
security,
|
|
20
|
+
baseURL,
|
|
21
|
+
returnType,
|
|
22
|
+
throwOnErrorDefault,
|
|
23
|
+
}: {
|
|
24
|
+
node: ast.HttpOperationNode
|
|
25
|
+
types: OperationTypeNames
|
|
26
|
+
validatorHooks: ValidatorHooks | null
|
|
27
|
+
security?: Array<SecurityRequirement>
|
|
28
|
+
baseURL?: string
|
|
29
|
+
returnType: ReturnTypeOption
|
|
30
|
+
throwOnErrorDefault: boolean
|
|
31
|
+
}): { generics: Array<string>; paramsSignature: string; returnType: string; statement: string; mergeContentType: boolean } {
|
|
32
|
+
const { callConfig, mergeContentType, eventStream } = buildCallConfig({ node, validatorHooks, security, baseURL, throwOnErrorDefault, returnType })
|
|
33
|
+
const signature = buildGroupedOptionsSignature({ node, types, returnType, throwOnErrorDefault, eventStream })
|
|
34
|
+
const stream = eventStream ? buildEventStreamReturn({ node, types, callConfig, returnType, throwOnErrorDefault }) : null
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
generics: signature.generics,
|
|
38
|
+
paramsSignature: signature.paramsSignature,
|
|
39
|
+
returnType: stream?.returnType ?? signature.returnType,
|
|
40
|
+
statement: stream?.statement ?? buildReturnStatement({ node, types, callConfig, returnType, throwOnErrorDefault }),
|
|
41
|
+
mergeContentType,
|
|
42
|
+
}
|
|
43
|
+
}
|