@robohall/react-query-factory 2.1.5 → 2.2.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/README.md +71 -4
- package/dist/index.d.mts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +13 -17
- package/dist/index.mjs +13 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -218,6 +218,23 @@ queryClient.invalidateQueries(describeInstances({ MaxResults: 20 }));
|
|
|
218
218
|
|
|
219
219
|
---
|
|
220
220
|
|
|
221
|
+
## Which pattern?
|
|
222
|
+
|
|
223
|
+
| Pattern | Use when |
|
|
224
|
+
| ------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
225
|
+
| Basic | API returns a single, non-paginated response |
|
|
226
|
+
| Async iterator | `queryFn` returns an `AsyncIterable` (e.g. an AWS SDK v3 paginator) — no cursor wiring required |
|
|
227
|
+
| Crawl-then-render | Paginated API; UI needs all data before it's useful (dropdowns, counts, totals) |
|
|
228
|
+
| Render-while-crawling | Paginated API; UI can show partial results as pages arrive |
|
|
229
|
+
| On-demand (`.infinite()`) | Paginated API; user clicks "load more" or navigates pages |
|
|
230
|
+
| Client-side search | Paginated API; find a subset without server-side filtering — stop crawling when condition is met |
|
|
231
|
+
|
|
232
|
+
**Async iterator** is a `queryFn` style, not a display pattern — combine it with any crawl pattern above when your SDK provides a paginator function.
|
|
233
|
+
|
|
234
|
+
**Composition** and **Invalidation** apply alongside any pattern: use composition when multiple views share one cache entry, invalidation after a mutation changes server state.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
221
238
|
## Installation
|
|
222
239
|
|
|
223
240
|
```bash
|
|
@@ -247,11 +264,25 @@ const { data: partial } = useQuery(
|
|
|
247
264
|
);
|
|
248
265
|
```
|
|
249
266
|
|
|
267
|
+
### Error behavior
|
|
268
|
+
|
|
269
|
+
If any page fetch throws, the error propagates immediately — there is no per-page retry or partial-result fallback. TanStack Query receives the error exactly as it would from a single-page `queryFn` and applies its normal `retry`, `throwOnError`, and error-state semantics.
|
|
270
|
+
|
|
271
|
+
When TanStack retries, the crawl starts over from `initialPageParam`. There is no resume-from-page-N.
|
|
272
|
+
|
|
273
|
+
The crawl also respects the abort signal between pages. When the signal fires (component unmounts, query superseded by a newer one), the loop exits after the current in-flight page completes. TanStack does not commit the partial result.
|
|
274
|
+
|
|
250
275
|
---
|
|
251
276
|
|
|
252
277
|
## Async iterator queryFns
|
|
253
278
|
|
|
254
|
-
When `queryFn` returns an `AsyncIterable`, the library
|
|
279
|
+
When `queryFn` returns an `AsyncIterable`, the library walks it with `for await...of` instead of calling `queryFn` repeatedly with successive `pageParam` values. The cursor lives inside the iterator rather than in `getNextPageParam` — that's the only meaningful difference from a cursor-based factory. `shouldFetchNextPage`, `reduce`, `crawlOptions`, and `.infinite()` all work identically.
|
|
280
|
+
|
|
281
|
+
One caveat for `.infinite()`: `getNextPageParam` is still required, but its role shifts — instead of wiring each individual API page, it records where the next batch should start when the user loads more.
|
|
282
|
+
|
|
283
|
+
Without `shouldFetchNextPage`, the library exhausts the iterator on every call — every page, every time.
|
|
284
|
+
|
|
285
|
+
Any source of `AsyncIterable<TPage>` works:
|
|
255
286
|
|
|
256
287
|
```typescript
|
|
257
288
|
import { paginateDescribeInstances } from '@aws-sdk/client-ec2';
|
|
@@ -273,9 +304,7 @@ const describeInstances = queryFactory({
|
|
|
273
304
|
});
|
|
274
305
|
```
|
|
275
306
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
For `.infinite()` mode, `getNextPageParam` is required to capture the next virtual page's starting cursor from the last yielded item. AWS SDK v3 paginators accept a `startingToken` to resume from a specific position — wire `ctx.pageParam` to it:
|
|
307
|
+
For `.infinite()`, wire `ctx.pageParam` to the iterator's resume parameter so each batch starts from the right position:
|
|
279
308
|
|
|
280
309
|
```typescript
|
|
281
310
|
const describeInstances = queryFactory({
|
|
@@ -388,6 +417,24 @@ The `.infinite()` key includes an `'infinite'` segment to keep it separate from
|
|
|
388
417
|
|
|
389
418
|
---
|
|
390
419
|
|
|
420
|
+
## Performance
|
|
421
|
+
|
|
422
|
+
TanStack Query's default `staleTime` is `0` — data is considered stale immediately, so a background refetch fires on every mount, window focus, and reconnect. For a single-page query that's one API call; for a crawling factory it's the full crawl repeated. Set `staleTime` in the factory config to match how often the underlying data actually changes:
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
const describeInstances = queryFactory({
|
|
426
|
+
queryKey: ['ec2:DescribeInstances'],
|
|
427
|
+
staleTime: 60_000, // re-crawl at most once per minute
|
|
428
|
+
// ...
|
|
429
|
+
});
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Child factories inherit `staleTime` and all other standard options from the parent, so setting it once on the root factory covers every derived view.
|
|
433
|
+
|
|
434
|
+
When freshness requirements allow it, `refetchOnWindowFocus` and `refetchOnMount` can be set to `false` on the factory for the same reason — each is a potential full re-crawl.
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
391
438
|
## Public API
|
|
392
439
|
|
|
393
440
|
### `queryFactory(config)`
|
|
@@ -440,6 +487,26 @@ Return type of `factory(params)`. Pass directly to `useQuery()`. Contains an `in
|
|
|
440
487
|
|
|
441
488
|
Return type of `factory.infinite(params)`. Pass directly to `useInfiniteQuery()`. The `select` field is typed to `InfiniteData<TData, TPageParam>`, which prevents accidental use with `useQuery`.
|
|
442
489
|
|
|
490
|
+
### `FactoryParams<F>`
|
|
491
|
+
|
|
492
|
+
Extracts the params type from a factory — the first argument of a factory call. Useful for typing component props that accept factory params.
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
import type { FactoryParams } from '@robohall/react-query-factory';
|
|
496
|
+
|
|
497
|
+
type Params = FactoryParams<typeof describeInstances>; // → DescribeInstancesRequest
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### `FactoryCrawlOptions<F>`
|
|
501
|
+
|
|
502
|
+
Extracts the crawl options type from a factory — the second argument of a factory call. Useful for typing helpers or components that accept crawl options.
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
import type { FactoryCrawlOptions } from '@robohall/react-query-factory';
|
|
506
|
+
|
|
507
|
+
type CrawlOpts = FactoryCrawlOptions<typeof describeInstances>; // → { minResults?: number }
|
|
508
|
+
```
|
|
509
|
+
|
|
443
510
|
---
|
|
444
511
|
|
|
445
512
|
## Running the sandbox
|
package/dist/index.d.mts
CHANGED
|
@@ -100,6 +100,10 @@ interface QueryFactory<TParams = void, TData = unknown, TError = Error, TSelecte
|
|
|
100
100
|
(params?: TParams, crawlOptions?: TCrawlOptions): ResolvedQueryOptions<TData, TError, TSelected>;
|
|
101
101
|
infinite(params?: TParams, crawlOptions?: TCrawlOptions): ResolvedInfiniteOptions<TData, TError, TPageParam, InfiniteData<TSelected, TPageParam>>;
|
|
102
102
|
}
|
|
103
|
+
/** Extracts the params type from a factory — the first argument of a factory call. */
|
|
104
|
+
type FactoryParams<F> = F extends QueryFactory<infer TParams, any, any, any, any, any, any> ? TParams : never;
|
|
105
|
+
/** Extracts the crawl options type from a factory — the second argument of a factory call. */
|
|
106
|
+
type FactoryCrawlOptions<F> = F extends QueryFactory<any, any, any, any, any, infer TCrawlOptions, any> ? TCrawlOptions : never;
|
|
103
107
|
/**
|
|
104
108
|
* Creates a standalone query factory with pagination and reduce. When `reduce` is
|
|
105
109
|
* present, `shouldFetchNextPage` receives `TSelected` (never undefined) because
|
|
@@ -208,4 +212,4 @@ declare function queryFactory<TParams = void, TData = unknown, TError = Error, T
|
|
|
208
212
|
shouldFetchNextPage: (combined: TSelected | undefined, crawlOptions: TCrawlOptions) => boolean;
|
|
209
213
|
}): QueryFactory<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions, false>;
|
|
210
214
|
|
|
211
|
-
export { type QueryFactory, type QueryFactoryConfig, type ResolvedInfiniteOptions, type ResolvedQueryOptions, type StandardQueryOptions, queryFactory };
|
|
215
|
+
export { type FactoryCrawlOptions, type FactoryParams, type QueryFactory, type QueryFactoryConfig, type ResolvedInfiniteOptions, type ResolvedQueryOptions, type StandardQueryOptions, queryFactory };
|
package/dist/index.d.ts
CHANGED
|
@@ -100,6 +100,10 @@ interface QueryFactory<TParams = void, TData = unknown, TError = Error, TSelecte
|
|
|
100
100
|
(params?: TParams, crawlOptions?: TCrawlOptions): ResolvedQueryOptions<TData, TError, TSelected>;
|
|
101
101
|
infinite(params?: TParams, crawlOptions?: TCrawlOptions): ResolvedInfiniteOptions<TData, TError, TPageParam, InfiniteData<TSelected, TPageParam>>;
|
|
102
102
|
}
|
|
103
|
+
/** Extracts the params type from a factory — the first argument of a factory call. */
|
|
104
|
+
type FactoryParams<F> = F extends QueryFactory<infer TParams, any, any, any, any, any, any> ? TParams : never;
|
|
105
|
+
/** Extracts the crawl options type from a factory — the second argument of a factory call. */
|
|
106
|
+
type FactoryCrawlOptions<F> = F extends QueryFactory<any, any, any, any, any, infer TCrawlOptions, any> ? TCrawlOptions : never;
|
|
103
107
|
/**
|
|
104
108
|
* Creates a standalone query factory with pagination and reduce. When `reduce` is
|
|
105
109
|
* present, `shouldFetchNextPage` receives `TSelected` (never undefined) because
|
|
@@ -208,4 +212,4 @@ declare function queryFactory<TParams = void, TData = unknown, TError = Error, T
|
|
|
208
212
|
shouldFetchNextPage: (combined: TSelected | undefined, crawlOptions: TCrawlOptions) => boolean;
|
|
209
213
|
}): QueryFactory<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions, false>;
|
|
210
214
|
|
|
211
|
-
export { type QueryFactory, type QueryFactoryConfig, type ResolvedInfiniteOptions, type ResolvedQueryOptions, type StandardQueryOptions, queryFactory };
|
|
215
|
+
export { type FactoryCrawlOptions, type FactoryParams, type QueryFactory, type QueryFactoryConfig, type ResolvedInfiniteOptions, type ResolvedQueryOptions, type StandardQueryOptions, queryFactory };
|
package/dist/index.js
CHANGED
|
@@ -64,7 +64,7 @@ function wrapGetNextPageParam(getNextPageParam, shouldFetchNextPage, crawlOption
|
|
|
64
64
|
}
|
|
65
65
|
function buildCrawlingQueryFn(queryFn, getNextPageParam, initialPageParam, shouldFetchNextPage, reduce) {
|
|
66
66
|
return async (params, crawlOptions, context) => {
|
|
67
|
-
var _a, _b, _c
|
|
67
|
+
var _a, _b, _c;
|
|
68
68
|
if ((_a = context.signal) == null ? void 0 : _a.aborted) {
|
|
69
69
|
if (reduce) throw new DOMException("Aborted", "AbortError");
|
|
70
70
|
return [];
|
|
@@ -74,10 +74,10 @@ function buildCrawlingQueryFn(queryFn, getNextPageParam, initialPageParam, shoul
|
|
|
74
74
|
if (isAsyncIterable(initialResult)) {
|
|
75
75
|
const pages2 = [];
|
|
76
76
|
let acc2 = void 0;
|
|
77
|
-
for await (const
|
|
77
|
+
for await (const page2 of initialResult) {
|
|
78
78
|
if ((_b = context.signal) == null ? void 0 : _b.aborted) break;
|
|
79
|
-
pages2.push(
|
|
80
|
-
if (reduce) acc2 = reduce(acc2,
|
|
79
|
+
pages2.push(page2);
|
|
80
|
+
if (reduce) acc2 = reduce(acc2, page2);
|
|
81
81
|
if (!shouldFetchNextPage(acc2, crawlOptions)) break;
|
|
82
82
|
}
|
|
83
83
|
if (reduce) {
|
|
@@ -90,9 +90,8 @@ function buildCrawlingQueryFn(queryFn, getNextPageParam, initialPageParam, shoul
|
|
|
90
90
|
const pageParams = [];
|
|
91
91
|
let currentParam = initialPageParam;
|
|
92
92
|
let acc = void 0;
|
|
93
|
-
let
|
|
93
|
+
let page = await initialResult;
|
|
94
94
|
while (true) {
|
|
95
|
-
const page = await nextResult;
|
|
96
95
|
pages.push(page);
|
|
97
96
|
pageParams.push(currentParam);
|
|
98
97
|
if (reduce) acc = reduce(acc, page);
|
|
@@ -102,9 +101,8 @@ function buildCrawlingQueryFn(queryFn, getNextPageParam, initialPageParam, shoul
|
|
|
102
101
|
const nextParam = getNextPageParam(page, pages, currentParam, pageParams);
|
|
103
102
|
if (nextParam == null) break;
|
|
104
103
|
currentParam = nextParam;
|
|
105
|
-
if ((_d = context.signal) == null ? void 0 : _d.aborted) break;
|
|
106
104
|
ctx.pageParam = currentParam;
|
|
107
|
-
|
|
105
|
+
page = await queryFn(params, ctx);
|
|
108
106
|
}
|
|
109
107
|
if (reduce) {
|
|
110
108
|
if (acc === void 0) throw new DOMException("Aborted", "AbortError");
|
|
@@ -115,7 +113,7 @@ function buildCrawlingQueryFn(queryFn, getNextPageParam, initialPageParam, shoul
|
|
|
115
113
|
}
|
|
116
114
|
function buildInfiniteCrawlingQueryFn(queryFn, getNextPageParam, shouldFetchNextPage, reduce) {
|
|
117
115
|
return async (params, crawlOptions, context) => {
|
|
118
|
-
var _a, _b, _c
|
|
116
|
+
var _a, _b, _c;
|
|
119
117
|
if ((_a = context.signal) == null ? void 0 : _a.aborted)
|
|
120
118
|
throw new DOMException("Aborted", "AbortError");
|
|
121
119
|
const ctx = { ...context, pageParam: context.pageParam };
|
|
@@ -125,11 +123,11 @@ function buildInfiniteCrawlingQueryFn(queryFn, getNextPageParam, shouldFetchNext
|
|
|
125
123
|
const startParam = context.pageParam;
|
|
126
124
|
let acc2 = void 0;
|
|
127
125
|
let nextBatchParam2 = null;
|
|
128
|
-
for await (const
|
|
126
|
+
for await (const page2 of initialResult) {
|
|
129
127
|
if ((_b = context.signal) == null ? void 0 : _b.aborted) break;
|
|
130
|
-
pages2.push(
|
|
131
|
-
acc2 = reduce(acc2,
|
|
132
|
-
const nextParam = getNextPageParam(
|
|
128
|
+
pages2.push(page2);
|
|
129
|
+
acc2 = reduce(acc2, page2);
|
|
130
|
+
const nextParam = getNextPageParam(page2, pages2, startParam, [
|
|
133
131
|
startParam
|
|
134
132
|
]);
|
|
135
133
|
nextBatchParam2 = nextParam != null ? nextParam : null;
|
|
@@ -144,9 +142,8 @@ function buildInfiniteCrawlingQueryFn(queryFn, getNextPageParam, shouldFetchNext
|
|
|
144
142
|
let currentParam = context.pageParam;
|
|
145
143
|
let acc = void 0;
|
|
146
144
|
let nextBatchParam = null;
|
|
147
|
-
let
|
|
145
|
+
let page = await initialResult;
|
|
148
146
|
while (true) {
|
|
149
|
-
const page = await nextResult;
|
|
150
147
|
pages.push(page);
|
|
151
148
|
pageParams.push(currentParam);
|
|
152
149
|
acc = reduce(acc, page);
|
|
@@ -156,9 +153,8 @@ function buildInfiniteCrawlingQueryFn(queryFn, getNextPageParam, shouldFetchNext
|
|
|
156
153
|
if (nextParam == null) break;
|
|
157
154
|
if (!shouldFetchNextPage(acc, crawlOptions)) break;
|
|
158
155
|
currentParam = nextParam;
|
|
159
|
-
if ((_d = context.signal) == null ? void 0 : _d.aborted) break;
|
|
160
156
|
ctx.pageParam = currentParam;
|
|
161
|
-
|
|
157
|
+
page = await queryFn(params, ctx);
|
|
162
158
|
}
|
|
163
159
|
if (acc === void 0) throw new DOMException("Aborted", "AbortError");
|
|
164
160
|
return { data: acc, nextPageParam: nextBatchParam };
|
package/dist/index.mjs
CHANGED
|
@@ -38,7 +38,7 @@ function wrapGetNextPageParam(getNextPageParam, shouldFetchNextPage, crawlOption
|
|
|
38
38
|
}
|
|
39
39
|
function buildCrawlingQueryFn(queryFn, getNextPageParam, initialPageParam, shouldFetchNextPage, reduce) {
|
|
40
40
|
return async (params, crawlOptions, context) => {
|
|
41
|
-
var _a, _b, _c
|
|
41
|
+
var _a, _b, _c;
|
|
42
42
|
if ((_a = context.signal) == null ? void 0 : _a.aborted) {
|
|
43
43
|
if (reduce) throw new DOMException("Aborted", "AbortError");
|
|
44
44
|
return [];
|
|
@@ -48,10 +48,10 @@ function buildCrawlingQueryFn(queryFn, getNextPageParam, initialPageParam, shoul
|
|
|
48
48
|
if (isAsyncIterable(initialResult)) {
|
|
49
49
|
const pages2 = [];
|
|
50
50
|
let acc2 = void 0;
|
|
51
|
-
for await (const
|
|
51
|
+
for await (const page2 of initialResult) {
|
|
52
52
|
if ((_b = context.signal) == null ? void 0 : _b.aborted) break;
|
|
53
|
-
pages2.push(
|
|
54
|
-
if (reduce) acc2 = reduce(acc2,
|
|
53
|
+
pages2.push(page2);
|
|
54
|
+
if (reduce) acc2 = reduce(acc2, page2);
|
|
55
55
|
if (!shouldFetchNextPage(acc2, crawlOptions)) break;
|
|
56
56
|
}
|
|
57
57
|
if (reduce) {
|
|
@@ -64,9 +64,8 @@ function buildCrawlingQueryFn(queryFn, getNextPageParam, initialPageParam, shoul
|
|
|
64
64
|
const pageParams = [];
|
|
65
65
|
let currentParam = initialPageParam;
|
|
66
66
|
let acc = void 0;
|
|
67
|
-
let
|
|
67
|
+
let page = await initialResult;
|
|
68
68
|
while (true) {
|
|
69
|
-
const page = await nextResult;
|
|
70
69
|
pages.push(page);
|
|
71
70
|
pageParams.push(currentParam);
|
|
72
71
|
if (reduce) acc = reduce(acc, page);
|
|
@@ -76,9 +75,8 @@ function buildCrawlingQueryFn(queryFn, getNextPageParam, initialPageParam, shoul
|
|
|
76
75
|
const nextParam = getNextPageParam(page, pages, currentParam, pageParams);
|
|
77
76
|
if (nextParam == null) break;
|
|
78
77
|
currentParam = nextParam;
|
|
79
|
-
if ((_d = context.signal) == null ? void 0 : _d.aborted) break;
|
|
80
78
|
ctx.pageParam = currentParam;
|
|
81
|
-
|
|
79
|
+
page = await queryFn(params, ctx);
|
|
82
80
|
}
|
|
83
81
|
if (reduce) {
|
|
84
82
|
if (acc === void 0) throw new DOMException("Aborted", "AbortError");
|
|
@@ -89,7 +87,7 @@ function buildCrawlingQueryFn(queryFn, getNextPageParam, initialPageParam, shoul
|
|
|
89
87
|
}
|
|
90
88
|
function buildInfiniteCrawlingQueryFn(queryFn, getNextPageParam, shouldFetchNextPage, reduce) {
|
|
91
89
|
return async (params, crawlOptions, context) => {
|
|
92
|
-
var _a, _b, _c
|
|
90
|
+
var _a, _b, _c;
|
|
93
91
|
if ((_a = context.signal) == null ? void 0 : _a.aborted)
|
|
94
92
|
throw new DOMException("Aborted", "AbortError");
|
|
95
93
|
const ctx = { ...context, pageParam: context.pageParam };
|
|
@@ -99,11 +97,11 @@ function buildInfiniteCrawlingQueryFn(queryFn, getNextPageParam, shouldFetchNext
|
|
|
99
97
|
const startParam = context.pageParam;
|
|
100
98
|
let acc2 = void 0;
|
|
101
99
|
let nextBatchParam2 = null;
|
|
102
|
-
for await (const
|
|
100
|
+
for await (const page2 of initialResult) {
|
|
103
101
|
if ((_b = context.signal) == null ? void 0 : _b.aborted) break;
|
|
104
|
-
pages2.push(
|
|
105
|
-
acc2 = reduce(acc2,
|
|
106
|
-
const nextParam = getNextPageParam(
|
|
102
|
+
pages2.push(page2);
|
|
103
|
+
acc2 = reduce(acc2, page2);
|
|
104
|
+
const nextParam = getNextPageParam(page2, pages2, startParam, [
|
|
107
105
|
startParam
|
|
108
106
|
]);
|
|
109
107
|
nextBatchParam2 = nextParam != null ? nextParam : null;
|
|
@@ -118,9 +116,8 @@ function buildInfiniteCrawlingQueryFn(queryFn, getNextPageParam, shouldFetchNext
|
|
|
118
116
|
let currentParam = context.pageParam;
|
|
119
117
|
let acc = void 0;
|
|
120
118
|
let nextBatchParam = null;
|
|
121
|
-
let
|
|
119
|
+
let page = await initialResult;
|
|
122
120
|
while (true) {
|
|
123
|
-
const page = await nextResult;
|
|
124
121
|
pages.push(page);
|
|
125
122
|
pageParams.push(currentParam);
|
|
126
123
|
acc = reduce(acc, page);
|
|
@@ -130,9 +127,8 @@ function buildInfiniteCrawlingQueryFn(queryFn, getNextPageParam, shouldFetchNext
|
|
|
130
127
|
if (nextParam == null) break;
|
|
131
128
|
if (!shouldFetchNextPage(acc, crawlOptions)) break;
|
|
132
129
|
currentParam = nextParam;
|
|
133
|
-
if ((_d = context.signal) == null ? void 0 : _d.aborted) break;
|
|
134
130
|
ctx.pageParam = currentParam;
|
|
135
|
-
|
|
131
|
+
page = await queryFn(params, ctx);
|
|
136
132
|
}
|
|
137
133
|
if (acc === void 0) throw new DOMException("Aborted", "AbortError");
|
|
138
134
|
return { data: acc, nextPageParam: nextBatchParam };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@robohall/react-query-factory",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "A factory abstraction for TanStack Query (React Query) with composable keys, crawling support, and automatic infinite query generation",
|
|
5
5
|
"author": "Robert Hall",
|
|
6
6
|
"license": "MIT",
|