@kubb/plugin-client 5.0.0-beta.42 → 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.
@@ -1 +1 @@
1
- {"version":3,"file":"axios.source.cjs","names":[],"sources":["../../../src/templates/clients/axios.source.ts"],"sourcesContent":["// @ts-expect-error - import attributes are handled at build time by importAttributeTextPlugin\nimport content from '../../../templates/clients/axios.ts' with { type: 'text' }\n\nexport const source = content as string\n"],"mappings":";;AAAA,MAAa,SAAS"}
1
+ {"version":3,"file":"axios.source.cjs","names":[],"sources":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"axios.source.js","names":[],"sources":["../../../src/templates/clients/axios.source.ts"],"sourcesContent":["// @ts-expect-error - import attributes are handled at build time by importAttributeTextPlugin\nimport content from '../../../templates/clients/axios.ts' with { type: 'text' }\n\nexport const source = content as string\n"],"mappings":";AAAA,MAAa,SAAS"}
1
+ {"version":3,"file":"axios.source.js","names":[],"sources":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.source.cjs","names":[],"sources":["../../../src/templates/clients/fetch.source.ts"],"sourcesContent":["// @ts-expect-error - import attributes are handled at build time by importAttributeTextPlugin\nimport content from '../../../templates/clients/fetch.ts' with { type: 'text' }\n\nexport const source = content as string\n"],"mappings":";;AAAA,MAAa,SAAS"}
1
+ {"version":3,"file":"fetch.source.cjs","names":[],"sources":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.source.js","names":[],"sources":["../../../src/templates/clients/fetch.source.ts"],"sourcesContent":["// @ts-expect-error - import attributes are handled at build time by importAttributeTextPlugin\nimport content from '../../../templates/clients/fetch.ts' with { type: 'text' }\n\nexport const source = content as string\n"],"mappings":";AAAA,MAAa,SAAS"}
1
+ {"version":3,"file":"fetch.source.js","names":[],"sources":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"config.source.cjs","names":[],"sources":["../../src/templates/config.source.ts"],"sourcesContent":["// @ts-expect-error - import attributes are handled at build time by importAttributeTextPlugin\nimport content from '../../templates/config.ts' with { type: 'text' }\n\nexport const source = content as string\n"],"mappings":";;AAAA,MAAa,SAAS"}
1
+ {"version":3,"file":"config.source.cjs","names":[],"sources":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"config.source.js","names":[],"sources":["../../src/templates/config.source.ts"],"sourcesContent":["// @ts-expect-error - import attributes are handled at build time by importAttributeTextPlugin\nimport content from '../../templates/config.ts' with { type: 'text' }\n\nexport const source = content as string\n"],"mappings":";AAAA,MAAa,SAAS"}
1
+ {"version":3,"file":"config.source.js","names":[],"sources":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/plugin-client",
3
- "version": "5.0.0-beta.42",
3
+ "version": "5.0.0-beta.56",
4
4
  "description": "Generate type-safe HTTP clients from your OpenAPI specification. Supports Axios, Fetch, and custom client adapters with full TypeScript inference and zero boilerplate.",
5
5
  "keywords": [
6
6
  "api-client",
@@ -24,7 +24,6 @@
24
24
  "files": [
25
25
  "src",
26
26
  "dist",
27
- "extension.yaml",
28
27
  "*.d.ts",
29
28
  "*.d.cts",
30
29
  "!/**/**.test.**",
@@ -87,18 +86,19 @@
87
86
  "registry": "https://registry.npmjs.org/"
88
87
  },
89
88
  "dependencies": {
90
- "@kubb/core": "5.0.0-beta.42",
91
- "@kubb/renderer-jsx": "5.0.0-beta.42",
92
- "@kubb/plugin-ts": "5.0.0-beta.42",
93
- "@kubb/plugin-zod": "5.0.0-beta.42"
89
+ "@kubb/ast": "5.0.0-beta.55",
90
+ "@kubb/core": "5.0.0-beta.55",
91
+ "@kubb/renderer-jsx": "5.0.0-beta.55",
92
+ "@kubb/plugin-ts": "5.0.0-beta.56",
93
+ "@kubb/plugin-zod": "5.0.0-beta.56"
94
94
  },
95
95
  "devDependencies": {
96
- "axios": "^1.16.1",
96
+ "axios": "^1.17.0",
97
97
  "@internals/shared": "0.0.0",
98
98
  "@internals/utils": "0.0.0"
99
99
  },
100
100
  "peerDependencies": {
101
- "@kubb/renderer-jsx": "5.0.0-beta.42",
101
+ "@kubb/renderer-jsx": "5.0.0-beta.55",
102
102
  "axios": "^1.7.2"
103
103
  },
104
104
  "peerDependenciesMeta": {
@@ -106,19 +106,12 @@
106
106
  "optional": true
107
107
  }
108
108
  },
109
- "size-limit": [
110
- {
111
- "path": "./dist/*.js",
112
- "limit": "510 KiB",
113
- "gzip": true
114
- }
115
- ],
116
109
  "engines": {
117
110
  "node": ">=22"
118
111
  },
119
112
  "scripts": {
120
- "build": "tsdown && size-limit",
121
- "clean": "npx rimraf ./dist",
113
+ "build": "tsdown",
114
+ "clean": "node -e \"require('node:fs').rmSync('./dist', {recursive:true,force:true})\"",
122
115
  "lint": "oxlint .",
123
116
  "lint:fix": "oxlint --fix .",
124
117
  "release": "pnpm publish --no-git-check",
@@ -1,5 +1,6 @@
1
1
  import { buildOperationComments, getContentTypeInfo, getOperationParameters } from '@internals/shared'
2
- import { buildJSDoc, URLPath } from '@internals/utils'
2
+ import { Url } from '@internals/utils'
3
+ import { buildJSDoc, stringify } from '@kubb/ast/utils'
3
4
  import { ast } from '@kubb/core'
4
5
  import type { ResolverTs } from '@kubb/plugin-ts'
5
6
  import { functionPrinter } from '@kubb/plugin-ts'
@@ -7,7 +8,16 @@ import type { ResolverZod } from '@kubb/plugin-zod'
7
8
  import { File } from '@kubb/renderer-jsx'
8
9
  import type { KubbReactNode } from '@kubb/renderer-jsx/types'
9
10
  import type { PluginClient } from '../types.ts'
10
- import { buildClassClientParams, buildFormDataLine, buildGenerics, buildHeaders, buildRequestDataLine, buildReturnStatement } from '../utils.ts'
11
+ import {
12
+ buildClassClientParams,
13
+ buildFormDataLine,
14
+ buildGenerics,
15
+ buildHeaders,
16
+ buildQueryParamsLine,
17
+ buildRequestDataLine,
18
+ buildReturnStatement,
19
+ resolveQueryParamsParser,
20
+ } from '../utils.ts'
11
21
  import { buildClientParamsNode } from './Client.tsx'
12
22
 
13
23
  type OperationData = {
@@ -59,26 +69,40 @@ function generateMethod({
59
69
  pathParamsType,
60
70
  }: GenerateMethodProps): string {
61
71
  if (!ast.isHttpOperationNode(node)) return ''
62
- const path = new URLPath(node.path, { casing: paramsCasing })
63
72
  const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node)
64
73
  const isFormData = !isMultipleContentTypes && contentType === 'multipart/form-data'
65
74
  const { header: headerParams } = getOperationParameters(node)
66
75
  const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]!) : null
67
76
  const headers = isMultipleContentTypes ? (headerParamsName ? ['...headers'] : []) : buildHeaders(contentType, !!headerParamsName)
68
- const generics = buildGenerics(node, tsResolver)
77
+ const generics = buildGenerics(node, tsResolver, { dataReturnType, zodResolver, parser })
69
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, isMultipleContentTypes, hasFormData, headers })
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
+ })
72
94
  const jsdoc = buildJSDoc(buildOperationComments(node, { link: 'urlPath', linkPosition: 'beforeDeprecated', splitLines: true }))
