@navios/react-query 0.5.2 → 0.6.1

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.
package/README.md CHANGED
@@ -1,376 +1,508 @@
1
- # Navios Zod React
1
+ # @navios/react-query
2
2
 
3
- `Navios Zod React` is a helper for a navios zod to use with Tanstack React Query.
3
+ Type-safe React Query integration for Navios API client with Zod schema validation.
4
4
 
5
- ## Why?
5
+ ## Features
6
6
 
7
- - **Type Safety**: By using Zod schemas, you can ensure that the data you receive from your API matches the expected structure. This helps catch errors early in the development process.
8
- - **Validation**: Zod provides powerful validation capabilities, allowing you to define complex validation rules for your data. This ensures that the data you work with is always valid and meets your requirements.
9
- - **Integration with Navios**: Navios is a powerful HTTP client that simplifies API requests. By combining it with Zod, you can create a robust and type-safe API client.
10
- - **React Query Support**: This package is designed to work seamlessly with Tanstack React Query, making it easy to manage API requests and responses in your React applications.
11
- - **Declarative API**: The API is designed to be declarative, allowing you to define your API endpoints and their schemas in a clear and concise manner. This makes it easy to understand and maintain your API client.
12
- - **Discriminated Union Support**: The package supports discriminated unions, allowing you to handle different response types based on a common property. This is useful for APIs that return different data structures based on the request.
13
- - **Customizable**: The package allows you to customize the behavior of the API client, such as using a custom Navios client or enabling/disabling certain features like whole response validation.
14
- - **Error Handling**: The package provides built-in error handling capabilities, allowing you to handle API errors gracefully and provide meaningful feedback to users.
7
+ - **Type Safety** - Full TypeScript support with Zod schema inference
8
+ - **Schema Validation** - Request and response validation using Zod schemas
9
+ - **React Query Integration** - Seamless integration with TanStack Query v5
10
+ - **Declarative API** - Define endpoints once, use everywhere
11
+ - **URL Parameters** - Built-in support for parameterized URLs (`/users/$userId`)
12
+ - **Optimistic Updates** - First-class support via `onMutate` callback
13
+ - **Stream Support** - Handle file downloads and blob responses
15
14
 
16
15
  ## Installation
17
16
 
18
17
  ```bash
19
- npm install --save @navios/navios-zod @navios/navios-zod-react zod navios
18
+ npm install @navios/react-query @navios/builder navios zod @tanstack/react-query
20
19
  ```
21
20
 
22
- or
21
+ ## Quick Start
23
22
 
24
- ```bash
25
- yarn add @navios/navios-zod @navios/navios-zod-react zod navios
26
- ```
23
+ ```typescript
24
+ import { builder } from '@navios/builder'
25
+ import { declareClient } from '@navios/react-query'
26
+ import { create } from 'navios'
27
+ import { z } from 'zod/v4'
27
28
 
28
- ## Usage of Mutations
29
+ // Create the API builder
30
+ const api = builder({})
31
+ api.provideClient(create({ baseURL: 'https://api.example.com' }))
29
32
 
30
- ```tsx
31
- import { createAPI } from '@navios/navios-zod/v4'
32
- import { declareClient } from '@navios/navios-zod-react'
33
+ // Create the client
34
+ const client = declareClient({ api })
35
+ ```
33
36
 
34
- import { z } from 'zod/v4'
37
+ ## Queries
35
38
 
36
- const publicApi = createAPI({
37
- baseURL: 'https://example.com/api/',
38
- useDiscriminatorResponse: true,
39
+ ### Basic Query
40
+
41
+ ```typescript
42
+ const getUsers = client.query({
43
+ method: 'GET',
44
+ url: '/users',
45
+ responseSchema: z.array(z.object({
46
+ id: z.string(),
47
+ name: z.string(),
48
+ email: z.string().email(),
49
+ })),
39
50
  })
40
51
 
