@kubb/plugin-client 5.0.0-beta.22 → 5.0.0-beta.25

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/plugin-client",
3
- "version": "5.0.0-beta.22",
3
+ "version": "5.0.0-beta.25",
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",
@@ -87,10 +87,10 @@
87
87
  "registry": "https://registry.npmjs.org/"
88
88
  },
89
89
  "dependencies": {
90
- "@kubb/core": "5.0.0-beta.22",
91
- "@kubb/renderer-jsx": "5.0.0-beta.22",
92
- "@kubb/plugin-ts": "5.0.0-beta.22",
93
- "@kubb/plugin-zod": "5.0.0-beta.22"
90
+ "@kubb/core": "5.0.0-beta.25",
91
+ "@kubb/renderer-jsx": "5.0.0-beta.25",
92
+ "@kubb/plugin-ts": "5.0.0-beta.25",
93
+ "@kubb/plugin-zod": "5.0.0-beta.25"
94
94
  },
95
95
  "devDependencies": {
96
96
  "axios": "^1.16.1",
@@ -98,7 +98,7 @@
98
98
  "@internals/utils": "0.0.0"
99
99
  },
100
100
  "peerDependencies": {
101
- "@kubb/renderer-jsx": "5.0.0-beta.22",
101
+ "@kubb/renderer-jsx": "5.0.0-beta.25",
102
102
  "axios": "^1.7.2"
103
103
  },
104
104
  "peerDependenciesMeta": {
@@ -14,7 +14,7 @@ export type RequestConfig<TData = unknown> = {
14
14
  data?: TData | FormData
15
15
  responseType?: 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'
16
16
  signal?: AbortSignal
17
- headers?: [string, string][] | Record<string, string>
17
+ headers?: Array<[string, string]> | Record<string, string>
18
18
  credentials?: RequestCredentials
19
19
  contentType?: string
20
20
  }
@@ -14,7 +14,7 @@ type OperationData = {
14
14
  node: ast.OperationNode
15
15
  name: string
16
16
  tsResolver: ResolverTs
17
- zodResolver?: ResolverZod
17
+ zodResolver?: ResolverZod | null
18
18
  }
19
19
 
20
20
  type Props = {
@@ -35,7 +35,7 @@ type GenerateMethodProps = {
35
35
  node: ast.OperationNode
36
36
  name: string
37
37
  tsResolver: ResolverTs
38
- zodResolver?: ResolverZod
38
+ zodResolver?: ResolverZod | null
39
39
  baseURL: string | null | undefined
40
40
  dataReturnType: PluginClient['resolvedOptions']['dataReturnType']
41
41
  parser: PluginClient['resolvedOptions']['parser'] | undefined
@@ -62,7 +62,7 @@ function generateMethod({
62
62
  const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node)
63
63
  const isFormData = !isMultipleContentTypes && contentType === 'multipart/form-data'
64
64
  const { header: headerParams } = getOperationParameters(node)
65
- const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]!) : undefined
65
+ const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]!) : null
66
66
  const headers = isMultipleContentTypes ? (headerParamsName ? ['...headers'] : []) : buildHeaders(contentType, !!headerParamsName)
67
67
  const generics = buildGenerics(node, tsResolver)
68
68
  const paramsNode = buildClientParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver, isConfigurable: true })
@@ -26,7 +26,7 @@ type Props = {
26
26
  parser: PluginClient['resolvedOptions']['parser'] | undefined
27
27
  node: ast.OperationNode
28
28
  tsResolver: ResolverTs
29
- zodResolver?: ResolverZod
29
+ zodResolver?: ResolverZod | null
30
30
  children?: KubbReactNode
31
31
  }
32
32
 
