@robohall/react-query-factory 2.1.6 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -218,6 +218,23 @@ queryClient.invalidateQueries(describeInstances({ MaxResults: 20 }));
218
218
 
219
219
  ---
220
220
 
221
+ ## Which pattern?
222
+
223
+ | Pattern | Use when |
224
+ | ------------------------- | ------------------------------------------------------------------------------------------------ |
225
+ | Basic | API returns a single, non-paginated response |
226
+ | Async iterator | `queryFn` returns an `AsyncIterable` (e.g. an AWS SDK v3 paginator) — no cursor wiring required |
227
+ | Crawl-then-render | Paginated API; UI needs all data before it's useful (dropdowns, counts, totals) |
228
+ | Render-while-crawling | Paginated API; UI can show partial results as pages arrive |
229
+ | On-demand (`.infinite()`) | Paginated API; user clicks "load more" or navigates pages |
230
+ | Client-side search | Paginated API; find a subset without server-side filtering — stop crawling when condition is met |
231
+
232
+ **Async iterator** is a `queryFn` style, not a display pattern — combine it with any crawl pattern above when your SDK provides a paginator function.
233
+
234
+ **Composition** and **Invalidation** apply alongside any pattern: use composition when multiple views share one cache entry, invalidation after a mutation changes server state.
235
+
236
+ ---
237
+
221
238
  ## Installation
222
239
 
223
240
  ```bash
@@ -247,11 +264,25 @@ const { data: partial } = useQuery(
247
264
  );
248
265
  ```
249
266
 
267
+ ### Error behavior
268
+
269
+ If any page fetch throws, the error propagates immediately — there is no per-page retry or partial-result fallback. TanStack Query receives the error exactly as it would from a single-page `queryFn` and applies its normal `retry`, `throwOnError`, and error-state semantics.
270
+
271
+ When TanStack retries, the crawl starts over from `initialPageParam`. There is no resume-from-page-N.
272
+
273
+ The crawl also respects the abort signal between pages. When the signal fires (component unmounts, query superseded by a newer one), the loop exits after the current in-flight page completes. TanStack does not commit the partial result.
274
+
250
275
  ---
251
276
 
252
277
  ## Async iterator queryFns
253
278
 
254
- When `queryFn` returns an `AsyncIterable`, the library 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:
279
+ When `queryFn` returns an `AsyncIterable`, the library walks it with `for await...of` instead of calling `queryFn` repeatedly with successive `pageParam` values. The cursor lives inside the iterator rather than in `getNextPageParam` that's the only meaningful difference from a cursor-based factory. `shouldFetchNextPage`, `reduce`, `crawlOptions`, and `.infinite()` all work identically.
280
+
281
+ One caveat for `.infinite()`: `getNextPageParam` is still required, but its role shifts — instead of wiring each individual API page, it records where the next batch should start when the user loads more.
282
+
283
+ Without `shouldFetchNextPage`, the library exhausts the iterator on every call — every page, every time.
284
+
285
+ Any source of `AsyncIterable<TPage>` works:
255
286
 
256
287
  ```typescript
257
288
  import { paginateDescribeInstances } from '@aws-sdk/client-ec2';
@@ -273,9 +304,7 @@ const describeInstances = queryFactory({
273
304
  });
274
305
  ```
275
306
 
276
- `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.
277
-
278
- For `.infinite()` mode, `getNextPageParam` is required to capture the next virtual page's starting cursor from the last yielded item. AWS SDK v3 paginators accept a `startingToken` to resume from a specific position — wire `ctx.pageParam` to it:
307
+ For `.infinite()`, wire `ctx.pageParam` to the iterator's resume parameter so each batch starts from the right position:
279
308
 
280
309
  ```typescript
