@robohall/react-query-factory 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +251 -65
- package/dist/index.d.mts +34 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +66 -17
- package/dist/index.mjs +66 -17
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -5,105 +5,293 @@
|
|
|
5
5
|

|
|
6
6
|
[](./LICENSE)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
<p align="center">
|
|
9
|
+
<a href="https://roberth26.github.io/react-query-factory/"><strong>Visit the Sandbox</strong></a>
|
|
10
|
+
</p>
|
|
9
11
|
|
|
10
|
-
|
|
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
|
+
|
|
14
|
+
Zero runtime dependencies.
|
|
11
15
|
|
|
12
16
|
---
|
|
13
17
|
|
|
14
|
-
##
|
|
18
|
+
## The problem
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
### Step 1 — wrap `useQuery` in a custom hook
|
|
21
|
+
|
|
22
|
+
The first instinct when a query is reused across components:
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
function useInstances(params: DescribeInstancesCommandInput) {
|
|
26
|
+
return useQuery({
|
|
27
|
+
queryKey: ['instances', params],
|
|
28
|
+
queryFn: () => fetchInstances(params),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
19
31
|
```
|
|
20
32
|
|
|
21
|
-
|
|
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.
|
|
22
34
|
|
|
23
|
-
|
|
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
|
+
```
|
|
24
55
|
|
|
25
|
-
|
|
56
|
+
The generics multiply with every new transform. The key is still trapped — prefetching and invalidation still can't reach it from outside.
|
|
57
|
+
|
|
58
|
+
### Step 2 — `queryOptions` for colocation
|
|
59
|
+
|
|
60
|
+
TanStack's `queryOptions` helper moves the key and fn into a shared object:
|
|
26
61
|
|
|
27
62
|
```typescript
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
63
|
+
const instancesOptions = (params: DescribeInstancesCommandInput) =>
|
|
64
|
+
queryOptions({
|
|
65
|
+
queryKey: ['instances', params],
|
|
66
|
+
queryFn: () => fetchInstances(params),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
useQuery(instancesOptions(params));
|
|
70
|
+
queryClient.prefetchQuery(instancesOptions(params));
|
|
71
|
+
queryClient.invalidateQueries(instancesOptions(params));
|
|
72
|
+
```
|
|
35
73
|
|
|
36
|
-
|
|
74
|
+
This is genuinely good — this library builds on the same pattern. But once you need multiple related queries, the cracks show.
|
|
37
75
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
76
|
+
### Step 3 — derived queries and key coordination
|
|
77
|
+
|
|
78
|
+
Say you want a running-instances view that shares the same cache entry as the full list. The natural move is to spread the base options and override `select`:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
const { data: running } = useQuery({
|
|
82
|
+
...instancesOptions(params),
|
|
83
|
+
select: data => data.filter(i => i.state === 'running'),
|
|
42
84
|
});
|
|
85
|
+
```
|
|
43
86
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
87
|
+
No key or `queryFn` duplication — this is the right approach. But `select` can only be applied at the call site, not captured in `instancesOptions` itself. And after a mutation you still need a magic string to bust the cache:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// If the key structure ever changes, every site breaks.
|
|
91
|
+
queryClient.invalidateQueries({ queryKey: ['instances'] });
|
|
50
92
|
```
|
|
51
93
|
|
|
52
|
-
|
|
94
|
+
### Step 4 — paginated APIs
|
|
53
95
|
|
|
54
|
-
|
|
96
|
+
`DescribeInstances` returns at most `MaxResults` instances per call. To get them all, you need to loop. The usual options:
|
|
55
97
|
|
|
56
|
-
|
|
98
|
+
**Put the loop in `queryFn`:**
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
const instancesOptions = params =>
|
|
102
|
+
queryOptions({
|
|
103
|
+
queryKey: ['instances', params],
|
|
104
|
+
queryFn: async () => {
|
|
105
|
+
let all: Instance[] = [];
|
|
106
|
+
let nextToken: string | undefined;
|
|
107
|
+
do {
|
|
108
|
+
const page = await ec2.send(
|
|
109
|
+
new DescribeInstancesCommand({ ...params, NextToken: nextToken }),
|
|
110
|
+
);
|
|
111
|
+
all = [
|
|
112
|
+
...all,
|
|
113
|
+
...(page.Reservations?.flatMap(r => r.Instances ?? []) ?? []),
|
|
114
|
+
];
|
|
115
|
+
nextToken = page.NextToken;
|
|
116
|
+
} while (nextToken);
|
|
117
|
+
return all;
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
```
|
|
57
121
|
|
|
58
|
-
|
|
122
|
+
The crawl logic is now baked in. Every call site gets all pages — you can't stop at 50 for a dropdown while fetching all for a table. The loop gets copy-pasted into every paginated query.
|
|
59
123
|
|
|
60
|
-
|
|
124
|
+
**Use `useInfiniteQuery`:**
|
|
61
125
|
|
|
62
126
|
```typescript
|
|
63
|
-
|
|
127
|
+
const instancesInfiniteOptions = params =>
|
|
128
|
+
infiniteQueryOptions({
|
|
129
|
+
queryKey: ['instances', 'infinite', params],
|
|
130
|
+
queryFn: ({ pageParam }) =>
|
|
131
|
+
ec2.send(
|
|
132
|
+
new DescribeInstancesCommand({ ...params, NextToken: pageParam }),
|
|
133
|
+
),
|
|
134
|
+
getNextPageParam: r => r.NextToken,
|
|
135
|
+
initialPageParam: undefined,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Caller still has to flatten, auto-advance, manage hasNextPage...
|
|
139
|
+
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery(
|
|
140
|
+
instancesInfiniteOptions(params),
|
|
141
|
+
);
|
|
142
|
+
const allInstances = data?.pages.flatMap(
|
|
143
|
+
page => page.Reservations?.flatMap(r => r.Instances ?? []) ?? [],
|
|
144
|
+
);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Now you have two separate factories that duplicate the key and queryFn and need to stay in sync. `useQuery` and `useInfiniteQuery` are separate cache entries. Derived queries, invalidation, and prefetching all have to be wired up independently for each.
|
|
148
|
+
|
|
149
|
+
### What's missing
|
|
150
|
+
|
|
151
|
+
- Define the query **once**: key, queryFn, pagination config
|
|
152
|
+
- Let each **call site** decide how much to crawl (e.g. 50 records, all of them, or none)
|
|
153
|
+
- Optionally have `useQuery` crawl and return the **accumulated result** instead of a single page
|
|
154
|
+
- Use **async iterables** as `queryFn` — pass a paginator function directly, no cursor wiring required
|
|
155
|
+
- Have `.infinite()` available on the **same factory**, no duplication
|
|
156
|
+
- Have derived queries **share the cache entry** automatically
|
|
157
|
+
- Have **scoped invalidation** through key composition — bust the whole namespace or just one param set and its children
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## The solution
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { queryFactory } from '@robohall/react-query-factory';
|
|
64
165
|
|
|
65
166
|
const describeInstances = queryFactory({
|
|
66
167
|
queryKey: ['ec2:DescribeInstances'],
|
|
67
168
|
queryFn: (params: DescribeInstancesCommandInput, ctx) =>
|
|
68
169
|
ec2.send(
|
|
69
170
|
new DescribeInstancesCommand({ ...params, NextToken: ctx.pageParam }),
|
|
70
|
-
{
|
|
171
|
+
{
|
|
172
|
+
abortSignal: ctx.signal,
|
|
173
|
+
},
|
|
71
174
|
),
|
|
72
|
-
getNextPageParam:
|
|
175
|
+
getNextPageParam: r => r.NextToken,
|
|
73
176
|
initialPageParam: undefined as string | undefined,
|
|
74
|
-
shouldFetchNextPage: () => true,
|
|
75
177
|
reduce: (acc, page): Instance[] => [
|
|
76
178
|
...(acc ?? []),
|
|
77
179
|
...(page.Reservations?.flatMap(r => r.Instances ?? []) ?? []),
|
|
78
180
|
],
|
|
181
|
+
shouldFetchNextPage: (instances, opts: { minResults?: number }) =>
|
|
182
|
+
opts.minResults == null || instances.length < opts.minResults,
|
|
79
183
|
});
|
|
80
184
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
185
|
+
// useQuery — crawls all pages, data is Instance[]
|
|
186
|
+
const { data } = useQuery(describeInstances({ MaxResults: 20 }));
|
|
187
|
+
|
|
188
|
+
// Stop at 50 — separate cache entry, independent crawl
|
|
189
|
+
const { data } = useQuery(
|
|
190
|
+
describeInstances({ MaxResults: 20 }, { minResults: 50 }),
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// UI-driven pagination — same factory, no duplication
|
|
194
|
+
const { data, fetchNextPage } = useInfiniteQuery(
|
|
195
|
+
describeInstances.infinite({ MaxResults: 20 }, { minResults: 50 }),
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
// Derived view — shares the cache entry, no extra API call
|
|
199
|
+
const runningInstances = queryFactory(describeInstances, {
|
|
200
|
+
select: instances => instances.filter(i => i.State?.Name === 'running'),
|
|
201
|
+
});
|
|
202
|
+
const { data: running } = useQuery(runningInstances({ MaxResults: 20 }));
|
|
203
|
+
|
|
204
|
+
// Prefetch in a route loader
|
|
205
|
+
await queryClient.prefetchQuery(describeInstances({ MaxResults: 20 }));
|
|
206
|
+
|
|
207
|
+
// Bust everything in the namespace
|
|
208
|
+
queryClient.invalidateQueries(describeInstances());
|
|
209
|
+
|
|
210
|
+
// Bust only this param set — cascades to runningInstances and any other child
|
|
211
|
+
queryClient.invalidateQueries(describeInstances({ MaxResults: 20 }));
|
|
85
212
|
```
|
|
86
213
|
|
|
87
|
-
`
|
|
214
|
+
`describeInstances({ ... })` returns a plain `{ queryKey, queryFn, ... }` object — pass it directly to `useQuery`, `useInfiniteQuery`, `prefetchQuery`, or `getQueryData`. The factory doesn't touch your query client.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Installation
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
npm install @robohall/react-query-factory
|
|
222
|
+
# peer dependency: @tanstack/react-query >= 5.0.0
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Crawling
|
|
228
|
+
|
|
229
|
+
`shouldFetchNextPage` is called after each page — return `true` to keep fetching, `false` to stop. `getNextPageParam` and `initialPageParam` follow the exact TanStack API. `reduce` folds pages into a single accumulated value; without it the result is an array of raw pages (`TData[]`).
|
|
230
|
+
|
|
231
|
+
The `crawlOptions` argument passed at call time is forwarded to `shouldFetchNextPage` and appended to the query key, so different call sites crawl independently and never share a cache entry:
|
|
88
232
|
|
|
89
233
|
```typescript
|
|
90
234
|
const describeInstances = queryFactory({
|
|
91
235
|
// ...
|
|
92
|
-
reduce: (acc, page): Instance[] => [...(acc ?? []), ...page.Reservations.flatMap(r => r.Instances)],
|
|
93
236
|
shouldFetchNextPage: (instances, opts: { minResults?: number }) =>
|
|
94
237
|
opts.minResults == null || instances.length < opts.minResults,
|
|
95
238
|
});
|
|
96
239
|
|
|
97
|
-
//
|
|
240
|
+
// two separate cache entries — crawl independently
|
|
98
241
|
const { data: all } = useQuery(describeInstances({ MaxResults: 20 }));
|
|
99
|
-
|
|
100
|
-
// stop after accumulating at least 50 instances (≥ 3 API calls)
|
|
101
242
|
const { data: partial } = useQuery(
|
|
102
|
-
describeInstances({ MaxResults: 20 }, { minResults: 50 })
|
|
243
|
+
describeInstances({ MaxResults: 20 }, { minResults: 50 }),
|
|
103
244
|
);
|
|
104
245
|
```
|
|
105
246
|
|
|
106
|
-
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Async iterator queryFns
|
|
250
|
+
|
|
251
|
+
When `queryFn` returns an `AsyncIterable`, the library detects it automatically and drives the crawl with `for await...of` instead of the cursor loop. This means `getNextPageParam` and `initialPageParam` are not required — the iterator manages its own cursor. AWS SDK v3 paginator functions (`paginateDescribeInstances`, etc.) are a common source of async iterables:
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
import { paginateDescribeInstances } from '@aws-sdk/client-ec2';
|
|
255
|
+
|
|
256
|
+
const describeInstances = queryFactory({
|
|
257
|
+
queryKey: ['ec2:DescribeInstances'],
|
|
258
|
+
queryFn: (params: DescribeInstancesCommandInput) =>
|
|
259
|
+
paginateDescribeInstances({ client: ec2 }, params),
|
|
260
|
+
shouldFetchNextPage: (instances, opts: { minResults?: number }) =>
|
|
261
|
+
opts.minResults == null || instances.length < opts.minResults,
|
|
262
|
+
reduce: (acc, page: DescribeInstancesResponse): Instance[] => [
|
|
263
|
+
...(acc ?? []),
|
|
264
|
+
...(page.Reservations?.flatMap(r => r.Instances ?? []) ?? []),
|
|
265
|
+
],
|
|
266
|
+
});
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
`shouldFetchNextPage` still controls early stopping — the library calls `next()` on the iterator only when it returns `true`. Any source of `AsyncIterable<TPage>` works, not just AWS SDK paginators.
|
|
270
|
+
|
|
271
|
+
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:
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
const describeInstances = queryFactory({
|
|
275
|
+
queryKey: ['ec2:DescribeInstances'],
|
|
276
|
+
queryFn: (params: DescribeInstancesCommandInput, ctx) =>
|
|
277
|
+
paginateDescribeInstances(
|
|
278
|
+
{ client: ec2 },
|
|
279
|
+
{ ...params, StartingToken: ctx.pageParam },
|
|
280
|
+
),
|
|
281
|
+
getNextPageParam: page => page.NextToken,
|
|
282
|
+
initialPageParam: undefined as string | undefined,
|
|
283
|
+
shouldFetchNextPage: (instances, opts: { minResults?: number }) =>
|
|
284
|
+
opts.minResults == null || instances.length < opts.minResults,
|
|
285
|
+
reduce: (acc, page): Instance[] => [
|
|
286
|
+
...(acc ?? []),
|
|
287
|
+
...(page.Instances ?? []),
|
|
288
|
+
],
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const { data, fetchNextPage } = useInfiniteQuery(
|
|
292
|
+
describeInstances.infinite({ MaxResults: 20 }, { minResults: 50 }),
|
|
293
|
+
);
|
|
294
|
+
```
|
|
107
295
|
|
|
108
296
|
---
|
|
109
297
|
|
|
@@ -139,7 +327,7 @@ const findInstance = queryFactory(describeInstances, {
|
|
|
139
327
|
// query key: ['ec2:DescribeInstances', { MaxResults: 20 }, 'find', { instanceId: 'i-0abc123def456' }]
|
|
140
328
|
// crawls pages until the target instance appears, then stops
|
|
141
329
|
const { data } = useQuery(
|
|
142
|
-
findInstance({ MaxResults: 20 }, { instanceId: 'i-0abc123def456' })
|
|
330
|
+
findInstance({ MaxResults: 20 }, { instanceId: 'i-0abc123def456' }),
|
|
143
331
|
);
|
|
144
332
|
```
|
|
145
333
|
|
|
@@ -180,18 +368,17 @@ Every factory exposes a `.infinite()` method that returns `useInfiniteQuery`-com
|
|
|
180
368
|
```typescript
|
|
181
369
|
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery(
|
|
182
370
|
// load 50 instances per UI page, each backed by up to 5 DescribeInstances calls
|
|
183
|
-
describeInstances.infinite({ MaxResults: 20 }, { minResults: 50 })
|
|
371
|
+
describeInstances.infinite({ MaxResults: 20 }, { minResults: 50 }),
|
|
184
372
|
);
|
|
185
373
|
|
|
186
374
|
// data.pages is Instance[][], one array per virtual page
|
|
187
375
|
```
|
|
188
376
|
|
|
189
377
|
The `.infinite()` key includes an `'infinite'` segment to keep it separate from the regular `useQuery` cache entry:
|
|
378
|
+
|
|
190
379
|
- `describeInstances({ MaxResults: 20 })` → `['ec2:DescribeInstances', { MaxResults: 20 }]`
|
|
191
380
|
- `describeInstances.infinite({ MaxResults: 20 })` → `['ec2:DescribeInstances', 'infinite', { MaxResults: 20 }]`
|
|
192
381
|
|
|
193
|
-
Child factories place `params` before their own key segments so that the parent key is always a prefix of the child key for the same params — enabling per-call-site scoped invalidation.
|
|
194
|
-
|
|
195
382
|
---
|
|
196
383
|
|
|
197
384
|
## Public API
|
|
@@ -209,6 +396,7 @@ queryFactory<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions>(
|
|
|
209
396
|
### `queryFactory(parent, config)`
|
|
210
397
|
|
|
211
398
|
Creates a child factory. Two overloads:
|
|
399
|
+
|
|
212
400
|
- **With a new `queryFn`** — inherits key namespace and standard options; crawling config must be re-declared if needed.
|
|
213
401
|
- **Without a `queryFn`** — inherits everything; accepts `queryKey`, `select`, standard options, and any crawling fields (`shouldFetchNextPage`, `reduce`, `getNextPageParam`, `getPreviousPageParam`, `initialPageParam`) to override the parent's. `select` is composed with the parent's.
|
|
214
402
|
|
|
@@ -216,17 +404,17 @@ Creates a child factory. Two overloads:
|
|
|
216
404
|
|
|
217
405
|
All fields except `reduce` and `shouldFetchNextPage` are the standard TanStack Query API — the same types and semantics you'd pass to `useQuery` or `useInfiniteQuery`. The factory doesn't reinvent them; it just requires certain combinations to be present in order to activate crawling.
|
|
218
406
|
|
|
219
|
-
| Field
|
|
220
|
-
|
|
221
|
-
| `queryKey`
|
|
222
|
-
| `queryFn`
|
|
223
|
-
| `select`
|
|
224
|
-
| `getNextPageParam`
|
|
225
|
-
| `initialPageParam`
|
|
226
|
-
| `getPreviousPageParam`
|
|
227
|
-
| `shouldFetchNextPage`
|
|
228
|
-
| `reduce`
|
|
229
|
-
| + all `StandardQueryOptions` fields |
|
|
407
|
+
| Field | Type | Notes |
|
|
408
|
+
| ----------------------------------- | ------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
409
|
+
| `queryKey` | `QueryKey` | Namespace segments. Params are appended at call time. |
|
|
410
|
+
| `queryFn` | `(params: TParams, ctx: QueryFunctionContext) => TData \| Promise<TData> \| AsyncIterable<TData>` | Same as TanStack, with an extra leading `params` argument. Returns an `AsyncIterable` to use iterator-based crawling. |
|
|
411
|
+
| `select` | `(data: TData) => TSelected` | Exact TanStack API. Composed automatically on child factories. |
|
|
412
|
+
| `getNextPageParam` | `GetNextPageParamFunction<TPageParam, TData>` | Exact TanStack API. Required (with `shouldFetchNextPage`) to activate cursor-based crawling. Required (with `initialPageParam`) for `.infinite()`. |
|
|
413
|
+
| `initialPageParam` | `TPageParam` | Exact TanStack API. Drives `TPageParam` inference. Required for `.infinite()` to work at runtime. |
|
|
414
|
+
| `getPreviousPageParam` | `GetPreviousPageParamFunction<TPageParam, TData>` | Exact TanStack API. Passed through on `.infinite()`. |
|
|
415
|
+
| `shouldFetchNextPage` | `(combined: TSelected \| undefined, crawlOptions: TCrawlOptions) => boolean` | Library addition. **Required to activate crawling.** Called after each page — return `true` to keep fetching, `false` to stop. |
|
|
416
|
+
| `reduce` | `(acc: TSelected \| undefined, page: TData) => TSelected` | Library addition. Optional. Folds crawled pages into a single `TSelected` value; when omitted the result is an array of all fetched raw pages (`TData[]`). |
|
|
417
|
+
| + all `StandardQueryOptions` fields | | `staleTime`, `gcTime`, `retry`, `enabled`, `refetchOnWindowFocus`, `placeholderData`, `initialData`, `meta`, etc. Function-form callbacks are supported wherever TanStack accepts them. |
|
|
230
418
|
|
|
231
419
|
### `QueryFactory<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions>`
|
|
232
420
|
|
|
@@ -249,10 +437,8 @@ Return type of `factory.infinite(params)`. Pass directly to `useInfiniteQuery()`
|
|
|
249
437
|
|
|
250
438
|
## Running the sandbox
|
|
251
439
|
|
|
252
|
-
The sandbox contains six interactive demos using a mock paginated API: basic single-page fetch, full crawl, factory composition, infinite query with per-page crawling, early-stop target search, and namespace-based cache invalidation.
|
|
253
|
-
|
|
254
440
|
```bash
|
|
255
441
|
npm run sandbox
|
|
256
442
|
```
|
|
257
443
|
|
|
258
|
-
|
|
444
|
+
Starts a Vite dev server with interactive demos covering every pattern: basic single-page fetch, async iterator queryFns, crawl-then-render, render-while-crawling, on-demand infinite pagination, client-side search with early stopping, factory composition, and scoped cache invalidation.
|
package/dist/index.d.mts
CHANGED
|
@@ -156,5 +156,39 @@ declare function queryFactory<TChildParams extends TParentParams, TData = unknow
|
|
|
156
156
|
reduce?: (accumulator: TChildSelected | undefined, page: TData) => TChildSelected;
|
|
157
157
|
shouldFetchNextPage?: (combined: TParentHasReduce extends true ? TChildSelected : TChildSelected | undefined, crawlOptions: TChildCrawlOptions) => boolean;
|
|
158
158
|
}): QueryFactory<TChildParams, TData, TError, TChildSelected, TPageParam, TChildCrawlOptions, TParentHasReduce>;
|
|
159
|
+
/**
|
|
160
|
+
* Creates a standalone factory whose queryFn returns an AsyncIterable (e.g. an AWS SDK v3
|
|
161
|
+
* paginator). The library drives the crawl with `for await...of`; `getNextPageParam` and
|
|
162
|
+
* `initialPageParam` are not required for `useQuery` mode. When `reduce` is present,
|
|
163
|
+
* `shouldFetchNextPage` receives `TSelected` (never undefined).
|
|
164
|
+
*/
|
|
165
|
+
declare function queryFactory<TParams = void, TData = unknown, TError = Error, TSelected = TData, TPageParam = unknown, TCrawlOptions extends Record<string, unknown> = Record<string, unknown>>(config: StandardQueryOptions<TError, TData> & {
|
|
166
|
+
queryKey: QueryKey;
|
|
167
|
+
queryFn: (params: TParams, context: QueryFunctionContext<QueryKey, [
|
|
168
|
+
unknown
|
|
169
|
+
] extends [TPageParam] ? never : TPageParam>) => AsyncIterable<TData>;
|
|
170
|
+
select?: (data: TData) => TSelected;
|
|
171
|
+
getNextPageParam?: GetNextPageParamFunction<TPageParam, TData>;
|
|
172
|
+
initialPageParam?: TPageParam;
|
|
173
|
+
getPreviousPageParam?: GetPreviousPageParamFunction<TPageParam, TData>;
|
|
174
|
+
reduce: (accumulator: TSelected | undefined, page: TData) => TSelected;
|
|
175
|
+
shouldFetchNextPage?: (combined: TSelected, crawlOptions: TCrawlOptions) => boolean;
|
|
176
|
+
}): QueryFactory<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions, true>;
|
|
177
|
+
/**
|
|
178
|
+
* Creates a standalone factory whose queryFn returns an AsyncIterable, without a `reduce`
|
|
179
|
+
* function. Result is `TData[]` (one element per yielded page).
|
|
180
|
+
*/
|
|
181
|
+
declare function queryFactory<TParams = void, TData = unknown, TError = Error, TSelected = TData, TPageParam = unknown, TCrawlOptions extends Record<string, unknown> = Record<string, unknown>>(config: StandardQueryOptions<TError, TData> & {
|
|
182
|
+
queryKey: QueryKey;
|
|
183
|
+
queryFn: (params: TParams, context: QueryFunctionContext<QueryKey, [
|
|
184
|
+
unknown
|
|
185
|
+
] extends [TPageParam] ? never : TPageParam>) => AsyncIterable<TData>;
|
|
186
|
+
select?: (data: TData) => TSelected;
|
|
187
|
+
getNextPageParam?: GetNextPageParamFunction<TPageParam, TData>;
|
|
188
|
+
initialPageParam?: TPageParam;
|
|
189
|
+
getPreviousPageParam?: GetPreviousPageParamFunction<TPageParam, TData>;
|
|
190
|
+
reduce?: never;
|
|
191
|
+
shouldFetchNextPage: (combined: TSelected | undefined, crawlOptions: TCrawlOptions) => boolean;
|
|
192
|
+
}): QueryFactory<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions, false>;
|
|
159
193
|
|
|
160
194
|
export { type QueryFactory, type QueryFactoryConfig, type ResolvedInfiniteOptions, type ResolvedQueryOptions, type StandardQueryOptions, queryFactory };
|
package/dist/index.d.ts
CHANGED
|
@@ -156,5 +156,39 @@ declare function queryFactory<TChildParams extends TParentParams, TData = unknow
|
|
|
156
156
|
reduce?: (accumulator: TChildSelected | undefined, page: TData) => TChildSelected;
|
|
157
157
|
shouldFetchNextPage?: (combined: TParentHasReduce extends true ? TChildSelected : TChildSelected | undefined, crawlOptions: TChildCrawlOptions) => boolean;
|
|
158
158
|
}): QueryFactory<TChildParams, TData, TError, TChildSelected, TPageParam, TChildCrawlOptions, TParentHasReduce>;
|
|
159
|
+
/**
|
|
160
|
+
* Creates a standalone factory whose queryFn returns an AsyncIterable (e.g. an AWS SDK v3
|
|
161
|
+
* paginator). The library drives the crawl with `for await...of`; `getNextPageParam` and
|
|
162
|
+
* `initialPageParam` are not required for `useQuery` mode. When `reduce` is present,
|
|
163
|
+
* `shouldFetchNextPage` receives `TSelected` (never undefined).
|
|
164
|
+
*/
|
|
165
|
+
declare function queryFactory<TParams = void, TData = unknown, TError = Error, TSelected = TData, TPageParam = unknown, TCrawlOptions extends Record<string, unknown> = Record<string, unknown>>(config: StandardQueryOptions<TError, TData> & {
|
|
166
|
+
queryKey: QueryKey;
|
|
167
|
+
queryFn: (params: TParams, context: QueryFunctionContext<QueryKey, [
|
|
168
|
+
unknown
|
|
169
|
+
] extends [TPageParam] ? never : TPageParam>) => AsyncIterable<TData>;
|
|
170
|
+
select?: (data: TData) => TSelected;
|
|
171
|
+
getNextPageParam?: GetNextPageParamFunction<TPageParam, TData>;
|
|
172
|
+
initialPageParam?: TPageParam;
|
|
173
|
+
getPreviousPageParam?: GetPreviousPageParamFunction<TPageParam, TData>;
|
|
174
|
+
reduce: (accumulator: TSelected | undefined, page: TData) => TSelected;
|
|
175
|
+
shouldFetchNextPage?: (combined: TSelected, crawlOptions: TCrawlOptions) => boolean;
|
|
176
|
+
}): QueryFactory<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions, true>;
|
|
177
|
+
/**
|
|
178
|
+
* Creates a standalone factory whose queryFn returns an AsyncIterable, without a `reduce`
|
|
179
|
+
* function. Result is `TData[]` (one element per yielded page).
|
|
180
|
+
*/
|
|
181
|
+
declare function queryFactory<TParams = void, TData = unknown, TError = Error, TSelected = TData, TPageParam = unknown, TCrawlOptions extends Record<string, unknown> = Record<string, unknown>>(config: StandardQueryOptions<TError, TData> & {
|
|
182
|
+
queryKey: QueryKey;
|
|
183
|
+
queryFn: (params: TParams, context: QueryFunctionContext<QueryKey, [
|
|
184
|
+
unknown
|
|
185
|
+
] extends [TPageParam] ? never : TPageParam>) => AsyncIterable<TData>;
|
|
186
|
+
select?: (data: TData) => TSelected;
|
|
187
|
+
getNextPageParam?: GetNextPageParamFunction<TPageParam, TData>;
|
|
188
|
+
initialPageParam?: TPageParam;
|
|
189
|
+
getPreviousPageParam?: GetPreviousPageParamFunction<TPageParam, TData>;
|
|
190
|
+
reduce?: never;
|
|
191
|
+
shouldFetchNextPage: (combined: TSelected | undefined, crawlOptions: TCrawlOptions) => boolean;
|
|
192
|
+
}): QueryFactory<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions, false>;
|
|
159
193
|
|
|
160
194
|
export { type QueryFactory, type QueryFactoryConfig, type ResolvedInfiniteOptions, type ResolvedQueryOptions, type StandardQueryOptions, queryFactory };
|
package/dist/index.js
CHANGED
|
@@ -28,6 +28,9 @@ module.exports = __toCommonJS(index_exports);
|
|
|
28
28
|
var FACTORY_CONFIG = /* @__PURE__ */ Symbol("factoryConfig");
|
|
29
29
|
var getEnvelopeNextPageParam = (envelope) => envelope.nextPageParam;
|
|
30
30
|
var noNextPage = () => void 0;
|
|
31
|
+
function isAsyncIterable(value) {
|
|
32
|
+
return value != null && typeof value[Symbol.asyncIterator] === "function";
|
|
33
|
+
}
|
|
31
34
|
function resolveKey(namespace, params, crawlOptions) {
|
|
32
35
|
const withParams = params === void 0 ? namespace : [...namespace, params];
|
|
33
36
|
if (!crawlOptions) return withParams;
|
|
@@ -55,31 +58,53 @@ function buildChildKey(parentKey, ownSegments, params, crawlOptions, infinite) {
|
|
|
55
58
|
function wrapGetNextPageParam(getNextPageParam, shouldFetchNextPage, crawlOptions, select) {
|
|
56
59
|
return (lastPage, allPages, lastPageParam, allPageParams) => {
|
|
57
60
|
const combined = select ? select(lastPage) : lastPage;
|
|
58
|
-
if (!shouldFetchNextPage(combined, crawlOptions))
|
|
59
|
-
return void 0;
|
|
61
|
+
if (!shouldFetchNextPage(combined, crawlOptions)) return void 0;
|
|
60
62
|
return getNextPageParam(lastPage, allPages, lastPageParam, allPageParams);
|
|
61
63
|
};
|
|
62
64
|
}
|
|
63
65
|
function buildCrawlingQueryFn(queryFn, getNextPageParam, initialPageParam, shouldFetchNextPage, reduce) {
|
|
64
66
|
return async (params, crawlOptions, context) => {
|
|
65
|
-
var _a, _b;
|
|
67
|
+
var _a, _b, _c, _d;
|
|
68
|
+
if ((_a = context.signal) == null ? void 0 : _a.aborted) {
|
|
69
|
+
if (reduce) throw new DOMException("Aborted", "AbortError");
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
const ctx = { ...context, pageParam: initialPageParam };
|
|
73
|
+
const initialResult = queryFn(params, ctx);
|
|
74
|
+
if (isAsyncIterable(initialResult)) {
|
|
75
|
+
const pages2 = [];
|
|
76
|
+
let acc2 = void 0;
|
|
77
|
+
for await (const page of initialResult) {
|
|
78
|
+
if ((_b = context.signal) == null ? void 0 : _b.aborted) break;
|
|
79
|
+
pages2.push(page);
|
|
80
|
+
if (reduce) acc2 = reduce(acc2, page);
|
|
81
|
+
if (!shouldFetchNextPage(acc2, crawlOptions)) break;
|
|
82
|
+
}
|
|
83
|
+
if (reduce) {
|
|
84
|
+
if (acc2 === void 0) throw new DOMException("Aborted", "AbortError");
|
|
85
|
+
return acc2;
|
|
86
|
+
}
|
|
87
|
+
return pages2;
|
|
88
|
+
}
|
|
66
89
|
const pages = [];
|
|
67
90
|
const pageParams = [];
|
|
68
91
|
let currentParam = initialPageParam;
|
|
69
92
|
let acc = void 0;
|
|
70
|
-
|
|
93
|
+
let nextResult = initialResult;
|
|
71
94
|
while (true) {
|
|
72
|
-
|
|
73
|
-
ctx.pageParam = currentParam;
|
|
74
|
-
const page = await queryFn(params, ctx);
|
|
95
|
+
const page = await nextResult;
|
|
75
96
|
pages.push(page);
|
|
76
97
|
pageParams.push(currentParam);
|
|
77
98
|
if (reduce) acc = reduce(acc, page);
|
|
78
|
-
if ((
|
|
99
|
+
if ((_c = context.signal) == null ? void 0 : _c.aborted) break;
|
|
79
100
|
if (!shouldFetchNextPage(acc, crawlOptions)) break;
|
|
101
|
+
if (!getNextPageParam) break;
|
|
80
102
|
const nextParam = getNextPageParam(page, pages, currentParam, pageParams);
|
|
81
103
|
if (nextParam == null) break;
|
|
82
104
|
currentParam = nextParam;
|
|
105
|
+
if ((_d = context.signal) == null ? void 0 : _d.aborted) break;
|
|
106
|
+
ctx.pageParam = currentParam;
|
|
107
|
+
nextResult = queryFn(params, ctx);
|
|
83
108
|
}
|
|
84
109
|
if (reduce) {
|
|
85
110
|
if (acc === void 0) throw new DOMException("Aborted", "AbortError");
|
|
@@ -90,26 +115,50 @@ function buildCrawlingQueryFn(queryFn, getNextPageParam, initialPageParam, shoul
|
|
|
90
115
|
}
|
|
91
116
|
function buildInfiniteCrawlingQueryFn(queryFn, getNextPageParam, shouldFetchNextPage, reduce) {
|
|
92
117
|
return async (params, crawlOptions, context) => {
|
|
93
|
-
var _a, _b;
|
|
118
|
+
var _a, _b, _c, _d;
|
|
119
|
+
if ((_a = context.signal) == null ? void 0 : _a.aborted)
|
|
120
|
+
throw new DOMException("Aborted", "AbortError");
|
|
121
|
+
const ctx = { ...context, pageParam: context.pageParam };
|
|
122
|
+
const initialResult = queryFn(params, ctx);
|
|
123
|
+
if (isAsyncIterable(initialResult)) {
|
|
124
|
+
const pages2 = [];
|
|
125
|
+
const startParam = context.pageParam;
|
|
126
|
+
let acc2 = void 0;
|
|
127
|
+
let nextBatchParam2 = null;
|
|
128
|
+
for await (const page of initialResult) {
|
|
129
|
+
if ((_b = context.signal) == null ? void 0 : _b.aborted) break;
|
|
130
|
+
pages2.push(page);
|
|
131
|
+
acc2 = reduce(acc2, page);
|
|
132
|
+
const nextParam = getNextPageParam(page, pages2, startParam, [
|
|
133
|
+
startParam
|
|
134
|
+
]);
|
|
135
|
+
nextBatchParam2 = nextParam != null ? nextParam : null;
|
|
136
|
+
if (nextParam == null) break;
|
|
137
|
+
if (!shouldFetchNextPage(acc2, crawlOptions)) break;
|
|
138
|
+
}
|
|
139
|
+
if (acc2 === void 0) throw new DOMException("Aborted", "AbortError");
|
|
140
|
+
return { data: acc2, nextPageParam: nextBatchParam2 };
|
|
141
|
+
}
|
|
94
142
|
const pages = [];
|
|
95
143
|
const pageParams = [];
|
|
96
144
|
let currentParam = context.pageParam;
|
|
97
145
|
let acc = void 0;
|
|
98
146
|
let nextBatchParam = null;
|
|
99
|
-
|
|
147
|
+
let nextResult = initialResult;
|
|
100
148
|
while (true) {
|
|
101
|
-
|
|
102
|
-
ctx.pageParam = currentParam;
|
|
103
|
-
const page = await queryFn(params, ctx);
|
|
149
|
+
const page = await nextResult;
|
|
104
150
|
pages.push(page);
|
|
105
151
|
pageParams.push(currentParam);
|
|
106
152
|
acc = reduce(acc, page);
|
|
107
|
-
if ((_b = context.signal) == null ? void 0 : _b.aborted) break;
|
|
108
153
|
const nextParam = getNextPageParam(page, pages, currentParam, pageParams);
|
|
109
154
|
nextBatchParam = nextParam != null ? nextParam : null;
|
|
155
|
+
if ((_c = context.signal) == null ? void 0 : _c.aborted) break;
|
|
110
156
|
if (nextParam == null) break;
|
|
111
157
|
if (!shouldFetchNextPage(acc, crawlOptions)) break;
|
|
112
158
|
currentParam = nextParam;
|
|
159
|
+
if ((_d = context.signal) == null ? void 0 : _d.aborted) break;
|
|
160
|
+
ctx.pageParam = currentParam;
|
|
161
|
+
nextResult = queryFn(params, ctx);
|
|
113
162
|
}
|
|
114
163
|
if (acc === void 0) throw new DOMException("Aborted", "AbortError");
|
|
115
164
|
return { data: acc, nextPageParam: nextBatchParam };
|
|
@@ -130,8 +179,8 @@ function buildFactory(cfg) {
|
|
|
130
179
|
} = cfg;
|
|
131
180
|
const ownSegments = parentKey !== void 0 ? namespace.slice(parentKey.length) : namespace;
|
|
132
181
|
const infiniteNamespace = [...namespace, "infinite"];
|
|
133
|
-
const hasCrawling = rawQueryFn !== void 0 &&
|
|
134
|
-
const hasInfiniteCrawling = hasCrawling && reduce !== void 0;
|
|
182
|
+
const hasCrawling = rawQueryFn !== void 0 && shouldFetchNextPage !== void 0;
|
|
183
|
+
const hasInfiniteCrawling = hasCrawling && reduce !== void 0 && getNextPageParam !== void 0;
|
|
135
184
|
const crawlingFn = hasCrawling ? buildCrawlingQueryFn(
|
|
136
185
|
rawQueryFn,
|
|
137
186
|
getNextPageParam,
|
|
@@ -233,7 +282,7 @@ function queryFactory(configOrParent, childConfig) {
|
|
|
233
282
|
} : {
|
|
234
283
|
getNextPageParam: (_b = childConfig.getNextPageParam) != null ? _b : parentCfg.getNextPageParam,
|
|
235
284
|
getPreviousPageParam: (_c = childConfig.getPreviousPageParam) != null ? _c : parentCfg.getPreviousPageParam,
|
|
236
|
-
initialPageParam:
|
|
285
|
+
initialPageParam: "initialPageParam" in childConfig ? childConfig.initialPageParam : parentCfg.initialPageParam,
|
|
237
286
|
shouldFetchNextPage: (_d = childConfig.shouldFetchNextPage) != null ? _d : parentCfg.shouldFetchNextPage,
|
|
238
287
|
reduce: (_e = childConfig.reduce) != null ? _e : parentCfg.reduce
|
|
239
288
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
var FACTORY_CONFIG = /* @__PURE__ */ Symbol("factoryConfig");
|
|
3
3
|
var getEnvelopeNextPageParam = (envelope) => envelope.nextPageParam;
|
|
4
4
|
var noNextPage = () => void 0;
|
|
5
|
+
function isAsyncIterable(value) {
|
|
6
|
+
return value != null && typeof value[Symbol.asyncIterator] === "function";
|
|
7
|
+
}
|
|
5
8
|
function resolveKey(namespace, params, crawlOptions) {
|
|
6
9
|
const withParams = params === void 0 ? namespace : [...namespace, params];
|
|
7
10
|
if (!crawlOptions) return withParams;
|
|
@@ -29,31 +32,53 @@ function buildChildKey(parentKey, ownSegments, params, crawlOptions, infinite) {
|
|
|
29
32
|
function wrapGetNextPageParam(getNextPageParam, shouldFetchNextPage, crawlOptions, select) {
|
|
30
33
|
return (lastPage, allPages, lastPageParam, allPageParams) => {
|
|
31
34
|
const combined = select ? select(lastPage) : lastPage;
|
|
32
|
-
if (!shouldFetchNextPage(combined, crawlOptions))
|
|
33
|
-
return void 0;
|
|
35
|
+
if (!shouldFetchNextPage(combined, crawlOptions)) return void 0;
|
|
34
36
|
return getNextPageParam(lastPage, allPages, lastPageParam, allPageParams);
|
|
35
37
|
};
|
|
36
38
|
}
|
|
37
39
|
function buildCrawlingQueryFn(queryFn, getNextPageParam, initialPageParam, shouldFetchNextPage, reduce) {
|
|
38
40
|
return async (params, crawlOptions, context) => {
|
|
39
|
-
var _a, _b;
|
|
41
|
+
var _a, _b, _c, _d;
|
|
42
|
+
if ((_a = context.signal) == null ? void 0 : _a.aborted) {
|
|
43
|
+
if (reduce) throw new DOMException("Aborted", "AbortError");
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
const ctx = { ...context, pageParam: initialPageParam };
|
|
47
|
+
const initialResult = queryFn(params, ctx);
|
|
48
|
+
if (isAsyncIterable(initialResult)) {
|
|
49
|
+
const pages2 = [];
|
|
50
|
+
let acc2 = void 0;
|
|
51
|
+
for await (const page of initialResult) {
|
|
52
|
+
if ((_b = context.signal) == null ? void 0 : _b.aborted) break;
|
|
53
|
+
pages2.push(page);
|
|
54
|
+
if (reduce) acc2 = reduce(acc2, page);
|
|
55
|
+
if (!shouldFetchNextPage(acc2, crawlOptions)) break;
|
|
56
|
+
}
|
|
57
|
+
if (reduce) {
|
|
58
|
+
if (acc2 === void 0) throw new DOMException("Aborted", "AbortError");
|
|
59
|
+
return acc2;
|
|
60
|
+
}
|
|
61
|
+
return pages2;
|
|
62
|
+
}
|
|
40
63
|
const pages = [];
|
|
41
64
|
const pageParams = [];
|
|
42
65
|
let currentParam = initialPageParam;
|
|
43
66
|
let acc = void 0;
|
|
44
|
-
|
|
67
|
+
let nextResult = initialResult;
|
|
45
68
|
while (true) {
|
|
46
|
-
|
|
47
|
-
ctx.pageParam = currentParam;
|
|
48
|
-
const page = await queryFn(params, ctx);
|
|
69
|
+
const page = await nextResult;
|
|
49
70
|
pages.push(page);
|
|
50
71
|
pageParams.push(currentParam);
|
|
51
72
|
if (reduce) acc = reduce(acc, page);
|
|
52
|
-
if ((
|
|
73
|
+
if ((_c = context.signal) == null ? void 0 : _c.aborted) break;
|
|
53
74
|
if (!shouldFetchNextPage(acc, crawlOptions)) break;
|
|
75
|
+
if (!getNextPageParam) break;
|
|
54
76
|
const nextParam = getNextPageParam(page, pages, currentParam, pageParams);
|
|
55
77
|
if (nextParam == null) break;
|
|
56
78
|
currentParam = nextParam;
|
|
79
|
+
if ((_d = context.signal) == null ? void 0 : _d.aborted) break;
|
|
80
|
+
ctx.pageParam = currentParam;
|
|
81
|
+
nextResult = queryFn(params, ctx);
|
|
57
82
|
}
|
|
58
83
|
if (reduce) {
|
|
59
84
|
if (acc === void 0) throw new DOMException("Aborted", "AbortError");
|
|
@@ -64,26 +89,50 @@ function buildCrawlingQueryFn(queryFn, getNextPageParam, initialPageParam, shoul
|
|
|
64
89
|
}
|
|
65
90
|
function buildInfiniteCrawlingQueryFn(queryFn, getNextPageParam, shouldFetchNextPage, reduce) {
|
|
66
91
|
return async (params, crawlOptions, context) => {
|
|
67
|
-
var _a, _b;
|
|
92
|
+
var _a, _b, _c, _d;
|
|
93
|
+
if ((_a = context.signal) == null ? void 0 : _a.aborted)
|
|
94
|
+
throw new DOMException("Aborted", "AbortError");
|
|
95
|
+
const ctx = { ...context, pageParam: context.pageParam };
|
|
96
|
+
const initialResult = queryFn(params, ctx);
|
|
97
|
+
if (isAsyncIterable(initialResult)) {
|
|
98
|
+
const pages2 = [];
|
|
99
|
+
const startParam = context.pageParam;
|
|
100
|
+
let acc2 = void 0;
|
|
101
|
+
let nextBatchParam2 = null;
|
|
102
|
+
for await (const page of initialResult) {
|
|
103
|
+
if ((_b = context.signal) == null ? void 0 : _b.aborted) break;
|
|
104
|
+
pages2.push(page);
|
|
105
|
+
acc2 = reduce(acc2, page);
|
|
106
|
+
const nextParam = getNextPageParam(page, pages2, startParam, [
|
|
107
|
+
startParam
|
|
108
|
+
]);
|
|
109
|
+
nextBatchParam2 = nextParam != null ? nextParam : null;
|
|
110
|
+
if (nextParam == null) break;
|
|
111
|
+
if (!shouldFetchNextPage(acc2, crawlOptions)) break;
|
|
112
|
+
}
|
|
113
|
+
if (acc2 === void 0) throw new DOMException("Aborted", "AbortError");
|
|
114
|
+
return { data: acc2, nextPageParam: nextBatchParam2 };
|
|
115
|
+
}
|
|
68
116
|
const pages = [];
|
|
69
117
|
const pageParams = [];
|
|
70
118
|
let currentParam = context.pageParam;
|
|
71
119
|
let acc = void 0;
|
|
72
120
|
let nextBatchParam = null;
|
|
73
|
-
|
|
121
|
+
let nextResult = initialResult;
|
|
74
122
|
while (true) {
|
|
75
|
-
|
|
76
|
-
ctx.pageParam = currentParam;
|
|
77
|
-
const page = await queryFn(params, ctx);
|
|
123
|
+
const page = await nextResult;
|
|
78
124
|
pages.push(page);
|
|
79
125
|
pageParams.push(currentParam);
|
|
80
126
|
acc = reduce(acc, page);
|
|
81
|
-
if ((_b = context.signal) == null ? void 0 : _b.aborted) break;
|
|
82
127
|
const nextParam = getNextPageParam(page, pages, currentParam, pageParams);
|
|
83
128
|
nextBatchParam = nextParam != null ? nextParam : null;
|
|
129
|
+
if ((_c = context.signal) == null ? void 0 : _c.aborted) break;
|
|
84
130
|
if (nextParam == null) break;
|
|
85
131
|
if (!shouldFetchNextPage(acc, crawlOptions)) break;
|
|
86
132
|
currentParam = nextParam;
|
|
133
|
+
if ((_d = context.signal) == null ? void 0 : _d.aborted) break;
|
|
134
|
+
ctx.pageParam = currentParam;
|
|
135
|
+
nextResult = queryFn(params, ctx);
|
|
87
136
|
}
|
|
88
137
|
if (acc === void 0) throw new DOMException("Aborted", "AbortError");
|
|
89
138
|
return { data: acc, nextPageParam: nextBatchParam };
|
|
@@ -104,8 +153,8 @@ function buildFactory(cfg) {
|
|
|
104
153
|
} = cfg;
|
|
105
154
|
const ownSegments = parentKey !== void 0 ? namespace.slice(parentKey.length) : namespace;
|
|
106
155
|
const infiniteNamespace = [...namespace, "infinite"];
|
|
107
|
-
const hasCrawling = rawQueryFn !== void 0 &&
|
|
108
|
-
const hasInfiniteCrawling = hasCrawling && reduce !== void 0;
|
|
156
|
+
const hasCrawling = rawQueryFn !== void 0 && shouldFetchNextPage !== void 0;
|
|
157
|
+
const hasInfiniteCrawling = hasCrawling && reduce !== void 0 && getNextPageParam !== void 0;
|
|
109
158
|
const crawlingFn = hasCrawling ? buildCrawlingQueryFn(
|
|
110
159
|
rawQueryFn,
|
|
111
160
|
getNextPageParam,
|
|
@@ -207,7 +256,7 @@ function queryFactory(configOrParent, childConfig) {
|
|
|
207
256
|
} : {
|
|
208
257
|
getNextPageParam: (_b = childConfig.getNextPageParam) != null ? _b : parentCfg.getNextPageParam,
|
|
209
258
|
getPreviousPageParam: (_c = childConfig.getPreviousPageParam) != null ? _c : parentCfg.getPreviousPageParam,
|
|
210
|
-
initialPageParam:
|
|
259
|
+
initialPageParam: "initialPageParam" in childConfig ? childConfig.initialPageParam : parentCfg.initialPageParam,
|
|
211
260
|
shouldFetchNextPage: (_d = childConfig.shouldFetchNextPage) != null ? _d : parentCfg.shouldFetchNextPage,
|
|
212
261
|
reduce: (_e = childConfig.reduce) != null ? _e : parentCfg.reduce
|
|
213
262
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@robohall/react-query-factory",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
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",
|
|
@@ -35,13 +35,15 @@
|
|
|
35
35
|
"test": "vitest run",
|
|
36
36
|
"test:watch": "vitest",
|
|
37
37
|
"prepublishOnly": "npm run build && npm test",
|
|
38
|
-
"sandbox": "cd sandbox && npm run dev"
|
|
38
|
+
"sandbox": "cd sandbox && npm run dev",
|
|
39
|
+
"format": "prettier --write ."
|
|
39
40
|
},
|
|
40
41
|
"peerDependencies": {
|
|
41
42
|
"@tanstack/react-query": ">=5.0.0"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
44
45
|
"@tanstack/react-query": "^5.0.0",
|
|
46
|
+
"prettier": "^3.8.3",
|
|
45
47
|
"tsup": "^8.0.0",
|
|
46
48
|
"typescript": "^5.0.0",
|
|
47
49
|
"vitest": "^2.0.0"
|