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

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 (37) hide show
  1. package/dist/clients/axios.cjs +18 -3
  2. package/dist/clients/axios.cjs.map +1 -1
  3. package/dist/clients/axios.d.ts +8 -2
  4. package/dist/clients/axios.js +18 -3
  5. package/dist/clients/axios.js.map +1 -1
  6. package/dist/clients/fetch.cjs +16 -1
  7. package/dist/clients/fetch.cjs.map +1 -1
  8. package/dist/clients/fetch.d.ts +8 -2
  9. package/dist/clients/fetch.js +16 -1
  10. package/dist/clients/fetch.js.map +1 -1
  11. package/dist/index.cjs +171 -94
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.ts +125 -65
  14. package/dist/index.js +171 -94
  15. package/dist/index.js.map +1 -1
  16. package/dist/templates/clients/axios.source.cjs +1 -1
  17. package/dist/templates/clients/axios.source.js +1 -1
  18. package/dist/templates/clients/fetch.source.cjs +1 -1
  19. package/dist/templates/clients/fetch.source.js +1 -1
  20. package/extension.yaml +774 -260
  21. package/package.json +6 -6
  22. package/src/clients/axios.ts +28 -5
  23. package/src/clients/fetch.ts +25 -2
  24. package/src/components/ClassClient.tsx +3 -3
  25. package/src/components/Client.tsx +27 -19
  26. package/src/components/StaticClassClient.tsx +3 -3
  27. package/src/components/Url.tsx +1 -1
  28. package/src/functionParams.ts +8 -8
  29. package/src/generators/classClientGenerator.tsx +16 -11
  30. package/src/generators/clientGenerator.tsx +16 -8
  31. package/src/generators/groupedClientGenerator.tsx +9 -3
  32. package/src/generators/operationsGenerator.tsx +7 -1
  33. package/src/generators/staticClassClientGenerator.tsx +16 -10
  34. package/src/plugin.ts +24 -11
  35. package/src/resolvers/resolverClient.ts +16 -9
  36. package/src/types.ts +66 -53
  37. package/src/utils.ts +17 -16
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
@@ -1,20 +1,27 @@
1
- import { camelCase, pascalCase } from '@internals/utils'
1
+ import { camelCase, ensureValidVarName, pascalCase } from '@internals/utils'
2
2
  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',
15
21
  pluginName: 'plugin-client',
16
22
  default(name, type) {
17
- return camelCase(name, { isFile: type === 'file' })
23
+ const resolved = camelCase(name, { isFile: type === 'file' })
24
+ return type === 'file' ? resolved : ensureValidVarName(resolved)
18
25
  },
