@kubb/plugin-client 5.0.0-beta.4 → 5.0.0-beta.56

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.
Files changed (56) hide show
  1. package/README.md +39 -24
  2. package/dist/clients/axios.cjs +26 -4
  3. package/dist/clients/axios.cjs.map +1 -1
  4. package/dist/clients/axios.d.ts +11 -5
  5. package/dist/clients/axios.js +26 -4
  6. package/dist/clients/axios.js.map +1 -1
  7. package/dist/clients/fetch.cjs +77 -9
  8. package/dist/clients/fetch.cjs.map +1 -1
  9. package/dist/clients/fetch.d.ts +10 -3
  10. package/dist/clients/fetch.js +77 -9
  11. package/dist/clients/fetch.js.map +1 -1
  12. package/dist/index.cjs +836 -514
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.ts +206 -105
  15. package/dist/index.js +833 -515
  16. package/dist/index.js.map +1 -1
  17. package/dist/templates/clients/axios.source.cjs +1 -1
  18. package/dist/templates/clients/axios.source.cjs.map +1 -1
  19. package/dist/templates/clients/axios.source.d.ts +1 -1
  20. package/dist/templates/clients/axios.source.js +1 -1
  21. package/dist/templates/clients/axios.source.js.map +1 -1
  22. package/dist/templates/clients/fetch.source.cjs +1 -1
  23. package/dist/templates/clients/fetch.source.cjs.map +1 -1
  24. package/dist/templates/clients/fetch.source.d.ts +1 -1
  25. package/dist/templates/clients/fetch.source.js +1 -1
  26. package/dist/templates/clients/fetch.source.js.map +1 -1
  27. package/dist/templates/config.source.cjs.map +1 -1
  28. package/dist/templates/config.source.d.ts +1 -1
  29. package/dist/templates/config.source.js.map +1 -1
  30. package/package.json +14 -26
  31. package/src/clients/axios.ts +41 -7
  32. package/src/clients/fetch.ts +106 -6
  33. package/src/components/ClassClient.tsx +47 -24
  34. package/src/components/Client.tsx +100 -71
  35. package/src/components/StaticClassClient.tsx +47 -24
  36. package/src/components/Url.tsx +10 -12
  37. package/src/components/WrapperClient.tsx +9 -5
  38. package/src/functionParams.ts +8 -8
  39. package/src/generators/classClientGenerator.tsx +63 -51
  40. package/src/generators/clientGenerator.tsx +45 -48
  41. package/src/generators/groupedClientGenerator.tsx +12 -6
  42. package/src/generators/operationsGenerator.ts +47 -0
  43. package/src/generators/staticClassClientGenerator.tsx +57 -45
  44. package/src/index.ts +2 -1
  45. package/src/plugin.ts +30 -28
  46. package/src/resolvers/resolverClient.ts +32 -8
  47. package/src/types.ts +111 -65
  48. package/src/utils.ts +142 -63
  49. package/extension.yaml +0 -776
  50. package/src/components/Operations.tsx +0 -28
  51. package/src/generators/operationsGenerator.tsx +0 -28
  52. package/templates/clients/axios.ts +0 -73
  53. package/templates/clients/fetch.ts +0 -96
  54. package/templates/config.ts +0 -43
  55. /package/dist/{chunk-ByKO4r7w.cjs → chunk-Bx3C2hgW.cjs} +0 -0
  56. /package/dist/{chunk--u3MIqq1.js → chunk-C0LytTxp.js} +0 -0
@@ -1,4 +1,14 @@
1
- import { isValidVarName, URLPath } from '@internals/utils'
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'
2
12
  import { ast } from '@kubb/core'
3
13
  import type { ResolverTs } from '@kubb/plugin-ts'
4
14
  import { functionPrinter } from '@kubb/plugin-ts'
@@ -7,8 +17,8 @@ import { File, Function } from '@kubb/renderer-jsx'
7
17
  import type { KubbReactNode } from '@kubb/renderer-jsx/types'
