@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
package/extension.yaml ADDED
@@ -0,0 +1,938 @@
1
+ $schema: https://kubb.dev/schemas/extension.json
2
+ kind: plugin
3
+ id: plugin-react-query
4
+ name: React Query
5
+ description: Generate React Query hooks (useQuery, useMutation) from OpenAPI specifications.
6
+ category: framework
7
+ type: official
8
+ npmPackage: "@kubb/plugin-react-query"
9
+ docsPath: /plugins/plugin-react-query
10
+ repo: https://github.com/kubb-labs/plugins
11
+ maintainers:
12
+ - name: Stijn Van Hulle
13
+ github: stijnvanhulle
14
+ compatibility:
15
+ kubb: ">=5.0.0"
16
+ node: ">=22"
17
+ tags:
18
+ - react-query
19
+ - tanstack-query
20
+ - react
21
+ - hooks
22
+ - data-fetching
23
+ - codegen
24
+ - openapi
25
+ dependencies:
26
+ - plugin-ts
27
+ - plugin-client
28
+ resources:
29
+ documentation: https://kubb.dev/plugins/plugin-react-query
30
+ repository: https://github.com/kubb-labs/plugins
31
+ issues: https://github.com/kubb-labs/plugins/issues
32
+ changelog: https://github.com/kubb-labs/plugins/blob/main/packages/plugin-react-query/CHANGELOG.md
33
+ codesandbox: https://codesandbox.io/p/github/kubb-labs/plugins/main/examples/react-query
34
+ featured: true
35
+ icon:
36
+ light: https://kubb.dev/feature/tanstack.svg
37
+ intro: |
38
+ # @kubb/plugin-react-query
39
+
40
+ Generate type-safe React Query hooks from your OpenAPI schema for data fetching, caching, and synchronization.
41
+ options:
42
+ - name: output
43
+ type: Output
44
+ required: false
45
+ default: "{ path: 'hooks', barrelType: 'named' }"
46
+ description: Specify the export location for the files and define the behavior of the output.
47
+ properties:
48
+ - name: path
49
+ type: string
50
+ required: true
51
+ description: Output directory or file for the generated code, relative to the global `output.path`.
52
+ tip: |
53
+ if `output.path` is a file, `group` cannot be used.
54
+ default: "'hooks'"
55
+ - name: barrelType
56
+ type: "'all' | 'named' | 'propagate' | false"
57
+ required: false
58
+ default: "'named'"
59
+ description: Specify what to export and optionally disable barrel-file generation.
60
+ tip: |
61
+ Using `propagate` will prevent a plugin from creating a barrel file, but it will still propagate, allowing [`output.barrelType`](https://kubb.dev/docs/5.x/configuration#output-barreltype) to export the specific function or type.
62
+ examples:
63
+ - name: all
64
+ files:
65
+ - lang: typescript
66
+ code: |
67
+ export * from './gen/petService.ts'
68
+ twoslash: false
69
+ - name: named
70
+ files:
71
+ - lang: typescript
72
+ code: |
73
+ export { PetService } from './gen/petService.ts'
74
+ twoslash: false
75
+ - name: propagate
76
+ files:
77
+ - lang: typescript
78
+ code: ""
79
+ twoslash: false
80
+ - name: "false"
81
+ files:
82
+ - lang: typescript
83
+ code: ""
84
+ twoslash: false
85
+ - name: banner
86
+ type: "string | ((node: RootNode) => string)"
87
+ required: false
88
+ description: Add a banner comment at the top of every generated file. Accepts a static string or a function that receives the `RootNode` and returns a string.
89
+ - name: footer
90
+ type: "string | ((node: RootNode) => string)"
91
+ required: false
92
+ description: Add a footer comment at the end of every generated file. Accepts a static string or a function that receives the `RootNode` and returns a string.
93
+ - name: override
94
+ type: boolean
95
+ required: false
96
+ default: "false"
97
+ description: Whether Kubb overrides existing external files that can be generated if they already exist.
98
+ - name: contentType
99
+ type: "'application/json' | (string & {})"
100
+ required: false
101
+ description: |
102
+ Define which content type to use.
103
+
104
+ By default, Kubb uses the first JSON-valid media type.
105
+ - name: group
106
+ type: Group
107
+ required: false
108
+ description: |
109
+ Grouping combines files in a folder based on a specific `type`.
110
+ examples:
111
+ - name: kubb.config.ts
112
+ files:
113
+ - lang: typescript
114
+ code: |
115
+ group: {
116
+ type: 'tag',
117
+ name({ group }) {
118
+ return `${group}Controller`
119
+ }
120
+ }
121
+ twoslash: false
122
+ body: |
123
+ With the configuration above, the generator emits:
124
+
125
+ ```text
126
+ .
127
+ ├── src/
128
+ │ └── petController/
129
+ │ │ ├── addPet.ts
130
+ │ │ └── getPet.ts
131
+ │ └── storeController/
132
+ │ ├── createStore.ts
133
+ │ └── getStoreById.ts
134
+ ├── petStore.yaml
135
+ ├── kubb.config.ts
136
+ └── package.json
137
+ ```
138
+ properties:
139
+ - name: type
140
+ type: "'tag'"
141
+ required: true
142
+ description: Specify the property to group files by. Required when `group` is defined.
143
+ note: |
144
+ `Required: true*` means this is required only when the `group` option is used. The `group` option itself is optional.
145
+ body: |
146
+ - `'tag'`: Uses the first tag from `operation.getTags().at(0)?.name`
147
+ - name: name
148
+ type: "(context: GroupContext) => string"
149
+ required: false
150
+ default: (ctx) => `${ctx.group}Controller`
151
+ description: Return the name of a group based on the group name. This is used for file and identifier generation.
152
+ - name: client
153
+ type: ClientImportPath & { clientType?, dataReturnType?, baseURL?, bundle? }
154
+ required: false
155
+ description: Client configuration for HTTP request generation.
156
+ properties:
157
+ - name: importPath
158
+ type: string
159
+ required: false
160
+ description: Path to the client used for API calls. Supports both relative and absolute paths.
161
+ details:
162
+ - title: When to use `importPath`
163
+ body: |
164
+ Use `importPath` when you want to:
165
+
166
+ - **Customize the HTTP client**: Provide your own client implementation with custom configurations (e.g., baseURL, headers, interceptors)
167
+ - **Add authentication**: Include authentication tokens or other security mechanisms in your client
168
+ - **Override default behavior**: Replace the default Kubb client with your own implementation
169
+ - title: Default behavior
170
+ body: |
171
+ When `importPath` is not specified:
172
+
173
+ - If `bundle: false` (default): Uses `@kubb/plugin-client/clients/${client}` where client is either `axios` or `fetch`.
174
+ - If `bundle: true`: Bundles the client into `.kubb/fetch.ts`.
175
+ - title: Import structure
176
+ body: "Generated code imports the client as a default import and the runtime types as named type imports:"
177
+ codeBlock:
178
+ lang: typescript
179
+ code: |-
180
+ /**
181
+ * Generated by Kubb (https://kubb.dev/).
182
+ * Do not edit manually.
183
+ */
184
+ import client from '${client.importPath}'
185
+ import type { RequestConfig, ResponseErrorConfig } from '${client.importPath}'
186
+ // ... rest of generated file
187
+ important: |
188
+ When using `importPath` with query plugins such as `@kubb/plugin-react-query` or `@kubb/plugin-vue-query`, the generated hooks also import a `Client` type alongside `RequestConfig` and `ResponseErrorConfig` from the custom module. Your custom client module **must** export all three types — if any is missing, TypeScript will report an unresolvable import error.
189
+ codeBlock:
190
+ lang: typescript
191
+ title: client.ts
192
+ code: |
193
+ export type RequestConfig<TData = unknown> = {
194
+ url?: string
195
+ method: 'GET' | 'PUT' | 'PATCH' | 'POST' | 'DELETE'
196
+ params?: object
197
+ data?: TData | FormData
198
+ responseType?: 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'
199
+ signal?: AbortSignal
200
+ headers?: HeadersInit
201
+ }
202
+
203
+ export type ResponseConfig<TData = unknown> = {
204
+ data: TData
205
+ status: number
206
+ statusText: string
207
+ }
208
+
209
+ export type ResponseErrorConfig<TError = unknown> = TError
210
+
211
+ // The Client type alias is required when using query plugins
212
+ export type Client = <TData, _TError = unknown, TVariables = unknown>(
213
+ config: RequestConfig<TVariables>
214
+ ) => Promise<ResponseConfig<TData>>
215
+
216
+ export const client: Client = async (config) => { /* ... */ }
217
+ export default client
218
+ examples:
219
+ - name: kubb.config.ts
220
+ files:
221
+ - lang: typescript
222
+ code: |
223
+ import { defineConfig } from 'kubb'
224
+ import { pluginClient } from '@kubb/plugin-client'
225
+
226
+ export default defineConfig({
227
+ // ...
228
+ plugins: [
229
+ pluginClient({
230
+ importPath: './src/client.ts', // Path to your custom client
231
+ }),
232
+ ],
233
+ })
234
+ twoslash: false
235
+ tip: |
236
+ Learn more about defining a custom client [here](https://kubb.dev/plugins/plugin-client#importpath).
237
+ - name: dataReturnType
238
+ type: "'data' | 'full'"
239
+ required: false
240
+ default: "'data'"
241
+ description: |
242
+ Return type used when calling the client.
243
+
244
+ - `'data'` returns `ResponseConfig['data']`.
245
+ - `'full'` returns the full `ResponseConfig`.
246
+ examples:
247
+ - name: data
248
+ files:
249
+ - lang: typescript
250
+ code: |
251
+ export async function getPetById<TData>(
252
+ petId: GetPetByIdPathParams,
253
+ ): Promise<ResponseConfig<TData>['data']> {
254
+ ...
255
+ }
256
+ twoslash: false
257
+ - name: full
258
+ files:
259
+ - lang: typescript
260
+ code: |
261
+ export async function getPetById<TData>(
262
+ petId: GetPetByIdPathParams,
263
+ ): Promise<ResponseConfig<TData>> {
264
+ ...
265
+ }
266
+ twoslash: false
267
+ - name: baseURL
268
+ type: string
269
+ required: false
270
+ description: Sets a custom base URL for all generated calls. When not set, the base URL is automatically taken from the OAS spec via the adapter (e.g. the `servers[0].url` field).
271
+ - name: clientType
272
+ type: "'function' | 'class'"
273
+ required: false
274
+ default: "'function'"
275
+ description: Specify whether to use function-based or class-based clients.
276
+ warning: |
277
+ This plugin is only compatible with `clientType: 'function'` (the default). If `clientType: 'class'` is detected, the plugin will automatically generate its own inline function-based client instead of importing from `@kubb/plugin-client`.
278
+ - name: bundle
279
+ type: boolean
280
+ required: false
281
+ default: "false"
282
+ description: Controls whether the HTTP client runtime is copied into the generated `.kubb` directory.
283
+ body: |
284
+ - `true` adds a `.kubb/fetch.ts` file containing the selected client template (fetch or axios). Generated clients remain self-contained.
285
+ - `false` keeps generated clients slim by importing the shared runtime from `@kubb/plugin-client/clients/{client}`.
286
+ - Override this behavior by providing a custom `client.importPath`.
287
+ - name: paramsType
288
+ type: "'object' | 'inline'"
289
+ required: false
290
+ default: "'inline'"
291
+ description: Defines how parameters are passed to generated functions. Switch between object-style parameters and inline parameters.
292
+ tip: |
293
+ When `paramsType` is set to `'object'`, `pathParams` will also be set to `'object'`.
294
+ body: |
295
+ - `'object'` returns params and pathParams as an object.
296
+ - `'inline'` returns params as comma-separated params.
297
+ examples:
298
+ - name: object
299
+ files:
300
+ - lang: typescript
301
+ code: |
302
+ export async function deletePet(
303
+ {
304
+ petId,
305
+ headers,
306
+ }: {
307
+ petId: DeletePetPathParams['petId']
308
+ headers?: DeletePetHeaderParams
309
+ },
310
+ config: Partial<RequestConfig> = {},
311
+ ) {
312
+ ...
313
+ }
314
+ twoslash: false
315
+ - name: inline
316
+ files:
317
+ - lang: typescript
318
+ code: |
319
+ export async function deletePet(
320
+ petId: DeletePetPathParams['petId'],
321
+ headers?: DeletePetHeaderParams,
322
+ config: Partial<RequestConfig> = {}
323
+ ){
324
+ ...
325
+ }
326
+ twoslash: false
327
+ - name: paramsCasing
328
+ type: "'camelcase'"
329
+ required: false
330
+ description: Transform parameter names to a specific casing format for path, query, and header parameters in generated client code.
331
+ important: |
332
+ When using `paramsCasing`, ensure that `@kubb/plugin-ts` also has the same `paramsCasing` setting. This option automatically maps transformed parameter names back to their original API names in HTTP requests.
333
+ body: |
334
+ - `'camelcase'` transforms parameter names to camelCase.
335
+ examples:
336
+ - name: With paramsCasing camelcase
337
+ files:
338
+ - lang: typescript
339
+ code: |
340
+ // Function parameters use camelCase
341
+ export async function deletePet(
342
+ petId: DeletePetPathParams['petId'], // ✓ camelCase
343
+ headers?: DeletePetHeaderParams,
344
+ config: Partial<RequestConfig> = {}
345
+ ) {
346
+ // Automatically maps back to original name for the API
347
+ const pet_id = petId
348
+
349
+ return fetch({
350
+ method: 'DELETE',
351
+ url: `/pet/${pet_id}`, // Uses original API parameter name
352
+ ...
353
+ })
354
+ }
355
+ twoslash: false
356
+ - name: Without paramsCasing
357
+ files:
358
+ - lang: typescript
359
+ code: |
360
+ // Parameters use original API naming
361
+ export async function deletePet(
362
+ pet_id: DeletePetPathParams['pet_id'], // Original naming
363
+ headers?: DeletePetHeaderParams,
364
+ config: Partial<RequestConfig> = {}
365
+ ) {
366
+ return fetch({
367
+ method: 'DELETE',
368
+ url: `/pet/${pet_id}`,
369
+ ...
370
+ })
371
+ }
372
+ twoslash: false
373
+ tip: |
374
+ The client automatically generates mapping code to convert camelCase parameter names back to the original API format. You write code with developer-friendly camelCase names, but HTTP requests use the exact parameter names from your OpenAPI specification.
375
+ - name: pathParamsType
376
+ type: "'object' | 'inline'"
377
+ required: false
378
+ default: "'inline'"
379
+ description: Defines how pathParams are passed to generated functions.
380
+ body: |
381
+ - `'object'` returns pathParams as an object.
382
+ - `'inline'` returns pathParams as comma-separated params.
383
+ examples:
384
+ - name: object
385
+ files:
386
+ - lang: typescript
387
+ code: |
388
+ export async function getPetById(
389
+ { petId }: GetPetByIdPathParams,
390
+ ) {
391
+ ...
392
+ }
393
+ twoslash: false
394
+ - name: inline
395
+ files:
396
+ - lang: typescript
397
+ code: |
398
+ export async function getPetById(
399
+ petId: GetPetByIdPathParams,
400
+ ) {
401
+ ...
402
+ }
403
+ twoslash: false
404
+ - name: parser
405
+ type: "'client' | 'zod'"
406
+ required: false
407
+ default: "'client'"
408
+ description: Parser used before returning data.
409
+ body: |
410
+ - `'zod'` uses `@kubb/plugin-zod` to parse data.
411
+ - `'client'` returns data without parsing.
412
+ - name: infinite
413
+ type: Infinite | false
414
+ required: false
415
+ default: "false"
416
+ description: |
417
+ Generate an `infiniteQuery` hook for paginated queries. Pass `false` to disable.
418
+ codeBlock:
419
+ lang: typescript
420
+ title: Infinite
421
+ code: |
422
+ type Infinite = {
423
+ /**
424
+ * Specify the params key used for `pageParam`.
425
+ * @default `'id'`
426
+ */
427
+ queryParam: string
428
+ /**
429
+ * Which field of the data will be used, set it to undefined when no cursor is known.
430
+ * @deprecated Use `nextParam` and `previousParam` instead for more flexible pagination handling.
431
+ */
432
+ cursorParam?: string | undefined
433
+ /**
434
+ * Which field of the data will be used to get the cursor for the next page.
435
+ * Supports dot notation (e.g. 'pagination.next.id') or array path (e.g. ['pagination', 'next', 'id']) to access nested fields.
436
+ */
437
+ nextParam?: string | string[] | undefined
438
+ /**
439
+ * Which field of the data will be used to get the cursor for the previous page.
440
+ * Supports dot notation (e.g. 'pagination.prev.id') or array path (e.g. ['pagination', 'prev', 'id']) to access nested fields.
441
+ */
442
+ previousParam?: string | string[] | undefined
443
+ /**
444
+ * The initial value, the value of the first page.
445
+ * @default `0`
446
+ */
447
+ initialPageParam: unknown
448
+ } | false
449
+ properties:
450
+ - name: queryParam
451
+ type: string
452
+ required: false
453
+ default: "'id'"
454
+ description: Specify the params key used for `pageParam`.
455
+ - name: initialPageParam
456
+ type: unknown
457
+ required: false
458
+ default: "0"
459
+ description: Specify the initial page param value.
460
+ - name: cursorParam
461
+ type: string | undefined
462
+ required: false
463
+ deprecated:
464
+ note: "`cursorParam` is deprecated. Use `nextParam` and `previousParam` instead for more flexible pagination handling."
465
+ description: Which field of the data will be used, set it to undefined when no cursor is known.
466
+ - name: nextParam
467
+ type: string | string[] | undefined
468
+ required: false
469
+ description: |
470
+ Which field of the data will be used to get the cursor for the next page.
471
+
472
+ Supports dot notation (e.g. `'pagination.next.id'`) or array path (e.g. `['pagination', 'next', 'id']`) to access nested fields.
473
+ - name: previousParam
474
+ type: string | string[] | undefined
475
+ required: false
476
+ description: |
477
+ Which field of the data will be used to get the cursor for the previous page.
478
+
479
+ Supports dot notation (e.g. `'pagination.prev.id'`) or array path (e.g. `['pagination', 'prev', 'id']`) to access nested fields.
480
+ - name: query
481
+ type: Query
482
+ required: false
483
+ description: |
484
+ Override some `useQuery` behaviors.
485
+
486
+ To disable the creation of hooks pass `false`, this will result in only creating `queryOptions`.
487
+ codeBlock:
488
+ lang: typescript
489
+ title: Query
490
+ code: |
491
+ type Query = {
492
+ methods: Array<HttpMethod>
493
+ importPath?: string
494
+ } | false
495
+ properties:
496
+ - name: methods
497
+ type: Array<HttpMethod>
498
+ required: false
499
+ default: "['get']"
500
+ description: Define which HttpMethods can be used for queries.
501
+ - name: importPath
502
+ type: string
503
+ required: false
504
+ default: "'@tanstack/react-query'"
505
+ description: |
506
+ Path to the `useQuery` that will be used to do the `useQuery` functionality.
507
+ It will be used as `import { useQuery } from '${hook.importPath}'`.
508
+ It allows both relative and absolute path. The path will be applied as is, so a relative path should be based on the file being generated.
509
+ - name: queryKey
510
+ type: "(props: { operation: Operation; schemas: OperationSchemas }) => unknown[]"
511
+ required: false
512
+ description: |
513
+ Customize the queryKey that will be used for the query.
514
+
515
+ The function receives an object with:
516
+ - `operation`: The OpenAPI operation object with methods like `getTags()`, `getOperationId()`, etc.
517
+ - `schemas`: An object containing operation schemas including `pathParams`, `queryParams`, `request`, `response`, etc.
518
+ warning: |
519
+ When using a string you need to use `JSON.stringify`.
520
+ details:
521
+ - title: Using tags and path parameters
522
+ body: |-
523
+ Generate a queryKey with operation tags and path parameters:
524
+
525
+ For a GET operation with tags `["user"]` and path parameter `username`, this generates:
526
+ codeBlock:
527
+ - lang: typescript
528
+ code: |-
529
+ import { defineConfig } from 'kubb'
530
+ import { pluginReactQuery } from '@kubb/plugin-react-query'
531
+
532
+ export default defineConfig({
533
+ // ...
534
+ plugins: [
535
+ pluginReactQuery({
536
+ queryKey: ({ operation, schemas }) => {
537
+ const tags = operation.getTags().map(tag => JSON.stringify(tag.name))
538
+ const pathParams = schemas.pathParams?.keys || []
539
+ return [...tags, ...pathParams]
540
+ },
541
+ }),
542
+ ],
543
+ })
544
+ - lang: typescript
545
+ code: |-
546
+ export const getUserByNameQueryKey = ({ username }: { username: GetUserByNamePathParams["username"] }) =>
547
+ ['user', username] as const
548
+ - title: Using the default transformer
549
+ body: |-
550
+ You can extend the default queryKey transformer:
551
+
552
+ This prepends a version to the default queryKey:
553
+ codeBlock:
554
+ - lang: typescript
555
+ code: |-
556
+ import { pluginReactQuery } from '@kubb/plugin-react-query'
557
+ import { QueryKey } from '@kubb/plugin-react-query/components'
558
+
559
+ export default defineConfig({
560
+ // ...
561
+ plugins: [
562
+ pluginReactQuery({
563
+ queryKey: (props) => {
564
+ const defaultKeys = QueryKey.getTransformer(props)
565
+ return [JSON.stringify('v5'), ...defaultKeys]
566
+ },
567
+ }),
568
+ ],
569
+ })
570
+ - lang: typescript
571
+ code: |-
572
+ export const findPetsByTagsQueryKey = (params?: FindPetsByTagsQueryParams) =>
573
+ ['v5', { url: '/pet/findByTags' }, ...(params ? [params] : [])] as const
574
+ - title: Using operation ID
575
+ body: "Create a simple queryKey using the operation ID:"
576
+ codeBlock:
577
+ lang: typescript
578
+ code: |-
579
+ import { pluginReactQuery } from '@kubb/plugin-react-query'
580
+
581
+ export default defineConfig({
582
+ // ...
583
+ plugins: [
584
+ pluginReactQuery({
585
+ queryKey: ({ operation }) => {
586
+ return [JSON.stringify(operation.getOperationId())]
587
+ },
588
+ }),
589
+ ],
590
+ })
591
+ - title: Conditional keys based on parameters
592
+ body: "Include query parameters when they exist:"
593
+ codeBlock:
594
+ lang: typescript
595
+ code: |-
596
+ import { pluginReactQuery } from '@kubb/plugin-react-query'
597
+
598
+ export default defineConfig({
599
+ // ...
600
+ plugins: [
601
+ pluginReactQuery({
602
+ queryKey: ({ operation, schemas }) => {
603
+ const keys = [JSON.stringify(operation.getOperationId())]
604
+
605
+ // Add path parameter values (without quotes, so they reference the variables)
606
+ if (schemas.pathParams?.keys) {
607
+ keys.push(...schemas.pathParams.keys)
608
+ }
609
+
610
+ // Add query params conditionally (the string gets embedded as code)
611
+ if (schemas.queryParams?.name) {
612
+ keys.push('...(params ? [params] : [])')
613
+ }
614
+
615
+ return keys
616
+ },
617
+ }),
618
+ ],
619
+ })
620
+ - name: suspense
621
+ type: object | false
622
+ required: false
623
+ description: When set, a `suspenseQuery` hook will be added. This will only work for v5 and react.
624
+ - name: mutation
625
+ type: Mutation
626
+ required: false
627
+ description: |
628
+ Override some `useMutation` behaviors.
629
+
630
+ To disable queries pass `false`.
631
+ codeBlock:
632
+ lang: typescript
633
+ title: Mutation
634
+ code: |
635
+ type Mutation = {
636
+ methods: Array<HttpMethod>
637
+ importPath?: string
638
+ } | false
639
+ properties:
640
+ - name: methods
641
+ type: Array<HttpMethod>
642
+ required: false
643
+ default: "['post', 'put', 'delete']"
644
+ description: Define which HttpMethods can be used for mutations.
645
+ - name: importPath
646
+ type: string
647
+ required: false
648
+ default: "'@tanstack/react-query'"
649
+ description: |
650
+ Path to the `useMutation` that will be used to do the `useMutation` functionality.
651
+ It will be used as `import { useMutation } from '${hook.importPath}'`.
652
+ It allows both relative and absolute path. The path will be applied as is, so a relative path should be based on the file being generated.
653
+ - name: mutationKey
654
+ type: "(props: { operation: Operation; schemas: OperationSchemas }) => unknown[]"
655
+ required: false
656
+ description: Customize the mutationKey.
657
+ warning: |
658
+ When using a string you need to use `JSON.stringify`.
659
+ - name: customOptions
660
+ type: CustomOptions
661
+ required: false
662
+ description: |
663
+ When set, a custom hook will be used to customize the options of the generated hooks.
664
+
665
+ It will also generate a `HookOptions` type that can be used to type the custom options of each hook for type-safety.
666
+ codeBlock:
667
+ lang: typescript
668
+ title: CustomOptions
669
+ code: |
670
+ type CustomOptions = {
671
+ importPath: string
672
+ name?: string
673
+ }
674
+ properties:
675
+ - name: importPath
676
+ type: string
677
+ required: true
678
+ description: |
679
+ Path to the hook that will be used to customize the hook options.
680
+ It will be used as `import ${customOptions.name} from '${customOptions.importPath}'`.
681
+ It allows both relative and absolute paths. However, the path will be applied as is, so relative paths should be based on the file being generated.
682
+ - name: name
683
+ type: string
684
+ required: false
685
+ default: "'useCustomHookOptions'"
686
+ description: |
687
+ Name of the exported hook that will be used to customize the hook options.
688
+ It will be used as `import ${customOptions.name} from '${customOptions.importPath}'`.
689
+ If not provided, it defaults to `useCustomHookOptions`.
690
+ details:
691
+ - title: Add custom hook options to invalidate queries
692
+ codeBlock:
693
+ lang: typescript
694
+ title: useCustomHookOptions.ts
695
+ code: |-
696
+ function getCustomHookOptions({ queryClient }: { queryClient: QueryClient }): Partial<HookOptions> {
697
+ return {
698
+ useUpdatePetHook: {
699
+ onSuccess: () => {
700
+ // Invalidate queries using a constant
701
+ void queryClient.invalidateQueries({ queryKey: ['pet'] })
702
+ },
703
+ },
704
+ useDeletePetHook: {
705
+ onSuccess: (_data, variables, _onMutateResult, _context) => {
706
+ // Invalidate queries using the provided variables
707
+ void queryClient.invalidateQueries({ queryKey: ['pet', variables.pet_id] })
708
+ },
709
+ },
710
+ useUpdateUserHook: {
711
+ onSuccess: (_data, variables, _onMutateResult, _context) => {
712
+ // Invalidate queries using the provided query key generator function
713
+ void queryClient.invalidateQueries({ queryKey: getUserByNameQueryKey({ username: variables.username }) })
714
+ },
715
+ },
716
+ // Add more custom hook options here...
717
+ }
718
+ }
719
+
720
+ export function useCustomHookOptions<T extends keyof HookOptions>({ hookName, operationId }: { hookName: T; operationId: string }): HookOptions[T] {
721
+ const queryClient = useQueryClient()
722
+ const customOptions = getCustomHookOptions({ queryClient })
723
+ return customOptions[hookName] ?? {}
724
+ }
725
+ - title: Add custom hook options to implement custom error handling
726
+ codeBlock:
727
+ lang: typescript
728
+ title: useCustomHookOptions.ts
729
+ code: |-
730
+ function getCustomHookOptions({queryClient}: { queryClient: QueryClient }): Partial<HookOptions> {
731
+ return {
732
+ useUpdatePetHook: {
733
+ onError: (error, _variables, _onMutateResult, _context) => {
734
+ // Log the error
735
+ console.error(`Failed to update pet:`, error);
736
+ },
737
+ },
738
+ useDeletePetHook: {
739
+ onError: (_error, variables, _onMutateResult, _context) => {
740
+ // Show a toast notification
741
+ toast.error(`Failed to delete pet with id '${variables.pet_id}'`);
742
+ },
743
+ },
744
+ useUpdateUserHook: {
745
+ onError: (_error, variables, _onMutateResult, _context) => {
746
+ // Post the error to a custom analytics service
747
+ postEvent('user_updated_failed', { username: variables.username, message: error.message, date: Date.now() })
748
+ },
749
+ },
750
+ // Add more custom hook options here...
751
+ }
752
+ }
753
+
754
+ export function useCustomHookOptions<T extends keyof HookOptions>({ hookName, operationId }: { hookName: T; operationId: string }): HookOptions[T] {
755
+ const queryClient = useQueryClient()
756
+ const customOptions = getCustomHookOptions({queryClient})
757
+ return customOptions[hookName] ?? {}
758
+ }
759
+ - name: include
760
+ type: Array<Include>
761
+ required: false
762
+ description: Array containing include parameters to include tags, operations, methods, paths, or content types.
763
+ codeBlock:
764
+ lang: typescript
765
+ title: Include
766
+ code: |
767
+ export type Include = {
768
+ type: 'tag' | 'operationId' | 'path' | 'method' | 'contentType'
769
+ pattern: string | RegExp
770
+ }
771
+ - name: exclude
772
+ type: Array<Exclude>
773
+ required: false
774
+ description: Array containing exclude parameters to exclude or skip tags, operations, methods, paths, or content types.
775
+ codeBlock:
776
+ lang: typescript
777
+ title: Exclude
778
+ code: |
779
+ export type Exclude = {
780
+ type: 'tag' | 'operationId' | 'path' | 'method' | 'contentType'
781
+ pattern: string | RegExp
782
+ }
783
+ - name: override
784
+ type: Array<Override>
785
+ required: false
786
+ description: Array containing override parameters to override `options` based on tags, operations, methods, paths, or content types.
787
+ codeBlock:
788
+ lang: typescript
789
+ title: Override
790
+ code: |
791
+ export type Override = {
792
+ type: 'tag' | 'operationId' | 'path' | 'method' | 'contentType'
793
+ pattern: string | RegExp
794
+ options: PluginOptions
795
+ }
796
+ - name: generators
797
+ type: Array<Generator<PluginReactQuery>>
798
+ required: false
799
+ experimental: true
800
+ description: |
801
+ Define additional generators next to the built-in generators.
802
+
803
+ See [Generators](https://kubb.dev/docs/5.x/guides/creating-plugins) for more information on how to use generators.
804
+ - name: transformers
805
+ type: "{ name?: (name: string, type?: ResolveType) => string }"
806
+ required: false
807
+ description: Customize the generated names based on the type provided by the plugin.
808
+ properties:
809
+ - name: name
810
+ type: "(name: string, type?: ResolveType) => string"
811
+ required: false
812
+ description: Customize the names based on the type that is provided by the plugin.
813
+ codeBlock:
814
+ lang: typescript
815
+ code: |
816
+ type ResolveType = 'file' | 'function' | 'type' | 'const'
817
+ - name: resolver
818
+ type: Partial<ResolverReactQuery> & ThisType<ResolverReactQuery>
819
+ required: false
820
+ description: |
821
+ A single resolver that overrides the naming and path-resolution conventions. Each method you provide replaces the corresponding built-in one. When a method returns `null` or `undefined`, the resolver's result is used as the fallback, so you only need to supply the methods you want to change.
822
+
823
+ `this` inside each method is bound to the **full** resolver, so you can call other resolver methods (e.g. `this.default(name, 'function')`) without losing context.
824
+ body: |
825
+ Each plugin ships a built-in resolver:
826
+
827
+ | Plugin | Default resolver |
828
+ | --- | --- |
829
+ | `@kubb/plugin-ts` | `resolverTs` |
830
+ | `@kubb/plugin-zod` | `resolverZod` |
831
+ | `@kubb/plugin-cypress` | `resolverCypress` |
832
+ | `@kubb/plugin-mcp` | `resolverMcp` |
833
+ codeBlock:
834
+ lang: typescript
835
+ title: Custom resolver (plugin-ts example)
836
+ code: |
837
+ import { pluginTs } from '@kubb/plugin-ts'
838
+
839
+ pluginTs({
840
+ resolver: {
841
+ resolveName(name) {
842
+ // Prefix every operation-derived name; falls back for names where
843
+ // this returns null/undefined.
844
+ return `Api${this.default(name, 'function')}`
845
+ },
846
+ },
847
+ })
848
+ tip: |
849
+ Use `resolver` for fine-grained control over naming conventions.
850
+ - name: transformer
851
+ type: Visitor
852
+ required: false
853
+ description: |
854
+ A single AST visitor applied to every node before code is printed. Each method you provide replaces the corresponding built-in one. When a method returns `null` or `undefined`, the preset transformer's result is used as the fallback — so you only need to supply the methods you want to change.
855
+
856
+ Visitor methods receive the node and a context object. Return a modified node to replace it, or return `undefined`/`void` to leave it unchanged.
857
+ examples:
858
+ - name: Strip descriptions before printing
859
+ files:
860
+ - lang: typescript
861
+ code: |
862
+ import { pluginTs } from '@kubb/plugin-ts'
863
+
864
+ pluginTs({
865
+ transformer: {
866
+ schema(node) {
867
+ return { ...node, description: undefined }
868
+ },
869
+ },
870
+ })
871
+ twoslash: false
872
+ - name: Prefix every operationId
873
+ files:
874
+ - lang: typescript
875
+ code: |
876
+ import { pluginTs } from '@kubb/plugin-ts'
877
+
878
+ pluginTs({
879
+ transformer: {
880
+ operation(node) {
881
+ return { ...node, operationId: `api_${node.operationId}` }
882
+ },
883
+ },
884
+ })
885
+ twoslash: false
886
+ tip: |
887
+ Use `transformer` to rewrite node properties before printing. For output naming customization, use `resolver` instead.
888
+ notes:
889
+ - type: tip
890
+ body: |
891
+ See [Tanstack Query](https://tanstack.com/query) for more information about React Query.
892
+ examples:
893
+ - name: kubb.config.ts
894
+ files:
895
+ - lang: typescript
896
+ code: |
897
+ import { defineConfig } from 'kubb'
898
+ import { pluginTs } from '@kubb/plugin-ts'
899
+ import { pluginReactQuery } from '@kubb/plugin-react-query'
900
+
901
+ export default defineConfig({
902
+ input: {
903
+ path: './petStore.yaml',
904
+ },
905
+ output: {
906
+ path: './src/gen',
907
+ },
908
+ plugins: [
909
+ pluginTs(),
910
+ pluginReactQuery({
911
+ output: {
912
+ path: './hooks',
913
+ },
914
+ group: {
915
+ type: 'tag',
916
+ name: ({ group }) => `${group}Hooks`,
917
+ },
918
+ client: {
919
+ dataReturnType: 'full',
920
+ },
921
+ mutation: {
922
+ methods: ['post', 'put', 'delete'],
923
+ },
924
+ infinite: {
925
+ queryParam: 'next_page',
926
+ initialPageParam: 0,
927
+ nextParam: 'pagination.next.cursor',
928
+ previousParam: ['pagination', 'prev', 'cursor'],
929
+ },
930
+ query: {
931
+ methods: ['get'],
932
+ importPath: '@tanstack/react-query',
933
+ },
934
+ suspense: {},
935
+ }),
936
+ ],
937
+ })
938
+ twoslash: false