@kubb/plugin-vue-query 5.0.0-beta.3 → 5.0.0-beta.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 (41) hide show
  1. package/README.md +26 -5
  2. package/dist/{components-D1UhYFgY.js → components-B4IlVmNa.js} +244 -353
  3. package/dist/components-B4IlVmNa.js.map +1 -0
  4. package/dist/{components-qfOFRSoM.cjs → components-CIedagno.cjs} +269 -354
  5. package/dist/components-CIedagno.cjs.map +1 -0
  6. package/dist/components.cjs +1 -1
  7. package/dist/components.d.ts +3 -67
  8. package/dist/components.js +1 -1
  9. package/dist/{generators-CbnIVBgY.js → generators-ClYptnDj.js} +144 -132
  10. package/dist/generators-ClYptnDj.js.map +1 -0
  11. package/dist/{generators-C4gs_P1i.cjs → generators-D7kNtBBo.cjs} +142 -130
  12. package/dist/generators-D7kNtBBo.cjs.map +1 -0
  13. package/dist/generators.cjs +1 -1
  14. package/dist/generators.d.ts +17 -1
  15. package/dist/generators.js +1 -1
  16. package/dist/index.cjs +100 -17
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.ts +29 -1
  19. package/dist/index.js +100 -17
  20. package/dist/index.js.map +1 -1
  21. package/dist/types-D-LjzI_Q.d.ts +270 -0
  22. package/extension.yaml +1273 -0
  23. package/package.json +16 -18
  24. package/src/components/InfiniteQuery.tsx +11 -48
  25. package/src/components/InfiniteQueryOptions.tsx +38 -50
  26. package/src/components/Mutation.tsx +33 -42
  27. package/src/components/Query.tsx +11 -49
  28. package/src/components/QueryKey.tsx +8 -61
  29. package/src/components/QueryOptions.tsx +14 -67
  30. package/src/generators/infiniteQueryGenerator.tsx +46 -51
  31. package/src/generators/mutationGenerator.tsx +41 -49
  32. package/src/generators/queryGenerator.tsx +43 -49
  33. package/src/plugin.ts +43 -15
  34. package/src/resolvers/resolverVueQuery.ts +61 -4
  35. package/src/types.ts +129 -53
  36. package/src/utils.ts +44 -25
  37. package/dist/components-D1UhYFgY.js.map +0 -1
  38. package/dist/components-qfOFRSoM.cjs.map +0 -1
  39. package/dist/generators-C4gs_P1i.cjs.map +0 -1
  40. package/dist/generators-CbnIVBgY.js.map +0 -1
  41. package/dist/types-nVDTfuS1.d.ts +0 -194
@@ -1,11 +1,11 @@
1
- import { URLPath } from '@internals/utils'
2
- import { ast } from '@kubb/core'
1
+ import type { ast } from '@kubb/core'
3
2
  import type { ResolverTs } from '@kubb/plugin-ts'
4
3
  import { functionPrinter } from '@kubb/plugin-ts'
5
4
  import { File, Function, Type } from '@kubb/renderer-jsx'
6
5
  import type { KubbReactNode } from '@kubb/renderer-jsx/types'
6
+ import { queryKeyTransformer } from '@internals/tanstack-query'
7
7
  import type { Transformer } from '../types.ts'
8
- import { buildQueryKeyParams } from '../utils.ts'
8
+ import { buildQueryKeyParams, wrapWithMaybeRefOrGetter } from '../utils.ts'
9
9
 
