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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +38 -91
  2. package/dist/{components-dAKJEn9b.cjs → components-DQAYLQW0.cjs} +409 -279
  3. package/dist/components-DQAYLQW0.cjs.map +1 -0
  4. package/dist/{components-DTGLu4UV.js → components-IArDg-DO.js} +379 -279
  5. package/dist/components-IArDg-DO.js.map +1 -0
  6. package/dist/components.cjs +1 -1
  7. package/dist/components.d.ts +5 -77
  8. package/dist/components.js +1 -1
  9. package/dist/{generators-C_fbcjpG.js → generators-B86BJkmW.js} +346 -419
  10. package/dist/generators-B86BJkmW.js.map +1 -0
  11. package/dist/{generators-CWEQsdO9.cjs → generators-BqGaMUH6.cjs} +344 -417
  12. package/dist/generators-BqGaMUH6.cjs.map +1 -0
  13. package/dist/generators.cjs +1 -1
  14. package/dist/generators.d.ts +49 -10
  15. package/dist/generators.js +1 -1
  16. package/dist/index.cjs +176 -26
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.ts +32 -4
  19. package/dist/index.js +177 -27
  20. package/dist/index.js.map +1 -1
  21. package/dist/types-Dh4HNR9K.d.ts +400 -0
  22. package/extension.yaml +910 -364
  23. package/package.json +15 -17
  24. package/src/components/InfiniteQuery.tsx +24 -13
  25. package/src/components/InfiniteQueryOptions.tsx +37 -55
  26. package/src/components/Mutation.tsx +35 -15
  27. package/src/components/MutationOptions.tsx +14 -13
  28. package/src/components/Query.tsx +14 -10
  29. package/src/components/QueryOptions.tsx +17 -34
  30. package/src/components/SuspenseInfiniteQuery.tsx +19 -13
  31. package/src/components/SuspenseInfiniteQueryOptions.tsx +28 -52
  32. package/src/components/SuspenseQuery.tsx +9 -10
  33. package/src/generators/customHookOptionsFileGenerator.tsx +18 -14
  34. package/src/generators/hookOptionsGenerator.tsx +44 -51
  35. package/src/generators/infiniteQueryGenerator.tsx +57 -78
  36. package/src/generators/mutationGenerator.tsx +53 -64
  37. package/src/generators/queryGenerator.tsx +54 -63
  38. package/src/generators/suspenseInfiniteQueryGenerator.tsx +52 -65
  39. package/src/generators/suspenseQueryGenerator.tsx +56 -76
  40. package/src/plugin.ts +45 -31
  41. package/src/resolvers/resolverReactQuery.ts +102 -6
  42. package/src/types.ts +199 -61
  43. package/src/utils.ts +10 -33
  44. package/dist/components-DTGLu4UV.js.map +0 -1
  45. package/dist/components-dAKJEn9b.cjs.map +0 -1
  46. package/dist/generators-CWEQsdO9.cjs.map +0 -1
  47. package/dist/generators-C_fbcjpG.js.map +0 -1
  48. package/dist/types-DfaFRSBf.d.ts +0 -284
  49. /package/dist/{chunk--u3MIqq1.js → chunk-C0LytTxp.js} +0 -0
@@ -1,11 +1,11 @@
1
+ import { getOperationParameters } from '@internals/shared'
1
2
  import { ast } from '@kubb/core'
2
3
  import type { ResolverTs } from '@kubb/plugin-ts'
3
4
  import { functionPrinter } from '@kubb/plugin-ts'
4
5
  import { File, Function } from '@kubb/renderer-jsx'
5
6
  import type { KubbReactNode } from '@kubb/renderer-jsx/types'
6
7
  import type { Infinite, PluginReactQuery } from '../types.ts'
7
- import { getComments, resolveErrorNames } from '../utils.ts'
8
- import { QueryKey } from './QueryKey.tsx'
8
+ import { buildQueryKeyParams, getComments, resolveErrorNames, resolveSuccessNames } from '../utils.ts'
9
9
  import { getQueryOptionsParams } from './QueryOptions.tsx'
10
10
 
