@kubb/plugin-client 5.0.0-beta.22 → 5.0.0-beta.27
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/clients/axios.cjs +18 -3
- package/dist/clients/axios.cjs.map +1 -1
- package/dist/clients/axios.d.ts +8 -2
- package/dist/clients/axios.js +18 -3
- package/dist/clients/axios.js.map +1 -1
- package/dist/clients/fetch.cjs +16 -1
- package/dist/clients/fetch.cjs.map +1 -1
- package/dist/clients/fetch.d.ts +8 -2
- package/dist/clients/fetch.js +16 -1
- package/dist/clients/fetch.js.map +1 -1
- package/dist/index.cjs +171 -94
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +125 -65
- package/dist/index.js +171 -94
- package/dist/index.js.map +1 -1
- package/dist/templates/clients/axios.source.cjs +1 -1
- package/dist/templates/clients/axios.source.js +1 -1
- package/dist/templates/clients/fetch.source.cjs +1 -1
- package/dist/templates/clients/fetch.source.js +1 -1
- package/extension.yaml +774 -260
- package/package.json +6 -6
- package/src/clients/axios.ts +28 -5
- package/src/clients/fetch.ts +25 -2
- package/src/components/ClassClient.tsx +3 -3
- package/src/components/Client.tsx +27 -19
- package/src/components/StaticClassClient.tsx +3 -3
- package/src/components/Url.tsx +1 -1
- package/src/functionParams.ts +8 -8
- package/src/generators/classClientGenerator.tsx +16 -11
- package/src/generators/clientGenerator.tsx +16 -8
- package/src/generators/groupedClientGenerator.tsx +9 -3
- package/src/generators/operationsGenerator.tsx +7 -1
- package/src/generators/staticClassClientGenerator.tsx +16 -10
- package/src/plugin.ts +24 -11
- package/src/resolvers/resolverClient.ts +16 -9
- package/src/types.ts +66 -53
- package/src/utils.ts +17 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubb/plugin-client",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.27",
|
|
4
4
|
"description": "Generate type-safe HTTP clients from your OpenAPI specification. Supports Axios, Fetch, and custom client adapters with full TypeScript inference and zero boilerplate.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"api-client",
|
|
@@ -87,10 +87,10 @@
|
|
|
87
87
|
"registry": "https://registry.npmjs.org/"
|
|
88
88
|
},
|
|
89
89
|
"dependencies": {
|
|
90
|
-
"@kubb/core": "5.0.0-beta.
|
|
91
|
-
"@kubb/renderer-jsx": "5.0.0-beta.
|
|
92
|
-
"@kubb/plugin-ts": "5.0.0-beta.
|
|
93
|
-
"@kubb/plugin-zod": "5.0.0-beta.
|
|
90
|
+
"@kubb/core": "5.0.0-beta.27",
|
|
91
|
+
"@kubb/renderer-jsx": "5.0.0-beta.27",
|
|
92
|
+
"@kubb/plugin-ts": "5.0.0-beta.27",
|
|
93
|
+
"@kubb/plugin-zod": "5.0.0-beta.27"
|
|
94
94
|
},
|
|
95
95
|
"devDependencies": {
|
|
96
96
|
"axios": "^1.16.1",
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
"@internals/utils": "0.0.0"
|
|
99
99
|
},
|
|
100
100
|
"peerDependencies": {
|
|
101
|
-
"@kubb/renderer-jsx": "5.0.0-beta.
|
|
101
|
+
"@kubb/renderer-jsx": "5.0.0-beta.27",
|
|
102
102
|
"axios": "^1.7.2"
|
|
103
103
|
},
|
|
104
104
|
"peerDependenciesMeta": {
|
package/src/clients/axios.ts
CHANGED
|
@@ -4,6 +4,13 @@ import axios from 'axios'
|
|
|
4
4
|
declare const AXIOS_BASE: string
|
|
5
5
|
declare const AXIOS_HEADERS: string
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Header values may be objects (e.g. JSON-encoded headers like `X-Filter` in the Linode API).
|
|
9
|
+
* Non-string values are JSON-serialized before the request is sent.
|
|
10
|
+
*/
|
|
11
|
+
export type HeaderValue = string | number | boolean | null | undefined | object
|
|
12
|
+
export type HeadersInit = Array<[string, HeaderValue]> | Record<string, HeaderValue>
|
|
13
|
+
|
|
7
14
|
/**
|
|
8
15
|
* Subset of AxiosRequestConfig
|
|
9
16
|
*/
|
|
@@ -16,7 +23,7 @@ export type RequestConfig<TData = unknown> = {
|
|
|
16
23
|
responseType?: 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'
|
|
17
24
|
signal?: AbortSignal
|
|
18
25
|
validateStatus?: (status: number) => boolean
|
|
19
|
-
headers?:
|
|
26
|
+
headers?: HeadersInit
|
|
20
27
|
paramsSerializer?: AxiosRequestConfig['paramsSerializer']
|
|
21
28
|
contentType?: string
|
|
22
29
|
}
|
|
@@ -56,14 +63,30 @@ export const mergeConfig = <T extends RequestConfig>(...configs: Array<Partial<T
|
|
|
56
63
|
...merged,
|
|
57
64
|
...config,
|
|
58
65
|
headers: {
|
|
59
|
-
...merged.headers,
|
|
60
|
-
...config.headers,
|
|
66
|
+
...(Array.isArray(merged.headers) ? Object.fromEntries(merged.headers) : merged.headers),
|
|
67
|
+
...(Array.isArray(config.headers) ? Object.fromEntries(config.headers) : config.headers),
|
|
61
68
|
},
|
|
62
69
|
}
|
|
63
70
|
}, {})
|
|
64
71
|
}
|
|
65
72
|
|
|
66
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Serializes header values into the string form axios ultimately puts on the wire.
|
|
75
|
+
* Objects (including arrays) are JSON-stringified so spec-defined object headers like `X-Filter`
|
|
76
|
+
* are sent in their canonical JSON-string form rather than `[object Object]`.
|
|
77
|
+
*/
|
|
78
|
+
function serializeHeaders(headers: HeadersInit | undefined): Record<string, string> {
|
|
79
|
+
if (!headers) return {}
|
|
80
|
+
const entries = Array.isArray(headers) ? headers : Object.entries(headers)
|
|
81
|
+
const result: Record<string, string> = {}
|
|
82
|
+
for (const [key, value] of entries) {
|
|
83
|
+
if (value === undefined || value === null) continue
|
|
84
|
+
result[key] = typeof value === 'string' ? value : typeof value === 'object' ? JSON.stringify(value) : String(value)
|
|
85
|
+
}
|
|
86
|
+
return result
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const axiosInstance = axios.create(getConfig() as AxiosRequestConfig)
|
|
67
90
|
|
|
68
91
|
export const client = async <TResponseData, TError = unknown, TRequestData = unknown>(
|
|
69
92
|
config: RequestConfig<TRequestData>,
|
|
@@ -76,7 +99,7 @@ export const client = async <TResponseData, TError = unknown, TRequestData = unk
|
|
|
76
99
|
...axiosConfig,
|
|
77
100
|
headers: {
|
|
78
101
|
...(contentType && contentType !== 'multipart/form-data' ? { 'Content-Type': contentType } : {}),
|
|
79
|
-
...headers,
|
|
102
|
+
...serializeHeaders(headers),
|
|
80
103
|
},
|
|
81
104
|
})
|
|
82
105
|
.catch((e: AxiosError<TError>) => {
|
package/src/clients/fetch.ts
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export type RequestCredentials = 'omit' | 'same-origin' | 'include'
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Header values may be objects (e.g. JSON-encoded filter headers like `X-Filter`).
|
|
8
|
+
* Non-string values are JSON-serialized before the request is sent.
|
|
9
|
+
*/
|
|
10
|
+
export type HeaderValue = string | number | boolean | null | undefined | object
|
|
11
|
+
export type HeadersInit = Array<[string, HeaderValue]> | Record<string, HeaderValue>
|
|
12
|
+
|
|
6
13
|
/**
|
|
7
14
|
* Subset of FetchRequestConfig
|
|
8
15
|
*/
|
|
@@ -14,7 +21,7 @@ export type RequestConfig<TData = unknown> = {
|
|
|
14
21
|
data?: TData | FormData
|
|
15
22
|
responseType?: 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'
|
|
16
23
|
signal?: AbortSignal
|
|
17
|
-
headers?:
|
|
24
|
+
headers?: HeadersInit
|
|
18
25
|
credentials?: RequestCredentials
|
|
19
26
|
contentType?: string
|
|
20
27
|
}
|
|
@@ -51,6 +58,22 @@ export const mergeConfig = <T extends RequestConfig>(...configs: Array<Partial<T
|
|
|
51
58
|
}, {})
|
|
52
59
|
}
|
|
53
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Serializes header values into the string form `fetch` expects.
|
|
63
|
+
* Objects (including arrays) are JSON-stringified so spec-defined object
|
|
64
|
+
* headers like `X-Filter` are sent in their canonical JSON-string form.
|
|
65
|
+
*/
|
|
66
|
+
function serializeHeaders(headers: HeadersInit | undefined): Record<string, string> {
|
|
67
|
+
if (!headers) return {}
|
|
68
|
+
const entries = Array.isArray(headers) ? headers : Object.entries(headers)
|
|
69
|
+
const result: Record<string, string> = {}
|
|
70
|
+
for (const [key, value] of entries) {
|
|
71
|
+
if (value === undefined || value === null) continue
|
|
72
|
+
result[key] = typeof value === 'string' ? value : typeof value === 'object' ? JSON.stringify(value) : String(value)
|
|
73
|
+
}
|
|
74
|
+
return result
|
|
75
|
+
}
|
|
76
|
+
|
|
54
77
|
export type ResponseErrorConfig<TError = unknown> = TError
|
|
55
78
|
|
|
56
79
|
export type Client = <TResponseData, _TError = unknown, TRequestData = unknown>(
|
|
@@ -85,7 +108,7 @@ export const client = async <TResponseData, _TError = unknown, RequestData = unk
|
|
|
85
108
|
signal: config.signal,
|
|
86
109
|
headers: {
|
|
87
110
|
...(config.contentType && config.contentType !== 'multipart/form-data' ? { 'Content-Type': config.contentType } : {}),
|
|
88
|
-
...(
|
|
111
|
+
...serializeHeaders(config.headers),
|
|
89
112
|
},
|
|
90
113
|
})
|
|
91
114
|
|
|
@@ -14,7 +14,7 @@ type OperationData = {
|
|
|
14
14
|
node: ast.OperationNode
|
|
15
15
|
name: string
|
|
16
16
|
tsResolver: ResolverTs
|
|
17
|
-
zodResolver?: ResolverZod
|
|
17
|
+
zodResolver?: ResolverZod | null
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
type Props = {
|
|
@@ -35,7 +35,7 @@ type GenerateMethodProps = {
|
|
|
35
35
|
node: ast.OperationNode
|
|
36
36
|
name: string
|
|
37
37
|
tsResolver: ResolverTs
|
|
38
|
-
zodResolver?: ResolverZod
|
|
38
|
+
zodResolver?: ResolverZod | null
|
|
39
39
|
baseURL: string | null | undefined
|
|
40
40
|
dataReturnType: PluginClient['resolvedOptions']['dataReturnType']
|
|
41
41
|
parser: PluginClient['resolvedOptions']['parser'] | undefined
|
|
@@ -62,7 +62,7 @@ function generateMethod({
|
|
|
62
62
|
const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node)
|
|
63
63
|
const isFormData = !isMultipleContentTypes && contentType === 'multipart/form-data'
|
|
64
64
|
const { header: headerParams } = getOperationParameters(node)
|
|
65
|
-
const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]!) :
|
|
65
|
+
const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]!) : null
|
|
66
66
|
const headers = isMultipleContentTypes ? (headerParamsName ? ['...headers'] : []) : buildHeaders(contentType, !!headerParamsName)
|
|
67
67
|
const generics = buildGenerics(node, tsResolver)
|
|
68
68
|
const paramsNode = buildClientParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver, isConfigurable: true })
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
buildOperationComments,
|
|
3
|
+
buildParamsMapping,
|
|
4
|
+
buildRequestConfigType,
|
|
5
|
+
getContentTypeInfo,
|
|
6
|
+
getOperationParameters,
|
|
7
|
+
resolveSuccessNames,
|
|
8
|
+
} from '@internals/shared'
|
|
2
9
|
import { isValidVarName, URLPath } from '@internals/utils'
|
|
3
10
|
import { ast } from '@kubb/core'
|
|
4
11
|
import type { ResolverTs } from '@kubb/plugin-ts'
|
|
@@ -26,7 +33,7 @@ type Props = {
|
|
|
26
33
|
parser: PluginClient['resolvedOptions']['parser'] | undefined
|
|
27
34
|
node: ast.OperationNode
|
|
28
35
|
tsResolver: ResolverTs
|
|
29
|
-
zodResolver?: ResolverZod
|
|
36
|
+
zodResolver?: ResolverZod | null
|
|
30
37
|
children?: KubbReactNode
|
|
31
38
|
}
|
|
32
39
|
|
|
@@ -96,17 +103,18 @@ export function Client({
|
|
|
96
103
|
const { path: originalPathParams, query: originalQueryParams, header: originalHeaderParams } = getOperationParameters(node)
|
|
97
104
|
const { path: casedPathParams, query: casedQueryParams, header: casedHeaderParams } = getOperationParameters(node, { paramsCasing })
|
|
98
105
|
|
|
99
|
-
const pathParamsMapping = paramsCasing && !urlName ? buildParamsMapping(originalPathParams, casedPathParams) :
|
|
100
|
-
const queryParamsMapping = paramsCasing ? buildParamsMapping(originalQueryParams, casedQueryParams) :
|
|
101
|
-
const headerParamsMapping = paramsCasing ? buildParamsMapping(originalHeaderParams, casedHeaderParams) :
|
|
106
|
+
const pathParamsMapping = paramsCasing && !urlName ? buildParamsMapping(originalPathParams, casedPathParams) : null
|
|
107
|
+
const queryParamsMapping = paramsCasing ? buildParamsMapping(originalQueryParams, casedQueryParams) : null
|
|
108
|
+
const headerParamsMapping = paramsCasing ? buildParamsMapping(originalHeaderParams, casedHeaderParams) : null
|
|
102
109
|
|
|
103
|
-
const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) :
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
const
|
|
110
|
+
const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null
|
|
111
|
+
const successNames = resolveSuccessNames(node, tsResolver)
|
|
112
|
+
const responseName = successNames.length > 0 ? successNames.join(' | ') : tsResolver.resolveResponseName(node)
|
|
113
|
+
const queryParamsName = originalQueryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, originalQueryParams[0]!) : null
|
|
114
|
+
const headerParamsName = originalHeaderParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, originalHeaderParams[0]!) : null
|
|
107
115
|
|
|
108
|
-
const zodResponseName = zodResolver && parser === 'zod' ? zodResolver.resolveResponseName?.(node) :
|
|
109
|
-
const zodRequestName = zodResolver && parser === 'zod' && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) :
|
|
116
|
+
const zodResponseName = zodResolver && parser === 'zod' ? zodResolver.resolveResponseName?.(node) : null
|
|
117
|
+
const zodRequestName = zodResolver && parser === 'zod' && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null
|
|
110
118
|
|
|
111
119
|
const errorNames = node.responses
|
|
112
120
|
.filter((r) => {
|
|
@@ -116,8 +124,8 @@ export function Client({
|
|
|
116
124
|
.map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode))
|
|
117
125
|
|
|
118
126
|
const headers = [
|
|
119
|
-
!isMultipleContentTypes && contentType !== 'application/json' && contentType !== 'multipart/form-data' ? `'Content-Type': '${contentType}'` :
|
|
120
|
-
headerParamsName ? (headerParamsMapping ? '...mappedHeaders' : '...headers') :
|
|
127
|
+
!isMultipleContentTypes && contentType !== 'application/json' && contentType !== 'multipart/form-data' ? `'Content-Type': '${contentType}'` : null,
|
|
128
|
+
headerParamsName ? (headerParamsMapping ? '...mappedHeaders' : '...headers') : null,
|
|
121
129
|
].filter(Boolean)
|
|
122
130
|
|
|
123
131
|
const TError = `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(' | ') : 'Error'}>`
|
|
@@ -158,8 +166,8 @@ export function Client({
|
|
|
158
166
|
? {
|
|
159
167
|
value: `\`${baseURL}\``,
|
|
160
168
|
}
|
|
161
|
-
:
|
|
162
|
-
params: queryParamsName ? (queryParamsMapping ? { value: 'mappedParams' } : {}) :
|
|
169
|
+
: null,
|
|
170
|
+
params: queryParamsName ? (queryParamsMapping ? { value: 'mappedParams' } : {}) : null,
|
|
163
171
|
data: requestName
|
|
164
172
|
? {
|
|
165
173
|
value:
|
|
@@ -169,18 +177,18 @@ export function Client({
|
|
|
169
177
|
? 'formData as FormData'
|
|
170
178
|
: 'requestData',
|
|
171
179
|
}
|
|
172
|
-
:
|
|
173
|
-
contentType: isConfigurable && isMultipleContentTypes ? {} :
|
|
180
|
+
: null,
|
|
181
|
+
contentType: isConfigurable && isMultipleContentTypes ? {} : null,
|
|
174
182
|
requestConfig: isConfigurable
|
|
175
183
|
? {
|
|
176
184
|
mode: 'inlineSpread',
|
|
177
185
|
}
|
|
178
|
-
:
|
|
186
|
+
: null,
|
|
179
187
|
headers: headers.length
|
|
180
188
|
? {
|
|
181
189
|
value: isConfigurable ? `{ ${headers.join(', ')}, ...requestConfig.headers }` : `{ ${headers.join(', ')} }`,
|
|
182
190
|
}
|
|
183
|
-
:
|
|
191
|
+
: null,
|
|
184
192
|
},
|
|
185
193
|
},
|
|
186
194
|
})
|
|
@@ -14,7 +14,7 @@ type OperationData = {
|
|
|
14
14
|
node: ast.OperationNode
|
|
15
15
|
name: string
|
|
16
16
|
tsResolver: ResolverTs
|
|
17
|
-
zodResolver?: ResolverZod
|
|
17
|
+
zodResolver?: ResolverZod | null
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
type Props = {
|
|
@@ -35,7 +35,7 @@ type GenerateMethodProps = {
|
|
|
35
35
|
node: ast.OperationNode
|
|
36
36
|
name: string
|
|
37
37
|
tsResolver: ResolverTs
|
|
38
|
-
zodResolver?: ResolverZod
|
|
38
|
+
zodResolver?: ResolverZod | null
|
|
39
39
|
baseURL: string | null | undefined
|
|
40
40
|
dataReturnType: PluginClient['resolvedOptions']['dataReturnType']
|
|
41
41
|
parser: PluginClient['resolvedOptions']['parser'] | undefined
|
|
@@ -62,7 +62,7 @@ function generateMethod({
|
|
|
62
62
|
const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node)
|
|
63
63
|
const isFormData = !isMultipleContentTypes && contentType === 'multipart/form-data'
|
|
64
64
|
const { header: headerParams } = getOperationParameters(node)
|
|
65
|
-
const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]!) :
|
|
65
|
+
const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]!) : null
|
|
66
66
|
const headers = isMultipleContentTypes ? (headerParamsName ? ['...headers'] : []) : buildHeaders(contentType, !!headerParamsName)
|
|
67
67
|
const generics = buildGenerics(node, tsResolver)
|
|
68
68
|
const paramsNode = buildClientParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver, isConfigurable: true })
|
package/src/components/Url.tsx
CHANGED
|
@@ -70,7 +70,7 @@ export function Url({
|
|
|
70
70
|
|
|
71
71
|
const { path: originalPathParams } = getOperationParameters(node)
|
|
72
72
|
const { path: casedPathParams } = getOperationParameters(node, { paramsCasing })
|
|
73
|
-
const pathParamsMapping = paramsCasing ? buildParamsMapping(originalPathParams, casedPathParams) :
|
|
73
|
+
const pathParamsMapping = paramsCasing ? buildParamsMapping(originalPathParams, casedPathParams) : null
|
|
74
74
|
|
|
75
75
|
return (
|
|
76
76
|
<File.Source name={name} isExportable={isExportable} isIndexable={isIndexable}>
|
package/src/functionParams.ts
CHANGED
|
@@ -11,7 +11,7 @@ type ParamLeaf = {
|
|
|
11
11
|
|
|
12
12
|
type ParamGroup = {
|
|
13
13
|
mode: 'object' | 'inlineSpread'
|
|
14
|
-
children: Record<string, ParamLeaf | undefined>
|
|
14
|
+
children: Record<string, ParamLeaf | null | undefined>
|
|
15
15
|
default?: string
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -25,14 +25,14 @@ function isGroup(spec: ParamSpec): spec is ParamGroup {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
function createType(type?: string) {
|
|
28
|
-
return type ? ast.createParamsType({ variant: 'reference', name: type }) :
|
|
28
|
+
return type ? ast.createParamsType({ variant: 'reference', name: type }) : null
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
function createDeclarationLeaf(name: string, spec: ParamLeaf): ast.FunctionParameterNode {
|
|
32
32
|
if (spec.default !== undefined) {
|
|
33
33
|
return ast.createFunctionParameter({
|
|
34
34
|
name,
|
|
35
|
-
type: createType(spec.type),
|
|
35
|
+
type: createType(spec.type) ?? undefined,
|
|
36
36
|
default: spec.default,
|
|
37
37
|
rest: spec.mode === 'inlineSpread',
|
|
38
38
|
})
|
|
@@ -40,7 +40,7 @@ function createDeclarationLeaf(name: string, spec: ParamLeaf): ast.FunctionParam
|
|
|
40
40
|
|
|
41
41
|
return ast.createFunctionParameter({
|
|
42
42
|
name,
|
|
43
|
-
type: createType(spec.type),
|
|
43
|
+
type: createType(spec.type) ?? undefined,
|
|
44
44
|
optional: !!spec.optional,
|
|
45
45
|
rest: spec.mode === 'inlineSpread',
|
|
46
46
|
})
|
|
@@ -52,7 +52,7 @@ function createDeclarationParam(name: string, spec: ParamSpec): ast.FunctionPara
|
|
|
52
52
|
inline: spec.mode === 'inlineSpread',
|
|
53
53
|
default: spec.default,
|
|
54
54
|
properties: Object.entries(spec.children)
|
|
55
|
-
.filter(([, child]) => child
|
|
55
|
+
.filter(([, child]) => child != null)
|
|
56
56
|
.map(([childName, child]) => createDeclarationLeaf(childName, child!)),
|
|
57
57
|
})
|
|
58
58
|
}
|
|
@@ -65,7 +65,7 @@ function createCallParam(name: string, spec: ParamSpec): ast.FunctionParameterNo
|
|
|
65
65
|
return ast.createParameterGroup({
|
|
66
66
|
inline: spec.mode === 'inlineSpread',
|
|
67
67
|
properties: Object.entries(spec.children)
|
|
68
|
-
.filter(([, child]) => child
|
|
68
|
+
.filter(([, child]) => child != null)
|
|
69
69
|
.map(([childName, child]) =>
|
|
70
70
|
ast.createFunctionParameter({
|
|
71
71
|
name:
|
|
@@ -92,8 +92,8 @@ function createCallParam(name: string, spec: ParamSpec): ast.FunctionParameterNo
|
|
|
92
92
|
* Creates function parameter builders for generating function signatures and calls.
|
|
93
93
|
* Returns utilities to output constructor signatures (`toConstructor()`) or call expressions (`toCall()`).
|
|
94
94
|
*/
|
|
95
|
-
export function createFunctionParams(params: Record<string, ParamSpec | undefined>) {
|
|
96
|
-
const entries = Object.entries(params).filter(([, spec]) => spec
|
|
95
|
+
export function createFunctionParams(params: Record<string, ParamSpec | null | undefined>) {
|
|
96
|
+
const entries = Object.entries(params).filter(([, spec]) => spec != null) as Array<[string, ParamSpec]>
|
|
97
97
|
|
|
98
98
|
return {
|
|
99
99
|
toConstructor() {
|
|
@@ -16,9 +16,9 @@ type OperationData = {
|
|
|
16
16
|
node: ast.OperationNode
|
|
17
17
|
name: string
|
|
18
18
|
tsResolver: ResolverTs
|
|
19
|
-
zodResolver: ResolverZod |
|
|
19
|
+
zodResolver: ResolverZod | null
|
|
20
20
|
typeFile: ast.FileNode
|
|
21
|
-
zodFile: ast.FileNode |
|
|
21
|
+
zodFile: ast.FileNode | null
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
type Controller = {
|
|
@@ -33,13 +33,18 @@ function resolveTypeImportNames(node: ast.OperationNode, tsResolver: ResolverTs)
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
function resolveZodImportNames(node: ast.OperationNode, zodResolver: ResolverZod): Array<string> {
|
|
36
|
-
const names: Array<string | undefined> = [
|
|
36
|
+
const names: Array<string | null | undefined> = [
|
|
37
37
|
zodResolver.resolveResponseName?.(node),
|
|
38
|
-
node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) :
|
|
38
|
+
node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
|
|
39
39
|
]
|
|
40
40
|
return names.filter((n): n is string => Boolean(n))
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Built-in `operations` generator for `@kubb/plugin-client` when
|
|
45
|
+
* `clientType: 'class'`. Emits one class per tag, with one instance method
|
|
46
|
+
* per operation and a shared constructor for request configuration.
|
|
47
|
+
*/
|
|
43
48
|
export const classClientGenerator = defineGenerator<PluginClient>({
|
|
44
49
|
name: 'classClient',
|
|
45
50
|
renderer: jsxRendererSync,
|
|
@@ -53,8 +58,8 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
53
58
|
|
|
54
59
|
const tsResolver = driver.getResolver(pluginTsName)
|
|
55
60
|
const tsPluginOptions = pluginTs.options
|
|
56
|
-
const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) :
|
|
57
|
-
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) :
|
|
61
|
+
const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
|
|
62
|
+
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
|
|
58
63
|
|
|
59
64
|
function buildOperationData(node: ast.OperationNode): OperationData {
|
|
60
65
|
const typeFile = tsResolver.resolveFile(
|
|
@@ -65,9 +70,9 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
65
70
|
zodResolver && pluginZod?.options
|
|
66
71
|
? zodResolver.resolveFile(
|
|
67
72
|
{ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
|
|
68
|
-
{ root, output: pluginZod.options?.output ?? output, group: pluginZod.options?.group },
|
|
73
|
+
{ root, output: pluginZod.options?.output ?? output, group: pluginZod.options?.group ?? undefined },
|
|
69
74
|
)
|
|
70
|
-
:
|
|
75
|
+
: null
|
|
71
76
|
|
|
72
77
|
return {
|
|
73
78
|
node: node,
|
|
@@ -85,7 +90,7 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
85
90
|
|
|
86
91
|
if (!tag && !group) {
|
|
87
92
|
const name = resolver.resolveClassName('ApiClient')
|
|
88
|
-
const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group })
|
|
93
|
+
const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group: group ?? undefined })
|
|
89
94
|
const operationData = buildOperationData(operationNode)
|
|
90
95
|
const previous = acc.find((item) => item.file.path === file.path)
|
|
91
96
|
|
|
@@ -99,7 +104,7 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
99
104
|
|
|
100
105
|
if (tag) {
|
|
101
106
|
const name = groupName
|
|
102
|
-
const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group })
|
|
107
|
+
const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group: group ?? undefined })
|
|
103
108
|
const operationData = buildOperationData(operationNode)
|
|
104
109
|
const previous = acc.find((item) => item.file.path === file.path)
|
|
105
110
|
|
|
@@ -215,7 +220,7 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
215
220
|
})
|
|
216
221
|
|
|
217
222
|
if (sdk) {
|
|
218
|
-
const sdkFile = resolver.resolveFile({ name: sdk.className, extname: '.ts' }, { root, output, group })
|
|
223
|
+
const sdkFile = resolver.resolveFile({ name: sdk.className, extname: '.ts' }, { root, output, group: group ?? undefined })
|
|
219
224
|
|
|
220
225
|
files.push(
|
|
221
226
|
<File
|
|
@@ -8,6 +8,11 @@ import { Client } from '../components/Client'
|
|
|
8
8
|
import { Url } from '../components/Url.tsx'
|
|
9
9
|
import type { PluginClient } from '../types'
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Built-in operation generator for `@kubb/plugin-client`. Emits one async
|
|
13
|
+
* function per OpenAPI operation, plus the matching URL helper. Used when
|
|
14
|
+
* `clientType: 'function'` (the default).
|
|
15
|
+
*/
|
|
11
16
|
export const clientGenerator = defineGenerator<PluginClient>({
|
|
12
17
|
name: 'client',
|
|
13
18
|
renderer: jsxRendererSync,
|
|
@@ -24,14 +29,14 @@ export const clientGenerator = defineGenerator<PluginClient>({
|
|
|
24
29
|
|
|
25
30
|
const tsResolver = driver.getResolver(pluginTsName)
|
|
26
31
|
|
|
27
|
-
const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) :
|
|
28
|
-
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) :
|
|
32
|
+
const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
|
|
33
|
+
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
|
|
29
34
|
|
|
30
35
|
const importedTypeNames = resolveOperationTypeNames(node, tsResolver, { paramsCasing })
|
|
31
36
|
|
|
32
37
|
const importedZodNames =
|
|
33
38
|
zodResolver && parser === 'zod'
|
|
34
|
-
? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) :
|
|
39
|
+
? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null].filter(
|
|
35
40
|
(name): name is string => Boolean(name),
|
|
36
41
|
)
|
|
37
42
|
: []
|
|
@@ -39,13 +44,16 @@ export const clientGenerator = defineGenerator<PluginClient>({
|
|
|
39
44
|
const meta = {
|
|
40
45
|
name: resolver.resolveName(node.operationId),
|
|
41
46
|
urlName: resolver.resolveUrlName(node),
|
|
42
|
-
file: resolver.resolveFile(
|
|
47
|
+
file: resolver.resolveFile(
|
|
48
|
+
{ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
|
|
49
|
+
{ root, output, group: group ?? undefined },
|
|
50
|
+
),
|
|
43
51
|
fileTs: tsResolver.resolveFile(
|
|
44
52
|
{ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
|
|
45
53
|
{
|
|
46
54
|
root,
|
|
47
55
|
output: pluginTs.options?.output ?? output,
|
|
48
|
-
group: pluginTs.options?.group,
|
|
56
|
+
group: pluginTs.options?.group ?? undefined,
|
|
49
57
|
},
|
|
50
58
|
),
|
|
51
59
|
fileZod:
|
|
@@ -55,10 +63,10 @@ export const clientGenerator = defineGenerator<PluginClient>({
|
|
|
55
63
|
{
|
|
56
64
|
root,
|
|
57
65
|
output: pluginZod.options.output ?? output,
|
|
58
|
-
group: pluginZod.options?.group,
|
|
66
|
+
group: pluginZod.options?.group ?? undefined,
|
|
59
67
|
},
|
|
60
68
|
)
|
|
61
|
-
:
|
|
69
|
+
: null,
|
|
62
70
|
} as const
|
|
63
71
|
|
|
64
72
|
const hasFormData = node.requestBody?.content?.some((e) => e.contentType === 'multipart/form-data') ?? false
|
|
@@ -90,7 +98,7 @@ export const clientGenerator = defineGenerator<PluginClient>({
|
|
|
90
98
|
|
|
91
99
|
{hasFormData && <File.Import name={['buildFormData']} root={meta.file.path} path={path.resolve(root, '.kubb/config.ts')} />}
|
|
92
100
|
|
|
93
|
-
{meta.fileZod && importedZodNames.length > 0 && <File.Import name={importedZodNames as string
|
|
101
|
+
{meta.fileZod && importedZodNames.length > 0 && <File.Import name={importedZodNames as Array<string>} root={meta.file.path} path={meta.fileZod.path} />}
|
|
94
102
|
|
|
95
103
|
{meta.fileTs && importedTypeNames.length > 0 && (
|
|
96
104
|
<File.Import name={Array.from(new Set(importedTypeNames))} root={meta.file.path} path={meta.fileTs.path} isTypeOnly />
|
|
@@ -4,6 +4,12 @@ import { defineGenerator } from '@kubb/core'
|
|
|
4
4
|
import { File, Function, jsxRendererSync } from '@kubb/renderer-jsx'
|
|
5
5
|
import type { PluginClient } from '../types'
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Emits one aggregate file per tag/group when `group` is configured. Each
|
|
9
|
+
* file re-exports every client function for that group, so callers can
|
|
10
|
+
* `import { petController } from './gen/clients'` instead of importing
|
|
11
|
+
* each operation individually.
|
|
12
|
+
*/
|
|
7
13
|
export const groupedClientGenerator = defineGenerator<PluginClient>({
|
|
8
14
|
name: 'groupedClient',
|
|
9
15
|
renderer: jsxRendererSync,
|
|
@@ -15,16 +21,16 @@ export const groupedClientGenerator = defineGenerator<PluginClient>({
|
|
|
15
21
|
(acc, operationNode) => {
|
|
16
22
|
if (group?.type === 'tag') {
|
|
17
23
|
const tag = operationNode.tags[0]
|
|
18
|
-
const name = tag ? group?.name?.({ group: camelCase(tag) }) :
|
|
24
|
+
const name = tag ? group?.name?.({ group: camelCase(tag) }) : null
|
|
19
25
|
|
|
20
26
|
if (!tag || !name) {
|
|
21
27
|
return acc
|
|
22
28
|
}
|
|
23
29
|
|
|
24
|
-
const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group })
|
|
30
|
+
const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group: group ?? undefined })
|
|
25
31
|
const clientFile = resolver.resolveFile(
|
|
26
32
|
{ name: operationNode.operationId, extname: '.ts', tag: operationNode.tags[0] ?? 'default', path: operationNode.path },
|
|
27
|
-
{ root, output, group },
|
|
33
|
+
{ root, output, group: group ?? undefined },
|
|
28
34
|
)
|
|
29
35
|
|
|
30
36
|
const client = {
|
|
@@ -3,6 +3,12 @@ import { File, jsxRendererSync } from '@kubb/renderer-jsx'
|
|
|
3
3
|
import { Operations } from '../components/Operations'
|
|
4
4
|
import type { PluginClient } from '../types'
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Generates an `operations.ts` file that re-exports every operation grouped
|
|
8
|
+
* by HTTP method. Enabled when `pluginClient({ operations: true })`. Useful
|
|
9
|
+
* for building meta-tooling on top of the generated client (route
|
|
10
|
+
* registries, API explorers).
|
|
11
|
+
*/
|
|
6
12
|
export const operationsGenerator = defineGenerator<PluginClient>({
|
|
7
13
|
name: 'client',
|
|
8
14
|
renderer: jsxRendererSync,
|
|
@@ -11,7 +17,7 @@ export const operationsGenerator = defineGenerator<PluginClient>({
|
|
|
11
17
|
const { output, group } = ctx.options
|
|
12
18
|
|
|
13
19
|
const name = 'operations'
|
|
14
|
-
const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group })
|
|
20
|
+
const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group: group ?? undefined })
|
|
15
21
|
|
|
16
22
|
return (
|
|
17
23
|
<File
|
|
@@ -15,9 +15,9 @@ type OperationData = {
|
|
|
15
15
|
node: ast.OperationNode
|
|
16
16
|
name: string
|
|
17
17
|
tsResolver: ResolverTs
|
|
18
|
-
zodResolver: ResolverZod |
|
|
18
|
+
zodResolver: ResolverZod | null
|
|
19
19
|
typeFile: ast.FileNode
|
|
20
|
-
zodFile: ast.FileNode |
|
|
20
|
+
zodFile: ast.FileNode | null
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
type Controller = {
|
|
@@ -31,13 +31,19 @@ function resolveTypeImportNames(node: ast.OperationNode, tsResolver: ResolverTs)
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
function resolveZodImportNames(node: ast.OperationNode, zodResolver: ResolverZod): Array<string> {
|
|
34
|
-
const names: Array<string | undefined> = [
|
|
34
|
+
const names: Array<string | null | undefined> = [
|
|
35
35
|
zodResolver.resolveResponseName?.(node),
|
|
36
|
-
node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) :
|
|
36
|
+
node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
|
|
37
37
|
]
|
|
38
38
|
return names.filter((n): n is string => Boolean(n))
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Built-in `operations` generator for `@kubb/plugin-client` when
|
|
43
|
+
* `clientType: 'staticClass'`. Emits one class per tag, with a static method
|
|
44
|
+
* per operation so callers can use `Pet.getPetById(...)` without
|
|
45
|
+
* instantiating the class.
|
|
46
|
+
*/
|
|
41
47
|
export const staticClassClientGenerator = defineGenerator<PluginClient>({
|
|
42
48
|
name: 'staticClassClient',
|
|
43
49
|
renderer: jsxRendererSync,
|
|
@@ -51,8 +57,8 @@ export const staticClassClientGenerator = defineGenerator<PluginClient>({
|
|
|
51
57
|
|
|
52
58
|
const tsResolver = driver.getResolver(pluginTsName)
|
|
53
59
|
const tsPluginOptions = pluginTs.options
|
|
54
|
-
const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) :
|
|
55
|
-
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) :
|
|
60
|
+
const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
|
|
61
|
+
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
|
|
56
62
|
|
|
57
63
|
function buildOperationData(node: ast.OperationNode): OperationData {
|
|
58
64
|
const typeFile = tsResolver.resolveFile(
|
|
@@ -63,9 +69,9 @@ export const staticClassClientGenerator = defineGenerator<PluginClient>({
|
|
|
63
69
|
zodResolver && pluginZod?.options
|
|
64
70
|
? zodResolver.resolveFile(
|
|
65
71
|
{ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
|
|
66
|
-
{ root, output: pluginZod.options?.output ?? output, group: pluginZod.options?.group },
|
|
72
|
+
{ root, output: pluginZod.options?.output ?? output, group: pluginZod.options?.group ?? undefined },
|
|
67
73
|
)
|
|
68
|
-
:
|
|
74
|
+
: null
|
|
69
75
|
|
|
70
76
|
return {
|
|
71
77
|
node: node,
|
|
@@ -83,7 +89,7 @@ export const staticClassClientGenerator = defineGenerator<PluginClient>({
|
|
|
83
89
|
|
|
84
90
|
if (!tag && !group) {
|
|
85
91
|
const name = resolver.resolveClassName('ApiClient')
|
|
86
|
-
const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group })
|
|
92
|
+
const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group: group ?? undefined })
|
|
87
93
|
const operationData = buildOperationData(operationNode)
|
|
88
94
|
const previous = acc.find((item) => item.file.path === file.path)
|
|
89
95
|
|
|
@@ -97,7 +103,7 @@ export const staticClassClientGenerator = defineGenerator<PluginClient>({
|
|
|
97
103
|
|
|
98
104
|
if (tag) {
|
|
99
105
|
const name = groupName
|
|
100
|
-
const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group })
|
|
106
|
+
const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group: group ?? undefined })
|
|
101
107
|
const operationData = buildOperationData(operationNode)
|
|
102
108
|
const previous = acc.find((item) => item.file.path === file.path)
|
|
103
109
|
|