10
10
  type Props = {
11
11
  name: string
@@ -14,71 +14,22 @@ type Props = {
14
14
  tsResolver: ResolverTs
15
15
  paramsCasing: 'camelcase' | undefined
16
16
  pathParamsType: 'object' | 'inline'
17
- transformer: Transformer | undefined
17
+ transformer: Transformer | null | undefined
18
18
  }
19
19
 
20
20
  const declarationPrinter = functionPrinter({ mode: 'declaration' })
21
- const callPrinter = functionPrinter({ mode: 'call' })
22
21
 
23
- function wrapWithMaybeRefOrGetter(paramsNode: ast.FunctionParametersNode): ast.FunctionParametersNode {
24
- const wrappedParams = paramsNode.params.map((param) => {
25
- if ('kind' in param && (param as ast.ParameterGroupNode).kind === 'ParameterGroup') {
26
- const group = param as ast.ParameterGroupNode
27
- return {
28
- ...group,
29
- properties: group.properties.map((p) => ({
30
- ...p,
31
- type: p.type ? ast.createParamsType({ variant: 'reference', name: `MaybeRefOrGetter<${printType(p.type)}>` }) : p.type,
32
- })),
33
- }
34
- }
35
- const fp = param as ast.FunctionParameterNode
36
- return {
37
- ...fp,
38
- type: fp.type ? ast.createParamsType({ variant: 'reference', name: `MaybeRefOrGetter<${printType(fp.type)}>` }) : fp.type,
39
- }
40
- })
41
- return ast.createFunctionParameters({ params: wrappedParams })
42
- }
43
-
44
- function printType(typeNode: ast.ParamsTypeNode | undefined): string {
45
- if (!typeNode) return 'unknown'
46
- if (typeNode.variant === 'reference') return typeNode.name
47
- if (typeNode.variant === 'member') return `${typeNode.base}['${typeNode.key}']`
48
- if (typeNode.variant === 'struct') {
49
- const parts = typeNode.properties.map((p) => {
50
- const typeStr = printType(p.type)
51
- const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(p.name) ? p.name : JSON.stringify(p.name)
52
- return p.optional ? `${key}?: ${typeStr}` : `${key}: ${typeStr}`
53
- })
54
- return `{ ${parts.join('; ')} }`
55
- }
56
- return 'unknown'
57
- }
58
-
59
- function getParams(
22
+ export function buildQueryKeyParamsNode(
60
23
  node: ast.OperationNode,
61
24
  options: { pathParamsType: 'object' | 'inline'; paramsCasing: 'camelcase' | undefined; resolver: ResolverTs },
62
25
  ): ast.FunctionParametersNode {
63
26
  return wrapWithMaybeRefOrGetter(buildQueryKeyParams(node, options))
64
27
  }
65
28
 
66
- const getTransformer: Transformer = ({ node, casing }) => {
67
- const path = new URLPath(node.path, { casing })
68
- const hasQueryParams = node.parameters.some((p) => p.in === 'query')
69
- const hasRequestBody = !!node.requestBody?.content?.[0]?.schema
70
-
71
- return [
72
- path.toObject({ type: 'path', stringify: true }),
73
- hasQueryParams ? '...(params ? [params] : [])' : undefined,
74
- hasRequestBody ? '...(data ? [data] : [])' : undefined,
75
- ].filter(Boolean) as string[]
76
- }
77
-
78
- export function QueryKey({ name, node, tsResolver, paramsCasing, pathParamsType, typeName, transformer = getTransformer }: Props): KubbReactNode {
79
- const paramsNode = getParams(node, { pathParamsType, paramsCasing, resolver: tsResolver })
29
+ export function QueryKey({ name, node, tsResolver, paramsCasing, pathParamsType, typeName, transformer }: Props): KubbReactNode {
30
+ const paramsNode = buildQueryKeyParamsNode(node, { pathParamsType, paramsCasing, resolver: tsResolver })
80
31
  const paramsSignature = declarationPrinter.print(paramsNode) ?? ''
81
- const keys = transformer({
32
+ const keys = (transformer ?? queryKeyTransformer)({
82
33
  node,
83
34
  casing: paramsCasing,
84
35
  })
@@ -98,7 +49,3 @@ export function QueryKey({ name, node, tsResolver, paramsCasing, pathParamsType,
98
49
  </>
99
50
  )
100
51
  }
101
-
102
- QueryKey.getParams = getParams
103
- QueryKey.getTransformer = getTransformer
104
- QueryKey.callPrinter = callPrinter
@@ -3,9 +3,10 @@ import type { ResolverTs } from '@kubb/plugin-ts'
3
3
  import { functionPrinter } from '@kubb/plugin-ts'
4
4
  import { File, Function } from '@kubb/renderer-jsx'
5
5
  import type { KubbReactNode } from '@kubb/renderer-jsx/types'
6
+ import { buildEnabledCheck } from '@internals/tanstack-query'
6
7
  import type { PluginVueQuery } from '../types.ts'
7
- import { resolveErrorNames } from '../utils.ts'
8
- import { QueryKey } from './QueryKey.tsx'
8
+ import { resolveErrorNames, resolveSuccessNames, wrapWithMaybeRefOrGetter } from '../utils.ts'
9
+ import { buildQueryKeyParamsNode } from './QueryKey.tsx'
9
10
 
10
11
  type Props = {
11
12
  name: string
@@ -32,7 +33,7 @@ export function getQueryOptionsParams(
32
33
  },
33
34
  ): ast.FunctionParametersNode {
34
35
  const { paramsType, paramsCasing, pathParamsType, resolver } = options
35
- const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : undefined
36
+ const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null
36
37
 
37
38
  const baseParams = ast.createOperationParams(node, {
38
39
  paramsType,
@@ -51,65 +52,7 @@ export function getQueryOptionsParams(
51
52
  ],
52
53
  })
53
54
 
54
- return wrapOperationParamsWithMaybeRef(baseParams)
55
- }
56
-
57
- function wrapOperationParamsWithMaybeRef(paramsNode: ast.FunctionParametersNode): ast.FunctionParametersNode {
58
- const wrappedParams = paramsNode.params.map((param) => {
59
- if ('kind' in param && (param as ast.ParameterGroupNode).kind === 'ParameterGroup') {
60
- const group = param as ast.ParameterGroupNode
61
- return {
62
- ...group,
63
- properties: group.properties.map((p) => ({
64
- ...p,
65
- type: p.type ? ast.createParamsType({ variant: 'reference', name: `MaybeRefOrGetter<${printType(p.type)}>` }) : p.type,
66
- })),
67
- }
68
- }
69
- const fp = param as ast.FunctionParameterNode
70
- // Don't wrap 'config' param — it's not reactive
71
- if (fp.name === 'config') return fp
72
- return {
73
- ...fp,
74
- type: fp.type ? ast.createParamsType({ variant: 'reference', name: `MaybeRefOrGetter<${printType(fp.type)}>` }) : fp.type,
75
- }
76
- })
77
- return ast.createFunctionParameters({ params: wrappedParams })
78
- }
79
-
80
- function printType(typeNode: ast.ParamsTypeNode | undefined): string {
81
- if (!typeNode) return 'unknown'
82
- if (typeNode.variant === 'reference') return typeNode.name
83
- if (typeNode.variant === 'member') return `${typeNode.base}['${typeNode.key}']`
84
- if (typeNode.variant === 'struct') {
85
- const parts = typeNode.properties.map((p) => {
86
- const typeStr = printType(p.type)
87
- const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(p.name) ? p.name : JSON.stringify(p.name)
88
- return p.optional ? `${key}?: ${typeStr}` : `${key}: ${typeStr}`
89
- })
90
- return `{ ${parts.join('; ')} }`
91
- }
92
- return 'unknown'
93
- }
94
-
95
- export function buildEnabledCheck(paramsNode: ast.FunctionParametersNode): string {
96
- const required: string[] = []
97
- for (const param of paramsNode.params) {
98
- if ('kind' in param && (param as ast.ParameterGroupNode).kind === 'ParameterGroup') {
99
- const group = param as ast.ParameterGroupNode
100
- for (const child of group.properties) {
101
- if (!child.optional && child.default === undefined) {
102
- required.push(child.name)
103
- }
104
- }
105
- } else {
106
- const fp = param as ast.FunctionParameterNode
107
- if (!fp.optional && fp.default === undefined) {
108
- required.push(fp.name)
109
- }
110
- }
111
- }
112
- return required.join(' && ')
55
+ return wrapWithMaybeRefOrGetter(baseParams, (name) => name === 'config')
113
56
  }
114
57
 
115
58
  export function QueryOptions({
@@ -123,7 +66,8 @@ export function QueryOptions({
123
66
  pathParamsType,
124
67
  queryKeyName,
125
68
  }: Props): KubbReactNode {
126
- const responseName = tsResolver.resolveResponseName(node)
69
+ const successNames = resolveSuccessNames(node, tsResolver)
70
+ const responseName = successNames.length > 0 ? successNames.join(' | ') : tsResolver.resolveResponseName(node)
127
71
  const errorNames = resolveErrorNames(node, tsResolver)
128
72
 
129
73
  const TData = dataReturnType === 'data' ? responseName : `ResponseConfig<${responseName}>`
@@ -136,11 +80,16 @@ export function QueryOptions({
136
80
  // Transform: wrap non-config params with toValue(), add signal to config
137
81
  const clientCallStr = rawParamsCall.replace(/\bconfig\b(?=[^,]*$)/, '{ ...config, signal: config.signal ?? signal }')
138
82
 
139
- const queryKeyParamsNode = QueryKey.getParams(node, { pathParamsType, paramsCasing, resolver: tsResolver })
83
+ const queryKeyParamsNode = buildQueryKeyParamsNode(node, { pathParamsType, paramsCasing, resolver: tsResolver })
140
84
  const queryKeyParamsCall = callPrinter.print(queryKeyParamsNode) ?? ''
141
85
 
142
86
  const enabledSource = buildEnabledCheck(queryKeyParamsNode)
143
- const enabledText = enabledSource ? `enabled: () => !!(${enabledSource}),` : ''
87
+ const enabledText = enabledSource
88
+ ? `enabled: () => ${enabledSource
89
+ .split(' && ')
90
+ .map((n) => `!!toValue(${n.trim()})`)
91
+ .join(' && ')},`
92
+ : ''
144
93
 
145
94
  return (
146
95
  <File.Source name={name} isExportable isIndexable>
@@ -189,5 +138,3 @@ function addToValueCalls(callStr: string): string {
189
138
 
190
139
  return result
191
140
  }
192
-
193
- QueryOptions.getParams = getQueryOptionsParams
@@ -1,20 +1,27 @@
1
1
  import path from 'node:path'
2
- import { ast, defineGenerator } from '@kubb/core'
2
+ import { getOperationParameters, resolveOperationTypeNames } from '@internals/shared'
3
+ import { resolveZodSchemaNames } from '@internals/tanstack-query'
4
+ import { defineGenerator } from '@kubb/core'
3
5
  import { Client, pluginClientName } from '@kubb/plugin-client'
4
6
  import { pluginTsName } from '@kubb/plugin-ts'
5
7
  import { pluginZodName } from '@kubb/plugin-zod'
6
- import { File, jsxRenderer } from '@kubb/renderer-jsx'
8
+ import { File, jsxRendererSync } from '@kubb/renderer-jsx'
7
9
  import { difference } from 'remeda'
8
10
  import { InfiniteQuery, InfiniteQueryOptions, QueryKey } from '../components'
9
11
  import type { PluginVueQuery } from '../types'
10
- import { transformName } from '../utils.ts'
11
12
 
13
+ /**
14
+ * Built-in generator for `useInfiniteQuery` composables. Enabled when
15
+ * `pluginVueQuery({ infinite: { ... } })`. Emits one `useFooInfiniteQuery`
16
+ * composable per query operation, wiring the configured cursor path into
17
+ * TanStack Query's cursor-based pagination.
18
+ */
12
19
  export const infiniteQueryGenerator = defineGenerator<PluginVueQuery>({
13
20
  name: 'vue-query-infinite',
14
- renderer: jsxRenderer,
21
+ renderer: jsxRendererSync,
15
22
  operation(node, ctx) {
16
- const { adapter, config, driver, resolver, root } = ctx
17
- const { output, query, mutation, infinite, paramsCasing, paramsType, pathParamsType, parser, client: clientOptions, group, transformers } = ctx.options
23
+ const { config, driver, resolver, root } = ctx
24
+ const { output, query, mutation, infinite, paramsCasing, paramsType, pathParamsType, parser, client: clientOptions, group } = ctx.options
18
25
 
19
26
  const pluginTs = driver.getPlugin(pluginTsName)
20
27
  if (!pluginTs) return null
@@ -25,13 +32,13 @@ export const infiniteQueryGenerator = defineGenerator<PluginVueQuery>({
25
32
  mutation !== false &&
26
33
  !isQuery &&
27
34
  difference(mutation ? mutation.methods : [], query ? query.methods : []).some((method) => node.method.toLowerCase() === method.toLowerCase())
28
- const infiniteOptions = infinite && typeof infinite === 'object' ? infinite : undefined
35
+ const infiniteOptions = infinite && typeof infinite === 'object' ? infinite : null
29
36
 
30
37
  if (!isQuery || isMutation || !infiniteOptions) return null
31
38
 
32
39
  // Validate queryParam exists in operation's query parameters
33
40
  const normalizeKey = (key: string) => key.replace(/\?$/, '')
34
- const queryParamKeys = node.parameters.filter((p) => p.in === 'query').map((p) => p.name)
41
+ const queryParamKeys = getOperationParameters(node).query.map((p) => p.name)
35
42
  const hasQueryParam = infiniteOptions.queryParam ? queryParamKeys.some((k) => normalizeKey(k) === infiniteOptions.queryParam) : false
36
43
  // cursorParam validation against response schema keys is skipped in v5 (complex schema inspection)
37
44
  const hasCursorParam = !infiniteOptions.cursorParam || true
@@ -40,53 +47,43 @@ export const infiniteQueryGenerator = defineGenerator<PluginVueQuery>({
40
47
 
41
48
  const importPath = query ? query.importPath : '@tanstack/vue-query'
42
49
 
43
- const baseName = resolver.resolveName(node.operationId)
44
- const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)
45
- const queryName = transformName(`use${capitalize(baseName)}Infinite`, 'function', transformers)
46
- const queryOptionsName = transformName(`${baseName}InfiniteQueryOptions`, 'function', transformers)
47
- const queryKeyName = transformName(`${baseName}InfiniteQueryKey`, 'const', transformers)
48
- const queryKeyTypeName = transformName(`${capitalize(baseName)}InfiniteQueryKey`, 'type', transformers)
49
- const clientBaseName = transformName(`${baseName}Infinite`, 'function', transformers)
50
+ const queryName = resolver.resolveInfiniteQueryName(node)
51
+ const queryOptionsName = resolver.resolveInfiniteQueryOptionsName(node)
52
+ const queryKeyName = resolver.resolveInfiniteQueryKeyName(node)
53
+ const queryKeyTypeName = resolver.resolveInfiniteQueryKeyTypeName(node)
54
+ const clientBaseName = resolver.resolveInfiniteClientName(node)
50
55
 
51
56
  const meta = {
52
- file: resolver.resolveFile({ name: queryName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
57
+ file: resolver.resolveFile(
58
+ { name: queryName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
59
+ { root, output, group: group ?? undefined },
60
+ ),
53
61
  fileTs: tsResolver.resolveFile(
54
62
  { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
55
- { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group },
63
+ { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group ?? undefined },
56
64
  ),
57
65
  }
58
66
 
59
- const casedParams = ast.caseParams(node.parameters, paramsCasing)
60
- const pathParams = casedParams.filter((p) => p.in === 'path')
61
- const queryParams = casedParams.filter((p) => p.in === 'query')
62
- const headerParams = casedParams.filter((p) => p.in === 'header')
63
-
64
- const importedTypeNames = [
65
- node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : undefined,
66
- tsResolver.resolveResponseName(node),
67
- ...pathParams.map((p) => tsResolver.resolvePathParamsName(node, p)),
68
- ...queryParams.map((p) => tsResolver.resolveQueryParamsName(node, p)),
69
- ...headerParams.map((p) => tsResolver.resolveHeaderParamsName(node, p)),
70
- ...node.responses.map((res) => tsResolver.resolveResponseStatusName(node, res.statusCode)),
71
- ].filter((name): name is string => !!name && name !== queryKeyTypeName)
72
-
73
- const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : undefined
74
- const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : undefined
67
+ const importedTypeNames = resolveOperationTypeNames(node, tsResolver, {
68
+ paramsCasing,
69
+ exclude: [queryKeyTypeName],
70
+ order: 'body-response-first',
71
+ })
72
+
73
+ const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
74
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
75
75
  const fileZod = zodResolver
76
76
  ? zodResolver.resolveFile(
77
77
  { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
78
- { root, output: pluginZod?.options?.output ?? output, group: pluginZod?.options?.group },
78
+ { root, output: pluginZod?.options?.output ?? output, group: pluginZod?.options?.group ?? undefined },
79
79
  )
80
- : undefined
81
- const zodSchemaNames =
82
- zodResolver && parser === 'zod'
83
- ? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : undefined].filter(Boolean)
84
- : []
80
+ : null
81
+ const zodSchemaNames = resolveZodSchemaNames(node, zodResolver)
85
82
 
86
83
  const clientPlugin = driver.getPlugin(pluginClientName)
87
84
  const hasClientPlugin = clientPlugin?.name === pluginClientName
88
85
  const shouldUseClientPlugin = hasClientPlugin && clientOptions.clientType !== 'class'
89
- const clientResolver = shouldUseClientPlugin ? driver.getResolver(pluginClientName) : undefined
86
+ const clientResolver = shouldUseClientPlugin ? driver.getResolver(pluginClientName) : null
90
87
 
91
88
  const clientFile = shouldUseClientPlugin
92
89
  ? clientResolver?.resolveFile(
@@ -94,10 +91,10 @@ export const infiniteQueryGenerator = defineGenerator<PluginVueQuery>({
94
91
  {
95
92
  root,
96
93
  output: clientPlugin?.options?.output ?? output,
97
- group: clientPlugin?.options?.group,
94
+ group: clientPlugin?.options?.group ?? undefined,
98
95
  },
99
96
  )
100
- : undefined
97
+ : null
101
98
 
102
99
  const resolvedClientName = shouldUseClientPlugin ? (clientResolver?.resolveName(node.operationId) ?? clientBaseName) : clientBaseName
103
100
 
@@ -106,29 +103,27 @@ export const infiniteQueryGenerator = defineGenerator<PluginVueQuery>({
106
103
  baseName={meta.file.baseName}
107
104
  path={meta.file.path}
108
105
  meta={meta.file.meta}
109
- banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
110
- footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
106
+ banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
107
+ footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
111
108
  >
112
- {parser === 'zod' && fileZod && zodSchemaNames.length > 0 && (
113
- <File.Import name={zodSchemaNames as string[]} root={meta.file.path} path={fileZod.path} />
114
- )}
109
+ {fileZod && zodSchemaNames.length > 0 && <File.Import name={zodSchemaNames} root={meta.file.path} path={fileZod.path} />}
115
110
  {clientOptions.importPath ? (
116
111
  <>
117
- {!shouldUseClientPlugin && <File.Import name={'fetch'} path={clientOptions.importPath} />}
112
+ {!shouldUseClientPlugin && <File.Import name={'client'} path={clientOptions.importPath} />}
118
113
  <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={clientOptions.importPath} isTypeOnly />
119
114
  {clientOptions.dataReturnType === 'full' && <File.Import name={['ResponseConfig']} path={clientOptions.importPath} isTypeOnly />}
120
115
  </>
121
116
  ) : (
122
117
  <>
123
- {!shouldUseClientPlugin && <File.Import name={['fetch']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} />}
118
+ {!shouldUseClientPlugin && <File.Import name={['client']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} />}
124
119
  <File.Import
125
120
  name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
126
121
  root={meta.file.path}
127
- path={path.resolve(root, '.kubb/fetch.ts')}
122
+ path={path.resolve(root, '.kubb/client.ts')}
128
123
  isTypeOnly
129
124
  />
130
125
  {clientOptions.dataReturnType === 'full' && (
131
- <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} isTypeOnly />
126
+ <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} isTypeOnly />
132
127
  )}
133
128
  </>
134
129
  )}
@@ -1,20 +1,26 @@
1
1
  import path from 'node:path'
2
- import { ast, defineGenerator } from '@kubb/core'
2
+ import { resolveOperationTypeNames } from '@internals/shared'
3
+ import { resolveZodSchemaNames } from '@internals/tanstack-query'
4
+ import { defineGenerator } from '@kubb/core'
3
5
  import { Client, pluginClientName } from '@kubb/plugin-client'
4
6
  import { pluginTsName } from '@kubb/plugin-ts'
5
7
  import { pluginZodName } from '@kubb/plugin-zod'
6
- import { File, jsxRenderer } from '@kubb/renderer-jsx'
8
+ import { File, jsxRendererSync } from '@kubb/renderer-jsx'
7
9
  import { difference } from 'remeda'
8
10
  import { Mutation, MutationKey } from '../components'
9
11
  import type { PluginVueQuery } from '../types'
10
- import { transformName } from '../utils.ts'
11
12
 
13
+ /**
14
+ * Built-in generator for `useMutation` composables. Emits one
15
+ * `useFooMutation` composable per POST/PUT/DELETE operation (configurable
16
+ * via `mutation.methods`) plus the matching `fooMutationKey` helper.
17
+ */
12
18
  export const mutationGenerator = defineGenerator<PluginVueQuery>({
13
19
  name: 'vue-query-mutation',
14
- renderer: jsxRenderer,
20
+ renderer: jsxRendererSync,
15
21
  operation(node, ctx) {
16
- const { adapter, config, driver, resolver, root } = ctx
17
- const { output, query, mutation, paramsCasing, paramsType, pathParamsType, parser, client: clientOptions, group, transformers } = ctx.options
22
+ const { config, driver, resolver, root } = ctx
23
+ const { output, query, mutation, paramsCasing, paramsType, pathParamsType, parser, client: clientOptions, group } = ctx.options
18
24
 
19
25
  const pluginTs = driver.getPlugin(pluginTsName)
20
26
  if (!pluginTs) return null
@@ -30,52 +36,38 @@ export const mutationGenerator = defineGenerator<PluginVueQuery>({
30
36
 
31
37
  const importPath = mutation ? mutation.importPath : '@tanstack/vue-query'
32
38
 
33
- const baseName = resolver.resolveName(node.operationId)
34
- const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)
35
- const mutationHookName = transformName(`use${capitalize(baseName)}`, 'function', transformers)
36
- const mutationTypeName = transformName(`${capitalize(baseName)}`, 'type', transformers)
37
- const mutationKeyName = transformName(`${baseName}MutationKey`, 'const', transformers)
38
- const clientName = transformName(baseName, 'function', transformers)
39
+ const mutationHookName = resolver.resolveMutationName(node)
40
+ const mutationTypeName = resolver.resolveMutationTypeName(node)
41
+ const mutationKeyName = resolver.resolveMutationKeyName(node)
42
+ const clientName = resolver.resolveClientName(node)
39
43
 
40
44
  const meta = {
41
- file: resolver.resolveFile({ name: mutationHookName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
45
+ file: resolver.resolveFile(
46
+ { name: mutationHookName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
47
+ { root, output, group: group ?? undefined },
48
+ ),
42
49
  fileTs: tsResolver.resolveFile(
43
50
  { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
44
- { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group },
51
+ { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group ?? undefined },
45
52
  ),
46
53
  }
47
54
 
48
- const casedParams = ast.caseParams(node.parameters, paramsCasing)
49
- const pathParams = casedParams.filter((p) => p.in === 'path')
50
- const queryParams = casedParams.filter((p) => p.in === 'query')
51
- const headerParams = casedParams.filter((p) => p.in === 'header')
52
-
53
- const importedTypeNames = [
54
- node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : undefined,
55
- tsResolver.resolveResponseName(node),
56
- ...pathParams.map((p) => tsResolver.resolvePathParamsName(node, p)),
57
- ...queryParams.map((p) => tsResolver.resolveQueryParamsName(node, p)),
58
- ...headerParams.map((p) => tsResolver.resolveHeaderParamsName(node, p)),
59
- ...node.responses.map((res) => tsResolver.resolveResponseStatusName(node, res.statusCode)),
60
- ].filter((name): name is string => !!name)
61
-
62
- const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : undefined
63
- const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : undefined
55
+ const importedTypeNames = resolveOperationTypeNames(node, tsResolver, { paramsCasing, order: 'body-response-first' })
56
+
57
+ const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
58
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
64
59
  const fileZod = zodResolver
65
60
  ? zodResolver.resolveFile(
66
61
  { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
67
- { root, output: pluginZod?.options?.output ?? output, group: pluginZod?.options?.group },
62
+ { root, output: pluginZod?.options?.output ?? output, group: pluginZod?.options?.group ?? undefined },
68
63
  )
69
- : undefined
70
- const zodSchemaNames =
71
- zodResolver && parser === 'zod'
72
- ? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : undefined].filter(Boolean)
73
- : []
64
+ : null
65
+ const zodSchemaNames = resolveZodSchemaNames(node, zodResolver)
74
66
 
75
67
  const clientPlugin = driver.getPlugin(pluginClientName)
76
68
  const hasClientPlugin = clientPlugin?.name === pluginClientName
77
69
  const shouldUseClientPlugin = hasClientPlugin && clientOptions.clientType !== 'class'
78
- const clientResolver = shouldUseClientPlugin ? driver.getResolver(pluginClientName) : undefined
70
+ const clientResolver = shouldUseClientPlugin ? driver.getResolver(pluginClientName) : null
79
71
 
80
72
  const clientFile = shouldUseClientPlugin
81
73
  ? clientResolver?.resolveFile(
@@ -83,10 +75,10 @@ export const mutationGenerator = defineGenerator<PluginVueQuery>({
83
75
  {
84
76
  root,
85
77
  output: clientPlugin?.options?.output ?? output,
86
- group: clientPlugin?.options?.group,
78
+ group: clientPlugin?.options?.group ?? undefined,
87
79
  },
88
80
  )
89
- : undefined
81
+ : null
90
82
 
91
83
  const resolvedClientName = shouldUseClientPlugin ? (clientResolver?.resolveName(node.operationId) ?? clientName) : clientName
92
84
 
@@ -95,35 +87,35 @@ export const mutationGenerator = defineGenerator<PluginVueQuery>({
95
87
  baseName={meta.file.baseName}
96
88
  path={meta.file.path}
97
89
  meta={meta.file.meta}
98
- banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
99
- footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
90
+ banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
91
+ footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
100
92
  >
101
- {parser === 'zod' && fileZod && zodSchemaNames.length > 0 && (
102
- <File.Import name={zodSchemaNames as string[]} root={meta.file.path} path={fileZod.path} />
103
- )}
93
+ {fileZod && zodSchemaNames.length > 0 && <File.Import name={zodSchemaNames} root={meta.file.path} path={fileZod.path} />}
104
94
  {clientOptions.importPath ? (
105
95
  <>
106
- {!shouldUseClientPlugin && <File.Import name={'fetch'} path={clientOptions.importPath} />}
96
+ {!shouldUseClientPlugin && <File.Import name={'client'} path={clientOptions.importPath} />}
107
97
  <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={clientOptions.importPath} isTypeOnly />
108
98
  {clientOptions.dataReturnType === 'full' && <File.Import name={['ResponseConfig']} path={clientOptions.importPath} isTypeOnly />}
109
99
  </>
110
100
  ) : (
111
101
  <>
112
- {!shouldUseClientPlugin && <File.Import name={['fetch']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} />}
102
+ {!shouldUseClientPlugin && <File.Import name={['client']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} />}
113
103
  <File.Import
114
104
  name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
115
105
  root={meta.file.path}
116
- path={path.resolve(root, '.kubb/fetch.ts')}
106
+ path={path.resolve(root, '.kubb/client.ts')}
117
107
  isTypeOnly
118
108
  />
119
109
  {clientOptions.dataReturnType === 'full' && (
120
- <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} isTypeOnly />
110
+ <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} isTypeOnly />
121
111
  )}
122
112
  </>
123
113
  )}
124
114
  <File.Import name={['MaybeRefOrGetter']} path="vue" isTypeOnly />
125
115
  {shouldUseClientPlugin && clientFile && <File.Import name={[resolvedClientName]} root={meta.file.path} path={clientFile.path} />}
126
- {!shouldUseClientPlugin && <File.Import name={['buildFormData']} root={meta.file.path} path={path.resolve(root, '.kubb/config.ts')} />}
116
+ {!shouldUseClientPlugin && node.requestBody?.content?.some((e) => e.contentType === 'multipart/form-data') && (
117
+ <File.Import name={['buildFormData']} root={meta.file.path} path={path.resolve(root, '.kubb/config.ts')} />
118
+ )}
127
119
  {meta.fileTs && importedTypeNames.length > 0 && (
128
120
  <File.Import name={Array.from(new Set(importedTypeNames))} root={meta.file.path} path={meta.fileTs.path} isTypeOnly />
129
121
  )}