@kubb/plugin-vue-query 5.0.0-beta.3 → 5.0.0-beta.31

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-qfOFRSoM.cjs → components-B6lPYyOP.cjs} +367 -381
  3. package/dist/components-B6lPYyOP.cjs.map +1 -0
  4. package/dist/{components-D1UhYFgY.js → components-BZqujNQv.js} +336 -380
  5. package/dist/components-BZqujNQv.js.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-C-3isXzW.js} +158 -203
  10. package/dist/generators-C-3isXzW.js.map +1 -0
  11. package/dist/{generators-C4gs_P1i.cjs → generators-QHQkbNnm.cjs} +157 -202
  12. package/dist/generators-QHQkbNnm.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 +136 -23
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.ts +29 -1
  19. package/dist/index.js +136 -23
  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 +16 -48
  25. package/src/components/InfiniteQueryOptions.tsx +43 -58
  26. package/src/components/Mutation.tsx +33 -42
  27. package/src/components/Query.tsx +16 -49
  28. package/src/components/QueryKey.tsx +9 -61
  29. package/src/components/QueryOptions.tsx +20 -76
  30. package/src/generators/infiniteQueryGenerator.tsx +55 -63
  31. package/src/generators/mutationGenerator.tsx +50 -61
  32. package/src/generators/queryGenerator.tsx +52 -61
  33. package/src/plugin.ts +46 -30
  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 { getEnabledParamNames, markParamsOptional, 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,23 @@ 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 baseParamsNode = buildQueryKeyParamsNode(node, { pathParamsType, paramsCasing, resolver: tsResolver })
31
+ const paramsNode = markParamsOptional(baseParamsNode, getEnabledParamNames(baseParamsNode))
80
32
  const paramsSignature = declarationPrinter.print(paramsNode) ?? ''
81
- const keys = transformer({
33
+ const keys = (transformer ?? queryKeyTransformer)({
82
34
  node,
83
35
  casing: paramsCasing,
84
36
  })
@@ -98,7 +50,3 @@ export function QueryKey({ name, node, tsResolver, paramsCasing, pathParamsType,
98
50
  </>
99
51
  )
100
52
  }
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 { getEnabledParamNames, markParamsOptional } 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,25 +66,26 @@ 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}>`
130
74
  const TError = `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(' | ') : 'Error'}>`
131
75
 
132
- const paramsNode = getQueryOptionsParams(node, { paramsType, paramsCasing, pathParamsType, resolver: tsResolver })
76
+ const queryKeyParamsNode = buildQueryKeyParamsNode(node, { pathParamsType, paramsCasing, resolver: tsResolver })
77
+ const queryKeyParamsCall = callPrinter.print(queryKeyParamsNode) ?? ''
78
+
79
+ const enabledNames = getEnabledParamNames(queryKeyParamsNode)
80
+ const enabledText = enabledNames.length ? `enabled: () => ${enabledNames.map((n) => `!!toValue(${n})`).join(' && ')},` : ''
81
+
82
+ const paramsNode = markParamsOptional(getQueryOptionsParams(node, { paramsType, paramsCasing, pathParamsType, resolver: tsResolver }), enabledNames)
133
83
  const paramsSignature = declarationPrinter.print(paramsNode) ?? ''
134
84
  const rawParamsCall = callPrinter.print(paramsNode) ?? ''
135
85
 
136
86
  // Transform: wrap non-config params with toValue(), add signal to config
137
87
  const clientCallStr = rawParamsCall.replace(/\bconfig\b(?=[^,]*$)/, '{ ...config, signal: config.signal ?? signal }')
138
88
 
139
- const queryKeyParamsNode = QueryKey.getParams(node, { pathParamsType, paramsCasing, resolver: tsResolver })
140
- const queryKeyParamsCall = callPrinter.print(queryKeyParamsNode) ?? ''
141
-
142
- const enabledSource = buildEnabledCheck(queryKeyParamsNode)
143
- const enabledText = enabledSource ? `enabled: () => !!(${enabledSource}),` : ''
144
-
145
89
  return (
146
90
  <File.Source name={name} isExportable isIndexable>
147
91
  <Function name={name} export params={paramsSignature}>
@@ -151,7 +95,7 @@ export function QueryOptions({
151
95
  ${enabledText}
152
96
  queryKey,
153
97
  queryFn: async ({ signal }) => {
154
- return ${clientName}(${addToValueCalls(clientCallStr)})
98
+ return ${clientName}(${addToValueCalls(clientCallStr, enabledNames)})
155
99
  },
156
100
  })
157
101
  `}
