@kubb/plugin-react-query 5.0.0-beta.15 → 5.0.0-beta.25

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 (45) hide show
  1. package/dist/{components-BZ3a2O0G.cjs → components-C1_zAoAO.cjs} +65 -84
  2. package/dist/components-C1_zAoAO.cjs.map +1 -0
  3. package/dist/{components-DJqIUiZW.js → components-C91DnOOV.js} +65 -84
  4. package/dist/components-C91DnOOV.js.map +1 -0
  5. package/dist/components.cjs +1 -1
  6. package/dist/components.d.ts +1 -1
  7. package/dist/components.js +1 -1
  8. package/dist/{generators-BQ_vEksc.js → generators-9srJC_zb.js} +115 -75
  9. package/dist/generators-9srJC_zb.js.map +1 -0
  10. package/dist/{generators-DSjer1xY.cjs → generators-DS3JH1hR.cjs} +115 -75
  11. package/dist/generators-DS3JH1hR.cjs.map +1 -0
  12. package/dist/generators.cjs +1 -1
  13. package/dist/generators.d.ts +41 -1
  14. package/dist/generators.js +1 -1
  15. package/dist/index.cjs +51 -11
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.ts +30 -1
  18. package/dist/index.js +51 -11
  19. package/dist/index.js.map +1 -1
  20. package/dist/{types-DG_OxOym.d.ts → types-DiZPLTXl.d.ts} +92 -55
  21. package/extension.yaml +904 -325
  22. package/package.json +7 -7
  23. package/src/components/InfiniteQuery.tsx +4 -4
  24. package/src/components/InfiniteQueryOptions.tsx +21 -31
  25. package/src/components/Mutation.tsx +2 -2
  26. package/src/components/MutationOptions.tsx +1 -1
  27. package/src/components/Query.tsx +1 -1
  28. package/src/components/QueryOptions.tsx +1 -1
  29. package/src/components/SuspenseInfiniteQuery.tsx +4 -4
  30. package/src/components/SuspenseInfiniteQueryOptions.tsx +21 -31
  31. package/src/components/SuspenseQuery.tsx +1 -1
  32. package/src/generators/customHookOptionsFileGenerator.tsx +7 -1
  33. package/src/generators/hookOptionsGenerator.tsx +17 -11
  34. package/src/generators/infiniteQueryGenerator.tsx +22 -13
  35. package/src/generators/mutationGenerator.tsx +20 -12
  36. package/src/generators/queryGenerator.tsx +20 -12
  37. package/src/generators/suspenseInfiniteQueryGenerator.tsx +22 -13
  38. package/src/generators/suspenseQueryGenerator.tsx +21 -12
  39. package/src/plugin.ts +34 -5
  40. package/src/resolvers/resolverReactQuery.ts +15 -4
  41. package/src/types.ts +89 -52
  42. package/dist/components-BZ3a2O0G.cjs.map +0 -1
  43. package/dist/components-DJqIUiZW.js.map +0 -1
  44. package/dist/generators-BQ_vEksc.js.map +0 -1
  45. package/dist/generators-DSjer1xY.cjs.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/plugin-react-query",
3
- "version": "5.0.0-beta.15",
3
+ "version": "5.0.0-beta.25",
4
4
  "description": "Generate type-safe TanStack Query (React Query) hooks from your OpenAPI specification. Covers useQuery, useMutation, useInfiniteQuery, and queryOptions with full TypeScript support.",
5
5
  "keywords": [
6
6
  "code-generation",
@@ -68,12 +68,12 @@
68
68
  "registry": "https://registry.npmjs.org/"
69
69
  },
70
70
  "dependencies": {
71
- "@kubb/core": "5.0.0-beta.19",
72
- "@kubb/renderer-jsx": "5.0.0-beta.19",
71
+ "@kubb/core": "5.0.0-beta.25",
72
+ "@kubb/renderer-jsx": "5.0.0-beta.25",
73
73
  "remeda": "^2.34.1",
74
- "@kubb/plugin-client": "5.0.0-beta.15",
75
- "@kubb/plugin-ts": "5.0.0-beta.15",
76
- "@kubb/plugin-zod": "5.0.0-beta.15"
74
+ "@kubb/plugin-client": "5.0.0-beta.25",
75
+ "@kubb/plugin-ts": "5.0.0-beta.25",
76
+ "@kubb/plugin-zod": "5.0.0-beta.25"
77
77
  },
78
78
  "devDependencies": {
79
79
  "@internals/shared": "0.0.0",
@@ -81,7 +81,7 @@
81
81
  "@internals/utils": "0.0.0"
82
82
  },