41
- const publicClient = declareClient({
42
- api: publicApi,
52
+ // In a component
53
+ function UsersList() {
54
+ const { data } = getUsers.useSuspense({})
55
+ return (
56
+ <ul>
57
+ {data.map(user => <li key={user.id}>{user.name}</li>)}
58
+ </ul>
59
+ )
60
+ }
61
+ ```
62
+
63
+ ### Query with URL Parameters
64
+
65
+ ```typescript
66
+ const getUser = client.query({
67
+ method: 'GET',
68
+ url: '/users/$userId',
69
+ responseSchema: z.object({
70
+ id: z.string(),
71
+ name: z.string(),
72
+ }),
43
73
  })
44
74
 
45
- const RequestSchema = z.object({
46
- email: z.string().email(),
47
- password: z.string().min(8).max(32),
75
+ function UserProfile({ userId }: { userId: string }) {
76
+ const { data } = getUser.useSuspense({
77
+ urlParams: { userId },
78
+ })
79
+ return <h1>{data.name}</h1>
80
+ }
81
+ ```
82
+
83
+ ### Query with Query Parameters
84
+
85
+ ```typescript
86
+ const searchUsers = client.query({
87
+ method: 'GET',
88
+ url: '/users',
89
+ querySchema: z.object({
90
+ page: z.number().default(1),
91
+ limit: z.number().default(10),
92
+ search: z.string().optional(),
93
+ }),
94
+ responseSchema: z.object({
95
+ users: z.array(UserSchema),
96
+ total: z.number(),
97
+ }),
48
98
  })
49
99
 
50
- const loginMutation = publicClient.mutation({
51
- url: 'auth/login',
52
- method: 'post',
53
- // Navios Zod also validates the request body
54
- requestSchema: RequestSchema,
100
+ function UsersPage() {
101
+ const { data } = searchUsers.useSuspense({
102
+ params: { page: 1, limit: 20, search: 'john' },
103
+ })
104
+ return <div>Found {data.total} users</div>
105
+ }
106
+ ```
107
+
108
+ ### Query with Response Transformation
109
+
110
+ ```typescript
111
+ const getUsers = client.query({
112
+ method: 'GET',
113
+ url: '/users',
55
114
  responseSchema: z.discriminatedUnion('success', [
56
115
  z.object({
57
116
  success: z.literal(true),
58
- data: z.object({
59
- accessToken: z.string(),
60
- refreshToken: z.string(),
61
- }),
117
+ data: z.array(UserSchema),
62
118
  }),
63
119
  z.object({
64
120
  success: z.literal(false),
65
- error: z.object({
66
- message: z.string(),
67
- }),
121
+ error: z.object({ message: z.string() }),
68
122
  }),
69
123
  ]),
70
124
  processResponse: (response) => {
71
- if (response.success) {
72
- return response.data
73
- } else {
125
+ if (!response.success) {
74
126
  throw new Error(response.error.message)
75
127
  }
128
+ return response.data
76
129
  },
77
130
  })
131
+ ```
78
132
 
79
- export function Login() {
80
- const { mutateAsync: login, data, isSuccess, error } = loginMutation()
133
+ ### Query Helpers
81
134
 
82
- const form = useForm({
83
- resolver: zodResolver(RequestSchema),
84
- defaultValues: {
85
- email: '',
86
- password: '',
87
- },
88
- })
135
+ ```typescript
136
+ // Get query options for use with useQuery
137
+ const options = getUsers({ params: { page: 1 } })
89
138
 
90
- useEffect(() => {
91
- if (isSuccess) {
92
- console.log('Login successful:', data)
93
- }
94
- }, [isSuccess, data])
139
+ // Helper hooks
140
+ const { data } = getUsers.use({ params: { page: 1 } })
141
+ const { data } = getUsers.useSuspense({ params: { page: 1 } })
142
+
143
+ // Invalidation
144
+ getUsers.invalidate({ params: { page: 1 } })
145
+ getUsers.invalidateAll({}) // Invalidate all pages
146
+ ```
147
+
148
+ ## Infinite Queries
149
+
150
+ ```typescript
151
+ const getUsers = client.infiniteQuery({
152
+ method: 'GET',
153
+ url: '/users',
154
+ querySchema: z.object({
155
+ cursor: z.string().optional(),
156
+ limit: z.number().default(20),
157
+ }),
158
+ responseSchema: z.object({
159
+ users: z.array(UserSchema),
160
+ nextCursor: z.string().nullable(),
161
+ }),
162
+ getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
163
+ })
164
+
165
+ function InfiniteUsersList() {
166
+ const { data, fetchNextPage, hasNextPage } = getUsers.useSuspense({
167
+ params: { limit: 20 },
168
+ })
95
169
 
96
170
  return (
97
- <form onSubmit={form.handleSubmit(login)}>
98
- {error && <p>{error.message}</p>}
99
- <input {...form.register('email')} placeholder="Email" />
100
- <input
101
- {...form.register('password')}
102
- type="password"
103
- placeholder="Password"
104
- />
105
- <button type="submit">Login</button>
106
- </form>
171
+ <div>
172
+ {data.pages.flatMap(page =>
173
+ page.users.map(user => <UserCard key={user.id} user={user} />)
174
+ )}
175
+ {hasNextPage && (
176
+ <button onClick={() => fetchNextPage()}>Load More</button>
177
+ )}
178
+ </div>
107
179
  )
108
180
  }
109
181
  ```
110
182
 
111
- ## Usage of Queries
183
+ ## Mutations
112
184
 
113
- ```tsx
114
- import { createAPI } from '@navios/navios-zod/v4'
115
- import { declareClient } from '@navios/navios-zod-react'
116
-
117
- import { z } from 'zod/v4'
185
+ ### Basic Mutation
118
186
 
119
- const publicApi = createAPI({
120
- baseURL: 'https://example.com/api/',
121
- useDiscriminatorResponse: true,
187
+ ```typescript
188
+ const createUser = client.mutation({
189
+ method: 'POST',
190
+ url: '/users',
191
+ requestSchema: z.object({
192
+ name: z.string(),
193
+ email: z.string().email(),
194
+ }),
195
+ responseSchema: z.object({
196
+ id: z.string(),
197
+ name: z.string(),
198
+ }),
199
+ processResponse: (data) => data,
122
200
  })
123
201
 
124
- const publicClient = declareClient({
125
- api: publicApi,
126
- })
202
+ function CreateUserForm() {
203
+ const { mutateAsync, isPending } = createUser()
127
204
 
128
- const usersList = publicClient.query({
129
- url: 'users',
130
- method: 'GET',
131
- querySchema: z.object({
132
- page: z.number().optional().default(1),
133
- limit: z.number().optional().default(10),
205
+ const handleSubmit = async (formData: FormData) => {
206
+ await mutateAsync({
207
+ data: {
208
+ name: formData.get('name') as string,
209
+ email: formData.get('email') as string,
210
+ },
211
+ })
212
+ }
213
+
214
+ return <form onSubmit={handleSubmit}>...</form>
215
+ }
216
+ ```
217
+
218
+ ### Mutation with URL Parameters
219
+
220
+ ```typescript
221
+ const updateUser = client.mutation({
222
+ method: 'PUT',
223
+ url: '/users/$userId',
224
+ requestSchema: z.object({
225
+ name: z.string(),
134
226
  }),
135
- responseSchema: z.discriminatedUnion('success', [
136
- z.object({
137
- success: z.literal(true),
138
- data: z.array(
139
- z.object({
140
- id: z.string(),
141
- name: z.string(),
142
- email: z.string().email(),
143
- }),
144
- ),
145
- }),
146
- z.object({
147
- success: z.literal(false),
148
- error: z.object({
149
- message: z.string(),
150
- }),
151
- }),
152
- ]),
153
- processResponse: (response) => {
154
- if (response.success) {
155
- return response.data
156
- } else {
157
- throw new Error(response.error.message)
158
- }
159
- },
227
+ responseSchema: UserSchema,
228
+ processResponse: (data) => data,
160
229
  })
161
230
 
162
- export function UsersList() {
163
- const { page, limit } = routeApi.useSearch()
164
- const navigate = routeApi.useNavigate()
165
- const { data, isLoading, error } = usersList.use({
166
- params: {
167
- page,
168
- limit,
169
- },
170
- })
231
+ function EditUserForm({ userId }: { userId: string }) {
232
+ const { mutateAsync } = updateUser()
171
233
 
172
- if (isLoading) {
173
- return <p>Loading...</p>
234
+ const handleSubmit = async (name: string) => {
235
+ await mutateAsync({
236
+ urlParams: { userId },
237
+ data: { name },
238
+ })
174
239
  }
175
240
 
176
- if (error) {
177
- return <p>{error.message}</p>
178
- }
179
-
180
- return (
181
- <ul>
182
- {data?.map((user) => (
183
- <li key={user.id}>{user.name}</li>
184
- ))}
185
- </ul>
186
- )
241
+ return <form>...</form>
187
242
  }
188
243
  ```
189
244
 
190
- ## Usage of Infinite Queries
191
-
192
- ```tsx
193
- import { createAPI } from '@navios/navios-zod/v4'
194
- import { declareClient } from '@navios/navios-zod-react'
245
+ ### Mutation with Callbacks and Optimistic Updates
195
246
 
196
- import { z } from 'zod/v4'
247
+ ```typescript
248
+ const updateUser = client.mutation({
249
+ method: 'PUT',
250
+ url: '/users/$userId',
251
+ requestSchema: z.object({ name: z.string() }),
252
+ responseSchema: UserSchema,
253
+ processResponse: (data) => data,
197
254
 
198
- const publicApi = createAPI({
199
- baseURL: 'https://example.com/api/',
200
- useDiscriminatorResponse: true,
201
- })
255
+ // Provide context (e.g., queryClient) via hook
256
+ useContext: () => {
257
+ const queryClient = useQueryClient()
258
+ return { queryClient }
259
+ },
202
260
 
203
- const publicClient = declareClient({
204
- api: publicApi,
205
- })
261
+ // Called before mutation - return value available as context.onMutateResult
262
+ onMutate: async (variables, context) => {
263
+ // Cancel outgoing queries
264
+ await context.queryClient.cancelQueries({
265
+ queryKey: ['users', variables.urlParams.userId]
266
+ })
267
+
268
+ // Snapshot previous value
269
+ const previousUser = context.queryClient.getQueryData(
270
+ ['users', variables.urlParams.userId]
271
+ )
272
+
273
+ // Optimistically update
274
+ context.queryClient.setQueryData(
275
+ ['users', variables.urlParams.userId],
276
+ { ...previousUser, name: variables.data.name }
277
+ )
278
+
279
+ return { previousUser }
280
+ },
206
281
 
207
- const usersList = publicClient.infiniteQuery({
208
- url: 'users',
209
- method: 'GET',
210
- querySchema: z.object({
211
- page: z.number().optional().default(1),
212
- limit: z.number().optional().default(10),
213
- }),
214
- responseSchema: z.discriminatedUnion('success', [
215
- z.object({
216
- success: z.literal(true),
217
- data: z.array(
218
- z.object({
219
- id: z.string(),
220
- name: z.string(),
221
- email: z.string().email(),
222
- }),
223
- ),
224
- meta: z.object({
225
- total: z.number(),
226
- totalPages: z.number(),
227
- page: z.number(),
228
- }),
229
- }),
230
- z.object({
231
- success: z.literal(false),
232
- error: z.object({
233
- message: z.string(),
234
- }),
235
- }),
236
- ]),
237
- processResponse: (response) => {
238
- if (response.success) {
239
- return response.data
240
- } else {
241
- throw new Error(response.error.message)
242
- }
282
+ // Called on success
283
+ onSuccess: (data, variables, context) => {
284
+ context.queryClient.invalidateQueries({ queryKey: ['users'] })
243
285
  },
244
- getNextPageParam: (lastPage, pages) => {
245
- if (lastPage.meta.page < lastPage.meta.totalPages) {
246
- return lastPage.meta.page + 1
286
+
287
+ // Called on error - rollback optimistic update
288
+ onError: (error, variables, context) => {
289
+ if (context.onMutateResult?.previousUser) {
290
+ context.queryClient.setQueryData(
291
+ ['users', variables.urlParams.userId],
292
+ context.onMutateResult.previousUser
293
+ )
247
294
  }
248
- return undefined
249
295
  },
250
- select: (data) => {
251
- return data.pages.flatMap((page) => page.data)
296
+
297
+ // Called on both success and error
298
+ onSettled: (data, error, variables, context) => {
299
+ context.queryClient.invalidateQueries({
300
+ queryKey: ['users', variables.urlParams.userId]
301
+ })
252
302
  },
253
303
  })
304
+ ```
254
305
 
255
- export function UsersList() {
256
- const { page, limit } = routeApi.useSearch()
257
- const { data, isLoading, error, fetchNextPage, hasNextPage } = usersList.use({
258
- params: {
259
- page,
260
- limit,
261
- },
262
- })
306
+ ### DELETE Mutation
263
307
 
264
- if (isLoading) {
265
- return <p>Loading...</p>
266
- }
308
+ ```typescript
309
+ const deleteUser = client.mutation({
310
+ method: 'DELETE',
311
+ url: '/users/$userId',
312
+ responseSchema: z.object({ success: z.boolean() }),
313
+ processResponse: (data) => data,
314
+ })
267
315
 
268
- if (error) {
269
- return <p>{error.message}</p>
270
- }
316
+ const { mutateAsync } = deleteUser()
317
+ await mutateAsync({ urlParams: { userId: '123' } })
318
+ ```
271
319
 
272
- return (
273
- <div>
274
- <ul>
275
- {data?.map((page) =>
276
- page.data.map((user) => <li key={user.id}>{user.name}</li>),
277
- )}
278
- </ul>
279
- <button disabled={!hasNextPage} onClick={() => fetchNextPage()}>
280
- Load more
281
- </button>
282
- </div>
283
- )
284
- }
320
+ ### Mutation with useKey (Scoped Mutations)
321
+
322
+ When `useKey` is true, mutations are scoped by URL parameters, preventing parallel mutations to the same resource:
323
+
324
+ ```typescript
325
+ const updateUser = client.mutation({
326
+ method: 'PUT',
327
+ url: '/users/$userId',
328
+ useKey: true,
329
+ requestSchema: UpdateUserSchema,
330
+ responseSchema: UserSchema,
331
+ processResponse: (data) => data,
332
+ })
333
+
334
+ // With useKey, you must pass urlParams when calling the hook
335
+ const { mutateAsync, isPending } = updateUser({
336
+ urlParams: { userId: '123' }
337
+ })
338
+
339
+ // Check if any mutation for this user is in progress
340
+ const isMutating = updateUser.useIsMutating({ userId: '123' })
285
341
  ```
286
342
 
287
- ## API
343
+ ## Stream Endpoints (File Downloads)
288
344
 
289
- ### `declareClient`
345
+ ```typescript
346
+ // Define stream endpoint
347
+ const downloadFile = api.declareStream({
348
+ method: 'GET',
349
+ url: '/files/$fileId/download',
350
+ })
290
351
 
291
- This function is used to create a client for the API. It takes an object with the following properties:
352
+ // Create mutation from stream endpoint
353
+ const useDownloadFile = client.mutationFromEndpoint(downloadFile, {
354
+ // processResponse is optional - defaults to returning Blob
355
+ processResponse: (blob) => URL.createObjectURL(blob),
292
356
 
293
- - `api`: The API object created using `declareAPI` or `createAPI` from `@navios/navios-zod`.
357
+ onSuccess: (url, variables, context) => {
358
+ window.open(url)
359
+ },
360
+ })
294
361
 
295
- The client object will have the following properties:
362
+ function DownloadButton({ fileId }: { fileId: string }) {
363
+ const { mutate, isPending } = useDownloadFile()
296
364
 
297
- - `query`: A function that takes a configuration object and returns a query object.
298
- - `mutation`: A function that takes a configuration object and returns a mutation object.
299
- - `infiniteQuery`: A function that takes a configuration object and returns an infinite query object.
365
+ return (
366
+ <button
367
+ onClick={() => mutate({ urlParams: { fileId } })}
368
+ disabled={isPending}
369
+ >
370
+ {isPending ? 'Downloading...' : 'Download'}
371
+ </button>
372
+ )
373
+ }
374
+ ```
300
375
 
301
- #### `query`
376
+ ## Using Existing Endpoints
302
377
 
303
- This function is used to create a query for the API. It takes a configuration object with the following properties:
378
+ If you have endpoints defined separately, you can use them with the client:
304
379
 
305
- - `url`: The URL of the API endpoint. For parameterized URLs, use the format `/users/$userId`.
306
- - `method`: The HTTP method to use (GET, POST, PUT, DELETE, etc.).
307
- - `querySchema`: A Zod schema for validating the query parameters. (optional)
308
- - `responseSchema`: A Zod schema for validating the response data.
309
- - `processResponse`: A function that takes the response data and returns the processed data. (optional, but recommended)
380
+ ```typescript
381
+ // Define endpoint separately
382
+ const getUserEndpoint = api.declareEndpoint({
383
+ method: 'GET',
384
+ url: '/users/$userId',
385
+ responseSchema: UserSchema,
386
+ })
310
387
 
311
- The result is a function that can be used to get query options. The function takes an object with the following properties:
388
+ // Create query from endpoint
389
+ const getUser = client.queryFromEndpoint(getUserEndpoint, {
390
+ processResponse: (data) => data,
391
+ })
312
392
 
313
- - `params`: An object with the query parameters to send with the request. (required if `querySchema` is defined)
314
- - `urlParams`: An object with the URL parameters to send with the request. (required if `url` contains URL parameters)
393
+ // Create mutation from endpoint
394
+ const updateUser = client.mutationFromEndpoint(updateUserEndpoint, {
395
+ processResponse: (data) => data,
396
+ onSuccess: (data, variables, context) => {
397
+ console.log('Updated:', data)
398
+ },
399
+ })
400
+ ```
315
401
 
316
- Function returns options for `useQuery` or `useSuspenseQuery` from `@tanstack/react-query`.
402
+ ## Multipart Mutations (File Uploads)
403
+
404
+ ```typescript
405
+ const uploadAvatar = client.multipartMutation({
406
+ method: 'POST',
407
+ url: '/users/$userId/avatar',
408
+ requestSchema: z.object({
409
+ file: z.instanceof(File),
410
+ }),
411
+ responseSchema: z.object({
412
+ url: z.string(),
413
+ }),
414
+ processResponse: (data) => data,
415
+ })
416
+
417
+ function AvatarUpload({ userId }: { userId: string }) {
418
+ const { mutateAsync } = uploadAvatar()
419
+
420
+ const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
421
+ const file = e.target.files?.[0]
422
+ if (file) {
423
+ const result = await mutateAsync({
424
+ urlParams: { userId },
425
+ data: { file },
426
+ })
427
+ console.log('Uploaded to:', result.url)
428
+ }
429
+ }
430
+
431
+ return <input type="file" onChange={handleFileChange} />
432
+ }
433
+ ```
317
434
 
318
- ##### `queryName.use`
435
+ ## API Reference
319
436
 
320
- This function is a helper hook which is a wrapper around `useQuery` from `@tanstack/react-query`. It takes the same parameters as the `query` result function and returns the query result.
437
+ ### `declareClient(options)`
321
438
 
322
- ##### `queryName.useSuspense`
439
+ Creates a client instance for making type-safe queries and mutations.
323
440
 
324
- This function is a helper hook which is a wrapper around `useSuspenseQuery` from `@tanstack/react-query`. It takes the same parameters as the `query` result function and returns the query result.
441
+ **Options:**
442
+ - `api` - The API builder created with `@navios/builder`
443
+ - `defaults` - Default options applied to all queries/mutations
325
444
 
326
- ##### `queryName.invalidate`
445
+ **Returns:** `ClientInstance` with the following methods:
327
446
 
328
- This function is a helper function which is a wrapper around `invalidateQueries` from `@tanstack/react-query`. It takes parameters:
447
+ ### Query Methods
329
448
 
330
- - `queryClient`: The query client to use. (optional, defaults to the query client from the context)
331
- - `params`: An object with `urlParams` and `params` to invalidate the query. (required if `url` contains URL parameters or `querySchema` is defined)
449
+ - `client.query(config)` - Create a query
450
+ - `client.queryFromEndpoint(endpoint, options)` - Create a query from an existing endpoint
451
+ - `client.infiniteQuery(config)` - Create an infinite query
452
+ - `client.infiniteQueryFromEndpoint(endpoint, options)` - Create an infinite query from an endpoint
332
453
 
333
- This function is used to invalidate the query in the cache. It can be used to refetch the query data when the data changes or when the user navigates to a different page.
454
+ ### Mutation Methods
334
455
 
335
- ##### `queryName.invalidateAll`
456
+ - `client.mutation(config)` - Create a mutation
457
+ - `client.mutationFromEndpoint(endpoint, options)` - Create a mutation from an existing endpoint
458
+ - `client.multipartMutation(config)` - Create a multipart/form-data mutation
336
459
 
337
- This function is a helper function which is a wrapper around `invalidateQueries` from `@tanstack/react-query`. It takes parameters:
460
+ ### Query Config
338
461
 
339
- - `queryClient`: The query client to use. (optional, defaults to the query client from the context)
340
- - `params`: An object with `urlParams` to invalidate the query. (required if `url` contains URL parameters)
462
+ | Property | Type | Required | Description |
463
+ |----------|------|----------|-------------|
464
+ | `method` | `'GET' \| 'POST' \| 'HEAD' \| 'OPTIONS'` | Yes | HTTP method |
465
+ | `url` | `string` | Yes | URL pattern (e.g., `/users/$userId`) |
466
+ | `responseSchema` | `ZodSchema` | Yes | Zod schema for response validation |
467
+ | `querySchema` | `ZodObject` | No | Zod schema for query parameters |
468
+ | `requestSchema` | `ZodSchema` | No | Zod schema for request body (POST queries) |
469
+ | `processResponse` | `(data) => Result` | No | Transform the response |
341
470
 
342
- This function is used to invalidate query ignoring query params. It can be used to refetch all query data when the data changes or when the user navigates to a different page.
471
+ ### Mutation Config
343
472
 
344
- #### `mutation`
473
+ | Property | Type | Required | Description |
474
+ |----------|------|----------|-------------|
475
+ | `method` | `'POST' \| 'PUT' \| 'PATCH' \| 'DELETE'` | Yes | HTTP method |
476
+ | `url` | `string` | Yes | URL pattern (e.g., `/users/$userId`) |
477
+ | `responseSchema` | `ZodSchema` | Yes | Zod schema for response validation |
478
+ | `requestSchema` | `ZodSchema` | No | Zod schema for request body |
479
+ | `querySchema` | `ZodObject` | No | Zod schema for query parameters |
480
+ | `processResponse` | `(data) => Result` | No | Transform the response |
481
+ | `useKey` | `boolean` | No | Enable mutation key for scoping |
482
+ | `useContext` | `() => Context` | No | Hook to provide context to callbacks |
483
+ | `onMutate` | `(variables, context) => onMutateResult` | No | Called before mutation |
484
+ | `onSuccess` | `(data, variables, context) => void` | No | Called on success |
485
+ | `onError` | `(error, variables, context) => void` | No | Called on error |
486
+ | `onSettled` | `(data, error, variables, context) => void` | No | Called on completion |
345
487
 
346
- This function is used to create a mutation for the API. It takes a configuration object with the following properties:
488
+ ### Context Object
347
489
 
348
- - `url`: The URL of the API endpoint. For parameterized URLs, use the format `/users/$userId`.
349
- - `method`: The HTTP method to use (PATCH, POST, PUT, DELETE, etc.).
350
- - `requestSchema`: A Zod schema for validating the request body.
351
- - `responseSchema`: A Zod schema for validating the response data.
352
- - `processResponse`: A function that takes the response data and returns the processed data. (optional, but recommended)
353
- - `useContext`: A function that is called before the mutation is executed. It can be used to set the context for the onSuccess and onError. (optional)
354
- - `onSuccess`: A function that is called when the mutation is successful. (optional)
355
- - `onError`: A function that is called when the mutation fails. (optional)
356
- - `useKey`: If true, the mutation will have a mutation key which can be used to get the mutation status, limit parallel requests, etc. (optional, defaults to false)
490
+ The context passed to mutation callbacks includes:
357
491
 
358
- The result is a function that can be used to get mutation in react query. When `useKey` is true, the function requires a `urlParams` argument.
492
+ - Properties from `useContext()` return value
493
+ - `mutationId` - TanStack Query mutation ID
494
+ - `meta` - Mutation metadata
495
+ - `onMutateResult` - Return value from `onMutate` (in `onSuccess`, `onError`, `onSettled`)
359
496
 
360
- The result is a react query mutation object
497
+ ## Migration from 0.5.x
361
498
 
362
- #### `infiniteQuery`
499
+ See [CHANGELOG.md](./CHANGELOG.md) for migration guide from 0.5.x to 0.6.0.
363
500
 
364
- This function is used to create an infinite query for the API. It takes a configuration object with the following properties:
501
+ Key changes:
502
+ - Mutation callbacks now receive `(data, variables, context)` instead of `(queryClient, data, variables)`
503
+ - Use `useContext` hook to provide `queryClient` and other dependencies
504
+ - New `onMutate` and `onSettled` callbacks for optimistic updates
365
505
 
366
- - `url`: The URL of the API endpoint. For parameterized URLs, use the format `/users/$userId`.
367
- - `method`: The HTTP method to use (GET, POST, PUT, DELETE, etc.).
368
- - `querySchema`: A Zod schema for validating the query parameters. (required)
369
- - `responseSchema`: A Zod schema for validating the response data.
370
- - `processResponse`: A function that takes the response data and returns the processed data. (optional, but recommended)
371
- - `getNextPageParam`: A function that takes the last page and all pages and returns the next page param. (required)
372
- - `initialPageData`: The initial data to use for the first page. (optional)
373
- - `getPreviousPageParam`: A function that takes the first page and all pages and returns the previous page param. (optional)
374
- - `select`: A function that takes the data and returns the selected data. (optional)
506
+ ## License
375
507
 
376
- It works the same as `query`, but it returns an infinite query object. Please refer to the `query` section for more details.
508
+ MIT