73
95
 
74
96
  const requestDataLine = buildRequestDataLine({ parser, node, zodResolver })
97
+ const queryParamsLine = buildQueryParamsLine({ parser, node, zodResolver })
75
98
  const formDataLine = buildFormDataLine(isFormData || (isMultipleContentTypes && hasFormData), !!node.requestBody?.content?.[0]?.schema)
76
- const returnStatement = buildReturnStatement({ dataReturnType, parser, node, zodResolver })
99
+ const returnStatement = buildReturnStatement({ dataReturnType, parser, node, zodResolver, tsResolver })
77
100
 
78
101
  const methodBody = [
79
- `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ''}...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,
@@ -7,7 +7,8 @@ import {
7
7
  getResponseType,
8
8
  resolveSuccessNames,
9
9
  } from '@internals/shared'
10
- import { isValidVarName, URLPath } from '@internals/utils'
10
+ import { isValidVarName, Url } from '@internals/utils'
11
+ import { stringify } from '@kubb/ast/utils'
11
12
  import { ast } from '@kubb/core'
12
13
  import type { ResolverTs } from '@kubb/plugin-ts'
13
14
  import { functionPrinter } from '@kubb/plugin-ts'
@@ -16,6 +17,7 @@ import { File, Function } from '@kubb/renderer-jsx'
16
17
  import type { KubbReactNode } from '@kubb/renderer-jsx/types'
17
18
  import { createFunctionParams } from '../functionParams.ts'
18
19
  import type { PluginClient } from '../types.ts'
20
+ import { buildStatusUnionType, resolveQueryParamsParser, resolveRequestParser, resolveResponseParser } from '../utils.ts'
19
21
  import { buildUrlParamsNode } from './Url.tsx'
20
22
 
21
23
  type Props = {
@@ -98,7 +100,6 @@ export function Client({
98
100
  isConfigurable = true,
99
101
  }: Props): KubbReactNode {
100
102
  if (!ast.isHttpOperationNode(node)) return null
101
- const path = new URLPath(node.path)
102
103
  const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node)
103
104
  const isFormData = !isMultipleContentTypes && contentType === 'multipart/form-data'
104
105
  const responseType = getResponseType(node)
@@ -116,8 +117,13 @@ export function Client({
116
117
  const queryParamsName = originalQueryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, originalQueryParams[0]!) : null
117
118
  const headerParamsName = originalHeaderParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, originalHeaderParams[0]!) : null
118
119
 
119
- const zodResponseName = zodResolver && parser === 'zod' ? zodResolver.resolveResponseName?.(node) : null
120
- const zodRequestName = zodResolver && parser === 'zod' && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null
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
121
127
 
122
128
  const errorNames = node.responses
123
129
  .filter((r) => {
@@ -133,7 +139,11 @@ export function Client({
133
139
 
134
140
  const TError = `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(' | ') : 'Error'}>`
135
141
 
136
- const generics = [responseName, TError, requestName || 'unknown'].filter(Boolean)
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)
137
147
  const paramsNode = buildClientParamsNode({
138
148
  paramsType,
139
149
  paramsCasing,
@@ -159,10 +169,10 @@ export function Client({
159
169
  mode: 'object',
160
170
  children: {
161
171
  method: {
162
- value: JSON.stringify(node.method.toUpperCase()),
172
+ value: stringify(node.method.toUpperCase()),
163
173
  },
164
174
  url: {
165
- value: urlName ? `${urlName}(${urlParamsCall}).url.toString()` : path.template,
175
+ value: urlName ? `${urlName}(${urlParamsCall}).url.toString()` : Url.toTemplateString(node.path),
166
176
  },
167
177
  baseURL:
168
178
  baseURL && !urlName
@@ -170,7 +180,7 @@ export function Client({
170
180
  value: `\`${baseURL}\``,
