@ic-reactor/react 3.0.0-beta.8 → 3.0.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.
Files changed (46) hide show
  1. package/README.md +11 -10
  2. package/dist/createActorHooks.d.ts +2 -0
  3. package/dist/createActorHooks.d.ts.map +1 -1
  4. package/dist/createActorHooks.js +2 -0
  5. package/dist/createActorHooks.js.map +1 -1
  6. package/dist/createMutation.d.ts.map +1 -1
  7. package/dist/createMutation.js +4 -0
  8. package/dist/createMutation.js.map +1 -1
  9. package/dist/hooks/index.d.ts +18 -5
  10. package/dist/hooks/index.d.ts.map +1 -1
  11. package/dist/hooks/index.js +15 -5
  12. package/dist/hooks/index.js.map +1 -1
  13. package/dist/hooks/useActorInfiniteQuery.d.ts +13 -11
  14. package/dist/hooks/useActorInfiniteQuery.d.ts.map +1 -1
  15. package/dist/hooks/useActorInfiniteQuery.js.map +1 -1
  16. package/dist/hooks/useActorMethod.d.ts +105 -0
  17. package/dist/hooks/useActorMethod.d.ts.map +1 -0
  18. package/dist/hooks/useActorMethod.js +192 -0
  19. package/dist/hooks/useActorMethod.js.map +1 -0
  20. package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts +13 -10
  21. package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts.map +1 -1
  22. package/dist/hooks/useActorSuspenseInfiniteQuery.js.map +1 -1
  23. package/package.json +7 -6
  24. package/src/createActorHooks.ts +146 -0
  25. package/src/createAuthHooks.ts +137 -0
  26. package/src/createInfiniteQuery.ts +471 -0
  27. package/src/createMutation.ts +163 -0
  28. package/src/createQuery.ts +197 -0
  29. package/src/createSuspenseInfiniteQuery.ts +478 -0
  30. package/src/createSuspenseQuery.ts +215 -0
  31. package/src/hooks/index.ts +93 -0
  32. package/src/hooks/useActorInfiniteQuery.test.tsx +457 -0
  33. package/src/hooks/useActorInfiniteQuery.ts +134 -0
  34. package/src/hooks/useActorMethod.test.tsx +798 -0
  35. package/src/hooks/useActorMethod.ts +397 -0
  36. package/src/hooks/useActorMutation.test.tsx +220 -0
  37. package/src/hooks/useActorMutation.ts +124 -0
  38. package/src/hooks/useActorQuery.test.tsx +287 -0
  39. package/src/hooks/useActorQuery.ts +110 -0
  40. package/src/hooks/useActorSuspenseInfiniteQuery.test.tsx +472 -0
  41. package/src/hooks/useActorSuspenseInfiniteQuery.ts +137 -0
  42. package/src/hooks/useActorSuspenseQuery.test.tsx +254 -0
  43. package/src/hooks/useActorSuspenseQuery.ts +112 -0
  44. package/src/index.ts +21 -0
  45. package/src/types.ts +435 -0
  46. package/src/validation.ts +202 -0
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Query Factory - Generic wrapper for React Query-based canister data
3
+ *
4
+ * Creates unified fetch/hook/invalidate functions for any canister method.
5
+ * Works with any Reactor instance.
6
+ *
7
+ * @example
8
+ * const userQuery = createQuery(todoManager, {
9
+ * functionName: "get_user",
10
+ * select: (result) => result.user,
11
+ * })
12
+ *
13
+ * // In component
14
+ * const { data: user } = userQuery.useQuery()
15
+ */
16
+
17
+ import type {
18
+ Reactor,
19
+ FunctionName,
20
+ ReactorArgs,
21
+ TransformKey,
22
+ } from "@ic-reactor/core"
23
+ import { QueryKey, useQuery } from "@tanstack/react-query"
24
+ import type {
25
+ QueryFnData,
26
+ QueryError,
27
+ QueryConfig,
28
+ UseQueryWithSelect,
29
+ QueryResult,
30
+ QueryFactoryConfig,
31
+ NoInfer,
32
+ } from "./types"
33
+
34
+ // ============================================================================
35
+ // Internal Implementation
36
+ // ============================================================================
37
+
38
+ const createQueryImpl = <
39
+ A,
40
+ M extends FunctionName<A> = FunctionName<A>,
41
+ T extends TransformKey = "candid",
42
+ TSelected = QueryFnData<A, M, T>,
43
+ >(
44
+ reactor: Reactor<A, T>,
45
+ config: QueryConfig<A, M, T, TSelected>
46
+ ): QueryResult<QueryFnData<A, M, T>, TSelected, QueryError<A, M, T>> => {
47
+ type TData = QueryFnData<A, M, T>
48
+ type TError = QueryError<A, M, T>
49
+
50
+ const {
51
+ functionName,
52
+ args,
53
+ staleTime = 5 * 60 * 1000,
54
+ select,
55
+ queryKey: customQueryKey,
56
+ ...rest
57
+ } = config
58
+
59
+ const params = {
60
+ functionName,
61
+ args,
62
+ queryKey: customQueryKey,
63
+ }
64
+
65
+ // Get query key from actor manager
66
+ const getQueryKey = (): QueryKey => {
67
+ return reactor.generateQueryKey(params)
68
+ }
69
+
70
+ // Fetch function for loaders (cache-first)
71
+ const fetch = async (): Promise<TSelected> => {
72
+ const result = await reactor.fetchQuery(params)
73
+ return select ? select(result) : (result as TSelected)
74
+ }
75
+
76
+ // Implementation
77
+ const useQueryHook: UseQueryWithSelect<TData, TSelected, TError> = (
78
+ options: any
79
+ ): any => {
80
+ const baseOptions = reactor.getQueryOptions(params)
81
+
82
+ // Chain the selects: raw -> config.select -> options.select
83
+ const chainedSelect = (rawData: TData) => {
84
+ const firstPass = config.select ? config.select(rawData) : rawData
85
+ if (options?.select) {
86
+ return options.select(firstPass)
87
+ }
88
+ return firstPass
89
+ }
90
+ return useQuery(
91
+ {
92
+ queryKey: baseOptions.queryKey,
93
+ staleTime,
94
+ ...rest,
95
+ ...options,
96
+ queryFn: baseOptions.queryFn,
97
+ select: chainedSelect,
98
+ },
99
+ reactor.queryClient
100
+ )
101
+ }
102
+
103
+ // Invalidate function
104
+ const invalidate = async (): Promise<void> => {
105
+ const queryKey = getQueryKey()
106
+ await reactor.queryClient.invalidateQueries({ queryKey })
107
+ }
108
+
109
+ // Get data from cache without fetching
110
+ const getCacheData: any = (selectFn?: (data: TData) => any) => {
111
+ const cachedRawData = reactor.getQueryData(params)
112
+
113
+ if (cachedRawData === undefined) {
114
+ return undefined
115
+ }
116
+
117
+ // Apply config.select to raw cache data
118
+ const selectedData = (
119
+ config.select ? config.select(cachedRawData) : cachedRawData
120
+ ) as TData
121
+
122
+ // Apply optional select parameter
123
+ if (selectFn) {
124
+ return selectFn(selectedData)
125
+ }
126
+
127
+ return selectedData
128
+ }
129
+
130
+ return {
131
+ fetch,
132
+ useQuery: useQueryHook,
133
+ invalidate,
134
+ getQueryKey,
135
+ getCacheData,
136
+ }
137
+ }
138
+
139
+ // ============================================================================
140
+ // Factory Function
141
+ // ============================================================================
142
+
143
+ export function createQuery<
144
+ A,
145
+ T extends TransformKey,
146
+ M extends FunctionName<A> = FunctionName<A>,
147
+ TSelected = QueryFnData<A, M, T>,
148
+ >(
149
+ reactor: Reactor<A, T>,
150
+ config: QueryConfig<NoInfer<A>, M, T, TSelected>
151
+ ): QueryResult<QueryFnData<A, M, T>, TSelected, QueryError<A, M, T>> {
152
+ return createQueryImpl(reactor, config as QueryConfig<A, M, T, TSelected>)
153
+ }
154
+
155
+ // ============================================================================
156
+ // Convenience: Create query with dynamic args
157
+ // ============================================================================
158
+
159
+ export function createQueryFactory<
160
+ A,
161
+ T extends TransformKey,
162
+ M extends FunctionName<A> = FunctionName<A>,
163
+ TSelected = QueryFnData<A, M, T>,
164
+ >(
165
+ reactor: Reactor<A, T>,
166
+ config: QueryFactoryConfig<NoInfer<A>, M, T, TSelected>
167
+ ): (
168
+ args: ReactorArgs<A, M, T>
169
+ ) => QueryResult<QueryFnData<A, M, T>, TSelected, QueryError<A, M, T>> {
170
+ const cache = new Map<
171
+ string,
172
+ QueryResult<QueryFnData<A, M, T>, TSelected, QueryError<A, M, T>>
173
+ >()
174
+
175
+ return (
176
+ args: ReactorArgs<A, M, T>
177
+ ): QueryResult<QueryFnData<A, M, T>, TSelected, QueryError<A, M, T>> => {
178
+ const key = reactor.generateQueryKey({
179
+ functionName: config.functionName as M,
180
+ args,
181
+ })
182
+ const cacheKey = JSON.stringify(key)
183
+
184
+ if (cache.has(cacheKey)) {
185
+ return cache.get(cacheKey)!
186
+ }
187
+
188
+ const result = createQueryImpl<A, M, T, TSelected>(reactor, {
189
+ ...config,
190
+ args,
191
+ })
192
+
193
+ cache.set(cacheKey, result)
194
+
195
+ return result
196
+ }
197
+ }
@@ -0,0 +1,478 @@
1
+ /**
2
+ * Suspense Infinite Query Factory - Generic wrapper for React Query suspense-based paginated canister data
3
+ *
4
+ * Creates unified fetch/hook/invalidate functions for any paginated canister method.
5
+ * Works with any Reactor instance.
6
+ *
7
+ * Uses `useSuspenseInfiniteQuery` which:
8
+ * - Requires wrapping in <Suspense> boundary
9
+ * - Data is always defined (no undefined checks)
10
+ * - Does NOT support `enabled` or `placeholderData` options
11
+ *
12
+ * @example
13
+ * const postsQuery = createSuspenseInfiniteQuery(reactor, {
14
+ * functionName: "get_posts",
15
+ * initialPageParam: 0,
16
+ * getArgs: (cursor) => [{ cursor, limit: 10 }],
17
+ * getNextPageParam: (lastPage) => lastPage.nextCursor,
18
+ * })
19
+ *
20
+ * // In component (wrap in Suspense)
21
+ * const { data, fetchNextPage, hasNextPage } = postsQuery.useSuspenseInfiniteQuery()
22
+ *
23
+ * // Flatten all pages
24
+ * const allPosts = data.pages.flatMap(page => page.posts)
25
+ */
26
+
27
+ import type {
28
+ Reactor,
29
+ FunctionName,
30
+ ReactorArgs,
31
+ BaseActor,
32
+ TransformKey,
33
+ ReactorReturnOk,
34
+ ReactorReturnErr,
35
+ } from "@ic-reactor/core"
36
+ import {
37
+ QueryKey,
38
+ useSuspenseInfiniteQuery,
39
+ InfiniteData,
40
+ UseSuspenseInfiniteQueryResult,
41
+ UseSuspenseInfiniteQueryOptions,
42
+ QueryFunctionContext,
43
+ FetchInfiniteQueryOptions,
44
+ InfiniteQueryObserverOptions,
45
+ } from "@tanstack/react-query"
46
+ import { CallConfig } from "@icp-sdk/core/agent"
47
+ import { NoInfer } from "./types"
48
+
49
+ // ============================================================================
50
+ // Type Definitions
51
+ // ============================================================================
52
+
53
+ /** The raw page data type returned by the query function */
54
+ export type SuspenseInfiniteQueryPageData<
55
+ A = BaseActor,
56
+ M extends FunctionName<A> = FunctionName<A>,
57
+ T extends TransformKey = "candid",
58
+ > = ReactorReturnOk<A, M, T>
59
+
60
+ /** The error type for infinite queries */
61
+ export type SuspenseInfiniteQueryError<
62
+ A = BaseActor,
63
+ M extends FunctionName<A> = FunctionName<A>,
64
+ T extends TransformKey = "candid",
65
+ > = ReactorReturnErr<A, M, T>
66
+
67
+ // ============================================================================
68
+ // Configuration Types
69
+ // ============================================================================
70
+
71
+ /**
72
+ * Configuration for createActorSuspenseInfiniteQuery.
73
+ * Extends InfiniteQueryObserverOptions to accept all React Query options at the create level.
74
+ *
75
+ * @template A - The actor interface type
76
+ * @template M - The method name on the actor
77
+ * @template T - The transformation key (identity, display, etc.)
78
+ * @template TPageParam - The type of the page parameter
79
+ * @template TSelected - The type returned after select transformation
80
+ */
81
+ export interface SuspenseInfiniteQueryConfig<
82
+ A = BaseActor,
83
+ M extends FunctionName<A> = FunctionName<A>,
84
+ T extends TransformKey = "candid",
85
+ TPageParam = unknown,
86
+ TSelected = InfiniteData<SuspenseInfiniteQueryPageData<A, M, T>, TPageParam>,
87
+ > extends Omit<
88
+ InfiniteQueryObserverOptions<
89
+ SuspenseInfiniteQueryPageData<A, M, T>,
90
+ SuspenseInfiniteQueryError<A, M, T>,
91
+ TSelected,
92
+ QueryKey,
93
+ TPageParam
94
+ >,
95
+ "queryKey" | "queryFn"
96
+ > {
97
+ /** The method to call on the canister */
98
+ functionName: M
99
+ /** Call configuration for the actor method */
100
+ callConfig?: CallConfig
101
+ /** Custom query key (optional, auto-generated if not provided) */
102
+ queryKey?: QueryKey
103
+ /** Function to get args from page parameter */
104
+ getArgs: (pageParam: TPageParam) => ReactorArgs<A, M, T>
105
+ }
106
+
107
+ /**
108
+ * Configuration for createActorSuspenseInfiniteQueryFactory (without initialPageParam, getArgs determined at call time).
109
+ */
110
+ export type SuspenseInfiniteQueryFactoryConfig<
111
+ A = BaseActor,
112
+ M extends FunctionName<A> = FunctionName<A>,
113
+ T extends TransformKey = "candid",
114
+ TPageParam = unknown,
115
+ TSelected = InfiniteData<SuspenseInfiniteQueryPageData<A, M, T>, TPageParam>,
116
+ > = Omit<SuspenseInfiniteQueryConfig<A, M, T, TPageParam, TSelected>, "getArgs">
117
+
118
+ // ============================================================================
119
+ // Hook Interface
120
+ // ============================================================================
121
+
122
+ /**
123
+ * useSuspenseInfiniteQuery hook with chained select support.
124
+ */
125
+ export interface UseSuspenseInfiniteQueryWithSelect<
126
+ TPageData,
127
+ TPageParam,
128
+ TSelected = InfiniteData<TPageData, TPageParam>,
129
+ TError = Error,
130
+ > {
131
+ // Overload 1: Without select - returns TSelected
132
+ (
133
+ options?: Omit<
134
+ UseSuspenseInfiniteQueryOptions<
135
+ TPageData,
136
+ TError,
137
+ TSelected,
138
+ QueryKey,
139
+ TPageParam
140
+ >,
141
+ | "select"
142
+ | "queryKey"
143
+ | "queryFn"
144
+ | "initialPageParam"
145
+ | "getNextPageParam"
146
+ | "getPreviousPageParam"
147
+ >
148
+ ): UseSuspenseInfiniteQueryResult<TSelected, TError>
149
+
150
+ // Overload 2: With select - chains on top and returns TFinal
151
+ <TFinal = TSelected>(
152
+ options: Omit<
153
+ UseSuspenseInfiniteQueryOptions<
154
+ TPageData,
155
+ TError,
156
+ TFinal,
157
+ QueryKey,
158
+ TPageParam
159
+ >,
160
+ | "queryKey"
161
+ | "queryFn"
162
+ | "select"
163
+ | "initialPageParam"
164
+ | "getNextPageParam"
165
+ | "getPreviousPageParam"
166
+ > & {
167
+ select: (data: TSelected) => TFinal
168
+ }
169
+ ): UseSuspenseInfiniteQueryResult<TFinal, TError>
170
+ }
171
+
172
+ // ============================================================================
173
+ // Result Interface
174
+ // ============================================================================
175
+
176
+ /**
177
+ * Result from createActorSuspenseInfiniteQuery
178
+ *
179
+ * @template TPageData - The raw page data type
180
+ * @template TPageParam - The page parameter type
181
+ * @template TSelected - The type after select transformation
182
+ * @template TError - The error type
183
+ */
184
+ export interface SuspenseInfiniteQueryResult<
185
+ TPageData,
186
+ TPageParam,
187
+ TSelected = InfiniteData<TPageData, TPageParam>,
188
+ TError = Error,
189
+ > {
190
+ /** Fetch first page in loader (uses ensureInfiniteQueryData for cache-first) */
191
+ fetch: () => Promise<TSelected>
192
+
193
+ /** React hook for components - supports pagination */
194
+ useSuspenseInfiniteQuery: UseSuspenseInfiniteQueryWithSelect<
195
+ TPageData,
196
+ TPageParam,
197
+ TSelected,
198
+ TError
199
+ >
200
+
201
+ /** Invalidate the cache (refetches if query is active) */
202
+ invalidate: () => Promise<void>
203
+
204
+ /** Get query key (for advanced React Query usage) */
205
+ getQueryKey: () => QueryKey
206
+
207
+ /**
208
+ * Read data directly from cache without fetching.
209
+ * Returns undefined if data is not in cache.
210
+ */
211
+ getCacheData: {
212
+ (): TSelected | undefined
213
+ <TFinal>(select: (data: TSelected) => TFinal): TFinal | undefined
214
+ }
215
+ }
216
+
217
+ // ============================================================================
218
+ // Internal Implementation
219
+ // ============================================================================
220
+
221
+ const createSuspenseInfiniteQueryImpl = <
222
+ A,
223
+ M extends FunctionName<A> = FunctionName<A>,
224
+ T extends TransformKey = "candid",
225
+ TPageParam = unknown,
226
+ TSelected = InfiniteData<SuspenseInfiniteQueryPageData<A, M, T>, TPageParam>,
227
+ >(
228
+ reactor: Reactor<A, T>,
229
+ config: SuspenseInfiniteQueryConfig<A, M, T, TPageParam, TSelected>
230
+ ): SuspenseInfiniteQueryResult<
231
+ SuspenseInfiniteQueryPageData<A, M, T>,
232
+ TPageParam,
233
+ TSelected,
234
+ SuspenseInfiniteQueryError<A, M, T>
235
+ > => {
236
+ type TPageData = SuspenseInfiniteQueryPageData<A, M, T>
237
+ type TError = SuspenseInfiniteQueryError<A, M, T>
238
+ type TInfiniteData = InfiniteData<TPageData, TPageParam>
239
+
240
+ const {
241
+ functionName,
242
+ callConfig,
243
+ queryKey: customQueryKey,
244
+ initialPageParam,
245
+ getArgs,
246
+ getNextPageParam,
247
+ getPreviousPageParam,
248
+ maxPages,
249
+ staleTime = 5 * 60 * 1000,
250
+ select,
251
+ ...rest
252
+ } = config
253
+
254
+ // Get query key from actor manager
255
+ const getQueryKey = (): QueryKey => {
256
+ return reactor.generateQueryKey({
257
+ functionName,
258
+ queryKey: customQueryKey,
259
+ })
260
+ }
261
+
262
+ // Query function - accepts QueryFunctionContext
263
+ const queryFn = async (
264
+ context: QueryFunctionContext<QueryKey, TPageParam>
265
+ ): Promise<TPageData> => {
266
+ // pageParam is typed as unknown in QueryFunctionContext, but we know its type
267
+ const pageParam = context.pageParam as TPageParam
268
+ const args = getArgs(pageParam)
269
+ const result = await reactor.callMethod({
270
+ functionName,
271
+ args,
272
+ callConfig,
273
+ })
274
+ return result
275
+ }
276
+
277
+ // Get infinite query options for fetchInfiniteQuery
278
+ const getInfiniteQueryOptions = (): FetchInfiniteQueryOptions<
279
+ TPageData,
280
+ TError,
281
+ TPageData,
282
+ QueryKey,
283
+ TPageParam
284
+ > => ({
285
+ queryKey: getQueryKey(),
286
+ queryFn,
287
+ initialPageParam,
288
+ getNextPageParam,
289
+ staleTime,
290
+ })
291
+
292
+ // Fetch function for loaders (cache-first, fetches first page)
293
+ const fetch = async (): Promise<TSelected> => {
294
+ // Use ensureInfiniteQueryData to get cached data or fetch if stale
295
+ const result = await reactor.queryClient.ensureInfiniteQueryData(
296
+ getInfiniteQueryOptions()
297
+ )
298
+
299
+ // Result is already InfiniteData format
300
+ return select ? select(result) : (result as unknown as TSelected)
301
+ }
302
+
303
+ // Implementation
304
+ const useSuspenseInfiniteQueryHook: UseSuspenseInfiniteQueryWithSelect<
305
+ TPageData,
306
+ TPageParam,
307
+ TSelected,
308
+ TError
309
+ > = (options: any): any => {
310
+ // Chain the selects: raw -> config.select -> options.select
311
+ const chainedSelect = (rawData: TInfiniteData) => {
312
+ const firstPass = select ? select(rawData) : rawData
313
+ if (options?.select) {
314
+ return options.select(firstPass)
315
+ }
316
+ return firstPass
317
+ }
318
+
319
+ return useSuspenseInfiniteQuery(
320
+ {
321
+ queryKey: getQueryKey(),
322
+ queryFn,
323
+ initialPageParam,
324
+ getNextPageParam,
325
+ getPreviousPageParam,
326
+ maxPages,
327
+ staleTime,
328
+ ...rest,
329
+ ...options,
330
+ select: chainedSelect,
331
+ },
332
+ reactor.queryClient
333
+ )
334
+ }
335
+
336
+ // Invalidate function
337
+ const invalidate = async (): Promise<void> => {
338
+ const queryKey = getQueryKey()
339
+ await reactor.queryClient.invalidateQueries({ queryKey })
340
+ }
341
+
342
+ // Get data from cache without fetching
343
+ const getCacheData = (selectFn?: (data: TSelected) => any) => {
344
+ const queryKey = getQueryKey()
345
+ const cachedRawData = reactor.queryClient.getQueryData(
346
+ queryKey
347
+ ) as TInfiniteData
348
+
349
+ if (cachedRawData === undefined) {
350
+ return undefined
351
+ }
352
+
353
+ // Apply config.select to raw cache data
354
+ const selectedData = (
355
+ select ? select(cachedRawData) : cachedRawData
356
+ ) as TSelected
357
+
358
+ // Apply optional select parameter
359
+ if (selectFn) {
360
+ return selectFn(selectedData)
361
+ }
362
+
363
+ return selectedData
364
+ }
365
+
366
+ return {
367
+ fetch,
368
+ useSuspenseInfiniteQuery: useSuspenseInfiniteQueryHook,
369
+ invalidate,
370
+ getQueryKey,
371
+ getCacheData,
372
+ }
373
+ }
374
+
375
+ // ============================================================================
376
+ // Factory Function
377
+ // ============================================================================
378
+
379
+ export function createSuspenseInfiniteQuery<
380
+ A,
381
+ T extends TransformKey,
382
+ M extends FunctionName<A> = FunctionName<A>,
383
+ TPageParam = unknown,
384
+ TSelected = InfiniteData<SuspenseInfiniteQueryPageData<A, M, T>, TPageParam>,
385
+ >(
386
+ reactor: Reactor<A, T>,
387
+ config: SuspenseInfiniteQueryConfig<NoInfer<A>, M, T, TPageParam, TSelected>
388
+ ): SuspenseInfiniteQueryResult<
389
+ SuspenseInfiniteQueryPageData<A, M, T>,
390
+ TPageParam,
391
+ TSelected,
392
+ SuspenseInfiniteQueryError<A, M, T>
393
+ > {
394
+ return createSuspenseInfiniteQueryImpl(
395
+ reactor,
396
+ config as SuspenseInfiniteQueryConfig<A, M, T, TPageParam, TSelected>
397
+ )
398
+ }
399
+
400
+ // ============================================================================
401
+ // Factory with Dynamic Args
402
+ // ============================================================================
403
+
404
+ /**
405
+ * Create a suspense infinite query factory that accepts getArgs at call time.
406
+ * Useful when pagination logic varies by context.
407
+ *
408
+ * @template A - The actor interface type
409
+ * @template M - The method name on the actor
410
+ * @template T - The transformation key (identity, display, etc.)
411
+ * @template TPageParam - The page parameter type
412
+ * @template TSelected - The type returned after select transformation
413
+ *
414
+ * @param reactor - The Reactor instance
415
+ * @param config - Suspense infinite query configuration (without getArgs)
416
+ * @returns A function that accepts getArgs and returns an SuspenseActorInfiniteQueryResult
417
+ *
418
+ * @example
419
+ * const getPostsQuery = createActorSuspenseInfiniteQueryFactory(reactor, {
420
+ * functionName: "get_posts",
421
+ * initialPageParam: 0,
422
+ * getNextPageParam: (lastPage) => lastPage.nextCursor,
423
+ * })
424
+ *
425
+ * // Create query with specific args builder
426
+ * const userPostsQuery = getPostsQuery((cursor) => [{ userId, cursor, limit: 10 }])
427
+ * const { data, fetchNextPage } = userPostsQuery.useSuspenseInfiniteQuery()
428
+ */
429
+
430
+ export function createSuspenseInfiniteQueryFactory<
431
+ A,
432
+ T extends TransformKey,
433
+ M extends FunctionName<A> = FunctionName<A>,
434
+ TPageParam = unknown,
435
+ TSelected = InfiniteData<SuspenseInfiniteQueryPageData<A, M, T>, TPageParam>,
436
+ >(
437
+ reactor: Reactor<A, T>,
438
+ config: SuspenseInfiniteQueryFactoryConfig<
439
+ NoInfer<A>,
440
+ M,
441
+ T,
442
+ TPageParam,
443
+ TSelected
444
+ >
445
+ ): (
446
+ getArgs: (pageParam: TPageParam) => ReactorArgs<A, M, T>,
447
+ options?: { queryKey?: QueryKey }
448
+ ) => SuspenseInfiniteQueryResult<
449
+ SuspenseInfiniteQueryPageData<A, M, T>,
450
+ TPageParam,
451
+ TSelected,
452
+ SuspenseInfiniteQueryError<A, M, T>
453
+ > {
454
+ return (
455
+ getArgs: (pageParam: TPageParam) => ReactorArgs<A, M, T>,
456
+ options?: { queryKey?: QueryKey }
457
+ ): SuspenseInfiniteQueryResult<
458
+ SuspenseInfiniteQueryPageData<A, M, T>,
459
+ TPageParam,
460
+ TSelected,
461
+ SuspenseInfiniteQueryError<A, M, T>
462
+ > => {
463
+ return createSuspenseInfiniteQueryImpl<A, M, T, TPageParam, TSelected>(
464
+ reactor,
465
+ {
466
+ ...(config as SuspenseInfiniteQueryFactoryConfig<
467
+ A,
468
+ M,
469
+ T,
470
+ TPageParam,
471
+ TSelected
472
+ >),
473
+ ...options,
474
+ getArgs,
475
+ } as SuspenseInfiniteQueryConfig<A, M, T, TPageParam, TSelected>
476
+ )
477
+ }
478
+ }