@barandurakk/query-api-router 0.1.0
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/LICENSE +21 -0
- package/README.md +439 -0
- package/dist/api.d.ts +10 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +115 -0
- package/dist/api.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.d.ts +20 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +12 -0
- package/dist/internal.js.map +1 -0
- package/dist/types.d.ts +68 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +68 -0
- package/src/api.ts +155 -0
- package/src/index.ts +2 -0
- package/src/internal.ts +64 -0
- package/src/types.ts +183 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Baran Durak
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
# Query API Router
|
|
2
|
+
Type-safe endpoint definitions on top of TanStack Query for clean, reusable, low-boilerplate data hooks.
|
|
3
|
+
|
|
4
|
+
Build API “routers” once, then use consistent `useQuery`, `useMutation`, `useSuspenseQuery`, `useInfiniteQuery`, `getKey`, and `getOptions` everywhere.
|
|
5
|
+
|
|
6
|
+
## Why This Exists
|
|
7
|
+
|
|
8
|
+
TanStack Query is powerful, but large apps often drift into:
|
|
9
|
+
|
|
10
|
+
- Repeated query key strings
|
|
11
|
+
- Repeated `queryFn` wiring
|
|
12
|
+
- Inconsistent invalidation logic
|
|
13
|
+
- Loader/prefetch code that duplicates component query logic
|
|
14
|
+
- Hard-to-find endpoint behavior and defaults
|
|
15
|
+
|
|
16
|
+
`Query API Router` solves this by making endpoint definitions the single source of truth.
|
|
17
|
+
|
|
18
|
+
You define each endpoint once, and get:
|
|
19
|
+
|
|
20
|
+
- Reusable hooks
|
|
21
|
+
- Centralized keys
|
|
22
|
+
- Reusable query options for loaders/prefetch/fetchQuery
|
|
23
|
+
- Built-in mutation-driven invalidation
|
|
24
|
+
- Strong TypeScript inference
|
|
25
|
+
|
|
26
|
+
## Key Features
|
|
27
|
+
|
|
28
|
+
- Endpoint router pattern: organize by domain (`users`, `orders`, `attachments`, etc.)
|
|
29
|
+
- Typed query/mutation/infinite-query definitions
|
|
30
|
+
- Generated hook API per endpoint
|
|
31
|
+
- `getKey()` for stable key access and manual invalidation
|
|
32
|
+
- `getOptions()` for `ensureQueryData`, `fetchQuery`, `useQueries`, and non-React usage
|
|
33
|
+
- TanStack `QueryFunctionContext` access for cancellation, metadata, keys, and the active client
|
|
34
|
+
- Automatic invalidation via `invalidateQueries` callback on mutations
|
|
35
|
+
- Layered defaults:
|
|
36
|
+
- API-level defaults
|
|
37
|
+
- Endpoint-level defaults
|
|
38
|
+
- Call-site overrides
|
|
39
|
+
- Transport-agnostic (Axios, fetch, GraphQL client, anything async)
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pnpm add @barandurakk/query-api-router @tanstack/react-query react
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Compatibility
|
|
48
|
+
|
|
49
|
+
- TanStack React Query: `>=5.90.18 <6`
|
|
50
|
+
- React: `>=18`
|
|
51
|
+
- Package format: ESM
|
|
52
|
+
|
|
53
|
+
CI verifies the library on maintained Node.js releases and tests the packed artifact against both the exact minimum peer versions and the latest compatible TanStack Query v5/React release. The compatibility matrix also runs weekly so newly published compatible peer versions are exercised even when this repository has no new commits.
|
|
54
|
+
|
|
55
|
+
## Quick Start
|
|
56
|
+
|
|
57
|
+
### 1) Create QueryClient
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { QueryClient } from '@tanstack/react-query';
|
|
61
|
+
|
|
62
|
+
export const queryClient = new QueryClient({
|
|
63
|
+
defaultOptions: {
|
|
64
|
+
queries: {
|
|
65
|
+
retry: 2,
|
|
66
|
+
refetchOnWindowFocus: false,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Pass this client to `QueryClientProvider` as usual. Generated mutations use that active client for invalidation unless the router explicitly supplies a different client.
|
|
73
|
+
|
|
74
|
+
### 2) Define an API router
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import type { QueryKey } from '@tanstack/react-query';
|
|
78
|
+
import api from '@barandurakk/query-api-router';
|
|
79
|
+
import { apiClient } from './http'; // axios/fetch wrapper
|
|
80
|
+
|
|
81
|
+
type ApiError = unknown;
|
|
82
|
+
type UserProfile = { id: string; email: string; language: string };
|
|
83
|
+
type Response<T> = { data: T; success: boolean; message: string };
|
|
84
|
+
|
|
85
|
+
// The invalidation callback receives this explicit router contract. This keeps
|
|
86
|
+
// endpoint names and state arguments type-safe without changing endpoint syntax.
|
|
87
|
+
type UsersInvalidationApi = {
|
|
88
|
+
getKey: () => QueryKey;
|
|
89
|
+
getProfile: {
|
|
90
|
+
getKey: () => QueryKey;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const usersApi = api(
|
|
95
|
+
'users',
|
|
96
|
+
{
|
|
97
|
+
getProfile: api.query<Response<UserProfile>, void, ApiError>({
|
|
98
|
+
queryFn: async () => (await apiClient.get('/users/profile')).data,
|
|
99
|
+
}),
|
|
100
|
+
|
|
101
|
+
updateLanguage: api.mutation<
|
|
102
|
+
Response<{ language: string }>,
|
|
103
|
+
{ language: string },
|
|
104
|
+
ApiError,
|
|
105
|
+
UsersInvalidationApi
|
|
106
|
+
>({
|
|
107
|
+
mutationFn: async (body) => (await apiClient.put('/users/profile/language', body)).data,
|
|
108
|
+
invalidateQueries: (router) => [router.getProfile.getKey()],
|
|
109
|
+
}),
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
queryOptions: {
|
|
113
|
+
staleTime: 60_000,
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 3) Use in components
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
function ProfileSection() {
|
|
123
|
+
const { data, isLoading } = usersApi.getProfile.useQuery();
|
|
124
|
+
const { mutateAsync: updateLanguage, isPending } = usersApi.updateLanguage.useMutation();
|
|
125
|
+
|
|
126
|
+
if (isLoading) return <div>Loading...</div>;
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<button
|
|
130
|
+
disabled={isPending}
|
|
131
|
+
onClick={() => updateLanguage({ language: 'ru' })}
|
|
132
|
+
>
|
|
133
|
+
Current language: {data?.data.language}
|
|
134
|
+
</button>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Core Concepts
|
|
140
|
+
|
|
141
|
+
## 1) API Router
|
|
142
|
+
|
|
143
|
+
`api(baseKey, endpoints, defaults?)`
|
|
144
|
+
|
|
145
|
+
- `baseKey` becomes the root query key namespace (example: `'orders'`)
|
|
146
|
+
- `endpoints` is an object of `query`, `mutation`, `infiniteQuery`
|
|
147
|
+
- `defaults` can inject:
|
|
148
|
+
- `queryClient` (optional explicit invalidation-client override)
|
|
149
|
+
- shared `queryOptions`
|
|
150
|
+
- shared `mutationOptions`
|
|
151
|
+
|
|
152
|
+
Mutation invalidation uses the `QueryClient` from the active mutation context by default. Supply `queryClient` only when invalidation intentionally needs to target a different client.
|
|
153
|
+
|
|
154
|
+
Shared defaults use TanStack Query's default-option contracts, so misspelled or unsupported option names fail TypeScript checks.
|
|
155
|
+
|
|
156
|
+
## 2) Endpoint Definition Types
|
|
157
|
+
|
|
158
|
+
- `api.query({ queryFn, options? })`
|
|
159
|
+
- `api.mutation({ mutationFn, options?, invalidateQueries? })`
|
|
160
|
+
- `api.infiniteQuery({ queryFn, options })`
|
|
161
|
+
|
|
162
|
+
## 3) Generated Endpoint API
|
|
163
|
+
|
|
164
|
+
For a `query` endpoint:
|
|
165
|
+
|
|
166
|
+
- `useQuery(state?, options?)`
|
|
167
|
+
- `useSuspenseQuery(state?, options?)`
|
|
168
|
+
- `getKey(state?)`
|
|
169
|
+
- `getOptions(state?)`
|
|
170
|
+
|
|
171
|
+
For a `mutation` endpoint:
|
|
172
|
+
|
|
173
|
+
- `useMutation(options?)`
|
|
174
|
+
- `getKey()`
|
|
175
|
+
|
|
176
|
+
For an `infiniteQuery` endpoint:
|
|
177
|
+
|
|
178
|
+
- `useInfiniteQuery(state?, options?)`
|
|
179
|
+
- `getKey(state?)`
|
|
180
|
+
- `getOptions(state?)`
|
|
181
|
+
|
|
182
|
+
## 4) Automatic Invalidation
|
|
183
|
+
|
|
184
|
+
Mutations can invalidate related queries centrally:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
type OrdersInvalidationApi = {
|
|
188
|
+
getKey: () => QueryKey;
|
|
189
|
+
list: { getKey: () => QueryKey };
|
|
190
|
+
myOrders: { getKey: () => QueryKey };
|
|
191
|
+
getById: { getKey: (state: { orderId: string }) => QueryKey };
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
createOrder: api.mutation<
|
|
195
|
+
Response<Order>,
|
|
196
|
+
{ body: CreateOrder },
|
|
197
|
+
ApiError,
|
|
198
|
+
OrdersInvalidationApi
|
|
199
|
+
>({
|
|
200
|
+
mutationFn: async ({ body }) => (await apiClient.post('/orders', body)).data,
|
|
201
|
+
invalidateQueries: (router, variables, result, onMutateResult, context) => [
|
|
202
|
+
router.list.getKey(),
|
|
203
|
+
router.myOrders.getKey(),
|
|
204
|
+
router.getById.getKey({ orderId: result.data.id }),
|
|
205
|
+
],
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
This removes invalidation duplication from components.
|
|
210
|
+
|
|
211
|
+
## 5) Option Layering (important)
|
|
212
|
+
|
|
213
|
+
Final options are merged in this order:
|
|
214
|
+
|
|
215
|
+
1. API defaults
|
|
216
|
+
2. Endpoint options
|
|
217
|
+
3. Call-site options
|
|
218
|
+
|
|
219
|
+
This gives global consistency with local flexibility.
|
|
220
|
+
|
|
221
|
+
Call sites may intentionally override a generated query key:
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
const query = usersApi.getProfile.useQuery(undefined, {
|
|
225
|
+
queryKey: ['users', 'profile', 'embedded-view'],
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
`getKey()` still returns the canonical endpoint key. When a call site uses a different key, that call site owns invalidation of the alternate key.
|
|
230
|
+
|
|
231
|
+
## Real Usage Patterns
|
|
232
|
+
|
|
233
|
+
## Pattern: Router Loaders / Prefetch
|
|
234
|
+
|
|
235
|
+
Use the exact same endpoint definition in route loaders:
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
await queryClient.ensureQueryData(
|
|
239
|
+
ordersApi.getById.getOptions({ orderId })
|
|
240
|
+
);
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
No duplicate keys. No duplicate fetch logic.
|
|
244
|
+
|
|
245
|
+
## Pattern: Suspense
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
const { data } = ordersApi.getById.useSuspenseQuery({ orderId });
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Pattern: Query cancellation and context
|
|
252
|
+
|
|
253
|
+
Standard query functions receive state first and TanStack's `QueryFunctionContext` second:
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
download: api.query<Blob, { documentId: string }, ApiError>({
|
|
257
|
+
queryFn: async ({ documentId }, { signal }) => {
|
|
258
|
+
const response = await fetch(`/documents/${documentId}`, { signal });
|
|
259
|
+
return response.blob();
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Existing query functions that only need state may omit the second parameter.
|
|
265
|
+
|
|
266
|
+
## Pattern: `useQueries` composition
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
const results = useQueries({
|
|
270
|
+
queries: [
|
|
271
|
+
ordersApi.myOrders.getOptions({ page: 1, limit: 100 }),
|
|
272
|
+
ticketApi.myTickets.getOptions({ page: 1, limit: 100 }),
|
|
273
|
+
usersApi.getProfile.getOptions(),
|
|
274
|
+
],
|
|
275
|
+
});
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Pattern: Dynamic endpoint selection
|
|
279
|
+
|
|
280
|
+
When context decides which endpoint to call, compose with `getOptions()`:
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
const options =
|
|
284
|
+
context === 'invoice'
|
|
285
|
+
? invoicesApi.getDownloadUrl.getOptions({ invoiceId: id })
|
|
286
|
+
: attachmentsApi.getDownloadUrl.getOptions({ attachmentId: id });
|
|
287
|
+
|
|
288
|
+
const result = await queryClient.fetchQuery({ ...options, staleTime: 0 });
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Pattern: Infinite query endpoint definition
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
const addressesApi = api(
|
|
295
|
+
'addresses',
|
|
296
|
+
{
|
|
297
|
+
infiniteList: api.infiniteQuery<Paginated<Address[]>, void, number, ApiError>({
|
|
298
|
+
queryFn: async (_state, { pageParam }) =>
|
|
299
|
+
(await apiClient.get(`/account/addresses?page=${pageParam}&limit=10`)).data,
|
|
300
|
+
options: {
|
|
301
|
+
initialPageParam: 1,
|
|
302
|
+
getNextPageParam: (lastPage) =>
|
|
303
|
+
lastPage.pagination.totalPages > lastPage.pagination.page
|
|
304
|
+
? lastPage.pagination.page + 1
|
|
305
|
+
: undefined,
|
|
306
|
+
},
|
|
307
|
+
}),
|
|
308
|
+
},
|
|
309
|
+
);
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Usage:
|
|
313
|
+
|
|
314
|
+
```tsx
|
|
315
|
+
const listQuery = addressesApi.infiniteList.useInfiniteQuery();
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Required pagination behavior lives with the endpoint so `getOptions()` is also complete. Components can still provide partial overrides as the second argument.
|
|
319
|
+
|
|
320
|
+
## Pattern: Manual invalidation when needed
|
|
321
|
+
|
|
322
|
+
You still have full QueryClient control:
|
|
323
|
+
|
|
324
|
+
```ts
|
|
325
|
+
await queryClient.invalidateQueries({
|
|
326
|
+
queryKey: ordersApi.getById.getKey({ orderId }),
|
|
327
|
+
});
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## API Reference
|
|
331
|
+
|
|
332
|
+
## `api(baseKey, endpoints, defaultOptions?)`
|
|
333
|
+
|
|
334
|
+
- `baseKey: string`
|
|
335
|
+
- `endpoints: Record<string, EndpointDefinition>`
|
|
336
|
+
- `defaultOptions?: { queryClient?: QueryClient; queryOptions?: ApiQueryOptions; mutationOptions?: ApiMutationOptions }`
|
|
337
|
+
|
|
338
|
+
Returns an API instance with:
|
|
339
|
+
|
|
340
|
+
- `getKey(): QueryKey` for base namespace
|
|
341
|
+
- endpoint objects with generated helpers/hooks
|
|
342
|
+
|
|
343
|
+
## `api.query<TQueryFnData, TState, TError, TData = TQueryFnData>(definition)`
|
|
344
|
+
|
|
345
|
+
`definition`:
|
|
346
|
+
|
|
347
|
+
- `queryFn: (state: TState, context: QueryFunctionContext) => Promise<TQueryFnData>`
|
|
348
|
+
- `options?: UseQueryOptions without queryKey/queryFn`
|
|
349
|
+
|
|
350
|
+
## `api.mutation<TData, TVariables, TError, TApi, TOnMutateResult>(definition)`
|
|
351
|
+
|
|
352
|
+
`definition`:
|
|
353
|
+
|
|
354
|
+
- `mutationFn: (variables: TVariables) => Promise<TData>`
|
|
355
|
+
- `options?: UseMutationOptions without mutationFn`
|
|
356
|
+
- `invalidateQueries?: (api, variables, data, onMutateResult, context) => Array<QueryKey | InvalidateQueryFilters>`
|
|
357
|
+
|
|
358
|
+
`TApi` is an explicit router contract for invalidation callbacks. Query state, results, hook options, and mutation values remain inferred from their endpoint definitions.
|
|
359
|
+
|
|
360
|
+
## `api.infiniteQuery<TQueryFnData, TState, TPageParam, TError, TData = InfiniteData<TQueryFnData, TPageParam>>(definition)`
|
|
361
|
+
|
|
362
|
+
`definition`:
|
|
363
|
+
|
|
364
|
+
- `queryFn: (state: TState, ctx: QueryFunctionContext) => Promise<TQueryFnData>`
|
|
365
|
+
- `options: UseInfiniteQueryOptions without queryKey/queryFn`
|
|
366
|
+
|
|
367
|
+
`options` must include `initialPageParam` and `getNextPageParam`. This guarantees that both the generated hook and `getOptions()` are valid TanStack infinite-query configurations.
|
|
368
|
+
|
|
369
|
+
## Why Teams Like It
|
|
370
|
+
|
|
371
|
+
- Less boilerplate than raw hooks per endpoint
|
|
372
|
+
- Better consistency across large codebases
|
|
373
|
+
- Easy discoverability: “all order endpoints are in `ordersApi`”
|
|
374
|
+
- Shared patterns for components, loaders, and utilities
|
|
375
|
+
- Safer refactors with central key ownership
|
|
376
|
+
- Easy onboarding for new team members
|
|
377
|
+
|
|
378
|
+
## Before vs After
|
|
379
|
+
|
|
380
|
+
Raw React Query style:
|
|
381
|
+
|
|
382
|
+
```ts
|
|
383
|
+
useQuery({
|
|
384
|
+
queryKey: ['orders', 'getById', { orderId }],
|
|
385
|
+
queryFn: () => apiClient.get(`/orders/${orderId}`).then((r) => r.data),
|
|
386
|
+
});
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
With Query API Router:
|
|
390
|
+
|
|
391
|
+
```ts
|
|
392
|
+
ordersApi.getById.useQuery({ orderId });
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
And for loaders:
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
await queryClient.ensureQueryData(ordersApi.getById.getOptions({ orderId }));
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
One endpoint definition, reused everywhere.
|
|
402
|
+
|
|
403
|
+
## Best Practices
|
|
404
|
+
|
|
405
|
+
- Keep one router per domain (`usersApi`, `ordersApi`, `ticketApi`)
|
|
406
|
+
- Use descriptive endpoint names (`getById`, `list`, `update`, `activate`)
|
|
407
|
+
- Keep `state` objects serializable and stable
|
|
408
|
+
- Put cache policy (`staleTime`, `gcTime`, polling) at endpoint level
|
|
409
|
+
- Prefer mutation `invalidateQueries` over scattered component invalidation
|
|
410
|
+
- Use `getOptions()` for non-hook contexts (loaders, utility flows, background tasks)
|
|
411
|
+
- Use API-level defaults for cross-cutting behavior (retry/error handling)
|
|
412
|
+
|
|
413
|
+
## FAQ
|
|
414
|
+
|
|
415
|
+
## Does this replace TanStack Query?
|
|
416
|
+
|
|
417
|
+
No. It is a structured layer on top of TanStack Query.
|
|
418
|
+
|
|
419
|
+
## Can I still use QueryClient directly?
|
|
420
|
+
|
|
421
|
+
Yes. Fully compatible. `getKey()` and `getOptions()` are designed for that.
|
|
422
|
+
|
|
423
|
+
## Can I use it without Axios?
|
|
424
|
+
|
|
425
|
+
Yes. Axios is not a dependency or peer dependency. Any async client works, and applications define their own error types.
|
|
426
|
+
|
|
427
|
+
## Does it support Suspense and Infinite Query?
|
|
428
|
+
|
|
429
|
+
Yes. `useSuspenseQuery` and `useInfiniteQuery` are first-class.
|
|
430
|
+
|
|
431
|
+
## Is this good for large apps?
|
|
432
|
+
|
|
433
|
+
Yes, that is the primary use case.
|
|
434
|
+
|
|
435
|
+
## Package and License
|
|
436
|
+
|
|
437
|
+
The package publishes ESM JavaScript, TypeScript declarations, declaration maps, runtime source maps, and their TypeScript sources. Runtime maps embed their source content, while declaration maps resolve to the shipped `src/` tree. This keeps debugging and editor navigation working from the installed package; the export map still prevents unsupported source-level imports.
|
|
438
|
+
|
|
439
|
+
Query API Router is available under the [MIT License](./LICENSE).
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { InfiniteData } from '@tanstack/react-query';
|
|
2
|
+
import { AnyEndpointDefinition, ApiInstance, ApiOptions, InfiniteQueryEndpointDefinition, InfiniteQueryEndpointOptions, InvalidationApi, MutationEndpointDefinition, MutationEndpointOptions, QueryEndpointDefinition, QueryEndpointOptions } from './types.js';
|
|
3
|
+
declare const api: {
|
|
4
|
+
<TBaseKey extends string, TEndpoints extends Record<string, AnyEndpointDefinition>>(baseKey: TBaseKey, endpoints: TEndpoints, defaultOptions?: ApiOptions): ApiInstance<TBaseKey, TEndpoints>;
|
|
5
|
+
query<TQueryFnData = unknown, TState = void, TError = unknown, TData = TQueryFnData>(definition: QueryEndpointOptions<TQueryFnData, TState, TData, TError>): QueryEndpointDefinition<TQueryFnData, TState, TData, TError>;
|
|
6
|
+
infiniteQuery<TQueryFnData = unknown, TState = void, TPageParam = unknown, TError = unknown, TData = InfiniteData<TQueryFnData, TPageParam>>(definition: InfiniteQueryEndpointOptions<TQueryFnData, TState, TPageParam, TError, TData>): InfiniteQueryEndpointDefinition<TQueryFnData, TState, TPageParam, TData, TError>;
|
|
7
|
+
mutation<TData = unknown, TState = unknown, TError = unknown, TApi = InvalidationApi, TOnMutateResult = unknown>(definition: MutationEndpointOptions<TData, TState, TError, TApi, TOnMutateResult>): MutationEndpointDefinition<TData, TState, TError, TApi, TOnMutateResult>;
|
|
8
|
+
};
|
|
9
|
+
export default api;
|
|
10
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAkC,MAAM,uBAAuB,CAAC;AAE1F,OAAO,EACH,qBAAqB,EACrB,WAAW,EACX,UAAU,EACV,+BAA+B,EAC/B,4BAA4B,EAC5B,eAAe,EACf,0BAA0B,EAC1B,uBAAuB,EACvB,uBAAuB,EACvB,oBAAoB,EACvB,MAAM,YAAY,CAAC;AAIpB,QAAA,MAAM,GAAG;KAAI,QAAQ,SAAS,MAAM,EAAE,UAAU,SAAS,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,WACjF,QAAQ,aACN,UAAU,mBACJ,UAAU,GAC5B,WAAW,CAAC,QAAQ,EAAE,UAAU,CAAC;UAwGvB,YAAY,YAAY,MAAM,SAAS,MAAM,YAAY,KAAK,6BAC3D,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,GACtE,uBAAuB,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC;kBAM3D,YAAY,YACZ,MAAM,SACN,UAAU,YACV,MAAM,YACN,KAAK,uDAEO,4BAA4B,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,GAC1F,+BAA+B,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,CAAC;aAKnE,KAAK,YAAY,MAAM,YAAY,MAAM,YAAY,IAAI,oBAAoB,eAAe,wBAC5F,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,CAAC,GAClF,0BAA0B,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,CAAC;CAzB1E,CAAC;AA8BF,eAAe,GAAG,CAAC"}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { useInfiniteQuery, useMutation, useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
|
3
|
+
import { createEndpointKey, handleMutationSuccess } from './internal.js';
|
|
4
|
+
// --- API Builder ---
|
|
5
|
+
const api = (baseKey, endpoints, defaultOptions) => {
|
|
6
|
+
const configuredQueryClient = defaultOptions?.queryClient;
|
|
7
|
+
const apiInstance = {
|
|
8
|
+
getKey: () => [baseKey],
|
|
9
|
+
};
|
|
10
|
+
for (const key in endpoints) {
|
|
11
|
+
const definition = endpoints[key];
|
|
12
|
+
if (definition.type === 'query') {
|
|
13
|
+
const queryDefinition = definition;
|
|
14
|
+
apiInstance[key] = {
|
|
15
|
+
getKey: (state) => createEndpointKey(baseKey, key, state),
|
|
16
|
+
useQuery: (state, options = {}) => {
|
|
17
|
+
const queryKey = createEndpointKey(baseKey, key, state);
|
|
18
|
+
return useQuery({
|
|
19
|
+
queryKey,
|
|
20
|
+
queryFn: (context) => queryDefinition.queryFn(state, context),
|
|
21
|
+
...defaultOptions?.queryOptions,
|
|
22
|
+
...queryDefinition.options,
|
|
23
|
+
...options,
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
useSuspenseQuery: (state, options = {}) => {
|
|
27
|
+
const queryKey = createEndpointKey(baseKey, key, state);
|
|
28
|
+
return useSuspenseQuery({
|
|
29
|
+
queryKey,
|
|
30
|
+
queryFn: (context) => queryDefinition.queryFn(state, context),
|
|
31
|
+
...defaultOptions?.queryOptions,
|
|
32
|
+
...queryDefinition.options,
|
|
33
|
+
...options,
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
getOptions: (state) => ({
|
|
37
|
+
queryKey: createEndpointKey(baseKey, key, state),
|
|
38
|
+
queryFn: (context) => queryDefinition.queryFn(state, context),
|
|
39
|
+
...defaultOptions?.queryOptions,
|
|
40
|
+
...queryDefinition.options,
|
|
41
|
+
}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
else if (definition.type === 'infiniteQuery') {
|
|
45
|
+
const queryDefinition = definition;
|
|
46
|
+
apiInstance[key] = {
|
|
47
|
+
getKey: (state) => createEndpointKey(baseKey, key, state),
|
|
48
|
+
useInfiniteQuery: (state, options = {}) => {
|
|
49
|
+
const queryKey = createEndpointKey(baseKey, key, state);
|
|
50
|
+
return useInfiniteQuery({
|
|
51
|
+
queryKey,
|
|
52
|
+
...defaultOptions?.queryOptions,
|
|
53
|
+
...queryDefinition.options,
|
|
54
|
+
...options,
|
|
55
|
+
queryFn: (context) => queryDefinition.queryFn(state, context),
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
getOptions: (state) => ({
|
|
59
|
+
queryKey: createEndpointKey(baseKey, key, state),
|
|
60
|
+
queryFn: (context) => queryDefinition.queryFn(state, context),
|
|
61
|
+
...defaultOptions?.queryOptions,
|
|
62
|
+
...queryDefinition.options,
|
|
63
|
+
}),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
else if (definition.type === 'mutation') {
|
|
67
|
+
const mutationDefinition = definition;
|
|
68
|
+
apiInstance[key] = {
|
|
69
|
+
getKey: () => [baseKey, key],
|
|
70
|
+
useMutation: (options = {}) => {
|
|
71
|
+
const mutationKey = [baseKey, key];
|
|
72
|
+
const defaultMutationOptions = defaultOptions?.mutationOptions;
|
|
73
|
+
const endpointMutationOptions = mutationDefinition.options;
|
|
74
|
+
return useMutation({
|
|
75
|
+
mutationKey,
|
|
76
|
+
mutationFn: mutationDefinition.mutationFn,
|
|
77
|
+
...defaultMutationOptions,
|
|
78
|
+
...endpointMutationOptions,
|
|
79
|
+
...options,
|
|
80
|
+
onSuccess: async (data, variables, onMutateResult, context) => {
|
|
81
|
+
await handleMutationSuccess({
|
|
82
|
+
apiInstance,
|
|
83
|
+
callSiteOnSuccess: options?.onSuccess,
|
|
84
|
+
context,
|
|
85
|
+
data,
|
|
86
|
+
defaultOnSuccess: defaultMutationOptions?.onSuccess,
|
|
87
|
+
endpointOnSuccess: endpointMutationOptions?.onSuccess,
|
|
88
|
+
invalidateQueries: mutationDefinition.invalidateQueries,
|
|
89
|
+
onMutateResult,
|
|
90
|
+
queryClient: configuredQueryClient ?? context.client,
|
|
91
|
+
variables,
|
|
92
|
+
});
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return apiInstance;
|
|
100
|
+
};
|
|
101
|
+
// --- Attach factory functions ---
|
|
102
|
+
api.query = (definition) => ({
|
|
103
|
+
...definition,
|
|
104
|
+
type: 'query',
|
|
105
|
+
});
|
|
106
|
+
api.infiniteQuery = (definition) => ({
|
|
107
|
+
...definition,
|
|
108
|
+
type: 'infiniteQuery',
|
|
109
|
+
});
|
|
110
|
+
api.mutation = (definition) => ({
|
|
111
|
+
...definition,
|
|
112
|
+
type: 'mutation',
|
|
113
|
+
});
|
|
114
|
+
export default api;
|
|
115
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { useInfiniteQuery, useMutation, useQuery, useSuspenseQuery } from '@tanstack/react-query';\nimport type { InfiniteData, QueryFunctionContext, QueryKey } from '@tanstack/react-query';\n\nimport {\n AnyEndpointDefinition,\n ApiInstance,\n ApiOptions,\n InfiniteQueryEndpointDefinition,\n InfiniteQueryEndpointOptions,\n InvalidationApi,\n MutationEndpointDefinition,\n MutationEndpointOptions,\n QueryEndpointDefinition,\n QueryEndpointOptions,\n} from './types.js';\nimport { createEndpointKey, handleMutationSuccess } from './internal.js';\n\n// --- API Builder ---\nconst api = <TBaseKey extends string, TEndpoints extends Record<string, AnyEndpointDefinition>>(\n baseKey: TBaseKey,\n endpoints: TEndpoints,\n defaultOptions?: ApiOptions,\n): ApiInstance<TBaseKey, TEndpoints> => {\n const configuredQueryClient = defaultOptions?.queryClient;\n\n const apiInstance: Record<string, unknown> & { getKey: () => QueryKey } = {\n getKey: () => [baseKey],\n };\n\n for (const key in endpoints) {\n const definition = endpoints[key];\n\n if (definition.type === 'query') {\n const queryDefinition = definition as QueryEndpointDefinition<any, any, any, any>;\n\n apiInstance[key] = {\n getKey: (state: unknown) => createEndpointKey(baseKey, key, state),\n useQuery: (state: unknown, options: any = {}) => {\n const queryKey = createEndpointKey(baseKey, key, state);\n return useQuery({\n queryKey,\n queryFn: (context) => queryDefinition.queryFn(state, context),\n ...defaultOptions?.queryOptions,\n ...queryDefinition.options,\n ...options,\n });\n },\n useSuspenseQuery: (state: unknown, options: any = {}) => {\n const queryKey = createEndpointKey(baseKey, key, state);\n return useSuspenseQuery({\n queryKey,\n queryFn: (context) => queryDefinition.queryFn(state, context),\n ...defaultOptions?.queryOptions,\n ...queryDefinition.options,\n ...options,\n });\n },\n getOptions: (state: unknown) => ({\n queryKey: createEndpointKey(baseKey, key, state),\n queryFn: (context: QueryFunctionContext<QueryKey>) => queryDefinition.queryFn(state, context),\n ...defaultOptions?.queryOptions,\n ...queryDefinition.options,\n }),\n };\n } else if (definition.type === 'infiniteQuery') {\n const queryDefinition = definition as InfiniteQueryEndpointDefinition<any, any, any, any, any>;\n\n apiInstance[key] = {\n getKey: (state: unknown) => createEndpointKey(baseKey, key, state),\n useInfiniteQuery: (state: unknown, options: any = {}) => {\n const queryKey = createEndpointKey(baseKey, key, state);\n return useInfiniteQuery({\n queryKey,\n ...defaultOptions?.queryOptions,\n ...queryDefinition.options,\n ...options,\n queryFn: (context: QueryFunctionContext<QueryKey, unknown>) => queryDefinition.queryFn(state, context),\n });\n },\n getOptions: (state: unknown) => ({\n queryKey: createEndpointKey(baseKey, key, state),\n queryFn: (context: QueryFunctionContext<QueryKey, unknown>) => queryDefinition.queryFn(state, context),\n ...defaultOptions?.queryOptions,\n ...queryDefinition.options,\n }),\n };\n } else if (definition.type === 'mutation') {\n const mutationDefinition = definition as MutationEndpointDefinition<any, any, any, any, any>;\n\n apiInstance[key] = {\n getKey: () => [baseKey, key],\n useMutation: (options: any = {}) => {\n const mutationKey = [baseKey, key];\n const defaultMutationOptions = defaultOptions?.mutationOptions;\n const endpointMutationOptions = mutationDefinition.options;\n\n return useMutation({\n mutationKey,\n mutationFn: mutationDefinition.mutationFn,\n ...defaultMutationOptions,\n ...endpointMutationOptions,\n ...options,\n onSuccess: async (data, variables, onMutateResult, context) => {\n await handleMutationSuccess({\n apiInstance,\n callSiteOnSuccess: options?.onSuccess,\n context,\n data,\n defaultOnSuccess: defaultMutationOptions?.onSuccess,\n endpointOnSuccess: endpointMutationOptions?.onSuccess,\n invalidateQueries: mutationDefinition.invalidateQueries,\n onMutateResult,\n queryClient: configuredQueryClient ?? context.client,\n variables,\n });\n },\n });\n },\n };\n }\n }\n\n return apiInstance as ApiInstance<TBaseKey, TEndpoints>;\n};\n\n// --- Attach factory functions ---\napi.query = <TQueryFnData = unknown, TState = void, TError = unknown, TData = TQueryFnData>(\n definition: QueryEndpointOptions<TQueryFnData, TState, TData, TError>,\n): QueryEndpointDefinition<TQueryFnData, TState, TData, TError> => ({\n ...definition,\n type: 'query',\n});\n\napi.infiniteQuery = <\n TQueryFnData = unknown,\n TState = void,\n TPageParam = unknown,\n TError = unknown,\n TData = InfiniteData<TQueryFnData, TPageParam>,\n>(\n definition: InfiniteQueryEndpointOptions<TQueryFnData, TState, TPageParam, TError, TData>,\n): InfiniteQueryEndpointDefinition<TQueryFnData, TState, TPageParam, TData, TError> => ({\n ...definition,\n type: 'infiniteQuery',\n});\n\napi.mutation = <TData = unknown, TState = unknown, TError = unknown, TApi = InvalidationApi, TOnMutateResult = unknown>(\n definition: MutationEndpointOptions<TData, TState, TError, TApi, TOnMutateResult>,\n): MutationEndpointDefinition<TData, TState, TError, TApi, TOnMutateResult> => ({\n ...definition,\n type: 'mutation',\n});\n\nexport default api;\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,cAAc,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,cAAc,YAAY,CAAC","sourcesContent":["export { default } from './api.js';\nexport * from './types.js';\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { InvalidateQueryFilters, MutationFunctionContext, QueryClient, QueryKey } from '@tanstack/react-query';
|
|
2
|
+
export type InvalidateQueryTarget = QueryKey | InvalidateQueryFilters;
|
|
3
|
+
export type MutationSuccessHandlerOptions<TApi, TData, TVariables, TOnMutateResult> = {
|
|
4
|
+
apiInstance: TApi;
|
|
5
|
+
callSiteOnSuccess?: MutationOnSuccess<TData, TVariables, TOnMutateResult>;
|
|
6
|
+
context: MutationFunctionContext;
|
|
7
|
+
data: TData;
|
|
8
|
+
defaultOnSuccess?: MutationOnSuccess<TData, TVariables, TOnMutateResult>;
|
|
9
|
+
endpointOnSuccess?: MutationOnSuccess<TData, TVariables, TOnMutateResult>;
|
|
10
|
+
invalidateQueries?: (api: TApi, variables: TVariables, data: TData, onMutateResult: TOnMutateResult, context: MutationFunctionContext) => readonly InvalidateQueryTarget[];
|
|
11
|
+
onMutateResult: TOnMutateResult;
|
|
12
|
+
queryClient: Pick<QueryClient, 'invalidateQueries'>;
|
|
13
|
+
variables: TVariables;
|
|
14
|
+
};
|
|
15
|
+
type MutationOnSuccess<TData, TVariables, TOnMutateResult> = (data: TData, variables: TVariables, onMutateResult: TOnMutateResult, context: MutationFunctionContext) => Promise<unknown> | unknown;
|
|
16
|
+
export declare const createEndpointKey: (baseKey: string, endpointKey: string, state?: unknown) => QueryKey;
|
|
17
|
+
export declare const normalizeInvalidateQueryTarget: (target: InvalidateQueryTarget) => InvalidateQueryFilters;
|
|
18
|
+
export declare const handleMutationSuccess: <TApi, TData, TVariables, TOnMutateResult>({ apiInstance, callSiteOnSuccess, context, data, defaultOnSuccess, endpointOnSuccess, invalidateQueries, onMutateResult, queryClient, variables, }: MutationSuccessHandlerOptions<TApi, TData, TVariables, TOnMutateResult>) => Promise<void>;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=internal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEpH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,GAAG,sBAAsB,CAAC;AAEtE,MAAM,MAAM,6BAA6B,CAAC,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,eAAe,IAAI;IAClF,WAAW,EAAE,IAAI,CAAC;IAClB,iBAAiB,CAAC,EAAE,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;IAC1E,OAAO,EAAE,uBAAuB,CAAC;IACjC,IAAI,EAAE,KAAK,CAAC;IACZ,gBAAgB,CAAC,EAAE,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;IACzE,iBAAiB,CAAC,EAAE,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;IAC1E,iBAAiB,CAAC,EAAE,CAChB,GAAG,EAAE,IAAI,EACT,SAAS,EAAE,UAAU,EACrB,IAAI,EAAE,KAAK,EACX,cAAc,EAAE,eAAe,EAC/B,OAAO,EAAE,uBAAuB,KAC/B,SAAS,qBAAqB,EAAE,CAAC;IACtC,cAAc,EAAE,eAAe,CAAC;IAChC,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC;IACpD,SAAS,EAAE,UAAU,CAAC;CACzB,CAAC;AAEF,KAAK,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,eAAe,IAAI,CACzD,IAAI,EAAE,KAAK,EACX,SAAS,EAAE,UAAU,EACrB,cAAc,EAAE,eAAe,EAC/B,OAAO,EAAE,uBAAuB,KAC/B,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;AAEhC,eAAO,MAAM,iBAAiB,GAAI,SAAS,MAAM,EAAE,aAAa,MAAM,EAAE,QAAQ,OAAO,KAAG,QAEzF,CAAC;AAEF,eAAO,MAAM,8BAA8B,GAAI,QAAQ,qBAAqB,KAAG,sBAE9E,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAAU,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,oJAWnF,6BAA6B,CAAC,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,eAAe,CAAC,KAAG,OAAO,CAAC,IAAI,CAcxF,CAAC"}
|
package/dist/internal.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const createEndpointKey = (baseKey, endpointKey, state) => (state === undefined ? [baseKey, endpointKey] : [baseKey, endpointKey, state]);
|
|
2
|
+
export const normalizeInvalidateQueryTarget = (target) => (Array.isArray(target) ? { queryKey: target } : target);
|
|
3
|
+
export const handleMutationSuccess = async ({ apiInstance, callSiteOnSuccess, context, data, defaultOnSuccess, endpointOnSuccess, invalidateQueries, onMutateResult, queryClient, variables, }) => {
|
|
4
|
+
if (invalidateQueries) {
|
|
5
|
+
const invalidationTargets = invalidateQueries(apiInstance, variables, data, onMutateResult, context);
|
|
6
|
+
await Promise.all(invalidationTargets.map((invalidationTarget) => (queryClient.invalidateQueries(normalizeInvalidateQueryTarget(invalidationTarget)))));
|
|
7
|
+
}
|
|
8
|
+
await defaultOnSuccess?.(data, variables, onMutateResult, context);
|
|
9
|
+
await endpointOnSuccess?.(data, variables, onMutateResult, context);
|
|
10
|
+
await callSiteOnSuccess?.(data, variables, onMutateResult, context);
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=internal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal.js","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AA8BA,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,OAAe,EAAE,WAAmB,EAAE,KAAe,EAAY,EAAE,CAAC,CAClG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,CAAC,CAC/E,CAAC;AAEF,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAAC,MAA6B,EAA0B,EAAE,CAAC,CACrG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAkB,EAAE,CAAC,CAAC,CAAC,MAAgC,CAC9F,CAAC;AAEF,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,EAA4C,EAClF,WAAW,EACX,iBAAiB,EACjB,OAAO,EACP,IAAI,EACJ,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,WAAW,EACX,SAAS,GAC6D,EAAiB,EAAE;IACzF,IAAI,iBAAiB,EAAE,CAAC;QACpB,MAAM,mBAAmB,GAAG,iBAAiB,CAAC,WAAW,EAAE,SAAS,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC;QAErG,MAAM,OAAO,CAAC,GAAG,CACb,mBAAmB,CAAC,GAAG,CAAC,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAC5C,WAAW,CAAC,iBAAiB,CAAC,8BAA8B,CAAC,kBAAkB,CAAC,CAAC,CACpF,CAAC,CACL,CAAC;IACN,CAAC;IAED,MAAM,gBAAgB,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC;IACnE,MAAM,iBAAiB,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC;IACpE,MAAM,iBAAiB,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC;AACxE,CAAC,CAAC","sourcesContent":["import type { InvalidateQueryFilters, MutationFunctionContext, QueryClient, QueryKey } from '@tanstack/react-query';\n\nexport type InvalidateQueryTarget = QueryKey | InvalidateQueryFilters;\n\nexport type MutationSuccessHandlerOptions<TApi, TData, TVariables, TOnMutateResult> = {\n apiInstance: TApi;\n callSiteOnSuccess?: MutationOnSuccess<TData, TVariables, TOnMutateResult>;\n context: MutationFunctionContext;\n data: TData;\n defaultOnSuccess?: MutationOnSuccess<TData, TVariables, TOnMutateResult>;\n endpointOnSuccess?: MutationOnSuccess<TData, TVariables, TOnMutateResult>;\n invalidateQueries?: (\n api: TApi,\n variables: TVariables,\n data: TData,\n onMutateResult: TOnMutateResult,\n context: MutationFunctionContext,\n ) => readonly InvalidateQueryTarget[];\n onMutateResult: TOnMutateResult;\n queryClient: Pick<QueryClient, 'invalidateQueries'>;\n variables: TVariables;\n};\n\ntype MutationOnSuccess<TData, TVariables, TOnMutateResult> = (\n data: TData,\n variables: TVariables,\n onMutateResult: TOnMutateResult,\n context: MutationFunctionContext,\n) => Promise<unknown> | unknown;\n\nexport const createEndpointKey = (baseKey: string, endpointKey: string, state?: unknown): QueryKey => (\n state === undefined ? [baseKey, endpointKey] : [baseKey, endpointKey, state]\n);\n\nexport const normalizeInvalidateQueryTarget = (target: InvalidateQueryTarget): InvalidateQueryFilters => (\n Array.isArray(target) ? { queryKey: target as QueryKey } : target as InvalidateQueryFilters\n);\n\nexport const handleMutationSuccess = async <TApi, TData, TVariables, TOnMutateResult>({\n apiInstance,\n callSiteOnSuccess,\n context,\n data,\n defaultOnSuccess,\n endpointOnSuccess,\n invalidateQueries,\n onMutateResult,\n queryClient,\n variables,\n}: MutationSuccessHandlerOptions<TApi, TData, TVariables, TOnMutateResult>): Promise<void> => {\n if (invalidateQueries) {\n const invalidationTargets = invalidateQueries(apiInstance, variables, data, onMutateResult, context);\n\n await Promise.all(\n invalidationTargets.map((invalidationTarget) => (\n queryClient.invalidateQueries(normalizeInvalidateQueryTarget(invalidationTarget))\n )),\n );\n }\n\n await defaultOnSuccess?.(data, variables, onMutateResult, context);\n await endpointOnSuccess?.(data, variables, onMutateResult, context);\n await callSiteOnSuccess?.(data, variables, onMutateResult, context);\n};\n"]}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { DefaultOptions, InfiniteData, InvalidateQueryFilters, MutationFunction, MutationFunctionContext, MutationKey, QueryClient, QueryFunctionContext, QueryKey, UseInfiniteQueryOptions, UseInfiniteQueryResult, UseMutationOptions, UseMutationResult, UseQueryOptions, UseQueryResult, UseSuspenseQueryOptions, UseSuspenseQueryResult } from '@tanstack/react-query';
|
|
2
|
+
export type QueryEndpointOptions<TQueryFnData = unknown, TState = void, TData = TQueryFnData, TError = unknown> = {
|
|
3
|
+
queryFn: (state: TState, context: QueryFunctionContext<QueryKey>) => Promise<TQueryFnData>;
|
|
4
|
+
options?: Omit<UseQueryOptions<TQueryFnData, TError, TData>, 'queryKey' | 'queryFn'>;
|
|
5
|
+
};
|
|
6
|
+
export type InfiniteQueryEndpointOptions<TQueryFnData = unknown, TState = void, TPageParam = unknown, TError = unknown, TData = InfiniteData<TQueryFnData, TPageParam>> = {
|
|
7
|
+
queryFn: (state: TState, context: QueryFunctionContext<QueryKey, TPageParam>) => Promise<TQueryFnData>;
|
|
8
|
+
options: Omit<UseInfiniteQueryOptions<TQueryFnData, TError, TData, QueryKey, TPageParam>, 'queryKey' | 'queryFn'>;
|
|
9
|
+
};
|
|
10
|
+
export type InvalidateQueryTarget = QueryKey | InvalidateQueryFilters;
|
|
11
|
+
export type InvalidationApi = {
|
|
12
|
+
getKey: () => QueryKey;
|
|
13
|
+
};
|
|
14
|
+
export type MutationInvalidateQueries<TApi = InvalidationApi, TData = unknown, TState = unknown, TOnMutateResult = unknown> = (api: TApi, variables: TState, data: TData, onMutateResult: TOnMutateResult, context: MutationFunctionContext) => readonly InvalidateQueryTarget[];
|
|
15
|
+
export type MutationEndpointOptions<TData = unknown, TState = unknown, TError = unknown, TApi = InvalidationApi, TOnMutateResult = unknown> = {
|
|
16
|
+
mutationFn: MutationFunction<TData, TState>;
|
|
17
|
+
options?: Omit<UseMutationOptions<TData, TError, TState, TOnMutateResult>, 'mutationFn'>;
|
|
18
|
+
invalidateQueries?: MutationInvalidateQueries<TApi, TData, TState, TOnMutateResult>;
|
|
19
|
+
};
|
|
20
|
+
export type QueryEndpointDefinition<TQueryFnData, TState, TData, TError> = QueryEndpointOptions<TQueryFnData, TState, TData, TError> & {
|
|
21
|
+
type: 'query';
|
|
22
|
+
};
|
|
23
|
+
export type InfiniteQueryEndpointDefinition<TQueryFnData, TState, TPageParam, TData, TError> = InfiniteQueryEndpointOptions<TQueryFnData, TState, TPageParam, TError, TData> & {
|
|
24
|
+
type: 'infiniteQuery';
|
|
25
|
+
};
|
|
26
|
+
export type MutationEndpointDefinition<TData = unknown, TState = unknown, TError = unknown, TApi = InvalidationApi, TOnMutateResult = unknown> = MutationEndpointOptions<TData, TState, TError, TApi, TOnMutateResult> & {
|
|
27
|
+
type: 'mutation';
|
|
28
|
+
};
|
|
29
|
+
export type AnyEndpointDefinition = QueryEndpointDefinition<any, any, any, any> | InfiniteQueryEndpointDefinition<any, any, any, any, any> | MutationEndpointDefinition<any, any, any, any, any>;
|
|
30
|
+
export type ApiQueryOptions = NonNullable<DefaultOptions<unknown>['queries']> & {
|
|
31
|
+
queryKey?: QueryKey;
|
|
32
|
+
};
|
|
33
|
+
export type ApiMutationOptions = NonNullable<DefaultOptions<unknown>['mutations']>;
|
|
34
|
+
export interface ApiOptions {
|
|
35
|
+
queryClient?: QueryClient;
|
|
36
|
+
queryOptions?: ApiQueryOptions;
|
|
37
|
+
mutationOptions?: ApiMutationOptions;
|
|
38
|
+
}
|
|
39
|
+
type MaybeStateArgs<TState> = undefined extends TState ? [state?: TState] : [state: TState];
|
|
40
|
+
type StateAndOptionsArgs<TState, TOptions> = undefined extends TState ? [state?: TState, options?: TOptions] : [state: TState, options?: TOptions];
|
|
41
|
+
type QueryCallSiteOptions<TQueryFnData, TError, TData> = Omit<UseQueryOptions<TQueryFnData, TError, TData, QueryKey>, 'queryKey' | 'queryFn'> & {
|
|
42
|
+
queryKey?: QueryKey;
|
|
43
|
+
};
|
|
44
|
+
type SuspenseQueryCallSiteOptions<TQueryFnData, TError, TData> = Omit<UseSuspenseQueryOptions<TQueryFnData, TError, TData, QueryKey>, 'queryKey' | 'queryFn'> & {
|
|
45
|
+
queryKey?: QueryKey;
|
|
46
|
+
};
|
|
47
|
+
type InfiniteQueryCallSiteOptions<TQueryFnData, TError, TData, TPageParam> = Partial<Omit<UseInfiniteQueryOptions<TQueryFnData, TError, TData, QueryKey, TPageParam>, 'queryKey' | 'queryFn'>> & {
|
|
48
|
+
queryKey?: QueryKey;
|
|
49
|
+
};
|
|
50
|
+
export type ApiInstance<_TBaseKey extends string, TEndpoints extends Record<string, AnyEndpointDefinition>> = {
|
|
51
|
+
getKey: () => QueryKey;
|
|
52
|
+
} & {
|
|
53
|
+
[K in keyof TEndpoints]: K extends string ? TEndpoints[K] extends QueryEndpointDefinition<infer TQueryFnData, infer TState, infer TData, infer TError> ? {
|
|
54
|
+
getKey: (...args: MaybeStateArgs<TState>) => QueryKey;
|
|
55
|
+
getOptions: (...args: MaybeStateArgs<TState>) => UseQueryOptions<TQueryFnData, TError, TData, QueryKey>;
|
|
56
|
+
useQuery: <TQueryData = TData>(...args: StateAndOptionsArgs<TState, QueryCallSiteOptions<TQueryFnData, TError, TQueryData>>) => UseQueryResult<TQueryData, TError>;
|
|
57
|
+
useSuspenseQuery: <TSuspenseData = TData>(...args: StateAndOptionsArgs<TState, SuspenseQueryCallSiteOptions<TQueryFnData, TError, TSuspenseData>>) => UseSuspenseQueryResult<TSuspenseData, TError>;
|
|
58
|
+
} : TEndpoints[K] extends InfiniteQueryEndpointDefinition<infer TQueryFnData, infer TState, infer TPageParam, infer TData, infer TError> ? {
|
|
59
|
+
getKey: (...args: MaybeStateArgs<TState>) => QueryKey;
|
|
60
|
+
getOptions: (...args: MaybeStateArgs<TState>) => UseInfiniteQueryOptions<TQueryFnData, TError, TData, QueryKey, TPageParam>;
|
|
61
|
+
useInfiniteQuery: <TQueryData = TData>(...args: StateAndOptionsArgs<TState, InfiniteQueryCallSiteOptions<TQueryFnData, TError, TQueryData, TPageParam>>) => UseInfiniteQueryResult<TQueryData, TError>;
|
|
62
|
+
} : TEndpoints[K] extends MutationEndpointDefinition<infer TData, infer TState, infer TError, infer _TApi, infer TOnMutateResult> ? {
|
|
63
|
+
useMutation: (options?: Omit<UseMutationOptions<TData, TError, TState, TOnMutateResult>, 'mutationFn'>) => UseMutationResult<TData, TError, TState, TOnMutateResult>;
|
|
64
|
+
getKey: () => MutationKey;
|
|
65
|
+
} : never : never;
|
|
66
|
+
};
|
|
67
|
+
export {};
|
|
68
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import {\n DefaultOptions,\n InfiniteData,\n InvalidateQueryFilters,\n MutationFunction,\n MutationFunctionContext,\n MutationKey,\n QueryClient,\n QueryFunctionContext,\n QueryKey,\n UseInfiniteQueryOptions,\n UseInfiniteQueryResult,\n UseMutationOptions,\n UseMutationResult,\n UseQueryOptions,\n UseQueryResult,\n UseSuspenseQueryOptions,\n UseSuspenseQueryResult,\n} from '@tanstack/react-query';\n\n// --- Core Types ---\nexport type QueryEndpointOptions<\n TQueryFnData = unknown,\n TState = void,\n TData = TQueryFnData,\n TError = unknown,\n> = {\n queryFn: (state: TState, context: QueryFunctionContext<QueryKey>) => Promise<TQueryFnData>;\n options?: Omit<UseQueryOptions<TQueryFnData, TError, TData>, 'queryKey' | 'queryFn'>;\n};\n\nexport type InfiniteQueryEndpointOptions<\n TQueryFnData = unknown,\n TState = void,\n TPageParam = unknown,\n TError = unknown,\n TData = InfiniteData<TQueryFnData, TPageParam>,\n> = {\n queryFn: (state: TState, context: QueryFunctionContext<QueryKey, TPageParam>) => Promise<TQueryFnData>;\n options: Omit<UseInfiniteQueryOptions<TQueryFnData, TError, TData, QueryKey, TPageParam>, 'queryKey' | 'queryFn'>;\n};\n\nexport type InvalidateQueryTarget = QueryKey | InvalidateQueryFilters;\n\nexport type InvalidationApi = {\n getKey: () => QueryKey;\n};\n\nexport type MutationInvalidateQueries<\n TApi = InvalidationApi,\n TData = unknown,\n TState = unknown,\n TOnMutateResult = unknown,\n> = (\n api: TApi,\n variables: TState,\n data: TData,\n onMutateResult: TOnMutateResult,\n context: MutationFunctionContext,\n) => readonly InvalidateQueryTarget[];\n\nexport type MutationEndpointOptions<\n TData = unknown,\n TState = unknown,\n TError = unknown,\n TApi = InvalidationApi,\n TOnMutateResult = unknown,\n> = {\n mutationFn: MutationFunction<TData, TState>;\n options?: Omit<UseMutationOptions<TData, TError, TState, TOnMutateResult>, 'mutationFn'>;\n invalidateQueries?: MutationInvalidateQueries<TApi, TData, TState, TOnMutateResult>;\n};\n\nexport type QueryEndpointDefinition<TQueryFnData, TState, TData, TError> = QueryEndpointOptions<TQueryFnData, TState, TData, TError> & {\n type: 'query';\n};\n\nexport type InfiniteQueryEndpointDefinition<TQueryFnData, TState, TPageParam, TData, TError> = InfiniteQueryEndpointOptions<\n TQueryFnData,\n TState,\n TPageParam,\n TError,\n TData\n> & {\n type: 'infiniteQuery';\n};\n\nexport type MutationEndpointDefinition<\n TData = unknown,\n TState = unknown,\n TError = unknown,\n TApi = InvalidationApi,\n TOnMutateResult = unknown,\n> = MutationEndpointOptions<TData, TState, TError, TApi, TOnMutateResult> & {\n type: 'mutation';\n};\n\nexport type AnyEndpointDefinition =\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n | QueryEndpointDefinition<any, any, any, any>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n | InfiniteQueryEndpointDefinition<any, any, any, any, any>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n | MutationEndpointDefinition<any, any, any, any, any>;\n\nexport type ApiQueryOptions = NonNullable<DefaultOptions<unknown>['queries']> & {\n queryKey?: QueryKey;\n};\n\nexport type ApiMutationOptions = NonNullable<DefaultOptions<unknown>['mutations']>;\n\nexport interface ApiOptions {\n queryClient?: QueryClient;\n queryOptions?: ApiQueryOptions;\n mutationOptions?: ApiMutationOptions;\n}\n\ntype MaybeStateArgs<TState> = undefined extends TState ? [state?: TState] : [state: TState];\n\ntype StateAndOptionsArgs<TState, TOptions> = undefined extends TState\n ? [state?: TState, options?: TOptions]\n : [state: TState, options?: TOptions];\n\ntype QueryCallSiteOptions<TQueryFnData, TError, TData> = Omit<\n UseQueryOptions<TQueryFnData, TError, TData, QueryKey>,\n 'queryKey' | 'queryFn'\n> & {\n queryKey?: QueryKey;\n};\n\ntype SuspenseQueryCallSiteOptions<TQueryFnData, TError, TData> = Omit<\n UseSuspenseQueryOptions<TQueryFnData, TError, TData, QueryKey>,\n 'queryKey' | 'queryFn'\n> & {\n queryKey?: QueryKey;\n};\n\ntype InfiniteQueryCallSiteOptions<TQueryFnData, TError, TData, TPageParam> = Partial<\n Omit<UseInfiniteQueryOptions<TQueryFnData, TError, TData, QueryKey, TPageParam>, 'queryKey' | 'queryFn'>\n> & {\n queryKey?: QueryKey;\n};\n\n// --- API Instance Type ---\nexport type ApiInstance<_TBaseKey extends string, TEndpoints extends Record<string, AnyEndpointDefinition>> = {\n getKey: () => QueryKey;\n} & {\n [K in keyof TEndpoints]: K extends string\n ? TEndpoints[K] extends QueryEndpointDefinition<infer TQueryFnData, infer TState, infer TData, infer TError>\n ? {\n getKey: (...args: MaybeStateArgs<TState>) => QueryKey;\n getOptions: (...args: MaybeStateArgs<TState>) => UseQueryOptions<TQueryFnData, TError, TData, QueryKey>;\n useQuery: <TQueryData = TData>(\n ...args: StateAndOptionsArgs<TState, QueryCallSiteOptions<TQueryFnData, TError, TQueryData>>\n ) => UseQueryResult<TQueryData, TError>;\n useSuspenseQuery: <TSuspenseData = TData>(\n ...args: StateAndOptionsArgs<\n TState,\n SuspenseQueryCallSiteOptions<TQueryFnData, TError, TSuspenseData>\n >\n ) => UseSuspenseQueryResult<TSuspenseData, TError>;\n }\n : TEndpoints[K] extends InfiniteQueryEndpointDefinition<infer TQueryFnData, infer TState, infer TPageParam, infer TData, infer TError>\n ? {\n getKey: (...args: MaybeStateArgs<TState>) => QueryKey;\n getOptions: (...args: MaybeStateArgs<TState>) => UseInfiniteQueryOptions<TQueryFnData, TError, TData, QueryKey, TPageParam>;\n useInfiniteQuery: <TQueryData = TData>(\n ...args: StateAndOptionsArgs<\n TState,\n InfiniteQueryCallSiteOptions<TQueryFnData, TError, TQueryData, TPageParam>\n >\n ) => UseInfiniteQueryResult<TQueryData, TError>;\n }\n : TEndpoints[K] extends MutationEndpointDefinition<infer TData, infer TState, infer TError, infer _TApi, infer TOnMutateResult>\n ? {\n useMutation: (\n options?: Omit<UseMutationOptions<TData, TError, TState, TOnMutateResult>, 'mutationFn'>\n ) => UseMutationResult<TData, TError, TState, TOnMutateResult>;\n getKey: () => MutationKey;\n }\n : never\n : never;\n};\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@barandurakk/query-api-router",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Type-safe endpoint router helpers for TanStack Query.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"tanstack-query",
|
|
7
|
+
"react-query",
|
|
8
|
+
"typescript",
|
|
9
|
+
"query-keys",
|
|
10
|
+
"data-fetching"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/barandurakk/query-api-router#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/barandurakk/query-api-router/issues"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/barandurakk/query-api-router.git"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"author": {
|
|
22
|
+
"name": "Baran Durak",
|
|
23
|
+
"url": "https://github.com/barandurakk"
|
|
24
|
+
},
|
|
25
|
+
"type": "module",
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"src",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"import": "./dist/index.js",
|
|
37
|
+
"default": "./dist/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./package.json": "./package.json"
|
|
40
|
+
},
|
|
41
|
+
"main": "./dist/index.js",
|
|
42
|
+
"module": "./dist/index.js",
|
|
43
|
+
"types": "./dist/index.d.ts",
|
|
44
|
+
"packageManager": "pnpm@10.18.2",
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsc -p tsconfig.build.json",
|
|
47
|
+
"test": "pnpm run build && node --test tests/*.test.mjs",
|
|
48
|
+
"test:consumer": "node scripts/test-packed-consumer.mjs",
|
|
49
|
+
"type-check": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.type-tests.json --noEmit",
|
|
50
|
+
"verify": "pnpm run type-check && pnpm test",
|
|
51
|
+
"prepack": "pnpm run verify"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"@tanstack/react-query": "^5.90.18",
|
|
55
|
+
"react": ">=18.0.0 || >=19.0.0"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@tanstack/react-query": "^5.90.18",
|
|
59
|
+
"@types/react": "^19.2.8",
|
|
60
|
+
"@types/react-test-renderer": "^19.1.0",
|
|
61
|
+
"react": "^19.2.3",
|
|
62
|
+
"react-test-renderer": "^19.2.7",
|
|
63
|
+
"typescript": "^5.9.3"
|
|
64
|
+
},
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public"
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/api.ts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { useInfiniteQuery, useMutation, useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
|
3
|
+
import type { InfiniteData, QueryFunctionContext, QueryKey } from '@tanstack/react-query';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
AnyEndpointDefinition,
|
|
7
|
+
ApiInstance,
|
|
8
|
+
ApiOptions,
|
|
9
|
+
InfiniteQueryEndpointDefinition,
|
|
10
|
+
InfiniteQueryEndpointOptions,
|
|
11
|
+
InvalidationApi,
|
|
12
|
+
MutationEndpointDefinition,
|
|
13
|
+
MutationEndpointOptions,
|
|
14
|
+
QueryEndpointDefinition,
|
|
15
|
+
QueryEndpointOptions,
|
|
16
|
+
} from './types.js';
|
|
17
|
+
import { createEndpointKey, handleMutationSuccess } from './internal.js';
|
|
18
|
+
|
|
19
|
+
// --- API Builder ---
|
|
20
|
+
const api = <TBaseKey extends string, TEndpoints extends Record<string, AnyEndpointDefinition>>(
|
|
21
|
+
baseKey: TBaseKey,
|
|
22
|
+
endpoints: TEndpoints,
|
|
23
|
+
defaultOptions?: ApiOptions,
|
|
24
|
+
): ApiInstance<TBaseKey, TEndpoints> => {
|
|
25
|
+
const configuredQueryClient = defaultOptions?.queryClient;
|
|
26
|
+
|
|
27
|
+
const apiInstance: Record<string, unknown> & { getKey: () => QueryKey } = {
|
|
28
|
+
getKey: () => [baseKey],
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
for (const key in endpoints) {
|
|
32
|
+
const definition = endpoints[key];
|
|
33
|
+
|
|
34
|
+
if (definition.type === 'query') {
|
|
35
|
+
const queryDefinition = definition as QueryEndpointDefinition<any, any, any, any>;
|
|
36
|
+
|
|
37
|
+
apiInstance[key] = {
|
|
38
|
+
getKey: (state: unknown) => createEndpointKey(baseKey, key, state),
|
|
39
|
+
useQuery: (state: unknown, options: any = {}) => {
|
|
40
|
+
const queryKey = createEndpointKey(baseKey, key, state);
|
|
41
|
+
return useQuery({
|
|
42
|
+
queryKey,
|
|
43
|
+
queryFn: (context) => queryDefinition.queryFn(state, context),
|
|
44
|
+
...defaultOptions?.queryOptions,
|
|
45
|
+
...queryDefinition.options,
|
|
46
|
+
...options,
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
useSuspenseQuery: (state: unknown, options: any = {}) => {
|
|
50
|
+
const queryKey = createEndpointKey(baseKey, key, state);
|
|
51
|
+
return useSuspenseQuery({
|
|
52
|
+
queryKey,
|
|
53
|
+
queryFn: (context) => queryDefinition.queryFn(state, context),
|
|
54
|
+
...defaultOptions?.queryOptions,
|
|
55
|
+
...queryDefinition.options,
|
|
56
|
+
...options,
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
getOptions: (state: unknown) => ({
|
|
60
|
+
queryKey: createEndpointKey(baseKey, key, state),
|
|
61
|
+
queryFn: (context: QueryFunctionContext<QueryKey>) => queryDefinition.queryFn(state, context),
|
|
62
|
+
...defaultOptions?.queryOptions,
|
|
63
|
+
...queryDefinition.options,
|
|
64
|
+
}),
|
|
65
|
+
};
|
|
66
|
+
} else if (definition.type === 'infiniteQuery') {
|
|
67
|
+
const queryDefinition = definition as InfiniteQueryEndpointDefinition<any, any, any, any, any>;
|
|
68
|
+
|
|
69
|
+
apiInstance[key] = {
|
|
70
|
+
getKey: (state: unknown) => createEndpointKey(baseKey, key, state),
|
|
71
|
+
useInfiniteQuery: (state: unknown, options: any = {}) => {
|
|
72
|
+
const queryKey = createEndpointKey(baseKey, key, state);
|
|
73
|
+
return useInfiniteQuery({
|
|
74
|
+
queryKey,
|
|
75
|
+
...defaultOptions?.queryOptions,
|
|
76
|
+
...queryDefinition.options,
|
|
77
|
+
...options,
|
|
78
|
+
queryFn: (context: QueryFunctionContext<QueryKey, unknown>) => queryDefinition.queryFn(state, context),
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
getOptions: (state: unknown) => ({
|
|
82
|
+
queryKey: createEndpointKey(baseKey, key, state),
|
|
83
|
+
queryFn: (context: QueryFunctionContext<QueryKey, unknown>) => queryDefinition.queryFn(state, context),
|
|
84
|
+
...defaultOptions?.queryOptions,
|
|
85
|
+
...queryDefinition.options,
|
|
86
|
+
}),
|
|
87
|
+
};
|
|
88
|
+
} else if (definition.type === 'mutation') {
|
|
89
|
+
const mutationDefinition = definition as MutationEndpointDefinition<any, any, any, any, any>;
|
|
90
|
+
|
|
91
|
+
apiInstance[key] = {
|
|
92
|
+
getKey: () => [baseKey, key],
|
|
93
|
+
useMutation: (options: any = {}) => {
|
|
94
|
+
const mutationKey = [baseKey, key];
|
|
95
|
+
const defaultMutationOptions = defaultOptions?.mutationOptions;
|
|
96
|
+
const endpointMutationOptions = mutationDefinition.options;
|
|
97
|
+
|
|
98
|
+
return useMutation({
|
|
99
|
+
mutationKey,
|
|
100
|
+
mutationFn: mutationDefinition.mutationFn,
|
|
101
|
+
...defaultMutationOptions,
|
|
102
|
+
...endpointMutationOptions,
|
|
103
|
+
...options,
|
|
104
|
+
onSuccess: async (data, variables, onMutateResult, context) => {
|
|
105
|
+
await handleMutationSuccess({
|
|
106
|
+
apiInstance,
|
|
107
|
+
callSiteOnSuccess: options?.onSuccess,
|
|
108
|
+
context,
|
|
109
|
+
data,
|
|
110
|
+
defaultOnSuccess: defaultMutationOptions?.onSuccess,
|
|
111
|
+
endpointOnSuccess: endpointMutationOptions?.onSuccess,
|
|
112
|
+
invalidateQueries: mutationDefinition.invalidateQueries,
|
|
113
|
+
onMutateResult,
|
|
114
|
+
queryClient: configuredQueryClient ?? context.client,
|
|
115
|
+
variables,
|
|
116
|
+
});
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return apiInstance as ApiInstance<TBaseKey, TEndpoints>;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// --- Attach factory functions ---
|
|
128
|
+
api.query = <TQueryFnData = unknown, TState = void, TError = unknown, TData = TQueryFnData>(
|
|
129
|
+
definition: QueryEndpointOptions<TQueryFnData, TState, TData, TError>,
|
|
130
|
+
): QueryEndpointDefinition<TQueryFnData, TState, TData, TError> => ({
|
|
131
|
+
...definition,
|
|
132
|
+
type: 'query',
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
api.infiniteQuery = <
|
|
136
|
+
TQueryFnData = unknown,
|
|
137
|
+
TState = void,
|
|
138
|
+
TPageParam = unknown,
|
|
139
|
+
TError = unknown,
|
|
140
|
+
TData = InfiniteData<TQueryFnData, TPageParam>,
|
|
141
|
+
>(
|
|
142
|
+
definition: InfiniteQueryEndpointOptions<TQueryFnData, TState, TPageParam, TError, TData>,
|
|
143
|
+
): InfiniteQueryEndpointDefinition<TQueryFnData, TState, TPageParam, TData, TError> => ({
|
|
144
|
+
...definition,
|
|
145
|
+
type: 'infiniteQuery',
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
api.mutation = <TData = unknown, TState = unknown, TError = unknown, TApi = InvalidationApi, TOnMutateResult = unknown>(
|
|
149
|
+
definition: MutationEndpointOptions<TData, TState, TError, TApi, TOnMutateResult>,
|
|
150
|
+
): MutationEndpointDefinition<TData, TState, TError, TApi, TOnMutateResult> => ({
|
|
151
|
+
...definition,
|
|
152
|
+
type: 'mutation',
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
export default api;
|
package/src/index.ts
ADDED
package/src/internal.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { InvalidateQueryFilters, MutationFunctionContext, QueryClient, QueryKey } from '@tanstack/react-query';
|
|
2
|
+
|
|
3
|
+
export type InvalidateQueryTarget = QueryKey | InvalidateQueryFilters;
|
|
4
|
+
|
|
5
|
+
export type MutationSuccessHandlerOptions<TApi, TData, TVariables, TOnMutateResult> = {
|
|
6
|
+
apiInstance: TApi;
|
|
7
|
+
callSiteOnSuccess?: MutationOnSuccess<TData, TVariables, TOnMutateResult>;
|
|
8
|
+
context: MutationFunctionContext;
|
|
9
|
+
data: TData;
|
|
10
|
+
defaultOnSuccess?: MutationOnSuccess<TData, TVariables, TOnMutateResult>;
|
|
11
|
+
endpointOnSuccess?: MutationOnSuccess<TData, TVariables, TOnMutateResult>;
|
|
12
|
+
invalidateQueries?: (
|
|
13
|
+
api: TApi,
|
|
14
|
+
variables: TVariables,
|
|
15
|
+
data: TData,
|
|
16
|
+
onMutateResult: TOnMutateResult,
|
|
17
|
+
context: MutationFunctionContext,
|
|
18
|
+
) => readonly InvalidateQueryTarget[];
|
|
19
|
+
onMutateResult: TOnMutateResult;
|
|
20
|
+
queryClient: Pick<QueryClient, 'invalidateQueries'>;
|
|
21
|
+
variables: TVariables;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type MutationOnSuccess<TData, TVariables, TOnMutateResult> = (
|
|
25
|
+
data: TData,
|
|
26
|
+
variables: TVariables,
|
|
27
|
+
onMutateResult: TOnMutateResult,
|
|
28
|
+
context: MutationFunctionContext,
|
|
29
|
+
) => Promise<unknown> | unknown;
|
|
30
|
+
|
|
31
|
+
export const createEndpointKey = (baseKey: string, endpointKey: string, state?: unknown): QueryKey => (
|
|
32
|
+
state === undefined ? [baseKey, endpointKey] : [baseKey, endpointKey, state]
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export const normalizeInvalidateQueryTarget = (target: InvalidateQueryTarget): InvalidateQueryFilters => (
|
|
36
|
+
Array.isArray(target) ? { queryKey: target as QueryKey } : target as InvalidateQueryFilters
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
export const handleMutationSuccess = async <TApi, TData, TVariables, TOnMutateResult>({
|
|
40
|
+
apiInstance,
|
|
41
|
+
callSiteOnSuccess,
|
|
42
|
+
context,
|
|
43
|
+
data,
|
|
44
|
+
defaultOnSuccess,
|
|
45
|
+
endpointOnSuccess,
|
|
46
|
+
invalidateQueries,
|
|
47
|
+
onMutateResult,
|
|
48
|
+
queryClient,
|
|
49
|
+
variables,
|
|
50
|
+
}: MutationSuccessHandlerOptions<TApi, TData, TVariables, TOnMutateResult>): Promise<void> => {
|
|
51
|
+
if (invalidateQueries) {
|
|
52
|
+
const invalidationTargets = invalidateQueries(apiInstance, variables, data, onMutateResult, context);
|
|
53
|
+
|
|
54
|
+
await Promise.all(
|
|
55
|
+
invalidationTargets.map((invalidationTarget) => (
|
|
56
|
+
queryClient.invalidateQueries(normalizeInvalidateQueryTarget(invalidationTarget))
|
|
57
|
+
)),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await defaultOnSuccess?.(data, variables, onMutateResult, context);
|
|
62
|
+
await endpointOnSuccess?.(data, variables, onMutateResult, context);
|
|
63
|
+
await callSiteOnSuccess?.(data, variables, onMutateResult, context);
|
|
64
|
+
};
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DefaultOptions,
|
|
3
|
+
InfiniteData,
|
|
4
|
+
InvalidateQueryFilters,
|
|
5
|
+
MutationFunction,
|
|
6
|
+
MutationFunctionContext,
|
|
7
|
+
MutationKey,
|
|
8
|
+
QueryClient,
|
|
9
|
+
QueryFunctionContext,
|
|
10
|
+
QueryKey,
|
|
11
|
+
UseInfiniteQueryOptions,
|
|
12
|
+
UseInfiniteQueryResult,
|
|
13
|
+
UseMutationOptions,
|
|
14
|
+
UseMutationResult,
|
|
15
|
+
UseQueryOptions,
|
|
16
|
+
UseQueryResult,
|
|
17
|
+
UseSuspenseQueryOptions,
|
|
18
|
+
UseSuspenseQueryResult,
|
|
19
|
+
} from '@tanstack/react-query';
|
|
20
|
+
|
|
21
|
+
// --- Core Types ---
|
|
22
|
+
export type QueryEndpointOptions<
|
|
23
|
+
TQueryFnData = unknown,
|
|
24
|
+
TState = void,
|
|
25
|
+
TData = TQueryFnData,
|
|
26
|
+
TError = unknown,
|
|
27
|
+
> = {
|
|
28
|
+
queryFn: (state: TState, context: QueryFunctionContext<QueryKey>) => Promise<TQueryFnData>;
|
|
29
|
+
options?: Omit<UseQueryOptions<TQueryFnData, TError, TData>, 'queryKey' | 'queryFn'>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type InfiniteQueryEndpointOptions<
|
|
33
|
+
TQueryFnData = unknown,
|
|
34
|
+
TState = void,
|
|
35
|
+
TPageParam = unknown,
|
|
36
|
+
TError = unknown,
|
|
37
|
+
TData = InfiniteData<TQueryFnData, TPageParam>,
|
|
38
|
+
> = {
|
|
39
|
+
queryFn: (state: TState, context: QueryFunctionContext<QueryKey, TPageParam>) => Promise<TQueryFnData>;
|
|
40
|
+
options: Omit<UseInfiniteQueryOptions<TQueryFnData, TError, TData, QueryKey, TPageParam>, 'queryKey' | 'queryFn'>;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type InvalidateQueryTarget = QueryKey | InvalidateQueryFilters;
|
|
44
|
+
|
|
45
|
+
export type InvalidationApi = {
|
|
46
|
+
getKey: () => QueryKey;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type MutationInvalidateQueries<
|
|
50
|
+
TApi = InvalidationApi,
|
|
51
|
+
TData = unknown,
|
|
52
|
+
TState = unknown,
|
|
53
|
+
TOnMutateResult = unknown,
|
|
54
|
+
> = (
|
|
55
|
+
api: TApi,
|
|
56
|
+
variables: TState,
|
|
57
|
+
data: TData,
|
|
58
|
+
onMutateResult: TOnMutateResult,
|
|
59
|
+
context: MutationFunctionContext,
|
|
60
|
+
) => readonly InvalidateQueryTarget[];
|
|
61
|
+
|
|
62
|
+
export type MutationEndpointOptions<
|
|
63
|
+
TData = unknown,
|
|
64
|
+
TState = unknown,
|
|
65
|
+
TError = unknown,
|
|
66
|
+
TApi = InvalidationApi,
|
|
67
|
+
TOnMutateResult = unknown,
|
|
68
|
+
> = {
|
|
69
|
+
mutationFn: MutationFunction<TData, TState>;
|
|
70
|
+
options?: Omit<UseMutationOptions<TData, TError, TState, TOnMutateResult>, 'mutationFn'>;
|
|
71
|
+
invalidateQueries?: MutationInvalidateQueries<TApi, TData, TState, TOnMutateResult>;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export type QueryEndpointDefinition<TQueryFnData, TState, TData, TError> = QueryEndpointOptions<TQueryFnData, TState, TData, TError> & {
|
|
75
|
+
type: 'query';
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export type InfiniteQueryEndpointDefinition<TQueryFnData, TState, TPageParam, TData, TError> = InfiniteQueryEndpointOptions<
|
|
79
|
+
TQueryFnData,
|
|
80
|
+
TState,
|
|
81
|
+
TPageParam,
|
|
82
|
+
TError,
|
|
83
|
+
TData
|
|
84
|
+
> & {
|
|
85
|
+
type: 'infiniteQuery';
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export type MutationEndpointDefinition<
|
|
89
|
+
TData = unknown,
|
|
90
|
+
TState = unknown,
|
|
91
|
+
TError = unknown,
|
|
92
|
+
TApi = InvalidationApi,
|
|
93
|
+
TOnMutateResult = unknown,
|
|
94
|
+
> = MutationEndpointOptions<TData, TState, TError, TApi, TOnMutateResult> & {
|
|
95
|
+
type: 'mutation';
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export type AnyEndpointDefinition =
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
100
|
+
| QueryEndpointDefinition<any, any, any, any>
|
|
101
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
102
|
+
| InfiniteQueryEndpointDefinition<any, any, any, any, any>
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
104
|
+
| MutationEndpointDefinition<any, any, any, any, any>;
|
|
105
|
+
|
|
106
|
+
export type ApiQueryOptions = NonNullable<DefaultOptions<unknown>['queries']> & {
|
|
107
|
+
queryKey?: QueryKey;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export type ApiMutationOptions = NonNullable<DefaultOptions<unknown>['mutations']>;
|
|
111
|
+
|
|
112
|
+
export interface ApiOptions {
|
|
113
|
+
queryClient?: QueryClient;
|
|
114
|
+
queryOptions?: ApiQueryOptions;
|
|
115
|
+
mutationOptions?: ApiMutationOptions;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
type MaybeStateArgs<TState> = undefined extends TState ? [state?: TState] : [state: TState];
|
|
119
|
+
|
|
120
|
+
type StateAndOptionsArgs<TState, TOptions> = undefined extends TState
|
|
121
|
+
? [state?: TState, options?: TOptions]
|
|
122
|
+
: [state: TState, options?: TOptions];
|
|
123
|
+
|
|
124
|
+
type QueryCallSiteOptions<TQueryFnData, TError, TData> = Omit<
|
|
125
|
+
UseQueryOptions<TQueryFnData, TError, TData, QueryKey>,
|
|
126
|
+
'queryKey' | 'queryFn'
|
|
127
|
+
> & {
|
|
128
|
+
queryKey?: QueryKey;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
type SuspenseQueryCallSiteOptions<TQueryFnData, TError, TData> = Omit<
|
|
132
|
+
UseSuspenseQueryOptions<TQueryFnData, TError, TData, QueryKey>,
|
|
133
|
+
'queryKey' | 'queryFn'
|
|
134
|
+
> & {
|
|
135
|
+
queryKey?: QueryKey;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
type InfiniteQueryCallSiteOptions<TQueryFnData, TError, TData, TPageParam> = Partial<
|
|
139
|
+
Omit<UseInfiniteQueryOptions<TQueryFnData, TError, TData, QueryKey, TPageParam>, 'queryKey' | 'queryFn'>
|
|
140
|
+
> & {
|
|
141
|
+
queryKey?: QueryKey;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// --- API Instance Type ---
|
|
145
|
+
export type ApiInstance<_TBaseKey extends string, TEndpoints extends Record<string, AnyEndpointDefinition>> = {
|
|
146
|
+
getKey: () => QueryKey;
|
|
147
|
+
} & {
|
|
148
|
+
[K in keyof TEndpoints]: K extends string
|
|
149
|
+
? TEndpoints[K] extends QueryEndpointDefinition<infer TQueryFnData, infer TState, infer TData, infer TError>
|
|
150
|
+
? {
|
|
151
|
+
getKey: (...args: MaybeStateArgs<TState>) => QueryKey;
|
|
152
|
+
getOptions: (...args: MaybeStateArgs<TState>) => UseQueryOptions<TQueryFnData, TError, TData, QueryKey>;
|
|
153
|
+
useQuery: <TQueryData = TData>(
|
|
154
|
+
...args: StateAndOptionsArgs<TState, QueryCallSiteOptions<TQueryFnData, TError, TQueryData>>
|
|
155
|
+
) => UseQueryResult<TQueryData, TError>;
|
|
156
|
+
useSuspenseQuery: <TSuspenseData = TData>(
|
|
157
|
+
...args: StateAndOptionsArgs<
|
|
158
|
+
TState,
|
|
159
|
+
SuspenseQueryCallSiteOptions<TQueryFnData, TError, TSuspenseData>
|
|
160
|
+
>
|
|
161
|
+
) => UseSuspenseQueryResult<TSuspenseData, TError>;
|
|
162
|
+
}
|
|
163
|
+
: TEndpoints[K] extends InfiniteQueryEndpointDefinition<infer TQueryFnData, infer TState, infer TPageParam, infer TData, infer TError>
|
|
164
|
+
? {
|
|
165
|
+
getKey: (...args: MaybeStateArgs<TState>) => QueryKey;
|
|
166
|
+
getOptions: (...args: MaybeStateArgs<TState>) => UseInfiniteQueryOptions<TQueryFnData, TError, TData, QueryKey, TPageParam>;
|
|
167
|
+
useInfiniteQuery: <TQueryData = TData>(
|
|
168
|
+
...args: StateAndOptionsArgs<
|
|
169
|
+
TState,
|
|
170
|
+
InfiniteQueryCallSiteOptions<TQueryFnData, TError, TQueryData, TPageParam>
|
|
171
|
+
>
|
|
172
|
+
) => UseInfiniteQueryResult<TQueryData, TError>;
|
|
173
|
+
}
|
|
174
|
+
: TEndpoints[K] extends MutationEndpointDefinition<infer TData, infer TState, infer TError, infer _TApi, infer TOnMutateResult>
|
|
175
|
+
? {
|
|
176
|
+
useMutation: (
|
|
177
|
+
options?: Omit<UseMutationOptions<TData, TError, TState, TOnMutateResult>, 'mutationFn'>
|
|
178
|
+
) => UseMutationResult<TData, TError, TState, TOnMutateResult>;
|
|
179
|
+
getKey: () => MutationKey;
|
|
180
|
+
}
|
|
181
|
+
: never
|
|
182
|
+
: never;
|
|
183
|
+
};
|