@robohall/react-query-factory 1.0.5 → 1.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
@@ -38,7 +38,6 @@ const describeInstances = queryFactory({
38
38
  queryKey: ['ec2:DescribeInstances'],
39
39
  queryFn: (params: DescribeInstancesCommandInput, ctx) =>
40
40
  ec2.send(new DescribeInstancesCommand(params), { abortSignal: ctx.signal }),
41
- staleTime: 30_000,
42
41
  });
43
42
 
44
43
  function InstanceList() {
@@ -46,7 +45,6 @@ function InstanceList() {
46
45
  describeInstances({ Filters: [{ Name: 'instance-state-name', Values: ['running'] }] })
47
46
  );
48
47
  // query key: ['ec2:DescribeInstances', { Filters: [...] }]
49
- // staleTime: 30 000 ms, no repetition required
50
48
  }
51
49
  ```
52
50
 
@@ -56,9 +54,9 @@ function InstanceList() {
56
54
 
57
55
  ## Crawling
58
56
 
59
- `DescribeInstances` is paginated. If you have more than 1000 instances, one call won't get them all. The standard approach — chaining `fetchNextPage` calls, accumulating results, checking `NextToken` — is correct but tedious to repeat everywhere.
57
+ `DescribeInstances` is paginated. If you have more than 20 instances, one call won't get them all. The standard approach — chaining `fetchNextPage` calls, accumulating results, checking `NextToken` — is correct but tedious to repeat everywhere.
60
58
 
61
- Add `getNextPageParam`, `initialPageParam`, and `reduce` to your factory config, and the generated `queryFn` will walk every page automatically, reducing them into a single flat array:
59
+ Add `getNextPageParam` and `shouldFetchNextPage` to activate crawling those two are the only required pieces. `initialPageParam` types `ctx.pageParam` in your `queryFn` (without it and without a `getNextPageParam` that provides inference, `ctx.pageParam` is `never`). `reduce` folds crawled pages into a single value; without it the result is an array of all fetched raw pages (`TData[]`). **`shouldFetchNextPage`** is called after each page — return `true` to keep fetching, `false` to stop. Use `() => true` to walk every page:
62
60
 
63
61
  ```typescript
64
62
  import type { Instance, DescribeInstancesCommandInput } from '@aws-sdk/client-ec2';
@@ -72,32 +70,35 @@ const describeInstances = queryFactory({
72
70
  ),
73
71
  getNextPageParam: response => response.NextToken,
74
72
  initialPageParam: undefined as string | undefined,
73
+ shouldFetchNextPage: () => true,
75
74
  reduce: (acc, page): Instance[] => [
76
75
  ...(acc ?? []),
77
76
  ...(page.Reservations?.flatMap(r => r.Instances ?? []) ?? []),
78
77
  ],
79
- staleTime: 30_000,
80
78
  });
81
79
 
82
80
  function InstanceList() {
83
81
  // one useQuery call; data is Instance[], not DescribeInstancesResponse[]
84
- const { data } = useQuery(describeInstances({ MaxResults: 1000 }));
82
+ const { data } = useQuery(describeInstances({ MaxResults: 20 }));
85
83
  }
86
84
  ```
87
85
 
88
- **`shouldFetchNextPage`** lets a call site stop the crawl early based on what has been accumulated so far. The arguments are the current reduced value and a `crawlOptions` object passed at call time. When `reduce` is present, `combined` is typed as `TSelected` (never undefined) because `reduce` always runs before this callback:
86
+ `shouldFetchNextPage` also accepts a `crawlOptions` object passed at call time, letting each call site control the crawl independently:
89
87
 
90
88
  ```typescript
91
89
  const describeInstances = queryFactory({
92
90
  // ...
93
91
  reduce: (acc, page): Instance[] => [...(acc ?? []), ...page.Reservations.flatMap(r => r.Instances)],
94
92
  shouldFetchNextPage: (instances, opts: { minResults?: number }) =>
95
- opts.minResults != null && instances.length < opts.minResults,
93
+ opts.minResults == null || instances.length < opts.minResults,
96
94
  });
97
95
 