8
18
  import { createFunctionParams } from '../functionParams.ts'
9
19
  import type { PluginClient } from '../types.ts'
10
- import { buildParamsMapping, getComments } from '../utils.ts'
11
- import { Url } from './Url.tsx'
20
+ import { buildStatusUnionType, resolveQueryParamsParser, resolveRequestParser, resolveResponseParser } from '../utils.ts'
21
+ import { buildUrlParamsNode } from './Url.tsx'
12
22
 
13
23
  type Props = {
14
24
  name: string
@@ -18,7 +28,7 @@ type Props = {
18
28
  isConfigurable?: boolean
19
29
  returnType?: string
20
30
 
21
- baseURL: string | undefined
31
+ baseURL: string | null | undefined
22
32
  dataReturnType: PluginClient['resolvedOptions']['dataReturnType']
23
33
  paramsCasing: PluginClient['resolvedOptions']['paramsCasing']
24
34
  paramsType: PluginClient['resolvedOptions']['pathParamsType']
@@ -26,7 +36,7 @@ type Props = {
26
36
  parser: PluginClient['resolvedOptions']['parser'] | undefined
27
37
  node: ast.OperationNode
28
38
  tsResolver: ResolverTs
29
- zodResolver?: ResolverZod
39
+ zodResolver?: ResolverZod | null
30
40
  children?: KubbReactNode
31
41
  }
32
42
 
@@ -41,26 +51,33 @@ type GetParamsProps = {
41
51
 
42
52
  const declarationPrinter = functionPrinter({ mode: 'declaration' })
43
53
 
44
- function getParams({ paramsType, paramsCasing, pathParamsType, node, tsResolver, isConfigurable }: GetParamsProps): ast.FunctionParametersNode {
45
- const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : undefined
46
-
54
+ export function buildClientParamsNode({
55
+ paramsType,
56
+ paramsCasing,
57
+ pathParamsType,
58
+ node,
59
+ tsResolver,
60
+ isConfigurable,
61
+ }: GetParamsProps): ast.FunctionParametersNode {
47
62
  return ast.createOperationParams(node, {
48
63
  paramsType,
49
64
  pathParamsType: paramsType === 'object' ? 'object' : pathParamsType === 'object' ? 'object' : 'inline',
50
65
  paramsCasing,
51
66
  resolver: tsResolver,
52
- extraParams: isConfigurable
53
- ? [
54
- ast.createFunctionParameter({
55
- name: 'config',
56
- type: ast.createParamsType({
57
- variant: 'reference',
58
- name: requestName ? `Partial<RequestConfig<${requestName}>> & { client?: Client }` : 'Partial<RequestConfig> & { client?: Client }',
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: '{}',
59
77
  }),
60
- default: '{}',
61
- }),
62
- ]
63
- : [],
78
+ ]
79
+ : []),
80
+ ],
64
81
  })
65
82
  }
66
83
 
@@ -82,28 +99,31 @@ export function Client({
82
99
  children,
83
100
  isConfigurable = true,
84
101
  }: Props): KubbReactNode {
85
- const path = new URLPath(node.path)
86
- const contentType = node.requestBody?.content?.[0]?.contentType ?? 'application/json'
87
- const isFormData = contentType === 'multipart/form-data'
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)
88
106
 
89
- const originalPathParams = node.parameters.filter((p) => p.in === 'path')
90
- const casedPathParams = ast.caseParams(originalPathParams, paramsCasing)
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)
107
+ const { path: originalPathParams, query: originalQueryParams, header: originalHeaderParams } = getOperationParameters(node)
108
+ const { path: casedPathParams, query: casedQueryParams, header: casedHeaderParams } = getOperationParameters(node, { paramsCasing })
95
109
 
96
- const pathParamsMapping = paramsCasing && !urlName ? buildParamsMapping(originalPathParams, casedPathParams) : undefined
97
- const queryParamsMapping = paramsCasing ? buildParamsMapping(originalQueryParams, casedQueryParams) : undefined
98
- const headerParamsMapping = paramsCasing ? buildParamsMapping(originalHeaderParams, casedHeaderParams) : undefined
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
99
113
 