281
310
  const describeInstances = queryFactory({
@@ -388,6 +417,24 @@ The `.infinite()` key includes an `'infinite'` segment to keep it separate from
388
417
 
389
418
  ---
390
419
 
420
+ ## Performance
421
+
422
+ TanStack Query's default `staleTime` is `0` — data is considered stale immediately, so a background refetch fires on every mount, window focus, and reconnect. For a single-page query that's one API call; for a crawling factory it's the full crawl repeated. Set `staleTime` in the factory config to match how often the underlying data actually changes:
423
+
424
+ ```typescript
425
+ const describeInstances = queryFactory({
426
+ queryKey: ['ec2:DescribeInstances'],
427
+ staleTime: 60_000, // re-crawl at most once per minute
428
+ // ...
429
+ });
430
+ ```
431
+
432
+ Child factories inherit `staleTime` and all other standard options from the parent, so setting it once on the root factory covers every derived view.
433
+
434
+ When freshness requirements allow it, `refetchOnWindowFocus` and `refetchOnMount` can be set to `false` on the factory for the same reason — each is a potential full re-crawl.
435
+
436
+ ---
437
+
391
438
  ## Public API
392
439
 
393
440
  ### `queryFactory(config)`
@@ -440,6 +487,26 @@ Return type of `factory(params)`. Pass directly to `useQuery()`. Contains an `in
440
487
 
441
488
  Return type of `factory.infinite(params)`. Pass directly to `useInfiniteQuery()`. The `select` field is typed to `InfiniteData<TData, TPageParam>`, which prevents accidental use with `useQuery`.
442
489
 
490
+ ### `FactoryParams<F>`
491
+
492
+ Extracts the params type from a factory — the first argument of a factory call. Useful for typing component props that accept factory params.
493
+
494
+ ```typescript
495
+ import type { FactoryParams } from '@robohall/react-query-factory';
496
+
497
+ type Params = FactoryParams<typeof describeInstances>; // → DescribeInstancesRequest
498
+ ```
499
+
500
+ ### `FactoryCrawlOptions<F>`
501
+
502
+ Extracts the crawl options type from a factory — the second argument of a factory call. Useful for typing helpers or components that accept crawl options.
503
+
504
+ ```typescript
505
+ import type { FactoryCrawlOptions } from '@robohall/react-query-factory';
506
+
507
+ type CrawlOpts = FactoryCrawlOptions<typeof describeInstances>; // → { minResults?: number }
508
+ ```
509
+
443
510
  ---
444
511
 
445
512
  ## Running the sandbox
package/dist/index.d.mts CHANGED
@@ -100,6 +100,10 @@ interface QueryFactory<TParams = void, TData = unknown, TError = Error, TSelecte
100
100
  (params?: TParams, crawlOptions?: TCrawlOptions): ResolvedQueryOptions<TData, TError, TSelected>;
101
101
  infinite(params?: TParams, crawlOptions?: TCrawlOptions): ResolvedInfiniteOptions<TData, TError, TPageParam, InfiniteData<TSelected, TPageParam>>;
102
102
  }
103
+ /** Extracts the params type from a factory — the first argument of a factory call. */
104
+ type FactoryParams<F> = F extends QueryFactory<infer TParams, any, any, any, any, any, any> ? TParams : never;
105
+ /** Extracts the crawl options type from a factory — the second argument of a factory call. */
106
+ type FactoryCrawlOptions<F> = F extends QueryFactory<any, any, any, any, any, infer TCrawlOptions, any> ? TCrawlOptions : never;
103
107
  /**
104
108
  * Creates a standalone query factory with pagination and reduce. When `reduce` is
105
109
  * present, `shouldFetchNextPage` receives `TSelected` (never undefined) because
@@ -208,4 +212,4 @@ declare function queryFactory<TParams = void, TData = unknown, TError = Error, T
208
212
  shouldFetchNextPage: (combined: TSelected | undefined, crawlOptions: TCrawlOptions) => boolean;
209
213
  }): QueryFactory<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions, false>;
210
214
 
211
- export { type QueryFactory, type QueryFactoryConfig, type ResolvedInfiniteOptions, type ResolvedQueryOptions, type StandardQueryOptions, queryFactory };
215
+ export { type FactoryCrawlOptions, type FactoryParams, type QueryFactory, type QueryFactoryConfig, type ResolvedInfiniteOptions, type ResolvedQueryOptions, type StandardQueryOptions, queryFactory };
package/dist/index.d.ts CHANGED
@@ -100,6 +100,10 @@ interface QueryFactory<TParams = void, TData = unknown, TError = Error, TSelecte
100
100
  (params?: TParams, crawlOptions?: TCrawlOptions): ResolvedQueryOptions<TData, TError, TSelected>;
101
101
  infinite(params?: TParams, crawlOptions?: TCrawlOptions): ResolvedInfiniteOptions<TData, TError, TPageParam, InfiniteData<TSelected, TPageParam>>;
102
102
  }
103
+ /** Extracts the params type from a factory — the first argument of a factory call. */
104
+ type FactoryParams<F> = F extends QueryFactory<infer TParams, any, any, any, any, any, any> ? TParams : never;
105
+ /** Extracts the crawl options type from a factory — the second argument of a factory call. */
106
+ type FactoryCrawlOptions<F> = F extends QueryFactory<any, any, any, any, any, infer TCrawlOptions, any> ? TCrawlOptions : never;
103
107
  /**
104
108
  * Creates a standalone query factory with pagination and reduce. When `reduce` is
105
109
  * present, `shouldFetchNextPage` receives `TSelected` (never undefined) because
@@ -208,4 +212,4 @@ declare function queryFactory<TParams = void, TData = unknown, TError = Error, T
208
212
  shouldFetchNextPage: (combined: TSelected | undefined, crawlOptions: TCrawlOptions) => boolean;
209
213
  }): QueryFactory<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions, false>;
210
214
 
211
- export { type QueryFactory, type QueryFactoryConfig, type ResolvedInfiniteOptions, type ResolvedQueryOptions, type StandardQueryOptions, queryFactory };
215
+ export { type FactoryCrawlOptions, type FactoryParams, type QueryFactory, type QueryFactoryConfig, type ResolvedInfiniteOptions, type ResolvedQueryOptions, type StandardQueryOptions, queryFactory };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robohall/react-query-factory",
3
- "version": "2.1.6",
3
+ "version": "2.2.0",
4
4
  "description": "A factory abstraction for TanStack Query (React Query) with composable keys, crawling support, and automatic infinite query generation",
5
5
  "author": "Robert Hall",
6
6
  "license": "MIT",