19
26
  resolveName(name) {
20
27
  return this.default(name, 'function')
@@ -23,13 +30,13 @@ export const resolverClient = defineResolver<PluginClient>(() => ({
23
30
  return this.default(name, type)
24
31
  },
25
32
  resolveClassName(name) {
26
- return pascalCase(name)
33
+ return ensureValidVarName(pascalCase(name))
27
34
  },
28
35
  resolveGroupName(name) {
29
- return pascalCase(name)
36
+ return ensureValidVarName(pascalCase(name))
30
37
  },
31
38
  resolveClientPropertyName(name) {
32
- return camelCase(name)
39
+ return ensureValidVarName(camelCase(name))
33
40
  },
34
41
  resolveUrlName(node) {
35
42
  const name = this.resolveName(node.operationId)
package/src/types.ts CHANGED
@@ -46,9 +46,10 @@ export type ResolverClient = Resolver & {
46
46
  export type ClientImportPath =
47
47
  | {
48
48
  /**
49
- * Which client should be used to do the HTTP calls.
50
- * - 'axios' uses axios client for HTTP requests.
51
- * - 'fetch' uses native fetch API for HTTP requests.
49
+ * HTTP client used by the generated code.
50
+ * - `'axios'` imports from `@kubb/plugin-client/clients/axios`. Requires `axios` at runtime.
51
+ * - `'fetch'` imports from `@kubb/plugin-client/clients/fetch`. Uses the global `fetch`.
52
+ *
52
53
  * @default 'axios'
53
54
  */
54
55
  client?: 'axios' | 'fetch'
@@ -57,9 +58,12 @@ export type ClientImportPath =
57
58
  | {
58
59
  client?: never
59
60
  /**
60
- * Client import path for API calls.
61
- * Used as `import client from '${importPath}'`.
62
- * Accepts relative and absolute paths; path changes are not performed.
61
+ * Path to a custom client module. Generated files import their HTTP runtime from here
62
+ * instead of `@kubb/plugin-client/clients/{client}`. Accepts both relative paths and
63
+ * bare module specifiers; the value is used as-is.
64
+ *
65
+ * @note When combined with a query plugin, the module must export `Client`,
66
+ * `RequestConfig`, and `ResponseErrorConfig` types.
63
67
  */
64
68
  importPath: string
65
69
  /**
@@ -81,29 +85,28 @@ export type ClientImportPath =
81
85
  type ParamsTypeOptions =
82
86
  | {
83
87
  /**
84
- * All parameters path, query, headers, and body are merged into a single
88
+ * Every operation parameter (path, query, headers, body) is wrapped in a single
85
89
  * destructured object argument.
86
- * - 'object' returns the params and pathParams as an object.
87
- * @default 'inline'
88
90
  */
89
91
  paramsType: 'object'
90
92
  /**
91
93
  * `pathParamsType` has no effect when `paramsType` is `'object'`.
92
- * Path params are already inside the single destructured object.
94
+ * Path params already live inside the single destructured object.
93
95
  */
94
96
  pathParamsType?: never
95
97
  }
96
98
  | {
97
99
  /**
98
- * Each parameter group is emitted as a separate function argument.
99
- * - 'inline' returns the params as comma separated params.
100
+ * Each parameter group is emitted as a separate positional function argument.
101
+ *
100
102
  * @default 'inline'
101
103
  */
102
104
  paramsType?: 'inline'
103
105
  /**
104
- * Controls how path parameters are arranged within the inline argument list.
105
- * - 'object' groups path params into a destructured object: `{ petId }: PathParams`.
106
- * - 'inline' emits each path param as its own argument: `petId: string`.
106
+ * How URL path parameters are arranged inside the inline argument list.
107
+ * - `'object'` groups them into one destructured object: `{ petId }: PathParams`.
108
+ * - `'inline'` emits each path param as its own argument: `petId: string`.
109
+ *
107
110
  * @default 'inline'
108
111
  */
109
112
  pathParamsType?: 'object' | 'inline'
@@ -111,87 +114,96 @@ type ParamsTypeOptions =
111
114
 
112
115
  export type Options = {
113
116
  /**
114
- * Specify the export location for the files and define the behavior of the output.
115
- * @default { path: 'clients', barrelType: 'named' }
117
+ * Where the generated client files are written and how they are exported.
118
+ *
119
+ * @default { path: 'clients', barrel: { type: 'named' } }
116
120
  */
117
121
  output?: Output
118
122
  /**
119
- * Group the clients based on the provided name.
123
+ * Split generated files into subfolders based on the operation's tag.
120
124
  */
121
125
  group?: Group
122
126
  /**
123
- * Array containing exclude parameters to exclude/skip tags/operations/methods/paths.
127
+ * Skip operations matching at least one entry in the list.
124
128
  */
125
129
  exclude?: Array<Exclude>
126
130
  /**
127
- * Array containing include parameters to include tags/operations/methods/paths.
131
+ * Restrict generation to operations matching at least one entry in the list.
128
132
  */
129
133
  include?: Array<Include>
130
134
  /**
131
- * Array containing override parameters to override `options` based on tags/operations/methods/paths.
135
+ * Apply a different options object to operations matching a pattern.
132
136
  */
133
137
  override?: Array<Override<ResolvedOptions>>
134
138
  /**
135
- * Create `operations.ts` file with all operations grouped by methods.
139
+ * Emit an `operations.ts` file that re-exports every generated function grouped by HTTP method.
140
+ *
136
141
  * @default false
137
142
  */
138
143
  operations?: boolean
139
144
  /**
140
- * Export urls that are used by operation x.
141
- * - 'export' makes them part of your barrel file.
142
- * - false does not make them exportable.
145
+ * Whether to also export the URL builder helpers (`get<Operation>Url`).
146
+ * - `'export'` exposes them via the barrel.
147
+ * - `false` keeps them private.
148
+ *
143
149
  * @default false
144
- * @example getGetPetByIdUrl
145
150
  */
146
151
  urlType?: 'export' | false
147
152
  /**
148
- * Allows you to set a custom base url for all generated calls.
153
+ * Base URL prepended to every request. When omitted, falls back to the adapter's
154
+ * server URL (typically `servers[0].url`).
149
155
  */
150
156
  baseURL?: string
151
157
  /**
152
- * ReturnType that is used when calling the client.
153
- * - 'data' returns ResponseConfig[data].
154
- * - 'full' returns ResponseConfig.
158
+ * Shape of the value returned by each generated client function.
159
+ * - `'data'` only the response body.
160
+ * - `'full'` the full response config (body, status, headers, request).
161
+ *
155
162
  * @default 'data'
156
163
  */
157
164
  dataReturnType?: 'data' | 'full'
158
165
  /**
159
- * How to style your params, by default no casing is applied.
160
- * - 'camelcase' uses camelCase for pathParams, queryParams and headerParams names
161
- * @note response types (data/body) are not affected by this option
166
+ * Rename parameter properties in the generated client (path, query, headers).
167
+ * The HTTP request still uses the original spec names; Kubb writes the mapping for you.
168
+ *
169
+ * @note Use the same value on `@kubb/plugin-ts` so types stay compatible.
162
170
  */
163
171
  paramsCasing?: 'camelcase'
164
172
  /**
165
- * Which parser can be used before returning the data.
166
- * - 'client' returns the data as-is from the client.
167
- * - 'zod' uses @kubb/plugin-zod to parse the data.
173
+ * Validator applied to response bodies before they are returned to the caller.
174
+ * - `'client'` no validation. Trusts the API.
175
+ * - `'zod'` pipes responses through schemas from `@kubb/plugin-zod`.
176
+ *
168
177
  * @default 'client'
169
178
  */
170
179
  parser?: 'client' | 'zod'
171
180
  /**
172
- * How to generate the client code.
173
- * - 'function' generates standalone functions for each operation.
174
- * - 'class' generates a class with methods for each operation.
175
- * - 'staticClass' generates a class with static methods for each operation.
181
+ * Shape of the generated client.
182
+ * - `'function'` one standalone async function per operation.
183
+ * - `'class'` one class per tag with instance methods.
184
+ * - `'staticClass'` one class per tag with static methods.
185
+ *
176
186
  * @default 'function'
187
+ * @note Only `'function'` is compatible with query plugins.
177
188
  */
178
189
  clientType?: 'function' | 'class' | 'staticClass'
179
190
  /**
180
- * Bundle the selected client into the generated `.kubb` directory.
181
- * When disabled the generated clients will import the shared runtime from `@kubb/plugin-client/clients/*`.
191
+ * Copy the HTTP client runtime into the generated output so consumers do not need
192
+ * `@kubb/plugin-client` at runtime. When `false`, generated files import from
193
+ * `@kubb/plugin-client/clients/{client}`.
194
+ *
182
195
  * @default false
183
- * In version 5 of Kubb this is by default true
184
196
  */
185
197
  bundle?: boolean
186
198
  /**
187
- * Generate an SDK facade class that composes all tag-based client classes into a single entry point.
188
- * Setting this option automatically enables `clientType: 'class'`.
199
+ * Generate a single SDK class composing every tag-based client into one entry point.
200
+ * Automatically enables `clientType: 'class'`.
201
+ *
189
202
  * @example
190
203
  * ```ts
191
204
  * pluginClient({
192
205
  * sdk: { className: 'PetStoreSDK' },
193
206
  * })
194
- * // Generates a class with a shared constructor config and one property per tag:
195
207
  * // class PetStoreSDK {
196
208
  * // readonly petController: petController
197
209
  * // readonly storeController: storeController
@@ -201,22 +213,23 @@ export type Options = {
201
213
  */
202
214
  sdk?: {
203
215
  /**
204
- * Name of the generated SDK facade class.
216
+ * Name of the generated SDK facade class. Also the file name.
205
217
  */
206
218
  className: string
207
219
  }
208
220
  /**
209
- * Override individual resolver methods. Any method you omit falls back to the
210
- * preset resolver's implementation. Use `this.default(...)` to call it.
221
+ * Override how names and file paths are built for the generated client.
222
+ * Methods you omit fall back to the default resolver. `this` is bound to the
223
+ * full resolver, so `this.default(name)` delegates to the built-in implementation.
211
224
  */
212
225
  resolver?: Partial<ResolverClient> & ThisType<ResolverClient>
213
226
  /**
214
- * Single AST visitor applied to each node before printing.
215
- * Return `null` or `undefined` from a method to leave the node unchanged.
227
+ * AST visitor applied to each operation node before code is printed.
228
+ * Return `null` or `undefined` to leave the node unchanged.
216
229
  */
217
230
  transformer?: ast.Visitor
218
231
  /**
219
- * Define some generators next to the client generators.
232
+ * Custom generators that run alongside the built-in client generators.
220
233
  */
221
234
  generators?: Array<Generator<PluginClient>>
222
235
  } & ClientImportPath &
@@ -227,7 +240,7 @@ type ResolvedOptions = {
227
240
  exclude: Array<Exclude>
228
241
  include: Array<Include> | undefined
229
242
  override: Array<Override<ResolvedOptions>>
230
- group: Group | undefined
243
+ group: Group | null
231
244
  client: Options['client']
232
245
  clientType: NonNullable<Options['clientType']>
233
246
  bundle: NonNullable<Options['bundle']>
package/src/utils.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { getOperationParameters } from '@internals/shared'
1
+ import { getOperationParameters, resolveSuccessNames } from '@internals/shared'
2
2
  import type { URLPath } from '@internals/utils'
3
3
  import type { ast } from '@kubb/core'
4
4
  import type { ResolverTs } from '@kubb/plugin-ts'
@@ -12,8 +12,8 @@ import type { PluginClient } from './types.ts'
12
12
  */
13
13
  export function buildHeaders(contentType: string, hasHeaderParams: boolean): Array<string> {
14
14
  return [
15
- contentType !== 'application/json' && contentType !== 'multipart/form-data' ? `'Content-Type': '${contentType}'` : undefined,
16
- hasHeaderParams ? '...headers' : undefined,
15
+ contentType !== 'application/json' && contentType !== 'multipart/form-data' ? `'Content-Type': '${contentType}'` : null,
16
+ hasHeaderParams ? '...headers' : null,
17
17
  ].filter(Boolean) as Array<string>
18
18
  }
19
19
 
@@ -22,8 +22,9 @@ export function buildHeaders(contentType: string, hasHeaderParams: boolean): Arr
22
22
  * Includes response type, error type, and optional request type.
23
23
  */
24
24
  export function buildGenerics(node: ast.OperationNode, tsResolver: ResolverTs): Array<string> {
25
- const responseName = tsResolver.resolveResponseName(node)
26
- const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : undefined
25
+ const successNames = resolveSuccessNames(node, tsResolver)
26
+ const responseName = successNames.length > 0 ? successNames.join(' | ') : tsResolver.resolveResponseName(node)
27
+ const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null
27
28
  const errorNames = node.responses.filter((r) => Number.parseInt(r.statusCode, 10) >= 400).map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode))
28
29
  const TError = `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(' | ') : 'Error'}>`
29
30
  return [responseName, TError, requestName || 'unknown'].filter(Boolean)
@@ -53,8 +54,8 @@ export function buildClassClientParams({
53
54
  headers: Array<string>
54
55
  }) {
55
56
  const { query: queryParams } = getOperationParameters(node)
56
- const queryParamsName = queryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, queryParams[0]!) : undefined
57
- const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : undefined
57
+ const queryParamsName = queryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, queryParams[0]!) : null
58
+ const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null
58
59
 
59
60
  return createFunctionParams({
60
61
  config: {
@@ -73,8 +74,8 @@ export function buildClassClientParams({
73
74
  ? {
74
75
  value: JSON.stringify(baseURL),
75
76
  }
76
- : undefined,
77
- params: queryParamsName ? {} : undefined,
77
+ : null,
78
+ params: queryParamsName ? {} : null,
78
79
  data: requestName
79
80
  ? {
80
81
  value:
@@ -84,13 +85,13 @@ export function buildClassClientParams({
84
85
  ? 'formData as FormData'
85
86
  : 'requestData',
86
87
  }
87
- : undefined,
88
- contentType: isMultipleContentTypes ? {} : undefined,
88
+ : null,
89
+ contentType: isMultipleContentTypes ? {} : null,
89
90
  headers: headers.length
90
91
  ? {
91
92
  value: `{ ${headers.join(', ')}, ...requestConfig.headers }`,
92
93
  }
93
- : undefined,
94
+ : null,
94
95
  },
95
96
  },
96
97
  })
@@ -107,9 +108,9 @@ export function buildRequestDataLine({
107
108
  }: {
108
109
  parser: PluginClient['resolvedOptions']['parser'] | undefined
109
110
  node: ast.OperationNode
110
- zodResolver?: ResolverZod
111
+ zodResolver?: ResolverZod | null
111
112
  }): string {
112
- const zodRequestName = zodResolver && parser === 'zod' && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : undefined
113
+ const zodRequestName = zodResolver && parser === 'zod' && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null
113
114
  if (parser === 'zod' && zodRequestName) {
114
115
  return `const requestData = ${zodRequestName}.parse(data)`
115
116
  }
@@ -140,9 +141,9 @@ export function buildReturnStatement({
140
141
  dataReturnType: PluginClient['resolvedOptions']['dataReturnType']
141
142
  parser: PluginClient['resolvedOptions']['parser'] | undefined
142
143
  node: ast.OperationNode
143
- zodResolver?: ResolverZod
144
+ zodResolver?: ResolverZod | null
144
145
  }): string {
145
- const zodResponseName = zodResolver && parser === 'zod' ? zodResolver.resolveResponseName?.(node) : undefined
146
+ const zodResponseName = zodResolver && parser === 'zod' ? zodResolver.resolveResponseName?.(node) : null
146
147
  if (dataReturnType === 'full' && parser === 'zod' && zodResponseName) {
147
148
  return `return {...res, data: ${zodResponseName}.parse(res.data)}`
148
149
  }