171
181
  }
172
182
  : null,
173
- params: queryParamsName ? (queryParamsMapping ? { value: 'mappedParams' } : {}) : null,
183
+ params: queryParamsName ? (zodQueryParamsName ? { value: 'requestParams' } : queryParamsMapping ? { value: 'mappedParams' } : {}) : null,
174
184
  data: requestName
175
185
  ? {
176
186
  value:
@@ -182,7 +192,7 @@ export function Client({
182
192
  }
183
193
  : null,
184
194
  contentType: isConfigurable && isMultipleContentTypes ? {} : null,
185
- responseType: responseType ? { value: JSON.stringify(responseType) } : null,
195
+ responseType: responseType ? { value: stringify(responseType) } : null,
186
196
  requestConfig: isConfigurable
187
197
  ? {
188
198
  mode: 'inlineSpread',
@@ -197,14 +207,20 @@ export function Client({
197
207
  },
198
208
  })
199
209
 
210
+ const statusUnionType = dataReturnType === 'full' ? buildStatusUnionType(node, tsResolver) : null
211
+
200
212
  const childrenElement = children ? (
201
213
  children
202
214
  ) : (
203
215
  <>
204
- {dataReturnType === 'full' && parser === 'zod' && zodResponseName && `return {...res, data: ${zodResponseName}.parse(res.data)}`}
205
- {dataReturnType === 'data' && parser === 'zod' && zodResponseName && `return ${zodResponseName}.parse(res.data)`}
206
- {dataReturnType === 'full' && parser !== 'zod' && 'return res'}
207
- {dataReturnType === 'data' && parser !== 'zod' && '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'}
208
224
  </>
209
225
  )
210
226
 
@@ -224,28 +240,21 @@ export function Client({
224
240
  returnType={returnType}
225
241
  >
226
242
  {isConfigurable
227
- ? `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ''}...requestConfig } = config`
243
+ ? `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${stringify(contentType)}, ` : ''}...requestConfig } = config`
228
244
  : ''}
229
245
  <br />
230
- <br />
231
246
  {pathParamsMapping &&
232
247
  Object.entries(pathParamsMapping)
233
248
  .filter(([originalName, camelCaseName]) => isValidVarName(originalName) && originalName !== camelCaseName)
234
249
  .map(([originalName, camelCaseName]) => `const ${originalName} = ${camelCaseName}`)
235
250
  .join('\n')}
236
- {pathParamsMapping && (
237
- <>
238
- <br />
239
- <br />
240
- </>
241
- )}
251
+ {pathParamsMapping && <br />}
242
252
  {queryParamsMapping && queryParamsName && (
243
253
  <>
244
254
  {`const mappedParams = params ? { ${Object.entries(queryParamsMapping)
245
255
  .map(([originalName, camelCaseName]) => `"${originalName}": params.${camelCaseName}`)
246
256
  .join(', ')} } : undefined`}
247
257
  <br />
248
- <br />
249
258
  </>
250
259
  )}
251
260
  {headerParamsMapping && headerParamsName && (
@@ -254,11 +263,10 @@ export function Client({
254
263
  .map(([originalName, camelCaseName]) => `"${originalName}": headers.${camelCaseName}`)
255
264
  .join(', ')} } : undefined`}
256
265
  <br />
257
- <br />
258
266
  </>
259
267
  )}
