@robohall/react-query-factory 2.1.0 → 2.1.2
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 +34 -8
- package/dist/index.js +6 -3
- package/dist/index.mjs +6 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<a href="https://roberth26.github.io/react-query-factory/"><strong>Visit the Sandbox</strong></a>
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
|
-
TanStack Query handles caching, syncing, and invalidation. What it doesn't do is crawl paginated APIs for you. This library adds that — a factory function that wraps your `queryFn` with a configurable crawl loop so `useQuery` can return accumulated results instead of a single page. The same factory produces `useInfiniteQuery` options, composes into child factories that share the cache, and exposes scope-aware invalidation keys. TanStack's API stays fully exposed at every call site.
|
|
12
|
+
TanStack Query handles caching, syncing, and invalidation. What it doesn't do is crawl paginated APIs for you. This library adds that — a factory function that wraps your `queryFn` with a configurable crawl loop so `useQuery` can return accumulated results instead of a single page. The `queryFn` can be a plain async function or an async iterable (e.g. an AWS SDK paginator), with no cursor wiring required in the latter case. The same factory produces `useInfiniteQuery` options, composes into child factories that share the cache, and exposes scope-aware invalidation keys. TanStack's API stays fully exposed at every call site.
|
|
13
13
|
|
|
14
14
|
Zero runtime dependencies.
|
|
15
15
|
|
|
@@ -30,7 +30,30 @@ function useInstances(params: DescribeInstancesCommandInput) {
|
|
|
30
30
|
}
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
Works,
|
|
33
|
+
Works, until requirements grow. You need a `select` option — so the hook grows a generic. You need a `useInfiniteQuery` variant — so you write a second hook with a key differentiator to avoid a cache collision. You need to prefetch in a route loader — but the key is trapped inside the hook.
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
function useInstances<TSelected = Instance[]>(
|
|
37
|
+
params: DescribeInstancesCommandInput,
|
|
38
|
+
options?: { select?: (data: Instance[]) => TSelected },
|
|
39
|
+
) {
|
|
40
|
+
return useQuery({
|
|
41
|
+
queryKey: ['instances', params],
|
|
42
|
+
queryFn: () => fetchInstances(params),
|
|
43
|
+
select: options?.select,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// separate hook, duplicated key and queryFn, must stay in sync manually
|
|
48
|
+
function useInstancesInfinite(params: DescribeInstancesCommandInput) {
|
|
49
|
+
return useInfiniteQuery({
|
|
50
|
+
queryKey: ['instances', 'infinite', params],
|
|
51
|
+
// ...
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The generics multiply with every new transform. The key is still trapped — prefetching and invalidation still can't reach it from outside.
|
|
34
57
|
|
|
35
58
|
### Step 2 — `queryOptions` for colocation
|
|
36
59
|
|
|
@@ -126,7 +149,7 @@ Now you have two separate factories that duplicate the key and queryFn and need
|
|
|
126
149
|
### What's missing
|
|
127
150
|
|
|
128
151
|
- Define the query **once**: key, queryFn, pagination config
|
|
129
|
-
- Let each **call site** decide how much to crawl (e.g. 50 records
|
|
152
|
+
- Let each **call site** decide how much to crawl (e.g. 50 records, all of them, or none)
|
|
130
153
|
- Optionally have `useQuery` crawl and return the **accumulated result** instead of a single page
|
|
131
154
|
- Use **async iterables** as `queryFn` — pass a paginator function directly, no cursor wiring required
|
|
132
155
|
- Have `.infinite()` available on the **same factory**, no duplication
|
|
@@ -144,7 +167,10 @@ const describeInstances = queryFactory({
|
|
|
144
167
|
queryKey: ['ec2:DescribeInstances'],
|
|
145
168
|
queryFn: (params: DescribeInstancesCommandInput, ctx) =>
|
|
146
169
|
ec2.send(
|
|
147
|
-
new DescribeInstancesCommand({
|
|
170
|
+
new DescribeInstancesCommand({
|
|
171
|
+
...params,
|
|
172
|
+
NextToken: ctx.pageParam ?? params.NextToken,
|
|
173
|
+
}),
|
|
148
174
|
{
|
|
149
175
|
abortSignal: ctx.signal,
|
|
150
176
|
},
|
|
@@ -156,7 +182,7 @@ const describeInstances = queryFactory({
|
|
|
156
182
|
...(page.Reservations?.flatMap(r => r.Instances ?? []) ?? []),
|
|
157
183
|
],
|
|
158
184
|
shouldFetchNextPage: (instances, opts: { minResults?: number }) =>
|
|
159
|
-
opts.minResults
|
|
185
|
+
opts.minResults != null && instances.length < opts.minResults,
|
|
160
186
|
});
|
|
161
187
|
|
|
162
188
|
// useQuery — crawls all pages, data is Instance[]
|
|
@@ -211,7 +237,7 @@ The `crawlOptions` argument passed at call time is forwarded to `shouldFetchNext
|
|
|
211
237
|
const describeInstances = queryFactory({
|
|
212
238
|
// ...
|
|
213
239
|
shouldFetchNextPage: (instances, opts: { minResults?: number }) =>
|
|
214
|
-
opts.minResults
|
|
240
|
+
opts.minResults != null && instances.length < opts.minResults,
|
|
215
241
|
});
|
|
216
242
|
|
|
217
243
|
// two separate cache entries — crawl independently
|
|
@@ -235,7 +261,7 @@ const describeInstances = queryFactory({
|
|
|
235
261
|
queryFn: (params: DescribeInstancesCommandInput) =>
|
|
236
262
|
paginateDescribeInstances({ client: ec2 }, params),
|
|
237
263
|
shouldFetchNextPage: (instances, opts: { minResults?: number }) =>
|
|
238
|
-
opts.minResults
|
|
264
|
+
opts.minResults != null && instances.length < opts.minResults,
|
|
239
265
|
reduce: (acc, page: DescribeInstancesResponse): Instance[] => [
|
|
240
266
|
...(acc ?? []),
|
|
241
267
|
...(page.Reservations?.flatMap(r => r.Instances ?? []) ?? []),
|
|
@@ -258,7 +284,7 @@ const describeInstances = queryFactory({
|
|
|
258
284
|
getNextPageParam: page => page.NextToken,
|
|
259
285
|
initialPageParam: undefined as string | undefined,
|
|
260
286
|
shouldFetchNextPage: (instances, opts: { minResults?: number }) =>
|
|
261
|
-
opts.minResults
|
|
287
|
+
opts.minResults != null && instances.length < opts.minResults,
|
|
262
288
|
reduce: (acc, page): Instance[] => [
|
|
263
289
|
...(acc ?? []),
|
|
264
290
|
...(page.Instances ?? []),
|
package/dist/index.js
CHANGED
|
@@ -122,13 +122,16 @@ function buildInfiniteCrawlingQueryFn(queryFn, getNextPageParam, shouldFetchNext
|
|
|
122
122
|
const initialResult = queryFn(params, ctx);
|
|
123
123
|
if (isAsyncIterable(initialResult)) {
|
|
124
124
|
const pages2 = [];
|
|
125
|
+
const startParam = context.pageParam;
|
|
125
126
|
let acc2 = void 0;
|
|
126
127
|
let nextBatchParam2 = null;
|
|
127
128
|
for await (const page of initialResult) {
|
|
128
129
|
if ((_b = context.signal) == null ? void 0 : _b.aborted) break;
|
|
129
130
|
pages2.push(page);
|
|
130
131
|
acc2 = reduce(acc2, page);
|
|
131
|
-
const nextParam = getNextPageParam(page, pages2,
|
|
132
|
+
const nextParam = getNextPageParam(page, pages2, startParam, [
|
|
133
|
+
startParam
|
|
134
|
+
]);
|
|
132
135
|
nextBatchParam2 = nextParam != null ? nextParam : null;
|
|
133
136
|
if (nextParam == null) break;
|
|
134
137
|
if (!shouldFetchNextPage(acc2, crawlOptions)) break;
|
|
@@ -147,9 +150,9 @@ function buildInfiniteCrawlingQueryFn(queryFn, getNextPageParam, shouldFetchNext
|
|
|
147
150
|
pages.push(page);
|
|
148
151
|
pageParams.push(currentParam);
|
|
149
152
|
acc = reduce(acc, page);
|
|
150
|
-
if ((_c = context.signal) == null ? void 0 : _c.aborted) break;
|
|
151
153
|
const nextParam = getNextPageParam(page, pages, currentParam, pageParams);
|
|
152
154
|
nextBatchParam = nextParam != null ? nextParam : null;
|
|
155
|
+
if ((_c = context.signal) == null ? void 0 : _c.aborted) break;
|
|
153
156
|
if (nextParam == null) break;
|
|
154
157
|
if (!shouldFetchNextPage(acc, crawlOptions)) break;
|
|
155
158
|
currentParam = nextParam;
|
|
@@ -279,7 +282,7 @@ function queryFactory(configOrParent, childConfig) {
|
|
|
279
282
|
} : {
|
|
280
283
|
getNextPageParam: (_b = childConfig.getNextPageParam) != null ? _b : parentCfg.getNextPageParam,
|
|
281
284
|
getPreviousPageParam: (_c = childConfig.getPreviousPageParam) != null ? _c : parentCfg.getPreviousPageParam,
|
|
282
|
-
initialPageParam:
|
|
285
|
+
initialPageParam: "initialPageParam" in childConfig ? childConfig.initialPageParam : parentCfg.initialPageParam,
|
|
283
286
|
shouldFetchNextPage: (_d = childConfig.shouldFetchNextPage) != null ? _d : parentCfg.shouldFetchNextPage,
|
|
284
287
|
reduce: (_e = childConfig.reduce) != null ? _e : parentCfg.reduce
|
|
285
288
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -96,13 +96,16 @@ function buildInfiniteCrawlingQueryFn(queryFn, getNextPageParam, shouldFetchNext
|
|
|
96
96
|
const initialResult = queryFn(params, ctx);
|
|
97
97
|
if (isAsyncIterable(initialResult)) {
|
|
98
98
|
const pages2 = [];
|
|
99
|
+
const startParam = context.pageParam;
|
|
99
100
|
let acc2 = void 0;
|
|
100
101
|
let nextBatchParam2 = null;
|
|
101
102
|
for await (const page of initialResult) {
|
|
102
103
|
if ((_b = context.signal) == null ? void 0 : _b.aborted) break;
|
|
103
104
|
pages2.push(page);
|
|
104
105
|
acc2 = reduce(acc2, page);
|
|
105
|
-
const nextParam = getNextPageParam(page, pages2,
|
|
106
|
+
const nextParam = getNextPageParam(page, pages2, startParam, [
|
|
107
|
+
startParam
|
|
108
|
+
]);
|
|
106
109
|
nextBatchParam2 = nextParam != null ? nextParam : null;
|
|
107
110
|
if (nextParam == null) break;
|
|
108
111
|
if (!shouldFetchNextPage(acc2, crawlOptions)) break;
|
|
@@ -121,9 +124,9 @@ function buildInfiniteCrawlingQueryFn(queryFn, getNextPageParam, shouldFetchNext
|
|
|
121
124
|
pages.push(page);
|
|
122
125
|
pageParams.push(currentParam);
|
|
123
126
|
acc = reduce(acc, page);
|
|
124
|
-
if ((_c = context.signal) == null ? void 0 : _c.aborted) break;
|
|
125
127
|
const nextParam = getNextPageParam(page, pages, currentParam, pageParams);
|
|
126
128
|
nextBatchParam = nextParam != null ? nextParam : null;
|
|
129
|
+
if ((_c = context.signal) == null ? void 0 : _c.aborted) break;
|
|
127
130
|
if (nextParam == null) break;
|
|
128
131
|
if (!shouldFetchNextPage(acc, crawlOptions)) break;
|
|
129
132
|
currentParam = nextParam;
|
|
@@ -253,7 +256,7 @@ function queryFactory(configOrParent, childConfig) {
|
|
|
253
256
|
} : {
|
|
254
257
|
getNextPageParam: (_b = childConfig.getNextPageParam) != null ? _b : parentCfg.getNextPageParam,
|
|
255
258
|
getPreviousPageParam: (_c = childConfig.getPreviousPageParam) != null ? _c : parentCfg.getPreviousPageParam,
|
|
256
|
-
initialPageParam:
|
|
259
|
+
initialPageParam: "initialPageParam" in childConfig ? childConfig.initialPageParam : parentCfg.initialPageParam,
|
|
257
260
|
shouldFetchNextPage: (_d = childConfig.shouldFetchNextPage) != null ? _d : parentCfg.shouldFetchNextPage,
|
|
258
261
|
reduce: (_e = childConfig.reduce) != null ? _e : parentCfg.reduce
|
|
259
262
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@robohall/react-query-factory",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
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",
|