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

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 (54) hide show
  1. package/LICENSE +17 -10
  2. package/README.md +34 -85
  3. package/dist/components-Dow6tde8.js +1459 -0
  4. package/dist/components-Dow6tde8.js.map +1 -0
  5. package/dist/components-HwdCDefj.cjs +1603 -0
  6. package/dist/components-HwdCDefj.cjs.map +1 -0
  7. package/dist/components.cjs +1 -1
  8. package/dist/components.d.ts +49 -179
  9. package/dist/components.js +1 -1
  10. package/dist/generators-CcOmnTPa.cjs +1454 -0
  11. package/dist/generators-CcOmnTPa.cjs.map +1 -0
  12. package/dist/generators-yfZr_qfT.js +1412 -0
  13. package/dist/generators-yfZr_qfT.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 +197 -126
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.ts +4 -4
  20. package/dist/index.js +193 -126
  21. package/dist/index.js.map +1 -1
  22. package/dist/types-DG_OxOym.d.ts +363 -0
  23. package/extension.yaml +911 -0
  24. package/package.json +59 -64
  25. package/src/components/InfiniteQuery.tsx +79 -138
  26. package/src/components/InfiniteQueryOptions.tsx +55 -166
  27. package/src/components/Mutation.tsx +74 -111
  28. package/src/components/MutationOptions.tsx +61 -80
  29. package/src/components/Query.tsx +66 -142
  30. package/src/components/QueryOptions.tsx +56 -138
  31. package/src/components/SuspenseInfiniteQuery.tsx +79 -138
  32. package/src/components/SuspenseInfiniteQueryOptions.tsx +55 -166
  33. package/src/components/SuspenseQuery.tsx +66 -152
  34. package/src/generators/customHookOptionsFileGenerator.tsx +37 -51
  35. package/src/generators/hookOptionsGenerator.tsx +111 -174
  36. package/src/generators/infiniteQueryGenerator.tsx +158 -178
  37. package/src/generators/mutationGenerator.tsx +112 -139
  38. package/src/generators/queryGenerator.tsx +128 -142
  39. package/src/generators/suspenseInfiniteQueryGenerator.tsx +157 -156
  40. package/src/generators/suspenseQueryGenerator.tsx +126 -152
  41. package/src/index.ts +1 -1
  42. package/src/plugin.ts +134 -187
  43. package/src/resolvers/resolverReactQuery.ts +107 -0
  44. package/src/types.ts +172 -49
  45. package/src/utils.ts +10 -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
  54. package/dist/types-D5S7Ny9r.d.ts +0 -270
@@ -1,75 +1,61 @@
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'
9
7
 
