@kubb/plugin-client 5.0.0-beta.56 → 5.5.2
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 +8 -61
- package/dist/index.cjs +754 -1745
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +170 -349
- package/dist/index.js +733 -1733
- package/dist/index.js.map +1 -1
- package/package.json +10 -63
- package/src/generators/clientGenerator.tsx +6 -132
- package/src/index.ts +1 -9
- package/src/plugin.ts +32 -122
- package/src/types.ts +27 -249
- package/dist/chunk-Bx3C2hgW.cjs +0 -38
- package/dist/clients/axios.cjs +0 -68
- package/dist/clients/axios.cjs.map +0 -1
- package/dist/clients/axios.d.ts +0 -49
- package/dist/clients/axios.js +0 -58
- package/dist/clients/axios.js.map +0 -1
- package/dist/clients/fetch.cjs +0 -128
- package/dist/clients/fetch.cjs.map +0 -1
- package/dist/clients/fetch.d.ts +0 -50
- package/dist/clients/fetch.js +0 -120
- package/dist/clients/fetch.js.map +0 -1
- package/dist/templates/clients/axios.source.cjs +0 -7
- package/dist/templates/clients/axios.source.cjs.map +0 -1
- package/dist/templates/clients/axios.source.d.ts +0 -7
- package/dist/templates/clients/axios.source.js +0 -6
- package/dist/templates/clients/axios.source.js.map +0 -1
- package/dist/templates/clients/fetch.source.cjs +0 -7
- package/dist/templates/clients/fetch.source.cjs.map +0 -1
- package/dist/templates/clients/fetch.source.d.ts +0 -7
- package/dist/templates/clients/fetch.source.js +0 -6
- package/dist/templates/clients/fetch.source.js.map +0 -1
- package/dist/templates/config.source.cjs +0 -7
- package/dist/templates/config.source.cjs.map +0 -1
- package/dist/templates/config.source.d.ts +0 -7
- package/dist/templates/config.source.js +0 -6
- package/dist/templates/config.source.js.map +0 -1
- package/src/clients/axios.ts +0 -113
- package/src/clients/fetch.ts +0 -201
- package/src/components/ClassClient.tsx +0 -161
- package/src/components/Client.tsx +0 -281
- package/src/components/StaticClassClient.tsx +0 -153
- package/src/components/Url.tsx +0 -90
- package/src/components/WrapperClient.tsx +0 -33
- package/src/functionParams.ts +0 -118
- package/src/generators/classClientGenerator.tsx +0 -261
- package/src/generators/groupedClientGenerator.tsx +0 -82
- package/src/generators/operationsGenerator.ts +0 -47
- package/src/generators/staticClassClientGenerator.tsx +0 -240
- package/src/resolvers/resolverClient.ts +0 -46
- package/src/templates/clients/axios.source.ts +0 -4
- package/src/templates/clients/fetch.source.ts +0 -4
- package/src/templates/config.source.ts +0 -4
- package/src/utils.ts +0 -259
- /package/dist/{chunk-C0LytTxp.js → rolldown-runtime-C0LytTxp.js} +0 -0
package/src/clients/fetch.ts
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RequestCredentials
|
|
3
|
-
*/
|
|
4
|
-
export type RequestCredentials = 'omit' | 'same-origin' | 'include'
|
|
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
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Subset of FetchRequestConfig
|
|
15
|
-
*/
|
|
16
|
-
export type RequestConfig<TData = unknown> = {
|
|
17
|
-
baseURL?: string
|
|
18
|
-
url?: string
|
|
19
|
-
method?: 'GET' | 'PUT' | 'PATCH' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD'
|
|
20
|
-
params?: unknown
|
|
21
|
-
data?: TData | FormData
|
|
22
|
-
responseType?: 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'
|
|
23
|
-
signal?: AbortSignal
|
|
24
|
-
headers?: HeadersInit
|
|
25
|
-
credentials?: RequestCredentials
|
|
26
|
-
contentType?: string
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Subset of FetchResponse
|
|
31
|
-
*/
|
|
32
|
-
export type ResponseConfig<TData = unknown> = {
|
|
33
|
-
data: TData
|
|
34
|
-
status: number
|
|
35
|
-
statusText: string
|
|
36
|
-
headers: Headers
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
let _config: Partial<RequestConfig> = {}
|
|
40
|
-
|
|
41
|
-
export const getConfig = () => _config
|
|
42
|
-
|
|
43
|
-
export const setConfig = (config: Partial<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
|
-
...(Array.isArray(merged.headers) ? Object.fromEntries(merged.headers) : merged.headers),
|
|
55
|
-
...(Array.isArray(config.headers) ? Object.fromEntries(config.headers) : config.headers),
|
|
56
|
-
},
|
|
57
|
-
}
|
|
58
|
-
}, {})
|
|
59
|
-
}
|
|
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
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Serializes the request body into the form `fetch` expects.
|
|
79
|
-
* `FormData`, `URLSearchParams`, `Blob`, `ArrayBuffer` and string bodies are passed through
|
|
80
|
-
* untouched. Plain objects are encoded as `URLSearchParams` for `application/x-www-form-urlencoded`
|
|
81
|
-
* and JSON-serialized otherwise.
|
|
82
|
-
*/
|
|
83
|
-
function serializeBody(data: unknown, contentType?: string): BodyInit | undefined {
|
|
84
|
-
if (data === undefined || data === null) return undefined
|
|
85
|
-
if (data instanceof FormData || data instanceof URLSearchParams || data instanceof Blob || data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
|
|
86
|
-
return data as BodyInit
|
|
87
|
-
}
|
|
88
|
-
if (typeof data === 'string') return data
|
|
89
|
-
if (contentType?.includes('application/x-www-form-urlencoded')) {
|
|
90
|
-
return new URLSearchParams(data as Record<string, string>)
|
|
91
|
-
}
|
|
92
|
-
return JSON.stringify(data)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Picks a `responseType` from a `Content-Type` header, or `undefined` when it is not recognized.
|
|
97
|
-
*/
|
|
98
|
-
function detectResponseType(contentType: string | null): RequestConfig['responseType'] {
|
|
99
|
-
if (!contentType) return undefined
|
|
100
|
-
if (contentType.includes('application/json') || contentType.includes('text/json')) return 'json'
|
|
101
|
-
if (contentType.includes('text/')) return 'text'
|
|
102
|
-
if (contentType.includes('image/') || contentType.includes('application/octet-stream')) return 'blob'
|
|
103
|
-
return undefined
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Parses a `fetch` response body.
|
|
108
|
-
*
|
|
109
|
-
* - Empty responses (204/205/304 or no body) resolve to `{}`.
|
|
110
|
-
* - An explicit `responseType` (or one detected from the `Content-Type` header) forces the matching
|
|
111
|
-
* `Response` method.
|
|
112
|
-
* - As a last resort the body is read as text and `JSON.parse`d, falling back to the raw text.
|
|
113
|
-
*/
|
|
114
|
-
async function parseResponse<TData>(response: Response, responseType?: RequestConfig['responseType']): Promise<TData> {
|
|
115
|
-
if ([204, 205, 304].includes(response.status) || !response.body) {
|
|
116
|
-
return {} as TData
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
switch (responseType ?? detectResponseType(response.headers.get('Content-Type'))) {
|
|
120
|
-
case 'text':
|
|
121
|
-
case 'document':
|
|
122
|
-
return (await response.text()) as TData
|
|
123
|
-
case 'blob':
|
|
124
|
-
return (await response.blob()) as TData
|
|
125
|
-
case 'arraybuffer':
|
|
126
|
-
return (await response.arrayBuffer()) as TData
|
|
127
|
-
case 'stream':
|
|
128
|
-
return response.body as TData
|
|
129
|
-
case 'json':
|
|
130
|
-
return (await response.json()) as TData
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Explicit but unrecognized responseType keeps the JSON default; otherwise read text and parse it.
|
|
134
|
-
if (responseType) {
|
|
135
|
-
return (await response.json()) as TData
|
|
136
|
-
}
|
|
137
|
-
const text = await response.text()
|
|
138
|
-
if (!text) {
|
|
139
|
-
return {} as TData
|
|
140
|
-
}
|
|
141
|
-
try {
|
|
142
|
-
return JSON.parse(text) as TData
|
|
143
|
-
} catch {
|
|
144
|
-
return text as TData
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export type ResponseErrorConfig<TError = unknown> = TError
|
|
149
|
-
|
|
150
|
-
export type Client = <TResponseData, _TError = unknown, TRequestData = unknown>(
|
|
151
|
-
config: RequestConfig<TRequestData>,
|
|
152
|
-
request?: unknown,
|
|
153
|
-
) => Promise<ResponseConfig<TResponseData>>
|
|
154
|
-
|
|
155
|
-
export const client = async <TResponseData, _TError = unknown, RequestData = unknown>(
|
|
156
|
-
paramsConfig: RequestConfig<RequestData>,
|
|
157
|
-
_request?: unknown,
|
|
158
|
-
): Promise<ResponseConfig<TResponseData>> => {
|
|
159
|
-
const normalizedParams = new URLSearchParams()
|
|
160
|
-
|
|
161
|
-
const config = mergeConfig(getConfig(), paramsConfig)
|
|
162
|
-
|
|
163
|
-
Object.entries(config.params || {}).forEach(([key, value]) => {
|
|
164
|
-
if (value !== undefined) {
|
|
165
|
-
normalizedParams.append(key, value === null ? 'null' : value.toString())
|
|
166
|
-
}
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
let targetUrl = [config.baseURL, config.url].filter(Boolean).join('')
|
|
170
|
-
|
|
171
|
-
if (config.params) {
|
|
172
|
-
targetUrl += `?${normalizedParams}`
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const headers: Record<string, string> = {
|
|
176
|
-
...(config.contentType && config.contentType !== 'multipart/form-data' ? { 'Content-Type': config.contentType } : {}),
|
|
177
|
-
...serializeHeaders(config.headers),
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const response = await globalThis.fetch(targetUrl, {
|
|
181
|
-
credentials: config.credentials || 'same-origin',
|
|
182
|
-
method: config.method?.toUpperCase(),
|
|
183
|
-
body: serializeBody(config.data, headers['Content-Type'] ?? headers['content-type']),
|
|
184
|
-
signal: config.signal,
|
|
185
|
-
headers,
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
const data = await parseResponse<TResponseData>(response, config.responseType)
|
|
189
|
-
|
|
190
|
-
return {
|
|
191
|
-
data,
|
|
192
|
-
status: response.status,
|
|
193
|
-
statusText: response.statusText,
|
|
194
|
-
headers: response.headers as Headers,
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
client.getConfig = getConfig
|
|
199
|
-
client.setConfig = setConfig
|
|
200
|
-
|
|
201
|
-
export default client
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import { buildOperationComments, getContentTypeInfo, getOperationParameters } from '@internals/shared'
|
|
2
|
-
import { Url } from '@internals/utils'
|
|
3
|
-
import { buildJSDoc, stringify } from '@kubb/ast/utils'
|
|
4
|
-
import { ast } from '@kubb/core'
|
|
5
|
-
import type { ResolverTs } from '@kubb/plugin-ts'
|
|
6
|
-
import { functionPrinter } from '@kubb/plugin-ts'
|
|
7
|
-
import type { ResolverZod } from '@kubb/plugin-zod'
|
|
8
|
-
import { File } from '@kubb/renderer-jsx'
|
|
9
|
-
import type { KubbReactNode } from '@kubb/renderer-jsx/types'
|
|
10
|
-
import type { PluginClient } from '../types.ts'
|
|
11
|
-
import {
|
|
12
|
-
buildClassClientParams,
|
|
13
|
-
buildFormDataLine,
|
|
14
|
-
buildGenerics,
|
|
15
|
-
buildHeaders,
|
|
16
|
-
buildQueryParamsLine,
|
|
17
|
-
buildRequestDataLine,
|
|
18
|
-
buildReturnStatement,
|
|
19
|
-
resolveQueryParamsParser,
|
|
20
|
-
} from '../utils.ts'
|
|
21
|
-
import { buildClientParamsNode } from './Client.tsx'
|
|
22
|
-
|
|
23
|
-
type OperationData = {
|
|
24
|
-
node: ast.OperationNode
|
|
25
|
-
name: string
|
|
26
|
-
tsResolver: ResolverTs
|
|
27
|
-
zodResolver?: ResolverZod | null
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
type Props = {
|
|
31
|
-
name: string
|
|
32
|
-
isExportable?: boolean
|
|
33
|
-
isIndexable?: boolean
|
|
34
|
-
operations: Array<OperationData>
|
|
35
|
-
baseURL: string | null | undefined
|
|
36
|
-
dataReturnType: PluginClient['resolvedOptions']['dataReturnType']
|
|
37
|
-
paramsCasing: PluginClient['resolvedOptions']['paramsCasing']
|
|
38
|
-
paramsType: PluginClient['resolvedOptions']['pathParamsType']
|
|
39
|
-
pathParamsType: PluginClient['resolvedOptions']['pathParamsType']
|
|
40
|
-
parser: PluginClient['resolvedOptions']['parser'] | undefined
|
|
41
|
-
children?: KubbReactNode
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
type GenerateMethodProps = {
|
|
45
|
-
node: ast.OperationNode
|
|
46
|
-
name: string
|
|
47
|
-
tsResolver: ResolverTs
|
|
48
|
-
zodResolver?: ResolverZod | null
|
|
49
|
-
baseURL: string | null | undefined
|
|
50
|
-
dataReturnType: PluginClient['resolvedOptions']['dataReturnType']
|
|
51
|
-
parser: PluginClient['resolvedOptions']['parser'] | undefined
|
|
52
|
-
paramsType: PluginClient['resolvedOptions']['paramsType']
|
|
53
|
-
paramsCasing: PluginClient['resolvedOptions']['paramsCasing']
|
|
54
|
-
pathParamsType: PluginClient['resolvedOptions']['pathParamsType']
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const declarationPrinter = functionPrinter({ mode: 'declaration' })
|
|
58
|
-
|
|
59
|
-
function generateMethod({
|
|
60
|
-
node,
|
|
61
|
-
name,
|
|
62
|
-
tsResolver,
|
|
63
|
-
zodResolver,
|
|
64
|
-
baseURL,
|
|
65
|
-
dataReturnType,
|
|
66
|
-
parser,
|
|
67
|
-
paramsType,
|
|
68
|
-
paramsCasing,
|
|
69
|
-
pathParamsType,
|
|
70
|
-
}: GenerateMethodProps): string {
|
|
71
|
-
if (!ast.isHttpOperationNode(node)) return ''
|
|
72
|
-
const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node)
|
|
73
|
-
const isFormData = !isMultipleContentTypes && contentType === 'multipart/form-data'
|
|
74
|
-
const { header: headerParams } = getOperationParameters(node)
|
|
75
|
-
const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]!) : null
|
|
76
|
-
const headers = isMultipleContentTypes ? (headerParamsName ? ['...headers'] : []) : buildHeaders(contentType, !!headerParamsName)
|
|
77
|
-
const generics = buildGenerics(node, tsResolver, { dataReturnType, zodResolver, parser })
|
|
78
|
-
const paramsNode = buildClientParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver, isConfigurable: true })
|
|
79
|
-
const paramsSignature = declarationPrinter.print(paramsNode) ?? ''
|
|
80
|
-
const { query: queryParams } = getOperationParameters(node)
|
|
81
|
-
const zodQueryParamsName =
|
|
82
|
-
zodResolver && resolveQueryParamsParser(parser) === 'zod' && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]!) : null
|
|
83
|
-
const clientParams = buildClassClientParams({
|
|
84
|
-
node,
|
|
85
|
-
url: Url.toTemplateString(node.path, { casing: paramsCasing }),
|
|
86
|
-
baseURL,
|
|
87
|
-
tsResolver,
|
|
88
|
-
isFormData,
|
|
89
|
-
isMultipleContentTypes,
|
|
90
|
-
hasFormData,
|
|
91
|
-
headers,
|
|
92
|
-
zodQueryParamsName,
|
|
93
|
-
})
|
|
94
|
-
const jsdoc = buildJSDoc(buildOperationComments(node, { link: 'urlPath', linkPosition: 'beforeDeprecated', splitLines: true }))
|
|
95
|
-
|
|
96
|
-
const requestDataLine = buildRequestDataLine({ parser, node, zodResolver })
|
|
97
|
-
const queryParamsLine = buildQueryParamsLine({ parser, node, zodResolver })
|
|
98
|
-
const formDataLine = buildFormDataLine(isFormData || (isMultipleContentTypes && hasFormData), !!node.requestBody?.content?.[0]?.schema)
|
|
99
|
-
const returnStatement = buildReturnStatement({ dataReturnType, parser, node, zodResolver, tsResolver })
|
|
100
|
-
|
|
101
|
-
const methodBody = [
|
|
102
|
-
`const { client: request = client, ${isMultipleContentTypes ? `contentType = ${stringify(contentType)}, ` : ''}...requestConfig } = mergeConfig(this.#config, config)`,
|
|
103
|
-
'',
|
|
104
|
-
requestDataLine,
|
|
105
|
-
queryParamsLine,
|
|
106
|
-
formDataLine,
|
|
107
|
-
`const res = await request<${generics.join(', ')}>(${clientParams.toCall()})`,
|
|
108
|
-
returnStatement,
|
|
109
|
-
]
|
|
110
|
-
.filter(Boolean)
|
|
111
|
-
.map((line) => ` ${line}`)
|
|
112
|
-
.join('\n')
|
|
113
|
-
|
|
114
|
-
return `${jsdoc}async ${name}(${paramsSignature}) {\n${methodBody}\n }`
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export function ClassClient({
|
|
118
|
-
name,
|
|
119
|
-
isExportable = true,
|
|
120
|
-
isIndexable = true,
|
|
121
|
-
operations,
|
|
122
|
-
baseURL,
|
|
123
|
-
dataReturnType,
|
|
124
|
-
parser,
|
|
125
|
-
paramsType,
|
|
126
|
-
paramsCasing,
|
|
127
|
-
pathParamsType,
|
|
128
|
-
children,
|
|
129
|
-
}: Props): KubbReactNode {
|
|
130
|
-
const methods = operations.map(({ node, name: methodName, tsResolver, zodResolver }) =>
|
|
131
|
-
generateMethod({
|
|
132
|
-
node,
|
|
133
|
-
name: methodName,
|
|
134
|
-
tsResolver,
|
|
135
|
-
zodResolver,
|
|
136
|
-
baseURL,
|
|
137
|
-
dataReturnType,
|
|
138
|
-
parser,
|
|
139
|
-
paramsType,
|
|
140
|
-
paramsCasing,
|
|
141
|
-
pathParamsType,
|
|
142
|
-
}),
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
const classCode = `export class ${name} {
|
|
146
|
-
#config: Partial<RequestConfig> & { client?: Client }
|
|
147
|
-
|
|
148
|
-
constructor(config: Partial<RequestConfig> & { client?: Client } = {}) {
|
|
149
|
-
this.#config = config
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
${methods.join('\n\n')}
|
|
153
|
-
}`
|
|
154
|
-
|
|
155
|
-
return (
|
|
156
|
-
<File.Source name={name} isExportable={isExportable} isIndexable={isIndexable}>
|
|
157
|
-
{classCode}
|
|
158
|
-
{children}
|
|
159
|
-
</File.Source>
|
|
160
|
-
)
|
|
161
|
-
}
|
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
buildOperationComments,
|
|
3
|
-
buildParamsMapping,
|
|
4
|
-
buildRequestConfigType,
|
|
5
|
-
getContentTypeInfo,
|
|
6
|
-
getOperationParameters,
|
|
7
|
-
getResponseType,
|
|
8
|
-
resolveSuccessNames,
|
|
9
|
-
} from '@internals/shared'
|
|
10
|
-
import { isValidVarName, Url } from '@internals/utils'
|
|
11
|
-
import { stringify } from '@kubb/ast/utils'
|
|
12
|
-
import { ast } from '@kubb/core'
|
|
13
|
-
import type { ResolverTs } from '@kubb/plugin-ts'
|
|
14
|
-
import { functionPrinter } from '@kubb/plugin-ts'
|
|
15
|
-
import type { ResolverZod } from '@kubb/plugin-zod'
|
|
16
|
-
import { File, Function } from '@kubb/renderer-jsx'
|
|
17
|
-
import type { KubbReactNode } from '@kubb/renderer-jsx/types'
|
|
18
|
-
import { createFunctionParams } from '../functionParams.ts'
|
|
19
|
-
import type { PluginClient } from '../types.ts'
|
|
20
|
-
import { buildStatusUnionType, resolveQueryParamsParser, resolveRequestParser, resolveResponseParser } from '../utils.ts'
|
|
21
|
-
import { buildUrlParamsNode } from './Url.tsx'
|
|
22
|
-
|
|
23
|
-
type Props = {
|
|
24
|
-
name: string
|
|
25
|
-
urlName?: string
|
|
26
|
-
isExportable?: boolean
|
|
27
|
-
isIndexable?: boolean
|
|
28
|
-
isConfigurable?: boolean
|
|
29
|
-
returnType?: string
|
|
30
|
-
|
|
31
|
-
baseURL: string | null | undefined
|
|
32
|
-
dataReturnType: PluginClient['resolvedOptions']['dataReturnType']
|
|
33
|
-
paramsCasing: PluginClient['resolvedOptions']['paramsCasing']
|
|
34
|
-
paramsType: PluginClient['resolvedOptions']['pathParamsType']
|
|
35
|
-
pathParamsType: PluginClient['resolvedOptions']['pathParamsType']
|
|
36
|
-
parser: PluginClient['resolvedOptions']['parser'] | undefined
|
|
37
|
-
node: ast.OperationNode
|
|
38
|
-
tsResolver: ResolverTs
|
|
39
|
-
zodResolver?: ResolverZod | null
|
|
40
|
-
children?: KubbReactNode
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
type GetParamsProps = {
|
|
44
|
-
paramsCasing: PluginClient['resolvedOptions']['paramsCasing']
|
|
45
|
-
paramsType: PluginClient['resolvedOptions']['paramsType']
|
|
46
|
-
pathParamsType: PluginClient['resolvedOptions']['pathParamsType']
|
|
47
|
-
node: ast.OperationNode
|
|
48
|
-
tsResolver: ResolverTs
|
|
49
|
-
isConfigurable: boolean
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const declarationPrinter = functionPrinter({ mode: 'declaration' })
|
|
53
|
-
|
|
54
|
-
export function buildClientParamsNode({
|
|
55
|
-
paramsType,
|
|
56
|
-
paramsCasing,
|
|
57
|
-
pathParamsType,
|
|
58
|
-
node,
|
|
59
|
-
tsResolver,
|
|
60
|
-
isConfigurable,
|
|
61
|
-
}: GetParamsProps): ast.FunctionParametersNode {
|
|
62
|
-
return ast.createOperationParams(node, {
|
|
63
|
-
paramsType,
|
|
64
|
-
pathParamsType: paramsType === 'object' ? 'object' : pathParamsType === 'object' ? 'object' : 'inline',
|
|
65
|
-
paramsCasing,
|
|
66
|
-
resolver: tsResolver,
|
|
67
|
-
extraParams: [
|
|
68
|
-
...(isConfigurable
|
|
69
|
-
? [
|
|
70
|
-
ast.createFunctionParameter({
|
|
71
|
-
name: 'config',
|
|
72
|
-
type: ast.createParamsType({
|
|
73
|
-
variant: 'reference',
|
|
74
|
-
name: buildRequestConfigType(node, tsResolver),
|
|
75
|
-
}),
|
|
76
|
-
default: '{}',
|
|
77
|
-
}),
|
|
78
|
-
]
|
|
79
|
-
: []),
|
|
80
|
-
],
|
|
81
|
-
})
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function Client({
|
|
85
|
-
name,
|
|
86
|
-
isExportable = true,
|
|
87
|
-
isIndexable = true,
|
|
88
|
-
returnType,
|
|
89
|
-
baseURL,
|
|
90
|
-
dataReturnType,
|
|
91
|
-
parser,
|
|
92
|
-
paramsType,
|
|
93
|
-
paramsCasing,
|
|
94
|
-
pathParamsType,
|
|
95
|
-
node,
|
|
96
|
-
tsResolver,
|
|
97
|
-
zodResolver,
|
|
98
|
-
urlName,
|
|
99
|
-
children,
|
|
100
|
-
isConfigurable = true,
|
|
101
|
-
}: Props): KubbReactNode {
|
|
102
|
-
if (!ast.isHttpOperationNode(node)) return null
|
|
103
|
-
const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node)
|
|
104
|
-
const isFormData = !isMultipleContentTypes && contentType === 'multipart/form-data'
|
|
105
|
-
const responseType = getResponseType(node)
|
|
106
|
-
|
|
107
|
-
const { path: originalPathParams, query: originalQueryParams, header: originalHeaderParams } = getOperationParameters(node)
|
|
108
|
-
const { path: casedPathParams, query: casedQueryParams, header: casedHeaderParams } = getOperationParameters(node, { paramsCasing })
|
|
109
|
-
|
|
110
|
-
const pathParamsMapping = paramsCasing && !urlName ? buildParamsMapping(originalPathParams, casedPathParams) : null
|
|
111
|
-
const queryParamsMapping = paramsCasing ? buildParamsMapping(originalQueryParams, casedQueryParams) : null
|
|
112
|
-
const headerParamsMapping = paramsCasing ? buildParamsMapping(originalHeaderParams, casedHeaderParams) : null
|
|
113
|
-
|
|
114
|
-
const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null
|
|
115
|
-
const successNames = resolveSuccessNames(node, tsResolver)
|
|
116
|
-
const responseName = successNames.length > 0 ? successNames.join(' | ') : tsResolver.resolveResponseName(node)
|
|
117
|
-
const queryParamsName = originalQueryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, originalQueryParams[0]!) : null
|
|
118
|
-
const headerParamsName = originalHeaderParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, originalHeaderParams[0]!) : null
|
|
119
|
-
|
|
120
|
-
const requestParser = resolveRequestParser(parser)
|
|
121
|
-
const responseParser = resolveResponseParser(parser)
|
|
122
|
-
const zodResponseName = zodResolver && responseParser === 'zod' ? zodResolver.resolveResponseName?.(node) : null
|
|
123
|
-
const zodRequestName = zodResolver && requestParser === 'zod' && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null
|
|
124
|
-
const queryParamsParser = resolveQueryParamsParser(parser)
|
|
125
|
-
const zodQueryParamsName =
|
|
126
|
-
zodResolver && queryParamsParser === 'zod' && originalQueryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, originalQueryParams[0]!) : null
|
|
127
|
-
|
|
128
|
-
const errorNames = node.responses
|
|
129
|
-
.filter((r) => {
|
|
130
|
-
const code = Number.parseInt(r.statusCode, 10)
|
|
131
|
-
return code >= 400
|
|
132
|
-
})
|
|
133
|
-
.map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode))
|
|
134
|
-
|
|
135
|
-
const headers = [
|
|
136
|
-
!isMultipleContentTypes && contentType !== 'application/json' && contentType !== 'multipart/form-data' ? `'Content-Type': '${contentType}'` : null,
|
|
137
|
-
headerParamsName ? (headerParamsMapping ? '...mappedHeaders' : '...headers') : null,
|
|
138
|
-
].filter(Boolean)
|
|
139
|
-
|
|
140
|
-
const TError = `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(' | ') : 'Error'}>`
|
|
141
|
-
|
|
142
|
-
const allStatusNames = node.responses.map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode))
|
|
143
|
-
const genericsResponseName = dataReturnType === 'full' ? (allStatusNames.length > 0 ? allStatusNames.join(' | ') : responseName) : responseName
|
|
144
|
-
// z.output<> reflects the post-transform type (e.g. date coercion turns Date → string), avoiding a compile error on the generated call
|
|
145
|
-
const requestGenericType = parser === 'zod' && zodRequestName ? `z.output<typeof ${zodRequestName}>` : requestName || 'unknown'
|
|
146
|
-
const generics = [genericsResponseName, TError, requestGenericType].filter(Boolean)
|
|
147
|
-
const paramsNode = buildClientParamsNode({
|
|
148
|
-
paramsType,
|
|
149
|
-
paramsCasing,
|
|
150
|
-
pathParamsType,
|
|
151
|
-
node,
|
|
152
|
-
tsResolver,
|
|
153
|
-
isConfigurable,
|
|
154
|
-
})
|
|
155
|
-
const paramsSignature = declarationPrinter.print(paramsNode) ?? ''
|
|
156
|
-
|
|
157
|
-
const urlParamsNode = buildUrlParamsNode({
|
|
158
|
-
paramsType,
|
|
159
|
-
paramsCasing,
|
|
160
|
-
pathParamsType,
|
|
161
|
-
node,
|
|
162
|
-
tsResolver,
|
|
163
|
-
})
|
|
164
|
-
const callPrinter = functionPrinter({ mode: 'call' })
|
|
165
|
-
const urlParamsCall = callPrinter.print(urlParamsNode) ?? ''
|
|
166
|
-
|
|
167
|
-
const clientParams = createFunctionParams({
|
|
168
|
-
config: {
|
|
169
|
-
mode: 'object',
|
|
170
|
-
children: {
|
|
171
|
-
method: {
|
|
172
|
-
value: stringify(node.method.toUpperCase()),
|
|
173
|
-
},
|
|
174
|
-
url: {
|
|
175
|
-
value: urlName ? `${urlName}(${urlParamsCall}).url.toString()` : Url.toTemplateString(node.path),
|
|
176
|
-
},
|
|
177
|
-
baseURL:
|
|
178
|
-
baseURL && !urlName
|
|
179
|
-
? {
|
|
180
|
-
value: `\`${baseURL}\``,
|
|
181
|
-
}
|
|
182
|
-
: null,
|
|
183
|
-
params: queryParamsName ? (zodQueryParamsName ? { value: 'requestParams' } : queryParamsMapping ? { value: 'mappedParams' } : {}) : null,
|
|
184
|
-
data: requestName
|
|
185
|
-
? {
|
|
186
|
-
value:
|
|
187
|
-
isMultipleContentTypes && hasFormData
|
|
188
|
-
? "contentType === 'multipart/form-data' ? formData as FormData : requestData"
|
|
189
|
-
: isFormData
|
|
190
|
-
? 'formData as FormData'
|
|
191
|
-
: 'requestData',
|
|
192
|
-
}
|
|
193
|
-
: null,
|
|
194
|
-
contentType: isConfigurable && isMultipleContentTypes ? {} : null,
|
|
195
|
-
responseType: responseType ? { value: stringify(responseType) } : null,
|
|
196
|
-
requestConfig: isConfigurable
|
|
197
|
-
? {
|
|
198
|
-
mode: 'inlineSpread',
|
|
199
|
-
}
|
|
200
|
-
: null,
|
|
201
|
-
headers: headers.length
|
|
202
|
-
? {
|
|
203
|
-
value: isConfigurable ? `{ ${headers.join(', ')}, ...requestConfig.headers }` : `{ ${headers.join(', ')} }`,
|
|
204
|
-
}
|
|
205
|
-
: null,
|
|
206
|
-
},
|
|
207
|
-
},
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
const statusUnionType = dataReturnType === 'full' ? buildStatusUnionType(node, tsResolver) : null
|
|
211
|
-
|
|
212
|
-
const childrenElement = children ? (
|
|
213
|
-
children
|
|
214
|
-
) : (
|
|
215
|
-
<>
|
|
216
|
-
{dataReturnType === 'full' &&
|
|
217
|
-
responseParser === 'zod' &&
|
|
218
|
-
zodResponseName &&
|
|
219
|
-
statusUnionType &&
|
|
220
|
-
`return {...res, data: ${zodResponseName}.parse(res.data)} as ${statusUnionType}`}
|
|
221
|
-
{dataReturnType === 'full' && responseParser !== 'zod' && statusUnionType && `return res as ${statusUnionType}`}
|
|
222
|
-
{dataReturnType === 'data' && responseParser === 'zod' && zodResponseName && `return ${zodResponseName}.parse(res.data)`}
|
|
223
|
-
{dataReturnType === 'data' && responseParser !== 'zod' && 'return res.data'}
|
|
224
|
-
</>
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
return (
|
|
228
|
-
<>
|
|
229
|
-
<br />
|
|
230
|
-
|
|
231
|
-
<File.Source name={name} isExportable={isExportable} isIndexable={isIndexable}>
|
|
232
|
-
<Function
|
|
233
|
-
name={name}
|
|
234
|
-
async
|
|
235
|
-
export={isExportable}
|
|
236
|
-
params={paramsSignature}
|
|
237
|
-
JSDoc={{
|
|
238
|
-
comments: buildOperationComments(node, { link: 'urlPath', linkPosition: 'beforeDeprecated', splitLines: true }),
|
|
239
|
-
}}
|
|
240
|
-
returnType={returnType}
|
|
241
|
-
>
|
|
242
|
-
{isConfigurable
|
|
243
|
-
? `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${stringify(contentType)}, ` : ''}...requestConfig } = config`
|
|
244
|
-
: ''}
|
|
245
|
-
<br />
|
|
246
|
-
{pathParamsMapping &&
|
|
247
|
-
Object.entries(pathParamsMapping)
|
|
248
|
-
.filter(([originalName, camelCaseName]) => isValidVarName(originalName) && originalName !== camelCaseName)
|
|
249
|
-
.map(([originalName, camelCaseName]) => `const ${originalName} = ${camelCaseName}`)
|
|
250
|
-
.join('\n')}
|
|
251
|
-
{pathParamsMapping && <br />}
|
|
252
|
-
{queryParamsMapping && queryParamsName && (
|
|
253
|
-
<>
|
|
254
|
-
{`const mappedParams = params ? { ${Object.entries(queryParamsMapping)
|
|
255
|
-
.map(([originalName, camelCaseName]) => `"${originalName}": params.${camelCaseName}`)
|
|
256
|
-
.join(', ')} } : undefined`}
|
|
257
|
-
<br />
|
|
258
|
-
</>
|
|
259
|
-
)}
|
|
260
|
-
{headerParamsMapping && headerParamsName && (
|
|
261
|
-
<>
|
|
262
|
-
{`const mappedHeaders = headers ? { ${Object.entries(headerParamsMapping)
|
|
263
|
-
.map(([originalName, camelCaseName]) => `"${originalName}": headers.${camelCaseName}`)
|
|
264
|
-
.join(', ')} } : undefined`}
|
|
265
|
-
<br />
|
|
266
|
-
</>
|
|
267
|
-
)}
|
|
268
|
-
{requestParser === 'zod' && zodRequestName ? `const requestData = ${zodRequestName}.parse(data)` : requestName && 'const requestData = data'}
|
|
269
|
-
{zodQueryParamsName && `const requestParams = ${zodQueryParamsName}.parse(params)`}
|
|
270
|
-
{(isFormData || (isMultipleContentTypes && hasFormData)) && requestName && 'const formData = buildFormData(requestData)'}
|
|
271
|
-
<br />
|
|
272
|
-
{isConfigurable
|
|
273
|
-
? `const res = await request<${generics.join(', ')}>(${clientParams.toCall()})`
|
|
274
|
-
: `const res = await client<${generics.join(', ')}>(${clientParams.toCall()})`}
|
|
275
|
-
<br />
|
|
276
|
-
{childrenElement}
|
|
277
|
-
</Function>
|
|
278
|
-
</File.Source>
|
|
279
|
-
</>
|
|
280
|
-
)
|
|
281
|
-
}
|