@kubb/plugin-react-query 5.0.0-beta.3 → 5.0.0-beta.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +34 -83
  2. package/dist/{components-DTGLu4UV.js → components-CDmg-RPi.js} +275 -255
  3. package/dist/components-CDmg-RPi.js.map +1 -0
  4. package/dist/{components-dAKJEn9b.cjs → components-MPBTffPl.cjs} +299 -255
  5. package/dist/components-MPBTffPl.cjs.map +1 -0
  6. package/dist/components.cjs +1 -1
  7. package/dist/components.d.ts +1 -75
  8. package/dist/components.js +1 -1
  9. package/dist/{generators-C_fbcjpG.js → generators-Bma51Uar.js} +301 -261
  10. package/dist/generators-Bma51Uar.js.map +1 -0
  11. package/dist/{generators-CWEQsdO9.cjs → generators-BtsWNz-6.cjs} +299 -259
  12. package/dist/generators-BtsWNz-6.cjs.map +1 -0
  13. package/dist/generators.cjs +1 -1
  14. package/dist/generators.d.ts +41 -1
  15. package/dist/generators.js +1 -1
  16. package/dist/index.cjs +143 -20
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.ts +30 -1
  19. package/dist/index.js +143 -20
  20. package/dist/index.js.map +1 -1
  21. package/dist/types-DiZPLTXl.d.ts +400 -0
  22. package/extension.yaml +1507 -0
  23. package/package.json +16 -18
  24. package/src/components/InfiniteQuery.tsx +19 -13
  25. package/src/components/InfiniteQueryOptions.tsx +29 -47
  26. package/src/components/Mutation.tsx +35 -15
  27. package/src/components/MutationOptions.tsx +14 -13
  28. package/src/components/Query.tsx +9 -10
  29. package/src/components/QueryOptions.tsx +6 -27
  30. package/src/components/SuspenseInfiniteQuery.tsx +19 -13
  31. package/src/components/SuspenseInfiniteQueryOptions.tsx +29 -47
  32. package/src/components/SuspenseQuery.tsx +9 -10
  33. package/src/generators/customHookOptionsFileGenerator.tsx +18 -14
  34. package/src/generators/hookOptionsGenerator.tsx +36 -33
  35. package/src/generators/infiniteQueryGenerator.tsx +46 -64
  36. package/src/generators/mutationGenerator.tsx +42 -50
  37. package/src/generators/queryGenerator.tsx +43 -49
  38. package/src/generators/suspenseInfiniteQueryGenerator.tsx +41 -51
  39. package/src/generators/suspenseQueryGenerator.tsx +44 -62
  40. package/src/plugin.ts +42 -16
  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
@@ -1,33 +1,27 @@
1
1
  import path from 'node:path'
2
- import { ast, defineGenerator } from '@kubb/core'
2
+ import { getOperationParameters, resolveOperationTypeNames } from '@internals/shared'
3
+ import { resolveZodSchemaNames } from '@internals/tanstack-query'
4
+ import { defineGenerator } from '@kubb/core'
3
5
  import { Client, pluginClientName } from '@kubb/plugin-client'
4
6
  import { pluginTsName } from '@kubb/plugin-ts'
5
7
  import { pluginZodName } from '@kubb/plugin-zod'
6
- import { File, jsxRenderer } from '@kubb/renderer-jsx'
8
+ import { File, jsxRendererSync } from '@kubb/renderer-jsx'
7
9
  import { difference } from 'remeda'
8
10
  import { InfiniteQuery, InfiniteQueryOptions, QueryKey } from '../components'
9
11
  import type { PluginReactQuery } from '../types'
10
- import { transformName } from '../utils.ts'
11
12
 
