@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 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
@@ -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"]}
@@ -0,0 +1,3 @@
1
+ export { default } from './api.js';
2
+ export * from './types.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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,3 @@
1
+ export { default } from './api.js';
2
+ export * from './types.js';
3
+ //# sourceMappingURL=index.js.map
@@ -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"}
@@ -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"]}
@@ -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":"AAAA,OAAO,EACH,cAAc,EACd,YAAY,EACZ,sBAAsB,EACtB,gBAAgB,EAChB,uBAAuB,EACvB,WAAW,EACX,WAAW,EACX,oBAAoB,EACpB,QAAQ,EACR,uBAAuB,EACvB,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,uBAAuB,EACvB,sBAAsB,EACzB,MAAM,uBAAuB,CAAC;AAG/B,MAAM,MAAM,oBAAoB,CAC5B,YAAY,GAAG,OAAO,EACtB,MAAM,GAAG,IAAI,EACb,KAAK,GAAG,YAAY,EACpB,MAAM,GAAG,OAAO,IAChB;IACA,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,CAAC,QAAQ,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IAC3F,OAAO,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC,CAAC;CACxF,CAAC;AAEF,MAAM,MAAM,4BAA4B,CACpC,YAAY,GAAG,OAAO,EACtB,MAAM,GAAG,IAAI,EACb,UAAU,GAAG,OAAO,EACpB,MAAM,GAAG,OAAO,EAChB,KAAK,GAAG,YAAY,CAAC,YAAY,EAAE,UAAU,CAAC,IAC9C;IACA,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,CAAC,QAAQ,EAAE,UAAU,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IACvG,OAAO,EAAE,IAAI,CAAC,uBAAuB,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC,CAAC;CACrH,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,QAAQ,GAAG,sBAAsB,CAAC;AAEtE,MAAM,MAAM,eAAe,GAAG;IAC1B,MAAM,EAAE,MAAM,QAAQ,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,yBAAyB,CACjC,IAAI,GAAG,eAAe,EACtB,KAAK,GAAG,OAAO,EACf,MAAM,GAAG,OAAO,EAChB,eAAe,GAAG,OAAO,IACzB,CACA,GAAG,EAAE,IAAI,EACT,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,KAAK,EACX,cAAc,EAAE,eAAe,EAC/B,OAAO,EAAE,uBAAuB,KAC/B,SAAS,qBAAqB,EAAE,CAAC;AAEtC,MAAM,MAAM,uBAAuB,CAC/B,KAAK,GAAG,OAAO,EACf,MAAM,GAAG,OAAO,EAChB,MAAM,GAAG,OAAO,EAChB,IAAI,GAAG,eAAe,EACtB,eAAe,GAAG,OAAO,IACzB;IACA,UAAU,EAAE,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,IAAI,CAAC,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,YAAY,CAAC,CAAC;IACzF,iBAAiB,CAAC,EAAE,yBAAyB,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,CAAC,CAAC;CACvF,CAAC;AAEF,MAAM,MAAM,uBAAuB,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,IAAI,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG;IACnI,IAAI,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,+BAA+B,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,IAAI,4BAA4B,CACvH,YAAY,EACZ,MAAM,EACN,UAAU,EACV,MAAM,EACN,KAAK,CACR,GAAG;IACA,IAAI,EAAE,eAAe,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAClC,KAAK,GAAG,OAAO,EACf,MAAM,GAAG,OAAO,EAChB,MAAM,GAAG,OAAO,EAChB,IAAI,GAAG,eAAe,EACtB,eAAe,GAAG,OAAO,IACzB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,CAAC,GAAG;IACxE,IAAI,EAAE,UAAU,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAE3B,uBAAuB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAE3C,+BAA+B,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAExD,0BAA0B,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAE1D,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,GAAG;IAC5E,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,WAAW,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;AAEnF,MAAM,WAAW,UAAU;IACvB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,YAAY,CAAC,EAAE,eAAe,CAAC;IAC/B,eAAe,CAAC,EAAE,kBAAkB,CAAC;CACxC;AAED,KAAK,cAAc,CAAC,MAAM,IAAI,SAAS,SAAS,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;AAE5F,KAAK,mBAAmB,CAAC,MAAM,EAAE,QAAQ,IAAI,SAAS,SAAS,MAAM,GAC/D,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,GACpC,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;AAE1C,KAAK,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,IAAI,IAAI,CACzD,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,EACtD,UAAU,GAAG,SAAS,CACzB,GAAG;IACA,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACvB,CAAC;AAEF,KAAK,4BAA4B,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,IAAI,IAAI,CACjE,uBAAuB,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,EAC9D,UAAU,GAAG,SAAS,CACzB,GAAG;IACA,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACvB,CAAC;AAEF,KAAK,4BAA4B,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,IAAI,OAAO,CAChF,IAAI,CAAC,uBAAuB,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC,CAC3G,GAAG;IACA,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACvB,CAAC;AAGF,MAAM,MAAM,WAAW,CAAC,SAAS,SAAS,MAAM,EAAE,UAAU,SAAS,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,IAAI;IAC1G,MAAM,EAAE,MAAM,QAAQ,CAAC;CAC1B,GAAG;KACC,CAAC,IAAI,MAAM,UAAU,GAAG,CAAC,SAAS,MAAM,GACnC,UAAU,CAAC,CAAC,CAAC,SAAS,uBAAuB,CAAC,MAAM,YAAY,EAAE,MAAM,MAAM,EAAE,MAAM,KAAK,EAAE,MAAM,MAAM,CAAC,GACtG;QACI,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,cAAc,CAAC,MAAM,CAAC,KAAK,QAAQ,CAAC;QACtD,UAAU,EAAE,CAAC,GAAG,IAAI,EAAE,cAAc,CAAC,MAAM,CAAC,KAAK,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;QACxG,QAAQ,EAAE,CAAC,UAAU,GAAG,KAAK,EACzB,GAAG,IAAI,EAAE,mBAAmB,CAAC,MAAM,EAAE,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,KAC3F,cAAc,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QACxC,gBAAgB,EAAE,CAAC,aAAa,GAAG,KAAK,EACpC,GAAG,IAAI,EAAE,mBAAmB,CACxB,MAAM,EACN,4BAA4B,CAAC,YAAY,EAAE,MAAM,EAAE,aAAa,CAAC,CACpE,KACA,sBAAsB,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;KACtD,GACD,UAAU,CAAC,CAAC,CAAC,SAAS,+BAA+B,CAAC,MAAM,YAAY,EAAE,MAAM,MAAM,EAAE,MAAM,UAAU,EAAE,MAAM,KAAK,EAAE,MAAM,MAAM,CAAC,GAClI;QACI,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,cAAc,CAAC,MAAM,CAAC,KAAK,QAAQ,CAAC;QACtD,UAAU,EAAE,CAAC,GAAG,IAAI,EAAE,cAAc,CAAC,MAAM,CAAC,KAAK,uBAAuB,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;QAC5H,gBAAgB,EAAE,CAAC,UAAU,GAAG,KAAK,EACjC,GAAG,IAAI,EAAE,mBAAmB,CACxB,MAAM,EACN,4BAA4B,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,CAAC,CAC7E,KACA,sBAAsB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;KACnD,GACD,UAAU,CAAC,CAAC,CAAC,SAAS,0BAA0B,CAAC,MAAM,KAAK,EAAE,MAAM,MAAM,EAAE,MAAM,MAAM,EAAE,MAAM,KAAK,EAAE,MAAM,eAAe,CAAC,GAC3H;QACI,WAAW,EAAE,CACT,OAAO,CAAC,EAAE,IAAI,CAAC,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,YAAY,CAAC,KACvF,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,CAAC,CAAC;QAC/D,MAAM,EAAE,MAAM,WAAW,CAAC;KAC7B,GACD,KAAK,GACb,KAAK;CACd,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -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
@@ -0,0 +1,2 @@
1
+ export { default } from './api.js';
2
+ export * from './types.js';
@@ -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
+ };