@kubb/plugin-client 5.0.0-beta.3 → 5.0.0-beta.31
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 +24 -4
- package/dist/clients/axios.cjs +25 -3
- package/dist/clients/axios.cjs.map +1 -1
- package/dist/clients/axios.d.ts +9 -2
- package/dist/clients/axios.js +25 -3
- package/dist/clients/axios.js.map +1 -1
- package/dist/clients/fetch.cjs +76 -8
- package/dist/clients/fetch.cjs.map +1 -1
- package/dist/clients/fetch.d.ts +9 -2
- package/dist/clients/fetch.js +76 -8
- package/dist/clients/fetch.js.map +1 -1
- package/dist/index.cjs +627 -353
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +153 -86
- package/dist/index.js +628 -354
- 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 +1293 -0
- package/package.json +11 -17
- package/src/clients/axios.ts +41 -7
- package/src/clients/fetch.ts +106 -6
- package/src/components/ClassClient.tsx +19 -20
- package/src/components/Client.tsx +74 -53
- package/src/components/Operations.tsx +2 -1
- package/src/components/StaticClassClient.tsx +19 -20
- package/src/components/Url.tsx +8 -9
- package/src/components/WrapperClient.tsx +9 -5
- package/src/functionParams.ts +8 -8
- package/src/generators/classClientGenerator.tsx +51 -47
- package/src/generators/clientGenerator.tsx +37 -48
- package/src/generators/groupedClientGenerator.tsx +14 -8
- package/src/generators/operationsGenerator.tsx +14 -8
- package/src/generators/staticClassClientGenerator.tsx +45 -41
- package/src/plugin.ts +27 -26
- package/src/resolvers/resolverClient.ts +31 -8
- package/src/types.ts +93 -55
- package/src/utils.ts +35 -56
- package/templates/clients/axios.ts +0 -73
- package/templates/clients/fetch.ts +0 -96
- package/templates/config.ts +0 -43
package/src/utils.ts
CHANGED
|
@@ -1,51 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
1
|
+
import { getOperationParameters, getResponseType, resolveSuccessNames } from '@internals/shared'
|
|
2
|
+
import type { URLPath } from '@internals/utils'
|
|
3
|
+
import { ast } from '@kubb/core'
|
|
3
4
|
import type { ResolverTs } from '@kubb/plugin-ts'
|
|
4
5
|
import type { ResolverZod } from '@kubb/plugin-zod'
|
|
5
6
|
import { createFunctionParams } from './functionParams.ts'
|
|
6
7
|
import type { PluginClient } from './types.ts'
|
|
7
8
|
|
|
8
|
-
/**
|
|
9
|
-
* Extracts documentation comments from an operation node.
|
|
10
|
-
* Includes description, summary, link, and deprecation information.
|
|
11
|
-
*/
|
|
12
|
-
export function getComments(node: ast.OperationNode): Array<string> {
|
|
13
|
-
return [
|
|
14
|
-
node.description && `@description ${node.description}`,
|
|
15
|
-
node.summary && `@summary ${node.summary}`,
|
|
16
|
-
node.path && `{@link ${new URLPath(node.path).URL}}`,
|
|
17
|
-
node.deprecated && '@deprecated',
|
|
18
|
-
]
|
|
19
|
-
.filter((x): x is string => Boolean(x))
|
|
20
|
-
.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim()))
|
|
21
|
-
.filter((x): x is string => Boolean(x))
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Builds a mapping of original parameter names to their transformed (cased) names.
|
|
26
|
-
* Returns undefined if no names have changed.
|
|
27
|
-
*/
|
|
28
|
-
export function buildParamsMapping(originalParams: Array<ast.ParameterNode>, casedParams: Array<ast.ParameterNode>): Record<string, string> | undefined {
|
|
29
|
-
const mapping: Record<string, string> = {}
|
|
30
|
-
let hasChanged = false
|
|
31
|
-
originalParams.forEach((param, i) => {
|
|
32
|
-
const casedName = casedParams[i]?.name ?? param.name
|
|
33
|
-
mapping[param.name] = casedName
|
|
34
|
-
if (param.name !== casedName) {
|
|
35
|
-
hasChanged = true
|
|
36
|
-
}
|
|
37
|
-
})
|
|
38
|
-
return hasChanged ? mapping : undefined
|
|
39
|
-
}
|
|
40
|
-
|
|
41
9
|
/**
|
|
42
10
|
* Builds HTTP headers array for a client request.
|
|
43
11
|
* Includes Content-Type (if not default) and spreads header parameters if present.
|
|
44
12
|
*/
|
|
45
13
|
export function buildHeaders(contentType: string, hasHeaderParams: boolean): Array<string> {
|
|
46
14
|
return [
|
|
47
|
-
contentType !== 'application/json' && contentType !== 'multipart/form-data' ? `'Content-Type': '${contentType}'` :
|
|
48
|
-
hasHeaderParams ? '...headers' :
|
|
15
|
+
contentType !== 'application/json' && contentType !== 'multipart/form-data' ? `'Content-Type': '${contentType}'` : null,
|
|
16
|
+
hasHeaderParams ? '...headers' : null,
|
|
49
17
|
].filter(Boolean) as Array<string>
|
|
50
18
|
}
|
|
51
19
|
|
|
@@ -54,8 +22,9 @@ export function buildHeaders(contentType: string, hasHeaderParams: boolean): Arr
|
|
|
54
22
|
* Includes response type, error type, and optional request type.
|
|
55
23
|
*/
|
|
56
24
|
export function buildGenerics(node: ast.OperationNode, tsResolver: ResolverTs): Array<string> {
|
|
57
|
-
const
|
|
58
|
-
const
|
|
25
|
+
const successNames = resolveSuccessNames(node, tsResolver)
|
|
26
|
+
const responseName = successNames.length > 0 ? successNames.join(' | ') : tsResolver.resolveResponseName(node)
|
|
27
|
+
const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null
|
|
59
28
|
const errorNames = node.responses.filter((r) => Number.parseInt(r.statusCode, 10) >= 400).map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode))
|
|
60
29
|
const TError = `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(' | ') : 'Error'}>`
|
|
61
30
|
return [responseName, TError, requestName || 'unknown'].filter(Boolean)
|
|
@@ -71,20 +40,23 @@ export function buildClassClientParams({
|
|
|
71
40
|
baseURL,
|
|
72
41
|
tsResolver,
|
|
73
42
|
isFormData,
|
|
43
|
+
isMultipleContentTypes,
|
|
44
|
+
hasFormData,
|
|
74
45
|
headers,
|
|
75
46
|
}: {
|
|
76
47
|
node: ast.OperationNode
|
|
77
48
|
path: URLPath
|
|
78
|
-
baseURL: string | undefined
|
|
49
|
+
baseURL: string | null | undefined
|
|
79
50
|
tsResolver: ResolverTs
|
|
80
51
|
isFormData: boolean
|
|
52
|
+
isMultipleContentTypes: boolean
|
|
53
|
+
hasFormData: boolean
|
|
81
54
|
headers: Array<string>
|
|
82
55
|
}) {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : undefined
|
|
56
|
+
const { query: queryParams } = getOperationParameters(node)
|
|
57
|
+
const queryParamsName = queryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, queryParams[0]!) : null
|
|
58
|
+
const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null
|
|
59
|
+
const responseType = getResponseType(node)
|
|
88
60
|
|
|
89
61
|
return createFunctionParams({
|
|
90
62
|
config: {
|
|
@@ -94,7 +66,7 @@ export function buildClassClientParams({
|
|
|
94
66
|
mode: 'inlineSpread',
|
|
95
67
|
},
|
|
96
68
|
method: {
|
|
97
|
-
value: JSON.stringify(node.method.toUpperCase()),
|
|
69
|
+
value: JSON.stringify(ast.isHttpOperationNode(node) ? node.method.toUpperCase() : ''),
|
|
98
70
|
},
|
|
99
71
|
url: {
|
|
100
72
|
value: path.template,
|
|
@@ -103,18 +75,25 @@ export function buildClassClientParams({
|
|
|
103
75
|
? {
|
|
104
76
|
value: JSON.stringify(baseURL),
|
|
105
77
|
}
|
|
106
|
-
:
|
|
107
|
-
params: queryParamsName ? {} :
|
|
78
|
+
: null,
|
|
79
|
+
params: queryParamsName ? {} : null,
|
|
108
80
|
data: requestName
|
|
109
81
|
? {
|
|
110
|
-
value:
|
|
82
|
+
value:
|
|
83
|
+
isMultipleContentTypes && hasFormData
|
|
84
|
+
? "contentType === 'multipart/form-data' ? formData as FormData : requestData"
|
|
85
|
+
: isFormData
|
|
86
|
+
? 'formData as FormData'
|
|
87
|
+
: 'requestData',
|
|
111
88
|
}
|
|
112
|
-
:
|
|
89
|
+
: null,
|
|
90
|
+
contentType: isMultipleContentTypes ? {} : null,
|
|
91
|
+
responseType: responseType ? { value: JSON.stringify(responseType) } : null,
|
|
113
92
|
headers: headers.length
|
|
114
93
|
? {
|
|
115
94
|
value: `{ ${headers.join(', ')}, ...requestConfig.headers }`,
|
|
116
95
|
}
|
|
117
|
-
:
|
|
96
|
+
: null,
|
|
118
97
|
},
|
|
119
98
|
},
|
|
120
99
|
})
|
|
@@ -131,9 +110,9 @@ export function buildRequestDataLine({
|
|
|
131
110
|
}: {
|
|
132
111
|
parser: PluginClient['resolvedOptions']['parser'] | undefined
|
|
133
112
|
node: ast.OperationNode
|
|
134
|
-
zodResolver?: ResolverZod
|
|
113
|
+
zodResolver?: ResolverZod | null
|
|
135
114
|
}): string {
|
|
136
|
-
const zodRequestName = zodResolver && parser === 'zod' && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) :
|
|
115
|
+
const zodRequestName = zodResolver && parser === 'zod' && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null
|
|
137
116
|
if (parser === 'zod' && zodRequestName) {
|
|
138
117
|
return `const requestData = ${zodRequestName}.parse(data)`
|
|
139
118
|
}
|
|
@@ -164,16 +143,16 @@ export function buildReturnStatement({
|
|
|
164
143
|
dataReturnType: PluginClient['resolvedOptions']['dataReturnType']
|
|
165
144
|
parser: PluginClient['resolvedOptions']['parser'] | undefined
|
|
166
145
|
node: ast.OperationNode
|
|
167
|
-
zodResolver?: ResolverZod
|
|
146
|
+
zodResolver?: ResolverZod | null
|
|
168
147
|
}): string {
|
|
169
|
-
const zodResponseName = zodResolver && parser === 'zod' ? zodResolver.resolveResponseName?.(node) :
|
|
148
|
+
const zodResponseName = zodResolver && parser === 'zod' ? zodResolver.resolveResponseName?.(node) : null
|
|
170
149
|
if (dataReturnType === 'full' && parser === 'zod' && zodResponseName) {
|
|
171
150
|
return `return {...res, data: ${zodResponseName}.parse(res.data)}`
|
|
172
151
|
}
|
|
173
152
|
if (dataReturnType === 'data' && parser === 'zod' && zodResponseName) {
|
|
174
153
|
return `return ${zodResponseName}.parse(res.data)`
|
|
175
154
|
}
|
|
176
|
-
if (dataReturnType === 'full' && parser
|
|
155
|
+
if (dataReturnType === 'full' && parser !== 'zod') {
|
|
177
156
|
return 'return res'
|
|
178
157
|
}
|
|
179
158
|
return 'return res.data'
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'
|
|
2
|
-
import axios from 'axios'
|
|
3
|
-
|
|
4
|
-
declare const AXIOS_BASE: string
|
|
5
|
-
declare const AXIOS_HEADERS: string
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Subset of AxiosRequestConfig
|
|
9
|
-
*/
|
|
10
|
-
export type RequestConfig<TData = unknown> = {
|
|
11
|
-
baseURL?: string
|
|
12
|
-
url?: string
|
|
13
|
-
method?: 'GET' | 'PUT' | 'PATCH' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD'
|
|
14
|
-
params?: unknown
|
|
15
|
-
data?: TData | FormData
|
|
16
|
-
responseType?: 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'
|
|
17
|
-
signal?: AbortSignal
|
|
18
|
-
validateStatus?: (status: number) => boolean
|
|
19
|
-
headers?: AxiosRequestConfig['headers']
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Subset of AxiosResponse
|
|
24
|
-
*/
|
|
25
|
-
export type ResponseConfig<TData = unknown> = {
|
|
26
|
-
data: TData
|
|
27
|
-
status: number
|
|
28
|
-
statusText: string
|
|
29
|
-
headers: AxiosResponse['headers']
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export type ResponseErrorConfig<TError = unknown> = AxiosError<TError>
|
|
33
|
-
|
|
34
|
-
export type Client = <TData, _TError = unknown, TVariables = unknown>(config: RequestConfig<TVariables>, request?: unknown) => Promise<ResponseConfig<TData>>
|
|
35
|
-
|
|
36
|
-
let _config: Partial<RequestConfig> = {
|
|
37
|
-
baseURL: typeof AXIOS_BASE !== 'undefined' ? AXIOS_BASE : undefined,
|
|
38
|
-
headers: typeof AXIOS_HEADERS !== 'undefined' ? JSON.parse(AXIOS_HEADERS) : undefined,
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export const getConfig = () => _config
|
|
42
|
-
|
|
43
|
-
export const setConfig = (config: RequestConfig) => {
|
|
44
|
-
_config = config
|
|
45
|
-
return getConfig()
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export const mergeConfig = <T extends RequestConfig>(...configs: Array<Partial<T>>): Partial<T> => {
|
|
49
|
-
return configs.reduce<Partial<T>>((merged, config) => {
|
|
50
|
-
return {
|
|
51
|
-
...merged,
|
|
52
|
-
...config,
|
|
53
|
-
headers: {
|
|
54
|
-
...merged.headers,
|
|
55
|
-
...config.headers,
|
|
56
|
-
},
|
|
57
|
-
}
|
|
58
|
-
}, {})
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export const axiosInstance = axios.create(getConfig())
|
|
62
|
-
|
|
63
|
-
export const fetch = async <TData, TError = unknown, TVariables = unknown>(
|
|
64
|
-
config: RequestConfig<TVariables>,
|
|
65
|
-
_request?: unknown,
|
|
66
|
-
): Promise<ResponseConfig<TData>> => {
|
|
67
|
-
return axiosInstance.request<TData, ResponseConfig<TData>>(mergeConfig(getConfig(), config)).catch((e: AxiosError<TError>) => {
|
|
68
|
-
throw e
|
|
69
|
-
})
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
fetch.getConfig = getConfig
|
|
73
|
-
fetch.setConfig = setConfig
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RequestCredentials
|
|
3
|
-
*/
|
|
4
|
-
export type RequestCredentials = 'omit' | 'same-origin' | 'include'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Subset of FetchRequestConfig
|
|
8
|
-
*/
|
|
9
|
-
export type RequestConfig<TData = unknown> = {
|
|
10
|
-
baseURL?: string
|
|
11
|
-
url?: string
|
|
12
|
-
method?: 'GET' | 'PUT' | 'PATCH' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD'
|
|
13
|
-
params?: unknown
|
|
14
|
-
data?: TData | FormData
|
|
15
|
-
responseType?: 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'
|
|
16
|
-
signal?: AbortSignal
|
|
17
|
-
headers?: [string, string][] | Record<string, string>
|
|
18
|
-
credentials?: RequestCredentials
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Subset of FetchResponse
|
|
23
|
-
*/
|
|
24
|
-
export type ResponseConfig<TData = unknown> = {
|
|
25
|
-
data: TData
|
|
26
|
-
status: number
|
|
27
|
-
statusText: string
|
|
28
|
-
headers: Headers
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
let _config: Partial<RequestConfig> = {}
|
|
32
|
-
|
|
33
|
-
export const getConfig = () => _config
|
|
34
|
-
|
|
35
|
-
export const setConfig = (config: Partial<RequestConfig>) => {
|
|
36
|
-
_config = config
|
|
37
|
-
return getConfig()
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export const mergeConfig = <T extends RequestConfig>(...configs: Array<Partial<T>>): Partial<T> => {
|
|
41
|
-
return configs.reduce<Partial<T>>((merged, config) => {
|
|
42
|
-
return {
|
|
43
|
-
...merged,
|
|
44
|
-
...config,
|
|
45
|
-
headers: {
|
|
46
|
-
...(Array.isArray(merged.headers) ? Object.fromEntries(merged.headers) : merged.headers),
|
|
47
|
-
...(Array.isArray(config.headers) ? Object.fromEntries(config.headers) : config.headers),
|
|
48
|
-
},
|
|
49
|
-
}
|
|
50
|
-
}, {})
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export type ResponseErrorConfig<TError = unknown> = TError
|
|
54
|
-
|
|
55
|
-
export type Client = <TData, _TError = unknown, TVariables = unknown>(config: RequestConfig<TVariables>, request?: unknown) => Promise<ResponseConfig<TData>>
|
|
56
|
-
|
|
57
|
-
export const fetch = async <TData, _TError = unknown, TVariables = unknown>(
|
|
58
|
-
paramsConfig: RequestConfig<TVariables>,
|
|
59
|
-
_request?: unknown,
|
|
60
|
-
): Promise<ResponseConfig<TData>> => {
|
|
61
|
-
const normalizedParams = new URLSearchParams()
|
|
62
|
-
|
|
63
|
-
const config = mergeConfig(getConfig(), paramsConfig)
|
|
64
|
-
|
|
65
|
-
Object.entries(config.params || {}).forEach(([key, value]) => {
|
|
66
|
-
if (value !== undefined) {
|
|
67
|
-
normalizedParams.append(key, value === null ? 'null' : value.toString())
|
|
68
|
-
}
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
let targetUrl = [config.baseURL, config.url].filter(Boolean).join('')
|
|
72
|
-
|
|
73
|
-
if (config.params) {
|
|
74
|
-
targetUrl += `?${normalizedParams}`
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const response = await globalThis.fetch(targetUrl, {
|
|
78
|
-
credentials: config.credentials || 'same-origin',
|
|
79
|
-
method: config.method?.toUpperCase(),
|
|
80
|
-
body: config.data instanceof FormData ? config.data : JSON.stringify(config.data),
|
|
81
|
-
signal: config.signal,
|
|
82
|
-
headers: config.headers,
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
const data = [204, 205, 304].includes(response.status) || !response.body ? {} : await response.json()
|
|
86
|
-
|
|
87
|
-
return {
|
|
88
|
-
data: data as TData,
|
|
89
|
-
status: response.status,
|
|
90
|
-
statusText: response.statusText,
|
|
91
|
-
headers: response.headers as Headers,
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
fetch.getConfig = getConfig
|
|
96
|
-
fetch.setConfig = setConfig
|
package/templates/config.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
export function buildFormData<T = unknown>(data: T): FormData {
|
|
2
|
-
const formData = new FormData()
|
|
3
|
-
|
|
4
|
-
function appendData(key: string, value: any) {
|
|
5
|
-
if (value instanceof Blob) {
|
|
6
|
-
formData.append(key, value)
|
|
7
|
-
return
|
|
8
|
-
}
|
|
9
|
-
if (value instanceof Date) {
|
|
10
|
-
formData.append(key, value.toISOString())
|
|
11
|
-
return
|
|
12
|
-
}
|
|
13
|
-
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
14
|
-
formData.append(key, String(value))
|
|
15
|
-
return
|
|
16
|
-
}
|
|
17
|
-
if (typeof value === 'string') {
|
|
18
|
-
formData.append(key, value)
|
|
19
|
-
return
|
|
20
|
-
}
|
|
21
|
-
if (typeof value === 'object') {
|
|
22
|
-
formData.append(key, new Blob([JSON.stringify(value)], { type: 'application/json' }))
|
|
23
|
-
return
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (data) {
|
|
28
|
-
Object.entries(data).forEach(([key, value]) => {
|
|
29
|
-
if (value === undefined || value === null) return
|
|
30
|
-
|
|
31
|
-
if (Array.isArray(value)) {
|
|
32
|
-
for (const valueItem of value) {
|
|
33
|
-
if (valueItem === undefined || valueItem === null) continue
|
|
34
|
-
appendData(key, valueItem)
|
|
35
|
-
}
|
|
36
|
-
} else {
|
|
37
|
-
appendData(key, value)
|
|
38
|
-
}
|
|
39
|
-
})
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return formData
|
|
43
|
-
}
|