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

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-DTGLu4UV.js → components-DL0Cai7l.js} +570 -514
  3. package/dist/components-DL0Cai7l.js.map +1 -0
  4. package/dist/{components-dAKJEn9b.cjs → components-yMQOuFmI.cjs} +600 -514
  5. package/dist/components-yMQOuFmI.cjs.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-BG-Vcvfg.js} +444 -597
  10. package/dist/generators-BG-Vcvfg.js.map +1 -0
  11. package/dist/{generators-CWEQsdO9.cjs → generators-zGKP8yII.cjs} +442 -595
  12. package/dist/generators-zGKP8yII.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 +201 -28
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.ts +32 -4
  19. package/dist/index.js +203 -30
  20. package/dist/index.js.map +1 -1
  21. package/dist/types-X7D0NSvJ.d.ts +396 -0
  22. package/package.json +18 -27
  23. package/src/components/InfiniteQuery.tsx +27 -17
  24. package/src/components/InfiniteQueryOptions.tsx +60 -81
  25. package/src/components/Mutation.tsx +39 -20
  26. package/src/components/MutationOptions.tsx +15 -14
  27. package/src/components/Query.tsx +18 -15
  28. package/src/components/QueryOptions.tsx +20 -56
  29. package/src/components/SuspenseInfiniteQuery.tsx +22 -17
  30. package/src/components/SuspenseInfiniteQueryOptions.tsx +51 -76
  31. package/src/components/SuspenseQuery.tsx +13 -15
  32. package/src/generators/customHookOptionsFileGenerator.tsx +16 -12
  33. package/src/generators/hookOptionsGenerator.tsx +42 -49
  34. package/src/generators/infiniteQueryGenerator.tsx +55 -80
  35. package/src/generators/mutationGenerator.tsx +54 -66
  36. package/src/generators/queryGenerator.tsx +52 -65
  37. package/src/generators/suspenseInfiniteQueryGenerator.tsx +50 -67
  38. package/src/generators/suspenseQueryGenerator.tsx +54 -78
  39. package/src/plugin.ts +47 -33
  40. package/src/resolvers/resolverReactQuery.ts +104 -8
  41. package/src/types.ts +202 -68
  42. package/src/utils.ts +11 -33
  43. package/dist/components-DTGLu4UV.js.map +0 -1
  44. package/dist/components-dAKJEn9b.cjs.map +0 -1
  45. package/dist/generators-CWEQsdO9.cjs.map +0 -1
  46. package/dist/generators-C_fbcjpG.js.map +0 -1
  47. package/dist/types-DfaFRSBf.d.ts +0 -284
  48. package/extension.yaml +0 -938
  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
- import { Client, pluginClientName } from '@kubb/plugin-client'
5
+ import { Client, isParserEnabled, pluginClientName } from '@kubb/plugin-client'
4
6
  import { pluginTsName } from '@kubb/plugin-ts'
5
7
  import { pluginZodName } from '@kubb/plugin-zod'
6
8
  import { File, jsxRenderer } from '@kubb/renderer-jsx'
7
- import { difference } from 'remeda'
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
20
  renderer: jsxRenderer,
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 = isParserEnabled(parser) ? 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, parser)
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,30 +100,24 @@ 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
- {clientOptions.dataReturnType === 'full' && <File.Import name={['ResponseConfig']} path={clientOptions.importPath} isTypeOnly />}
133
111
  </>
134
112
  ) : (
135
113
  <>
136
- {!shouldUseClientPlugin && <File.Import name={['fetch']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} />}
114
+ {!shouldUseClientPlugin && <File.Import name={['client']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} />}
137
115
  <File.Import
138
116
  name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
139
117
  root={meta.file.path}
140
- path={path.resolve(root, '.kubb/fetch.ts')}
118
+ path={path.resolve(root, '.kubb/client.ts')}
141
119
  isTypeOnly
142
120
  />
143
- {clientOptions.dataReturnType === 'full' && (
144
- <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} isTypeOnly />
145
- )}
146
121
  </>
147
122
  )}
148
123
  {shouldUseClientPlugin && clientFile && <File.Import name={[resolvedClientName]} root={meta.file.path} path={clientFile.path} />}
@@ -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
- import { Client, pluginClientName } from '@kubb/plugin-client'
5
+ import { Client, isParserEnabled, pluginClientName } from '@kubb/plugin-client'
4
6
  import { pluginTsName } from '@kubb/plugin-ts'
5
7
  import { pluginZodName } from '@kubb/plugin-zod'
6
8
  import { File, jsxRenderer } from '@kubb/renderer-jsx'
7
- import { difference } from 'remeda'
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
19
  renderer: jsxRenderer,
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 = isParserEnabled(parser) ? 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, parser)
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,33 @@ 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
- {clientOptions.dataReturnType === 'full' && <File.Import name={['ResponseConfig']} path={clientOptions.importPath} isTypeOnly />}
110
96
  </>
111
97
  ) : (
112
98
  <>
113
- {!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')} />}
114
100
  <File.Import
115
101
  name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
116
102
  root={meta.file.path}
117
- path={path.resolve(root, '.kubb/fetch.ts')}
103
+ path={path.resolve(root, '.kubb/client.ts')}
118
104
  isTypeOnly
119
105
  />
120
- {clientOptions.dataReturnType === 'full' && (
121
- <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} isTypeOnly />
122
- )}
123
106
  </>
124
107
  )}
125
108
  {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')} />}
109
+ {!shouldUseClientPlugin && node.requestBody?.content?.some((e) => e.contentType === 'multipart/form-data') && (
110
+ <File.Import name={['buildFormData']} root={meta.file.path} path={path.resolve(root, '.kubb/config.ts')} />
111
+ )}
112
+ {!shouldUseClientPlugin && parser === 'zod' && zodResolver && node.requestBody?.content?.[0]?.schema && (
113
+ <File.Import name={['z']} path="zod" isTypeOnly />
114
+ )}
127
115
  {customOptions && <File.Import name={[customOptions.name]} path={customOptions.importPath} />}
128
116
  {meta.fileTs && importedTypeNames.length > 0 && (
129
117
  <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
- import { Client, pluginClientName } from '@kubb/plugin-client'
5
+ import { Client, isParserEnabled, pluginClientName } from '@kubb/plugin-client'
4
6
  import { pluginTsName } from '@kubb/plugin-ts'
5
7
  import { pluginZodName } from '@kubb/plugin-zod'
6
8
  import { File, jsxRenderer } from '@kubb/renderer-jsx'
7
- import { difference } from 'remeda'
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
19
  renderer: jsxRenderer,
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 = isParserEnabled(parser) ? 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, parser)
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,30 +90,24 @@ 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
- {clientOptions.dataReturnType === 'full' && <File.Import name={['ResponseConfig']} path={clientOptions.importPath} isTypeOnly />}
111
101
  </>
112
102
  ) : (
113
103
  <>
114
- {!shouldUseClientPlugin && <File.Import name={['fetch']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} />}
104
+ {!shouldUseClientPlugin && <File.Import name={['client']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} />}
115
105
  <File.Import
116
106
  name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
117
107
  root={meta.file.path}
118
- path={path.resolve(root, '.kubb/fetch.ts')}
108
+ path={path.resolve(root, '.kubb/client.ts')}
119
109
  isTypeOnly
120
110
  />
121
- {clientOptions.dataReturnType === 'full' && (
122
- <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} isTypeOnly />
123
- )}
124
111
  </>
125
112
  )}
126
113
  {shouldUseClientPlugin && clientFile && <File.Import name={[resolvedClientName]} root={meta.file.path} path={clientFile.path} />}