260
- {parser === 'zod' && zodRequestName ? `const requestData = ${zodRequestName}.parse(data)` : requestName && 'const requestData = data'}
261
- <br />
268
+ {requestParser === 'zod' && zodRequestName ? `const requestData = ${zodRequestName}.parse(data)` : requestName && 'const requestData = data'}
269
+ {zodQueryParamsName && `const requestParams = ${zodQueryParamsName}.parse(params)`}
262
270
  {(isFormData || (isMultipleContentTypes && hasFormData)) && requestName && 'const formData = buildFormData(requestData)'}
263
271
  <br />
264
272
  {isConfigurable
@@ -1,5 +1,6 @@
1
1
  import { buildOperationComments, getContentTypeInfo, getOperationParameters } from '@internals/shared'
2
- import { buildJSDoc, URLPath } from '@internals/utils'
2
+ import { Url } from '@internals/utils'
3
+ import { buildJSDoc, stringify } from '@kubb/ast/utils'
3
4
  import { ast } from '@kubb/core'
4
5
  import type { ResolverTs } from '@kubb/plugin-ts'
5
6
  import { functionPrinter } from '@kubb/plugin-ts'
@@ -7,7 +8,16 @@ import type { ResolverZod } from '@kubb/plugin-zod'
7
8
  import { File } from '@kubb/renderer-jsx'
8
9
  import type { KubbReactNode } from '@kubb/renderer-jsx/types'
9
10
  import type { PluginClient } from '../types.ts'
10
- import { buildClassClientParams, buildFormDataLine, buildGenerics, buildHeaders, buildRequestDataLine, buildReturnStatement } from '../utils.ts'
11
+ import {
12
+ buildClassClientParams,
13
+ buildFormDataLine,
14
+ buildGenerics,
15
+ buildHeaders,
16
+ buildQueryParamsLine,
17
+ buildRequestDataLine,
18
+ buildReturnStatement,
19
+ resolveQueryParamsParser,
20
+ } from '../utils.ts'
11
21
  import { buildClientParamsNode } from './Client.tsx'
12
22
 
13
23
  type OperationData = {
@@ -59,26 +69,40 @@ function generateMethod({
59
69
  pathParamsType,
60
70
  }: GenerateMethodProps): string {
61
71
  if (!ast.isHttpOperationNode(node)) return ''
62
- const path = new URLPath(node.path, { casing: paramsCasing })
63
72
  const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node)
64
73
  const isFormData = !isMultipleContentTypes && contentType === 'multipart/form-data'
65
74
  const { header: headerParams } = getOperationParameters(node)
66
75
  const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]!) : null