@@ -96,17 +96,17 @@ export function Client({
96
96
  const { path: originalPathParams, query: originalQueryParams, header: originalHeaderParams } = getOperationParameters(node)
97
97
  const { path: casedPathParams, query: casedQueryParams, header: casedHeaderParams } = getOperationParameters(node, { paramsCasing })
98
98
 
99
- const pathParamsMapping = paramsCasing && !urlName ? buildParamsMapping(originalPathParams, casedPathParams) : undefined
100
- const queryParamsMapping = paramsCasing ? buildParamsMapping(originalQueryParams, casedQueryParams) : undefined
101
- const headerParamsMapping = paramsCasing ? buildParamsMapping(originalHeaderParams, casedHeaderParams) : undefined
99
+ const pathParamsMapping = paramsCasing && !urlName ? buildParamsMapping(originalPathParams, casedPathParams) : null
100
+ const queryParamsMapping = paramsCasing ? buildParamsMapping(originalQueryParams, casedQueryParams) : null
101
+ const headerParamsMapping = paramsCasing ? buildParamsMapping(originalHeaderParams, casedHeaderParams) : null
102
102
 
103
- const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : undefined
103
+ const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null
104
104
  const responseName = tsResolver.resolveResponseName(node)
105
- const queryParamsName = originalQueryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, originalQueryParams[0]!) : undefined
106
- const headerParamsName = originalHeaderParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, originalHeaderParams[0]!) : undefined
105
+ const queryParamsName = originalQueryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, originalQueryParams[0]!) : null
106
+ const headerParamsName = originalHeaderParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, originalHeaderParams[0]!) : null
107
107
 
108
- const zodResponseName = zodResolver && parser === 'zod' ? zodResolver.resolveResponseName?.(node) : undefined
109
- const zodRequestName = zodResolver && parser === 'zod' && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : undefined
108
+ const zodResponseName = zodResolver && parser === 'zod' ? zodResolver.resolveResponseName?.(node) : null
109
+ const zodRequestName = zodResolver && parser === 'zod' && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null
110
110
 
111
111
  const errorNames = node.responses
112
112
  .filter((r) => {
@@ -116,8 +116,8 @@ export function Client({
116
116
  .map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode))
117
117
 
118
118
  const headers = [
119
- !isMultipleContentTypes && contentType !== 'application/json' && contentType !== 'multipart/form-data' ? `'Content-Type': '${contentType}'` : undefined,
120
- headerParamsName ? (headerParamsMapping ? '...mappedHeaders' : '...headers') : undefined,
119
+ !isMultipleContentTypes && contentType !== 'application/json' && contentType !== 'multipart/form-data' ? `'Content-Type': '${contentType}'` : null,
120
+ headerParamsName ? (headerParamsMapping ? '...mappedHeaders' : '...headers') : null,
121
121
  ].filter(Boolean)
122
122
 
123
123
  const TError = `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(' | ') : 'Error'}>`
@@ -158,8 +158,8 @@ export function Client({
158
158
  ? {
159
159
  value: `\`${baseURL}\``,
160
160
  }
161
- : undefined,
162
- params: queryParamsName ? (queryParamsMapping ? { value: 'mappedParams' } : {}) : undefined,
161
+ : null,
162
+ params: queryParamsName ? (queryParamsMapping ? { value: 'mappedParams' } : {}) : null,
163
163
  data: requestName
164
164
  ? {
165
165
  value:
@@ -169,18 +169,18 @@ export function Client({
169
169
  ? 'formData as FormData'
170
170
  : 'requestData',
171
171
  }
172
- : undefined,
173
- contentType: isConfigurable && isMultipleContentTypes ? {} : undefined,
172
+ : null,
173
+ contentType: isConfigurable && isMultipleContentTypes ? {} : null,
174
174
  requestConfig: isConfigurable
175
175
  ? {
176
176
  mode: 'inlineSpread',
177
177
  }
178
- : undefined,
178
+ : null,
179
179
  headers: headers.length
180
180
  ? {
181
181
  value: isConfigurable ? `{ ${headers.join(', ')}, ...requestConfig.headers }` : `{ ${headers.join(', ')} }`,
182
182
  }
183
- : undefined,
183
+ : null,
184
184
  },
185
185
  },
186
186
  })
@@ -14,7 +14,7 @@ type OperationData = {
14
14
  node: ast.OperationNode
15
15
  name: string
16
16
  tsResolver: ResolverTs
17
- zodResolver?: ResolverZod
17
+ zodResolver?: ResolverZod | null
18
18
  }
19
19
 
20
20
  type Props = {
@@ -35,7 +35,7 @@ type GenerateMethodProps = {
35
35
  node: ast.OperationNode
36
36
  name: string
37
37
  tsResolver: ResolverTs
38
- zodResolver?: ResolverZod
38
+ zodResolver?: ResolverZod | null
39
39
  baseURL: string | null | undefined
40
40
  dataReturnType: PluginClient['resolvedOptions']['dataReturnType']
41
41
  parser: PluginClient['resolvedOptions']['parser'] | undefined
@@ -62,7 +62,7 @@ function generateMethod({
62
62
  const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node)
63
63
  const isFormData = !isMultipleContentTypes && contentType === 'multipart/form-data'
64
64
  const { header: headerParams } = getOperationParameters(node)
65
- const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]!) : undefined
65
+ const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]!) : null
66
66
  const headers = isMultipleContentTypes ? (headerParamsName ? ['...headers'] : []) : buildHeaders(contentType, !!headerParamsName)
67
67
  const generics = buildGenerics(node, tsResolver)
68
68
  const paramsNode = buildClientParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver, isConfigurable: true })
