@kubb/plugin-client 5.0.0-alpha.3 → 5.0.0-alpha.30

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 (42) hide show
  1. package/dist/clients/axios.d.ts +2 -2
  2. package/dist/index.cjs +1893 -74
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.ts +480 -4
  5. package/dist/index.js +1885 -77
  6. package/dist/index.js.map +1 -1
  7. package/package.json +10 -25
  8. package/src/components/ClassClient.tsx +42 -138
  9. package/src/components/Client.tsx +85 -124
  10. package/src/components/ClientLegacy.tsx +501 -0
  11. package/src/components/Operations.tsx +8 -8
  12. package/src/components/StaticClassClient.tsx +41 -135
  13. package/src/components/Url.tsx +37 -46
  14. package/src/generators/classClientGenerator.tsx +129 -152
  15. package/src/generators/clientGenerator.tsx +93 -82
  16. package/src/generators/groupedClientGenerator.tsx +48 -51
  17. package/src/generators/operationsGenerator.tsx +9 -17
  18. package/src/generators/staticClassClientGenerator.tsx +163 -168
  19. package/src/index.ts +11 -1
  20. package/src/plugin.ts +115 -108
  21. package/src/presets.ts +25 -0
  22. package/src/resolvers/resolverClient.ts +26 -0
  23. package/src/resolvers/resolverClientLegacy.ts +26 -0
  24. package/src/types.ts +105 -40
  25. package/src/utils.ts +148 -0
  26. package/dist/StaticClassClient-By-aMAe4.cjs +0 -677
  27. package/dist/StaticClassClient-By-aMAe4.cjs.map +0 -1
  28. package/dist/StaticClassClient-CCn9g9eF.js +0 -636
  29. package/dist/StaticClassClient-CCn9g9eF.js.map +0 -1
  30. package/dist/components.cjs +0 -7
  31. package/dist/components.d.ts +0 -216
  32. package/dist/components.js +0 -2
  33. package/dist/generators-C2jT7XCH.js +0 -723
  34. package/dist/generators-C2jT7XCH.js.map +0 -1
  35. package/dist/generators-qkDW17Hf.cjs +0 -753
  36. package/dist/generators-qkDW17Hf.cjs.map +0 -1
  37. package/dist/generators.cjs +0 -7
  38. package/dist/generators.d.ts +0 -512
  39. package/dist/generators.js +0 -2
  40. package/dist/types-CdM4DK1M.d.ts +0 -169
  41. package/src/components/index.ts +0 -5
  42. package/src/generators/index.ts +0 -5
package/src/plugin.ts CHANGED
@@ -1,130 +1,173 @@
1
1
  import path from 'node:path'
2
2
  import { camelCase } from '@internals/utils'
3
- import { definePlugin, type Group, getBarrelFiles, getMode } from '@kubb/core'
4
- import { OperationGenerator, pluginOasName } from '@kubb/plugin-oas'
3
+ import { createPlugin, type Group, getPreset, mergeGenerators } from '@kubb/core'
4
+ import { pluginTsName } from '@kubb/plugin-ts'
5
5
  import { pluginZodName } from '@kubb/plugin-zod'
6
- import { classClientGenerator, operationsGenerator } from './generators'
6
+ import { version } from '../package.json'
7
+ import { classClientGenerator } from './generators/classClientGenerator.tsx'
7
8
  import { clientGenerator } from './generators/clientGenerator.tsx'
8
9
  import { groupedClientGenerator } from './generators/groupedClientGenerator.tsx'
10
+ import { operationsGenerator } from './generators/operationsGenerator.tsx'
9
11
  import { staticClassClientGenerator } from './generators/staticClassClientGenerator.tsx'
12
+ import { presets } from './presets.ts'
10
13
  import { source as axiosClientSource } from './templates/clients/axios.source.ts'
11
14
  import { source as fetchClientSource } from './templates/clients/fetch.source.ts'
12
15
  import { source as configSource } from './templates/config.source.ts'
13
16
  import type { PluginClient } from './types.ts'
14
17
 
18
+ /**
19
+ * Canonical plugin name for `@kubb/plugin-client`, used to identify the plugin
20
+ * in driver lookups and warnings.
21
+ */
15
22
  export const pluginClientName = 'plugin-client' satisfies PluginClient['name']