67
76
  const headers = isMultipleContentTypes ? (headerParamsName ? ['...headers'] : []) : buildHeaders(contentType, !!headerParamsName)
68
- const generics = buildGenerics(node, tsResolver)
77
+ const generics = buildGenerics(node, tsResolver, { dataReturnType, zodResolver, parser })
69
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, isMultipleContentTypes, hasFormData, headers })
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
+ })
72
94
  const jsdoc = buildJSDoc(buildOperationComments(node, { link: 'urlPath', linkPosition: 'beforeDeprecated', splitLines: true }))
73
95
 
74
96
  const requestDataLine = buildRequestDataLine({ parser, node, zodResolver })
97
+ const queryParamsLine = buildQueryParamsLine({ parser, node, zodResolver })
75
98
  const formDataLine = buildFormDataLine(isFormData || (isMultipleContentTypes && hasFormData), !!node.requestBody?.content?.[0]?.schema)
76
- const returnStatement = buildReturnStatement({ dataReturnType, parser, node, zodResolver })
99
+ const returnStatement = buildReturnStatement({ dataReturnType, parser, node, zodResolver, tsResolver })
77
100
 
78
101
  const methodBody = [
79
- `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ''}...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,
@@ -1,5 +1,5 @@
1
1
  import { buildParamsMapping, getOperationParameters } from '@internals/shared'
2
- import { isValidVarName, URLPath } from '@internals/utils'
2
+ import { isValidVarName, Url as UrlHelper } from '@internals/utils'
3
3
  import { ast } from '@kubb/core'
4
4
  import type { ResolverTs } from '@kubb/plugin-ts'
5
5
  import { functionPrinter } from '@kubb/plugin-ts'
@@ -58,7 +58,6 @@ export function Url({
58
58
  tsResolver,
59
59
  }: Props): KubbReactNode {
60
60
  if (!ast.isHttpOperationNode(node)) return null
61
- const path = new URLPath(node.path)
62
61
 
63
62
  const paramsNode = buildUrlParamsNode({
64
63
  paramsType,
@@ -82,7 +81,7 @@ export function Url({
82
81
  .map(([originalName, camelCaseName]) => `const ${originalName} = ${camelCaseName}`)
83
82
  .join('\n')}
84
83
  {pathParamsMapping && Object.keys(pathParamsMapping).length > 0 && <br />}
85
- <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>
86
85
  <br />
87
86
  return res
88
87
  </Function>
@@ -1,15 +1,16 @@
1
1
  import path from 'node:path'
2
- import { operationFileEntry, resolveOperationTypeNames } from '@internals/shared'
2
+ import { getOperationParameters, operationFileEntry, resolveOperationTypeNames } from '@internals/shared'
3
3
  import { camelCase } from '@internals/utils'
4
4
  import { ast, defineGenerator } from '@kubb/core'
5
5
  import type { ResolverTs } from '@kubb/plugin-ts'
6
6
  import { pluginTsName } from '@kubb/plugin-ts'
7
7
  import type { ResolverZod } from '@kubb/plugin-zod'
8
8
  import { pluginZodName } from '@kubb/plugin-zod'
9
- import { File, jsxRendererSync } from '@kubb/renderer-jsx'
9
+ import { File, jsxRenderer } from '@kubb/renderer-jsx'
10
10
  import { ClassClient } from '../components/ClassClient'
11
11
  import { WrapperClient } from '../components/WrapperClient'
12
12
  import type { PluginClient } from '../types'
13
+ import { isParserEnabled, resolveQueryParamsParser, resolveRequestParser, resolveResponseParser } from '../utils.ts'
13
14
 
14
15
  type OperationData = {
15
16
  node: ast.OperationNode
@@ -31,10 +32,12 @@ function resolveTypeImportNames(node: ast.OperationNode, tsResolver: ResolverTs)
31
32
  return resolveOperationTypeNames(node, tsResolver, { order: 'body-response-first' })
32
33
  }
33
34
 
34
- function resolveZodImportNames(node: ast.OperationNode, zodResolver: ResolverZod): Array<string> {
35
+ function resolveZodImportNames(node: ast.OperationNode, zodResolver: ResolverZod, parser: PluginClient['resolvedOptions']['parser']): Array<string> {
36
+ const { query: queryParams } = getOperationParameters(node)
35
37
  const names: Array<string | null | undefined> = [
36
- zodResolver.resolveResponseName?.(node),
37
- node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
38
+ resolveResponseParser(parser) === 'zod' ? zodResolver.resolveResponseName?.(node) : null,
39
+ resolveRequestParser(parser) === 'zod' && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
40
+ resolveQueryParamsParser(parser) === 'zod' && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]!) : null,
38
41
  ]
39
42
  return names.filter((n): n is string => Boolean(n))
40
43
  }
@@ -46,7 +49,7 @@ function resolveZodImportNames(node: ast.OperationNode, zodResolver: ResolverZod
46
49
  */
47
50
  export const classClientGenerator = defineGenerator<PluginClient>({
48
51
  name: 'classClient',
49
- renderer: jsxRendererSync,
52
+ renderer: jsxRenderer,
50
53
  operations(nodes, ctx) {
51
54
  const { config, driver, resolver, root } = ctx
52
55
  const { output, group, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, sdk } = ctx.options
@@ -57,7 +60,7 @@ export const classClientGenerator = defineGenerator<PluginClient>({
57
60
 
58
61
  const tsResolver = driver.getResolver(pluginTsName)
59
62
  const tsPluginOptions = pluginTs.options
60
- const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
63
+ const pluginZod = isParserEnabled(parser) ? driver.getPlugin(pluginZodName) : null
61
64
  const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
62
65
 
63
66
  function buildOperationData(node: ast.OperationNode): OperationData {
@@ -88,7 +91,7 @@ export const classClientGenerator = defineGenerator<PluginClient>({
88
91
  const controllers = nodes.reduce((acc, operationNode) => {
89
92
  if (!ast.isHttpOperationNode(operationNode)) return acc
90
93
  const tag = operationNode.tags[0]
91
- const groupName = tag ? (group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag)) : resolver.resolveGroupName('Client')
94
+ const groupName = tag ? (group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag)) : resolver.resolveClassName('Client')
92
95
 
93
96
  if (!tag && !group) {
94
97
  const name = resolver.resolveClassName('ApiClient')
@@ -145,7 +148,7 @@ export const classClientGenerator = defineGenerator<PluginClient>({
145
148
 
146
149
  ops.forEach((op) => {
147
150
  if (!op.zodFile || !zodResolver) return
148
- const names = resolveZodImportNames(op.node, zodResolver)
151
+ const names = resolveZodImportNames(op.node, zodResolver, parser)
149
152
  if (!zodImportsByFile.has(op.zodFile.path)) {
150
153
  zodImportsByFile.set(op.zodFile.path, new Set())
151
154
  }
@@ -161,8 +164,9 @@ export const classClientGenerator = defineGenerator<PluginClient>({
161
164
 
162
165
  const files = controllers.map(({ name, file, operations: ops }) => {
163
166
  const { typeImportsByFile, typeFilesByPath } = collectTypeImports(ops)
164
- const { zodImportsByFile, zodFilesByPath } =
165
- parser === 'zod' ? collectZodImports(ops) : { zodImportsByFile: new Map<string, Set<string>>(), zodFilesByPath: new Map<string, ast.FileNode>() }
167
+ const { zodImportsByFile, zodFilesByPath } = isParserEnabled(parser)
168
+ ? collectZodImports(ops)
169
+ : { zodImportsByFile: new Map<string, Set<string>>(), zodFilesByPath: new Map<string, ast.FileNode>() }
166
170
  const hasFormData = ops.some((op) => op.node.requestBody?.content?.some((e) => e.contentType === 'multipart/form-data') ?? false)
167
171
 
168
172
  return (
@@ -190,6 +194,10 @@ export const classClientGenerator = defineGenerator<PluginClient>({
190
194
 
191
195
  {hasFormData && <File.Import name={['buildFormData']} root={file.path} path={path.resolve(root, '.kubb/config.ts')} />}
192
196
 
197
+ {parser === 'zod' && zodResolver && ops.some((op) => op.node.requestBody?.content?.[0]?.schema != null) && (
198
+ <File.Import name={['z']} path="zod" isTypeOnly />
199
+ )}
200
+
193
201
  {Array.from(typeImportsByFile.entries()).map(([filePath, importSet]) => {
194
202
  const typeFile = typeFilesByPath.get(filePath)
195
203
  if (!typeFile) return null
@@ -198,7 +206,7 @@ export const classClientGenerator = defineGenerator<PluginClient>({
198
206
  return <File.Import key={filePath} name={importNames} root={file.path} path={typeFile.path} isTypeOnly />
199
207
  })}
200
208
 
201
- {parser === 'zod' &&
209
+ {isParserEnabled(parser) &&
202
210
  Array.from(zodImportsByFile.entries()).map(([filePath, importSet]) => {
203
211
  const zodFile = zodFilesByPath.get(filePath)
204
212
  if (!zodFile) return null
@@ -1,12 +1,13 @@
1
1
  import path from 'node:path'
2
- import { operationFileEntry, resolveOperationTypeNames } from '@internals/shared'
2
+ import { getOperationParameters, operationFileEntry, resolveOperationTypeNames } from '@internals/shared'
3
3
  import { ast, defineGenerator } from '@kubb/core'
4
4
  import { pluginTsName } from '@kubb/plugin-ts'
5
5
  import { pluginZodName } from '@kubb/plugin-zod'
6
- import { File, jsxRendererSync } from '@kubb/renderer-jsx'
6
+ import { File, jsxRenderer } from '@kubb/renderer-jsx'
7
7
  import { Client } from '../components/Client'
8
8
  import { Url } from '../components/Url.tsx'
9
9
  import type { PluginClient } from '../types'
10
+ import { isParserEnabled, resolveQueryParamsParser, resolveRequestParser, resolveResponseParser } from '../utils.ts'
10
11
 
11
12
  /**
12
13
  * Built-in operation generator for `@kubb/plugin-client`. Emits one async
@@ -15,7 +16,7 @@ import type { PluginClient } from '../types'
15
16
  */
16
17
  export const clientGenerator = defineGenerator<PluginClient>({
17
18
  name: 'client',
18
- renderer: jsxRendererSync,
19
+ renderer: jsxRenderer,
19
20
  operation(node, ctx) {
20
21
  if (!ast.isHttpOperationNode(node)) return null
21
22
  const { config, driver, resolver, root } = ctx
@@ -30,17 +31,22 @@ export const clientGenerator = defineGenerator<PluginClient>({
30
31
 
31
32
  const tsResolver = driver.getResolver(pluginTsName)
32
33
 
33
- const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
34
+ const pluginZod = isParserEnabled(parser) ? driver.getPlugin(pluginZodName) : null
34
35
  const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
35
36
 
36
37
  const importedTypeNames = resolveOperationTypeNames(node, tsResolver, { paramsCasing })
37
38
 
38
- const importedZodNames =
39
- zodResolver && parser === 'zod'
40
- ? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null].filter(
41
- (name): name is string => Boolean(name),
42
- )
43
- : []
39
+ const { query: queryParams } = getOperationParameters(node)
40
+
41
+ const importedZodNames = zodResolver
42
+ ? [
43
+ resolveResponseParser(parser) === 'zod' ? zodResolver.resolveResponseName?.(node) : null,
44
+ resolveRequestParser(parser) === 'zod' && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
45
+ resolveQueryParamsParser(parser) === 'zod' && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]!) : null,
46
+ ].filter((name): name is string => Boolean(name))
47
+ : []
48
+
49
+ const zodRequestName = zodResolver && parser === 'zod' && node.requestBody?.content?.[0]?.schema ? (zodResolver.resolveDataName?.(node) ?? null) : null
44
50
 
45
51
  const meta = {
46
52
  name: resolver.resolveName(node.operationId),
@@ -90,6 +96,8 @@ export const clientGenerator = defineGenerator<PluginClient>({
90
96
 
91
97
  {hasFormData && <File.Import name={['buildFormData']} root={meta.file.path} path={path.resolve(root, '.kubb/config.ts')} />}
92
98
 
99
+ {zodRequestName && <File.Import name={['z']} path="zod" isTypeOnly />}
100
+
93
101
  {meta.fileZod && importedZodNames.length > 0 && <File.Import name={importedZodNames as Array<string>} root={meta.file.path} path={meta.fileZod.path} />}
94
102
 
95
103
  {meta.fileTs && importedTypeNames.length > 0 && (
@@ -1,7 +1,7 @@
1
1
  import { camelCase } from '@internals/utils'
2
2
  import type { ast } from '@kubb/core'
3
3
  import { defineGenerator } from '@kubb/core'
4
- import { File, Function, jsxRendererSync } from '@kubb/renderer-jsx'
4
+ import { File, Function, jsxRenderer } from '@kubb/renderer-jsx'
5
5
  import type { PluginClient } from '../types'
6
6
 
7
7
  /**
@@ -12,7 +12,7 @@ import type { PluginClient } from '../types'
12
12
  */
13
13
  export const groupedClientGenerator = defineGenerator<PluginClient>({
14
14
  name: 'groupedClient',
15
- renderer: jsxRendererSync,
15
+ renderer: jsxRenderer,
16
16
  operations(nodes, ctx) {
17
17
  const { config, resolver, root } = ctx
18
18
  const { output, group } = ctx.options