@@ -70,7 +70,7 @@ export function Url({
70
70
 
71
71
  const { path: originalPathParams } = getOperationParameters(node)
72
72
  const { path: casedPathParams } = getOperationParameters(node, { paramsCasing })
73
- const pathParamsMapping = paramsCasing ? buildParamsMapping(originalPathParams, casedPathParams) : undefined
73
+ const pathParamsMapping = paramsCasing ? buildParamsMapping(originalPathParams, casedPathParams) : null
74
74
 
75
75
  return (
76
76
  <File.Source name={name} isExportable={isExportable} isIndexable={isIndexable}>
@@ -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() {
@@ -16,9 +16,9 @@ type OperationData = {
16
16
  node: ast.OperationNode
17
17
  name: string
18
18
  tsResolver: ResolverTs
19
- zodResolver: ResolverZod | undefined
19
+ zodResolver: ResolverZod | null
20
20
  typeFile: ast.FileNode
21
- zodFile: ast.FileNode | undefined
21
+ zodFile: ast.FileNode | null
22
22
  }
23
23
 
24
24
  type Controller = {
@@ -33,13 +33,18 @@ function resolveTypeImportNames(node: ast.OperationNode, tsResolver: ResolverTs)
33
33
  }
34
34
 
35
35
  function resolveZodImportNames(node: ast.OperationNode, zodResolver: ResolverZod): Array<string> {
36
- const names: Array<string | undefined> = [
36
+ const names: Array<string | null | undefined> = [
37
37
  zodResolver.resolveResponseName?.(node),
38
- node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : undefined,
38
+ node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
39
39
  ]
40
40
  return names.filter((n): n is string => Boolean(n))
41
41
  }
42
42
 
43
+ /**
44
+ * Built-in `operations` generator for `@kubb/plugin-client` when
45
+ * `clientType: 'class'`. Emits one class per tag, with one instance method
46
+ * per operation and a shared constructor for request configuration.
47
+ */
43
48
  export const classClientGenerator = defineGenerator<PluginClient>({
44
49
  name: 'classClient',
45
50
  renderer: jsxRendererSync,
@@ -53,8 +58,8 @@ export const classClientGenerator = defineGenerator<PluginClient>({
53
58
 
54
59
  const tsResolver = driver.getResolver(pluginTsName)
55
60
  const tsPluginOptions = pluginTs.options
56
- const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : undefined
57
- const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : undefined
61
+ const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
62
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
58
63
 
59
64
  function buildOperationData(node: ast.OperationNode): OperationData {
60
65
  const typeFile = tsResolver.resolveFile(
@@ -65,9 +70,9 @@ export const classClientGenerator = defineGenerator<PluginClient>({
65
70
  zodResolver && pluginZod?.options
66
71
  ? zodResolver.resolveFile(
67
72
  { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
68
- { root, output: pluginZod.options?.output ?? output, group: pluginZod.options?.group },
73
+ { root, output: pluginZod.options?.output ?? output, group: pluginZod.options?.group ?? undefined },
69
74
  )
70
- : undefined
75
+ : null
71
76
 
72
77
  return {
73
78
  node: node,
@@ -85,7 +90,7 @@ export const classClientGenerator = defineGenerator<PluginClient>({
85
90
 
86
91
  if (!tag && !group) {
87
92
  const name = resolver.resolveClassName('ApiClient')
88
- const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group })
93
+ const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group: group ?? undefined })
89
94
  const operationData = buildOperationData(operationNode)
90
95
  const previous = acc.find((item) => item.file.path === file.path)
91
96
 
@@ -99,7 +104,7 @@ export const classClientGenerator = defineGenerator<PluginClient>({
99
104
 
100
105
  if (tag) {
101
106
  const name = groupName
102
- const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group })
107
+ const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group: group ?? undefined })
103
108
  const operationData = buildOperationData(operationNode)
104
109
  const previous = acc.find((item) => item.file.path === file.path)
105
110
 
@@ -215,7 +220,7 @@ export const classClientGenerator = defineGenerator<PluginClient>({
215
220
  })
216
221
 
217
222
  if (sdk) {
218
- const sdkFile = resolver.resolveFile({ name: sdk.className, extname: '.ts' }, { root, output, group })
223
+ const sdkFile = resolver.resolveFile({ name: sdk.className, extname: '.ts' }, { root, output, group: group ?? undefined })
219
224
 
220
225
  files.push(
221
226
  <File
@@ -8,6 +8,11 @@ import { Client } from '../components/Client'
8
8
  import { Url } from '../components/Url.tsx'
9
9
  import type { PluginClient } from '../types'
10
10
 
11
+ /**
12
+ * Built-in operation generator for `@kubb/plugin-client`. Emits one async
13
+ * function per OpenAPI operation, plus the matching URL helper. Used when
14
+ * `clientType: 'function'` (the default).
15
+ */
11
16
  export const clientGenerator = defineGenerator<PluginClient>({
12
17
  name: 'client',
13
18
  renderer: jsxRendererSync,
@@ -24,14 +29,14 @@ export const clientGenerator = defineGenerator<PluginClient>({
24
29
 
25
30
  const tsResolver = driver.getResolver(pluginTsName)
26
31
 
27
- const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : undefined
28
- const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : undefined
32
+ const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
33
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
29
34
 
30
35
  const importedTypeNames = resolveOperationTypeNames(node, tsResolver, { paramsCasing })
31
36
 
32
37
  const importedZodNames =
33
38
  zodResolver && parser === 'zod'
34
- ? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : undefined].filter(
39
+ ? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null].filter(
35
40
  (name): name is string => Boolean(name),
36
41
  )
37
42
  : []
@@ -39,13 +44,16 @@ export const clientGenerator = defineGenerator<PluginClient>({
39
44
  const meta = {
40
45
  name: resolver.resolveName(node.operationId),
41
46
  urlName: resolver.resolveUrlName(node),
42
- file: resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
47
+ file: resolver.resolveFile(
48
+ { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
49
+ { root, output, group: group ?? undefined },
50
+ ),
43
51
  fileTs: tsResolver.resolveFile(
44
52
  { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
45
53
  {
46
54
  root,
47
55
  output: pluginTs.options?.output ?? output,
48
- group: pluginTs.options?.group,
56
+ group: pluginTs.options?.group ?? undefined,
49
57
  },
50
58
  ),
51
59
  fileZod:
@@ -55,10 +63,10 @@ export const clientGenerator = defineGenerator<PluginClient>({
55
63
  {
56
64
  root,
57
65
  output: pluginZod.options.output ?? output,
58
- group: pluginZod.options?.group,
66
+ group: pluginZod.options?.group ?? undefined,
59
67
  },
60
68
  )
61
- : undefined,
69
+ : null,
62
70
  } as const
63
71
 
64
72
  const hasFormData = node.requestBody?.content?.some((e) => e.contentType === 'multipart/form-data') ?? false
@@ -90,7 +98,7 @@ export const clientGenerator = defineGenerator<PluginClient>({
90
98
 
91
99
  {hasFormData && <File.Import name={['buildFormData']} root={meta.file.path} path={path.resolve(root, '.kubb/config.ts')} />}
92
100
 
93
- {meta.fileZod && importedZodNames.length > 0 && <File.Import name={importedZodNames as string[]} root={meta.file.path} path={meta.fileZod.path} />}
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 && (
96
104
  <File.Import name={Array.from(new Set(importedTypeNames))} root={meta.file.path} path={meta.fileTs.path} isTypeOnly />
@@ -4,6 +4,12 @@ import { defineGenerator } from '@kubb/core'
4
4
  import { File, Function, jsxRendererSync } from '@kubb/renderer-jsx'
5
5
  import type { PluginClient } from '../types'
6
6
 
7
+ /**
8
+ * Emits one aggregate file per tag/group when `group` is configured. Each
9
+ * file re-exports every client function for that group, so callers can
10
+ * `import { petController } from './gen/clients'` instead of importing
11
+ * each operation individually.
12
+ */
7
13
  export const groupedClientGenerator = defineGenerator<PluginClient>({
8
14
  name: 'groupedClient',
9
15
  renderer: jsxRendererSync,
@@ -15,16 +21,16 @@ export const groupedClientGenerator = defineGenerator<PluginClient>({
15
21
  (acc, operationNode) => {
16
22
  if (group?.type === 'tag') {
17
23
  const tag = operationNode.tags[0]
18
- const name = tag ? group?.name?.({ group: camelCase(tag) }) : undefined
24
+ const name = tag ? group?.name?.({ group: camelCase(tag) }) : null
19
25
 
20
26
  if (!tag || !name) {
21
27
  return acc
22
28
  }
23
29
 
24
- const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group })
30
+ const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group: group ?? undefined })
25
31
  const clientFile = resolver.resolveFile(
26
32
  { name: operationNode.operationId, extname: '.ts', tag: operationNode.tags[0] ?? 'default', path: operationNode.path },
27
- { root, output, group },
33
+ { root, output, group: group ?? undefined },
28
34
  )
29
35
 
30
36
  const client = {
@@ -3,6 +3,12 @@ import { File, jsxRendererSync } from '@kubb/renderer-jsx'
3
3
  import { Operations } from '../components/Operations'
4
4
  import type { PluginClient } from '../types'
5
5
 
6
+ /**
7
+ * Generates an `operations.ts` file that re-exports every operation grouped
8
+ * by HTTP method. Enabled when `pluginClient({ operations: true })`. Useful
9
+ * for building meta-tooling on top of the generated client (route
10
+ * registries, API explorers).
11
+ */
6
12
  export const operationsGenerator = defineGenerator<PluginClient>({
7
13
  name: 'client',
8
14
  renderer: jsxRendererSync,
@@ -11,7 +17,7 @@ export const operationsGenerator = defineGenerator<PluginClient>({
11
17
  const { output, group } = ctx.options
12
18
 
13
19
  const name = 'operations'
14
- const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group })
20
+ const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group: group ?? undefined })
15
21
 
16
22
  return (
17
23
  <File
@@ -15,9 +15,9 @@ type OperationData = {
15
15
  node: ast.OperationNode
16
16
  name: string
17
17
  tsResolver: ResolverTs
18
- zodResolver: ResolverZod | undefined
18
+ zodResolver: ResolverZod | null
19
19
  typeFile: ast.FileNode
20
- zodFile: ast.FileNode | undefined
20
+ zodFile: ast.FileNode | null
21
21
  }
22
22
 
23
23
  type Controller = {
@@ -31,13 +31,19 @@ function resolveTypeImportNames(node: ast.OperationNode, tsResolver: ResolverTs)
31
31
  }
32
32
 
33
33
  function resolveZodImportNames(node: ast.OperationNode, zodResolver: ResolverZod): Array<string> {
34
- const names: Array<string | undefined> = [
34
+ const names: Array<string | null | undefined> = [
35
35
  zodResolver.resolveResponseName?.(node),
36
- node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : undefined,
36
+ node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
37
37
  ]
38
38
  return names.filter((n): n is string => Boolean(n))
39
39
  }
40
40
 
41
+ /**
42
+ * Built-in `operations` generator for `@kubb/plugin-client` when
43
+ * `clientType: 'staticClass'`. Emits one class per tag, with a static method
44
+ * per operation so callers can use `Pet.getPetById(...)` without
45
+ * instantiating the class.
46
+ */
41
47
  export const staticClassClientGenerator = defineGenerator<PluginClient>({
42
48
  name: 'staticClassClient',
43
49
  renderer: jsxRendererSync,
@@ -51,8 +57,8 @@ export const staticClassClientGenerator = defineGenerator<PluginClient>({
51
57
 
52
58
  const tsResolver = driver.getResolver(pluginTsName)
53
59
  const tsPluginOptions = pluginTs.options
54
- const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : undefined
55
- const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : undefined
60
+ const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
61
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
56
62
 
57
63
  function buildOperationData(node: ast.OperationNode): OperationData {
58
64
  const typeFile = tsResolver.resolveFile(
@@ -63,9 +69,9 @@ export const staticClassClientGenerator = defineGenerator<PluginClient>({
63
69
  zodResolver && pluginZod?.options
64
70
  ? zodResolver.resolveFile(
65
71
  { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
66
- { root, output: pluginZod.options?.output ?? output, group: pluginZod.options?.group },
72
+ { root, output: pluginZod.options?.output ?? output, group: pluginZod.options?.group ?? undefined },
67
73
  )
68
- : undefined
74
+ : null
69
75
 
70
76
  return {
71
77
  node: node,
@@ -83,7 +89,7 @@ export const staticClassClientGenerator = defineGenerator<PluginClient>({
83
89
 
84
90
  if (!tag && !group) {
85
91
  const name = resolver.resolveClassName('ApiClient')
86
- const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group })
92
+ const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group: group ?? undefined })
87
93
  const operationData = buildOperationData(operationNode)
88
94
  const previous = acc.find((item) => item.file.path === file.path)
89
95
 
@@ -97,7 +103,7 @@ export const staticClassClientGenerator = defineGenerator<PluginClient>({
97
103
 
98
104
  if (tag) {
99
105
  const name = groupName
100
- const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group })
106
+ const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group: group ?? undefined })
101
107
  const operationData = buildOperationData(operationNode)
102
108
  const previous = acc.find((item) => item.file.path === file.path)
103
109
 
package/src/plugin.ts CHANGED
@@ -16,20 +16,33 @@ import { source as configSource } from './templates/config.source.ts'
16
16
  import type { PluginClient } from './types.ts'
17
17
 
18
18
  /**
19
- * Canonical plugin name for `@kubb/plugin-client`, used in driver lookups and warnings.
19
+ * Canonical plugin name for `@kubb/plugin-client`. Used for driver lookups and
20
+ * cross-plugin dependency references.
20
21
  */
21
22
  export const pluginClientName = 'plugin-client' satisfies PluginClient['name']
22
23
 
23
24
  /**
24
- * Generates type-safe HTTP client functions or classes from an OpenAPI specification.
25
- * Creates client APIs by walking operations and delegating to generators.
26
- * Writes barrel files based on the configured `barrelType`.
25
+ * Generates one HTTP client function per OpenAPI operation. Each function has
26
+ * typed path params, query params, body, and response, so callers use the API
27
+ * like any other typed function. Ships with `axios` and `fetch` runtimes; bring
28
+ * your own by setting `importPath`.
27
29
  *
28
- * @example Client generator
30
+ * @example
29
31
  * ```ts
30
- * import pluginClient from '@kubb/plugin-client'
32
+ * import { defineConfig } from 'kubb'
33
+ * import { pluginTs } from '@kubb/plugin-ts'
34
+ * import { pluginClient } from '@kubb/plugin-client'
35
+ *
31
36
  * export default defineConfig({
32
- * plugins: [pluginClient({ output: { path: 'clients' } })]
37
+ * input: { path: './petStore.yaml' },
38
+ * output: { path: './src/gen' },
39
+ * plugins: [
40
+ * pluginTs(),
41
+ * pluginClient({
42
+ * output: { path: './clients' },
43
+ * client: 'fetch',
44
+ * }),
45
+ * ],
33
46
  * })
34
47
  * ```
35
48
  */
@@ -63,8 +76,8 @@ export const pluginClient = definePlugin<PluginClient>((options) => {
63
76
  options.generators ??
64
77
  [
65
78
  clientType === 'staticClass' ? staticClassClientGenerator : clientType === 'class' ? classClientGenerator : clientGenerator,
66
- group && clientType === 'function' ? groupedClientGenerator : undefined,
67
- operations ? operationsGenerator : undefined,
79
+ group && clientType === 'function' ? groupedClientGenerator : null,
80
+ operations ? operationsGenerator : null,
68
81
  ].filter((x): x is NonNullable<typeof x> => Boolean(x))
69
82
 
70
83
  const groupConfig = group
@@ -79,12 +92,12 @@ export const pluginClient = definePlugin<PluginClient>((options) => {
79
92
  return `${camelCase(ctx.group)}Controller`
80
93
  },
81
94
  } satisfies Group)
82
- : undefined
95
+ : null
83
96
 
84
97
  return {
85
98
  name: pluginClientName,
86
99
  options,
87
- dependencies: [pluginTsName, parser === 'zod' ? pluginZodName : undefined].filter((dependency): dependency is string => Boolean(dependency)),
100
+ dependencies: [pluginTsName, parser === 'zod' ? pluginZodName : null].filter((dependency): dependency is string => Boolean(dependency)),
88
101
  hooks: {
89
102
  'kubb:plugin:setup'(ctx) {
90
103
  const resolver = userResolver ? { ...resolverClient, ...userResolver } : resolverClient
@@ -3,12 +3,18 @@ import { defineResolver } from '@kubb/core'
3
3
  import type { PluginClient } from '../types.ts'
4
4
 
5
5
  /**
6
- * Naming convention resolver for client plugin.
6
+ * Default resolver used by `@kubb/plugin-client`. Decides the names and file
7
+ * paths for every generated client function or class. Functions and files use
8
+ * camelCase; classes and tag groups use PascalCase.
7
9
  *
8
- * Provides default naming helpers using camelCase for functions and file paths.
10
+ * @example Resolve client function and class names
11
+ * ```ts
12
+ * import { resolverClient } from '@kubb/plugin-client'
9
13
  *
10
- * @example
11
- * `resolverClient.default('list pets', 'function') // 'listPets'`
14
+ * resolverClient.default('list pets', 'function') // 'listPets'
15
+ * resolverClient.resolveClassName('pet') // 'Pet'
16
+ * resolverClient.resolveUrlName(operationNode) // 'getShowPetByIdUrl'
17
+ * ```
12
18
  */
13
19
  export const resolverClient = defineResolver<PluginClient>(() => ({
14
20
  name: 'default',