16
23
 
17
- export const pluginClient = definePlugin<PluginClient>((options) => {
24
+ /**
25
+ * The `@kubb/plugin-client` plugin factory.
26
+ *
27
+ * Generates type-safe HTTP client functions (or classes) from an OpenAPI/AST `RootNode`.
28
+ * Walks operations, delegates rendering to the active generators,
29
+ * and writes barrel files based on `output.barrelType`.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * import { pluginClient } from '@kubb/plugin-client'
34
+ *
35
+ * export default defineConfig({
36
+ * plugins: [pluginClient({ output: { path: 'clients' } })],
37
+ * })
38
+ * ```
39
+ */
40
+ export const pluginClient = createPlugin<PluginClient>((options) => {
18
41
  const {
19
42
  output = { path: 'clients', barrelType: 'named' },
20
43
  group,
21
- urlType = false,
22
44
  exclude = [],
23
45
  include,
24
46
  override = [],
25
- transformers = {},
47
+ urlType = false,
26
48
  dataReturnType = 'data',
27
49
  paramsType = 'inline',
28
50
  pathParamsType = paramsType === 'object' ? 'object' : options.pathParamsType || 'inline',
29
51
  operations = false,
30
- baseURL,
31
52
  paramsCasing,
32
53
  clientType = 'function',
33
54
  parser = 'client',
34
55
  client = 'axios',
35
56
  importPath,
36
- contentType,
37
57
  bundle = false,
38
58
  wrapper,
59
+ baseURL,
60
+ compatibilityPreset = 'default',
61
+ resolver: userResolver,
62
+ transformer: userTransformer,
39
63
  } = options
40
64
 
41
65
  const resolvedImportPath = importPath ?? (!bundle ? `@kubb/plugin-client/clients/${client}` : undefined)
42
66
 
43
- const defaultGenerators = [
44
- clientType === 'staticClass' ? staticClassClientGenerator : clientType === 'class' ? classClientGenerator : clientGenerator,
45
- group && clientType === 'function' ? groupedClientGenerator : undefined,
46
- operations ? operationsGenerator : undefined,
47
- ].filter((x): x is NonNullable<typeof x> => Boolean(x))
67
+ const selectedGenerators =
68
+ options.generators ??
69
+ [
70
+ clientType === 'staticClass' ? staticClassClientGenerator : clientType === 'class' ? classClientGenerator : clientGenerator,
71
+ group && clientType === 'function' ? groupedClientGenerator : undefined,
72
+ operations ? operationsGenerator : undefined,
73
+ ].filter((x): x is NonNullable<typeof x> => Boolean(x))
48
74
 
49
- const generators = options.generators ?? defaultGenerators
75
+ const preset = getPreset({
76
+ preset: compatibilityPreset,
77
+ presets,
78
+ resolver: userResolver,
79
+ transformer: userTransformer,
80
+ generators: selectedGenerators,
81
+ })
82
+
83
+ const generators = preset.generators ?? []
84
+ const mergedGenerator = mergeGenerators(generators)
85
+
86
+ let resolveNameWarning = false
87
+ let resolvePathWarning = false
50
88
 
51
89
  return {
52
90
  name: pluginClientName,
53
- options: {
54
- client,
55
- clientType,
56
- bundle,
57
- output,
58
- group,
59
- parser,
60
- dataReturnType,
61
- importPath: resolvedImportPath,
62
- paramsType,
63
- paramsCasing,
64
- pathParamsType,
65
- baseURL,
66
- urlType,
67
- wrapper,
91
+ version,
92
+ get resolver() {
93
+ return preset.resolver
68
94
  },
69
- pre: [pluginOasName, parser === 'zod' ? pluginZodName : undefined].filter(Boolean),
70
- resolvePath(baseName, pathMode, options) {
71
- const root = path.resolve(this.config.root, this.config.output.path)
72
- const mode = pathMode ?? getMode(path.resolve(root, output.path))
73
-
74
- if (mode === 'single') {
75
- /**
76
- * when output is a file then we will always append to the same file(output file), see fileManager.addOrAppend
77
- * Other plugins then need to call addOrAppend instead of just add from the fileManager class
78
- */
79
- return path.resolve(root, output.path)
95
+ get transformer() {
96
+ return preset.transformer
97
+ },
98
+ get options() {
99
+ return {
100
+ client,
101
+ clientType,
102
+ bundle,
103
+ output,
104
+ exclude,
105
+ include,
106
+ override,
107
+ group: group
108
+ ? ({
109
+ ...group,
110
+ name: group.name
111
+ ? group.name
112
+ : (ctx: { group: string }) => {
113
+ if (group.type === 'path') {
114
+ return `${ctx.group.split('/')[1]}`
115
+ }
116
+ return `${camelCase(ctx.group)}Controller`
117
+ },
118
+ } satisfies Group)
119
+ : undefined,
120
+ parser,
121
+ dataReturnType,
122
+ importPath: resolvedImportPath,
123
+ baseURL,
124
+ paramsType,
125
+ paramsCasing,
126
+ pathParamsType,
127
+ urlType,
128
+ wrapper,
129
+ resolver: preset.resolver,
80
130
  }
81
-
82
- if (group && (options?.group?.path || options?.group?.tag)) {
83
- const groupName: Group['name'] = group?.name
84
- ? group.name
85
- : (ctx) => {
86
- if (group?.type === 'path') {
87
- return `${ctx.group.split('/')[1]}`
88
- }
89
- return `${camelCase(ctx.group)}Controller`
90
- }
91
-
92
- return path.resolve(
93
- root,
94
- output.path,
95
- groupName({
96
- group: group.type === 'path' ? options.group.path! : options.group.tag!,
97
- }),
98
- baseName,
99
- )
131
+ },
132
+ pre: [pluginTsName, parser === 'zod' ? pluginZodName : undefined].filter(Boolean),
133
+ resolvePath(baseName, pathMode, options) {
134
+ if (!resolvePathWarning) {
135
+ this.warn('Do not use resolvePath for pluginClient, use resolverClient.resolvePath instead')
136
+ resolvePathWarning = true
100
137
  }
101
138
 
102
- return path.resolve(root, output.path, baseName)
139
+ return this.plugin.resolver.resolvePath(
140
+ { baseName, pathMode, tag: options?.group?.tag, path: options?.group?.path },
141
+ { root: this.root, output, group: this.plugin.options.group },
142
+ )
103
143
  },
104
144
  resolveName(name, type) {
105
- const resolvedName = camelCase(name, { isFile: type === 'file' })
106
-
107
- if (type) {
108
- return transformers?.name?.(resolvedName, type) || resolvedName
145
+ if (!resolveNameWarning) {
146
+ this.warn('Do not use resolveName for pluginClient, use resolverClient.default instead')
147
+ resolveNameWarning = true
109
148
  }
110
149
 
111
- return resolvedName
150
+ return this.plugin.resolver.default(name, type)
151
+ },
152
+ async operation(node, options) {
153
+ return mergedGenerator.operation?.call(this, node, options)
112
154
  },
113
- async install() {
114
- const root = path.resolve(this.config.root, this.config.output.path)
115
- const mode = getMode(path.resolve(root, output.path))
116
- const oas = await this.getOas()
117
- const baseURL = await this.getBaseURL()
155
+ async operations(nodes, options) {
156
+ return mergedGenerator.operations?.call(this, nodes, options)
157
+ },
158
+ async buildStart() {
159
+ const { plugin } = this
160
+ const root = this.root
118
161
 
119
162
  // pre add bundled fetch
120
- if (bundle && !this.plugin.options.importPath) {
163
+ if (bundle && !plugin.options.importPath) {
121
164
  await this.addFile({
122
165
  baseName: 'fetch.ts',
123
166
  path: path.resolve(root, '.kubb/fetch.ts'),
124
167
  sources: [
125
168
  {
126
169
  name: 'fetch',
127
- value: this.plugin.options.client === 'fetch' ? fetchClientSource : axiosClientSource,
170
+ value: plugin.options.client === 'fetch' ? fetchClientSource : axiosClientSource,
128
171
  isExportable: true,
129
172
  isIndexable: true,
130
173
  },
@@ -148,42 +191,6 @@ export const pluginClient = definePlugin<PluginClient>((options) => {
148
191
  imports: [],
149
192
  exports: [],
150
193
  })
151
-
152
- const operationGenerator = new OperationGenerator(
153
- baseURL
154
- ? {
155
- ...this.plugin.options,
156
- baseURL,
157
- }
158
- : this.plugin.options,
159
- {
160
- fabric: this.fabric,
161
- oas,
162
- pluginManager: this.pluginManager,
163
- events: this.events,
164
- plugin: this.plugin,
165
- contentType,
166
- exclude,
167
- include,
168
- override,
169
- mode,
170
- },
171
- )
172
-
173
- const files = await operationGenerator.build(...generators)
174
-
175
- await this.upsertFile(...files)
176
-
177
- const barrelFiles = await getBarrelFiles(this.fabric.files, {
178
- type: output.barrelType ?? 'named',
179
- root,
180
- output,
181
- meta: {
182
- pluginName: this.plugin.name,
183
- },
184
- })
185
-
186
- await this.upsertFile(...barrelFiles)
187
194
  },
188
195
  }
189
196
  })
package/src/presets.ts ADDED
@@ -0,0 +1,25 @@
1
+ import { definePresets } from '@kubb/core'
2
+ import { resolverClient } from './resolvers/resolverClient.ts'
3
+ import { resolverClientLegacy } from './resolvers/resolverClientLegacy.ts'
4
+ import type { ResolverClient } from './types.ts'
5
+
6
+ /**
7
+ * Built-in preset registry for `@kubb/plugin-client`.
8
+ *
9
+ * - `default` — uses `resolverClient` with v5 naming conventions.
10
+ * - `kubbV4` — uses `resolverClientLegacy` with backward-compatible naming.
11
+ *
12
+ * Note: Unlike plugin-ts/plugin-zod, generators are not defined here because
13
+ * plugin-client selects generators dynamically based on `clientType`, `group`,
14
+ * and `operations` options. Generator selection happens in `plugin.ts`.
15
+ */
16
+ export const presets = definePresets<ResolverClient>({
17
+ default: {
18
+ name: 'default',
19
+ resolver: resolverClient,
20
+ },
21
+ kubbV4: {
22
+ name: 'kubbV4',
23
+ resolver: resolverClientLegacy,
24
+ },
25
+ })
@@ -0,0 +1,26 @@
1
+ import { camelCase } from '@internals/utils'
2
+ import { defineResolver } from '@kubb/core'
3
+ import type { PluginClient } from '../types.ts'
4
+
5
+ /**
6
+ * Resolver for `@kubb/plugin-client` that provides the default naming
7
+ * and path-resolution helpers used by the plugin.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { resolverClient } from '@kubb/plugin-client'
12
+ *
13
+ * resolverClient.default('list pets', 'function') // -> 'listPets'
14
+ * resolverClient.resolveName('show pet by id') // -> 'showPetById'
15
+ * ```
16
+ */
17
+ export const resolverClient = defineResolver<PluginClient>(() => ({
18
+ name: 'default',
19
+ pluginName: 'plugin-client',
20
+ default(name, type) {
21
+ return camelCase(name, { isFile: type === 'file' })
22
+ },
23
+ resolveName(name) {
24
+ return this.default(name, 'function')
25
+ },
26
+ }))
@@ -0,0 +1,26 @@
1
+ import { camelCase } from '@internals/utils'
2
+ import { defineResolver } from '@kubb/core'
3
+ import type { PluginClient } from '../types.ts'
4
+
5
+ /**
6
+ * Legacy resolver for `@kubb/plugin-client` that provides backward-compatible
7
+ * naming conventions matching the v4 behavior.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { resolverClientLegacy } from '@kubb/plugin-client'
12
+ *
13
+ * resolverClientLegacy.default('list pets', 'function') // -> 'listPets'
14
+ * resolverClientLegacy.resolveName('show pet by id') // -> 'showPetById'
15
+ * ```
16
+ */
17
+ export const resolverClientLegacy = defineResolver<PluginClient>(() => ({
18
+ name: 'kubbV4',
19
+ pluginName: 'plugin-client',
20
+ default(name, type) {
21
+ return camelCase(name, { isFile: type === 'file' })
22
+ },
23
+ resolveName(name) {
24
+ return this.default(name, 'function')
25
+ },
26
+ }))
package/src/types.ts CHANGED
@@ -1,8 +1,30 @@
1
- import type { Group, Output, PluginFactoryOptions, ResolveNameParams } from '@kubb/core'
1
+ import type { Visitor } from '@kubb/ast/types'
2
+ import type {
3
+ CompatibilityPreset,
4
+ Exclude,
5
+ Generator,
6
+ Group,
7
+ Include,
8
+ Output,
9
+ Override,
10
+ PluginFactoryOptions,
11
+ ResolvePathOptions,
12
+ Resolver,
13
+ UserGroup,
14
+ } from '@kubb/core'
2
15
 
3
- import type { contentType, Oas } from '@kubb/oas'
4
- import type { Exclude, Include, Override, ResolvePathOptions } from '@kubb/plugin-oas'
5
- import type { Generator } from '@kubb/plugin-oas/generators'
16
+ /**
17
+ * The concrete resolver type for `@kubb/plugin-client`.
18
+ * Extends the base `Resolver` with a `resolveName` helper for client function names.
19
+ */
20
+ export type ResolverClient = Resolver & {
21
+ /**
22
+ * Resolves the function name for a given raw operation name.
23
+ * @example
24
+ * resolver.resolveName('show pet by id') // -> 'showPetById'
25
+ */
26
+ resolveName(this: ResolverClient, name: string): string
27
+ }
6
28
 
7
29
  /**
8
30
  * Use either a preset `client` type OR a custom `importPath`, not both.
@@ -36,21 +58,56 @@ export type ClientImportPath =
36
58
  bundle?: never
37
59
  }
38
60
 
61
+ /**
62
+ * Discriminated union that ties `pathParamsType` to the `paramsType` values where it is meaningful.
63
+ *
64
+ * - `paramsType: 'object'` — all parameters (including path params) are merged into a single
65
+ * destructured object. `pathParamsType` is never reached in this code path and has no effect.
66
+ * - `paramsType?: 'inline'` (or omitted) — each parameter group is a separate function argument.
67
+ * `pathParamsType` controls whether the path-param group itself is destructured (`'object'`)
68
+ * or spread as individual arguments (`'inline'`).
69
+ */
70
+ type ParamsTypeOptions =
71
+ | {
72
+ /**
73
+ * All parameters — path, query, headers, and body — are merged into a single
74
+ * destructured object argument.
75
+ * - 'object' returns the params and pathParams as an object.
76
+ * @default 'inline'
77
+ */
78
+ paramsType: 'object'
79
+ /**
80
+ * `pathParamsType` has no effect when `paramsType` is `'object'`.
81
+ * Path params are already inside the single destructured object.
82
+ */
83
+ pathParamsType?: never
84
+ }
85
+ | {
86
+ /**
87
+ * Each parameter group is emitted as a separate function argument.
88
+ * - 'inline' returns the params as comma separated params.
89
+ * @default 'inline'
90
+ */
91
+ paramsType?: 'inline'
92
+ /**
93
+ * Controls how path parameters are arranged within the inline argument list.
94
+ * - 'object' groups path params into a destructured object: `{ petId }: PathParams`.
95
+ * - 'inline' emits each path param as its own argument: `petId: string`.
96
+ * @default 'inline'
97
+ */
98
+ pathParamsType?: 'object' | 'inline'
99
+ }
100
+
39
101
  export type Options = {
40
102
  /**
41
- * Specify the export location for the files and define the behavior of the output
103
+ * Specify the export location for the files and define the behavior of the output.
42
104
  * @default { path: 'clients', barrelType: 'named' }
43
105
  */
44
- output?: Output<Oas>
45
- /**
46
- * Define which contentType should be used.
47
- * By default, the first JSON valid mediaType is used
48
- */
49
- contentType?: contentType
106
+ output?: Output
50
107
  /**
51
108
  * Group the clients based on the provided name.
52
109
  */
53
- group?: Group
110
+ group?: UserGroup
54
111
  /**
55
112
  * Array containing exclude parameters to exclude/skip tags/operations/methods/paths.
56
113
  */
@@ -88,25 +145,11 @@ export type Options = {
88
145
  */
89
146
  dataReturnType?: 'data' | 'full'
90
147
  /**
91
- * How to style your params, by default no casing is applied
148
+ * How to style your params, by default no casing is applied.
92
149
  * - 'camelcase' uses camelCase for pathParams, queryParams and headerParams names
93
150
  * @note response types (data/body) are not affected by this option
94
151
  */
95
152
  paramsCasing?: 'camelcase'
96
- /**
97
- * How to pass your params.
98
- * - 'object' returns the params and pathParams as an object.
99
- * - 'inline' returns the params as comma separated params.
100
- * @default 'inline'
101
- */
102
- paramsType?: 'object' | 'inline'
103
- /**
104
- * How to pass your pathParams.
105
- * - 'object' returns the pathParams as an object.
106
- * - 'inline' returns the pathParams as comma separated params.
107
- * @default 'inline'
108
- */
109
- pathParamsType?: 'object' | 'inline'
110
153
  /**
111
154
  * Which parser can be used before returning the data.
112
155
  * - 'client' returns the data as-is from the client.
@@ -138,33 +181,55 @@ export type Options = {
138
181
  */
139
182
  className: string
140
183
  }
141
- transformers?: {
142
- /**
143
- * Customize the names based on the type that is provided by the plugin.
144
- */
145
- name?: (name: ResolveNameParams['name'], type?: ResolveNameParams['type']) => string
146
- }
147
184
  /**
148
- * Define some generators next to the client generators
185
+ * Apply a compatibility naming preset.
186
+ * @default 'default'
187
+ */
188
+ compatibilityPreset?: CompatibilityPreset
189
+ /**
190
+ * Override individual resolver methods. Any method you omit falls back to the
191
+ * preset resolver's implementation. Use `this.default(...)` to call it.
192
+ */
193
+ resolver?: Partial<ResolverClient> & ThisType<ResolverClient>
194
+ /**
195
+ * Single AST visitor applied to each node before printing.
196
+ * Return `null` or `undefined` from a method to leave the node unchanged.
197
+ */
198
+ transformer?: Visitor
199
+ /**
200
+ * Define some generators next to the client generators.
149
201
  */
150
202
  generators?: Array<Generator<PluginClient>>
151
- } & ClientImportPath
203
+ } & ClientImportPath &
204
+ ParamsTypeOptions
152
205
 
153
206
  type ResolvedOptions = {
154
- output: Output<Oas>
155
- group?: Options['group']
156
- baseURL: string | undefined
207
+ output: Output
208
+ exclude: Array<Exclude>
209
+ include: Array<Include> | undefined
210
+ override: Array<Override<ResolvedOptions>>
211
+ group: Group | undefined
157
212
  client: Options['client']
158
213
  clientType: NonNullable<Options['clientType']>
159
214
  bundle: NonNullable<Options['bundle']>
160
215
  parser: NonNullable<Options['parser']>
161
216
  urlType: NonNullable<Options['urlType']>
162
217
  importPath: Options['importPath']
218
+ baseURL: Options['baseURL']
163
219
  dataReturnType: NonNullable<Options['dataReturnType']>
164
- pathParamsType: NonNullable<Options['pathParamsType']>
220
+ pathParamsType: NonNullable<NonNullable<Options['pathParamsType']>>
165
221
  paramsType: NonNullable<Options['paramsType']>
166
222
  paramsCasing: Options['paramsCasing']
167
223
  wrapper: Options['wrapper']
224
+ resolver: ResolverClient
168
225
  }
169
226
 
170
- export type PluginClient = PluginFactoryOptions<'plugin-client', Options, ResolvedOptions, never, ResolvePathOptions>
227
+ export type PluginClient = PluginFactoryOptions<'plugin-client', Options, ResolvedOptions, never, ResolvePathOptions, ResolverClient>
228
+
229
+ declare global {
230
+ namespace Kubb {
231
+ interface PluginRegistry {
232
+ 'plugin-client': PluginClient
233
+ }
234
+ }
235
+ }