100
- const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : undefined
101
- const responseName = tsResolver.resolveResponseName(node)
102
- const queryParamsName = originalQueryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, originalQueryParams[0]!) : undefined
103
- const headerParamsName = originalHeaderParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, originalHeaderParams[0]!) : undefined
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
104
119
 
105
- const zodResponseName = zodResolver && parser === 'zod' ? zodResolver.resolveResponseName?.(node) : undefined
106
- const zodRequestName = zodResolver && parser === 'zod' && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : undefined
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
107
127
 
108
128
  const errorNames = node.responses
109
129
  .filter((r) => {
@@ -113,14 +133,18 @@ export function Client({
113
133
  .map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode))
114
134
 
115
135
  const headers = [
116
- contentType !== 'application/json' && contentType !== 'multipart/form-data' ? `'Content-Type': '${contentType}'` : undefined,
117
- headerParamsName ? (headerParamsMapping ? '...mappedHeaders' : '...headers') : undefined,
136
+ !isMultipleContentTypes && contentType !== 'application/json' && contentType !== 'multipart/form-data' ? `'Content-Type': '${contentType}'` : null,
137
+ headerParamsName ? (headerParamsMapping ? '...mappedHeaders' : '...headers') : null,
118
138
  ].filter(Boolean)
119
139
 
120
140
  const TError = `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(' | ') : 'Error'}>`
121
141
 
122
- const generics = [responseName, TError, requestName || 'unknown'].filter(Boolean)
123
- const paramsNode = getParams({
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({
124
148
  paramsType,
125
149
  paramsCasing,
126
150
  pathParamsType,
@@ -130,7 +154,7 @@ export function Client({
130
154
  })
131
155
  const paramsSignature = declarationPrinter.print(paramsNode) ?? ''
132
156
 
133
- const urlParamsNode = Url.getParams({
157
+ const urlParamsNode = buildUrlParamsNode({
134
158
  paramsType,
135
159
  paramsCasing,
136
160
  pathParamsType,
@@ -145,45 +169,58 @@ export function Client({
145
169
  mode: 'object',
146
170
  children: {
147
171
  method: {
148
- value: JSON.stringify(node.method.toUpperCase()),
172
+ value: stringify(node.method.toUpperCase()),
149
173
  },
150
174
  url: {
151
- value: urlName ? `${urlName}(${urlParamsCall}).url.toString()` : path.template,
175
+ value: urlName ? `${urlName}(${urlParamsCall}).url.toString()` : Url.toTemplateString(node.path),
152
176
  },
153
177
  baseURL:
154
178
  baseURL && !urlName
155
179
  ? {
156
180
  value: `\`${baseURL}\``,
157
181
  }
158
- : undefined,
159
- params: queryParamsName ? (queryParamsMapping ? { value: 'mappedParams' } : {}) : undefined,
182
+ : null,
183
+ params: queryParamsName ? (zodQueryParamsName ? { value: 'requestParams' } : queryParamsMapping ? { value: 'mappedParams' } : {}) : null,
160
184
  data: requestName
161
185
  ? {
162
- value: isFormData ? 'formData as FormData' : 'requestData',
186
+ value:
187
+ isMultipleContentTypes && hasFormData
188
+ ? "contentType === 'multipart/form-data' ? formData as FormData : requestData"
189
+ : isFormData
190
+ ? 'formData as FormData'
191
+ : 'requestData',
163
192
  }
164
- : undefined,
193
+ : null,
194
+ contentType: isConfigurable && isMultipleContentTypes ? {} : null,
195
+ responseType: responseType ? { value: stringify(responseType) } : null,
165
196
  requestConfig: isConfigurable
166
197
  ? {
167
198
  mode: 'inlineSpread',
168
199
  }
169
- : undefined,
200
+ : null,
170
201
  headers: headers.length
171
202
  ? {
172
203
  value: isConfigurable ? `{ ${headers.join(', ')}, ...requestConfig.headers }` : `{ ${headers.join(', ')} }`,
173
204
  }
174
- : undefined,
205
+ : null,
175
206
  },
176
207
  },
177
208
  })
178
209
 
210
+ const statusUnionType = dataReturnType === 'full' ? buildStatusUnionType(node, tsResolver) : null
211
+
179
212
  const childrenElement = children ? (
180
213
  children
181
214
  ) : (
182
215
  <>
183
- {dataReturnType === 'full' && parser === 'zod' && zodResponseName && `return {...res, data: ${zodResponseName}.parse(res.data)}`}
184
- {dataReturnType === 'data' && parser === 'zod' && zodResponseName && `return ${zodResponseName}.parse(res.data)`}
185
- {dataReturnType === 'full' && parser === 'client' && 'return res'}
186
- {dataReturnType === 'data' && parser === 'client' && 'return res.data'}
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'}
187
224
  </>
188
225
  )
189
226
 
@@ -198,31 +235,26 @@ export function Client({
198
235
  export={isExportable}
199
236
  params={paramsSignature}
200
237
  JSDoc={{
201
- comments: getComments(node),
238
+ comments: buildOperationComments(node, { link: 'urlPath', linkPosition: 'beforeDeprecated', splitLines: true }),
202
239
  }}
203
240
  returnType={returnType}
204
241
  >
205
- {isConfigurable ? 'const { client: request = fetch, ...requestConfig } = config' : ''}
206
- <br />
242
+ {isConfigurable
243
+ ? `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${stringify(contentType)}, ` : ''}...requestConfig } = config`
244
+ : ''}
207
245
  <br />
208
246
  {pathParamsMapping &&
209
247
  Object.entries(pathParamsMapping)
210
248
  .filter(([originalName, camelCaseName]) => isValidVarName(originalName) && originalName !== camelCaseName)
211
249
  .map(([originalName, camelCaseName]) => `const ${originalName} = ${camelCaseName}`)
212
250
  .join('\n')}
213
- {pathParamsMapping && (
214
- <>
215
- <br />
216
- <br />
217
- </>
218
- )}
251
+ {pathParamsMapping && <br />}
219
252
  {queryParamsMapping && queryParamsName && (
220
253
  <>
221
254
  {`const mappedParams = params ? { ${Object.entries(queryParamsMapping)
222
255
  .map(([originalName, camelCaseName]) => `"${originalName}": params.${camelCaseName}`)
223
256
  .join(', ')} } : undefined`}
224
257
  <br />
225
- <br />
226
258
  </>
227
259
  )}
228
260
  {headerParamsMapping && headerParamsName && (
@@ -231,16 +263,15 @@ export function Client({
231
263
  .map(([originalName, camelCaseName]) => `"${originalName}": headers.${camelCaseName}`)
232
264
  .join(', ')} } : undefined`}
233
265
  <br />
234
- <br />
235
266
  </>
236
267
  )}
237
- {parser === 'zod' && zodRequestName ? `const requestData = ${zodRequestName}.parse(data)` : requestName && 'const requestData = data'}
238
- <br />
239
- {isFormData && requestName && 'const formData = buildFormData(requestData)'}
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)'}
240
271
  <br />
241
272
  {isConfigurable
242
273
  ? `const res = await request<${generics.join(', ')}>(${clientParams.toCall()})`
243
- : `const res = await fetch<${generics.join(', ')}>(${clientParams.toCall()})`}
274
+ : `const res = await client<${generics.join(', ')}>(${clientParams.toCall()})`}
244
275
  <br />
245
276
  {childrenElement}
246
277
  </Function>
@@ -248,5 +279,3 @@ export function Client({
248
279
  </>
249
280
  )
250
281
  }
251
-
252
- Client.getParams = getParams
@@ -1,19 +1,30 @@
1
- import { buildJSDoc, URLPath } from '@internals/utils'
2
- import type { ast } from '@kubb/core'
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'
3
5
  import type { ResolverTs } from '@kubb/plugin-ts'
4
6
  import { functionPrinter } from '@kubb/plugin-ts'
5
7
  import type { ResolverZod } from '@kubb/plugin-zod'
6
8
  import { File } from '@kubb/renderer-jsx'
7
9
  import type { KubbReactNode } from '@kubb/renderer-jsx/types'
8
10
  import type { PluginClient } from '../types.ts'
9
- import { buildClassClientParams, buildFormDataLine, buildGenerics, buildHeaders, buildRequestDataLine, buildReturnStatement, getComments } from '../utils.ts'
10
- import { Client } from './Client.tsx'
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'
11
22
 
12
23
  type OperationData = {
13
24
  node: ast.OperationNode
14
25
  name: string
15
26
  tsResolver: ResolverTs
16
- zodResolver?: ResolverZod
27
+ zodResolver?: ResolverZod | null
17
28
  }
18
29
 
19
30
  type Props = {
@@ -21,7 +32,7 @@ type Props = {
21
32
  isExportable?: boolean
22
33
  isIndexable?: boolean
23
34
  operations: Array<OperationData>
24
- baseURL: string | undefined
35
+ baseURL: string | null | undefined
25
36
  dataReturnType: PluginClient['resolvedOptions']['dataReturnType']
26
37
  paramsCasing: PluginClient['resolvedOptions']['paramsCasing']
27
38
  paramsType: PluginClient['resolvedOptions']['pathParamsType']
@@ -34,8 +45,8 @@ type GenerateMethodProps = {
34
45
  node: ast.OperationNode
35
46
  name: string
36
47
  tsResolver: ResolverTs
37
- zodResolver?: ResolverZod
38
- baseURL: string | undefined
48
+ zodResolver?: ResolverZod | null
49
+ baseURL: string | null | undefined
39
50
  dataReturnType: PluginClient['resolvedOptions']['dataReturnType']
40
51
  parser: PluginClient['resolvedOptions']['parser'] | undefined
41
52
  paramsType: PluginClient['resolvedOptions']['paramsType']
@@ -57,28 +68,41 @@ function generateMethod({
57
68
  paramsCasing,
58
69
  pathParamsType,
59
70
  }: GenerateMethodProps): string {
60
- const path = new URLPath(node.path, { casing: paramsCasing })
61
- const contentType = node.requestBody?.content?.[0]?.contentType ?? 'application/json'
62
- const isFormData = contentType === 'multipart/form-data'
63
- const headerParamsName =
64
- node.parameters.filter((p) => p.in === 'header').length > 0
65
- ? tsResolver.resolveHeaderParamsName(node, node.parameters.filter((p) => p.in === 'header')[0]!)
66
- : undefined
67
- const headers = buildHeaders(contentType, !!headerParamsName)
68
- const generics = buildGenerics(node, tsResolver)
69
- const paramsNode = Client.getParams({ paramsType, paramsCasing, pathParamsType, node, tsResolver, isConfigurable: true })
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 })
70
79
  const paramsSignature = declarationPrinter.print(paramsNode) ?? ''
71
- const clientParams = buildClassClientParams({ node, path, baseURL, tsResolver, isFormData, headers })
72
- const jsdoc = buildJSDoc(getComments(node))
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 }))
73
95
 
74
96
  const requestDataLine = buildRequestDataLine({ parser, node, zodResolver })
75
- const formDataLine = buildFormDataLine(isFormData, !!node.requestBody?.content?.[0]?.schema)
76
- const returnStatement = buildReturnStatement({ dataReturnType, 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 })
77
100
 
78
101
  const methodBody = [
79
- 'const { client: request = fetch, ...requestConfig } = mergeConfig(this.#config, config)',
102
+ `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${stringify(contentType)}, ` : ''}...requestConfig } = mergeConfig(this.#config, config)`,
80
103
  '',
81
104
  requestDataLine,
105
+ queryParamsLine,
82
106
  formDataLine,
83
107
  `const res = await request<${generics.join(', ')}>(${clientParams.toCall()})`,
84
108
  returnStatement,
@@ -127,4 +151,3 @@ export function StaticClassClient({
127
151
  </File.Source>
128
152
  )
129
153
  }
130
- StaticClassClient.getParams = Client.getParams
@@ -1,18 +1,18 @@
1
- import { isValidVarName, URLPath } from '@internals/utils'
1
+ import { buildParamsMapping, getOperationParameters } from '@internals/shared'
2
+ import { isValidVarName, Url as UrlHelper } from '@internals/utils'
2
3
  import { ast } from '@kubb/core'
3
4
  import type { ResolverTs } from '@kubb/plugin-ts'
4
5
  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 getParams({ paramsType, paramsCasing, pathParamsType, node, tsResolver }: GetParamsProps): ast.FunctionParametersNode {
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,
@@ -57,9 +57,9 @@ export function Url({
57
57
  node,
58
58
  tsResolver,
59
59
  }: Props): KubbReactNode {
60
- const path = new URLPath(node.path)
60
+ if (!ast.isHttpOperationNode(node)) return null
61
61
 
62
- const paramsNode = getParams({
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.parameters.filter((p) => p.in === 'path')
72
- const casedPathParams = ast.caseParams(originalPathParams, paramsCasing)
73
- const pathParamsMapping = paramsCasing ? buildParamsMapping(originalPathParams, casedPathParams) : undefined
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}>
@@ -81,12 +81,10 @@ export function Url({
81
81
  .map(([originalName, camelCaseName]) => `const ${originalName} = ${camelCaseName}`)
82
82
  .join('\n')}
83
83
  {pathParamsMapping && Object.keys(pathParamsMapping).length > 0 && <br />}
84
- <Const name={'res'}>{`{ method: '${node.method.toUpperCase()}', url: ${path.toTemplateString({ prefix: baseURL })} as const }`}</Const>
84
+ <Const name={'res'}>{`{ method: '${node.method.toUpperCase()}', url: ${UrlHelper.toTemplateString(node.path, { prefix: baseURL })} as const }`}</Const>
85
85
  <br />
86
86
  return res
87
87
  </Function>
88
88
  </File.Source>
89
89
  )
90
90
  }
91
-
92
- Url.getParams = getParams
@@ -1,17 +1,21 @@
1
- import { camelCase } from '@internals/utils'
2
1
  import { File } from '@kubb/renderer-jsx'
3
2
  import type { KubbReactNode } from '@kubb/renderer-jsx/types'
4
3
 
4
+ type ClientController = {
5
+ className: string
6
+ propertyName: string
7
+ }
8
+
5
9
  type Props = {
6
10
  name: string
7
- classNames: Array<string>
11
+ controllers: Array<ClientController>
8
12
  isExportable?: boolean
9
13
  isIndexable?: boolean
10
14
  }
11
15
 
12
- export function WrapperClient({ name, classNames, isExportable = true, isIndexable = true }: Props): KubbReactNode {
13
- const properties = classNames.map((className) => ` readonly ${camelCase(className)}: ${className}`).join('\n')
14
- const assignments = classNames.map((className) => ` this.${camelCase(className)} = new ${className}(config)`).join('\n')
16
+ export function WrapperClient({ name, controllers, isExportable = true, isIndexable = true }: Props): KubbReactNode {
17
+ const properties = controllers.map(({ className, propertyName }) => ` readonly ${propertyName}: ${className}`).join('\n')
18
+ const assignments = controllers.map(({ className, propertyName }) => ` this.${propertyName} = new ${className}(config)`).join('\n')
15
19
 
16
20
  const classCode = `export class ${name} {
17
21
  ${properties}
@@ -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 }) : undefined
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 !== undefined)
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 !== undefined)
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 !== undefined) as Array<[string, ParamSpec]>
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() {