@kubb/plugin-react-query 5.0.0-beta.4 → 5.0.0-beta.42

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 (49) hide show
  1. package/README.md +38 -91
  2. package/dist/{components-dAKJEn9b.cjs → components-DQAYLQW0.cjs} +409 -279
  3. package/dist/components-DQAYLQW0.cjs.map +1 -0
  4. package/dist/{components-DTGLu4UV.js → components-IArDg-DO.js} +379 -279
  5. package/dist/components-IArDg-DO.js.map +1 -0
  6. package/dist/components.cjs +1 -1
  7. package/dist/components.d.ts +5 -77
  8. package/dist/components.js +1 -1
  9. package/dist/{generators-C_fbcjpG.js → generators-B86BJkmW.js} +346 -419
  10. package/dist/generators-B86BJkmW.js.map +1 -0
  11. package/dist/{generators-CWEQsdO9.cjs → generators-BqGaMUH6.cjs} +344 -417
  12. package/dist/generators-BqGaMUH6.cjs.map +1 -0
  13. package/dist/generators.cjs +1 -1
  14. package/dist/generators.d.ts +49 -10
  15. package/dist/generators.js +1 -1
  16. package/dist/index.cjs +176 -26
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.ts +32 -4
  19. package/dist/index.js +177 -27
  20. package/dist/index.js.map +1 -1
  21. package/dist/types-Dh4HNR9K.d.ts +400 -0
  22. package/extension.yaml +910 -364
  23. package/package.json +15 -17
  24. package/src/components/InfiniteQuery.tsx +24 -13
  25. package/src/components/InfiniteQueryOptions.tsx +37 -55
  26. package/src/components/Mutation.tsx +35 -15
  27. package/src/components/MutationOptions.tsx +14 -13
  28. package/src/components/Query.tsx +14 -10
  29. package/src/components/QueryOptions.tsx +17 -34
  30. package/src/components/SuspenseInfiniteQuery.tsx +19 -13
  31. package/src/components/SuspenseInfiniteQueryOptions.tsx +28 -52
  32. package/src/components/SuspenseQuery.tsx +9 -10
  33. package/src/generators/customHookOptionsFileGenerator.tsx +18 -14
  34. package/src/generators/hookOptionsGenerator.tsx +44 -51
  35. package/src/generators/infiniteQueryGenerator.tsx +57 -78
  36. package/src/generators/mutationGenerator.tsx +53 -64
  37. package/src/generators/queryGenerator.tsx +54 -63
  38. package/src/generators/suspenseInfiniteQueryGenerator.tsx +52 -65
  39. package/src/generators/suspenseQueryGenerator.tsx +56 -76
  40. package/src/plugin.ts +45 -31
  41. package/src/resolvers/resolverReactQuery.ts +102 -6
  42. package/src/types.ts +199 -61
  43. package/src/utils.ts +10 -33
  44. package/dist/components-DTGLu4UV.js.map +0 -1
  45. package/dist/components-dAKJEn9b.cjs.map +0 -1
  46. package/dist/generators-CWEQsdO9.cjs.map +0 -1
  47. package/dist/generators-C_fbcjpG.js.map +0 -1
  48. package/dist/types-DfaFRSBf.d.ts +0 -284
  49. /package/dist/{chunk--u3MIqq1.js → chunk-C0LytTxp.js} +0 -0
@@ -1,50 +1,45 @@
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'
7
- import { difference } from 'remeda'
8
+ import { File, jsxRendererSync } from '@kubb/renderer-jsx'
8
9
  import { InfiniteQuery, InfiniteQueryOptions, QueryKey } from '../components'
9
10
  import type { PluginReactQuery } from '../types'
10
- import { transformName } from '../utils.ts'
11
11
 
