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

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 (53) hide show
  1. package/LICENSE +17 -10
  2. package/README.md +1 -3
  3. package/dist/components-DTGLu4UV.js +1451 -0
  4. package/dist/components-DTGLu4UV.js.map +1 -0
  5. package/dist/components-dAKJEn9b.cjs +1571 -0
  6. package/dist/components-dAKJEn9b.cjs.map +1 -0
  7. package/dist/components.cjs +1 -1
  8. package/dist/components.d.ts +105 -161
  9. package/dist/components.js +1 -1
  10. package/dist/generators-CWEQsdO9.cjs +1502 -0
  11. package/dist/generators-CWEQsdO9.cjs.map +1 -0
  12. package/dist/generators-C_fbcjpG.js +1460 -0
  13. package/dist/generators-C_fbcjpG.js.map +1 -0
  14. package/dist/generators.cjs +1 -1
  15. package/dist/generators.d.ts +9 -505
  16. package/dist/generators.js +1 -1
  17. package/dist/index.cjs +114 -126
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.ts +4 -4
  20. package/dist/index.js +110 -126
  21. package/dist/index.js.map +1 -1
  22. package/dist/{types-D5S7Ny9r.d.ts → types-DfaFRSBf.d.ts} +100 -86
  23. package/extension.yaml +938 -0
  24. package/package.json +59 -62
  25. package/src/components/InfiniteQuery.tsx +75 -139
  26. package/src/components/InfiniteQueryOptions.tsx +62 -164
  27. package/src/components/Mutation.tsx +58 -113
  28. package/src/components/MutationOptions.tsx +61 -80
  29. package/src/components/Query.tsx +67 -140
  30. package/src/components/QueryOptions.tsx +75 -135
  31. package/src/components/SuspenseInfiniteQuery.tsx +75 -139
  32. package/src/components/SuspenseInfiniteQueryOptions.tsx +62 -164
  33. package/src/components/SuspenseQuery.tsx +67 -150
  34. package/src/generators/customHookOptionsFileGenerator.tsx +33 -45
  35. package/src/generators/hookOptionsGenerator.tsx +115 -175
  36. package/src/generators/infiniteQueryGenerator.tsx +183 -176
  37. package/src/generators/mutationGenerator.tsx +127 -138
  38. package/src/generators/queryGenerator.tsx +141 -141
  39. package/src/generators/suspenseInfiniteQueryGenerator.tsx +175 -155
  40. package/src/generators/suspenseQueryGenerator.tsx +149 -148
  41. package/src/index.ts +1 -1
  42. package/src/plugin.ts +133 -183
  43. package/src/resolvers/resolverReactQuery.ts +22 -0
  44. package/src/types.ts +67 -45
  45. package/src/utils.ts +40 -0
  46. package/dist/components-BHQT9ZLc.cjs +0 -1634
  47. package/dist/components-BHQT9ZLc.cjs.map +0 -1
  48. package/dist/components-CpyHYGOw.js +0 -1520
  49. package/dist/components-CpyHYGOw.js.map +0 -1
  50. package/dist/generators-DP07m3rH.cjs +0 -1469
  51. package/dist/generators-DP07m3rH.cjs.map +0 -1
  52. package/dist/generators-DkQwKTc2.js +0 -1427
  53. package/dist/generators-DkQwKTc2.js.map +0 -1
@@ -1,67 +1,55 @@
1
1
  import fs from 'node:fs'
2
2
  import path from 'node:path'
3
- import { usePluginDriver } from '@kubb/core/hooks'
4
- import type { Operation } from '@kubb/oas'
5
- import { createReactGenerator } from '@kubb/plugin-oas/generators'
6
- import { useOperationManager } from '@kubb/plugin-oas/hooks'
7
- import { File, Function } from '@kubb/react-fabric'
3
+
4
+ import { defineGenerator } from '@kubb/core'
5
+ import { File, Function, jsxRenderer } from '@kubb/renderer-jsx'
8
6
  import type { PluginReactQuery } from '../types'
7
+ import { transformName } from '../utils.ts'
9
8
 
