@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 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, 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.
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 or all of them)
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({ ...params, NextToken: ctx.pageParam }),
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 == null || instances.length < 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 == null || instances.length < 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 == null || instances.length < 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 == null || instances.length < 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, void 0, []);
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: childConfig.initialPageParam !== void 0 ? childConfig.initialPageParam : parentCfg.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, void 0, []);
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: childConfig.initialPageParam !== void 0 ? childConfig.initialPageParam : parentCfg.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.0",
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",