@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,19 +1,26 @@
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 { QueryKey, SuspenseInfiniteQuery, SuspenseInfiniteQueryOptions } from '../components'
9
11
  import type { PluginReactQuery } from '../types'
10
- import { transformName } from '../utils.ts'
11
12
 
13
+ /**
14
+ * Built-in generator for `useSuspenseInfiniteQuery` hooks. Enabled when both
15
+ * `suspense` and `infinite` are configured. Combines suspense semantics with
16
+ * cursor-based pagination — handlers throw promises while loading and pull
17
+ * additional pages on demand.
18
+ */
12
19
  export const suspenseInfiniteQueryGenerator = defineGenerator<PluginReactQuery>({
13
20
  name: 'react-suspense-infinite-query',
14
- renderer: jsxRenderer,
21
+ renderer: jsxRendererSync,
15
22
  operation(node, ctx) {
16
- const { adapter, config, driver, resolver, root } = ctx
23
+ const { config, driver, resolver, root } = ctx
17
24
  const {
18
25
  output,
19
26
  query,
@@ -26,7 +33,6 @@ export const suspenseInfiniteQueryGenerator = defineGenerator<PluginReactQuery>(
26
33
  parser,
27
34
  client: clientOptions,
28
35
  group,
29
- transformers,
30
36
  customOptions,
31
37
  } = ctx.options
32
38
 
@@ -40,13 +46,13 @@ export const suspenseInfiniteQueryGenerator = defineGenerator<PluginReactQuery>(
40
46
  !isQuery &&
41
47
  difference(mutation ? mutation.methods : [], query ? query.methods : []).some((method) => node.method.toLowerCase() === method.toLowerCase())
42
48
  const isSuspense = !!suspense
43
- const infiniteOptions = infinite && typeof infinite === 'object' ? infinite : undefined
49
+ const infiniteOptions = infinite && typeof infinite === 'object' ? infinite : null
44
50
 
45
51
  if (!isQuery || isMutation || !isSuspense || !infiniteOptions) return null
46
52
 
47
53
  // Validate queryParam exists in operation's query parameters
48
54
  const normalizeKey = (key: string) => key.replace(/\?$/, '')
49
- const queryParamKeys = node.parameters.filter((p) => p.in === 'query').map((p) => p.name)
55
+ const queryParamKeys = getOperationParameters(node).query.map((p) => p.name)
50
56
  const hasQueryParam = infiniteOptions.queryParam ? queryParamKeys.some((k) => normalizeKey(k) === infiniteOptions.queryParam) : false
51
57
  const hasCursorParam = !infiniteOptions.cursorParam || true
52
58
 
@@ -54,53 +60,39 @@ export const suspenseInfiniteQueryGenerator = defineGenerator<PluginReactQuery>(
54
60
 
55
61
  const importPath = query ? query.importPath : '@tanstack/react-query'
56
62
 
57
- const baseName = resolver.resolveName(node.operationId)
58
- const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)
59
- const queryName = transformName(`use${capitalize(baseName)}SuspenseInfinite`, 'function', transformers)
60
- const queryOptionsName = transformName(`${baseName}SuspenseInfiniteQueryOptions`, 'function', transformers)
61
- const queryKeyName = transformName(`${baseName}SuspenseInfiniteQueryKey`, 'const', transformers)
62
- const queryKeyTypeName = transformName(`${capitalize(baseName)}SuspenseInfiniteQueryKey`, 'type', transformers)
63
- const clientBaseName = transformName(`${baseName}SuspenseInfinite`, 'function', transformers)
63
+ const queryName = resolver.resolveSuspenseInfiniteQueryName(node)
64
+ const queryOptionsName = resolver.resolveSuspenseInfiniteQueryOptionsName(node)
65
+ const queryKeyName = resolver.resolveSuspenseInfiniteQueryKeyName(node)
66
+ const queryKeyTypeName = resolver.resolveSuspenseInfiniteQueryKeyTypeName(node)
67
+ const clientBaseName = resolver.resolveSuspenseInfiniteClientName(node)
64
68
 
65
69
  const meta = {
66
- file: resolver.resolveFile({ name: queryName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
70
+ file: resolver.resolveFile(
71
+ { name: queryName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
72
+ { root, output, group: group ?? undefined },
73
+ ),
67
74
  fileTs: tsResolver.resolveFile(
68
75
  { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
69
- { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group },
76
+ { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group ?? undefined },
70
77
  ),
71
78
  }
72
79
 
73
- const casedParams = ast.caseParams(node.parameters, paramsCasing)
74
- const pathParams = casedParams.filter((p) => p.in === 'path')
75
- const queryParams = casedParams.filter((p) => p.in === 'query')
76
- const headerParams = casedParams.filter((p) => p.in === 'header')
77
-
78
- const importedTypeNames = [
79
- node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : undefined,
80
- tsResolver.resolveResponseName(node),
81
- ...pathParams.map((p) => tsResolver.resolvePathParamsName(node, p)),
82
- ...queryParams.map((p) => tsResolver.resolveQueryParamsName(node, p)),
83
- ...headerParams.map((p) => tsResolver.resolveHeaderParamsName(node, p)),
84
- ...node.responses.map((res) => tsResolver.resolveResponseStatusName(node, res.statusCode)),
85
- ].filter(Boolean)
86
-
87
- const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : undefined
88
- const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : undefined
80
+ const importedTypeNames = resolveOperationTypeNames(node, tsResolver, { paramsCasing, order: 'body-response-first' })
81
+
82
+ const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
83
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
89
84
  const fileZod = zodResolver
90
85
  ? zodResolver.resolveFile(
91
86
  { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
92
- { root, output: pluginZod?.options?.output ?? output, group: pluginZod?.options?.group },
87
+ { root, output: pluginZod?.options?.output ?? output, group: pluginZod?.options?.group ?? undefined },
93
88
  )
94
- : undefined
95
- const zodSchemaNames =
96
- zodResolver && parser === 'zod'
97
- ? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : undefined].filter(Boolean)
98
- : []
89
+ : null
90
+ const zodSchemaNames = resolveZodSchemaNames(node, zodResolver)
99
91
 
100
92
  const clientPlugin = driver.getPlugin(pluginClientName)
101
93
  const hasClientPlugin = clientPlugin?.name === pluginClientName
102
94
  const shouldUseClientPlugin = hasClientPlugin && clientOptions.clientType !== 'class'
103
- const clientResolver = shouldUseClientPlugin ? driver.getResolver(pluginClientName) : undefined
95
+ const clientResolver = shouldUseClientPlugin ? driver.getResolver(pluginClientName) : null
104
96
 
105
97
  const clientFile = shouldUseClientPlugin
106
98
  ? clientResolver?.resolveFile(
@@ -108,10 +100,10 @@ export const suspenseInfiniteQueryGenerator = defineGenerator<PluginReactQuery>(
108
100
  {
109
101
  root,
110
102
  output: clientPlugin?.options?.output ?? output,
111
- group: clientPlugin?.options?.group,
103
+ group: clientPlugin?.options?.group ?? undefined,
112
104
  },
113
105
  )
114
- : undefined
106
+ : null
115
107
 
116
108
  const resolvedClientName = shouldUseClientPlugin ? (clientResolver?.resolveName(node.operationId) ?? clientBaseName) : clientBaseName
117
109
 
@@ -120,29 +112,27 @@ export const suspenseInfiniteQueryGenerator = defineGenerator<PluginReactQuery>(
120
112
  baseName={meta.file.baseName}
121
113
  path={meta.file.path}
122
114
  meta={meta.file.meta}
123
- banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
124
- footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
115
+ banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
116
+ footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
125
117
  >
126
- {parser === 'zod' && fileZod && zodSchemaNames.length > 0 && (
127
- <File.Import name={zodSchemaNames as string[]} root={meta.file.path} path={fileZod.path} />
128
- )}
118
+ {fileZod && zodSchemaNames.length > 0 && <File.Import name={zodSchemaNames} root={meta.file.path} path={fileZod.path} />}
129
119
  {clientOptions.importPath ? (
130
120
  <>
131
- {!shouldUseClientPlugin && <File.Import name={'fetch'} path={clientOptions.importPath} />}
121
+ {!shouldUseClientPlugin && <File.Import name={'client'} path={clientOptions.importPath} />}
132
122
  <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={clientOptions.importPath} isTypeOnly />
133
123
  {clientOptions.dataReturnType === 'full' && <File.Import name={['ResponseConfig']} path={clientOptions.importPath} isTypeOnly />}
134
124
  </>
135
125
  ) : (
136
126
  <>
137
- {!shouldUseClientPlugin && <File.Import name={['fetch']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} />}
127
+ {!shouldUseClientPlugin && <File.Import name={['client']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} />}
138
128
  <File.Import
139
129
  name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
140
130
  root={meta.file.path}
141
- path={path.resolve(root, '.kubb/fetch.ts')}
131
+ path={path.resolve(root, '.kubb/client.ts')}
142
132
  isTypeOnly
143
133
  />
144
134
  {clientOptions.dataReturnType === 'full' && (
145
- <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} isTypeOnly />
135
+ <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} isTypeOnly />
146
136
  )}
147
137
  </>
148
138
  )}
@@ -1,33 +1,27 @@
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 { QueryKey, QueryOptions, SuspenseQuery } from '../components'
9
11
  import type { PluginReactQuery } from '../types'
10
- import { transformName } from '../utils.ts'
11
12
 
13
+ /**
14
+ * Built-in generator for `useSuspenseQuery` hooks. Enabled when
15
+ * `pluginReactQuery({ suspense: {} })`. Emits one `useFooSuspenseQuery` hook
16
+ * per query operation. Suspense queries throw promises while loading and
17
+ * require a `<Suspense>` boundary in the React tree. TanStack Query v5+ only.
18
+ */
12
19
  export const suspenseQueryGenerator = defineGenerator<PluginReactQuery>({
13
20
  name: 'react-suspense-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
- suspense,
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, suspense, 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
@@ -45,53 +39,43 @@ export const suspenseQueryGenerator = defineGenerator<PluginReactQuery>({
45
39
 
46
40
  const importPath = query ? query.importPath : '@tanstack/react-query'
47
41
 
48
- const baseName = resolver.resolveName(node.operationId)
49
- const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)
50
- const queryName = transformName(`use${capitalize(baseName)}Suspense`, 'function', transformers)
51
- const queryOptionsName = transformName(`${baseName}SuspenseQueryOptions`, 'function', transformers)
52
- const queryKeyName = transformName(`${baseName}SuspenseQueryKey`, 'const', transformers)
53
- const queryKeyTypeName = transformName(`${capitalize(baseName)}SuspenseQueryKey`, 'type', transformers)
54
- const clientName = transformName(`${baseName}Suspense`, 'function', transformers)
42
+ const queryName = resolver.resolveSuspenseQueryName(node)
43
+ const queryOptionsName = resolver.resolveSuspenseQueryOptionsName(node)
44
+ const queryKeyName = resolver.resolveSuspenseQueryKeyName(node)
45
+ const queryKeyTypeName = resolver.resolveSuspenseQueryKeyTypeName(node)
46
+ const clientName = resolver.resolveSuspenseClientName(node)
55
47
 
56
48
  const meta = {
57
- file: resolver.resolveFile({ name: queryName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
49
+ file: resolver.resolveFile(
50
+ { name: queryName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
51
+ { root, output, group: group ?? undefined },
52
+ ),
58
53
  fileTs: tsResolver.resolveFile(
59
54
  { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
60
- { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group },
55
+ { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group ?? undefined },
61
56
  ),
62
57
  }
63
58
 
64
- const casedParams = ast.caseParams(node.parameters, paramsCasing)
65
- const pathParams = casedParams.filter((p) => p.in === 'path')
66
- const queryParams = casedParams.filter((p) => p.in === 'query')
67
- const headerParams = casedParams.filter((p) => p.in === 'header')
68
-
69
- const importedTypeNames = [
70
- node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : undefined,
71
- tsResolver.resolveResponseName(node),
72
- ...pathParams.map((p) => tsResolver.resolvePathParamsName(node, p)),
73
- ...queryParams.map((p) => tsResolver.resolveQueryParamsName(node, p)),
74
- ...headerParams.map((p) => tsResolver.resolveHeaderParamsName(node, p)),
75
- ...node.responses.map((res) => tsResolver.resolveResponseStatusName(node, res.statusCode)),
76
- ].filter((name): name is string => !!name && name !== queryKeyTypeName)
77
-
78
- const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : undefined
79
- const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : undefined
59
+ const importedTypeNames = resolveOperationTypeNames(node, tsResolver, {
60
+ paramsCasing,
61
+ exclude: [queryKeyTypeName],
62
+ order: 'body-response-first',
63
+ })
64
+
65
+ const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
66
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
80
67
  const fileZod = zodResolver
81
68
  ? zodResolver.resolveFile(
82
69
  { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
83
- { root, output: pluginZod?.options?.output ?? output, group: pluginZod?.options?.group },
70
+ { root, output: pluginZod?.options?.output ?? output, group: pluginZod?.options?.group ?? undefined },
84
71
  )
85
- : undefined
86
- const zodSchemaNames =
87
- zodResolver && parser === 'zod'
88
- ? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : undefined].filter(Boolean)
89
- : []
72
+ : null
73
+ const zodSchemaNames = resolveZodSchemaNames(node, zodResolver)
90
74
 
91
75
  const clientPlugin = driver.getPlugin(pluginClientName)
92
76
  const hasClientPlugin = clientPlugin?.name === pluginClientName
93
77
  const shouldUseClientPlugin = hasClientPlugin && clientOptions.clientType !== 'class'
94
- const clientResolver = shouldUseClientPlugin ? driver.getResolver(pluginClientName) : undefined
78
+ const clientResolver = shouldUseClientPlugin ? driver.getResolver(pluginClientName) : null
95
79
 
96
80
  const clientFile = shouldUseClientPlugin
97
81
  ? clientResolver?.resolveFile(
@@ -99,10 +83,10 @@ export const suspenseQueryGenerator = defineGenerator<PluginReactQuery>({
99
83
  {
100
84
  root,
101
85
  output: clientPlugin?.options?.output ?? output,
102
- group: clientPlugin?.options?.group,
86
+ group: clientPlugin?.options?.group ?? undefined,
103
87
  },
104
88
  )
105
- : undefined
89
+ : null
106
90
 
107
91
  const resolvedClientName = shouldUseClientPlugin ? (clientResolver?.resolveName(node.operationId) ?? clientName) : clientName
108
92
 
@@ -111,29 +95,27 @@ export const suspenseQueryGenerator = defineGenerator<PluginReactQuery>({
111
95
  baseName={meta.file.baseName}
112
96
  path={meta.file.path}
113
97
  meta={meta.file.meta}
114
- banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
115
- footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
98
+ banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
99
+ footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
116
100
  >
117
- {parser === 'zod' && fileZod && zodSchemaNames.length > 0 && (
118
- <File.Import name={zodSchemaNames as string[]} root={meta.file.path} path={fileZod.path} />
119
- )}
101
+ {fileZod && zodSchemaNames.length > 0 && <File.Import name={zodSchemaNames} root={meta.file.path} path={fileZod.path} />}
120
102
  {clientOptions.importPath ? (
121
103
  <>
122
- {!shouldUseClientPlugin && <File.Import name={'fetch'} path={clientOptions.importPath} />}
104
+ {!shouldUseClientPlugin && <File.Import name={'client'} path={clientOptions.importPath} />}
123
105
  <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={clientOptions.importPath} isTypeOnly />
124
106
  {clientOptions.dataReturnType === 'full' && <File.Import name={['ResponseConfig']} path={clientOptions.importPath} isTypeOnly />}
125
107
  </>
126
108
  ) : (
127
109
  <>
128
- {!shouldUseClientPlugin && <File.Import name={['fetch']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} />}
110
+ {!shouldUseClientPlugin && <File.Import name={['client']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} />}
129
111
  <File.Import
130
112
  name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
131
113
  root={meta.file.path}
132
- path={path.resolve(root, '.kubb/fetch.ts')}
114
+ path={path.resolve(root, '.kubb/client.ts')}
133
115
  isTypeOnly
134
116
  />
135
117
  {clientOptions.dataReturnType === 'full' && (
136
- <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} isTypeOnly />
118
+ <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} isTypeOnly />
137
119
  )}
138
120
  </>
139
121
  )}
package/src/plugin.ts CHANGED
@@ -7,8 +7,7 @@ import { source as fetchClientSource } from '@kubb/plugin-client/templates/clien
7
7
  import { source as configSource } from '@kubb/plugin-client/templates/config.source'
8
8
  import { pluginTsName } from '@kubb/plugin-ts'
9
9
  import { pluginZodName } from '@kubb/plugin-zod'
10
- import { MutationKey } from './components/MutationKey.tsx'
11
- import { QueryKey } from './components/QueryKey.tsx'
10
+ import { mutationKeyTransformer, queryKeyTransformer } from '@internals/tanstack-query'
12
11
  import {
13
12
  customHookOptionsFileGenerator,
14
13
  hookOptionsGenerator,
@@ -21,8 +20,37 @@ import {
21
20
  import { resolverReactQuery } from './resolvers/resolverReactQuery.ts'
22
21
  import type { PluginReactQuery } from './types.ts'
23
22
 
23
+ /**
24
+ * Canonical plugin name for `@kubb/plugin-react-query`. Used for driver lookups
25
+ * and cross-plugin dependency references.
26
+ */
24
27
  export const pluginReactQueryName = 'plugin-react-query' satisfies PluginReactQuery['name']
25
28
 
29
+ /**
30
+ * Generates one TanStack Query hook per OpenAPI operation for React. Queries
31
+ * become `useFooQuery`/`useFooSuspenseQuery`/`useFooInfiniteQuery`; mutations
32
+ * become `useFooMutation`. Each hook is fully typed: query keys, input
33
+ * variables, response data, and error shape all come from the spec.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * import { defineConfig } from 'kubb'
38
+ * import { pluginTs } from '@kubb/plugin-ts'
39
+ * import { pluginReactQuery } from '@kubb/plugin-react-query'
40
+ *
41
+ * export default defineConfig({
42
+ * input: { path: './petStore.yaml' },
43
+ * output: { path: './src/gen' },
44
+ * plugins: [
45
+ * pluginTs(),
46
+ * pluginReactQuery({
47
+ * output: { path: './hooks' },
48
+ * suspense: {},
49
+ * }),
50
+ * ],
51
+ * })
52
+ * ```
53
+ */
26
54
  export const pluginReactQuery = definePlugin<PluginReactQuery>((options) => {
27
55
  const {
28
56
  output = { path: 'hooks', barrelType: 'named' },
@@ -33,13 +61,12 @@ export const pluginReactQuery = definePlugin<PluginReactQuery>((options) => {
33
61
  parser = 'client',
34
62
  suspense = {},
35
63
  infinite = false,
36
- transformers = {},
37
64
  paramsType = 'inline',
38
65
  pathParamsType = paramsType === 'object' ? 'object' : options.pathParamsType || 'inline',
39
66
  mutation = {},
40
67
  query = {},
41
- mutationKey = MutationKey.getTransformer,
42
- queryKey = QueryKey.getTransformer,
68
+ mutationKey = mutationKeyTransformer,
69
+ queryKey = queryKeyTransformer,
43
70
  customOptions,
44
71
  paramsCasing,
45
72
  client,
@@ -61,7 +88,7 @@ export const pluginReactQuery = definePlugin<PluginReactQuery>((options) => {
61
88
  mutationGenerator,
62
89
  hookOptionsGenerator,
63
90
  customHookOptionsFileGenerator,
64
- ].filter(Boolean)
91
+ ].filter((generator): generator is NonNullable<typeof generator> => Boolean(generator))
65
92
 
66
93
  const groupConfig = group
67
94
  ? ({
@@ -75,19 +102,18 @@ export const pluginReactQuery = definePlugin<PluginReactQuery>((options) => {
75
102
  return `${camelCase(ctx.group)}Controller`
76
103
  },
77
104
  } satisfies Group)
78
- : undefined
105
+ : null
79
106
 
80
107
  return {
81
108
  name: pluginReactQueryName,
82
109
  options,
83
- dependencies: [pluginTsName, parser === 'zod' ? pluginZodName : undefined].filter(Boolean),
110
+ dependencies: [pluginTsName, parser === 'zod' ? pluginZodName : undefined].filter((dependency): dependency is string => Boolean(dependency)),
84
111
  hooks: {
85
112
  'kubb:plugin:setup'(ctx) {
86
113
  const resolver = userResolver ? { ...resolverReactQuery, ...userResolver } : resolverReactQuery
87
114
 
88
115
  ctx.setOptions({
89
116
  output,
90
- transformers,
91
117
  client: {
92
118
  bundle: client?.bundle,
93
119
  baseURL: client?.baseURL,
@@ -119,14 +145,14 @@ export const pluginReactQuery = definePlugin<PluginReactQuery>((options) => {
119
145
  ? {
120
146
  queryParam: 'id',
121
147
  initialPageParam: 0,
122
- cursorParam: undefined,
123
- nextParam: undefined,
124
- previousParam: undefined,
148
+ cursorParam: null,
149
+ nextParam: null,
150
+ previousParam: null,
125
151
  ...infinite,
126
152
  }
127
153
  : false,
128
154
  suspense,
129
- customOptions: customOptions ? { name: 'useCustomHookOptions', ...customOptions } : undefined,
155
+ customOptions: customOptions ? { name: 'useCustomHookOptions', ...customOptions } : null,
130
156
  parser,
131
157
  paramsType,
132
158
  pathParamsType,
@@ -154,11 +180,11 @@ export const pluginReactQuery = definePlugin<PluginReactQuery>((options) => {
154
180
 
155
181
  if (client?.bundle && !hasClientPlugin && !clientImportPath) {
156
182
  ctx.injectFile({
157
- baseName: 'fetch.ts',
158
- path: path.resolve(root, '.kubb/fetch.ts'),
183
+ baseName: 'client.ts',
184
+ path: path.resolve(root, '.kubb/client.ts'),
159
185
  sources: [
160
186
  ast.createSource({
161
- name: 'fetch',
187
+ name: 'client',
162
188
  nodes: [ast.createText(clientName === 'fetch' ? fetchClientSource : axiosClientSource)],
163
189
  isExportable: true,
164
190
  isIndexable: true,
@@ -2,21 +2,117 @@ import { camelCase } from '@internals/utils'
2
2
  import { defineResolver } from '@kubb/core'
3
3
  import type { PluginReactQuery } from '../types.ts'
4
4
 
5
+ function capitalize(name: string): string {
6
+ return `${name.charAt(0).toUpperCase()}${name.slice(1)}`
7
+ }
8
+
5
9
  /**
6
- * Naming convention resolver for React Query plugin.
10
+ * Default resolver used by `@kubb/plugin-react-query`. Decides the names and
11
+ * file paths for every generated TanStack Query hook (`useFooQuery`,
12
+ * `useFooMutation`, `useFooInfiniteQuery`, ...) and its companion helpers
13
+ * (`fooQueryKey`, `fooQueryOptions`).
14
+ *
15
+ * Functions and files use camelCase; hooks get the `use` prefix; suspense and
16
+ * infinite variants are suffixed with `Suspense`/`Infinite`.
7
17
  *
8
- * Provides default naming helpers using camelCase for functions and file paths.
18
+ * @example Resolve hook and helper names
19
+ * ```ts
20
+ * import { resolverReactQuery } from '@kubb/plugin-react-query'
9
21
  *
10
- * @example
11
- * `resolverReactQuery.default('list pets', 'function') // 'listPets'`
22
+ * resolverReactQuery.resolveQueryName(operationNode) // 'useGetPetById'
23
+ * resolverReactQuery.resolveMutationName(operationNode) // 'useUpdatePet'
24
+ * resolverReactQuery.resolveQueryKeyName(operationNode) // 'getPetByIdQueryKey'
25
+ * resolverReactQuery.resolveQueryOptionsName(operationNode) // 'getPetByIdQueryOptions'
26
+ * ```
12
27
  */
13
- export const resolverReactQuery = defineResolver<PluginReactQuery>((ctx) => ({
28
+ export const resolverReactQuery = defineResolver<PluginReactQuery>(() => ({
14
29
  name: 'default',
15
30
  pluginName: 'plugin-react-query',
16
31
  default(name, type) {
17
32
  return camelCase(name, { isFile: type === 'file' })
18
33
  },
19
34
  resolveName(name) {
20
- return ctx.default(name, 'function')
35
+ return this.default(name, 'function')
36
+ },
37
+ resolvePathName(name, type) {
38
+ return this.default(name, type)
39
+ },
40
+ resolveQueryName(node) {
41
+ return `use${capitalize(this.resolveName(node.operationId))}`
42
+ },
43
+ resolveSuspenseQueryName(node) {
44
+ return `use${capitalize(this.resolveName(node.operationId))}Suspense`
45
+ },
46
+ resolveInfiniteQueryName(node) {
47
+ return `use${capitalize(this.resolveName(node.operationId))}Infinite`
48
+ },
49
+ resolveSuspenseInfiniteQueryName(node) {
50
+ return `use${capitalize(this.resolveName(node.operationId))}SuspenseInfinite`
51
+ },
52
+ resolveMutationName(node) {
53
+ return `use${capitalize(this.resolveName(node.operationId))}`
54
+ },
55
+ resolveQueryOptionsName(node) {
56
+ return `${this.resolveName(node.operationId)}QueryOptions`
57
+ },
58
+ resolveSuspenseQueryOptionsName(node) {
59
+ return `${this.resolveName(node.operationId)}SuspenseQueryOptions`
60
+ },
61
+ resolveInfiniteQueryOptionsName(node) {
62
+ return `${this.resolveName(node.operationId)}InfiniteQueryOptions`
63
+ },
64
+ resolveSuspenseInfiniteQueryOptionsName(node) {
65
+ return `${this.resolveName(node.operationId)}SuspenseInfiniteQueryOptions`
66
+ },
67
+ resolveMutationOptionsName(node) {
68
+ return `${this.resolveName(node.operationId)}MutationOptions`
69
+ },
70
+ resolveQueryKeyName(node) {
71
+ return `${this.resolveName(node.operationId)}QueryKey`
72
+ },
73
+ resolveSuspenseQueryKeyName(node) {
74
+ return `${this.resolveName(node.operationId)}SuspenseQueryKey`
75
+ },
76
+ resolveInfiniteQueryKeyName(node) {
77
+ return `${this.resolveName(node.operationId)}InfiniteQueryKey`
78
+ },
79
+ resolveSuspenseInfiniteQueryKeyName(node) {
80
+ return `${this.resolveName(node.operationId)}SuspenseInfiniteQueryKey`
81
+ },
82
+ resolveMutationKeyName(node) {
83
+ return `${this.resolveName(node.operationId)}MutationKey`
84
+ },
85
+ resolveQueryKeyTypeName(node) {
86
+ return `${capitalize(this.resolveName(node.operationId))}QueryKey`
87
+ },
88
+ resolveSuspenseQueryKeyTypeName(node) {
89
+ return `${capitalize(this.resolveName(node.operationId))}SuspenseQueryKey`
90
+ },
91
+ resolveInfiniteQueryKeyTypeName(node) {
92
+ return `${capitalize(this.resolveName(node.operationId))}InfiniteQueryKey`
93
+ },
94
+ resolveSuspenseInfiniteQueryKeyTypeName(node) {
95
+ return `${capitalize(this.resolveName(node.operationId))}SuspenseInfiniteQueryKey`
96
+ },
97
+ resolveMutationTypeName(node) {
98
+ return capitalize(this.resolveName(node.operationId))
99
+ },
100
+ resolveClientName(node) {
101
+ return this.resolveName(node.operationId)
102
+ },
103
+ resolveSuspenseClientName(node) {
104
+ return `${this.resolveName(node.operationId)}Suspense`
105
+ },
106
+ resolveInfiniteClientName(node) {
107
+ return `${this.resolveName(node.operationId)}Infinite`
108
+ },
109
+ resolveSuspenseInfiniteClientName(node) {
110
+ return `${this.resolveName(node.operationId)}SuspenseInfinite`
111
+ },
112
+ resolveHookOptionsName() {
113
+ return 'HookOptions'
114
+ },
115
+ resolveCustomHookOptionsName() {
116
+ return 'getCustomHookOptions'
21
117
  },
22
118
  }))