10
- export const customHookOptionsFileGenerator = createReactGenerator<PluginReactQuery>({
9
+ export const customHookOptionsFileGenerator = defineGenerator<PluginReactQuery>({
11
10
  name: 'react-query-custom-hook-options-file',
12
- Operations({ operations, generator, plugin, config }) {
13
- const {
14
- options,
15
- options: { output },
16
- name: pluginName,
17
- } = plugin
18
- const driver = usePluginDriver()
19
-
20
- const { getFile } = useOperationManager(generator)
11
+ renderer: jsxRenderer,
12
+ operations(nodes, ctx) {
13
+ const { resolver, config, root } = ctx
14
+ const { output, customOptions, query, group, transformers } = ctx.options
21
15
 
22
- if (!options.customOptions) {
23
- return null
24
- }
16
+ if (!customOptions) return null
25
17
 
26
18
  const override = output.override ?? config.output.override ?? false
27
- const { importPath, name } = options.customOptions
19
+ const { importPath, name } = customOptions
28
20
 
29
- const root = path.resolve(config.root, config.output.path)
21
+ const reactQueryImportPath = query ? query.importPath : '@tanstack/react-query'
30
22
 
31
- const reactQueryImportPath = options.query ? options.query.importPath : '@tanstack/react-query'
23
+ const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)
32
24
 
33
- const getHookFilePath = (operations: Operation[]) => {
34
- const firstOperation = operations[0]
35
- if (firstOperation != null) {
36
- // Get the file of the first generated hook
37
- return getFile(firstOperation, { prefix: 'use' }).path
38
- }
39
- // Get the index file of the hooks directory
40
- return driver.getFile({ name: 'index', extname: '.ts', pluginName }).path
25
+ let hookFilePath: string
26
+ const firstNode = nodes[0]
27
+ if (firstNode) {
28
+ const baseName = resolver.resolveName(firstNode.operationId)
29
+ const hookName = transformName(`use${capitalize(baseName)}`, 'function', transformers)
30
+ const hookFile = resolver.resolveFile(
31
+ { name: hookName, extname: '.ts', tag: firstNode.tags[0] ?? 'default', path: firstNode.path },
32
+ { root, output, group },
33
+ )
34
+ hookFilePath = hookFile.path
35
+ } else {
36
+ hookFilePath = path.resolve(root, 'index.ts')
41
37
  }
42
38
 
43
39
  const ensureExtension = (filePath: string, extname: string) => {
44
- if (path.extname(filePath) === '') {
45
- return filePath + extname
46
- }
40
+ if (path.extname(filePath) === '') return filePath + extname
47
41
  return filePath
48
42
  }
49
43
 
50
- const getExternalFile = (filePath: string, rootPath: string) => {
51
- const actualFilePath = ensureExtension(filePath, '.ts')
52
- return {
53
- baseName: path.basename(actualFilePath) as `${string}.${string}`,
54
- name: path.basename(actualFilePath, path.extname(actualFilePath)),
55
- path: path.resolve(rootPath, actualFilePath),
56
- }
44
+ const basePath = path.dirname(hookFilePath)
45
+ const actualFilePath = ensureExtension(importPath, '.ts')
46
+ const file = {
47
+ baseName: path.basename(actualFilePath) as `${string}.${string}`,
48
+ name: path.basename(actualFilePath, path.extname(actualFilePath)),
49
+ path: path.resolve(basePath, actualFilePath),
57
50
  }
58
51
 
59
- const basePath = path.dirname(getHookFilePath(operations))
60
- const file = getExternalFile(importPath, basePath)
61
-
62
- if (fs.existsSync(file.path) && !override) {
63
- return null
64
- }
52
+ if (fs.existsSync(file.path) && !override) return null
65
53
 
66
54
  return (
67
55
  <File baseName={file.baseName} path={file.path}>
@@ -1,193 +1,133 @@
1
- import { usePluginDriver } from '@kubb/core/hooks'
2
- import type { Operation } from '@kubb/oas'
3
- import { createReactGenerator } from '@kubb/plugin-oas/generators'
4
- import { useOas, useOperationManager } from '@kubb/plugin-oas/hooks'
5
- import { getBanner, getFooter } from '@kubb/plugin-oas/utils'
6
- import { File, Type } from '@kubb/react-fabric'
1
+ import { defineGenerator } from '@kubb/core'
2
+ import { File, jsxRenderer, Type } from '@kubb/renderer-jsx'
3
+ import type { KubbReactNode } from '@kubb/renderer-jsx/types'
7
4
  import { difference } from 'remeda'
8
5
  import type { PluginReactQuery } from '../types'
6
+ import { resolveOperationOverrides, transformName } from '../utils.ts'
9
7
 
10
- export const hookOptionsGenerator = createReactGenerator<PluginReactQuery>({
11
- name: 'react-query-hook-options',
12
- Operations({ operations, plugin, generator }) {
13
- const {
14
- options,
15
- options: { output },
16
- name: pluginName,
17
- } = plugin
18
- const driver = usePluginDriver()
19
-
20
- const oas = useOas()
21
- const { getName, getFile } = useOperationManager(generator)
22
-
23
- if (!options.customOptions) {
24
- return null
25
- }
26
-
27
- const name = 'HookOptions'
28
- const file = driver.getFile({ name, extname: '.ts', pluginName })
29
-
30
- const getOperationOptions = (operation: Operation) => {
31
- const operationOptions = generator.getOptions(operation, operation.method)
32
- return { ...options, ...operationOptions }
33
- }
34
-
35
- const isQuery = (operation: Operation) => {
36
- const operationOptions = getOperationOptions(operation)
37
- return typeof operationOptions.query === 'boolean' ? true : operationOptions.query?.methods.some((method) => operation.method === method)
38
- }
8
+ type QueryOption = PluginReactQuery['resolvedOptions']['query']
9
+ type MutationOption = PluginReactQuery['resolvedOptions']['mutation']
39
10
 
40
- const isMutation = (operation: Operation) => {
41
- const operationOptions = getOperationOptions(operation)
42
- return (
43
- operationOptions.mutation !== false &&
44
- !isQuery(operation) &&
45
- difference(operationOptions.mutation ? operationOptions.mutation.methods : [], operationOptions.query ? operationOptions.query.methods : []).some(
46
- (method) => operation.method === method,
11
+ export const hookOptionsGenerator = defineGenerator<PluginReactQuery>({
12
+ name: 'react-query-hook-options',
13
+ renderer: jsxRenderer,
14
+ operations(nodes, ctx) {
15
+ const { resolver, config, root, adapter } = ctx
16
+ const { output, customOptions, query, mutation, suspense, infinite, group, transformers, override } = ctx.options
17
+
18
+ if (!customOptions) return null
19
+
20
+ const resolvedFile = resolver.resolveFile({ name: 'HookOptions', extname: '.ts' }, { root, output, group })
21
+ const hookOptionsFile = {
22
+ ...resolvedFile,
23
+ baseName: 'HookOptions.ts' as const,
24
+ path: resolvedFile.path.replace(/hookOptions\.ts$/, 'HookOptions.ts'),
25
+ }
26
+
27
+ const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)
28
+
29
+ const imports: KubbReactNode[] = []
30
+ const hookOptions: Record<string, string> = {}
31
+
32
+ for (const node of nodes) {
33
+ const baseName = resolver.resolveName(node.operationId)
34
+ const opOverrides = resolveOperationOverrides(node, override)
35
+ const nodeQuery: QueryOption = 'query' in opOverrides ? (opOverrides.query as QueryOption) : query
36
+ const nodeMutation: MutationOption = 'mutation' in opOverrides ? (opOverrides.mutation as MutationOption) : mutation
37
+ const nodeInfinite = 'infinite' in opOverrides ? opOverrides.infinite : infinite
38
+ const nodeInfiniteOptions = nodeInfinite && typeof nodeInfinite === 'object' ? nodeInfinite : undefined
39
+
40
+ // query: false means "still a query but skip the useQuery hook"
41
+ const isQueryOp =
42
+ nodeQuery === false
43
+ ? !!query && query.methods.some((m) => node.method.toLowerCase() === m.toLowerCase())
44
+ : !!nodeQuery && nodeQuery.methods.some((m) => node.method.toLowerCase() === m.toLowerCase())
45
+ const isMutationOp =
46
+ nodeMutation !== false &&
47
+ !isQueryOp &&
48
+ difference(nodeMutation ? nodeMutation.methods : [], nodeQuery ? nodeQuery.methods : []).some((m) => node.method.toLowerCase() === m.toLowerCase())
49
+ const isSuspenseOp = !!suspense
50
+ const isInfiniteOp = !!nodeInfiniteOptions
51
+
52
+ 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 },
47
58
  )
48
- )
49
- }
50
-
51
- const isSuspense = (operation: Operation) => {
52
- const operationOptions = getOperationOptions(operation)
53
- return !!operationOptions.suspense
54
- }
55
-
56
- const isInfinite = (operation: Operation) => {
57
- const operationOptions = getOperationOptions(operation)
58
- const infiniteOptions = operationOptions.infinite && typeof operationOptions.infinite === 'object' ? operationOptions.infinite : undefined
59
- return !!infiniteOptions
60
- }
61
-
62
- // Query/mutation hooks
63
- const getHookName = (operation: Operation) => {
64
- return getName(operation, { type: 'function', prefix: 'use' })
65
- }
66
-
67
- const getHookFile = (operation: Operation) => {
68
- return getFile(operation, { prefix: 'use' })
69
- }
70
-
71
- // Query hooks
72
- const getQueryHookOptions = (operation: Operation) => {
73
- return getName(operation, { type: 'function', suffix: 'QueryOptions' })
74
- }
75
-
76
- const getQueryHookOptionsImport = (operation: Operation) => {
77
- return <File.Import name={[getQueryHookOptions(operation)]} root={file.path} path={getHookFile(operation).path} />
78
- }
79
-
80
- // Mutation hooks
81
- const getMutationHookOptions = (operation: Operation) => {
82
- return getName(operation, { type: 'function', suffix: 'MutationOptions' })
83
- }
84
-
85
- const getMutationHookOptionsImport = (operation: Operation) => {
86
- return <File.Import name={[getMutationHookOptions(operation)]} root={file.path} path={getHookFile(operation).path} />
87
- }
88
-
89
- // Suspense hooks
90
- const getSuspenseHookName = (operation: Operation) => {
91
- return getName(operation, { type: 'function', prefix: 'use', suffix: 'suspense' })
92
- }
93
-
94
- const getSuspenseHookFile = (operation: Operation) => {
95
- return getFile(operation, { prefix: 'use', suffix: 'suspense' })
96
- }
97
-
98
- const getSuspenseHookOptions = (operation: Operation) => {
99
- return getName(operation, { type: 'function', suffix: 'SuspenseQueryOptions' })
100
- }
101
-
102
- const getSuspenseHookOptionsImport = (operation: Operation) => {
103
- return <File.Import name={[getSuspenseHookOptions(operation)]} root={file.path} path={getSuspenseHookFile(operation).path} />
104
- }
105
-
106
- // Infinite hooks
107
- const getInfiniteHookName = (operation: Operation) => {
108
- return getName(operation, { type: 'function', prefix: 'use', suffix: 'infinite' })
109
- }
110
-
111
- const getInfiniteHookFile = (operation: Operation) => {
112
- return getFile(operation, { prefix: 'use', suffix: 'infinite' })
113
- }
114
-
115
- const getInfiniteHookOptions = (operation: Operation) => {
116
- return getName(operation, { type: 'function', suffix: 'InfiniteQueryOptions' })
117
- }
118
-
119
- const getInfiniteHookOptionsImport = (operation: Operation) => {
120
- return <File.Import name={[getInfiniteHookOptions(operation)]} root={file.path} path={getInfiniteHookFile(operation).path} />
121
- }
122
-
123
- // Suspense infinite hooks
124
- const getSuspenseInfiniteHookName = (operation: Operation) => {
125
- return getName(operation, { type: 'function', prefix: 'use', suffix: 'suspenseInfinite' })
126
- }
127
-
128
- const getSuspenseInfiniteHookFile = (operation: Operation) => {
129
- return getFile(operation, { prefix: 'use', suffix: 'suspenseInfinite' })
130
- }
131
-
132
- const getSuspenseInfiniteHookOptions = (operation: Operation) => {
133
- return getName(operation, { type: 'function', suffix: 'SuspenseInfiniteQueryOptions' })
134
- }
135
-
136
- const getSuspenseInfiniteHookOptionsImport = (operation: Operation) => {
137
- return <File.Import name={[getSuspenseInfiniteHookOptions(operation)]} root={file.path} path={getSuspenseInfiniteHookFile(operation).path} />
138
- }
139
-
140
- const imports = operations
141
- .flatMap((operation) => {
142
- if (isQuery(operation)) {
143
- return [
144
- getQueryHookOptionsImport(operation),
145
- isSuspense(operation) ? getSuspenseHookOptionsImport(operation) : undefined,
146
- isInfinite(operation) ? getInfiniteHookOptionsImport(operation) : undefined,
147
- isSuspense(operation) && isInfinite(operation) ? getSuspenseInfiniteHookOptionsImport(operation) : undefined,
148
- ].filter(Boolean)
149
- }
150
- if (isMutation(operation)) {
151
- return [getMutationHookOptionsImport(operation)]
59
+ imports.push(<File.Import name={[queryOptionsName]} root={hookOptionsFile.path} path={queryHookFile.path} />)
60
+ hookOptions[queryHookName] = `Partial<ReturnType<typeof ${queryOptionsName}>>`
61
+
62
+ 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
+ )
69
+ imports.push(<File.Import name={[suspenseOptionsName]} root={hookOptionsFile.path} path={suspenseHookFile.path} />)
70
+ hookOptions[suspenseHookName] = `Partial<ReturnType<typeof ${suspenseOptionsName}>>`
152
71
  }
153
- return []
154
- })
155
- .filter(Boolean)
156
-
157
- const hookOptions = operations.reduce(
158
- (acc, operation) => {
159
- if (isQuery(operation)) {
160
- acc[getHookName(operation)] = `Partial<ReturnType<typeof ${getQueryHookOptions(operation)}>>`
161
- if (isSuspense(operation)) {
162
- acc[getSuspenseHookName(operation)] = `Partial<ReturnType<typeof ${getSuspenseHookOptions(operation)}>>`
163
- }
164
- if (isInfinite(operation)) {
165
- acc[getInfiniteHookName(operation)] = `Partial<ReturnType<typeof ${getInfiniteHookOptions(operation)}>>`
166
- }
167
- if (isSuspense(operation) && isInfinite(operation)) {
168
- acc[getSuspenseInfiniteHookName(operation)] = `Partial<ReturnType<typeof ${getSuspenseInfiniteHookOptions(operation)}>>`
72
+
73
+ if (isInfiniteOp) {
74
+ // Validate queryParam
75
+ const normalizeKey = (key: string) => key.replace(/\?$/, '')
76
+ const queryParamKeys = node.parameters.filter((p) => p.in === 'query').map((p) => p.name)
77
+ const hasQueryParam = nodeInfiniteOptions!.queryParam ? queryParamKeys.some((k) => normalizeKey(k) === nodeInfiniteOptions!.queryParam) : false
78
+
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
+ )
86
+ imports.push(<File.Import name={[infiniteOptionsName]} root={hookOptionsFile.path} path={infiniteHookFile.path} />)
87
+ hookOptions[infiniteHookName] = `Partial<ReturnType<typeof ${infiniteOptionsName}>>`
88
+
89
+ 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
+ )
96
+ imports.push(<File.Import name={[suspenseInfiniteOptionsName]} root={hookOptionsFile.path} path={suspenseInfiniteHookFile.path} />)
97
+ hookOptions[suspenseInfiniteHookName] = `Partial<ReturnType<typeof ${suspenseInfiniteOptionsName}>>`
98
+ }
169
99
  }
170
100
  }
171
- if (isMutation(operation)) {
172
- acc[getHookName(operation)] = `Partial<ReturnType<typeof ${getMutationHookOptions(operation)}>>`
173
- }
174
- return acc
175
- },
176
- {} as Record<string, string>,
177
- )
101
+ }
102
+
103
+ 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
+ )
110
+ imports.push(<File.Import name={[mutationOptionsName]} root={hookOptionsFile.path} path={mutationHookFile.path} />)
111
+ hookOptions[mutationHookName] = `Partial<ReturnType<typeof ${mutationOptionsName}>>`
112
+ }
113
+ }
114
+
115
+ const name = 'HookOptions'
178
116
 
179
117
  return (
180
118
  <File
181
- baseName={file.baseName}
182
- path={file.path}
183
- meta={file.meta}
184
- banner={getBanner({ oas, output, config: driver.config })}
185
- footer={getFooter({ oas, output })}
119
+ baseName={hookOptionsFile.baseName}
120
+ path={hookOptionsFile.path}
121
+ meta={hookOptionsFile.meta}
122
+ banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
123
+ footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
186
124
  >
187
125
  {imports}
188
126
  <File.Source name={name} isExportable isIndexable isTypeOnly>
189
127
  <Type export name={name}>
190
- {`{ ${Object.keys(hookOptions).map((key) => `${JSON.stringify(key)}: ${hookOptions[key]}`)} }`}
128
+ {`{ ${Object.keys(hookOptions)
129
+ .map((key) => `${JSON.stringify(key)}: ${hookOptions[key]}`)
130
+ .join(', ')} }`}
191
131
  </Type>
192
132
  </File.Source>
193
133
  </File>