12
+ /**
13
+ * Built-in generator for `useInfiniteQuery` hooks. Enabled when
14
+ * `pluginReactQuery({ infinite: { ... } })`. Emits one `useFooInfiniteQuery`
15
+ * hook per query operation, wiring the configured `nextParam` /
16
+ * `previousParam` paths into TanStack Query's cursor-based pagination.
17
+ */
12
18
  export const infiniteQueryGenerator = defineGenerator<PluginReactQuery>({
13
19
  name: 'react-infinite-query',
14
- renderer: jsxRenderer,
20
+ renderer: jsxRendererSync,
15
21
  operation(node, ctx) {
16
- const { adapter, config, driver, resolver, root } = ctx
17
- const {
18
- output,
19
- query,
20
- mutation,
21
- infinite,
22
- paramsCasing,
23
- paramsType,
24
- pathParamsType,
25
- parser,
26
- client: clientOptions,
27
- group,
28
- transformers,
29
- customOptions,
30
- } = ctx.options
22
+ if (!ast.isHttpOperationNode(node)) return null
23
+ const { config, driver, resolver, root } = ctx
24
+ const { output, query, mutation, infinite, paramsCasing, paramsType, pathParamsType, parser, client: clientOptions, group, customOptions } = ctx.options
31
25
 
32
26
  const pluginTs = driver.getPlugin(pluginTsName)
33
27
  if (!pluginTs) return null
34
28
  const tsResolver = driver.getResolver(pluginTsName)
35
29
 
36
30
  const isQuery = query === false || (!!query && query.methods.some((method) => node.method.toLowerCase() === method.toLowerCase()))
31
+ const queryMethods = new Set(query ? query.methods : [])
37
32
  const isMutation =
38
33
  mutation !== false &&
39
34
  !isQuery &&
40
- difference(mutation ? mutation.methods : [], query ? query.methods : []).some((method) => node.method.toLowerCase() === method.toLowerCase())
41
- const infiniteOptions = infinite && typeof infinite === 'object' ? infinite : undefined
35
+ (mutation ? mutation.methods : []).some((method) => !queryMethods.has(method) && node.method.toLowerCase() === method.toLowerCase())
36
+ const infiniteOptions = infinite && typeof infinite === 'object' ? infinite : null
42
37
 
43
38
  if (!isQuery || isMutation || !infiniteOptions) return null
44
39
 
45
40
  // Validate queryParam exists in operation's query parameters
46
41
  const normalizeKey = (key: string) => key.replace(/\?$/, '')
47
- const queryParamKeys = node.parameters.filter((p) => p.in === 'query').map((p) => p.name)
42
+ const queryParamKeys = getOperationParameters(node).query.map((p) => p.name)
48
43
  const hasQueryParam = infiniteOptions.queryParam ? queryParamKeys.some((k) => normalizeKey(k) === infiniteOptions.queryParam) : false
49
44
  // cursorParam validation against response schema keys is skipped in v5 (complex schema inspection)
50
45
  const hasCursorParam = !infiniteOptions.cursorParam || true
@@ -53,64 +48,50 @@ export const infiniteQueryGenerator = defineGenerator<PluginReactQuery>({
53
48
 
54
49
  const importPath = query ? query.importPath : '@tanstack/react-query'
55
50
 
56
- const baseName = resolver.resolveName(node.operationId)
57
- const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)
58
- const queryName = transformName(`use${capitalize(baseName)}Infinite`, 'function', transformers)
59
- const queryOptionsName = transformName(`${baseName}InfiniteQueryOptions`, 'function', transformers)
60
- const queryKeyName = transformName(`${baseName}InfiniteQueryKey`, 'const', transformers)
61
- const queryKeyTypeName = transformName(`${capitalize(baseName)}InfiniteQueryKey`, 'type', transformers)
62
- 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)
63
56
 
64
57
  const meta = {
65
- file: resolver.resolveFile({ name: queryName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
66
- fileTs: tsResolver.resolveFile(
67
- { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
68
- { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group },
69
- ),
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
+ }),
70
64
  }
71
65
 
72
- const casedParams = ast.caseParams(node.parameters, paramsCasing)
73
- const pathParams = casedParams.filter((p) => p.in === 'path')
74
- const queryParams = casedParams.filter((p) => p.in === 'query')
75
- const headerParams = casedParams.filter((p) => p.in === 'header')
76
-
77
- const importedTypeNames = [
78
- node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : undefined,
79
- tsResolver.resolveResponseName(node),
80
- ...pathParams.map((p) => tsResolver.resolvePathParamsName(node, p)),
81
- ...queryParams.map((p) => tsResolver.resolveQueryParamsName(node, p)),
82
- ...headerParams.map((p) => tsResolver.resolveHeaderParamsName(node, p)),
83
- ...node.responses.map((res) => tsResolver.resolveResponseStatusName(node, res.statusCode)),
84
- ].filter((name): name is string => !!name && name !== queryKeyTypeName)
85
-
86
- const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : undefined
87
- 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
88
74
  const fileZod = zodResolver
89
- ? zodResolver.resolveFile(
90
- { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
91
- { root, output: pluginZod?.options?.output ?? output, group: pluginZod?.options?.group },
92
- )
93
- : undefined
94
- const zodSchemaNames =
95
- zodResolver && parser === 'zod'
96
- ? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : undefined].filter(Boolean)
97
- : []
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)
98
82
 
99
83
  const clientPlugin = driver.getPlugin(pluginClientName)
100
84
  const hasClientPlugin = clientPlugin?.name === pluginClientName
101
85
  const shouldUseClientPlugin = hasClientPlugin && clientOptions.clientType !== 'class'
102
- const clientResolver = shouldUseClientPlugin ? driver.getResolver(pluginClientName) : undefined
86
+ const clientResolver = shouldUseClientPlugin ? driver.getResolver(pluginClientName) : null
103
87
 
104
88
  const clientFile = shouldUseClientPlugin
105
- ? clientResolver?.resolveFile(
106
- { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
107
- {
108
- root,
109
- output: clientPlugin?.options?.output ?? output,
110
- group: clientPlugin?.options?.group,
111
- },
112
- )
113
- : 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
114
95
 
115
96
  const resolvedClientName = shouldUseClientPlugin ? (clientResolver?.resolveName(node.operationId) ?? clientBaseName) : clientBaseName
116
97
 
@@ -119,29 +100,27 @@ export const infiniteQueryGenerator = defineGenerator<PluginReactQuery>({
119
100
  baseName={meta.file.baseName}
120
101
  path={meta.file.path}
121
102
  meta={meta.file.meta}
122
- banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
123
- 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 } })}
124
105
  >
125
- {parser === 'zod' && fileZod && zodSchemaNames.length > 0 && (
126
- <File.Import name={zodSchemaNames as string[]} root={meta.file.path} path={fileZod.path} />
127
- )}
106
+ {fileZod && zodSchemaNames.length > 0 && <File.Import name={zodSchemaNames} root={meta.file.path} path={fileZod.path} />}
128
107
  {clientOptions.importPath ? (
129
108
  <>
130
- {!shouldUseClientPlugin && <File.Import name={'fetch'} path={clientOptions.importPath} />}
109
+ {!shouldUseClientPlugin && <File.Import name={'client'} path={clientOptions.importPath} />}
131
110
  <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={clientOptions.importPath} isTypeOnly />
132
111
  {clientOptions.dataReturnType === 'full' && <File.Import name={['ResponseConfig']} path={clientOptions.importPath} isTypeOnly />}
133
112
  </>
134
113
  ) : (
135
114
  <>
136
- {!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')} />}
137
116
  <File.Import
138
117
  name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
139
118
  root={meta.file.path}
140
- path={path.resolve(root, '.kubb/fetch.ts')}
119
+ path={path.resolve(root, '.kubb/client.ts')}
141
120
  isTypeOnly
142
121
  />
143
122
  {clientOptions.dataReturnType === 'full' && (
144
- <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 />
145
124
  )}
146
125
  </>
147
126
  )}
@@ -1,93 +1,82 @@
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'
7
- import { difference } from 'remeda'
8
+ import { File, jsxRendererSync } from '@kubb/renderer-jsx'
8
9
  import { Mutation, MutationKey, MutationOptions } from '../components'
9
10
  import type { PluginReactQuery } from '../types'
10
- import { transformName } from '../utils.ts'
11
11
 
12
+ /**
13
+ * Built-in generator for `useMutation` hooks. Emits one `useFooMutation` hook
14
+ * per POST/PUT/DELETE operation (configurable via `mutation.methods`) plus
15
+ * the matching `fooMutationKey` / `fooMutationOptions` helpers.
16
+ */
12
17
  export const mutationGenerator = defineGenerator<PluginReactQuery>({
13
18
  name: 'react-query-mutation',
14
- renderer: jsxRenderer,
19
+ renderer: jsxRendererSync,
15
20
  operation(node, ctx) {
16
- const { adapter, config, driver, resolver, root } = ctx
17
- const { output, query, mutation, paramsCasing, paramsType, pathParamsType, parser, client: clientOptions, group, transformers, customOptions } = ctx.options
21
+ if (!ast.isHttpOperationNode(node)) return null
22
+ const { config, driver, resolver, root } = ctx
23
+ const { output, query, mutation, paramsCasing, paramsType, pathParamsType, parser, client: clientOptions, group, customOptions } = ctx.options
18
24
 
19
25
  const pluginTs = driver.getPlugin(pluginTsName)
20
26
  if (!pluginTs) return null
21
27
  const tsResolver = driver.getResolver(pluginTsName)
22
28
 
23
29
  const isQuery = query === false || (!!query && query.methods.some((method) => node.method.toLowerCase() === method.toLowerCase()))
30
+ const queryMethods = new Set(query ? query.methods : [])
24
31
  const isMutation =
25
32
  mutation !== false &&
26
33
  !isQuery &&
27
- difference(mutation ? mutation.methods : [], query ? query.methods : []).some((method) => node.method.toLowerCase() === method.toLowerCase())
34
+ (mutation ? mutation.methods : []).some((method) => !queryMethods.has(method) && node.method.toLowerCase() === method.toLowerCase())
28
35
 
29
36
  if (!isMutation) return null
30
37
 
31
38
  const importPath = mutation ? mutation.importPath : '@tanstack/react-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 mutationOptionsName = transformName(`${baseName}MutationOptions`, 'function', transformers)
38
- const mutationKeyName = transformName(`${baseName}MutationKey`, 'const', transformers)
39
- const clientName = transformName(baseName, 'function', transformers)
40
+ const mutationHookName = resolver.resolveMutationName(node)
41
+ const mutationTypeName = resolver.resolveMutationTypeName(node)
42
+ const mutationOptionsName = resolver.resolveMutationOptionsName(node)
43
+ const mutationKeyName = resolver.resolveMutationKeyName(node)
44
+ const clientName = resolver.resolveClientName(node)
40
45
 
41
46
  const meta = {
42
- file: resolver.resolveFile({ name: mutationHookName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
43
- fileTs: tsResolver.resolveFile(
44
- { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
45
- { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group },
46
- ),
47
+ file: resolver.resolveFile(operationFileEntry(node, mutationHookName), { root, output, group: group ?? undefined }),
48
+ fileTs: tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
49
+ root,
50
+ output: pluginTs.options?.output ?? output,
51
+ group: pluginTs.options?.group ?? undefined,
52
+ }),
47
53
  }
48
54
 
49
- const casedParams = ast.caseParams(node.parameters, paramsCasing)
50
- const pathParams = casedParams.filter((p) => p.in === 'path')
51
- const queryParams = casedParams.filter((p) => p.in === 'query')
52
- const headerParams = casedParams.filter((p) => p.in === 'header')
53
-
54
- const importedTypeNames = [
55
- node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : undefined,
56
- tsResolver.resolveResponseName(node),
57
- ...pathParams.map((p) => tsResolver.resolvePathParamsName(node, p)),
58
- ...queryParams.map((p) => tsResolver.resolveQueryParamsName(node, p)),
59
- ...headerParams.map((p) => tsResolver.resolveHeaderParamsName(node, p)),
60
- ...node.responses.map((res) => tsResolver.resolveResponseStatusName(node, res.statusCode)),
61
- ].filter((name): name is string => !!name)
62
-
63
- const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : undefined
64
- 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
65
59
  const fileZod = zodResolver
66
- ? zodResolver.resolveFile(
67
- { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
68
- { root, output: pluginZod?.options?.output ?? output, group: pluginZod?.options?.group },
69
- )
70
- : undefined
71
- const zodSchemaNames =
72
- zodResolver && parser === 'zod'
73
- ? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : undefined].filter(Boolean)
74
- : []
60
+ ? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
61
+ root,
62
+ output: pluginZod?.options?.output ?? output,
63
+ group: pluginZod?.options?.group ?? undefined,
64
+ })
65
+ : null
66
+ const zodSchemaNames = resolveZodSchemaNames(node, zodResolver)
75
67
 
76
68
  const clientPlugin = driver.getPlugin(pluginClientName)
77
69
  const hasClientPlugin = clientPlugin?.name === pluginClientName
78
70
  const shouldUseClientPlugin = hasClientPlugin && clientOptions.clientType !== 'class'
79
- const clientResolver = shouldUseClientPlugin ? driver.getResolver(pluginClientName) : undefined
71
+ const clientResolver = shouldUseClientPlugin ? driver.getResolver(pluginClientName) : null
80
72
 
81
73
  const clientFile = shouldUseClientPlugin
82
- ? clientResolver?.resolveFile(
83
- { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
84
- {
85
- root,
86
- output: clientPlugin?.options?.output ?? output,
87
- group: clientPlugin?.options?.group,
88
- },
89
- )
90
- : undefined
74
+ ? clientResolver?.resolveFile(operationFileEntry(node, node.operationId), {
75
+ root,
76
+ output: clientPlugin?.options?.output ?? output,
77
+ group: clientPlugin?.options?.group ?? undefined,
78
+ })
79
+ : null
91
80
 
92
81
  const resolvedClientName = shouldUseClientPlugin ? (clientResolver?.resolveName(node.operationId) ?? clientName) : clientName
93
82
 
@@ -96,34 +85,34 @@ export const mutationGenerator = defineGenerator<PluginReactQuery>({
96
85
  baseName={meta.file.baseName}
97
86
  path={meta.file.path}
98
87
  meta={meta.file.meta}
99
- banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
100
- footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
88
+ banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
89
+ footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
101
90
  >
102
- {parser === 'zod' && fileZod && zodSchemaNames.length > 0 && (
103
- <File.Import name={zodSchemaNames as string[]} root={meta.file.path} path={fileZod.path} />
104
- )}
91
+ {fileZod && zodSchemaNames.length > 0 && <File.Import name={zodSchemaNames} root={meta.file.path} path={fileZod.path} />}
105
92
  {clientOptions.importPath ? (
106
93
  <>
107
- {!shouldUseClientPlugin && <File.Import name={'fetch'} path={clientOptions.importPath} />}
94
+ {!shouldUseClientPlugin && <File.Import name={'client'} path={clientOptions.importPath} />}
108
95
  <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={clientOptions.importPath} isTypeOnly />
109
96
  {clientOptions.dataReturnType === 'full' && <File.Import name={['ResponseConfig']} path={clientOptions.importPath} isTypeOnly />}
110
97
  </>
111
98
  ) : (
112
99
  <>
113
- {!shouldUseClientPlugin && <File.Import name={['fetch']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} />}
100
+ {!shouldUseClientPlugin && <File.Import name={['client']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} />}
114
101
  <File.Import
115
102
  name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
116
103
  root={meta.file.path}
117
- path={path.resolve(root, '.kubb/fetch.ts')}
104
+ path={path.resolve(root, '.kubb/client.ts')}
118
105
  isTypeOnly
119
106
  />
120
107
  {clientOptions.dataReturnType === 'full' && (
121
- <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} isTypeOnly />
108
+ <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} isTypeOnly />
122
109
  )}
123
110
  </>
124
111
  )}
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
  {customOptions && <File.Import name={[customOptions.name]} path={customOptions.importPath} />}
128
117
  {meta.fileTs && importedTypeNames.length > 0 && (
129
118
  <File.Import name={Array.from(new Set(importedTypeNames))} root={meta.file.path} path={meta.fileTs.path} isTypeOnly />
@@ -1,20 +1,26 @@
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'
7
- import { difference } from 'remeda'
8
+ import { File, jsxRendererSync } from '@kubb/renderer-jsx'
8
9
  import { Query, QueryKey, QueryOptions } from '../components'
9
10
  import type { PluginReactQuery } from '../types'
10
- import { transformName } from '../utils.ts'
11
11
 
12
+ /**
13
+ * Built-in generator for `useQuery` hooks. Emits one `useFooQuery` hook per
14
+ * GET operation (configurable via `query.methods`) plus the matching
15
+ * `fooQueryKey` / `fooQueryOptions` helpers.
16
+ */
12
17
  export const queryGenerator = defineGenerator<PluginReactQuery>({
13
18
  name: 'react-query',
14
- renderer: jsxRenderer,
19
+ renderer: jsxRendererSync,
15
20
  operation(node, ctx) {
16
- const { adapter, config, driver, resolver, root } = ctx
17
- const { output, query, mutation, paramsCasing, paramsType, pathParamsType, parser, client: clientOptions, group, transformers, customOptions } = ctx.options
21
+ if (!ast.isHttpOperationNode(node)) return null
22
+ const { config, driver, resolver, root } = ctx
23
+ const { output, query, mutation, paramsCasing, paramsType, pathParamsType, parser, client: clientOptions, group, customOptions } = ctx.options
18
24
 
19
25
  const pluginTs = driver.getPlugin(pluginTsName)
20
26
  if (!pluginTs) return null
@@ -22,73 +28,60 @@ export const queryGenerator = defineGenerator<PluginReactQuery>({
22
28
 
23
29
  // query: false means "this IS a query op, but skip the useQuery hook"
24
30
  const isQuery = query === false || (!!query && query.methods.some((method) => node.method.toLowerCase() === method.toLowerCase()))
31
+ const queryMethods = new Set(query ? query.methods : [])
25
32
  const isMutation =
26
33
  mutation !== false &&
27
34
  !isQuery &&
28
- difference(mutation ? mutation.methods : [], query ? query.methods : []).some((method) => node.method.toLowerCase() === method.toLowerCase())
35
+ (mutation ? mutation.methods : []).some((method) => !queryMethods.has(method) && node.method.toLowerCase() === method.toLowerCase())
29
36
 
30
37
  if (!isQuery || isMutation) return null
31
38
 
32
39
  const importPath = query ? query.importPath : '@tanstack/react-query'
33
40
 
34
- const baseName = resolver.resolveName(node.operationId)
35
- const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)
36
- const queryName = transformName(`use${capitalize(baseName)}`, 'function', transformers)
37
- const queryOptionsName = transformName(`${baseName}QueryOptions`, 'function', transformers)
38
- const queryKeyName = transformName(`${baseName}QueryKey`, 'const', transformers)
39
- const queryKeyTypeName = transformName(`${capitalize(baseName)}QueryKey`, 'type', transformers)
40
- const clientName = transformName(baseName, 'function', transformers)
41
+ const queryName = resolver.resolveQueryName(node)
42
+ const queryOptionsName = resolver.resolveQueryOptionsName(node)
43
+ const queryKeyName = resolver.resolveQueryKeyName(node)
44
+ const queryKeyTypeName = resolver.resolveQueryKeyTypeName(node)
45
+ const clientName = resolver.resolveClientName(node)
41
46
 
42
47
  const meta = {
43
- file: resolver.resolveFile({ name: queryName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
44
- fileTs: tsResolver.resolveFile(
45
- { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
46
- { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group },
47
- ),
48
+ file: resolver.resolveFile(operationFileEntry(node, queryName), { root, output, group: group ?? undefined }),
49
+ fileTs: tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
50
+ root,
51
+ output: pluginTs.options?.output ?? output,
52
+ group: pluginTs.options?.group ?? undefined,
53
+ }),
48
54
  }
49
55
 
50
- const casedParams = ast.caseParams(node.parameters, paramsCasing)
51
- const pathParams = casedParams.filter((p) => p.in === 'path')
52
- const queryParams = casedParams.filter((p) => p.in === 'query')
53
- const headerParams = casedParams.filter((p) => p.in === 'header')
54
-
55
- const importedTypeNames = [
56
- node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : undefined,
57
- tsResolver.resolveResponseName(node),
58
- ...pathParams.map((p) => tsResolver.resolvePathParamsName(node, p)),
59
- ...queryParams.map((p) => tsResolver.resolveQueryParamsName(node, p)),
60
- ...headerParams.map((p) => tsResolver.resolveHeaderParamsName(node, p)),
61
- ...node.responses.map((res) => tsResolver.resolveResponseStatusName(node, res.statusCode)),
62
- ].filter((name): name is string => !!name && name !== queryKeyTypeName)
63
-
64
- const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : undefined
65
- const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : undefined
56
+ const importedTypeNames = resolveOperationTypeNames(node, tsResolver, {
57
+ paramsCasing,
58
+ exclude: [queryKeyTypeName],
59
+ order: 'body-response-first',
60
+ })
61
+
62
+ const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
63
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
66
64
  const fileZod = zodResolver
67
- ? zodResolver.resolveFile(
68
- { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
69
- { root, output: pluginZod?.options?.output ?? output, group: pluginZod?.options?.group },
70
- )
71
- : undefined
72
- const zodSchemaNames =
73
- zodResolver && parser === 'zod'
74
- ? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : undefined].filter(Boolean)
75
- : []
65
+ ? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
66
+ root,
67
+ output: pluginZod?.options?.output ?? output,
68
+ group: pluginZod?.options?.group ?? undefined,
69
+ })
70
+ : null
71
+ const zodSchemaNames = resolveZodSchemaNames(node, zodResolver)
76
72
 
77
73
  const clientPlugin = driver.getPlugin(pluginClientName)
78
74
  const hasClientPlugin = clientPlugin?.name === pluginClientName
79
75
  const shouldUseClientPlugin = hasClientPlugin && clientOptions.clientType !== 'class'
80
- const clientResolver = shouldUseClientPlugin ? driver.getResolver(pluginClientName) : undefined
76
+ const clientResolver = shouldUseClientPlugin ? driver.getResolver(pluginClientName) : null
81
77
 
82
78
  const clientFile = shouldUseClientPlugin
83
- ? clientResolver?.resolveFile(
84
- { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
85
- {
86
- root,
87
- output: clientPlugin?.options?.output ?? output,
88
- group: clientPlugin?.options?.group,
89
- },
90
- )
91
- : undefined
79
+ ? clientResolver?.resolveFile(operationFileEntry(node, node.operationId), {
80
+ root,
81
+ output: clientPlugin?.options?.output ?? output,
82
+ group: clientPlugin?.options?.group ?? undefined,
83
+ })
84
+ : null
92
85
 
93
86
  const resolvedClientName = shouldUseClientPlugin ? (clientResolver?.resolveName(node.operationId) ?? clientName) : clientName
94
87
 
@@ -97,29 +90,27 @@ export const queryGenerator = defineGenerator<PluginReactQuery>({
97
90
  baseName={meta.file.baseName}
98
91
  path={meta.file.path}
99
92
  meta={meta.file.meta}
100
- banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
101
- footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
93
+ banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
94
+ footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
102
95
  >
103
- {parser === 'zod' && fileZod && zodSchemaNames.length > 0 && (
104
- <File.Import name={zodSchemaNames as string[]} root={meta.file.path} path={fileZod.path} />
105
- )}
96
+ {fileZod && zodSchemaNames.length > 0 && <File.Import name={zodSchemaNames} root={meta.file.path} path={fileZod.path} />}
106
97
  {clientOptions.importPath ? (
107
98
  <>
108
- {!shouldUseClientPlugin && <File.Import name={'fetch'} path={clientOptions.importPath} />}
99
+ {!shouldUseClientPlugin && <File.Import name={'client'} path={clientOptions.importPath} />}
109
100
  <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={clientOptions.importPath} isTypeOnly />
110
101
  {clientOptions.dataReturnType === 'full' && <File.Import name={['ResponseConfig']} path={clientOptions.importPath} isTypeOnly />}
111
102
  </>
112
103
  ) : (
113
104
  <>
114
- {!shouldUseClientPlugin && <File.Import name={['fetch']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} />}
105
+ {!shouldUseClientPlugin && <File.Import name={['client']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} />}
115
106
  <File.Import
116
107
  name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
117
108
  root={meta.file.path}
118
- path={path.resolve(root, '.kubb/fetch.ts')}
109
+ path={path.resolve(root, '.kubb/client.ts')}
119
110
  isTypeOnly
120
111
  />
121
112
  {clientOptions.dataReturnType === 'full' && (
122
- <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} isTypeOnly />
113
+ <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} isTypeOnly />
123
114
  )}
124
115
  </>
125
116
  )}