11
11
  type Props = {
@@ -27,7 +27,7 @@ type Props = {
27
27
  const declarationPrinter = functionPrinter({ mode: 'declaration' })
28
28
  const callPrinter = functionPrinter({ mode: 'call' })
29
29
 
30
- function getParams(
30
+ function buildSuspenseInfiniteQueryParamsNode(
31
31
  node: ast.OperationNode,
32
32
  options: {
33
33
  paramsType: PluginReactQuery['resolvedOptions']['paramsType']
@@ -39,7 +39,7 @@ function getParams(
39
39
  },
40
40
  ): ast.FunctionParametersNode {
41
41
  const { paramsType, paramsCasing, pathParamsType, resolver, pageParamGeneric } = options
42
- const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : undefined
42
+ const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null
43
43
 
44
44
  const optionsParam = ast.createFunctionParameter({
45
45
  name: 'options',
@@ -77,7 +77,8 @@ export function SuspenseInfiniteQuery({
77
77
  initialPageParam,
78
78
  queryParam,
79
79
  }: Props): KubbReactNode {
80
- const responseName = tsResolver.resolveResponseName(node)
80
+ const successNames = resolveSuccessNames(node, tsResolver)
81
+ const responseName = successNames.length > 0 ? successNames.join(' | ') : tsResolver.resolveResponseName(node)
81
82
  const errorNames = resolveErrorNames(node, tsResolver)
82
83
 
83
84
  const responseType = dataReturnType === 'data' ? responseName : `ResponseConfig<${responseName}>`
@@ -98,17 +99,17 @@ export function SuspenseInfiniteQuery({
98
99
  ? 'boolean'
99
100
  : 'unknown'
100
101
 
101
- const rawQueryParams = node.parameters.filter((p) => p.in === 'query')
102
+ const rawQueryParams = getOperationParameters(node).query
102
103
  const queryParamsTypeName =
103
104
  rawQueryParams.length > 0
104
105
  ? (() => {
105
106
  const groupName = tsResolver.resolveQueryParamsName(node, rawQueryParams[0]!)
106
107
  const individualName = tsResolver.resolveParamName(node, rawQueryParams[0]!)
107
- return groupName !== individualName ? groupName : undefined
108
+ return groupName !== individualName ? groupName : null
108
109
  })()
109
- : undefined
110
+ : null
110
111
 
111
- const queryParamType = queryParam && queryParamsTypeName ? `${queryParamsTypeName}['${queryParam}']` : undefined
112
+ const queryParamType = queryParam && queryParamsTypeName ? `${queryParamsTypeName}['${queryParam}']` : null
112
113
  const pageParamType = queryParamType ? (isInitialPageParamDefined ? `NonNullable<${queryParamType}>` : queryParamType) : fallbackPageParamType
113
114
 
114
115
  const returnType = 'UseSuspenseInfiniteQueryResult<TData, TError> & { queryKey: TQueryKey }'
@@ -120,13 +121,20 @@ export function SuspenseInfiniteQuery({
120
121
  `TPageParam = ${pageParamType}`,
121
122
  ]
122
123
 
123
- const queryKeyParamsNode = QueryKey.getParams(node, { pathParamsType, paramsCasing, resolver: tsResolver })
124
+ const queryKeyParamsNode = buildQueryKeyParams(node, { pathParamsType, paramsCasing, resolver: tsResolver })
124
125
  const queryKeyParamsCall = callPrinter.print(queryKeyParamsNode) ?? ''
125
126
 
126
127
  const queryOptionsParamsNode = getQueryOptionsParams(node, { paramsType, paramsCasing, pathParamsType, resolver: tsResolver })
127
128
  const queryOptionsParamsCall = callPrinter.print(queryOptionsParamsNode) ?? ''
128
129
 
129
- const paramsNode = getParams(node, { paramsType, paramsCasing, pathParamsType, dataReturnType, resolver: tsResolver, pageParamGeneric: 'TPageParam' })
130
+ const paramsNode = buildSuspenseInfiniteQueryParamsNode(node, {
131
+ paramsType,
132
+ paramsCasing,
133
+ pathParamsType,
134
+ dataReturnType,
135
+ resolver: tsResolver,
136
+ pageParamGeneric: 'TPageParam',
137
+ })
130
138
  const paramsSignature = declarationPrinter.print(paramsNode) ?? ''
131
139
 
132
140
  return (
@@ -152,5 +160,3 @@ export function SuspenseInfiniteQuery({
152
160
  </File.Source>
153
161
  )
154
162
  }
155
-
156
- SuspenseInfiniteQuery.getParams = getParams
@@ -1,3 +1,4 @@
1
+ import { getOperationParameters } from '@internals/shared'
1
2
  import { getNestedAccessor } from '@internals/utils'
2
3
  import type { ast } from '@kubb/core'
3
4
  import type { ResolverTs } from '@kubb/plugin-ts'
@@ -5,9 +6,8 @@ import { functionPrinter } from '@kubb/plugin-ts'
5
6
  import { File, Function } from '@kubb/renderer-jsx'
6
7
  import type { KubbReactNode } from '@kubb/renderer-jsx/types'
7
8
  import type { Infinite, PluginReactQuery } from '../types.ts'
8
- import { resolveErrorNames } from '../utils.ts'
9
- import { QueryKey } from './QueryKey.tsx'
10
- import { buildEnabledCheck, getQueryOptionsParams } from './QueryOptions.tsx'
9
+ import { buildQueryKeyParams, resolveErrorNames, resolveSuccessNames } from '../utils.ts'
10
+ import { getQueryOptionsParams } from './QueryOptions.tsx'
11
11
 
12
12
  type Props = {
13
13
  name: string
@@ -45,7 +45,8 @@ export function SuspenseInfiniteQueryOptions({
45
45
  queryParam,
46
46
  queryKeyName,
47
47
  }: Props): KubbReactNode {
48
- const responseName = tsResolver.resolveResponseName(node)
48
+ const successNames = resolveSuccessNames(node, tsResolver)
49
+ const responseName = successNames.length > 0 ? successNames.join(' | ') : tsResolver.resolveResponseName(node)
49
50
  const queryFnDataType = dataReturnType === 'data' ? responseName : `ResponseConfig<${responseName}>`
50
51
  const errorNames = resolveErrorNames(node, tsResolver)
51
52
  const errorType = `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(' | ') : 'Error'}>`
@@ -65,17 +66,17 @@ export function SuspenseInfiniteQueryOptions({
65
66
  ? 'boolean'
66
67
  : 'unknown'
67
68
 
68
- const rawQueryParams = node.parameters.filter((p) => p.in === 'query')
69
+ const rawQueryParams = getOperationParameters(node).query
69
70
  const queryParamsTypeName =
70
71
  rawQueryParams.length > 0
71
72
  ? (() => {
72
73
  const groupName = tsResolver.resolveQueryParamsName(node, rawQueryParams[0]!)
73
74
  const individualName = tsResolver.resolveParamName(node, rawQueryParams[0]!)
74
- return groupName !== individualName ? groupName : undefined
75
+ return groupName !== individualName ? groupName : null
75
76
  })()
76
- : undefined
77
+ : null
77
78
 
78
- const queryParamType = queryParam && queryParamsTypeName ? `${queryParamsTypeName}['${queryParam}']` : undefined
79
+ const queryParamType = queryParam && queryParamsTypeName ? `${queryParamsTypeName}['${queryParam}']` : null
79
80
  const pageParamType = queryParamType ? (isInitialPageParamDefined ? `NonNullable<${queryParamType}>` : queryParamType) : fallbackPageParamType
80
81
 
81
82
  const paramsNode = getQueryOptionsParams(node, { paramsType, paramsCasing, pathParamsType, resolver: tsResolver })
@@ -83,43 +84,30 @@ export function SuspenseInfiniteQueryOptions({
83
84
  const rawParamsCall = callPrinter.print(paramsNode) ?? ''
84
85
  const clientCallStr = rawParamsCall.replace(/\bconfig\b(?=[^,]*$)/, '{ ...config, signal: config.signal ?? signal }')
85
86
 
86
- const queryKeyParamsNode = QueryKey.getParams(node, { pathParamsType, paramsCasing, resolver: tsResolver })
87
+ const queryKeyParamsNode = buildQueryKeyParams(node, { pathParamsType, paramsCasing, resolver: tsResolver })
87
88
  const queryKeyParamsCall = callPrinter.print(queryKeyParamsNode) ?? ''
88
89
 
89
- const enabledSource = buildEnabledCheck(queryKeyParamsNode)
90
- const enabledText = enabledSource ? `enabled: !!(${enabledSource}),` : ''
90
+ const hasNewParams = nextParam != null || previousParam != null
91
91
 
92
- const hasNewParams = nextParam !== undefined || previousParam !== undefined
93
-
94
- let getNextPageParamExpr: string | undefined
95
- let getPreviousPageParamExpr: string | undefined
96
-
97
- if (hasNewParams) {
98
- if (nextParam) {
99
- const accessor = getNestedAccessor(nextParam, 'lastPage')
100
- if (accessor) {
101
- getNextPageParamExpr = `getNextPageParam: (lastPage) => ${accessor}`
102
- }
103
- }
104
- if (previousParam) {
105
- const accessor = getNestedAccessor(previousParam, 'firstPage')
106
- if (accessor) {
107
- getPreviousPageParamExpr = `getPreviousPageParam: (firstPage) => ${accessor}`
108
- }
92
+ const [getNextPageParamExpr, getPreviousPageParamExpr] = (() => {
93
+ if (hasNewParams) {
94
+ const nextAccessor = nextParam ? getNestedAccessor(nextParam, 'lastPage') : null
95
+ const prevAccessor = previousParam ? getNestedAccessor(previousParam, 'firstPage') : null
96
+ return [
97
+ nextAccessor ? `getNextPageParam: (lastPage) => ${nextAccessor}` : null,
98
+ prevAccessor ? `getPreviousPageParam: (firstPage) => ${prevAccessor}` : null,
99
+ ] as const
109
100
  }
110
- } else if (cursorParam) {
111
- getNextPageParamExpr = `getNextPageParam: (lastPage) => lastPage['${cursorParam}']`
112
- getPreviousPageParamExpr = `getPreviousPageParam: (firstPage) => firstPage['${cursorParam}']`
113
- } else {
114
- if (dataReturnType === 'full') {
115
- getNextPageParamExpr =
116
- 'getNextPageParam: (lastPage, _allPages, lastPageParam) => Array.isArray(lastPage.data) && lastPage.data.length === 0 ? undefined : lastPageParam + 1'
117
- } else {
118
- getNextPageParamExpr =
119
- 'getNextPageParam: (lastPage, _allPages, lastPageParam) => Array.isArray(lastPage) && lastPage.length === 0 ? undefined : lastPageParam + 1'
101
+ if (cursorParam) {
102
+ return [`getNextPageParam: (lastPage) => lastPage['${cursorParam}']`, `getPreviousPageParam: (firstPage) => firstPage['${cursorParam}']`] as const
120
103
  }
121
- getPreviousPageParamExpr = 'getPreviousPageParam: (_firstPage, _allPages, firstPageParam) => firstPageParam <= 1 ? undefined : firstPageParam - 1'
122
- }
104
+ return [
105
+ dataReturnType === 'full'
106
+ ? 'getNextPageParam: (lastPage, _allPages, lastPageParam) => Array.isArray(lastPage.data) && lastPage.data.length === 0 ? undefined : lastPageParam + 1'
107
+ : 'getNextPageParam: (lastPage, _allPages, lastPageParam) => Array.isArray(lastPage) && lastPage.length === 0 ? undefined : lastPageParam + 1',
108
+ 'getPreviousPageParam: (_firstPage, _allPages, firstPageParam) => firstPageParam <= 1 ? undefined : firstPageParam - 1',
109
+ ] as const
110
+ })()
123
111
 
124
112
  const queryOptionsArr = [
125
113
  `initialPageParam: ${typeof initialPageParam === 'string' ? JSON.stringify(initialPageParam) : initialPageParam}`,
@@ -143,7 +131,6 @@ export function SuspenseInfiniteQueryOptions({
143
131
  {`
144
132
  const queryKey = ${queryKeyName}(${queryKeyParamsCall})
145
133
  return infiniteQueryOptions<${queryFnDataType}, ${errorType}, InfiniteData<${queryFnDataType}>, typeof queryKey, ${pageParamType}>({
146
- ${enabledText}
147
134
  queryKey,
148
135
  queryFn: async ({ signal, pageParam }) => {
149
136
  ${infiniteOverrideParams}
@@ -163,7 +150,6 @@ export function SuspenseInfiniteQueryOptions({
163
150
  {`
164
151
  const queryKey = ${queryKeyName}(${queryKeyParamsCall})
165
152
  return infiniteQueryOptions<${queryFnDataType}, ${errorType}, InfiniteData<${queryFnDataType}>, typeof queryKey, ${pageParamType}>({
166
- ${enabledText}
167
153
  queryKey,
168
154
  queryFn: async ({ signal }) => {
169
155
  return ${clientName}(${clientCallStr})
@@ -175,13 +161,3 @@ export function SuspenseInfiniteQueryOptions({
175
161
  </File.Source>
176
162
  )
177
163
  }
178
-
179
- SuspenseInfiniteQueryOptions.getParams = (
180
- node: ast.OperationNode,
181
- options: {
182
- paramsType: PluginReactQuery['resolvedOptions']['paramsType']
183
- paramsCasing: PluginReactQuery['resolvedOptions']['paramsCasing']
184
- pathParamsType: PluginReactQuery['resolvedOptions']['pathParamsType']
185
- resolver: ResolverTs
186
- },
187
- ) => getQueryOptionsParams(node, options)
@@ -4,8 +4,7 @@ import { functionPrinter } from '@kubb/plugin-ts'
4
4
  import { File, Function } from '@kubb/renderer-jsx'
5
5
  import type { KubbReactNode } from '@kubb/renderer-jsx/types'
6
6
  import type { PluginReactQuery } from '../types.ts'
7
- import { getComments, resolveErrorNames } from '../utils.ts'
8
- import { QueryKey } from './QueryKey.tsx'
7
+ import { buildQueryKeyParams, getComments, resolveErrorNames, resolveSuccessNames } from '../utils.ts'
9
8
  import { getQueryOptionsParams } from './QueryOptions.tsx'
10
9
 
11
10
  type Props = {
@@ -25,7 +24,7 @@ type Props = {
25
24
  const declarationPrinter = functionPrinter({ mode: 'declaration' })
26
25
  const callPrinter = functionPrinter({ mode: 'call' })
27
26
 
28
- function getParams(
27
+ function buildSuspenseQueryParamsNode(
29
28
  node: ast.OperationNode,
30
29
  options: {
31
30
  paramsType: PluginReactQuery['resolvedOptions']['paramsType']
@@ -36,8 +35,9 @@ function getParams(
36
35
  },
37
36
  ): ast.FunctionParametersNode {
38
37
  const { paramsType, paramsCasing, pathParamsType, dataReturnType, resolver } = options
39
- const responseName = resolver.resolveResponseName(node)
40
- const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : undefined
38
+ const successNames = resolveSuccessNames(node, resolver)
39
+ const responseName = successNames.length > 0 ? successNames.join(' | ') : resolver.resolveResponseName(node)
40
+ const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null
41
41
  const errorNames = resolveErrorNames(node, resolver)
42
42
 
43
43
  const TData = dataReturnType === 'data' ? responseName : `ResponseConfig<${responseName}>`
@@ -77,7 +77,8 @@ export function SuspenseQuery({
77
77
  tsResolver,
78
78
  customOptions,
79
79
  }: Props): KubbReactNode {
80
- const responseName = tsResolver.resolveResponseName(node)
80
+ const successNames = resolveSuccessNames(node, tsResolver)
81
+ const responseName = successNames.length > 0 ? successNames.join(' | ') : tsResolver.resolveResponseName(node)
81
82
  const errorNames = resolveErrorNames(node, tsResolver)
82
83
 
83
84
  const TData = dataReturnType === 'data' ? responseName : `ResponseConfig<${responseName}>`
@@ -85,13 +86,13 @@ export function SuspenseQuery({
85
86
  const returnType = `UseSuspenseQueryResult<${'TData'}, ${TError}> & { queryKey: TQueryKey }`
86
87
  const generics = [`TData = ${TData}`, `TQueryKey extends QueryKey = ${queryKeyTypeName}`]
87
88
 
88
- const queryKeyParamsNode = QueryKey.getParams(node, { pathParamsType, paramsCasing, resolver: tsResolver })
89
+ const queryKeyParamsNode = buildQueryKeyParams(node, { pathParamsType, paramsCasing, resolver: tsResolver })
89
90
  const queryKeyParamsCall = callPrinter.print(queryKeyParamsNode) ?? ''
90
91
 
91
92
  const queryOptionsParamsNode = getQueryOptionsParams(node, { paramsType, paramsCasing, pathParamsType, resolver: tsResolver })
92
93
  const queryOptionsParamsCall = callPrinter.print(queryOptionsParamsNode) ?? ''
93
94
 
94
- const paramsNode = getParams(node, { paramsType, paramsCasing, pathParamsType, dataReturnType, resolver: tsResolver })
95
+ const paramsNode = buildSuspenseQueryParamsNode(node, { paramsType, paramsCasing, pathParamsType, dataReturnType, resolver: tsResolver })
95
96
  const paramsSignature = declarationPrinter.print(paramsNode) ?? ''
96
97
 
97
98
  return (
@@ -117,5 +118,3 @@ export function SuspenseQuery({
117
118
  </File.Source>
118
119
  )
119
120
  }
120
-
121
- SuspenseQuery.getParams = getParams
@@ -2,34 +2,38 @@ import fs from 'node:fs'
2
2
  import path from 'node:path'
3
3
 
4
4
  import { defineGenerator } from '@kubb/core'
5
- import { File, Function, jsxRenderer } from '@kubb/renderer-jsx'
5
+ import { File, Function, jsxRendererSync } from '@kubb/renderer-jsx'
6
6
  import type { PluginReactQuery } from '../types'
7
- import { transformName } from '../utils.ts'
8
7
 
8
+ /**
9
+ * Scaffolds the user-editable `useCustomHookOptions` file when
10
+ * `pluginReactQuery({ customOptions: { ... } })` is configured. The file is
11
+ * only created when it does not already exist, so user edits persist across
12
+ * regeneration.
13
+ */
9
14
  export const customHookOptionsFileGenerator = defineGenerator<PluginReactQuery>({
10
15
  name: 'react-query-custom-hook-options-file',
11
- renderer: jsxRenderer,
16
+ renderer: jsxRendererSync,
12
17
  operations(nodes, ctx) {
13
18
  const { resolver, config, root } = ctx
14
- const { output, customOptions, query, group, transformers } = ctx.options
19
+ const { output, customOptions, query, group } = ctx.options
15
20
 
16
21
  if (!customOptions) return null
17
22
 
18
23
  const override = output.override ?? config.output.override ?? false
19
24
  const { importPath, name } = customOptions
25
+ const hookOptionsName = resolver.resolveHookOptionsName()
26
+ const customHookOptionsName = resolver.resolveCustomHookOptionsName()
20
27
 
21
28
  const reactQueryImportPath = query ? query.importPath : '@tanstack/react-query'
22
29
 
23
- const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)
24
-
25
30
  let hookFilePath: string
26
31
  const firstNode = nodes[0]
27
32
  if (firstNode) {
28
- const baseName = resolver.resolveName(firstNode.operationId)
29
- const hookName = transformName(`use${capitalize(baseName)}`, 'function', transformers)
33
+ const hookName = resolver.resolveQueryName(firstNode)
30
34
  const hookFile = resolver.resolveFile(
31
35
  { name: hookName, extname: '.ts', tag: firstNode.tags[0] ?? 'default', path: firstNode.path },
32
- { root, output, group },
36
+ { root, output, group: group ?? undefined },
33
37
  )
34
38
  hookFilePath = hookFile.path
35
39
  } else {
@@ -55,9 +59,9 @@ export const customHookOptionsFileGenerator = defineGenerator<PluginReactQuery>(
55
59
  <File baseName={file.baseName} path={file.path}>
56
60
  <File.Import name={['QueryClient']} path={reactQueryImportPath} isTypeOnly />
57
61
  <File.Import name={['useQueryClient']} path={reactQueryImportPath} />
58
- <File.Import name={['HookOptions']} root={file.path} path={path.resolve(root, './index.ts')} />
62
+ <File.Import name={[hookOptionsName]} root={file.path} path={path.resolve(root, './index.ts')} />
59
63
  <File.Source name={file.name} isExportable isIndexable>
60
- <Function name="getCustomHookOptions" params="{ queryClient }: { queryClient: QueryClient }" returnType="Partial<HookOptions>">
64
+ <Function name={customHookOptionsName} params="{ queryClient }: { queryClient: QueryClient }" returnType={`Partial<${hookOptionsName}>`}>
61
65
  {`return {
62
66
  // TODO: Define custom hook options here
63
67
  // Example:
@@ -70,13 +74,13 @@ export const customHookOptionsFileGenerator = defineGenerator<PluginReactQuery>(
70
74
  </Function>
71
75
  <Function
72
76
  name={name}
73
- generics="T extends keyof HookOptions"
77
+ generics={`T extends keyof ${hookOptionsName}`}
74
78
  params="{ hookName, operationId }: { hookName: T, operationId: string }"
75
- returnType="HookOptions[T]"
79
+ returnType={`${hookOptionsName}[T]`}
76
80
  export
77
81
  >
78
82
  {`const queryClient = useQueryClient()
79
- const customOptions = getCustomHookOptions({ queryClient })
83
+ const customOptions = ${customHookOptionsName}({ queryClient })
80
84
  return customOptions[hookName] ?? {}`}
81
85
  </Function>
82
86
  </File.Source>
@@ -1,71 +1,71 @@
1
- import { defineGenerator } from '@kubb/core'
2
- import { File, jsxRenderer, Type } from '@kubb/renderer-jsx'
1
+ import { getOperationParameters, operationFileEntry } from '@internals/shared'
2
+ import { ast, defineGenerator } from '@kubb/core'
3
+ import { File, jsxRendererSync, Type } from '@kubb/renderer-jsx'
3
4
  import type { KubbReactNode } from '@kubb/renderer-jsx/types'
4
- import { difference } from 'remeda'
5
5
  import type { PluginReactQuery } from '../types'
6
- import { resolveOperationOverrides, transformName } from '../utils.ts'
6
+ import { resolveOperationOverrides } from '../utils.ts'
7
7
 
8
8
  type QueryOption = PluginReactQuery['resolvedOptions']['query']
9
9
  type MutationOption = PluginReactQuery['resolvedOptions']['mutation']
10
10
 
11
+ /**
12
+ * Emits the `HookOptions` type used by `customOptions`. Enabled when
13
+ * `pluginReactQuery({ customOptions: { ... } })`. The generated type lists
14
+ * every hook keyed by name so user-supplied options stay in sync with the
15
+ * generated hooks at compile time.
16
+ */
11
17
  export const hookOptionsGenerator = defineGenerator<PluginReactQuery>({
12
18
  name: 'react-query-hook-options',
13
- renderer: jsxRenderer,
19
+ renderer: jsxRendererSync,
14
20
  operations(nodes, ctx) {
15
- const { resolver, config, root, adapter } = ctx
16
- const { output, customOptions, query, mutation, suspense, infinite, group, transformers, override } = ctx.options
21
+ const { resolver, config, root } = ctx
22
+ const { output, customOptions, query, mutation, suspense, infinite, group, override } = ctx.options
17
23
 
18
24
  if (!customOptions) return null
19
25
 
20
- const resolvedFile = resolver.resolveFile({ name: 'HookOptions', extname: '.ts' }, { root, output, group })
26
+ const name = resolver.resolveHookOptionsName()
27
+ const resolvedFile = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group: group ?? undefined })
21
28
  const hookOptionsFile = {
22
29
  ...resolvedFile,
23
- baseName: 'HookOptions.ts' as const,
24
- path: resolvedFile.path.replace(/hookOptions\.ts$/, 'HookOptions.ts'),
30
+ baseName: `${name}.ts` as const,
31
+ path: resolvedFile.path.replace(/[^/\\]+\.ts$/, `${name}.ts`),
25
32
  }
26
33
 
27
- const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)
28
-
29
- const imports: KubbReactNode[] = []
34
+ const imports: Array<KubbReactNode> = []
30
35
  const hookOptions: Record<string, string> = {}
31
36
 
32
37
  for (const node of nodes) {
33
- const baseName = resolver.resolveName(node.operationId)
38
+ if (!ast.isHttpOperationNode(node)) continue
34
39
  const opOverrides = resolveOperationOverrides(node, override)
35
40
  const nodeQuery: QueryOption = 'query' in opOverrides ? (opOverrides.query as QueryOption) : query
36
41
  const nodeMutation: MutationOption = 'mutation' in opOverrides ? (opOverrides.mutation as MutationOption) : mutation
37
42
  const nodeInfinite = 'infinite' in opOverrides ? opOverrides.infinite : infinite
38
- const nodeInfiniteOptions = nodeInfinite && typeof nodeInfinite === 'object' ? nodeInfinite : undefined
43
+ const nodeInfiniteOptions = nodeInfinite && typeof nodeInfinite === 'object' ? nodeInfinite : null
39
44
 
40
45
  // query: false means "still a query but skip the useQuery hook"
41
46
  const isQueryOp =
42
47
  nodeQuery === false
43
48
  ? !!query && query.methods.some((m) => node.method.toLowerCase() === m.toLowerCase())
44
49
  : !!nodeQuery && nodeQuery.methods.some((m) => node.method.toLowerCase() === m.toLowerCase())
50
+ const nodeQueryMethods = new Set(nodeQuery ? nodeQuery.methods : [])
45
51
  const isMutationOp =
46
52
  nodeMutation !== false &&
47
53
  !isQueryOp &&
48
- difference(nodeMutation ? nodeMutation.methods : [], nodeQuery ? nodeQuery.methods : []).some((m) => node.method.toLowerCase() === m.toLowerCase())
54
+ (nodeMutation ? nodeMutation.methods : []).some((m) => !nodeQueryMethods.has(m) && node.method.toLowerCase() === m.toLowerCase())
49
55
  const isSuspenseOp = !!suspense
50
56
  const isInfiniteOp = !!nodeInfiniteOptions
51
57
 
52
58
  if (isQueryOp) {
53
- const queryOptionsName = transformName(`${baseName}QueryOptions`, 'function', transformers)
54
- const queryHookName = transformName(`use${capitalize(baseName)}`, 'function', transformers)
55
- const queryHookFile = resolver.resolveFile(
56
- { name: queryHookName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
57
- { root, output, group },
58
- )
59
+ const queryOptionsName = resolver.resolveQueryOptionsName(node)
60
+ const queryHookName = resolver.resolveQueryName(node)
61
+ const queryHookFile = resolver.resolveFile(operationFileEntry(node, queryHookName), { root, output, group: group ?? undefined })
59
62
  imports.push(<File.Import name={[queryOptionsName]} root={hookOptionsFile.path} path={queryHookFile.path} />)
60
63
  hookOptions[queryHookName] = `Partial<ReturnType<typeof ${queryOptionsName}>>`
61
64
 
62
65
  if (isSuspenseOp) {
63
- const suspenseOptionsName = transformName(`${baseName}SuspenseQueryOptions`, 'function', transformers)
64
- const suspenseHookName = transformName(`use${capitalize(baseName)}Suspense`, 'function', transformers)
65
- const suspenseHookFile = resolver.resolveFile(
66
- { name: suspenseHookName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
67
- { root, output, group },
68
- )
66
+ const suspenseOptionsName = resolver.resolveSuspenseQueryOptionsName(node)
67
+ const suspenseHookName = resolver.resolveSuspenseQueryName(node)
68
+ const suspenseHookFile = resolver.resolveFile(operationFileEntry(node, suspenseHookName), { root, output, group: group ?? undefined })
69
69
  imports.push(<File.Import name={[suspenseOptionsName]} root={hookOptionsFile.path} path={suspenseHookFile.path} />)
70
70
  hookOptions[suspenseHookName] = `Partial<ReturnType<typeof ${suspenseOptionsName}>>`
71
71
  }
@@ -73,26 +73,24 @@ export const hookOptionsGenerator = defineGenerator<PluginReactQuery>({
73
73
  if (isInfiniteOp) {
74
74
  // Validate queryParam
75
75
  const normalizeKey = (key: string) => key.replace(/\?$/, '')
76
- const queryParamKeys = node.parameters.filter((p) => p.in === 'query').map((p) => p.name)
76
+ const queryParamKeys = getOperationParameters(node).query.map((p) => p.name)
77
77
  const hasQueryParam = nodeInfiniteOptions!.queryParam ? queryParamKeys.some((k) => normalizeKey(k) === nodeInfiniteOptions!.queryParam) : false
78
78
 
79
79
  if (hasQueryParam) {
80
- const infiniteOptionsName = transformName(`${baseName}InfiniteQueryOptions`, 'function', transformers)
81
- const infiniteHookName = transformName(`use${capitalize(baseName)}Infinite`, 'function', transformers)
82
- const infiniteHookFile = resolver.resolveFile(
83
- { name: infiniteHookName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
84
- { root, output, group },
85
- )
80
+ const infiniteOptionsName = resolver.resolveInfiniteQueryOptionsName(node)
81
+ const infiniteHookName = resolver.resolveInfiniteQueryName(node)
82
+ const infiniteHookFile = resolver.resolveFile(operationFileEntry(node, infiniteHookName), { root, output, group: group ?? undefined })
86
83
  imports.push(<File.Import name={[infiniteOptionsName]} root={hookOptionsFile.path} path={infiniteHookFile.path} />)
87
84
  hookOptions[infiniteHookName] = `Partial<ReturnType<typeof ${infiniteOptionsName}>>`
88
85
 
89
86
  if (isSuspenseOp) {
90
- const suspenseInfiniteOptionsName = transformName(`${baseName}SuspenseInfiniteQueryOptions`, 'function', transformers)
91
- const suspenseInfiniteHookName = transformName(`use${capitalize(baseName)}SuspenseInfinite`, 'function', transformers)
92
- const suspenseInfiniteHookFile = resolver.resolveFile(
93
- { name: suspenseInfiniteHookName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
94
- { root, output, group },
95
- )
87
+ const suspenseInfiniteOptionsName = resolver.resolveSuspenseInfiniteQueryOptionsName(node)
88
+ const suspenseInfiniteHookName = resolver.resolveSuspenseInfiniteQueryName(node)
89
+ const suspenseInfiniteHookFile = resolver.resolveFile(operationFileEntry(node, suspenseInfiniteHookName), {
90
+ root,
91
+ output,
92
+ group: group ?? undefined,
93
+ })
96
94
  imports.push(<File.Import name={[suspenseInfiniteOptionsName]} root={hookOptionsFile.path} path={suspenseInfiniteHookFile.path} />)
97
95
  hookOptions[suspenseInfiniteHookName] = `Partial<ReturnType<typeof ${suspenseInfiniteOptionsName}>>`
98
96
  }
@@ -101,26 +99,21 @@ export const hookOptionsGenerator = defineGenerator<PluginReactQuery>({
101
99
  }
102
100
 
103
101
  if (isMutationOp) {
104
- const mutationOptionsName = transformName(`${baseName}MutationOptions`, 'function', transformers)
105
- const mutationHookName = transformName(`use${capitalize(baseName)}`, 'function', transformers)
106
- const mutationHookFile = resolver.resolveFile(
107
- { name: mutationHookName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
108
- { root, output, group },
109
- )
102
+ const mutationOptionsName = resolver.resolveMutationOptionsName(node)
103
+ const mutationHookName = resolver.resolveMutationName(node)
104
+ const mutationHookFile = resolver.resolveFile(operationFileEntry(node, mutationHookName), { root, output, group: group ?? undefined })
110
105
  imports.push(<File.Import name={[mutationOptionsName]} root={hookOptionsFile.path} path={mutationHookFile.path} />)
111
106
  hookOptions[mutationHookName] = `Partial<ReturnType<typeof ${mutationOptionsName}>>`
112
107
  }
113
108
  }
114
109
 
115
- const name = 'HookOptions'
116
-
117
110
  return (
118
111
  <File
119
112
  baseName={hookOptionsFile.baseName}
120
113
  path={hookOptionsFile.path}
121
114
  meta={hookOptionsFile.meta}
122
- banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
123
- footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
115
+ banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: hookOptionsFile.path, baseName: hookOptionsFile.baseName } })}
116
+ footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: hookOptionsFile.path, baseName: hookOptionsFile.baseName } })}
124
117
  >
125
118
  {imports}
126
119
  <File.Source name={name} isExportable isIndexable isTypeOnly>