@@ -167,8 +111,10 @@ export function QueryOptions({
167
111
  * Handles both inline params (`petId, config`) and object shorthand
168
112
  * params (`{ petId }, config`) by expanding to `{ petId: toValue(petId) }`.
169
113
  */
170
- function addToValueCalls(callStr: string): string {
114
+ function addToValueCalls(callStr: string, enabledNames: ReadonlyArray<string> = []): string {
115
+ const optional = new Set(enabledNames)
171
116
  // Step 1: Transform shorthand object params like { petId } → { petId: toValue(petId) }
117
+ // Params that drive the `enabled` guard are optional, so assert non-null: toValue(petId!)
172
118
  let result = callStr.replace(/\{\s*([\w,\s]+)\s*\}(?=\s*,)/g, (match, inner: string) => {
173
119
  // Only transform simple shorthand (no colons, no spread)
174
120
  if (inner.includes(':') || inner.includes('...')) return match
@@ -176,7 +122,7 @@ function addToValueCalls(callStr: string): string {
176
122
  .split(',')
177
123
  .map((k: string) => k.trim())
178
124
  .filter(Boolean)
179
- const wrapped = keys.map((k: string) => `${k}: toValue(${k})`).join(', ')
125
+ const wrapped = keys.map((k: string) => `${k}: toValue(${optional.has(k) ? `${k}!` : k})`).join(', ')
180
126
  return `{ ${wrapped} }`
181
127
  })
182
128
 
@@ -184,10 +130,8 @@ function addToValueCalls(callStr: string): string {
184
130
  result = result.replace(/(?<![{.:?])\b(\w+)\b(?=\s*,)/g, (match, name: string) => {
185
131
  if (name === 'config' || name === 'signal' || name === 'undefined') return match
186
132
  if (match.includes('toValue(')) return match
187
- return `toValue(${name})`
133
+ return `toValue(${optional.has(name) ? `${name}!` : name})`
188
134
  })
189
135
 
190
136
  return result
191
137
  }
192
-
193
- QueryOptions.getParams = getQueryOptionsParams
@@ -1,20 +1,28 @@
1
1
  import path from 'node:path'
2
+ import { getOperationParameters, operationFileEntry, resolveOperationTypeNames } from '@internals/shared'
3
+ import { resolveZodSchemaNames } from '@internals/tanstack-query'
2
4
  import { ast, 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
+ if (!ast.isHttpOperationNode(node)) return null
24
+ const { config, driver, resolver, root } = ctx
25
+ const { output, query, mutation, infinite, paramsCasing, paramsType, pathParamsType, parser, client: clientOptions, group } = ctx.options
18
26
 
19
27
  const pluginTs = driver.getPlugin(pluginTsName)
20
28
  if (!pluginTs) return null
@@ -25,13 +33,13 @@ export const infiniteQueryGenerator = defineGenerator<PluginVueQuery>({
25
33
  mutation !== false &&
26
34
  !isQuery &&
27
35
  difference(mutation ? mutation.methods : [], query ? query.methods : []).some((method) => node.method.toLowerCase() === method.toLowerCase())
28
- const infiniteOptions = infinite && typeof infinite === 'object' ? infinite : undefined
36
+ const infiniteOptions = infinite && typeof infinite === 'object' ? infinite : null
29
37
 
30
38
  if (!isQuery || isMutation || !infiniteOptions) return null
31
39
 
32
40
  // Validate queryParam exists in operation's query parameters
33
41
  const normalizeKey = (key: string) => key.replace(/\?$/, '')
34
- const queryParamKeys = node.parameters.filter((p) => p.in === 'query').map((p) => p.name)
42
+ const queryParamKeys = getOperationParameters(node).query.map((p) => p.name)
35
43
  const hasQueryParam = infiniteOptions.queryParam ? queryParamKeys.some((k) => normalizeKey(k) === infiniteOptions.queryParam) : false
36
44
  // cursorParam validation against response schema keys is skipped in v5 (complex schema inspection)
37
45
  const hasCursorParam = !infiniteOptions.cursorParam || true
@@ -40,64 +48,50 @@ export const infiniteQueryGenerator = defineGenerator<PluginVueQuery>({
40
48
 
41
49
  const importPath = query ? query.importPath : '@tanstack/vue-query'
42
50
 
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)
51
+ const queryName = resolver.resolveInfiniteQueryName(node)
52
+ const queryOptionsName = resolver.resolveInfiniteQueryOptionsName(node)
53
+ const queryKeyName = resolver.resolveInfiniteQueryKeyName(node)
54
+ const queryKeyTypeName = resolver.resolveInfiniteQueryKeyTypeName(node)
55
+ const clientBaseName = resolver.resolveInfiniteClientName(node)
50
56
 
51
57
  const meta = {
52
- file: resolver.resolveFile({ name: queryName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
53
- fileTs: tsResolver.resolveFile(
54
- { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
55
- { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group },
56
- ),
58
+ file: resolver.resolveFile(operationFileEntry(node, queryName), { root, output, group: group ?? undefined }),
59
+ fileTs: tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
60
+ root,
61
+ output: pluginTs.options?.output ?? output,
62
+ group: pluginTs.options?.group ?? undefined,
63
+ }),
57
64
  }
58
65
 
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
66
+ const importedTypeNames = resolveOperationTypeNames(node, tsResolver, {
67
+ paramsCasing,
68
+ exclude: [queryKeyTypeName],
69
+ order: 'body-response-first',
70
+ })
71
+
72
+ const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
73
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
75
74
  const fileZod = zodResolver
76
- ? zodResolver.resolveFile(
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 },
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
- : []
75
+ ? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
76
+ root,
77
+ output: pluginZod?.options?.output ?? output,
78
+ group: pluginZod?.options?.group ?? undefined,
79
+ })
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
- ? clientResolver?.resolveFile(
93
- { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
94
- {
95
- root,
96
- output: clientPlugin?.options?.output ?? output,
97
- group: clientPlugin?.options?.group,
98
- },
99
- )
100
- : undefined
89
+ ? clientResolver?.resolveFile(operationFileEntry(node, node.operationId), {
90
+ root,
91
+ output: clientPlugin?.options?.output ?? output,
92
+ group: clientPlugin?.options?.group ?? undefined,
93
+ })
94
+ : null
101
95
 
102
96
  const resolvedClientName = shouldUseClientPlugin ? (clientResolver?.resolveName(node.operationId) ?? clientBaseName) : clientBaseName
103
97
 
@@ -106,29 +100,27 @@ export const infiniteQueryGenerator = defineGenerator<PluginVueQuery>({
106
100
  baseName={meta.file.baseName}
107
101
  path={meta.file.path}
108
102
  meta={meta.file.meta}
109
- banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
110
- footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
103
+ banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
104
+ footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
111
105
  >
112
- {parser === 'zod' && fileZod && zodSchemaNames.length > 0 && (
113
- <File.Import name={zodSchemaNames as string[]} root={meta.file.path} path={fileZod.path} />
114
- )}
106
+ {fileZod && zodSchemaNames.length > 0 && <File.Import name={zodSchemaNames} root={meta.file.path} path={fileZod.path} />}
115
107
  {clientOptions.importPath ? (
116
108
  <>
117
- {!shouldUseClientPlugin && <File.Import name={'fetch'} path={clientOptions.importPath} />}
109
+ {!shouldUseClientPlugin && <File.Import name={'client'} path={clientOptions.importPath} />}
118
110
  <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={clientOptions.importPath} isTypeOnly />
119
111
  {clientOptions.dataReturnType === 'full' && <File.Import name={['ResponseConfig']} path={clientOptions.importPath} isTypeOnly />}
120
112
  </>
121
113
  ) : (
122
114
  <>
123
- {!shouldUseClientPlugin && <File.Import name={['fetch']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} />}
115
+ {!shouldUseClientPlugin && <File.Import name={['client']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} />}
124
116
  <File.Import
125
117
  name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
126
118
  root={meta.file.path}
127
- path={path.resolve(root, '.kubb/fetch.ts')}
119
+ path={path.resolve(root, '.kubb/client.ts')}
128
120
  isTypeOnly
129
121
  />
130
122
  {clientOptions.dataReturnType === 'full' && (
131
- <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} isTypeOnly />
123
+ <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} isTypeOnly />
132
124
  )}
133
125
  </>
134
126
  )}
@@ -1,20 +1,27 @@
1
1
  import path from 'node:path'
2
+ import { operationFileEntry, resolveOperationTypeNames } from '@internals/shared'
3
+ import { resolveZodSchemaNames } from '@internals/tanstack-query'
2
4
  import { ast, 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
+ if (!ast.isHttpOperationNode(node)) return null
23
+ const { config, driver, resolver, root } = ctx
24
+ const { output, query, mutation, paramsCasing, paramsType, pathParamsType, parser, client: clientOptions, group } = ctx.options
18
25
 
19
26
  const pluginTs = driver.getPlugin(pluginTsName)
20
27
  if (!pluginTs) return null
@@ -30,63 +37,45 @@ export const mutationGenerator = defineGenerator<PluginVueQuery>({
30
37
 
31
38
  const importPath = mutation ? mutation.importPath : '@tanstack/vue-query'
32
39
 
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)
40
+ const mutationHookName = resolver.resolveMutationName(node)
41
+ const mutationTypeName = resolver.resolveMutationTypeName(node)
42
+ const mutationKeyName = resolver.resolveMutationKeyName(node)
43
+ const clientName = resolver.resolveClientName(node)
39
44
 
40
45
  const meta = {
41
- file: resolver.resolveFile({ name: mutationHookName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
42
- fileTs: tsResolver.resolveFile(
43
- { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
44
- { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group },
45
- ),
46
+ file: resolver.resolveFile(operationFileEntry(node, mutationHookName), { root, output, group: group ?? undefined }),
47
+ fileTs: tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
48
+ root,
49
+ output: pluginTs.options?.output ?? output,
50
+ group: pluginTs.options?.group ?? undefined,
51
+ }),
46
52
  }
47
53
 
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
54
+ const importedTypeNames = resolveOperationTypeNames(node, tsResolver, { paramsCasing, order: 'body-response-first' })
55
+
56
+ const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
57
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
64
58
  const fileZod = zodResolver
65
- ? zodResolver.resolveFile(
66
- { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
67
- { root, output: pluginZod?.options?.output ?? output, group: pluginZod?.options?.group },
68
- )
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
- : []
59
+ ? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
60
+ root,
61
+ output: pluginZod?.options?.output ?? output,
62
+ group: pluginZod?.options?.group ?? undefined,
63
+ })
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
- ? clientResolver?.resolveFile(
82
- { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
83
- {
84
- root,
85
- output: clientPlugin?.options?.output ?? output,
86
- group: clientPlugin?.options?.group,
87
- },
88
- )
89
- : undefined
73
+ ? clientResolver?.resolveFile(operationFileEntry(node, node.operationId), {
74
+ root,
75
+ output: clientPlugin?.options?.output ?? output,
76
+ group: clientPlugin?.options?.group ?? undefined,
77
+ })
78
+ : null
90
79
 
91
80
  const resolvedClientName = shouldUseClientPlugin ? (clientResolver?.resolveName(node.operationId) ?? clientName) : clientName
92
81
 
@@ -95,35 +84,35 @@ export const mutationGenerator = defineGenerator<PluginVueQuery>({
95
84
  baseName={meta.file.baseName}
96
85
  path={meta.file.path}
97
86
  meta={meta.file.meta}
98
- banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
99
- footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
87
+ banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
88
+ footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
100
89
  >
101
- {parser === 'zod' && fileZod && zodSchemaNames.length > 0 && (
102
- <File.Import name={zodSchemaNames as string[]} root={meta.file.path} path={fileZod.path} />
103
- )}
90
+ {fileZod && zodSchemaNames.length > 0 && <File.Import name={zodSchemaNames} root={meta.file.path} path={fileZod.path} />}
104
91
  {clientOptions.importPath ? (
105
92
  <>
106
- {!shouldUseClientPlugin && <File.Import name={'fetch'} path={clientOptions.importPath} />}
93
+ {!shouldUseClientPlugin && <File.Import name={'client'} path={clientOptions.importPath} />}
107
94
  <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={clientOptions.importPath} isTypeOnly />
108
95
  {clientOptions.dataReturnType === 'full' && <File.Import name={['ResponseConfig']} path={clientOptions.importPath} isTypeOnly />}
109
96
  </>
110
97
  ) : (
111
98
  <>
112
- {!shouldUseClientPlugin && <File.Import name={['fetch']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} />}
99
+ {!shouldUseClientPlugin && <File.Import name={['client']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} />}
113
100
  <File.Import
114
101
  name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
115
102
  root={meta.file.path}
116
- path={path.resolve(root, '.kubb/fetch.ts')}
103
+ path={path.resolve(root, '.kubb/client.ts')}
117
104
  isTypeOnly
118
105
  />
119
106
  {clientOptions.dataReturnType === 'full' && (
120
- <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} isTypeOnly />
107
+ <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} isTypeOnly />
121
108
  )}
122
109
  </>
123
110
  )}
124
111
  <File.Import name={['MaybeRefOrGetter']} path="vue" isTypeOnly />
125
112
  {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')} />}
113
+ {!shouldUseClientPlugin && node.requestBody?.content?.some((e) => e.contentType === 'multipart/form-data') && (
114
+ <File.Import name={['buildFormData']} root={meta.file.path} path={path.resolve(root, '.kubb/config.ts')} />
115
+ )}
127
116
  {meta.fileTs && importedTypeNames.length > 0 && (
128
117
  <File.Import name={Array.from(new Set(importedTypeNames))} root={meta.file.path} path={meta.fileTs.path} isTypeOnly />
129
118
  )}