13
+ /**
14
+ * Built-in generator for `useInfiniteQuery` hooks. Enabled when
15
+ * `pluginReactQuery({ infinite: { ... } })`. Emits one `useFooInfiniteQuery`
16
+ * hook per query operation, wiring the configured `nextParam` /
17
+ * `previousParam` paths into TanStack Query's cursor-based pagination.
18
+ */
12
19
  export const infiniteQueryGenerator = defineGenerator<PluginReactQuery>({
13
20
  name: 'react-infinite-query',
14
- renderer: jsxRenderer,
21
+ renderer: jsxRendererSync,
15
22
  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
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
@@ -38,13 +32,13 @@ export const infiniteQueryGenerator = defineGenerator<PluginReactQuery>({
38
32
  mutation !== false &&
39
33
  !isQuery &&
40
34
  difference(mutation ? mutation.methods : [], query ? query.methods : []).some((method) => node.method.toLowerCase() === method.toLowerCase())
41
- const infiniteOptions = infinite && typeof infinite === 'object' ? infinite : undefined
35
+ const infiniteOptions = infinite && typeof infinite === 'object' ? infinite : null
42
36
 
43
37
  if (!isQuery || isMutation || !infiniteOptions) return null
44
38
 
45
39
  // Validate queryParam exists in operation's query parameters
46
40
  const normalizeKey = (key: string) => key.replace(/\?$/, '')
47
- const queryParamKeys = node.parameters.filter((p) => p.in === 'query').map((p) => p.name)
41
+ const queryParamKeys = getOperationParameters(node).query.map((p) => p.name)
48
42
  const hasQueryParam = infiniteOptions.queryParam ? queryParamKeys.some((k) => normalizeKey(k) === infiniteOptions.queryParam) : false
49
43
  // cursorParam validation against response schema keys is skipped in v5 (complex schema inspection)
50
44
  const hasCursorParam = !infiniteOptions.cursorParam || true
@@ -53,53 +47,43 @@ export const infiniteQueryGenerator = defineGenerator<PluginReactQuery>({
53
47
 
54
48
  const importPath = query ? query.importPath : '@tanstack/react-query'
55
49
 
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)
50
+ const queryName = resolver.resolveInfiniteQueryName(node)
51
+ const queryOptionsName = resolver.resolveInfiniteQueryOptionsName(node)
52
+ const queryKeyName = resolver.resolveInfiniteQueryKeyName(node)
53
+ const queryKeyTypeName = resolver.resolveInfiniteQueryKeyTypeName(node)
54
+ const clientBaseName = resolver.resolveInfiniteClientName(node)
63
55
 
64
56
  const meta = {
65
- file: resolver.resolveFile({ name: queryName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
57
+ file: resolver.resolveFile(
58
+ { name: queryName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
59
+ { root, output, group: group ?? undefined },
60
+ ),
66
61
  fileTs: tsResolver.resolveFile(
67
62
  { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
68
- { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group },
63
+ { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group ?? undefined },
69
64
  ),
70
65
  }
71
66
 
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
67
+ const importedTypeNames = resolveOperationTypeNames(node, tsResolver, {
68
+ paramsCasing,
69
+ exclude: [queryKeyTypeName],
70
+ order: 'body-response-first',
71
+ })
72
+
73
+ const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
74
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
88
75
  const fileZod = zodResolver
89
76
  ? zodResolver.resolveFile(
90
77
  { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
91
- { root, output: pluginZod?.options?.output ?? output, group: pluginZod?.options?.group },
78
+ { root, output: pluginZod?.options?.output ?? output, group: pluginZod?.options?.group ?? undefined },
92
79
  )
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
- : []
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
89
  ? clientResolver?.resolveFile(
@@ -107,10 +91,10 @@ export const infiniteQueryGenerator = defineGenerator<PluginReactQuery>({
107
91
  {
108
92
  root,
109
93
  output: clientPlugin?.options?.output ?? output,
110
- group: clientPlugin?.options?.group,
94
+ group: clientPlugin?.options?.group ?? undefined,
111
95
  },
112
96
  )
113
- : undefined
97
+ : null
114
98
 
115
99
  const resolvedClientName = shouldUseClientPlugin ? (clientResolver?.resolveName(node.operationId) ?? clientBaseName) : clientBaseName
116
100
 
@@ -119,29 +103,27 @@ export const infiniteQueryGenerator = defineGenerator<PluginReactQuery>({
119
103
  baseName={meta.file.baseName}
120
104
  path={meta.file.path}
121
105
  meta={meta.file.meta}
122
- banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
123
- footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
106
+ banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
107
+ footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
124
108
  >
125
- {parser === 'zod' && fileZod && zodSchemaNames.length > 0 && (
126
- <File.Import name={zodSchemaNames as string[]} root={meta.file.path} path={fileZod.path} />
127
- )}
109
+ {fileZod && zodSchemaNames.length > 0 && <File.Import name={zodSchemaNames} root={meta.file.path} path={fileZod.path} />}
128
110
  {clientOptions.importPath ? (
129
111
  <>
130
- {!shouldUseClientPlugin && <File.Import name={'fetch'} path={clientOptions.importPath} />}
112
+ {!shouldUseClientPlugin && <File.Import name={'client'} path={clientOptions.importPath} />}
131
113
  <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={clientOptions.importPath} isTypeOnly />
132
114
  {clientOptions.dataReturnType === 'full' && <File.Import name={['ResponseConfig']} path={clientOptions.importPath} isTypeOnly />}
133
115
  </>
134
116
  ) : (
135
117
  <>
136
- {!shouldUseClientPlugin && <File.Import name={['fetch']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} />}
118
+ {!shouldUseClientPlugin && <File.Import name={['client']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} />}
137
119
  <File.Import
138
120
  name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
139
121
  root={meta.file.path}
140
- path={path.resolve(root, '.kubb/fetch.ts')}
122
+ path={path.resolve(root, '.kubb/client.ts')}
141
123
  isTypeOnly
142
124
  />
143
125
  {clientOptions.dataReturnType === 'full' && (
144
- <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} isTypeOnly />
126
+ <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} isTypeOnly />
145
127
  )}
146
128
  </>
147
129
  )}
@@ -1,20 +1,26 @@
1
1
  import path from 'node:path'
2
- import { ast, defineGenerator } from '@kubb/core'
2
+ import { resolveOperationTypeNames } from '@internals/shared'
3
+ import { resolveZodSchemaNames } from '@internals/tanstack-query'
4
+ import { defineGenerator } from '@kubb/core'
3
5
  import { Client, pluginClientName } from '@kubb/plugin-client'
4
6
  import { pluginTsName } from '@kubb/plugin-ts'
5
7
  import { pluginZodName } from '@kubb/plugin-zod'
6
- import { File, jsxRenderer } from '@kubb/renderer-jsx'
8
+ import { File, jsxRendererSync } from '@kubb/renderer-jsx'
7
9
  import { difference } from 'remeda'
8
10
  import { Mutation, MutationKey, MutationOptions } from '../components'
9
11
  import type { PluginReactQuery } from '../types'
10
- import { transformName } from '../utils.ts'
11
12
 
13
+ /**
14
+ * Built-in generator for `useMutation` hooks. Emits one `useFooMutation` hook
15
+ * per POST/PUT/DELETE operation (configurable via `mutation.methods`) plus
16
+ * the matching `fooMutationKey` / `fooMutationOptions` helpers.
17
+ */
12
18
  export const mutationGenerator = defineGenerator<PluginReactQuery>({
13
19
  name: 'react-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, customOptions } = ctx.options
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
@@ -30,53 +36,39 @@ export const mutationGenerator = defineGenerator<PluginReactQuery>({
30
36
 
31
37
  const importPath = mutation ? mutation.importPath : '@tanstack/react-query'
32
38
 
33
- const baseName = resolver.resolveName(node.operationId)
34
- const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)
35
- const mutationHookName = transformName(`use${capitalize(baseName)}`, 'function', transformers)
36
- const mutationTypeName = transformName(`${capitalize(baseName)}`, 'type', transformers)
37
- const mutationOptionsName = transformName(`${baseName}MutationOptions`, 'function', transformers)
38
- const mutationKeyName = transformName(`${baseName}MutationKey`, 'const', transformers)
39
- const clientName = transformName(baseName, 'function', transformers)
39
+ const mutationHookName = resolver.resolveMutationName(node)
40
+ const mutationTypeName = resolver.resolveMutationTypeName(node)
41
+ const mutationOptionsName = resolver.resolveMutationOptionsName(node)
42
+ const mutationKeyName = resolver.resolveMutationKeyName(node)
43
+ const clientName = resolver.resolveClientName(node)
40
44
 
41
45
  const meta = {
42
- file: resolver.resolveFile({ name: mutationHookName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
46
+ file: resolver.resolveFile(
47
+ { name: mutationHookName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
48
+ { root, output, group: group ?? undefined },
49
+ ),
43
50
  fileTs: tsResolver.resolveFile(
44
51
  { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
45
- { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group },
52
+ { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group ?? undefined },
46
53
  ),
47
54
  }
48
55
 
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
56
+ const importedTypeNames = resolveOperationTypeNames(node, tsResolver, { paramsCasing, order: 'body-response-first' })
57
+
58
+ const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
59
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
65
60
  const fileZod = zodResolver
66
61
  ? zodResolver.resolveFile(
67
62
  { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
68
- { root, output: pluginZod?.options?.output ?? output, group: pluginZod?.options?.group },
63
+ { root, output: pluginZod?.options?.output ?? output, group: pluginZod?.options?.group ?? undefined },
69
64
  )
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
- : []
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
74
  ? clientResolver?.resolveFile(
@@ -84,10 +76,10 @@ export const mutationGenerator = defineGenerator<PluginReactQuery>({
84
76
  {
85
77
  root,
86
78
  output: clientPlugin?.options?.output ?? output,
87
- group: clientPlugin?.options?.group,
79
+ group: clientPlugin?.options?.group ?? undefined,
88
80
  },
89
81
  )
90
- : undefined
82
+ : null
91
83
 
92
84
  const resolvedClientName = shouldUseClientPlugin ? (clientResolver?.resolveName(node.operationId) ?? clientName) : clientName
93
85
 
@@ -96,34 +88,34 @@ export const mutationGenerator = defineGenerator<PluginReactQuery>({
96
88
  baseName={meta.file.baseName}
97
89
  path={meta.file.path}
98
90
  meta={meta.file.meta}
99
- banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
100
- footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
91
+ banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
92
+ footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
101
93
  >
102
- {parser === 'zod' && fileZod && zodSchemaNames.length > 0 && (
103
- <File.Import name={zodSchemaNames as string[]} root={meta.file.path} path={fileZod.path} />
104
- )}
94
+ {fileZod && zodSchemaNames.length > 0 && <File.Import name={zodSchemaNames} root={meta.file.path} path={fileZod.path} />}
105
95
  {clientOptions.importPath ? (
106
96
  <>
107
- {!shouldUseClientPlugin && <File.Import name={'fetch'} path={clientOptions.importPath} />}
97
+ {!shouldUseClientPlugin && <File.Import name={'client'} path={clientOptions.importPath} />}
108
98
  <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={clientOptions.importPath} isTypeOnly />
109
99
  {clientOptions.dataReturnType === 'full' && <File.Import name={['ResponseConfig']} path={clientOptions.importPath} isTypeOnly />}
110
100
  </>
111
101
  ) : (
112
102
  <>
113
- {!shouldUseClientPlugin && <File.Import name={['fetch']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} />}
103
+ {!shouldUseClientPlugin && <File.Import name={['client']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} />}
114
104
  <File.Import
115
105
  name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
116
106
  root={meta.file.path}
117
- path={path.resolve(root, '.kubb/fetch.ts')}
107
+ path={path.resolve(root, '.kubb/client.ts')}
118
108
  isTypeOnly
119
109
  />
120
110
  {clientOptions.dataReturnType === 'full' && (
121
- <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} isTypeOnly />
111
+ <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} isTypeOnly />
122
112
  )}
123
113
  </>
124
114
  )}
125
115
  {shouldUseClientPlugin && clientFile && <File.Import name={[resolvedClientName]} root={meta.file.path} path={clientFile.path} />}
126
- {!shouldUseClientPlugin && <File.Import name={['buildFormData']} root={meta.file.path} path={path.resolve(root, '.kubb/config.ts')} />}
116
+ {!shouldUseClientPlugin && node.requestBody?.content?.some((e) => e.contentType === 'multipart/form-data') && (
117
+ <File.Import name={['buildFormData']} root={meta.file.path} path={path.resolve(root, '.kubb/config.ts')} />
118
+ )}
127
119
  {customOptions && <File.Import name={[customOptions.name]} path={customOptions.importPath} />}
128
120
  {meta.fileTs && importedTypeNames.length > 0 && (
129
121
  <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 { ast, defineGenerator } from '@kubb/core'
2
+ import { resolveOperationTypeNames } from '@internals/shared'
3
+ import { resolveZodSchemaNames } from '@internals/tanstack-query'
4
+ import { defineGenerator } from '@kubb/core'
3
5
  import { Client, pluginClientName } from '@kubb/plugin-client'
4
6
  import { pluginTsName } from '@kubb/plugin-ts'
5
7
  import { pluginZodName } from '@kubb/plugin-zod'
6
- import { File, jsxRenderer } from '@kubb/renderer-jsx'
8
+ import { File, jsxRendererSync } from '@kubb/renderer-jsx'
7
9
  import { difference } from 'remeda'
8
10
  import { Query, QueryKey, QueryOptions } from '../components'
9
11
  import type { PluginReactQuery } from '../types'
10
- import { transformName } from '../utils.ts'
11
12
 
13
+ /**
14
+ * Built-in generator for `useQuery` hooks. Emits one `useFooQuery` hook per
15
+ * GET operation (configurable via `query.methods`) plus the matching
16
+ * `fooQueryKey` / `fooQueryOptions` helpers.
17
+ */
12
18
  export const queryGenerator = defineGenerator<PluginReactQuery>({
13
19
  name: 'react-query',
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, customOptions } = ctx.options
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
@@ -31,53 +37,43 @@ export const queryGenerator = defineGenerator<PluginReactQuery>({
31
37
 
32
38
  const importPath = query ? query.importPath : '@tanstack/react-query'
33
39
 
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)
40
+ const queryName = resolver.resolveQueryName(node)
41
+ const queryOptionsName = resolver.resolveQueryOptionsName(node)
42
+ const queryKeyName = resolver.resolveQueryKeyName(node)
43
+ const queryKeyTypeName = resolver.resolveQueryKeyTypeName(node)
44
+ const clientName = resolver.resolveClientName(node)
41
45
 
42
46
  const meta = {
43
- file: resolver.resolveFile({ name: queryName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
47
+ file: resolver.resolveFile(
48
+ { name: queryName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
49
+ { root, output, group: group ?? undefined },
50
+ ),
44
51
  fileTs: tsResolver.resolveFile(
45
52
  { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
46
- { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group },
53
+ { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group ?? undefined },
47
54
  ),
48
55
  }
49
56
 
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
57
+ const importedTypeNames = resolveOperationTypeNames(node, tsResolver, {
58
+ paramsCasing,
59
+ exclude: [queryKeyTypeName],
60
+ order: 'body-response-first',
61
+ })
62
+
63
+ const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
64
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
66
65
  const fileZod = zodResolver
67
66
  ? zodResolver.resolveFile(
68
67
  { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
69
- { root, output: pluginZod?.options?.output ?? output, group: pluginZod?.options?.group },
68
+ { root, output: pluginZod?.options?.output ?? output, group: pluginZod?.options?.group ?? undefined },
70
69
  )
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
- : []
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
79
  ? clientResolver?.resolveFile(
@@ -85,10 +81,10 @@ export const queryGenerator = defineGenerator<PluginReactQuery>({
85
81
  {
86
82
  root,
87
83
  output: clientPlugin?.options?.output ?? output,
88
- group: clientPlugin?.options?.group,
84
+ group: clientPlugin?.options?.group ?? undefined,
89
85
  },
90
86
  )
91
- : undefined
87
+ : null
92
88
 
93
89
  const resolvedClientName = shouldUseClientPlugin ? (clientResolver?.resolveName(node.operationId) ?? clientName) : clientName
94
90
 
@@ -97,29 +93,27 @@ export const queryGenerator = defineGenerator<PluginReactQuery>({
97
93
  baseName={meta.file.baseName}
98
94
  path={meta.file.path}
99
95
  meta={meta.file.meta}
100
- banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
101
- footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
96
+ banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
97
+ footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
102
98
  >
103
- {parser === 'zod' && fileZod && zodSchemaNames.length > 0 && (
104
- <File.Import name={zodSchemaNames as string[]} root={meta.file.path} path={fileZod.path} />
105
- )}
99
+ {fileZod && zodSchemaNames.length > 0 && <File.Import name={zodSchemaNames} root={meta.file.path} path={fileZod.path} />}
106
100
  {clientOptions.importPath ? (
107
101
  <>
108
- {!shouldUseClientPlugin && <File.Import name={'fetch'} path={clientOptions.importPath} />}
102
+ {!shouldUseClientPlugin && <File.Import name={'client'} path={clientOptions.importPath} />}
109
103
  <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={clientOptions.importPath} isTypeOnly />
110
104
  {clientOptions.dataReturnType === 'full' && <File.Import name={['ResponseConfig']} path={clientOptions.importPath} isTypeOnly />}
111
105
  </>
112
106
  ) : (
113
107
  <>
114
- {!shouldUseClientPlugin && <File.Import name={['fetch']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} />}
108
+ {!shouldUseClientPlugin && <File.Import name={['client']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} />}
115
109
  <File.Import
116
110
  name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
117
111
  root={meta.file.path}
118
- path={path.resolve(root, '.kubb/fetch.ts')}
112
+ path={path.resolve(root, '.kubb/client.ts')}
119
113
  isTypeOnly
120
114
  />
121
115
  {clientOptions.dataReturnType === 'full' && (
122
- <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} isTypeOnly />
116
+ <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} isTypeOnly />
123
117
  )}
124
118
  </>
125
119
  )}