@robohall/react-query-factory 2.0.0 → 2.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/README.md +228 -65
- package/dist/index.d.mts +34 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +62 -16
- package/dist/index.mjs +62 -16
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -5,105 +5,270 @@
|
|
|
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 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, but the key only exists inside the hook. Prefetching in a route loader, invalidating after a mutation, or fetching imperatively all require knowing `['instances', params]` without calling the hook.
|
|
22
34
|
|
|
23
|
-
|
|
35
|
+
### Step 2 — `queryOptions` for colocation
|
|
24
36
|
|
|
25
|
-
|
|
37
|
+
TanStack's `queryOptions` helper moves the key and fn into a shared object:
|
|
26
38
|
|
|
27
39
|
```typescript
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
40
|
+
const instancesOptions = (params: DescribeInstancesCommandInput) =>
|
|
41
|
+
queryOptions({
|
|
42
|
+
queryKey: ['instances', params],
|
|
43
|
+
queryFn: () => fetchInstances(params),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
useQuery(instancesOptions(params));
|
|
47
|
+
queryClient.prefetchQuery(instancesOptions(params));
|
|
48
|
+
queryClient.invalidateQueries(instancesOptions(params));
|
|
49
|
+
```
|
|
35
50
|
|
|
36
|
-
|
|
51
|
+
This is genuinely good — this library builds on the same pattern. But once you need multiple related queries, the cracks show.
|
|
37
52
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
53
|
+
### Step 3 — derived queries and key coordination
|
|
54
|
+
|
|
55
|
+
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`:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const { data: running } = useQuery({
|
|
59
|
+
...instancesOptions(params),
|
|
60
|
+
select: data => data.filter(i => i.state === 'running'),
|
|
42
61
|
});
|
|
62
|
+
```
|
|
43
63
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
64
|
+
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:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// If the key structure ever changes, every site breaks.
|
|
68
|
+
queryClient.invalidateQueries({ queryKey: ['instances'] });
|
|
50
69
|
```
|
|
51
70
|
|
|
52
|
-
|
|
71
|
+
### Step 4 — paginated APIs
|
|
53
72
|
|
|
54
|
-
|
|
73
|
+
`DescribeInstances` returns at most `MaxResults` instances per call. To get them all, you need to loop. The usual options:
|
|
55
74
|
|
|
56
|
-
|
|
75
|
+
**Put the loop in `queryFn`:**
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
const instancesOptions = params =>
|
|
79
|
+
queryOptions({
|
|
80
|
+
queryKey: ['instances', params],
|
|
81
|
+
queryFn: async () => {
|
|
82
|
+
let all: Instance[] = [];
|
|
83
|
+
let nextToken: string | undefined;
|
|
84
|
+
do {
|
|
85
|
+
const page = await ec2.send(
|
|
86
|
+
new DescribeInstancesCommand({ ...params, NextToken: nextToken }),
|
|
87
|
+
);
|
|
88
|
+
all = [
|
|
89
|
+
...all,
|
|
90
|
+
...(page.Reservations?.flatMap(r => r.Instances ?? []) ?? []),
|
|
91
|
+
];
|
|
92
|
+
nextToken = page.NextToken;
|
|
93
|
+
} while (nextToken);
|
|
94
|
+
return all;
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
```
|
|
57
98
|
|
|
58
|
-
|
|
99
|
+
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
100
|
|
|
60
|
-
|
|
101
|
+
**Use `useInfiniteQuery`:**
|
|
61
102
|
|
|
62
103
|
```typescript
|
|
63
|
-
|
|
104
|
+
const instancesInfiniteOptions = params =>
|
|
105
|
+
infiniteQueryOptions({
|
|
106
|
+
queryKey: ['instances', 'infinite', params],
|
|
107
|
+
queryFn: ({ pageParam }) =>
|
|
108
|
+
ec2.send(
|
|
109
|
+
new DescribeInstancesCommand({ ...params, NextToken: pageParam }),
|
|
110
|
+
),
|
|
111
|
+
getNextPageParam: r => r.NextToken,
|
|
112
|
+
initialPageParam: undefined,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Caller still has to flatten, auto-advance, manage hasNextPage...
|
|
116
|
+
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery(
|
|
117
|
+
instancesInfiniteOptions(params),
|
|
118
|
+
);
|
|
119
|
+
const allInstances = data?.pages.flatMap(
|
|
120
|
+
page => page.Reservations?.flatMap(r => r.Instances ?? []) ?? [],
|
|
121
|
+
);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
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.
|
|
125
|
+
|
|
126
|
+
### What's missing
|
|
127
|
+
|
|
128
|
+
- Define the query **once**: key, queryFn, pagination config
|
|
129
|
+
- Let each **call site** decide how much to crawl (e.g. 50 records or all of them)
|
|
130
|
+
- Optionally have `useQuery` crawl and return the **accumulated result** instead of a single page
|
|
131
|
+
- Use **async iterables** as `queryFn` — pass a paginator function directly, no cursor wiring required
|
|
132
|
+
- Have `.infinite()` available on the **same factory**, no duplication
|
|
133
|
+
- Have derived queries **share the cache entry** automatically
|
|
134
|
+
- Have **scoped invalidation** through key composition — bust the whole namespace or just one param set and its children
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## The solution
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import { queryFactory } from '@robohall/react-query-factory';
|
|
64
142
|
|
|
65
143
|
const describeInstances = queryFactory({
|
|
66
144
|
queryKey: ['ec2:DescribeInstances'],
|
|
67
145
|
queryFn: (params: DescribeInstancesCommandInput, ctx) =>
|
|
68
146
|
ec2.send(
|
|
69
147
|
new DescribeInstancesCommand({ ...params, NextToken: ctx.pageParam }),
|
|
70
|
-
{
|
|
148
|
+
{
|
|
149
|
+
abortSignal: ctx.signal,
|
|
150
|
+
},
|
|
71
151
|
),
|
|
72
|
-
getNextPageParam:
|
|
152
|
+
getNextPageParam: r => r.NextToken,
|
|
73
153
|
initialPageParam: undefined as string | undefined,
|
|
74
|
-
shouldFetchNextPage: () => true,
|
|
75
154
|
reduce: (acc, page): Instance[] => [
|
|
76
155
|
...(acc ?? []),
|
|
77
156
|
...(page.Reservations?.flatMap(r => r.Instances ?? []) ?? []),
|
|
78
157
|
],
|
|
158
|
+
shouldFetchNextPage: (instances, opts: { minResults?: number }) =>
|
|
159
|
+
opts.minResults == null || instances.length < opts.minResults,
|
|
79
160
|
});
|
|
80
161
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
162
|
+
// useQuery — crawls all pages, data is Instance[]
|
|
163
|
+
const { data } = useQuery(describeInstances({ MaxResults: 20 }));
|
|
164
|
+
|
|
165
|
+
// Stop at 50 — separate cache entry, independent crawl
|
|
166
|
+
const { data } = useQuery(
|
|
167
|
+
describeInstances({ MaxResults: 20 }, { minResults: 50 }),
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// UI-driven pagination — same factory, no duplication
|
|
171
|
+
const { data, fetchNextPage } = useInfiniteQuery(
|
|
172
|
+
describeInstances.infinite({ MaxResults: 20 }, { minResults: 50 }),
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
// Derived view — shares the cache entry, no extra API call
|
|
176
|
+
const runningInstances = queryFactory(describeInstances, {
|
|
177
|
+
select: instances => instances.filter(i => i.State?.Name === 'running'),
|
|
178
|
+
});
|
|
179
|
+
const { data: running } = useQuery(runningInstances({ MaxResults: 20 }));
|
|
180
|
+
|
|
181
|
+
// Prefetch in a route loader
|
|
182
|
+
await queryClient.prefetchQuery(describeInstances({ MaxResults: 20 }));
|
|
183
|
+
|
|
184
|
+
// Bust everything in the namespace
|
|
185
|
+
queryClient.invalidateQueries(describeInstances());
|
|
186
|
+
|
|
187
|
+
// Bust only this param set — cascades to runningInstances and any other child
|
|
188
|
+
queryClient.invalidateQueries(describeInstances({ MaxResults: 20 }));
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
`describeInstances({ ... })` returns a plain `{ queryKey, queryFn, ... }` object — pass it directly to `useQuery`, `useInfiniteQuery`, `prefetchQuery`, or `getQueryData`. The factory doesn't touch your query client.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Installation
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
npm install @robohall/react-query-factory
|
|
199
|
+
# peer dependency: @tanstack/react-query >= 5.0.0
|
|
85
200
|
```
|
|
86
201
|
|
|
87
|
-
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Crawling
|
|
205
|
+
|
|
206
|
+
`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[]`).
|
|
207
|
+
|
|
208
|
+
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
209
|
|
|
89
210
|
```typescript
|
|
90
211
|
const describeInstances = queryFactory({
|
|
91
212
|
// ...
|
|
92
|
-
reduce: (acc, page): Instance[] => [...(acc ?? []), ...page.Reservations.flatMap(r => r.Instances)],
|
|
93
213
|
shouldFetchNextPage: (instances, opts: { minResults?: number }) =>
|
|
94
214
|
opts.minResults == null || instances.length < opts.minResults,
|
|
95
215
|
});
|
|
96
216
|
|
|
97
|
-
//
|
|
217
|
+
// two separate cache entries — crawl independently
|
|
98
218
|
const { data: all } = useQuery(describeInstances({ MaxResults: 20 }));
|
|
99
|
-
|
|
100
|
-
// stop after accumulating at least 50 instances (≥ 3 API calls)
|
|
101
219
|
const { data: partial } = useQuery(
|
|
102
|
-
describeInstances({ MaxResults: 20 }, { minResults: 50 })
|
|
220
|
+
describeInstances({ MaxResults: 20 }, { minResults: 50 }),
|
|
103
221
|
);
|
|
104
222
|
```
|
|
105
223
|
|
|
106
|
-
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Async iterator queryFns
|
|
227
|
+
|
|
228
|
+
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:
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
import { paginateDescribeInstances } from '@aws-sdk/client-ec2';
|
|
232
|
+
|
|
233
|
+
const describeInstances = queryFactory({
|
|
234
|
+
queryKey: ['ec2:DescribeInstances'],
|
|
235
|
+
queryFn: (params: DescribeInstancesCommandInput) =>
|
|
236
|
+
paginateDescribeInstances({ client: ec2 }, params),
|
|
237
|
+
shouldFetchNextPage: (instances, opts: { minResults?: number }) =>
|
|
238
|
+
opts.minResults == null || instances.length < opts.minResults,
|
|
239
|
+
reduce: (acc, page: DescribeInstancesResponse): Instance[] => [
|
|
240
|
+
...(acc ?? []),
|
|
241
|
+
...(page.Reservations?.flatMap(r => r.Instances ?? []) ?? []),
|
|
242
|
+
],
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
`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.
|
|
247
|
+
|
|
248
|
+
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:
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
const describeInstances = queryFactory({
|
|
252
|
+
queryKey: ['ec2:DescribeInstances'],
|
|
253
|
+
queryFn: (params: DescribeInstancesCommandInput, ctx) =>
|
|
254
|
+
paginateDescribeInstances(
|
|
255
|
+
{ client: ec2 },
|
|
256
|
+
{ ...params, StartingToken: ctx.pageParam },
|
|
257
|
+
),
|
|
258
|
+
getNextPageParam: page => page.NextToken,
|
|
259
|
+
initialPageParam: undefined as string | undefined,
|
|
260
|
+
shouldFetchNextPage: (instances, opts: { minResults?: number }) =>
|
|
261
|
+
opts.minResults == null || instances.length < opts.minResults,
|
|
262
|
+
reduce: (acc, page): Instance[] => [
|
|
263
|
+
...(acc ?? []),
|
|
264
|
+
...(page.Instances ?? []),
|
|
265
|
+
],
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const { data, fetchNextPage } = useInfiniteQuery(
|
|
269
|
+
describeInstances.infinite({ MaxResults: 20 }, { minResults: 50 }),
|
|
270
|
+
);
|
|
271
|
+
```
|
|
107
272
|
|
|
108
273
|
---
|
|
109
274
|
|
|
@@ -139,7 +304,7 @@ const findInstance = queryFactory(describeInstances, {
|
|
|
139
304
|
// query key: ['ec2:DescribeInstances', { MaxResults: 20 }, 'find', { instanceId: 'i-0abc123def456' }]
|
|
140
305
|
// crawls pages until the target instance appears, then stops
|
|
141
306
|
const { data } = useQuery(
|
|
142
|
-
findInstance({ MaxResults: 20 }, { instanceId: 'i-0abc123def456' })
|
|
307
|
+
findInstance({ MaxResults: 20 }, { instanceId: 'i-0abc123def456' }),
|
|
143
308
|
);
|
|
144
309
|
```
|
|
145
310
|
|
|
@@ -180,18 +345,17 @@ Every factory exposes a `.infinite()` method that returns `useInfiniteQuery`-com
|
|
|
180
345
|
```typescript
|
|
181
346
|
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery(
|
|
182
347
|
// load 50 instances per UI page, each backed by up to 5 DescribeInstances calls
|
|
183
|
-
describeInstances.infinite({ MaxResults: 20 }, { minResults: 50 })
|
|
348
|
+
describeInstances.infinite({ MaxResults: 20 }, { minResults: 50 }),
|
|
184
349
|
);
|
|
185
350
|
|
|
186
351
|
// data.pages is Instance[][], one array per virtual page
|
|
187
352
|
```
|
|
188
353
|
|
|
189
354
|
The `.infinite()` key includes an `'infinite'` segment to keep it separate from the regular `useQuery` cache entry:
|
|
355
|
+
|
|
190
356
|
- `describeInstances({ MaxResults: 20 })` → `['ec2:DescribeInstances', { MaxResults: 20 }]`
|
|
191
357
|
- `describeInstances.infinite({ MaxResults: 20 })` → `['ec2:DescribeInstances', 'infinite', { MaxResults: 20 }]`
|
|
192
358
|
|
|
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
359
|
---
|
|
196
360
|
|
|
197
361
|
## Public API
|
|
@@ -209,6 +373,7 @@ queryFactory<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions>(
|
|
|
209
373
|
### `queryFactory(parent, config)`
|
|
210
374
|
|
|
211
375
|
Creates a child factory. Two overloads:
|
|
376
|
+
|
|
212
377
|
- **With a new `queryFn`** — inherits key namespace and standard options; crawling config must be re-declared if needed.
|
|
213
378
|
- **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
379
|
|
|
@@ -216,17 +381,17 @@ Creates a child factory. Two overloads:
|
|
|
216
381
|
|
|
217
382
|
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
383
|
|
|
219
|
-
| Field
|
|
220
|
-
|
|
221
|
-
| `queryKey`
|
|
222
|
-
| `queryFn`
|
|
223
|
-
| `select`
|
|
224
|
-
| `getNextPageParam`
|
|
225
|
-
| `initialPageParam`
|
|
226
|
-
| `getPreviousPageParam`
|
|
227
|
-
| `shouldFetchNextPage`
|
|
228
|
-
| `reduce`
|
|
229
|
-
| + all `StandardQueryOptions` fields |
|
|
384
|
+
| Field | Type | Notes |
|
|
385
|
+
| ----------------------------------- | ------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
386
|
+
| `queryKey` | `QueryKey` | Namespace segments. Params are appended at call time. |
|
|
387
|
+
| `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. |
|
|
388
|
+
| `select` | `(data: TData) => TSelected` | Exact TanStack API. Composed automatically on child factories. |
|
|
389
|
+
| `getNextPageParam` | `GetNextPageParamFunction<TPageParam, TData>` | Exact TanStack API. Required (with `shouldFetchNextPage`) to activate cursor-based crawling. Required (with `initialPageParam`) for `.infinite()`. |
|
|
390
|
+
| `initialPageParam` | `TPageParam` | Exact TanStack API. Drives `TPageParam` inference. Required for `.infinite()` to work at runtime. |
|
|
391
|
+
| `getPreviousPageParam` | `GetPreviousPageParamFunction<TPageParam, TData>` | Exact TanStack API. Passed through on `.infinite()`. |
|
|
392
|
+
| `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. |
|
|
393
|
+
| `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[]`). |
|
|
394
|
+
| + all `StandardQueryOptions` fields | | `staleTime`, `gcTime`, `retry`, `enabled`, `refetchOnWindowFocus`, `placeholderData`, `initialData`, `meta`, etc. Function-form callbacks are supported wherever TanStack accepts them. |
|
|
230
395
|
|
|
231
396
|
### `QueryFactory<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions>`
|
|
232
397
|
|
|
@@ -249,10 +414,8 @@ Return type of `factory.infinite(params)`. Pass directly to `useInfiniteQuery()`
|
|
|
249
414
|
|
|
250
415
|
## Running the sandbox
|
|
251
416
|
|
|
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
417
|
```bash
|
|
255
418
|
npm run sandbox
|
|
256
419
|
```
|
|
257
420
|
|
|
258
|
-
|
|
421
|
+
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,47 @@ 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
|
+
let acc2 = void 0;
|
|
126
|
+
let nextBatchParam2 = null;
|
|
127
|
+
for await (const page of initialResult) {
|
|
128
|
+
if ((_b = context.signal) == null ? void 0 : _b.aborted) break;
|
|
129
|
+
pages2.push(page);
|
|
130
|
+
acc2 = reduce(acc2, page);
|
|
131
|
+
const nextParam = getNextPageParam(page, pages2, void 0, []);
|
|
132
|
+
nextBatchParam2 = nextParam != null ? nextParam : null;
|
|
133
|
+
if (nextParam == null) break;
|
|
134
|
+
if (!shouldFetchNextPage(acc2, crawlOptions)) break;
|
|
135
|
+
}
|
|
136
|
+
if (acc2 === void 0) throw new DOMException("Aborted", "AbortError");
|
|
137
|
+
return { data: acc2, nextPageParam: nextBatchParam2 };
|
|
138
|
+
}
|
|
94
139
|
const pages = [];
|
|
95
140
|
const pageParams = [];
|
|
96
141
|
let currentParam = context.pageParam;
|
|
97
142
|
let acc = void 0;
|
|
98
143
|
let nextBatchParam = null;
|
|
99
|
-
|
|
144
|
+
let nextResult = initialResult;
|
|
100
145
|
while (true) {
|
|
101
|
-
|
|
102
|
-
ctx.pageParam = currentParam;
|
|
103
|
-
const page = await queryFn(params, ctx);
|
|
146
|
+
const page = await nextResult;
|
|
104
147
|
pages.push(page);
|
|
105
148
|
pageParams.push(currentParam);
|
|
106
149
|
acc = reduce(acc, page);
|
|
107
|
-
if ((
|
|
150
|
+
if ((_c = context.signal) == null ? void 0 : _c.aborted) break;
|
|
108
151
|
const nextParam = getNextPageParam(page, pages, currentParam, pageParams);
|
|
109
152
|
nextBatchParam = nextParam != null ? nextParam : null;
|
|
110
153
|
if (nextParam == null) break;
|
|
111
154
|
if (!shouldFetchNextPage(acc, crawlOptions)) break;
|
|
112
155
|
currentParam = nextParam;
|
|
156
|
+
if ((_d = context.signal) == null ? void 0 : _d.aborted) break;
|
|
157
|
+
ctx.pageParam = currentParam;
|
|
158
|
+
nextResult = queryFn(params, ctx);
|
|
113
159
|
}
|
|
114
160
|
if (acc === void 0) throw new DOMException("Aborted", "AbortError");
|
|
115
161
|
return { data: acc, nextPageParam: nextBatchParam };
|
|
@@ -130,8 +176,8 @@ function buildFactory(cfg) {
|
|
|
130
176
|
} = cfg;
|
|
131
177
|
const ownSegments = parentKey !== void 0 ? namespace.slice(parentKey.length) : namespace;
|
|
132
178
|
const infiniteNamespace = [...namespace, "infinite"];
|
|
133
|
-
const hasCrawling = rawQueryFn !== void 0 &&
|
|
134
|
-
const hasInfiniteCrawling = hasCrawling && reduce !== void 0;
|
|
179
|
+
const hasCrawling = rawQueryFn !== void 0 && shouldFetchNextPage !== void 0;
|
|
180
|
+
const hasInfiniteCrawling = hasCrawling && reduce !== void 0 && getNextPageParam !== void 0;
|
|
135
181
|
const crawlingFn = hasCrawling ? buildCrawlingQueryFn(
|
|
136
182
|
rawQueryFn,
|
|
137
183
|
getNextPageParam,
|
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,47 @@ 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
|
+
let acc2 = void 0;
|
|
100
|
+
let nextBatchParam2 = null;
|
|
101
|
+
for await (const page of initialResult) {
|
|
102
|
+
if ((_b = context.signal) == null ? void 0 : _b.aborted) break;
|
|
103
|
+
pages2.push(page);
|
|
104
|
+
acc2 = reduce(acc2, page);
|
|
105
|
+
const nextParam = getNextPageParam(page, pages2, void 0, []);
|
|
106
|
+
nextBatchParam2 = nextParam != null ? nextParam : null;
|
|
107
|
+
if (nextParam == null) break;
|
|
108
|
+
if (!shouldFetchNextPage(acc2, crawlOptions)) break;
|
|
109
|
+
}
|
|
110
|
+
if (acc2 === void 0) throw new DOMException("Aborted", "AbortError");
|
|
111
|
+
return { data: acc2, nextPageParam: nextBatchParam2 };
|
|
112
|
+
}
|
|
68
113
|
const pages = [];
|
|
69
114
|
const pageParams = [];
|
|
70
115
|
let currentParam = context.pageParam;
|
|
71
116
|
let acc = void 0;
|
|
72
117
|
let nextBatchParam = null;
|
|
73
|
-
|
|
118
|
+
let nextResult = initialResult;
|
|
74
119
|
while (true) {
|
|
75
|
-
|
|
76
|
-
ctx.pageParam = currentParam;
|
|
77
|
-
const page = await queryFn(params, ctx);
|
|
120
|
+
const page = await nextResult;
|
|
78
121
|
pages.push(page);
|
|
79
122
|
pageParams.push(currentParam);
|
|
80
123
|
acc = reduce(acc, page);
|
|
81
|
-
if ((
|
|
124
|
+
if ((_c = context.signal) == null ? void 0 : _c.aborted) break;
|
|
82
125
|
const nextParam = getNextPageParam(page, pages, currentParam, pageParams);
|
|
83
126
|
nextBatchParam = nextParam != null ? nextParam : null;
|
|
84
127
|
if (nextParam == null) break;
|
|
85
128
|
if (!shouldFetchNextPage(acc, crawlOptions)) break;
|
|
86
129
|
currentParam = nextParam;
|
|
130
|
+
if ((_d = context.signal) == null ? void 0 : _d.aborted) break;
|
|
131
|
+
ctx.pageParam = currentParam;
|
|
132
|
+
nextResult = queryFn(params, ctx);
|
|
87
133
|
}
|
|
88
134
|
if (acc === void 0) throw new DOMException("Aborted", "AbortError");
|
|
89
135
|
return { data: acc, nextPageParam: nextBatchParam };
|
|
@@ -104,8 +150,8 @@ function buildFactory(cfg) {
|
|
|
104
150
|
} = cfg;
|
|
105
151
|
const ownSegments = parentKey !== void 0 ? namespace.slice(parentKey.length) : namespace;
|
|
106
152
|
const infiniteNamespace = [...namespace, "infinite"];
|
|
107
|
-
const hasCrawling = rawQueryFn !== void 0 &&
|
|
108
|
-
const hasInfiniteCrawling = hasCrawling && reduce !== void 0;
|
|
153
|
+
const hasCrawling = rawQueryFn !== void 0 && shouldFetchNextPage !== void 0;
|
|
154
|
+
const hasInfiniteCrawling = hasCrawling && reduce !== void 0 && getNextPageParam !== void 0;
|
|
109
155
|
const crawlingFn = hasCrawling ? buildCrawlingQueryFn(
|
|
110
156
|
rawQueryFn,
|
|
111
157
|
getNextPageParam,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@robohall/react-query-factory",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.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",
|
|
@@ -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"
|