98
- // stop after accumulating at least 50 instances
99
- const { data } = useQuery(
100
- describeInstances({ MaxResults: 1000 }, { minResults: 50 })
96
+ // fetch all pages
97
+ const { data: all } = useQuery(describeInstances({ MaxResults: 20 }));
98
+
99
+ // stop after accumulating at least 50 instances (≥ 3 API calls)
100
+ const { data: partial } = useQuery(
101
+ describeInstances({ MaxResults: 20 }, { minResults: 50 })
101
102
  );
102
103
  ```
103
104
 
@@ -116,9 +117,9 @@ const runningInstances = queryFactory(describeInstances, {
116
117
  select: instances => instances.filter(i => i.State?.Name === 'running'),
117
118
  });
118
119
 
119
- // query key: ['ec2:DescribeInstances', { MaxResults: 1000 }] (same cache entry as parent)
120
+ // query key: ['ec2:DescribeInstances', { MaxResults: 20 }] (same cache entry as parent)
120
121
  // data: Instance[] filtered to State.Name === 'running'
121
- const { data } = useQuery(runningInstances({ MaxResults: 1000 }));
122
+ const { data } = useQuery(runningInstances({ MaxResults: 20 }));
122
123
  ```
123
124
 
124
125
  Parent and child `select` functions compose automatically — if the parent already has a `select`, the child's `select` receives the parent's output, not the raw API response.
@@ -131,13 +132,13 @@ const findInstance = queryFactory(describeInstances, {
131
132
  // queryFn, getNextPageParam, initialPageParam, and reduce are all inherited
132
133
  shouldFetchNextPage: (instances, opts: { instanceId?: string }) =>
133
134
  opts.instanceId != null &&
134
- !instances?.some(i => i.InstanceId === opts.instanceId),
135
+ !instances.some(i => i.InstanceId === opts.instanceId),
135
136
  });
136
137
 
137
- // query key: ['ec2:DescribeInstances', 'find', { MaxResults: 100 }, { instanceId: 'i-0abc123' }]
138
+ // query key: ['ec2:DescribeInstances', 'find', { MaxResults: 20 }, { instanceId: 'i-0abc123def456' }]
138
139
  // crawls pages until the target instance appears, then stops
139
140
  const { data } = useQuery(
140
- findInstance({ MaxResults: 100 }, { instanceId: 'i-0abc123def456' })
141
+ findInstance({ MaxResults: 20 }, { instanceId: 'i-0abc123def456' })
141
142
  );
142
143
  ```
143
144
 
@@ -159,15 +160,15 @@ Every factory exposes a `.infinite()` method that returns `useInfiniteQuery`-com
159
160
  ```typescript
160
161
  const { data, fetchNextPage, hasNextPage } = useInfiniteQuery(
161
162
  // load 50 instances per UI page, each backed by up to 5 DescribeInstances calls
162
- describeInstances.infinite({ MaxResults: 10 }, { minResults: 50 })
163
+ describeInstances.infinite({ MaxResults: 20 }, { minResults: 50 })
163
164
  );
164
165
 
165
166
  // data.pages is Instance[][], one array per virtual page
166
167
  ```
167
168
 
168
169
  The `.infinite()` key includes an `'infinite'` segment to keep it separate from the regular `useQuery` cache entry:
169
- - `describeInstances({ MaxResults: 10 })` → `['ec2:DescribeInstances', { MaxResults: 10 }]`
170
- - `describeInstances.infinite({ MaxResults: 10 })` → `['ec2:DescribeInstances', 'infinite', { MaxResults: 10 }]`
170
+ - `describeInstances({ MaxResults: 20 })` → `['ec2:DescribeInstances', { MaxResults: 20 }]`
171
+ - `describeInstances.infinite({ MaxResults: 20 })` → `['ec2:DescribeInstances', 'infinite', { MaxResults: 20 }]`
171
172
 
172
173
  ---
173
174
 
@@ -187,7 +188,7 @@ queryFactory<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions>(
187
188
 
188
189
  Creates a child factory. Two overloads:
189
190
  - **With a new `queryFn`** — inherits key namespace and standard options; crawling config must be re-declared if needed.
190
- - **Without a `queryFn`** — inherits everything; accepts only `queryKey`, `select`, and standard options. `select` is composed with the parent's.
191
+ - **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.
191
192
 
192
193
  ### `QueryFactoryConfig`
193
194
 
@@ -198,11 +199,11 @@ All fields except `reduce` and `shouldFetchNextPage` are the standard TanStack Q
198
199
  | `queryKey` | `QueryKey` | Namespace segments. Params are appended at call time. |
199
200
  | `queryFn` | `(params: TParams, ctx: QueryFunctionContext) => TData \| Promise<TData>` | Same as TanStack, with an extra leading `params` argument. |
200
201
  | `select` | `(data: TData) => TSelected` | Exact TanStack API. Composed automatically on child factories. |
201
- | `getNextPageParam` | `GetNextPageParamFunction<TPageParam, TData>` | Exact TanStack API. Providing this together with `initialPageParam` and `reduce` activates crawling. |
202
- | `initialPageParam` | `TPageParam` | Exact TanStack API. Required alongside `getNextPageParam` to enable crawling. |
202
+ | `getNextPageParam` | `GetNextPageParamFunction<TPageParam, TData>` | Exact TanStack API. Required (with `shouldFetchNextPage`) to activate crawling. Required (with `initialPageParam`) for `.infinite()`. |
203
+ | `initialPageParam` | `TPageParam` | Exact TanStack API. Drives `TPageParam` inference — without it and without a `getNextPageParam` that provides inference, `ctx.pageParam` is typed `never`. Required for `.infinite()` to work at runtime. |
203
204
  | `getPreviousPageParam` | `GetPreviousPageParamFunction<TPageParam, TData>` | Exact TanStack API. Passed through on `.infinite()`. |
204
- | `reduce` | `(acc: TSelected \| undefined, page: TData) => TSelected` | Library addition. Folds crawled pages into a single value; required for crawling and `.infinite()` crawling. |
205
- | `shouldFetchNextPage` | `(combined: TSelected, crawlOptions: TCrawlOptions) => boolean` when `reduce` is present; `(combined: TSelected \| undefined, ...) => boolean` otherwise | Library addition. Return `false` to stop the crawl early based on accumulated results. |
205
+ | `shouldFetchNextPage` | `(combined: TSelected \| undefined, crawlOptions: TCrawlOptions) => boolean` | Library addition. **Required (with `getNextPageParam`) to activate crawling.** Called after each page return `true` to keep fetching, `false` to stop. |
206
+ | `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[]`). |
206
207
  | + all `StandardQueryOptions` fields | | All options accepted by TanStack's `useQuery` / `useInfiniteQuery` except `queryKey`, `queryFn`, and `select` (which the factory owns). Includes `staleTime`, `gcTime`, `retry`, `retryOnMount`, `enabled`, `refetchOnWindowFocus`, `refetchOnReconnect`, `refetchOnMount`, `refetchInterval`, `refetchIntervalInBackground`, `networkMode`, `notifyOnChangeProps`, `throwOnError`, `structuralSharing`, `initialData`, `initialDataUpdatedAt`, `placeholderData`, `queryKeyHashFn`, `persister`, `meta`, `maxPages`, `experimental_prefetchInRender`. Function-form callbacks (e.g. `enabled: (query) => boolean`) are supported wherever TanStack accepts them. |
207
208
 
208
209
  ### `QueryFactory<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions>`
package/dist/index.d.mts CHANGED
@@ -31,27 +31,21 @@ type QueryFactoryConfig<TParams = void, TData = unknown, TError = Error, TSelect
31
31
  unknown
32
32
  ] extends [TPageParam] ? never : TPageParam>) => TData | Promise<TData>;
33
33
  select?: (data: TData) => TSelected;
34
- } & ({
35
34
  /** TanStack v5 generic order: GetNextPageParamFunction<TPageParam, TData> */
36
- getNextPageParam: GetNextPageParamFunction<TPageParam, TData>;
37
- /** Required alongside getNextPageParam drives TPageParam inference so
38
- * ctx.pageParam in queryFn is typed as TPageParam, not unknown. */
39
- initialPageParam: TPageParam;
35
+ getNextPageParam?: GetNextPageParamFunction<TPageParam, TData>;
36
+ /** Drives TPageParam inference so ctx.pageParam in queryFn is typed as TPageParam.
37
+ * When omitted TPageParam stays unknown and ctx.pageParam is typed as never.
38
+ * Required for .infinite() to work correctly at runtime. */
39
+ initialPageParam?: TPageParam;
40
40
  getPreviousPageParam?: GetPreviousPageParamFunction<TPageParam, TData>;
41
41
  /** Reduces crawled pages incrementally into the final query result.
42
42
  * Called once per page; accumulator is undefined on the first call.
43
- * When set, enables crawling on both the regular and .infinite variants. */
43
+ * When omitted, the crawl result is the last fetched page (TSelected = TData). */
44
44
  reduce?: (accumulator: TSelected | undefined, page: TData) => TSelected;
45
45
  /** Called after each page to decide whether to keep crawling.
46
- * `combined` may be undefined when reduce is absent. */
46
+ * Required (along with getNextPageParam) to activate crawling. */
47
47
  shouldFetchNextPage?: (combined: TSelected | undefined, crawlOptions: TCrawlOptions) => boolean;
48
- } | {
49
- getNextPageParam?: never;
50
- initialPageParam?: never;
51
- getPreviousPageParam?: never;
52
- shouldFetchNextPage?: never;
53
- reduce?: never;
54
- });
48
+ };
55
49
  /**
56
50
  * What `factory(params)` returns — pass directly to `useQuery()`.
57
51
  *
@@ -68,15 +62,20 @@ type ResolvedQueryOptions<TData = unknown, TError = Error, TSelected = TData> =
68
62
  /**
69
63
  * What `factory.infinite(params)` returns — pass directly to `useInfiniteQuery()`.
70
64
  *
71
- * The `select` field expects `InfiniteData<TData, TPageParam>`, which is a structural
72
- * guard making this type incompatible with `useQuery`.
65
+ * The `select` field input type (`InfiniteData<TData, TPageParam>`) is a structural
66
+ * guard making this type incompatible with `useQuery`, whose select expects `(data: TData)`.
67
+ *
68
+ * `TResult` is the concrete type that `useInfiniteQuery` will use as its `TData` — i.e.
69
+ * what `select` returns (or `InfiniteData<TData, TPageParam>` when no select is set).
70
+ * Carrying it explicitly avoids returning `any`, which would poison TResult inference
71
+ * in TypeScript 6 when callers spread this type and add their own `select`.
73
72
  */
74
- type ResolvedInfiniteOptions<TData = unknown, TError = Error, TPageParam = unknown> = Omit<StandardQueryOptions<TError, any>, 'persister'> & {
73
+ type ResolvedInfiniteOptions<TData = unknown, TError = Error, TPageParam = unknown, TResult = InfiniteData<TData, TPageParam>> = Omit<StandardQueryOptions<TError, any>, 'persister'> & {
75
74
  queryKey: QueryKey;
76
75
  queryFn?: (context: QueryFunctionContext<QueryKey, TPageParam>) => TData | Promise<TData>;
77
- /** Structural guard: the InfiniteData parameter type makes this incompatible with useQuery,
76
+ /** Structural guard: the InfiniteData input type makes this incompatible with useQuery,
78
77
  * whose select expects (data: TData) rather than (data: InfiniteData<TData, TPageParam>). */
79
- select?: (data: InfiniteData<TData, TPageParam>) => any;
78
+ select?: (data: InfiniteData<TData, TPageParam>) => TResult;
80
79
  /** Required so this type satisfies useInfiniteQuery, which requires getNextPageParam. */
81
80
  getNextPageParam: GetNextPageParamFunction<TPageParam, TData>;
82
81
  getPreviousPageParam?: GetPreviousPageParamFunction<TPageParam, TData>;
@@ -97,9 +96,9 @@ type ResolvedInfiniteOptions<TData = unknown, TError = Error, TPageParam = unkno
97
96
  * namespace key, which is useful for broad cache invalidation:
98
97
  * `queryClient.invalidateQueries(factory())`
99
98
  */
100
- interface QueryFactory<TParams = void, TData = unknown, TError = Error, TSelected = TData, TPageParam = unknown, TCrawlOptions extends Record<string, unknown> = Record<string, unknown>> {
99
+ interface QueryFactory<TParams = void, TData = unknown, TError = Error, TSelected = TData, TPageParam = unknown, TCrawlOptions extends Record<string, unknown> = Record<string, unknown>, THasReduce extends boolean = boolean> {
101
100
  (params?: TParams, crawlOptions?: TCrawlOptions): ResolvedQueryOptions<TData, TError, TSelected>;
102
- infinite(params?: TParams, crawlOptions?: TCrawlOptions): ResolvedInfiniteOptions<TData, TError, TPageParam>;
101
+ infinite(params?: TParams, crawlOptions?: TCrawlOptions): ResolvedInfiniteOptions<TData, TError, TPageParam, InfiniteData<TSelected, TPageParam>>;
103
102
  }
104
103
  /**
105
104
  * Creates a standalone query factory with pagination and reduce. When `reduce` is
@@ -112,12 +111,12 @@ declare function queryFactory<TParams = void, TData = unknown, TError = Error, T
112
111
  unknown
113
112
  ] extends [TPageParam] ? never : TPageParam>) => TData | Promise<TData>;
114
113
  select?: (data: TData) => TSelected;
115
- getNextPageParam: GetNextPageParamFunction<TPageParam, TData>;
116
- initialPageParam: TPageParam;
114
+ getNextPageParam?: GetNextPageParamFunction<TPageParam, TData>;
115
+ initialPageParam?: TPageParam;
117
116
  getPreviousPageParam?: GetPreviousPageParamFunction<TPageParam, TData>;
118
117
  reduce: (accumulator: TSelected | undefined, page: TData) => TSelected;
119
118
  shouldFetchNextPage?: (combined: TSelected, crawlOptions: TCrawlOptions) => boolean;
120
- }): QueryFactory<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions>;
119
+ }): QueryFactory<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions, true>;
121
120
  /**
122
121
  * Creates a standalone query factory from a config object.
123
122
  *
@@ -128,7 +127,7 @@ declare function queryFactory<TParams = void, TData = unknown, TError = Error, T
128
127
  * });
129
128
  * // useQuery(usersFactory({ page: 1 }))
130
129
  */
131
- declare function queryFactory<TParams = void, TData = unknown, TError = Error, TSelected = TData, TPageParam = unknown, TCrawlOptions extends Record<string, unknown> = Record<string, unknown>>(config: QueryFactoryConfig<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions>): QueryFactory<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions>;
130
+ declare function queryFactory<TParams = void, TData = unknown, TError = Error, TSelected = TData, TPageParam = unknown, TCrawlOptions extends Record<string, unknown> = Record<string, unknown>>(config: QueryFactoryConfig<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions>): QueryFactory<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions, false>;
132
131
  /**
133
132
  * Creates a child factory that inherits the query key and standard options from
134
133
  * `parent` and introduces a new `queryFn`. The child's query key is appended to
@@ -136,22 +135,10 @@ declare function queryFactory<TParams = void, TData = unknown, TError = Error, T
136
135
  *
137
136
  * Use this overload when the child fetches different data than the parent.
138
137
  */
139
- declare function queryFactory<TChildParams extends TParentParams, TData = unknown, TError = Error, TChildSelected = TData, TParentParams = TChildParams, TPageParam = unknown, TCrawlOptions extends Record<string, unknown> = Record<string, unknown>>(parent: QueryFactory<TParentParams, any, any, any, any, any>, config: Omit<QueryFactoryConfig<TChildParams, TData, TError, TChildSelected, TPageParam, TCrawlOptions>, 'queryKey' | 'getNextPageParam' | 'getPreviousPageParam' | 'initialPageParam' | 'shouldFetchNextPage' | 'reduce'> & {
138
+ declare function queryFactory<TChildParams extends TParentParams, TData = unknown, TError = Error, TChildSelected = TData, TParentParams = TChildParams, TPageParam = unknown, TCrawlOptions extends Record<string, unknown> = Record<string, unknown>>(parent: QueryFactory<TParentParams, any, any, any, any, any, any>, config: Omit<QueryFactoryConfig<TChildParams, TData, TError, TChildSelected, TPageParam, TCrawlOptions>, 'queryKey' | 'queryFn'> & {
140
139
  queryKey?: QueryKey;
141
140
  queryFn: NonNullable<QueryFactoryConfig<TChildParams, TData, TError, TChildSelected, TPageParam>['queryFn']>;
142
- } & ({
143
- getNextPageParam: GetNextPageParamFunction<TPageParam, TData>;
144
- initialPageParam: TPageParam;
145
- getPreviousPageParam?: GetPreviousPageParamFunction<TPageParam, TData>;
146
- shouldFetchNextPage?: (combined: TChildSelected | undefined, crawlOptions: TCrawlOptions) => boolean;
147
- reduce?: (accumulator: TChildSelected | undefined, page: TData) => TChildSelected;
148
- } | {
149
- getNextPageParam?: never;
150
- initialPageParam?: never;
151
- getPreviousPageParam?: never;
152
- shouldFetchNextPage?: never;
153
- reduce?: never;
154
- })): QueryFactory<TChildParams, TData, TError, TChildSelected, TPageParam, TCrawlOptions>;
141
+ }): QueryFactory<TChildParams, TData, TError, TChildSelected, TPageParam, TCrawlOptions, boolean>;
155
142
  /**
156
143
  * Creates a child factory that reuses the parent's `queryFn` and pagination
157
144
  * config. Useful for adding a `select` transform, narrowing params, or
@@ -159,7 +146,7 @@ declare function queryFactory<TChildParams extends TParentParams, TData = unknow
159
146
  * without changing what data is fetched. Parent and child `select` functions
160
147
  * are automatically composed: `child.select(parent.select(data))`.
161
148
  */
162
- declare function queryFactory<TChildParams extends TParentParams, TData = unknown, TError = Error, TParentSelected = TData, TChildSelected = TParentSelected, TParentParams = TChildParams, TPageParam = unknown, TParentCrawlOptions extends Record<string, unknown> = Record<string, unknown>, TChildCrawlOptions extends Record<string, unknown> = TParentCrawlOptions>(parent: QueryFactory<TParentParams, TData, any, TParentSelected, TPageParam, TParentCrawlOptions>, config: StandardQueryOptions<TError, TData> & {
149
+ declare function queryFactory<TChildParams extends TParentParams, TData = unknown, TError = Error, TParentSelected = TData, TChildSelected = TParentSelected, TParentParams = TChildParams, TPageParam = unknown, TParentCrawlOptions extends Record<string, unknown> = Record<string, unknown>, TChildCrawlOptions extends Record<string, unknown> = TParentCrawlOptions, TParentHasReduce extends boolean = boolean>(parent: QueryFactory<TParentParams, TData, any, TParentSelected, TPageParam, TParentCrawlOptions, TParentHasReduce>, config: StandardQueryOptions<TError, TData> & {
163
150
  queryKey?: QueryKey;
164
151
  queryFn?: never;
165
152
  select?: (data: TParentSelected) => TChildSelected;
@@ -167,7 +154,7 @@ declare function queryFactory<TChildParams extends TParentParams, TData = unknow
167
154
  initialPageParam?: TPageParam;
168
155
  getPreviousPageParam?: GetPreviousPageParamFunction<TPageParam, TData>;
169
156
  reduce?: (accumulator: TChildSelected | undefined, page: TData) => TChildSelected;
170
- shouldFetchNextPage?: (combined: TChildSelected | undefined, crawlOptions: TChildCrawlOptions) => boolean;
171
- }): QueryFactory<TChildParams, TData, TError, TChildSelected, TPageParam, TChildCrawlOptions>;
157
+ shouldFetchNextPage?: (combined: TParentHasReduce extends true ? TChildSelected : TChildSelected | undefined, crawlOptions: TChildCrawlOptions) => boolean;
158
+ }): QueryFactory<TChildParams, TData, TError, TChildSelected, TPageParam, TChildCrawlOptions, TParentHasReduce>;
172
159
 
173
160
  export { type QueryFactory, type QueryFactoryConfig, type ResolvedInfiniteOptions, type ResolvedQueryOptions, type StandardQueryOptions, queryFactory };
package/dist/index.d.ts CHANGED
@@ -31,27 +31,21 @@ type QueryFactoryConfig<TParams = void, TData = unknown, TError = Error, TSelect
31
31
  unknown
32
32
  ] extends [TPageParam] ? never : TPageParam>) => TData | Promise<TData>;
33
33
  select?: (data: TData) => TSelected;
34
- } & ({
35
34
  /** TanStack v5 generic order: GetNextPageParamFunction<TPageParam, TData> */
36
- getNextPageParam: GetNextPageParamFunction<TPageParam, TData>;
37
- /** Required alongside getNextPageParam drives TPageParam inference so
38
- * ctx.pageParam in queryFn is typed as TPageParam, not unknown. */
39
- initialPageParam: TPageParam;
35
+ getNextPageParam?: GetNextPageParamFunction<TPageParam, TData>;
36
+ /** Drives TPageParam inference so ctx.pageParam in queryFn is typed as TPageParam.
37
+ * When omitted TPageParam stays unknown and ctx.pageParam is typed as never.
38
+ * Required for .infinite() to work correctly at runtime. */
39
+ initialPageParam?: TPageParam;
40
40
  getPreviousPageParam?: GetPreviousPageParamFunction<TPageParam, TData>;
41
41
  /** Reduces crawled pages incrementally into the final query result.
42
42
  * Called once per page; accumulator is undefined on the first call.
43
- * When set, enables crawling on both the regular and .infinite variants. */
43
+ * When omitted, the crawl result is the last fetched page (TSelected = TData). */
44
44
  reduce?: (accumulator: TSelected | undefined, page: TData) => TSelected;
45
45
  /** Called after each page to decide whether to keep crawling.
46
- * `combined` may be undefined when reduce is absent. */
46
+ * Required (along with getNextPageParam) to activate crawling. */
47
47
  shouldFetchNextPage?: (combined: TSelected | undefined, crawlOptions: TCrawlOptions) => boolean;
48
- } | {
49
- getNextPageParam?: never;
50
- initialPageParam?: never;
51
- getPreviousPageParam?: never;
52
- shouldFetchNextPage?: never;
53
- reduce?: never;
54
- });
48
+ };
55
49
  /**
56
50
  * What `factory(params)` returns — pass directly to `useQuery()`.
57
51
  *
@@ -68,15 +62,20 @@ type ResolvedQueryOptions<TData = unknown, TError = Error, TSelected = TData> =
68
62
  /**
69
63
  * What `factory.infinite(params)` returns — pass directly to `useInfiniteQuery()`.
70
64
  *
71
- * The `select` field expects `InfiniteData<TData, TPageParam>`, which is a structural
72
- * guard making this type incompatible with `useQuery`.
65
+ * The `select` field input type (`InfiniteData<TData, TPageParam>`) is a structural
66
+ * guard making this type incompatible with `useQuery`, whose select expects `(data: TData)`.
67
+ *
68
+ * `TResult` is the concrete type that `useInfiniteQuery` will use as its `TData` — i.e.
69
+ * what `select` returns (or `InfiniteData<TData, TPageParam>` when no select is set).
70
+ * Carrying it explicitly avoids returning `any`, which would poison TResult inference
71
+ * in TypeScript 6 when callers spread this type and add their own `select`.
73
72
  */
74
- type ResolvedInfiniteOptions<TData = unknown, TError = Error, TPageParam = unknown> = Omit<StandardQueryOptions<TError, any>, 'persister'> & {
73
+ type ResolvedInfiniteOptions<TData = unknown, TError = Error, TPageParam = unknown, TResult = InfiniteData<TData, TPageParam>> = Omit<StandardQueryOptions<TError, any>, 'persister'> & {
75
74
  queryKey: QueryKey;
76
75
  queryFn?: (context: QueryFunctionContext<QueryKey, TPageParam>) => TData | Promise<TData>;
77
- /** Structural guard: the InfiniteData parameter type makes this incompatible with useQuery,
76
+ /** Structural guard: the InfiniteData input type makes this incompatible with useQuery,
78
77
  * whose select expects (data: TData) rather than (data: InfiniteData<TData, TPageParam>). */
79
- select?: (data: InfiniteData<TData, TPageParam>) => any;
78
+ select?: (data: InfiniteData<TData, TPageParam>) => TResult;
80
79
  /** Required so this type satisfies useInfiniteQuery, which requires getNextPageParam. */
81
80
  getNextPageParam: GetNextPageParamFunction<TPageParam, TData>;
82
81
  getPreviousPageParam?: GetPreviousPageParamFunction<TPageParam, TData>;
@@ -97,9 +96,9 @@ type ResolvedInfiniteOptions<TData = unknown, TError = Error, TPageParam = unkno
97
96
  * namespace key, which is useful for broad cache invalidation:
98
97
  * `queryClient.invalidateQueries(factory())`
99
98
  */
100
- interface QueryFactory<TParams = void, TData = unknown, TError = Error, TSelected = TData, TPageParam = unknown, TCrawlOptions extends Record<string, unknown> = Record<string, unknown>> {
99
+ interface QueryFactory<TParams = void, TData = unknown, TError = Error, TSelected = TData, TPageParam = unknown, TCrawlOptions extends Record<string, unknown> = Record<string, unknown>, THasReduce extends boolean = boolean> {
101
100
  (params?: TParams, crawlOptions?: TCrawlOptions): ResolvedQueryOptions<TData, TError, TSelected>;
102
- infinite(params?: TParams, crawlOptions?: TCrawlOptions): ResolvedInfiniteOptions<TData, TError, TPageParam>;
101
+ infinite(params?: TParams, crawlOptions?: TCrawlOptions): ResolvedInfiniteOptions<TData, TError, TPageParam, InfiniteData<TSelected, TPageParam>>;
103
102
  }
104
103
  /**
105
104
  * Creates a standalone query factory with pagination and reduce. When `reduce` is
@@ -112,12 +111,12 @@ declare function queryFactory<TParams = void, TData = unknown, TError = Error, T
112
111
  unknown
113
112
  ] extends [TPageParam] ? never : TPageParam>) => TData | Promise<TData>;
114
113
  select?: (data: TData) => TSelected;
115
- getNextPageParam: GetNextPageParamFunction<TPageParam, TData>;
116
- initialPageParam: TPageParam;
114
+ getNextPageParam?: GetNextPageParamFunction<TPageParam, TData>;
115
+ initialPageParam?: TPageParam;
117
116
  getPreviousPageParam?: GetPreviousPageParamFunction<TPageParam, TData>;
118
117
  reduce: (accumulator: TSelected | undefined, page: TData) => TSelected;
119
118
  shouldFetchNextPage?: (combined: TSelected, crawlOptions: TCrawlOptions) => boolean;
120
- }): QueryFactory<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions>;
119
+ }): QueryFactory<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions, true>;
121
120
  /**
122
121
  * Creates a standalone query factory from a config object.
123
122
  *
@@ -128,7 +127,7 @@ declare function queryFactory<TParams = void, TData = unknown, TError = Error, T
128
127
  * });
129
128
  * // useQuery(usersFactory({ page: 1 }))
130
129
  */
131
- declare function queryFactory<TParams = void, TData = unknown, TError = Error, TSelected = TData, TPageParam = unknown, TCrawlOptions extends Record<string, unknown> = Record<string, unknown>>(config: QueryFactoryConfig<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions>): QueryFactory<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions>;
130
+ declare function queryFactory<TParams = void, TData = unknown, TError = Error, TSelected = TData, TPageParam = unknown, TCrawlOptions extends Record<string, unknown> = Record<string, unknown>>(config: QueryFactoryConfig<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions>): QueryFactory<TParams, TData, TError, TSelected, TPageParam, TCrawlOptions, false>;
132
131
  /**
133
132
  * Creates a child factory that inherits the query key and standard options from
134
133
  * `parent` and introduces a new `queryFn`. The child's query key is appended to
@@ -136,22 +135,10 @@ declare function queryFactory<TParams = void, TData = unknown, TError = Error, T
136
135
  *
137
136
  * Use this overload when the child fetches different data than the parent.
138
137
  */
139
- declare function queryFactory<TChildParams extends TParentParams, TData = unknown, TError = Error, TChildSelected = TData, TParentParams = TChildParams, TPageParam = unknown, TCrawlOptions extends Record<string, unknown> = Record<string, unknown>>(parent: QueryFactory<TParentParams, any, any, any, any, any>, config: Omit<QueryFactoryConfig<TChildParams, TData, TError, TChildSelected, TPageParam, TCrawlOptions>, 'queryKey' | 'getNextPageParam' | 'getPreviousPageParam' | 'initialPageParam' | 'shouldFetchNextPage' | 'reduce'> & {
138
+ declare function queryFactory<TChildParams extends TParentParams, TData = unknown, TError = Error, TChildSelected = TData, TParentParams = TChildParams, TPageParam = unknown, TCrawlOptions extends Record<string, unknown> = Record<string, unknown>>(parent: QueryFactory<TParentParams, any, any, any, any, any, any>, config: Omit<QueryFactoryConfig<TChildParams, TData, TError, TChildSelected, TPageParam, TCrawlOptions>, 'queryKey' | 'queryFn'> & {
140
139
  queryKey?: QueryKey;
141
140
  queryFn: NonNullable<QueryFactoryConfig<TChildParams, TData, TError, TChildSelected, TPageParam>['queryFn']>;
142
- } & ({
143
- getNextPageParam: GetNextPageParamFunction<TPageParam, TData>;
144
- initialPageParam: TPageParam;
145
- getPreviousPageParam?: GetPreviousPageParamFunction<TPageParam, TData>;
146
- shouldFetchNextPage?: (combined: TChildSelected | undefined, crawlOptions: TCrawlOptions) => boolean;
147
- reduce?: (accumulator: TChildSelected | undefined, page: TData) => TChildSelected;
148
- } | {
149
- getNextPageParam?: never;
150
- initialPageParam?: never;
151
- getPreviousPageParam?: never;
152
- shouldFetchNextPage?: never;
153
- reduce?: never;
154
- })): QueryFactory<TChildParams, TData, TError, TChildSelected, TPageParam, TCrawlOptions>;
141
+ }): QueryFactory<TChildParams, TData, TError, TChildSelected, TPageParam, TCrawlOptions, boolean>;
155
142
  /**
156
143
  * Creates a child factory that reuses the parent's `queryFn` and pagination
157
144
  * config. Useful for adding a `select` transform, narrowing params, or
@@ -159,7 +146,7 @@ declare function queryFactory<TChildParams extends TParentParams, TData = unknow
159
146
  * without changing what data is fetched. Parent and child `select` functions
160
147
  * are automatically composed: `child.select(parent.select(data))`.
161
148
  */
162
- declare function queryFactory<TChildParams extends TParentParams, TData = unknown, TError = Error, TParentSelected = TData, TChildSelected = TParentSelected, TParentParams = TChildParams, TPageParam = unknown, TParentCrawlOptions extends Record<string, unknown> = Record<string, unknown>, TChildCrawlOptions extends Record<string, unknown> = TParentCrawlOptions>(parent: QueryFactory<TParentParams, TData, any, TParentSelected, TPageParam, TParentCrawlOptions>, config: StandardQueryOptions<TError, TData> & {
149
+ declare function queryFactory<TChildParams extends TParentParams, TData = unknown, TError = Error, TParentSelected = TData, TChildSelected = TParentSelected, TParentParams = TChildParams, TPageParam = unknown, TParentCrawlOptions extends Record<string, unknown> = Record<string, unknown>, TChildCrawlOptions extends Record<string, unknown> = TParentCrawlOptions, TParentHasReduce extends boolean = boolean>(parent: QueryFactory<TParentParams, TData, any, TParentSelected, TPageParam, TParentCrawlOptions, TParentHasReduce>, config: StandardQueryOptions<TError, TData> & {
163
150
  queryKey?: QueryKey;
164
151
  queryFn?: never;
165
152
  select?: (data: TParentSelected) => TChildSelected;
@@ -167,7 +154,7 @@ declare function queryFactory<TChildParams extends TParentParams, TData = unknow
167
154
  initialPageParam?: TPageParam;
168
155
  getPreviousPageParam?: GetPreviousPageParamFunction<TPageParam, TData>;
169
156
  reduce?: (accumulator: TChildSelected | undefined, page: TData) => TChildSelected;
170
- shouldFetchNextPage?: (combined: TChildSelected | undefined, crawlOptions: TChildCrawlOptions) => boolean;
171
- }): QueryFactory<TChildParams, TData, TError, TChildSelected, TPageParam, TChildCrawlOptions>;
157
+ shouldFetchNextPage?: (combined: TParentHasReduce extends true ? TChildSelected : TChildSelected | undefined, crawlOptions: TChildCrawlOptions) => boolean;
158
+ }): QueryFactory<TChildParams, TData, TError, TChildSelected, TPageParam, TChildCrawlOptions, TParentHasReduce>;
172
159
 
173
160
  export { type QueryFactory, type QueryFactoryConfig, type ResolvedInfiniteOptions, type ResolvedQueryOptions, type StandardQueryOptions, queryFactory };
package/dist/index.js CHANGED
@@ -40,9 +40,10 @@ function resolveKey(namespace, params, crawlOptions) {
40
40
  }
41
41
  return defined ? [...withParams, defined] : withParams;
42
42
  }
43
- function wrapGetNextPageParam(getNextPageParam, shouldFetchNextPage, crawlOptions) {
43
+ function wrapGetNextPageParam(getNextPageParam, shouldFetchNextPage, crawlOptions, select) {
44
44
  return (lastPage, allPages, lastPageParam, allPageParams) => {
45
- if (!shouldFetchNextPage(lastPage, crawlOptions))
45
+ const combined = select ? select(lastPage) : lastPage;
46
+ if (!shouldFetchNextPage(combined, crawlOptions))
46
47
  return void 0;
47
48
  return getNextPageParam(lastPage, allPages, lastPageParam, allPageParams);
48
49
  };
@@ -63,7 +64,7 @@ function buildCrawlingQueryFn(queryFn, getNextPageParam, initialPageParam, shoul
63
64
  pageParams.push(currentParam);
64
65
  if (reduce) acc = reduce(acc, page);
65
66
  if ((_b = context.signal) == null ? void 0 : _b.aborted) break;
66
- if (shouldFetchNextPage && !shouldFetchNextPage(acc, crawlOptions)) break;
67
+ if (!shouldFetchNextPage(acc, crawlOptions)) break;
67
68
  const nextParam = getNextPageParam(page, pages, currentParam, pageParams);
68
69
  if (nextParam == null) break;
69
70
  currentParam = nextParam;
@@ -95,7 +96,7 @@ function buildInfiniteCrawlingQueryFn(queryFn, getNextPageParam, shouldFetchNext
95
96
  const nextParam = getNextPageParam(page, pages, currentParam, pageParams);
96
97
  nextBatchParam = nextParam != null ? nextParam : null;
97
98
  if (nextParam == null) break;
98
- if (shouldFetchNextPage && !shouldFetchNextPage(acc, crawlOptions)) break;
99
+ if (!shouldFetchNextPage(acc, crawlOptions)) break;
99
100
  currentParam = nextParam;
100
101
  }
101
102
  if (acc === void 0) throw new DOMException("Aborted", "AbortError");
@@ -114,7 +115,7 @@ function buildFactory(cfg) {
114
115
  reduce,
115
116
  standardOptions
116
117
  } = cfg;
117
- const hasCrawling = rawQueryFn !== void 0 && getNextPageParam !== void 0;
118
+ const hasCrawling = rawQueryFn !== void 0 && getNextPageParam !== void 0 && shouldFetchNextPage !== void 0;
118
119
  const hasInfiniteCrawling = hasCrawling && reduce !== void 0;
119
120
  const crawlingFn = hasCrawling ? buildCrawlingQueryFn(
120
121
  rawQueryFn,
@@ -167,7 +168,8 @@ function buildFactory(cfg) {
167
168
  const infiniteGetNextPageParam = getNextPageParam && shouldFetchNextPage ? wrapGetNextPageParam(
168
169
  getNextPageParam,
169
170
  shouldFetchNextPage,
170
- crawlOptions
171
+ crawlOptions,
172
+ select
171
173
  ) : getNextPageParam != null ? getNextPageParam : noNextPage;
172
174
  return {
173
175
  ...standardOptions,
package/dist/index.mjs CHANGED
@@ -14,9 +14,10 @@ function resolveKey(namespace, params, crawlOptions) {
14
14
  }
15
15
  return defined ? [...withParams, defined] : withParams;
16
16
  }
17
- function wrapGetNextPageParam(getNextPageParam, shouldFetchNextPage, crawlOptions) {
17
+ function wrapGetNextPageParam(getNextPageParam, shouldFetchNextPage, crawlOptions, select) {
18
18
  return (lastPage, allPages, lastPageParam, allPageParams) => {
19
- if (!shouldFetchNextPage(lastPage, crawlOptions))
19
+ const combined = select ? select(lastPage) : lastPage;
20
+ if (!shouldFetchNextPage(combined, crawlOptions))
20
21
  return void 0;
21
22
  return getNextPageParam(lastPage, allPages, lastPageParam, allPageParams);
22
23
  };
@@ -37,7 +38,7 @@ function buildCrawlingQueryFn(queryFn, getNextPageParam, initialPageParam, shoul
37
38
  pageParams.push(currentParam);
38
39
  if (reduce) acc = reduce(acc, page);
39
40
  if ((_b = context.signal) == null ? void 0 : _b.aborted) break;
40
- if (shouldFetchNextPage && !shouldFetchNextPage(acc, crawlOptions)) break;
41
+ if (!shouldFetchNextPage(acc, crawlOptions)) break;
41
42
  const nextParam = getNextPageParam(page, pages, currentParam, pageParams);
42
43
  if (nextParam == null) break;
43
44
  currentParam = nextParam;
@@ -69,7 +70,7 @@ function buildInfiniteCrawlingQueryFn(queryFn, getNextPageParam, shouldFetchNext
69
70
  const nextParam = getNextPageParam(page, pages, currentParam, pageParams);
70
71
  nextBatchParam = nextParam != null ? nextParam : null;
71
72
  if (nextParam == null) break;
72
- if (shouldFetchNextPage && !shouldFetchNextPage(acc, crawlOptions)) break;
73
+ if (!shouldFetchNextPage(acc, crawlOptions)) break;
73
74
  currentParam = nextParam;
74
75
  }
75
76
  if (acc === void 0) throw new DOMException("Aborted", "AbortError");
@@ -88,7 +89,7 @@ function buildFactory(cfg) {
88
89
  reduce,
89
90
  standardOptions
90
91
  } = cfg;
91
- const hasCrawling = rawQueryFn !== void 0 && getNextPageParam !== void 0;
92
+ const hasCrawling = rawQueryFn !== void 0 && getNextPageParam !== void 0 && shouldFetchNextPage !== void 0;
92
93
  const hasInfiniteCrawling = hasCrawling && reduce !== void 0;
93
94
  const crawlingFn = hasCrawling ? buildCrawlingQueryFn(
94
95
  rawQueryFn,
@@ -141,7 +142,8 @@ function buildFactory(cfg) {
141
142
  const infiniteGetNextPageParam = getNextPageParam && shouldFetchNextPage ? wrapGetNextPageParam(
142
143
  getNextPageParam,
143
144
  shouldFetchNextPage,
144
- crawlOptions
145
+ crawlOptions,
146
+ select
145
147
  ) : getNextPageParam != null ? getNextPageParam : noNextPage;
146
148
  return {
147
149
  ...standardOptions,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robohall/react-query-factory",
3
- "version": "1.0.5",
3
+ "version": "1.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",