83
83
  "peerDependencies": {
84
- "@kubb/renderer-jsx": "5.0.0-beta.19"
84
+ "@kubb/renderer-jsx": "5.0.0-beta.25"
85
85
  },
86
86
  "size-limit": [
87
87
  {
@@ -39,7 +39,7 @@ function buildInfiniteQueryParamsNode(
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',
@@ -104,11 +104,11 @@ export function InfiniteQuery({
104
104
  ? (() => {
105
105
  const groupName = tsResolver.resolveQueryParamsName(node, rawQueryParams[0]!)
106
106
  const individualName = tsResolver.resolveParamName(node, rawQueryParams[0]!)
107
- return groupName !== individualName ? groupName : undefined
107
+ return groupName !== individualName ? groupName : null
108
108
  })()
109
- : undefined
109
+ : null
110
110
 
111
- const queryParamType = queryParam && queryParamsTypeName ? `${queryParamsTypeName}['${queryParam}']` : undefined
111
+ const queryParamType = queryParam && queryParamsTypeName ? `${queryParamsTypeName}['${queryParam}']` : null
112
112
  const pageParamType = queryParamType ? (isInitialPageParamDefined ? `NonNullable<${queryParamType}>` : queryParamType) : fallbackPageParamType
113
113
 
114
114
  const returnType = 'UseInfiniteQueryResult<TData, TError> & { queryKey: TQueryKey }'
@@ -72,11 +72,11 @@ export function InfiniteQueryOptions({
72
72
  ? (() => {
73
73
  const groupName = tsResolver.resolveQueryParamsName(node, rawQueryParams[0]!)
74
74
  const individualName = tsResolver.resolveParamName(node, rawQueryParams[0]!)
75
- return groupName !== individualName ? groupName : undefined
75
+ return groupName !== individualName ? groupName : null
76
76
  })()
77
- : undefined
77
+ : null
78
78
 
79
- const queryParamType = queryParam && queryParamsTypeName ? `${queryParamsTypeName}['${queryParam}']` : undefined
79
+ const queryParamType = queryParam && queryParamsTypeName ? `${queryParamsTypeName}['${queryParam}']` : null
80
80
  const pageParamType = queryParamType ? (isInitialPageParamDefined ? `NonNullable<${queryParamType}>` : queryParamType) : fallbackPageParamType
81
81
 
82
82
  const paramsNode = getQueryOptionsParams(node, { paramsType, paramsCasing, pathParamsType, resolver: tsResolver })
@@ -90,37 +90,27 @@ export function InfiniteQueryOptions({
90
90
  const enabledSource = buildEnabledCheck(queryKeyParamsNode)
91
91
  const enabledText = enabledSource ? `enabled: !!(${enabledSource}),` : ''
92
92
 
93
- const hasNewParams = nextParam !== undefined || previousParam !== undefined
93
+ const hasNewParams = nextParam != null || previousParam != null
94
94
 
95
- let getNextPageParamExpr: string | undefined
96
- let getPreviousPageParamExpr: string | undefined
97
-
98
- if (hasNewParams) {
99
- if (nextParam) {
100
- const accessor = getNestedAccessor(nextParam, 'lastPage')
101
- if (accessor) {
102
- getNextPageParamExpr = `getNextPageParam: (lastPage) => ${accessor}`
103
- }
104
- }
105
- if (previousParam) {
106
- const accessor = getNestedAccessor(previousParam, 'firstPage')
107
- if (accessor) {
108
- getPreviousPageParamExpr = `getPreviousPageParam: (firstPage) => ${accessor}`
109
- }
95
+ const [getNextPageParamExpr, getPreviousPageParamExpr] = (() => {
96
+ if (hasNewParams) {
97
+ const nextAccessor = nextParam ? getNestedAccessor(nextParam, 'lastPage') : null
98
+ const prevAccessor = previousParam ? getNestedAccessor(previousParam, 'firstPage') : null
99
+ return [
100
+ nextAccessor ? `getNextPageParam: (lastPage) => ${nextAccessor}` : null,
101
+ prevAccessor ? `getPreviousPageParam: (firstPage) => ${prevAccessor}` : null,
102
+ ] as const
110
103
  }
111
- } else if (cursorParam) {
112
- getNextPageParamExpr = `getNextPageParam: (lastPage) => lastPage['${cursorParam}']`
113
- getPreviousPageParamExpr = `getPreviousPageParam: (firstPage) => firstPage['${cursorParam}']`
114
- } else {
115
- if (dataReturnType === 'full') {
116
- getNextPageParamExpr =
117
- 'getNextPageParam: (lastPage, _allPages, lastPageParam) => Array.isArray(lastPage.data) && lastPage.data.length === 0 ? undefined : lastPageParam + 1'
118
- } else {
119
- getNextPageParamExpr =
120
- 'getNextPageParam: (lastPage, _allPages, lastPageParam) => Array.isArray(lastPage) && lastPage.length === 0 ? undefined : lastPageParam + 1'
104
+ if (cursorParam) {
105
+ return [`getNextPageParam: (lastPage) => lastPage['${cursorParam}']`, `getPreviousPageParam: (firstPage) => firstPage['${cursorParam}']`] as const
121
106
  }
122
- getPreviousPageParamExpr = 'getPreviousPageParam: (_firstPage, _allPages, firstPageParam) => firstPageParam <= 1 ? undefined : firstPageParam - 1'
123
- }
107
+ return [
108
+ dataReturnType === 'full'
109
+ ? 'getNextPageParam: (lastPage, _allPages, lastPageParam) => Array.isArray(lastPage.data) && lastPage.data.length === 0 ? undefined : lastPageParam + 1'
110
+ : 'getNextPageParam: (lastPage, _allPages, lastPageParam) => Array.isArray(lastPage) && lastPage.length === 0 ? undefined : lastPageParam + 1',
111
+ 'getPreviousPageParam: (_firstPage, _allPages, firstPageParam) => firstPageParam <= 1 ? undefined : firstPageParam - 1',
112
+ ] as const
113
+ })()
124
114
 
125
115
  const queryOptionsArr = [
126
116
  `initialPageParam: ${typeof initialPageParam === 'string' ? JSON.stringify(initialPageParam) : initialPageParam}`,
@@ -58,7 +58,7 @@ function buildMutationParamsNode(
58
58
  resolver,
59
59
  })
60
60
  const TRequest = mutationArgParamsNode.params.length > 0 ? (declarationPrinter.print(mutationArgParamsNode) ?? '') : ''
61
- const generics = [TData, TError, TRequest ? `{${TRequest}}` : 'void', 'TContext'].join(', ')
61
+ const generics = [TData, TError, TRequest ? `{${TRequest}}` : 'undefined', 'TContext'].join(', ')
62
62
 
63
63
  return ast.createFunctionParameters({
64
64
  params: [
@@ -89,7 +89,7 @@ export function Mutation({ name, mutationOptionsName, paramsCasing, dataReturnTy
89
89
  resolver: tsResolver,
90
90
  })
91
91
  const TRequest = mutationArgParamsNode.params.length > 0 ? (declarationPrinter.print(mutationArgParamsNode) ?? '') : ''
92
- const generics = [TData, TError, TRequest ? `{${TRequest}}` : 'void', 'TContext'].join(', ')
92
+ const generics = [TData, TError, TRequest ? `{${TRequest}}` : 'undefined', 'TContext'].join(', ')
93
93
  const returnType = `UseMutationResult<${generics}>`
94
94
 
95
95
  const mutationOptionsConfigNode = buildMutationConfigParamsNode(node, tsResolver)
@@ -90,7 +90,7 @@ export function MutationOptions({
90
90
  <Function name={name} export params={paramsSignature} generics={['TContext = unknown']}>
91
91
  {`
92
92
  const mutationKey = ${mutationKeyName}()
93
- return mutationOptions<${TData}, ${TError}, ${TRequest ? `{${TRequest}}` : 'void'}, TContext>({
93
+ return mutationOptions<${TData}, ${TError}, ${TRequest ? `{${TRequest}}` : 'undefined'}, TContext>({
94
94
  mutationKey,
95
95
  mutationFn: async(${hasMutationParams ? `{ ${argKeysStr} }` : '_'}) => {
96
96
  return ${clientName}(${clientCallStr})
@@ -36,7 +36,7 @@ function buildQueryParamsNode(
36
36
  ): ast.FunctionParametersNode {
37
37
  const { paramsType, paramsCasing, pathParamsType, dataReturnType, resolver } = options
38
38
  const responseName = resolver.resolveResponseName(node)
39
- const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : undefined
39
+ const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null
40
40
  const errorNames = resolveErrorNames(node, resolver)
41
41
 
42
42
  const TData = dataReturnType === 'data' ? responseName : `ResponseConfig<${responseName}>`
@@ -32,7 +32,7 @@ export function getQueryOptionsParams(
32
32
  },
33
33
  ): ast.FunctionParametersNode {
34
34
  const { paramsType, paramsCasing, pathParamsType, resolver } = options
35
- const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : undefined
35
+ const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null
36
36
 
37
37
  return ast.createOperationParams(node, {
38
38
  paramsType,
@@ -39,7 +39,7 @@ function buildSuspenseInfiniteQueryParamsNode(
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',
@@ -104,11 +104,11 @@ export function SuspenseInfiniteQuery({
104
104
  ? (() => {
105
105
  const groupName = tsResolver.resolveQueryParamsName(node, rawQueryParams[0]!)
106
106
  const individualName = tsResolver.resolveParamName(node, rawQueryParams[0]!)
107
- return groupName !== individualName ? groupName : undefined
107
+ return groupName !== individualName ? groupName : null
108
108
  })()
109
- : undefined
109
+ : null
110
110
 
111
- const queryParamType = queryParam && queryParamsTypeName ? `${queryParamsTypeName}['${queryParam}']` : undefined
111
+ const queryParamType = queryParam && queryParamsTypeName ? `${queryParamsTypeName}['${queryParam}']` : null
112
112
  const pageParamType = queryParamType ? (isInitialPageParamDefined ? `NonNullable<${queryParamType}>` : queryParamType) : fallbackPageParamType
113
113
 
114
114
  const returnType = 'UseSuspenseInfiniteQueryResult<TData, TError> & { queryKey: TQueryKey }'
@@ -72,11 +72,11 @@ export function SuspenseInfiniteQueryOptions({
72
72
  ? (() => {
73
73
  const groupName = tsResolver.resolveQueryParamsName(node, rawQueryParams[0]!)
74
74
  const individualName = tsResolver.resolveParamName(node, rawQueryParams[0]!)
75
- return groupName !== individualName ? groupName : undefined
75
+ return groupName !== individualName ? groupName : null
76
76
  })()
77
- : undefined
77
+ : null
78
78
 
79
- const queryParamType = queryParam && queryParamsTypeName ? `${queryParamsTypeName}['${queryParam}']` : undefined
79
+ const queryParamType = queryParam && queryParamsTypeName ? `${queryParamsTypeName}['${queryParam}']` : null
80
80
  const pageParamType = queryParamType ? (isInitialPageParamDefined ? `NonNullable<${queryParamType}>` : queryParamType) : fallbackPageParamType
81
81
 
82
82
  const paramsNode = getQueryOptionsParams(node, { paramsType, paramsCasing, pathParamsType, resolver: tsResolver })
@@ -90,37 +90,27 @@ export function SuspenseInfiniteQueryOptions({
90
90
  const enabledSource = buildEnabledCheck(queryKeyParamsNode)
91
91
  const enabledText = enabledSource ? `enabled: !!(${enabledSource}),` : ''
92
92
 
93
- const hasNewParams = nextParam !== undefined || previousParam !== undefined
93
+ const hasNewParams = nextParam != null || previousParam != null
94
94
 
95
- let getNextPageParamExpr: string | undefined
96
- let getPreviousPageParamExpr: string | undefined
97
-
98
- if (hasNewParams) {
99
- if (nextParam) {
100
- const accessor = getNestedAccessor(nextParam, 'lastPage')
101
- if (accessor) {
102
- getNextPageParamExpr = `getNextPageParam: (lastPage) => ${accessor}`
103
- }
104
- }
105
- if (previousParam) {
106
- const accessor = getNestedAccessor(previousParam, 'firstPage')
107
- if (accessor) {
108
- getPreviousPageParamExpr = `getPreviousPageParam: (firstPage) => ${accessor}`
109
- }
95
+ const [getNextPageParamExpr, getPreviousPageParamExpr] = (() => {
96
+ if (hasNewParams) {
97
+ const nextAccessor = nextParam ? getNestedAccessor(nextParam, 'lastPage') : null
98
+ const prevAccessor = previousParam ? getNestedAccessor(previousParam, 'firstPage') : null
99
+ return [
100
+ nextAccessor ? `getNextPageParam: (lastPage) => ${nextAccessor}` : null,
101
+ prevAccessor ? `getPreviousPageParam: (firstPage) => ${prevAccessor}` : null,
102
+ ] as const
110
103
  }
111
- } else if (cursorParam) {
112
- getNextPageParamExpr = `getNextPageParam: (lastPage) => lastPage['${cursorParam}']`
113
- getPreviousPageParamExpr = `getPreviousPageParam: (firstPage) => firstPage['${cursorParam}']`
114
- } else {
115
- if (dataReturnType === 'full') {
116
- getNextPageParamExpr =
117
- 'getNextPageParam: (lastPage, _allPages, lastPageParam) => Array.isArray(lastPage.data) && lastPage.data.length === 0 ? undefined : lastPageParam + 1'
118
- } else {
119
- getNextPageParamExpr =
120
- 'getNextPageParam: (lastPage, _allPages, lastPageParam) => Array.isArray(lastPage) && lastPage.length === 0 ? undefined : lastPageParam + 1'
104
+ if (cursorParam) {
105
+ return [`getNextPageParam: (lastPage) => lastPage['${cursorParam}']`, `getPreviousPageParam: (firstPage) => firstPage['${cursorParam}']`] as const
121
106
  }
122
- getPreviousPageParamExpr = 'getPreviousPageParam: (_firstPage, _allPages, firstPageParam) => firstPageParam <= 1 ? undefined : firstPageParam - 1'
123
- }
107
+ return [
108
+ dataReturnType === 'full'
109
+ ? 'getNextPageParam: (lastPage, _allPages, lastPageParam) => Array.isArray(lastPage.data) && lastPage.data.length === 0 ? undefined : lastPageParam + 1'
110
+ : 'getNextPageParam: (lastPage, _allPages, lastPageParam) => Array.isArray(lastPage) && lastPage.length === 0 ? undefined : lastPageParam + 1',
111
+ 'getPreviousPageParam: (_firstPage, _allPages, firstPageParam) => firstPageParam <= 1 ? undefined : firstPageParam - 1',
112
+ ] as const
113
+ })()
124
114
 
125
115
  const queryOptionsArr = [
126
116
  `initialPageParam: ${typeof initialPageParam === 'string' ? JSON.stringify(initialPageParam) : initialPageParam}`,
@@ -36,7 +36,7 @@ function buildSuspenseQueryParamsNode(
36
36
  ): ast.FunctionParametersNode {
37
37
  const { paramsType, paramsCasing, pathParamsType, dataReturnType, resolver } = options
38
38
  const responseName = resolver.resolveResponseName(node)
39
- const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : undefined
39
+ const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null
40
40
  const errorNames = resolveErrorNames(node, resolver)
41
41
 
42
42
  const TData = dataReturnType === 'data' ? responseName : `ResponseConfig<${responseName}>`
@@ -5,6 +5,12 @@ import { defineGenerator } from '@kubb/core'
5
5
  import { File, Function, jsxRendererSync } from '@kubb/renderer-jsx'
6
6
  import type { PluginReactQuery } from '../types'
7
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
+ */
8
14
  export const customHookOptionsFileGenerator = defineGenerator<PluginReactQuery>({
9
15
  name: 'react-query-custom-hook-options-file',
10
16
  renderer: jsxRendererSync,
@@ -27,7 +33,7 @@ export const customHookOptionsFileGenerator = defineGenerator<PluginReactQuery>(
27
33
  const hookName = resolver.resolveQueryName(firstNode)
28
34
  const hookFile = resolver.resolveFile(
29
35
  { name: hookName, extname: '.ts', tag: firstNode.tags[0] ?? 'default', path: firstNode.path },
30
- { root, output, group },
36
+ { root, output, group: group ?? undefined },
31
37
  )
32
38
  hookFilePath = hookFile.path
33
39
  } else {
@@ -9,24 +9,30 @@ import { resolveOperationOverrides } from '../utils.ts'
9
9
  type QueryOption = PluginReactQuery['resolvedOptions']['query']
10
10
  type MutationOption = PluginReactQuery['resolvedOptions']['mutation']
11
11
 
12
+ /**
13
+ * Emits the `HookOptions` type used by `customOptions`. Enabled when
14
+ * `pluginReactQuery({ customOptions: { ... } })`. The generated type lists
15
+ * every hook keyed by name so user-supplied options stay in sync with the
16
+ * generated hooks at compile time.
17
+ */
12
18
  export const hookOptionsGenerator = defineGenerator<PluginReactQuery>({
13
19
  name: 'react-query-hook-options',
14
20
  renderer: jsxRendererSync,
15
21
  operations(nodes, ctx) {
16
- const { resolver, config, root, inputNode } = ctx
22
+ const { resolver, config, root } = ctx
17
23
  const { output, customOptions, query, mutation, suspense, infinite, group, override } = ctx.options
18
24
 
19
25
  if (!customOptions) return null
20
26
 
21
27
  const name = resolver.resolveHookOptionsName()
22
- const resolvedFile = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group })
28
+ const resolvedFile = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group: group ?? undefined })
23
29
  const hookOptionsFile = {
24
30
  ...resolvedFile,
25
31
  baseName: `${name}.ts` as const,
26
32
  path: resolvedFile.path.replace(/[^/\\]+\.ts$/, `${name}.ts`),
27
33
  }
28
34
 
29
- const imports: KubbReactNode[] = []
35
+ const imports: Array<KubbReactNode> = []
30
36
  const hookOptions: Record<string, string> = {}
31
37
 
32
38
  for (const node of nodes) {
@@ -34,7 +40,7 @@ export const hookOptionsGenerator = defineGenerator<PluginReactQuery>({
34
40
  const nodeQuery: QueryOption = 'query' in opOverrides ? (opOverrides.query as QueryOption) : query
35
41
  const nodeMutation: MutationOption = 'mutation' in opOverrides ? (opOverrides.mutation as MutationOption) : mutation
36
42
  const nodeInfinite = 'infinite' in opOverrides ? opOverrides.infinite : infinite
37
- const nodeInfiniteOptions = nodeInfinite && typeof nodeInfinite === 'object' ? nodeInfinite : undefined
43
+ const nodeInfiniteOptions = nodeInfinite && typeof nodeInfinite === 'object' ? nodeInfinite : null
38
44
 
39
45
  // query: false means "still a query but skip the useQuery hook"
40
46
  const isQueryOp =
@@ -53,7 +59,7 @@ export const hookOptionsGenerator = defineGenerator<PluginReactQuery>({
53
59
  const queryHookName = resolver.resolveQueryName(node)
54
60
  const queryHookFile = resolver.resolveFile(
55
61
  { name: queryHookName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
56
- { root, output, group },
62
+ { root, output, group: group ?? undefined },
57
63
  )
58
64
  imports.push(<File.Import name={[queryOptionsName]} root={hookOptionsFile.path} path={queryHookFile.path} />)
59
65
  hookOptions[queryHookName] = `Partial<ReturnType<typeof ${queryOptionsName}>>`
@@ -63,7 +69,7 @@ export const hookOptionsGenerator = defineGenerator<PluginReactQuery>({
63
69
  const suspenseHookName = resolver.resolveSuspenseQueryName(node)
64
70
  const suspenseHookFile = resolver.resolveFile(
65
71
  { name: suspenseHookName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
66
- { root, output, group },
72
+ { root, output, group: group ?? undefined },
67
73
  )
68
74
  imports.push(<File.Import name={[suspenseOptionsName]} root={hookOptionsFile.path} path={suspenseHookFile.path} />)
69
75
  hookOptions[suspenseHookName] = `Partial<ReturnType<typeof ${suspenseOptionsName}>>`
@@ -80,7 +86,7 @@ export const hookOptionsGenerator = defineGenerator<PluginReactQuery>({
80
86
  const infiniteHookName = resolver.resolveInfiniteQueryName(node)
81
87
  const infiniteHookFile = resolver.resolveFile(
82
88
  { name: infiniteHookName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
83
- { root, output, group },
89
+ { root, output, group: group ?? undefined },
84
90
  )
85
91
  imports.push(<File.Import name={[infiniteOptionsName]} root={hookOptionsFile.path} path={infiniteHookFile.path} />)
86
92
  hookOptions[infiniteHookName] = `Partial<ReturnType<typeof ${infiniteOptionsName}>>`
@@ -90,7 +96,7 @@ export const hookOptionsGenerator = defineGenerator<PluginReactQuery>({
90
96
  const suspenseInfiniteHookName = resolver.resolveSuspenseInfiniteQueryName(node)
91
97
  const suspenseInfiniteHookFile = resolver.resolveFile(
92
98
  { name: suspenseInfiniteHookName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
93
- { root, output, group },
99
+ { root, output, group: group ?? undefined },
94
100
  )
95
101
  imports.push(<File.Import name={[suspenseInfiniteOptionsName]} root={hookOptionsFile.path} path={suspenseInfiniteHookFile.path} />)
96
102
  hookOptions[suspenseInfiniteHookName] = `Partial<ReturnType<typeof ${suspenseInfiniteOptionsName}>>`
@@ -104,7 +110,7 @@ export const hookOptionsGenerator = defineGenerator<PluginReactQuery>({
104
110
  const mutationHookName = resolver.resolveMutationName(node)
105
111
  const mutationHookFile = resolver.resolveFile(
106
112
  { name: mutationHookName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
107
- { root, output, group },
113
+ { root, output, group: group ?? undefined },
108
114
  )
109
115
  imports.push(<File.Import name={[mutationOptionsName]} root={hookOptionsFile.path} path={mutationHookFile.path} />)
110
116
  hookOptions[mutationHookName] = `Partial<ReturnType<typeof ${mutationOptionsName}>>`
@@ -116,8 +122,8 @@ export const hookOptionsGenerator = defineGenerator<PluginReactQuery>({
116
122
  baseName={hookOptionsFile.baseName}
117
123
  path={hookOptionsFile.path}
118
124
  meta={hookOptionsFile.meta}
119
- banner={resolver.resolveBanner(inputNode, { output, config })}
120
- footer={resolver.resolveFooter(inputNode, { output, config })}
125
+ banner={resolver.resolveBanner(ctx.meta, { output, config })}
126
+ footer={resolver.resolveFooter(ctx.meta, { output, config })}
121
127
  >
122
128
  {imports}
123
129
  <File.Source name={name} isExportable isIndexable isTypeOnly>
@@ -10,11 +10,17 @@ import { difference } from 'remeda'
10
10
  import { InfiniteQuery, InfiniteQueryOptions, QueryKey } from '../components'
11
11
  import type { PluginReactQuery } from '../types'
12
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
+ */
13
19
  export const infiniteQueryGenerator = defineGenerator<PluginReactQuery>({
14
20
  name: 'react-infinite-query',
15
21
  renderer: jsxRendererSync,
16
22
  operation(node, ctx) {
17
- const { config, driver, resolver, root, inputNode } = ctx
23
+ const { config, driver, resolver, root } = ctx
18
24
  const { output, query, mutation, infinite, paramsCasing, paramsType, pathParamsType, parser, client: clientOptions, group, customOptions } = ctx.options
19
25
 
20
26
  const pluginTs = driver.getPlugin(pluginTsName)
@@ -26,7 +32,7 @@ export const infiniteQueryGenerator = defineGenerator<PluginReactQuery>({
26
32
  mutation !== false &&
27
33
  !isQuery &&
28
34
  difference(mutation ? mutation.methods : [], query ? query.methods : []).some((method) => node.method.toLowerCase() === method.toLowerCase())
29
- const infiniteOptions = infinite && typeof infinite === 'object' ? infinite : undefined
35
+ const infiniteOptions = infinite && typeof infinite === 'object' ? infinite : null
30
36
 
31
37
  if (!isQuery || isMutation || !infiniteOptions) return null
32
38
 
@@ -48,10 +54,13 @@ export const infiniteQueryGenerator = defineGenerator<PluginReactQuery>({
48
54
  const clientBaseName = resolver.resolveInfiniteClientName(node)
49
55
 
50
56
  const meta = {
51
- 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
+ ),
52
61
  fileTs: tsResolver.resolveFile(
53
62
  { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
54
- { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group },
63
+ { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group ?? undefined },
55
64
  ),
56
65
  }
57
66
 
@@ -61,20 +70,20 @@ export const infiniteQueryGenerator = defineGenerator<PluginReactQuery>({
61
70
  order: 'body-response-first',
62
71
  })
63
72
 
64
- const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : undefined
65
- const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : undefined
73
+ const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
74
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
66
75
  const fileZod = zodResolver
67
76
  ? zodResolver.resolveFile(
68
77
  { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
69
- { root, output: pluginZod?.options?.output ?? output, group: pluginZod?.options?.group },
78
+ { root, output: pluginZod?.options?.output ?? output, group: pluginZod?.options?.group ?? undefined },
70
79
  )
71
- : undefined
80
+ : null
72
81
  const zodSchemaNames = resolveZodSchemaNames(node, zodResolver)
73
82
 
74
83
  const clientPlugin = driver.getPlugin(pluginClientName)
75
84
  const hasClientPlugin = clientPlugin?.name === pluginClientName
76
85
  const shouldUseClientPlugin = hasClientPlugin && clientOptions.clientType !== 'class'
77
- const clientResolver = shouldUseClientPlugin ? driver.getResolver(pluginClientName) : undefined
86
+ const clientResolver = shouldUseClientPlugin ? driver.getResolver(pluginClientName) : null
78
87
 
79
88
  const clientFile = shouldUseClientPlugin
80
89
  ? clientResolver?.resolveFile(
@@ -82,10 +91,10 @@ export const infiniteQueryGenerator = defineGenerator<PluginReactQuery>({
82
91
  {
83
92
  root,
84
93
  output: clientPlugin?.options?.output ?? output,
85
- group: clientPlugin?.options?.group,
94
+ group: clientPlugin?.options?.group ?? undefined,
86
95
  },
87
96
  )
88
- : undefined
97
+ : null
89
98
 
90
99
  const resolvedClientName = shouldUseClientPlugin ? (clientResolver?.resolveName(node.operationId) ?? clientBaseName) : clientBaseName
91
100
 
@@ -94,8 +103,8 @@ export const infiniteQueryGenerator = defineGenerator<PluginReactQuery>({
94
103
  baseName={meta.file.baseName}
95
104
  path={meta.file.path}
96
105
  meta={meta.file.meta}
97
- banner={resolver.resolveBanner(inputNode, { output, config })}
98
- footer={resolver.resolveFooter(inputNode, { output, config })}
106
+ banner={resolver.resolveBanner(ctx.meta, { output, config })}
107
+ footer={resolver.resolveFooter(ctx.meta, { output, config })}
99
108
  >
100
109
  {fileZod && zodSchemaNames.length > 0 && <File.Import name={zodSchemaNames} root={meta.file.path} path={fileZod.path} />}
101
110
  {clientOptions.importPath ? (
@@ -10,11 +10,16 @@ import { difference } from 'remeda'
10
10
  import { Mutation, MutationKey, MutationOptions } from '../components'
11
11
  import type { PluginReactQuery } from '../types'
12
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
+ */
13
18
  export const mutationGenerator = defineGenerator<PluginReactQuery>({
14
19
  name: 'react-query-mutation',
15
20
  renderer: jsxRendererSync,
16
21
  operation(node, ctx) {
17
- const { config, driver, resolver, root, inputNode } = ctx
22
+ const { config, driver, resolver, root } = ctx
18
23
  const { output, query, mutation, paramsCasing, paramsType, pathParamsType, parser, client: clientOptions, group, customOptions } = ctx.options
19
24
 
20
25
  const pluginTs = driver.getPlugin(pluginTsName)
@@ -38,29 +43,32 @@ export const mutationGenerator = defineGenerator<PluginReactQuery>({
38
43
  const clientName = resolver.resolveClientName(node)
39
44
 
40
45
  const meta = {
41
- 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
+ ),
42
50
  fileTs: tsResolver.resolveFile(
43
51
  { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
44
- { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group },
52
+ { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group ?? undefined },
45
53
  ),
46
54
  }
47
55
 
48
56
  const importedTypeNames = resolveOperationTypeNames(node, tsResolver, { paramsCasing, order: 'body-response-first' })
49
57
 
50
- const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : undefined
51
- const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : undefined
58
+ const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
59
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
52
60
  const fileZod = zodResolver
53
61
  ? zodResolver.resolveFile(
54
62
  { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
55
- { root, output: pluginZod?.options?.output ?? output, group: pluginZod?.options?.group },
63
+ { root, output: pluginZod?.options?.output ?? output, group: pluginZod?.options?.group ?? undefined },
56
64
  )
57
- : undefined
65
+ : null
58
66
  const zodSchemaNames = resolveZodSchemaNames(node, zodResolver)
59
67
 
60
68
  const clientPlugin = driver.getPlugin(pluginClientName)
61
69
  const hasClientPlugin = clientPlugin?.name === pluginClientName
62
70
  const shouldUseClientPlugin = hasClientPlugin && clientOptions.clientType !== 'class'
63
- const clientResolver = shouldUseClientPlugin ? driver.getResolver(pluginClientName) : undefined
71
+ const clientResolver = shouldUseClientPlugin ? driver.getResolver(pluginClientName) : null
64
72
 
65
73
  const clientFile = shouldUseClientPlugin
66
74
  ? clientResolver?.resolveFile(
@@ -68,10 +76,10 @@ export const mutationGenerator = defineGenerator<PluginReactQuery>({
68
76
  {
69
77
  root,
70
78
  output: clientPlugin?.options?.output ?? output,
71
- group: clientPlugin?.options?.group,
79
+ group: clientPlugin?.options?.group ?? undefined,
72
80
  },
73
81
  )
74
- : undefined
82
+ : null
75
83
 
76
84
  const resolvedClientName = shouldUseClientPlugin ? (clientResolver?.resolveName(node.operationId) ?? clientName) : clientName
77
85
 
@@ -80,8 +88,8 @@ export const mutationGenerator = defineGenerator<PluginReactQuery>({
80
88
  baseName={meta.file.baseName}
81
89
  path={meta.file.path}
82
90
  meta={meta.file.meta}
83
- banner={resolver.resolveBanner(inputNode, { output, config })}
84
- footer={resolver.resolveFooter(inputNode, { output, config })}
91
+ banner={resolver.resolveBanner(ctx.meta, { output, config })}
92
+ footer={resolver.resolveFooter(ctx.meta, { output, config })}
85
93
  >
86
94
  {fileZod && zodSchemaNames.length > 0 && <File.Import name={zodSchemaNames} root={meta.file.path} path={fileZod.path} />}
87
95
  {clientOptions.importPath ? (