10
- export const customHookOptionsFileGenerator = createReactGenerator<PluginReactQuery>({
8
+ export const customHookOptionsFileGenerator = defineGenerator<PluginReactQuery>({
11
9
  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)
10
+ renderer: jsxRenderer,
11
+ operations(nodes, ctx) {
12
+ const { resolver, config, root } = ctx
13
+ const { output, customOptions, query, group } = ctx.options
21
14
 
22
- if (!options.customOptions) {
23
- return null
24
- }
15
+ if (!customOptions) return null
25
16
 
26
17
  const override = output.override ?? config.output.override ?? false
27
- const { importPath, name } = options.customOptions
18
+ const { importPath, name } = customOptions
19
+ const hookOptionsName = resolver.resolveHookOptionsName()
20
+ const customHookOptionsName = resolver.resolveCustomHookOptionsName()
28
21
 
29
- const root = path.resolve(config.root, config.output.path)
22
+ const reactQueryImportPath = query ? query.importPath : '@tanstack/react-query'
30
23
 
31
- const reactQueryImportPath = options.query ? options.query.importPath : '@tanstack/react-query'
32
-
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
24
+ let hookFilePath: string
25
+ const firstNode = nodes[0]
26
+ if (firstNode) {
27
+ const hookName = resolver.resolveQueryName(firstNode)
28
+ const hookFile = resolver.resolveFile(
29
+ { name: hookName, extname: '.ts', tag: firstNode.tags[0] ?? 'default', path: firstNode.path },
30
+ { root, output, group },
31
+ )
32
+ hookFilePath = hookFile.path
33
+ } else {
34
+ hookFilePath = path.resolve(root, 'index.ts')
41
35
  }
42
36
 
43
37
  const ensureExtension = (filePath: string, extname: string) => {
44
- if (path.extname(filePath) === '') {
45
- return filePath + extname
46
- }
38
+ if (path.extname(filePath) === '') return filePath + extname
47
39
  return filePath
48
40
  }
49
41
 
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
- }
42
+ const basePath = path.dirname(hookFilePath)
43
+ const actualFilePath = ensureExtension(importPath, '.ts')
44
+ const file = {
45
+ baseName: path.basename(actualFilePath) as `${string}.${string}`,
46
+ name: path.basename(actualFilePath, path.extname(actualFilePath)),
47
+ path: path.resolve(basePath, actualFilePath),
57
48
  }
58
49
 
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
- }
50
+ if (fs.existsSync(file.path) && !override) return null
65
51
 
66
52
  return (
67
53
  <File baseName={file.baseName} path={file.path}>
68
54
  <File.Import name={['QueryClient']} path={reactQueryImportPath} isTypeOnly />
69
55
  <File.Import name={['useQueryClient']} path={reactQueryImportPath} />
70
- <File.Import name={['HookOptions']} root={file.path} path={path.resolve(root, './index.ts')} />
56
+ <File.Import name={[hookOptionsName]} root={file.path} path={path.resolve(root, './index.ts')} />
71
57
  <File.Source name={file.name} isExportable isIndexable>
72
- <Function name="getCustomHookOptions" params="{ queryClient }: { queryClient: QueryClient }" returnType="Partial<HookOptions>">
58
+ <Function name={customHookOptionsName} params="{ queryClient }: { queryClient: QueryClient }" returnType={`Partial<${hookOptionsName}>`}>
73
59
  {`return {
74
60
  // TODO: Define custom hook options here
75
61
  // Example:
@@ -82,13 +68,13 @@ export const customHookOptionsFileGenerator = createReactGenerator<PluginReactQu
82
68
  </Function>
83
69
  <Function
84
70
  name={name}
85
- generics="T extends keyof HookOptions"
71
+ generics={`T extends keyof ${hookOptionsName}`}
86
72
  params="{ hookName, operationId }: { hookName: T, operationId: string }"
87
- returnType="HookOptions[T]"
73
+ returnType={`${hookOptionsName}[T]`}
88
74
  export
89
75
  >
90
76
  {`const queryClient = useQueryClient()
91
- const customOptions = getCustomHookOptions({ queryClient })
77
+ const customOptions = ${customHookOptionsName}({ queryClient })
92
78
  return customOptions[hookName] ?? {}`}
93
79
  </Function>
94
80
  </File.Source>
@@ -1,193 +1,130 @@
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 { getOperationParameters } from '@internals/shared'
2
+ import { defineGenerator } from '@kubb/core'
3
+ import { File, jsxRenderer, Type } from '@kubb/renderer-jsx'
4
+ import type { KubbReactNode } from '@kubb/renderer-jsx/types'
7
5
  import { difference } from 'remeda'
8
6
  import type { PluginReactQuery } from '../types'
7
+ import { resolveOperationOverrides } from '../utils.ts'
9
8
 
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
- }
9
+ type QueryOption = PluginReactQuery['resolvedOptions']['query']
10
+ type MutationOption = PluginReactQuery['resolvedOptions']['mutation']
26
11
 
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
- }
39
-
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,
12
+ export const hookOptionsGenerator = defineGenerator<PluginReactQuery>({
13
+ name: 'react-query-hook-options',
14
+ renderer: jsxRenderer,
15
+ operations(nodes, ctx) {
16
+ const { resolver, config, root, adapter } = ctx
17
+ const { output, customOptions, query, mutation, suspense, infinite, group, override } = ctx.options
18
+
19
+ if (!customOptions) return null
20
+
21
+ const name = resolver.resolveHookOptionsName()
22
+ const resolvedFile = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group })
23
+ const hookOptionsFile = {
24
+ ...resolvedFile,
25
+ baseName: `${name}.ts` as const,
26
+ path: resolvedFile.path.replace(/[^/\\]+\.ts$/, `${name}.ts`),
27
+ }
28
+
29
+ const imports: KubbReactNode[] = []
30
+ const hookOptions: Record<string, string> = {}
31
+
32
+ for (const node of nodes) {
33
+ const opOverrides = resolveOperationOverrides(node, override)
34
+ const nodeQuery: QueryOption = 'query' in opOverrides ? (opOverrides.query as QueryOption) : query
35
+ const nodeMutation: MutationOption = 'mutation' in opOverrides ? (opOverrides.mutation as MutationOption) : mutation
36
+ const nodeInfinite = 'infinite' in opOverrides ? opOverrides.infinite : infinite
37
+ const nodeInfiniteOptions = nodeInfinite && typeof nodeInfinite === 'object' ? nodeInfinite : undefined
38
+
39
+ // query: false means "still a query but skip the useQuery hook"
40
+ const isQueryOp =
41
+ nodeQuery === false
42
+ ? !!query && query.methods.some((m) => node.method.toLowerCase() === m.toLowerCase())
43
+ : !!nodeQuery && nodeQuery.methods.some((m) => node.method.toLowerCase() === m.toLowerCase())
44
+ const isMutationOp =
45
+ nodeMutation !== false &&
46
+ !isQueryOp &&
47
+ difference(nodeMutation ? nodeMutation.methods : [], nodeQuery ? nodeQuery.methods : []).some((m) => node.method.toLowerCase() === m.toLowerCase())
48
+ const isSuspenseOp = !!suspense
49
+ const isInfiniteOp = !!nodeInfiniteOptions
50
+
51
+ if (isQueryOp) {
52
+ const queryOptionsName = resolver.resolveQueryOptionsName(node)
53
+ const queryHookName = resolver.resolveQueryName(node)
54
+ const queryHookFile = resolver.resolveFile(
55
+ { name: queryHookName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
56
+ { root, output, group },
47
57
  )
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)
58
+ imports.push(<File.Import name={[queryOptionsName]} root={hookOptionsFile.path} path={queryHookFile.path} />)
59
+ hookOptions[queryHookName] = `Partial<ReturnType<typeof ${queryOptionsName}>>`
60
+
61
+ if (isSuspenseOp) {
62
+ const suspenseOptionsName = resolver.resolveSuspenseQueryOptionsName(node)
63
+ const suspenseHookName = resolver.resolveSuspenseQueryName(node)
64
+ const suspenseHookFile = resolver.resolveFile(
65
+ { name: suspenseHookName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
66
+ { root, output, group },
67
+ )
68
+ imports.push(<File.Import name={[suspenseOptionsName]} root={hookOptionsFile.path} path={suspenseHookFile.path} />)
69
+ hookOptions[suspenseHookName] = `Partial<ReturnType<typeof ${suspenseOptionsName}>>`
149
70
  }
150
- if (isMutation(operation)) {
151
- return [getMutationHookOptionsImport(operation)]
152
- }
153
- return []
154
- })
155
- .filter(Boolean)
156
71
 
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)}>>`
72
+ if (isInfiniteOp) {
73
+ // Validate queryParam
74
+ const normalizeKey = (key: string) => key.replace(/\?$/, '')
75
+ const queryParamKeys = getOperationParameters(node).query.map((p) => p.name)
76
+ const hasQueryParam = nodeInfiniteOptions!.queryParam ? queryParamKeys.some((k) => normalizeKey(k) === nodeInfiniteOptions!.queryParam) : false
77
+
78
+ if (hasQueryParam) {
79
+ const infiniteOptionsName = resolver.resolveInfiniteQueryOptionsName(node)
80
+ const infiniteHookName = resolver.resolveInfiniteQueryName(node)
81
+ const infiniteHookFile = resolver.resolveFile(
82
+ { name: infiniteHookName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
83
+ { root, output, group },
84
+ )
85
+ imports.push(<File.Import name={[infiniteOptionsName]} root={hookOptionsFile.path} path={infiniteHookFile.path} />)
86
+ hookOptions[infiniteHookName] = `Partial<ReturnType<typeof ${infiniteOptionsName}>>`
87
+
88
+ if (isSuspenseOp) {
89
+ const suspenseInfiniteOptionsName = resolver.resolveSuspenseInfiniteQueryOptionsName(node)
90
+ const suspenseInfiniteHookName = resolver.resolveSuspenseInfiniteQueryName(node)
91
+ const suspenseInfiniteHookFile = resolver.resolveFile(
92
+ { name: suspenseInfiniteHookName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
93
+ { root, output, group },
94
+ )
95
+ imports.push(<File.Import name={[suspenseInfiniteOptionsName]} root={hookOptionsFile.path} path={suspenseInfiniteHookFile.path} />)
96
+ hookOptions[suspenseInfiniteHookName] = `Partial<ReturnType<typeof ${suspenseInfiniteOptionsName}>>`
97
+ }
166
98
  }
167
- if (isSuspense(operation) && isInfinite(operation)) {
168
- acc[getSuspenseInfiniteHookName(operation)] = `Partial<ReturnType<typeof ${getSuspenseInfiniteHookOptions(operation)}>>`
169
- }
170
- }
171
- if (isMutation(operation)) {
172
- acc[getHookName(operation)] = `Partial<ReturnType<typeof ${getMutationHookOptions(operation)}>>`
173
99
  }
174
- return acc
175
- },
176
- {} as Record<string, string>,
177
- )
100
+ }
101
+
102
+ if (isMutationOp) {
103
+ const mutationOptionsName = resolver.resolveMutationOptionsName(node)
104
+ const mutationHookName = resolver.resolveMutationName(node)
105
+ const mutationHookFile = resolver.resolveFile(
106
+ { name: mutationHookName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
107
+ { root, output, group },
108
+ )
109
+ imports.push(<File.Import name={[mutationOptionsName]} root={hookOptionsFile.path} path={mutationHookFile.path} />)
110
+ hookOptions[mutationHookName] = `Partial<ReturnType<typeof ${mutationOptionsName}>>`
111
+ }
112
+ }
178
113
 
179
114
  return (
180
115
  <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 })}
116
+ baseName={hookOptionsFile.baseName}
117
+ path={hookOptionsFile.path}
118
+ meta={hookOptionsFile.meta}
119
+ banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
120
+ footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
186
121
  >
187
122
  {imports}
188
123
  <File.Source name={name} isExportable isIndexable isTypeOnly>
189
124
  <Type export name={name}>
190
- {`{ ${Object.keys(hookOptions).map((key) => `${JSON.stringify(key)}: ${hookOptions[key]}`)} }`}
125
+ {`{ ${Object.keys(hookOptions)
126
+ .map((key) => `${JSON.stringify(key)}: ${hookOptions[key]}`)
127
+ .join(', ')} }`}
191
128
  </Type>
192
129
  </File.Source>
193
130
  </File>