@kubb/plugin-client 5.0.0-beta.3 → 5.0.0-beta.30
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 +20 -2
- package/dist/clients/fetch.cjs.map +1 -1
- package/dist/clients/fetch.d.ts +9 -2
- package/dist/clients/fetch.js +20 -2
- package/dist/clients/fetch.js.map +1 -1
- package/dist/index.cjs +524 -301
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +150 -84
- package/dist/index.js +525 -302
- 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 +30 -3
- package/src/components/ClassClient.tsx +17 -19
- package/src/components/Client.tsx +68 -51
- package/src/components/StaticClassClient.tsx +17 -19
- package/src/components/Url.tsx +7 -9
- package/src/components/WrapperClient.tsx +9 -5
- package/src/functionParams.ts +8 -8
- package/src/generators/classClientGenerator.tsx +40 -38
- package/src/generators/clientGenerator.tsx +32 -35
- package/src/generators/groupedClientGenerator.tsx +14 -8
- package/src/generators/operationsGenerator.tsx +12 -6
- package/src/generators/staticClassClientGenerator.tsx +34 -32
- package/src/plugin.ts +24 -11
- package/src/resolvers/resolverClient.ts +31 -8
- package/src/types.ts +90 -53
- package/src/utils.ts +30 -53
- package/templates/clients/axios.ts +0 -73
- package/templates/clients/fetch.ts +0 -96
- package/templates/config.ts +0 -43
package/package.json
CHANGED
|
@@ -1,23 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubb/plugin-client",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "5.0.0-beta.30",
|
|
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",
|
|
7
7
|
"axios",
|
|
8
|
-
"code-
|
|
8
|
+
"code-generation",
|
|
9
9
|
"codegen",
|
|
10
10
|
"fetch",
|
|
11
11
|
"http-client",
|
|
12
12
|
"kubb",
|
|
13
|
-
"oas",
|
|
14
13
|
"openapi",
|
|
15
|
-
"plugins",
|
|
16
|
-
"rest-api",
|
|
17
|
-
"sdk-generator",
|
|
18
14
|
"swagger",
|
|
19
|
-
"type-safe",
|
|
20
|
-
"type-safety",
|
|
21
15
|
"typescript"
|
|
22
16
|
],
|
|
23
17
|
"license": "MIT",
|
|
@@ -30,8 +24,7 @@
|
|
|
30
24
|
"files": [
|
|
31
25
|
"src",
|
|
32
26
|
"dist",
|
|
33
|
-
"
|
|
34
|
-
"plugin.json",
|
|
27
|
+
"extension.yaml",
|
|
35
28
|
"*.d.ts",
|
|
36
29
|
"*.d.cts",
|
|
37
30
|
"!/**/**.test.**",
|
|
@@ -94,17 +87,18 @@
|
|
|
94
87
|
"registry": "https://registry.npmjs.org/"
|
|
95
88
|
},
|
|
96
89
|
"dependencies": {
|
|
97
|
-
"@kubb/core": "5.0.0-beta.
|
|
98
|
-
"@kubb/renderer-jsx": "5.0.0-beta.
|
|
99
|
-
"@kubb/plugin-ts": "5.0.0-beta.
|
|
100
|
-
"@kubb/plugin-zod": "5.0.0-beta.
|
|
90
|
+
"@kubb/core": "5.0.0-beta.29",
|
|
91
|
+
"@kubb/renderer-jsx": "5.0.0-beta.29",
|
|
92
|
+
"@kubb/plugin-ts": "5.0.0-beta.30",
|
|
93
|
+
"@kubb/plugin-zod": "5.0.0-beta.30"
|
|
101
94
|
},
|
|
102
95
|
"devDependencies": {
|
|
103
|
-
"axios": "^1.
|
|
96
|
+
"axios": "^1.16.1",
|
|
97
|
+
"@internals/shared": "0.0.0",
|
|
104
98
|
"@internals/utils": "0.0.0"
|
|
105
99
|
},
|
|
106
100
|
"peerDependencies": {
|
|
107
|
-
"@kubb/renderer-jsx": "5.0.0-beta.
|
|
101
|
+
"@kubb/renderer-jsx": "5.0.0-beta.29",
|
|
108
102
|
"axios": "^1.7.2"
|
|
109
103
|
},
|
|
110
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,8 +23,9 @@ 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']
|
|
28
|
+
contentType?: string
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
/**
|
|
@@ -55,22 +63,48 @@ export const mergeConfig = <T extends RequestConfig>(...configs: Array<Partial<T
|
|
|
55
63
|
...merged,
|
|
56
64
|
...config,
|
|
57
65
|
headers: {
|
|
58
|
-
...merged.headers,
|
|
59
|
-
...config.headers,
|
|
66
|
+
...(Array.isArray(merged.headers) ? Object.fromEntries(merged.headers) : merged.headers),
|
|
67
|
+
...(Array.isArray(config.headers) ? Object.fromEntries(config.headers) : config.headers),
|
|
60
68
|
},
|
|
61
69
|
}
|
|
62
70
|
}, {})
|
|
63
71
|
}
|
|
64
72
|
|
|
65
|
-
|
|
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)
|
|
66
90
|
|
|
67
91
|
export const client = async <TResponseData, TError = unknown, TRequestData = unknown>(
|
|
68
92
|
config: RequestConfig<TRequestData>,
|
|
69
93
|
_request?: unknown,
|
|
70
94
|
): Promise<ResponseConfig<TResponseData>> => {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
95
|
+
const requestConfig = mergeConfig(getConfig(), config)
|
|
96
|
+
const { contentType, headers, ...axiosConfig } = requestConfig
|
|
97
|
+
return axiosInstance
|
|
98
|
+
.request<TResponseData, ResponseConfig<TResponseData>>({
|
|
99
|
+
...axiosConfig,
|
|
100
|
+
headers: {
|
|
101
|
+
...(contentType && contentType !== 'multipart/form-data' ? { 'Content-Type': contentType } : {}),
|
|
102
|
+
...serializeHeaders(headers),
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
.catch((e: AxiosError<TError>) => {
|
|
106
|
+
throw e
|
|
107
|
+
})
|
|
74
108
|
}
|
|
75
109
|
|
|
76
110
|
client.getConfig = getConfig
|
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,8 +21,9 @@ 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
|
|
26
|
+
contentType?: string
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
/**
|
|
@@ -50,6 +58,22 @@ export const mergeConfig = <T extends RequestConfig>(...configs: Array<Partial<T
|
|
|
50
58
|
}, {})
|
|
51
59
|
}
|
|
52
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
|
+
|
|
53
77
|
export type ResponseErrorConfig<TError = unknown> = TError
|
|
54
78
|
|
|
55
79
|
export type Client = <TResponseData, _TError = unknown, TRequestData = unknown>(
|
|
@@ -77,12 +101,15 @@ export const client = async <TResponseData, _TError = unknown, RequestData = unk
|
|
|
77
101
|
targetUrl += `?${normalizedParams}`
|
|
78
102
|
}
|
|
79
103
|
|
|
80
|
-
const response = await fetch(targetUrl, {
|
|
104
|
+
const response = await globalThis.fetch(targetUrl, {
|
|
81
105
|
credentials: config.credentials || 'same-origin',
|
|
82
106
|
method: config.method?.toUpperCase(),
|
|
83
107
|
body: config.data instanceof FormData ? config.data : JSON.stringify(config.data),
|
|
84
108
|
signal: config.signal,
|
|
85
|
-
headers:
|
|
109
|
+
headers: {
|
|
110
|
+
...(config.contentType && config.contentType !== 'multipart/form-data' ? { 'Content-Type': config.contentType } : {}),
|
|
111
|
+
...serializeHeaders(config.headers),
|
|
112
|
+
},
|
|
86
113
|
})
|
|
87
114
|
|
|
88
115
|
const data = [204, 205, 304].includes(response.status) || !response.body ? {} : await response.json()
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildOperationComments, getContentTypeInfo, getOperationParameters } from '@internals/shared'
|
|
1
2
|
import { buildJSDoc, URLPath } from '@internals/utils'
|
|
2
3
|
import type { ast } from '@kubb/core'
|
|
3
4
|
import type { ResolverTs } from '@kubb/plugin-ts'
|
|
@@ -6,14 +7,14 @@ import type { ResolverZod } from '@kubb/plugin-zod'
|
|
|
6
7
|
import { File } from '@kubb/renderer-jsx'
|
|
7
8
|
import type { KubbReactNode } from '@kubb/renderer-jsx/types'
|
|
8
9
|
import type { PluginClient } from '../types.ts'
|
|
9
|
-
import { buildClassClientParams, buildFormDataLine, buildGenerics, buildHeaders, buildRequestDataLine, buildReturnStatement
|
|
10
|
-
import {
|
|
10
|
+
import { buildClassClientParams, buildFormDataLine, buildGenerics, buildHeaders, buildRequestDataLine, buildReturnStatement } from '../utils.ts'
|
|
11
|
+
import { buildClientParamsNode } from './Client.tsx'
|
|
11
12
|
|
|
12
13
|
type OperationData = {
|
|
13
14
|
node: ast.OperationNode
|
|
14
15
|
name: string
|
|
15
16
|
tsResolver: ResolverTs
|
|
16
|
-
zodResolver?: ResolverZod
|
|
17
|
+
zodResolver?: ResolverZod | null
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
type Props = {
|
|
@@ -21,7 +22,7 @@ type Props = {
|
|
|
21
22
|
isExportable?: boolean
|
|
22
23
|
isIndexable?: boolean
|
|
23
24
|
operations: Array<OperationData>
|
|
24
|
-
baseURL: string | undefined
|
|
25
|
+
baseURL: string | null | undefined
|
|
25
26
|
dataReturnType: PluginClient['resolvedOptions']['dataReturnType']
|
|
26
27
|
paramsCasing: PluginClient['resolvedOptions']['paramsCasing']
|
|
27
28
|
paramsType: PluginClient['resolvedOptions']['pathParamsType']
|
|
@@ -34,8 +35,8 @@ type GenerateMethodProps = {
|
|
|
34
35
|
node: ast.OperationNode
|
|
35
36
|
name: string
|
|
36
37
|
tsResolver: ResolverTs
|
|
37
|
-
zodResolver?: ResolverZod
|
|
38
|
-
baseURL: string | undefined
|
|
38
|
+
zodResolver?: ResolverZod | null
|
|
39
|
+
baseURL: string | null | undefined
|
|
39
40
|
dataReturnType: PluginClient['resolvedOptions']['dataReturnType']
|
|
40
41
|
parser: PluginClient['resolvedOptions']['parser'] | undefined
|
|
41
42
|
paramsType: PluginClient['resolvedOptions']['paramsType']
|
|
@@ -58,25 +59,23 @@ function generateMethod({
|
|
|
58
59
|
pathParamsType,
|
|
59
60
|
}: GenerateMethodProps): string {
|
|
60
61
|
const path = new URLPath(node.path, { casing: paramsCasing })
|
|
61
|
-
const contentType = node
|
|
62
|
-
const isFormData = contentType === 'multipart/form-data'
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
: undefined
|
|
67
|
-
const headers = buildHeaders(contentType, !!headerParamsName)
|
|
62
|
+
const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node)
|
|
63
|
+
const isFormData = !isMultipleContentTypes && contentType === 'multipart/form-data'
|
|
64
|
+
const { header: headerParams } = getOperationParameters(node)
|
|
65
|
+
const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]!) : null
|
|
66
|
+
const headers = isMultipleContentTypes ? (headerParamsName ? ['...headers'] : []) : buildHeaders(contentType, !!headerParamsName)
|
|
68
67
|
const generics = buildGenerics(node, tsResolver)
|
|
69
|
-
const paramsNode =
|
|
68
|
+
const paramsNode = buildClientParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver, isConfigurable: true })
|
|
70
69
|
const paramsSignature = declarationPrinter.print(paramsNode) ?? ''
|
|
71
|
-
const clientParams = buildClassClientParams({ node, path, baseURL, tsResolver, isFormData, headers })
|
|
72
|
-
const jsdoc = buildJSDoc(
|
|
70
|
+
const clientParams = buildClassClientParams({ node, path, baseURL, tsResolver, isFormData, isMultipleContentTypes, hasFormData, headers })
|
|
71
|
+
const jsdoc = buildJSDoc(buildOperationComments(node, { link: 'urlPath', linkPosition: 'beforeDeprecated', splitLines: true }))
|
|
73
72
|
|
|
74
73
|
const requestDataLine = buildRequestDataLine({ parser, node, zodResolver })
|
|
75
|
-
const formDataLine = buildFormDataLine(isFormData, !!node.requestBody?.content?.[0]?.schema)
|
|
74
|
+
const formDataLine = buildFormDataLine(isFormData || (isMultipleContentTypes && hasFormData), !!node.requestBody?.content?.[0]?.schema)
|
|
76
75
|
const returnStatement = buildReturnStatement({ dataReturnType, parser, node, zodResolver })
|
|
77
76
|
|
|
78
77
|
const methodBody = [
|
|
79
|
-
|
|
78
|
+
`const { client: request = client, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ''}...requestConfig } = mergeConfig(this.#config, config)`,
|
|
80
79
|
'',
|
|
81
80
|
requestDataLine,
|
|
82
81
|
formDataLine,
|
|
@@ -135,4 +134,3 @@ ${methods.join('\n\n')}
|
|
|
135
134
|
</File.Source>
|
|
136
135
|
)
|
|
137
136
|
}
|
|
138
|
-
ClassClient.getParams = Client.getParams
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildOperationComments,
|
|
3
|
+
buildParamsMapping,
|
|
4
|
+
buildRequestConfigType,
|
|
5
|
+
getContentTypeInfo,
|
|
6
|
+
getOperationParameters,
|
|
7
|
+
resolveSuccessNames,
|
|
8
|
+
} from '@internals/shared'
|
|
1
9
|
import { isValidVarName, URLPath } from '@internals/utils'
|
|
2
10
|
import { ast } from '@kubb/core'
|
|
3
11
|
import type { ResolverTs } from '@kubb/plugin-ts'
|
|
@@ -7,8 +15,7 @@ import { File, Function } from '@kubb/renderer-jsx'
|
|
|
7
15
|
import type { KubbReactNode } from '@kubb/renderer-jsx/types'
|
|
8
16
|
import { createFunctionParams } from '../functionParams.ts'
|
|
9
17
|
import type { PluginClient } from '../types.ts'
|
|
10
|
-
import {
|
|
11
|
-
import { Url } from './Url.tsx'
|
|
18
|
+
import { buildUrlParamsNode } from './Url.tsx'
|
|
12
19
|
|
|
13
20
|
type Props = {
|
|
14
21
|
name: string
|
|
@@ -18,7 +25,7 @@ type Props = {
|
|
|
18
25
|
isConfigurable?: boolean
|
|
19
26
|
returnType?: string
|
|
20
27
|
|
|
21
|
-
baseURL: string | undefined
|
|
28
|
+
baseURL: string | null | undefined
|
|
22
29
|
dataReturnType: PluginClient['resolvedOptions']['dataReturnType']
|
|
23
30
|
paramsCasing: PluginClient['resolvedOptions']['paramsCasing']
|
|
24
31
|
paramsType: PluginClient['resolvedOptions']['pathParamsType']
|
|
@@ -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
|
|
|
@@ -41,26 +48,33 @@ type GetParamsProps = {
|
|
|
41
48
|
|
|
42
49
|
const declarationPrinter = functionPrinter({ mode: 'declaration' })
|
|
43
50
|
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
export function buildClientParamsNode({
|
|
52
|
+
paramsType,
|
|
53
|
+
paramsCasing,
|
|
54
|
+
pathParamsType,
|
|
55
|
+
node,
|
|
56
|
+
tsResolver,
|
|
57
|
+
isConfigurable,
|
|
58
|
+
}: GetParamsProps): ast.FunctionParametersNode {
|
|
47
59
|
return ast.createOperationParams(node, {
|
|
48
60
|
paramsType,
|
|
49
61
|
pathParamsType: paramsType === 'object' ? 'object' : pathParamsType === 'object' ? 'object' : 'inline',
|
|
50
62
|
paramsCasing,
|
|
51
63
|
resolver: tsResolver,
|
|
52
|
-
extraParams:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
64
|
+
extraParams: [
|
|
65
|
+
...(isConfigurable
|
|
66
|
+
? [
|
|
67
|
+
ast.createFunctionParameter({
|
|
68
|
+
name: 'config',
|
|
69
|
+
type: ast.createParamsType({
|
|
70
|
+
variant: 'reference',
|
|
71
|
+
name: buildRequestConfigType(node, tsResolver),
|
|
72
|
+
}),
|
|
73
|
+
default: '{}',
|
|
59
74
|
}),
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
: [],
|
|
75
|
+
]
|
|
76
|
+
: []),
|
|
77
|
+
],
|
|
64
78
|
})
|
|
65
79
|
}
|
|
66
80
|
|
|
@@ -83,27 +97,24 @@ export function Client({
|
|
|
83
97
|
isConfigurable = true,
|
|
84
98
|
}: Props): KubbReactNode {
|
|
85
99
|
const path = new URLPath(node.path)
|
|
86
|
-
const contentType = node
|
|
87
|
-
const isFormData = contentType === 'multipart/form-data'
|
|
100
|
+
const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node)
|
|
101
|
+
const isFormData = !isMultipleContentTypes && contentType === 'multipart/form-data'
|
|
88
102
|
|
|
89
|
-
const originalPathParams
|
|
90
|
-
const casedPathParams =
|
|
91
|
-
const originalQueryParams = node.parameters.filter((p) => p.in === 'query')
|
|
92
|
-
const casedQueryParams = ast.caseParams(originalQueryParams, paramsCasing)
|
|
93
|
-
const originalHeaderParams = node.parameters.filter((p) => p.in === 'header')
|
|
94
|
-
const casedHeaderParams = ast.caseParams(originalHeaderParams, paramsCasing)
|
|
103
|
+
const { path: originalPathParams, query: originalQueryParams, header: originalHeaderParams } = getOperationParameters(node)
|
|
104
|
+
const { path: casedPathParams, query: casedQueryParams, header: casedHeaderParams } = getOperationParameters(node, { paramsCasing })
|
|
95
105
|
|
|
96
|
-
const pathParamsMapping = paramsCasing && !urlName ? buildParamsMapping(originalPathParams, casedPathParams) :
|
|
97
|
-
const queryParamsMapping = paramsCasing ? buildParamsMapping(originalQueryParams, casedQueryParams) :
|
|
98
|
-
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
|
|
99
109
|
|
|
100
|
-
const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) :
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
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
|
|
104
115
|
|
|
105
|
-
const zodResponseName = zodResolver && parser === 'zod' ? zodResolver.resolveResponseName?.(node) :
|
|
106
|
-
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
|
|
107
118
|
|
|
108
119
|
const errorNames = node.responses
|
|
109
120
|
.filter((r) => {
|
|
@@ -113,14 +124,14 @@ export function Client({
|
|
|
113
124
|
.map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode))
|
|
114
125
|
|
|
115
126
|
const headers = [
|
|
116
|
-
contentType !== 'application/json' && contentType !== 'multipart/form-data' ? `'Content-Type': '${contentType}'` :
|
|
117
|
-
headerParamsName ? (headerParamsMapping ? '...mappedHeaders' : '...headers') :
|
|
127
|
+
!isMultipleContentTypes && contentType !== 'application/json' && contentType !== 'multipart/form-data' ? `'Content-Type': '${contentType}'` : null,
|
|
128
|
+
headerParamsName ? (headerParamsMapping ? '...mappedHeaders' : '...headers') : null,
|
|
118
129
|
].filter(Boolean)
|
|
119
130
|
|
|
120
131
|
const TError = `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(' | ') : 'Error'}>`
|
|
121
132
|
|
|
122
133
|
const generics = [responseName, TError, requestName || 'unknown'].filter(Boolean)
|
|
123
|
-
const paramsNode =
|
|
134
|
+
const paramsNode = buildClientParamsNode({
|
|
124
135
|
paramsType,
|
|
125
136
|
paramsCasing,
|
|
126
137
|
pathParamsType,
|
|
@@ -130,7 +141,7 @@ export function Client({
|
|
|
130
141
|
})
|
|
131
142
|
const paramsSignature = declarationPrinter.print(paramsNode) ?? ''
|
|
132
143
|
|
|
133
|
-
const urlParamsNode =
|
|
144
|
+
const urlParamsNode = buildUrlParamsNode({
|
|
134
145
|
paramsType,
|
|
135
146
|
paramsCasing,
|
|
136
147
|
pathParamsType,
|
|
@@ -155,23 +166,29 @@ export function Client({
|
|
|
155
166
|
? {
|
|
156
167
|
value: `\`${baseURL}\``,
|
|
157
168
|
}
|
|
158
|
-
:
|
|
159
|
-
params: queryParamsName ? (queryParamsMapping ? { value: 'mappedParams' } : {}) :
|
|
169
|
+
: null,
|
|
170
|
+
params: queryParamsName ? (queryParamsMapping ? { value: 'mappedParams' } : {}) : null,
|
|
160
171
|
data: requestName
|
|
161
172
|
? {
|
|
162
|
-
value:
|
|
173
|
+
value:
|
|
174
|
+
isMultipleContentTypes && hasFormData
|
|
175
|
+
? "contentType === 'multipart/form-data' ? formData as FormData : requestData"
|
|
176
|
+
: isFormData
|
|
177
|
+
? 'formData as FormData'
|
|
178
|
+
: 'requestData',
|
|
163
179
|
}
|
|
164
|
-
:
|
|
180
|
+
: null,
|
|
181
|
+
contentType: isConfigurable && isMultipleContentTypes ? {} : null,
|
|
165
182
|
requestConfig: isConfigurable
|
|
166
183
|
? {
|
|
167
184
|
mode: 'inlineSpread',
|
|
168
185
|
}
|
|
169
|
-
:
|
|
186
|
+
: null,
|
|
170
187
|
headers: headers.length
|
|
171
188
|
? {
|
|
172
189
|
value: isConfigurable ? `{ ${headers.join(', ')}, ...requestConfig.headers }` : `{ ${headers.join(', ')} }`,
|
|
173
190
|
}
|
|
174
|
-
:
|
|
191
|
+
: null,
|
|
175
192
|
},
|
|
176
193
|
},
|
|
177
194
|
})
|
|
@@ -198,11 +215,13 @@ export function Client({
|
|
|
198
215
|
export={isExportable}
|
|
199
216
|
params={paramsSignature}
|
|
200
217
|
JSDoc={{
|
|
201
|
-
comments:
|
|
218
|
+
comments: buildOperationComments(node, { link: 'urlPath', linkPosition: 'beforeDeprecated', splitLines: true }),
|
|
202
219
|
}}
|
|
203
220
|
returnType={returnType}
|
|
204
221
|
>
|
|
205
|
-
{isConfigurable
|
|
222
|
+
{isConfigurable
|
|
223
|
+
? `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ''}...requestConfig } = config`
|
|
224
|
+
: ''}
|
|
206
225
|
<br />
|
|
207
226
|
<br />
|
|
208
227
|
{pathParamsMapping &&
|
|
@@ -236,11 +255,11 @@ export function Client({
|
|
|
236
255
|
)}
|
|
237
256
|
{parser === 'zod' && zodRequestName ? `const requestData = ${zodRequestName}.parse(data)` : requestName && 'const requestData = data'}
|
|
238
257
|
<br />
|
|
239
|
-
{isFormData && requestName && 'const formData = buildFormData(requestData)'}
|
|
258
|
+
{(isFormData || (isMultipleContentTypes && hasFormData)) && requestName && 'const formData = buildFormData(requestData)'}
|
|
240
259
|
<br />
|
|
241
260
|
{isConfigurable
|
|
242
261
|
? `const res = await request<${generics.join(', ')}>(${clientParams.toCall()})`
|
|
243
|
-
: `const res = await
|
|
262
|
+
: `const res = await client<${generics.join(', ')}>(${clientParams.toCall()})`}
|
|
244
263
|
<br />
|
|
245
264
|
{childrenElement}
|
|
246
265
|
</Function>
|
|
@@ -248,5 +267,3 @@ export function Client({
|
|
|
248
267
|
</>
|
|
249
268
|
)
|
|
250
269
|
}
|
|
251
|
-
|
|
252
|
-
Client.getParams = getParams
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildOperationComments, getContentTypeInfo, getOperationParameters } from '@internals/shared'
|
|
1
2
|
import { buildJSDoc, URLPath } from '@internals/utils'
|
|
2
3
|
import type { ast } from '@kubb/core'
|
|
3
4
|
import type { ResolverTs } from '@kubb/plugin-ts'
|
|
@@ -6,14 +7,14 @@ import type { ResolverZod } from '@kubb/plugin-zod'
|
|
|
6
7
|
import { File } from '@kubb/renderer-jsx'
|
|
7
8
|
import type { KubbReactNode } from '@kubb/renderer-jsx/types'
|
|
8
9
|
import type { PluginClient } from '../types.ts'
|
|
9
|
-
import { buildClassClientParams, buildFormDataLine, buildGenerics, buildHeaders, buildRequestDataLine, buildReturnStatement
|
|
10
|
-
import {
|
|
10
|
+
import { buildClassClientParams, buildFormDataLine, buildGenerics, buildHeaders, buildRequestDataLine, buildReturnStatement } from '../utils.ts'
|
|
11
|
+
import { buildClientParamsNode } from './Client.tsx'
|
|
11
12
|
|
|
12
13
|
type OperationData = {
|
|
13
14
|
node: ast.OperationNode
|
|
14
15
|
name: string
|
|
15
16
|
tsResolver: ResolverTs
|
|
16
|
-
zodResolver?: ResolverZod
|
|
17
|
+
zodResolver?: ResolverZod | null
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
type Props = {
|
|
@@ -21,7 +22,7 @@ type Props = {
|
|
|
21
22
|
isExportable?: boolean
|
|
22
23
|
isIndexable?: boolean
|
|
23
24
|
operations: Array<OperationData>
|
|
24
|
-
baseURL: string | undefined
|
|
25
|
+
baseURL: string | null | undefined
|
|
25
26
|
dataReturnType: PluginClient['resolvedOptions']['dataReturnType']
|
|
26
27
|
paramsCasing: PluginClient['resolvedOptions']['paramsCasing']
|
|
27
28
|
paramsType: PluginClient['resolvedOptions']['pathParamsType']
|
|
@@ -34,8 +35,8 @@ type GenerateMethodProps = {
|
|
|
34
35
|
node: ast.OperationNode
|
|
35
36
|
name: string
|
|
36
37
|
tsResolver: ResolverTs
|
|
37
|
-
zodResolver?: ResolverZod
|
|
38
|
-
baseURL: string | undefined
|
|
38
|
+
zodResolver?: ResolverZod | null
|
|
39
|
+
baseURL: string | null | undefined
|
|
39
40
|
dataReturnType: PluginClient['resolvedOptions']['dataReturnType']
|
|
40
41
|
parser: PluginClient['resolvedOptions']['parser'] | undefined
|
|
41
42
|
paramsType: PluginClient['resolvedOptions']['paramsType']
|
|
@@ -58,25 +59,23 @@ function generateMethod({
|
|
|
58
59
|
pathParamsType,
|
|
59
60
|
}: GenerateMethodProps): string {
|
|
60
61
|
const path = new URLPath(node.path, { casing: paramsCasing })
|
|
61
|
-
const contentType = node
|
|
62
|
-
const isFormData = contentType === 'multipart/form-data'
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
: undefined
|
|
67
|
-
const headers = buildHeaders(contentType, !!headerParamsName)
|
|
62
|
+
const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node)
|
|
63
|
+
const isFormData = !isMultipleContentTypes && contentType === 'multipart/form-data'
|
|
64
|
+
const { header: headerParams } = getOperationParameters(node)
|
|
65
|
+
const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]!) : null
|
|
66
|
+
const headers = isMultipleContentTypes ? (headerParamsName ? ['...headers'] : []) : buildHeaders(contentType, !!headerParamsName)
|
|
68
67
|
const generics = buildGenerics(node, tsResolver)
|
|
69
|
-
const paramsNode =
|
|
68
|
+
const paramsNode = buildClientParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver, isConfigurable: true })
|
|
70
69
|
const paramsSignature = declarationPrinter.print(paramsNode) ?? ''
|
|
71
|
-
const clientParams = buildClassClientParams({ node, path, baseURL, tsResolver, isFormData, headers })
|
|
72
|
-
const jsdoc = buildJSDoc(
|
|
70
|
+
const clientParams = buildClassClientParams({ node, path, baseURL, tsResolver, isFormData, isMultipleContentTypes, hasFormData, headers })
|
|
71
|
+
const jsdoc = buildJSDoc(buildOperationComments(node, { link: 'urlPath', linkPosition: 'beforeDeprecated', splitLines: true }))
|
|
73
72
|
|
|
74
73
|
const requestDataLine = buildRequestDataLine({ parser, node, zodResolver })
|
|
75
|
-
const formDataLine = buildFormDataLine(isFormData, !!node.requestBody?.content?.[0]?.schema)
|
|
74
|
+
const formDataLine = buildFormDataLine(isFormData || (isMultipleContentTypes && hasFormData), !!node.requestBody?.content?.[0]?.schema)
|
|
76
75
|
const returnStatement = buildReturnStatement({ dataReturnType, parser, node, zodResolver })
|
|
77
76
|
|
|
78
77
|
const methodBody = [
|
|
79
|
-
|
|
78
|
+
`const { client: request = client, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ''}...requestConfig } = mergeConfig(this.#config, config)`,
|
|
80
79
|
'',
|
|
81
80
|
requestDataLine,
|
|
82
81
|
formDataLine,
|
|
@@ -127,4 +126,3 @@ export function StaticClassClient({
|
|
|
127
126
|
</File.Source>
|
|
128
127
|
)
|
|
129
128
|
}
|
|
130
|
-
StaticClassClient.getParams = Client.getParams
|
package/src/components/Url.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildParamsMapping, getOperationParameters } from '@internals/shared'
|
|
1
2
|
import { isValidVarName, URLPath } from '@internals/utils'
|
|
2
3
|
import { ast } from '@kubb/core'
|
|
3
4
|
import type { ResolverTs } from '@kubb/plugin-ts'
|
|
@@ -5,14 +6,13 @@ import { functionPrinter } from '@kubb/plugin-ts'
|
|
|
5
6
|
import { Const, File, Function } from '@kubb/renderer-jsx'
|
|
6
7
|
import type { KubbReactNode } from '@kubb/renderer-jsx/types'
|
|
7
8
|
import type { PluginClient } from '../types.ts'
|
|
8
|
-
import { buildParamsMapping } from '../utils.ts'
|
|
9
9
|
|
|
10
10
|
type Props = {
|
|
11
11
|
name: string
|
|
12
12
|
isExportable?: boolean
|
|
13
13
|
isIndexable?: boolean
|
|
14
14
|
|
|
15
|
-
baseURL: string | undefined
|
|
15
|
+
baseURL: string | null | undefined
|
|
16
16
|
paramsCasing: PluginClient['resolvedOptions']['paramsCasing']
|
|
17
17
|
paramsType: PluginClient['resolvedOptions']['pathParamsType']
|
|
18
18
|
pathParamsType: PluginClient['resolvedOptions']['pathParamsType']
|
|
@@ -30,7 +30,7 @@ type GetParamsProps = {
|
|
|
30
30
|
|
|
31
31
|
const declarationPrinter = functionPrinter({ mode: 'declaration' })
|
|
32
32
|
|
|
33
|
-
function
|
|
33
|
+
export function buildUrlParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver }: GetParamsProps): ast.FunctionParametersNode {
|
|
34
34
|
// Build a URL-only node with only path params (no body, query, header)
|
|
35
35
|
const urlNode: ast.OperationNode = {
|
|
36
36
|
...node,
|
|
@@ -59,7 +59,7 @@ export function Url({
|
|
|
59
59
|
}: Props): KubbReactNode {
|
|
60
60
|
const path = new URLPath(node.path)
|
|
61
61
|
|
|
62
|
-
const paramsNode =
|
|
62
|
+
const paramsNode = buildUrlParamsNode({
|
|
63
63
|
paramsType,
|
|
64
64
|
paramsCasing,
|
|
65
65
|
pathParamsType,
|
|
@@ -68,9 +68,9 @@ export function Url({
|
|
|
68
68
|
})
|
|
69
69
|
const paramsSignature = declarationPrinter.print(paramsNode) ?? ''
|
|
70
70
|
|
|
71
|
-
const originalPathParams = node
|
|
72
|
-
const casedPathParams =
|
|
73
|
-
const pathParamsMapping = paramsCasing ? buildParamsMapping(originalPathParams, casedPathParams) :
|
|
71
|
+
const { path: originalPathParams } = getOperationParameters(node)
|
|
72
|
+
const { path: casedPathParams } = getOperationParameters(node, { paramsCasing })
|
|
73
|
+
const pathParamsMapping = paramsCasing ? buildParamsMapping(originalPathParams, casedPathParams) : null
|
|
74
74
|
|
|
75
75
|
return (
|
|
76
76
|
<File.Source name={name} isExportable={isExportable} isIndexable={isIndexable}>
|
|
@@ -88,5 +88,3 @@ export function Url({
|
|
|
88
88
|
</File.Source>
|
|
89
89
|
)
|
|
90
90
|
}
|
|
91
|
-
|
|
92
|
-
Url.getParams = getParams
|