@ic-reactor/react 3.12.4 → 3.13.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 (152) hide show
  1. package/README.md +410 -44
  2. package/dist/auth/auth-client-compat.d.ts +122 -0
  3. package/dist/auth/auth-client-compat.d.ts.map +1 -0
  4. package/dist/auth/auth-client-compat.js +162 -0
  5. package/dist/auth/auth-client-compat.js.map +1 -0
  6. package/dist/auth/authentication-manager.d.ts +287 -5
  7. package/dist/auth/authentication-manager.d.ts.map +1 -1
  8. package/dist/auth/authentication-manager.js +920 -150
  9. package/dist/auth/authentication-manager.js.map +1 -1
  10. package/dist/auth/createIdentityAttributeHooks.d.ts.map +1 -1
  11. package/dist/auth/createIdentityAttributeHooks.js +36 -20
  12. package/dist/auth/createIdentityAttributeHooks.js.map +1 -1
  13. package/dist/auth/identity-attributes-manager.d.ts +2 -1
  14. package/dist/auth/identity-attributes-manager.d.ts.map +1 -1
  15. package/dist/auth/identity-attributes-manager.js +90 -6
  16. package/dist/auth/identity-attributes-manager.js.map +1 -1
  17. package/dist/auth/identity-attributes.d.ts.map +1 -1
  18. package/dist/auth/identity-attributes.js +57 -0
  19. package/dist/auth/identity-attributes.js.map +1 -1
  20. package/dist/auth/local-ii-probe.d.ts +12 -1
  21. package/dist/auth/local-ii-probe.d.ts.map +1 -1
  22. package/dist/auth/local-ii-probe.js +22 -3
  23. package/dist/auth/local-ii-probe.js.map +1 -1
  24. package/dist/auth/types.d.ts +48 -5
  25. package/dist/auth/types.d.ts.map +1 -1
  26. package/dist/createActorHooks.d.ts +9 -20
  27. package/dist/createActorHooks.d.ts.map +1 -1
  28. package/dist/createActorHooks.js.map +1 -1
  29. package/dist/createInfiniteQuery.d.ts +51 -10
  30. package/dist/createInfiniteQuery.d.ts.map +1 -1
  31. package/dist/createInfiniteQuery.js +39 -15
  32. package/dist/createInfiniteQuery.js.map +1 -1
  33. package/dist/createMutation.d.ts +4 -1
  34. package/dist/createMutation.d.ts.map +1 -1
  35. package/dist/createMutation.js +121 -84
  36. package/dist/createMutation.js.map +1 -1
  37. package/dist/createQuery.d.ts +35 -2
  38. package/dist/createQuery.d.ts.map +1 -1
  39. package/dist/createQuery.js +104 -17
  40. package/dist/createQuery.js.map +1 -1
  41. package/dist/createReactorProvider.d.ts +158 -0
  42. package/dist/createReactorProvider.d.ts.map +1 -0
  43. package/dist/createReactorProvider.js +256 -0
  44. package/dist/createReactorProvider.js.map +1 -0
  45. package/dist/createSuspenseInfiniteQuery.d.ts +16 -9
  46. package/dist/createSuspenseInfiniteQuery.d.ts.map +1 -1
  47. package/dist/createSuspenseInfiniteQuery.js +59 -27
  48. package/dist/createSuspenseInfiniteQuery.js.map +1 -1
  49. package/dist/createSuspenseQuery.d.ts +23 -2
  50. package/dist/createSuspenseQuery.d.ts.map +1 -1
  51. package/dist/createSuspenseQuery.js +68 -21
  52. package/dist/createSuspenseQuery.js.map +1 -1
  53. package/dist/defineDisplayReactor.d.ts +43 -0
  54. package/dist/defineDisplayReactor.d.ts.map +1 -0
  55. package/dist/defineDisplayReactor.js +42 -0
  56. package/dist/defineDisplayReactor.js.map +1 -0
  57. package/dist/defineReactor.d.ts +46 -72
  58. package/dist/defineReactor.d.ts.map +1 -1
  59. package/dist/defineReactor.js +11 -176
  60. package/dist/defineReactor.js.map +1 -1
  61. package/dist/defineReactorShared.d.ts +84 -0
  62. package/dist/defineReactorShared.d.ts.map +1 -0
  63. package/dist/defineReactorShared.js +139 -0
  64. package/dist/defineReactorShared.js.map +1 -0
  65. package/dist/hooks/createAuthHooks.d.ts +9 -2
  66. package/dist/hooks/createAuthHooks.d.ts.map +1 -1
  67. package/dist/hooks/createAuthHooks.js +184 -24
  68. package/dist/hooks/createAuthHooks.js.map +1 -1
  69. package/dist/hooks/useActorInfiniteQuery.d.ts +36 -8
  70. package/dist/hooks/useActorInfiniteQuery.d.ts.map +1 -1
  71. package/dist/hooks/useActorInfiniteQuery.js +54 -21
  72. package/dist/hooks/useActorInfiniteQuery.js.map +1 -1
  73. package/dist/hooks/useActorMethod.d.ts +37 -4
  74. package/dist/hooks/useActorMethod.d.ts.map +1 -1
  75. package/dist/hooks/useActorMethod.js +201 -57
  76. package/dist/hooks/useActorMethod.js.map +1 -1
  77. package/dist/hooks/useActorMutation.d.ts +15 -12
  78. package/dist/hooks/useActorMutation.d.ts.map +1 -1
  79. package/dist/hooks/useActorMutation.js +14 -13
  80. package/dist/hooks/useActorMutation.js.map +1 -1
  81. package/dist/hooks/useActorQuery.d.ts +17 -4
  82. package/dist/hooks/useActorQuery.d.ts.map +1 -1
  83. package/dist/hooks/useActorQuery.js +30 -9
  84. package/dist/hooks/useActorQuery.js.map +1 -1
  85. package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts +17 -5
  86. package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts.map +1 -1
  87. package/dist/hooks/useActorSuspenseInfiniteQuery.js +37 -17
  88. package/dist/hooks/useActorSuspenseInfiniteQuery.js.map +1 -1
  89. package/dist/hooks/useActorSuspenseQuery.d.ts +2 -2
  90. package/dist/hooks/useActorSuspenseQuery.d.ts.map +1 -1
  91. package/dist/hooks/useActorSuspenseQuery.js +20 -9
  92. package/dist/hooks/useActorSuspenseQuery.js.map +1 -1
  93. package/dist/index.d.ts +4 -0
  94. package/dist/index.d.ts.map +1 -1
  95. package/dist/index.js +6 -0
  96. package/dist/index.js.map +1 -1
  97. package/dist/ownedAuthentication.d.ts +52 -0
  98. package/dist/ownedAuthentication.d.ts.map +1 -0
  99. package/dist/ownedAuthentication.js +49 -0
  100. package/dist/ownedAuthentication.js.map +1 -0
  101. package/dist/server.d.ts +21 -0
  102. package/dist/server.d.ts.map +1 -0
  103. package/dist/server.js +23 -0
  104. package/dist/server.js.map +1 -0
  105. package/dist/testing.d.ts +19 -0
  106. package/dist/testing.d.ts.map +1 -0
  107. package/dist/testing.js +19 -0
  108. package/dist/testing.js.map +1 -0
  109. package/dist/types.d.ts +428 -21
  110. package/dist/types.d.ts.map +1 -1
  111. package/dist/types.js +1 -1
  112. package/dist/utils.d.ts +159 -3
  113. package/dist/utils.d.ts.map +1 -1
  114. package/dist/utils.js +301 -1
  115. package/dist/utils.js.map +1 -1
  116. package/dist/validation.d.ts +12 -7
  117. package/dist/validation.d.ts.map +1 -1
  118. package/dist/validation.js +34 -15
  119. package/dist/validation.js.map +1 -1
  120. package/llms.txt +259 -33
  121. package/package.json +17 -5
  122. package/src/auth/auth-client-compat.ts +273 -0
  123. package/src/auth/authentication-manager.ts +918 -96
  124. package/src/auth/createIdentityAttributeHooks.ts +47 -21
  125. package/src/auth/identity-attributes-manager.ts +100 -5
  126. package/src/auth/identity-attributes.ts +75 -0
  127. package/src/auth/local-ii-probe.ts +29 -3
  128. package/src/auth/types.ts +49 -6
  129. package/src/createActorHooks.ts +50 -42
  130. package/src/createInfiniteQuery.ts +120 -28
  131. package/src/createMutation.ts +213 -132
  132. package/src/createQuery.ts +164 -32
  133. package/src/createReactorProvider.ts +365 -0
  134. package/src/createSuspenseInfiniteQuery.ts +93 -43
  135. package/src/createSuspenseQuery.ts +102 -32
  136. package/src/defineDisplayReactor.ts +62 -0
  137. package/src/defineReactor.ts +81 -263
  138. package/src/defineReactorShared.ts +268 -0
  139. package/src/hooks/createAuthHooks.ts +210 -28
  140. package/src/hooks/useActorInfiniteQuery.ts +156 -55
  141. package/src/hooks/useActorMethod.ts +295 -92
  142. package/src/hooks/useActorMutation.ts +42 -30
  143. package/src/hooks/useActorQuery.ts +43 -10
  144. package/src/hooks/useActorSuspenseInfiniteQuery.ts +110 -54
  145. package/src/hooks/useActorSuspenseQuery.ts +30 -15
  146. package/src/index.ts +8 -0
  147. package/src/ownedAuthentication.ts +81 -0
  148. package/src/server.ts +23 -0
  149. package/src/testing.ts +18 -0
  150. package/src/types.ts +492 -22
  151. package/src/utils.ts +387 -3
  152. package/src/validation.ts +43 -19
@@ -13,7 +13,7 @@
13
13
  * const postsQuery = createSuspenseInfiniteQuery(reactor, {
14
14
  * functionName: "get_posts",
15
15
  * initialPageParam: 0,
16
- * getArgs: (cursor) => [{ cursor, limit: 10 }],
16
+ * getArgs: (cursor) => [{ cursor, limit: 10 }] as const,
17
17
  * getNextPageParam: (lastPage) => lastPage.nextCursor,
18
18
  * })
19
19
  *
@@ -46,11 +46,22 @@ import {
46
46
  InfiniteQueryObserverOptions,
47
47
  } from "@tanstack/react-query"
48
48
  import { CallConfig } from "@icp-sdk/core/agent"
49
- import { NoInfer } from "./types.js"
49
+ import type {
50
+ NoInfer,
51
+ QueryCacheControls,
52
+ QueryFactoryMethods,
53
+ } from "./types.js"
50
54
  import {
51
55
  buildChainedSelect,
56
+ callConfigForKey,
52
57
  mergeFactoryQueryKey,
58
+ mountWhileSuspended,
53
59
  normalizeQueryData,
60
+ pickFetchOptions,
61
+ queryCacheControls,
62
+ retryOption,
63
+ useMountQueryClient,
64
+ withQueryFactoryMethods,
54
65
  } from "./utils.js"
55
66
 
56
67
  type SuspenseInfiniteFactoryCallOptions = {
@@ -63,7 +74,7 @@ type SuspenseInfiniteQueryFactoryFn<
63
74
  Transform extends TransformKey,
64
75
  TPageParam,
65
76
  Selected,
66
- > = {
77
+ > = QueryFactoryMethods & {
67
78
  (
68
79
  getArgs: (pageParam: TPageParam) => ReactorArgs<Service, Method, Transform>
69
80
  ): SuspenseInfiniteQueryResult<
@@ -106,7 +117,7 @@ export type SuspenseInfiniteQueryError<
106
117
  // ============================================================================
107
118
 
108
119
  /**
109
- * Configuration for createActorSuspenseInfiniteQuery.
120
+ * Configuration for createSuspenseInfiniteQuery.
110
121
  * Extends InfiniteQueryObserverOptions to accept all React Query options at the create level.
111
122
  *
112
123
  * @template Service - The actor interface type
@@ -154,7 +165,7 @@ export interface SuspenseInfiniteQueryConfig<
154
165
  }
155
166
 
156
167
  /**
157
- * Configuration for createActorSuspenseInfiniteQueryFactory (without getArgs; provided at call time).
168
+ * Configuration for createSuspenseInfiniteQueryFactory (without getArgs; provided at call time).
158
169
  */
159
170
  export type SuspenseInfiniteQueryFactoryConfig<
160
171
  Service = BaseActor,
@@ -231,7 +242,11 @@ export interface UseSuspenseInfiniteQueryWithSelect<
231
242
  // ============================================================================
232
243
 
233
244
  /**
234
- * Result from createActorSuspenseInfiniteQuery
245
+ * Result from createSuspenseInfiniteQuery
246
+ *
247
+ * `cancel()`, `reset()` and `optimisticUpdate()` act on the whole page set:
248
+ * `optimisticUpdate` gets and returns the raw `InfiniteData`, `{ pages,
249
+ * pageParams }`.
235
250
  *
236
251
  * @template TPageData - The raw page data type
237
252
  * @template TPageParam - The page parameter type
@@ -243,7 +258,7 @@ export interface SuspenseInfiniteQueryResult<
243
258
  TPageParam,
244
259
  Selected = InfiniteData<TPageData, TPageParam>,
245
260
  TError = Error,
246
- > {
261
+ > extends QueryCacheControls<InfiniteData<TPageData, TPageParam>> {
247
262
  /** Fetch first page in loader (uses ensureInfiniteQueryData for cache-first) */
248
263
  fetch: () => Promise<Selected>
249
264
 
@@ -345,7 +360,7 @@ const createSuspenseInfiniteQueryImpl = <
345
360
  const result = await reactor.callMethod({
346
361
  functionName,
347
362
  args,
348
- callConfig,
363
+ callConfig: callConfigForKey(context.queryKey, callConfig),
349
364
  })
350
365
  return normalizeQueryData<ReactorReturnOk<Service, Method, Transform>>(
351
366
  result as ReactorReturnOk<Service, Method, Transform>
@@ -359,19 +374,30 @@ const createSuspenseInfiniteQueryImpl = <
359
374
  TPageData,
360
375
  QueryKey,
361
376
  TPageParam
362
- > => ({
363
- queryKey: getQueryKey(),
364
- queryFn,
365
- initialPageParam,
366
- getNextPageParam,
367
- staleTime,
368
- })
377
+ > => {
378
+ const queryKey = getQueryKey()
379
+ return {
380
+ // How the query function runs, shared with the hook; see
381
+ // pickFetchOptions. An update method's `retry` defaults as
382
+ // `Reactor.getQueryRetry` says.
383
+ ...pickFetchOptions(rest),
384
+ ...retryOption(rest.retry, reactor.getQueryRetry(functionName, queryKey)),
385
+ queryKey,
386
+ queryFn,
387
+ initialPageParam,
388
+ getNextPageParam,
389
+ staleTime,
390
+ }
391
+ }
369
392
 
370
393
  // Fetch function for loaders (cache-first, fetches first page)
371
394
  const fetch = async (): Promise<Selected> => {
372
- // Use ensureInfiniteQueryData to get cached data or fetch if stale
373
- const result = await reactor.queryClient.ensureInfiniteQueryData(
374
- getInfiniteQueryOptions()
395
+ // Use ensureInfiniteQueryData to get cached data or fetch if stale. A
396
+ // sign-in or sign-out while it is in flight cancels it; it then runs again
397
+ // for the new identity rather than rejecting with TanStack's
398
+ // CancelledError, as `reactor.fetchQuery` does.
399
+ const result = await reactor.clientManager.fetchAcrossIdentitySwitch(() =>
400
+ reactor.queryClient.ensureInfiniteQueryData(getInfiniteQueryOptions())
375
401
  )
376
402
 
377
403
  // Result is already InfiniteData format
@@ -385,6 +411,8 @@ const createSuspenseInfiniteQueryImpl = <
385
411
  Selected,
386
412
  TError
387
413
  > = (options: any): any => {
414
+ useMountQueryClient(reactor.queryClient)
415
+
388
416
  // Memoized and identity-preserving; see buildChainedSelect for why the
389
417
  // function identity matters to the observer's select-result cache.
390
418
  const chainedSelect = useMemo(
@@ -392,21 +420,34 @@ const createSuspenseInfiniteQueryImpl = <
392
420
  [options?.select]
393
421
  )
394
422
 
395
- return useSuspenseInfiniteQuery(
396
- {
397
- queryKey: getQueryKey(),
398
- queryFn,
399
- initialPageParam,
400
- getNextPageParam,
401
- getPreviousPageParam,
402
- maxPages,
403
- staleTime,
404
- ...rest,
405
- ...options,
406
- select: chainedSelect,
407
- },
408
- reactor.queryClient
409
- )
423
+ const queryKey = getQueryKey()
424
+
425
+ try {
426
+ return useSuspenseInfiniteQuery(
427
+ {
428
+ queryKey,
429
+ queryFn,
430
+ initialPageParam,
431
+ getNextPageParam,
432
+ getPreviousPageParam,
433
+ maxPages,
434
+ staleTime,
435
+ ...rest,
436
+ ...options,
437
+ select: chainedSelect,
438
+ // The hook's `retry`, else the config's, else an update method's
439
+ // default; see `Reactor.getQueryRetry`.
440
+ ...retryOption(
441
+ options?.retry ?? rest.retry,
442
+ reactor.getQueryRetry(functionName, queryKey)
443
+ ),
444
+ },
445
+ reactor.queryClient
446
+ )
447
+ } catch (thrown) {
448
+ mountWhileSuspended(reactor.queryClient, thrown)
449
+ throw thrown
450
+ }
410
451
  }
411
452
 
412
453
  // Invalidate function
@@ -445,6 +486,7 @@ const createSuspenseInfiniteQueryImpl = <
445
486
  invalidate,
446
487
  getQueryKey,
447
488
  getCacheData,
489
+ ...queryCacheControls<TInfiniteData>(reactor, getQueryKey),
448
490
  }
449
491
  }
450
492
 
@@ -504,10 +546,10 @@ export function createSuspenseInfiniteQuery<
504
546
  *
505
547
  * @param reactor - The Reactor instance
506
548
  * @param config - Suspense infinite query configuration (without getArgs)
507
- * @returns A function that accepts getArgs and returns an SuspenseActorInfiniteQueryResult
549
+ * @returns A function that accepts getArgs and returns a SuspenseInfiniteQueryResult
508
550
  *
509
551
  * @example
510
- * const getPostsQuery = createActorSuspenseInfiniteQueryFactory(reactor, {
552
+ * const getPostsQuery = createSuspenseInfiniteQueryFactory(reactor, {
511
553
  * functionName: "get_posts",
512
554
  * initialPageParam: 0,
513
555
  * getKeyArgs: (args) => {
@@ -526,6 +568,9 @@ export function createSuspenseInfiniteQuery<
526
568
  * (cursor) => [{ userId, cursor, limit: 10 }],
527
569
  * { queryKey: ["v2"] }
528
570
  * )
571
+ *
572
+ * // Every list this factory made, whatever its args and pages
573
+ * await getPostsQuery.invalidate()
529
574
  */
530
575
 
531
576
  export function createSuspenseInfiniteQueryFactory<
@@ -553,13 +598,7 @@ export function createSuspenseInfiniteQueryFactory<
553
598
  TPageParam,
554
599
  Selected
555
600
  > {
556
- const factory: SuspenseInfiniteQueryFactoryFn<
557
- Service,
558
- Method,
559
- Transform,
560
- TPageParam,
561
- Selected
562
- > = (
601
+ const factory = (
563
602
  getArgs: (pageParam: TPageParam) => ReactorArgs<Service, Method, Transform>,
564
603
  options?: SuspenseInfiniteFactoryCallOptions
565
604
  ) => {
@@ -597,5 +636,16 @@ export function createSuspenseInfiniteQueryFactory<
597
636
  >)
598
637
  }
599
638
 
600
- return factory
639
+ // What every instance's key starts with: the method, the `callConfig`
640
+ // segments and the config `queryKey`. A per-call `queryKey` and the args
641
+ // segment come after them.
642
+ return withQueryFactoryMethods(factory, reactor, () =>
643
+ reactor.generateQueryKey(
644
+ {
645
+ functionName: config.functionName as Method,
646
+ queryKey: config.queryKey,
647
+ },
648
+ config.callConfig
649
+ )
650
+ )
601
651
  }
@@ -34,9 +34,19 @@ import type {
34
34
  UseSuspenseQueryWithSelect,
35
35
  SuspenseQueryResult,
36
36
  SuspenseQueryFactoryConfig,
37
+ QueryFactoryFn,
37
38
  NoInfer,
38
39
  } from "./types.js"
39
- import { buildChainedSelect, createBoundedCache } from "./utils.js"
40
+ import {
41
+ buildChainedSelect,
42
+ createBoundedCache,
43
+ mountWhileSuspended,
44
+ pickFetchOptions,
45
+ queryCacheControls,
46
+ retryOption,
47
+ useMountQueryClient,
48
+ withQueryFactoryMethods,
49
+ } from "./utils.js"
40
50
 
41
51
  // ============================================================================
42
52
  // Internal Implementation
@@ -61,33 +71,51 @@ const createSuspenseQueryImpl = <
61
71
  const {
62
72
  functionName,
63
73
  args,
74
+ callConfig,
64
75
  staleTime = 5 * 60 * 1000,
65
76
  select,
66
77
  queryKey: customQueryKey,
67
78
  ...rest
68
79
  } = config
69
80
 
70
- const params = { functionName, args, queryKey: customQueryKey }
81
+ // `callConfig` goes wherever the hooks send it: to the call and into the
82
+ // key, so a query of another canister or agent has an entry of its own.
83
+ const params = { functionName, args, queryKey: customQueryKey, callConfig }
71
84
 
72
- const getQueryKey = () => reactor.generateQueryKey(params)
85
+ const getQueryKey = () => reactor.generateQueryKey(params, callConfig)
73
86
 
74
87
  const applySelect = (raw: TData): Selected =>
75
88
  select ? select(raw) : (raw as unknown as Selected)
76
89
 
90
+ // How the query function runs, shared with the hook; see pickFetchOptions.
91
+ const fetchOptions = pickFetchOptions(rest)
92
+
77
93
  /** Cache-first fetch for use in loaders / route preloading. */
78
94
  const fetch = async (): Promise<Selected> => {
79
- const result = await reactor.fetchQuery(params)
95
+ // Through the reactor rather than straight to the QueryClient: overriding
96
+ // `fetchQuery` in a Reactor subclass is a documented way to add logic to
97
+ // every factory fetch.
98
+ const result = await reactor.fetchQuery(params, fetchOptions)
80
99
  return applySelect(result)
81
100
  }
82
101
 
83
102
  /** Fire-and-forget prefetch — warms the cache without blocking. */
84
103
  const prefetch = (): Promise<void> => {
85
104
  const baseOptions = reactor.getQueryOptions(params)
86
- return reactor.queryClient.prefetchQuery({
87
- queryKey: baseOptions.queryKey,
88
- queryFn: baseOptions.queryFn,
89
- staleTime,
90
- })
105
+ // Runs again when a sign-in or sign-out cancels it, and never rejects;
106
+ // see `prefetch` in createQuery.
107
+ return reactor.clientManager
108
+ .fetchAcrossIdentitySwitch(() =>
109
+ reactor.queryClient.prefetchQuery({
110
+ ...fetchOptions,
111
+ // An update method's default `retry`; see `Reactor.getQueryRetry`.
112
+ ...retryOption(fetchOptions.retry, baseOptions.retry),
113
+ queryKey: baseOptions.queryKey,
114
+ queryFn: baseOptions.queryFn,
115
+ staleTime,
116
+ })
117
+ )
118
+ .catch(() => undefined)
91
119
  }
92
120
 
93
121
  const useSuspenseQueryHook: UseSuspenseQueryWithSelect<
@@ -95,6 +123,7 @@ const createSuspenseQueryImpl = <
95
123
  Selected,
96
124
  TError
97
125
  > = (options: any): any => {
126
+ useMountQueryClient(reactor.queryClient)
98
127
  const baseOptions = reactor.getQueryOptions(params)
99
128
  // Memoized so the observer's select-result cache can hit; see
100
129
  // buildChainedSelect. `select` comes from the factory config and is stable.
@@ -102,17 +131,25 @@ const createSuspenseQueryImpl = <
102
131
  () => buildChainedSelect(select, options?.select),
103
132
  [options?.select]
104
133
  )
105
- return useSuspenseQuery(
106
- {
107
- queryKey: baseOptions.queryKey,
108
- staleTime,
109
- ...rest,
110
- ...options,
111
- queryFn: baseOptions.queryFn,
112
- select: chainedSelect,
113
- },
114
- reactor.queryClient
115
- )
134
+ try {
135
+ return useSuspenseQuery(
136
+ {
137
+ queryKey: baseOptions.queryKey,
138
+ staleTime,
139
+ ...rest,
140
+ ...options,
141
+ queryFn: baseOptions.queryFn,
142
+ select: chainedSelect,
143
+ // The hook's `retry`, else the config's, else an update method's
144
+ // default; see `Reactor.getQueryRetry`.
145
+ ...retryOption(options?.retry ?? rest.retry, baseOptions.retry),
146
+ },
147
+ reactor.queryClient
148
+ )
149
+ } catch (thrown) {
150
+ mountWhileSuspended(reactor.queryClient, thrown)
151
+ throw thrown
152
+ }
116
153
  }
117
154
 
118
155
  const invalidate = async (): Promise<void> => {
@@ -124,7 +161,7 @@ const createSuspenseQueryImpl = <
124
161
  Selected,
125
162
  TError
126
163
  >["getCacheData"] = (selectFn?: (data: Selected) => unknown): any => {
127
- const raw = reactor.getQueryData(params)
164
+ const raw = reactor.getQueryData(params, callConfig)
128
165
  if (raw === undefined) return undefined
129
166
  const selected = applySelect(raw)
130
167
  return selectFn ? selectFn(selected) : selected
@@ -145,6 +182,7 @@ const createSuspenseQueryImpl = <
145
182
  getQueryKey,
146
183
  getCacheData,
147
184
  setData,
185
+ ...queryCacheControls<TData>(reactor, getQueryKey),
148
186
  }
149
187
  }
150
188
 
@@ -175,6 +213,27 @@ export function createSuspenseQuery<
175
213
  // Convenience: Create suspense query with dynamic args
176
214
  // ============================================================================
177
215
 
216
+ /**
217
+ * Create a suspense query factory: a function that takes the method's args
218
+ * and returns the suspense query object for them, the same object for the
219
+ * same args.
220
+ *
221
+ * The function also has `getQueryKey()`, the key prefix every query it
222
+ * returns shares, and `invalidate()`, which invalidates all of them whatever
223
+ * their args. Pass the function itself to a mutation's `invalidateQueries` to
224
+ * refresh every instance after the mutation.
225
+ *
226
+ * @example
227
+ * const getBalance = createSuspenseQueryFactory(ledger, {
228
+ * functionName: "icrc1_balance_of",
229
+ * })
230
+ *
231
+ * // In a component under <Suspense>
232
+ * const { data } = getBalance([{ owner, subaccount: [] }]).useSuspenseQuery()
233
+ *
234
+ * // Refetch every account's balance
235
+ * await getBalance.invalidate()
236
+ */
178
237
  export function createSuspenseQueryFactory<
179
238
  Service,
180
239
  Transform extends TransformKey,
@@ -188,12 +247,13 @@ export function createSuspenseQueryFactory<
188
247
  Transform,
189
248
  Selected
190
249
  >
191
- ): (
192
- args: ReactorArgs<Service, Method, Transform>
193
- ) => SuspenseQueryResult<
194
- QueryFnData<Service, Method, Transform>,
195
- Selected,
196
- QueryError<Service, Method, Transform>
250
+ ): QueryFactoryFn<
251
+ ReactorArgs<Service, Method, Transform>,
252
+ SuspenseQueryResult<
253
+ QueryFnData<Service, Method, Transform>,
254
+ Selected,
255
+ QueryError<Service, Method, Transform>
256
+ >
197
257
  > {
198
258
  const cache =
199
259
  createBoundedCache<
@@ -204,11 +264,11 @@ export function createSuspenseQueryFactory<
204
264
  >
205
265
  >()
206
266
 
207
- return (args: ReactorArgs<Service, Method, Transform>) => {
208
- const key = reactor.generateQueryKey({
209
- functionName: config.functionName as Method,
210
- args,
211
- })
267
+ const factory = (args: ReactorArgs<Service, Method, Transform>) => {
268
+ const key = reactor.generateQueryKey(
269
+ { functionName: config.functionName as Method, args },
270
+ config.callConfig
271
+ )
212
272
  const cacheKey = JSON.stringify(key)
213
273
 
214
274
  const existing = cache.get(cacheKey)
@@ -231,4 +291,14 @@ export function createSuspenseQueryFactory<
231
291
  cache.set(cacheKey, result)
232
292
  return result
233
293
  }
294
+
295
+ // The method's own prefix, at the canister and agent the config's
296
+ // `callConfig` names. A config `queryKey` follows the args segment in every
297
+ // instance's key, so it cannot narrow the prefix.
298
+ return withQueryFactoryMethods(factory, reactor, () =>
299
+ reactor.generateQueryKey(
300
+ { functionName: config.functionName as Method },
301
+ config.callConfig
302
+ )
303
+ )
234
304
  }
@@ -0,0 +1,62 @@
1
+ import { DisplayReactor } from "@ic-reactor/core"
2
+ import type { BaseActor, DisplayReactorParameters } from "@ic-reactor/core"
3
+ import { defineReactorWith } from "./defineReactorShared.js"
4
+ import type {
5
+ DefineReactorSharedParameters,
6
+ DefineReactorResult,
7
+ } from "./defineReactorShared.js"
8
+
9
+ /** Options for {@link defineDisplayReactor}: `defineReactor`'s, plus validators. */
10
+ export interface DefineDisplayReactorOptions<
11
+ Service,
12
+ > extends DefineReactorSharedParameters {
13
+ /** Optional initial argument validators (receive display types). */
14
+ validators?: DisplayReactorParameters<Service>["validators"]
15
+ }
16
+
17
+ /**
18
+ * One-call bootstrap for a {@link DisplayReactor} and its React hooks: the
19
+ * display counterpart of `defineReactor`, taking the same options.
20
+ *
21
+ * A DisplayReactor takes and returns UI-friendly values — text for
22
+ * `Principal`, `nat`, `int` and 64-bit integers, hex for blobs — which suits
23
+ * forms and components. Its codecs are built on zod, so this adds zod to the
24
+ * bundle. `defineReactor`, which builds a plain `Reactor` with raw Candid
25
+ * values, does not need zod, but until its deprecated `display` flag is removed
26
+ * at the next major it bundles zod anyway; `createActorHooks(new Reactor(...))`
27
+ * is the setup without it.
28
+ *
29
+ * Replaces `defineReactor({ display: true })`, which is deprecated.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * import { defineDisplayReactor } from "@ic-reactor/react"
34
+ * import { idlFactory, type _SERVICE } from "./declarations/backend"
35
+ *
36
+ * export const { reactor, useActorQuery, useActorMutation, useAuth } =
37
+ * defineDisplayReactor<_SERVICE>({
38
+ * name: "backend",
39
+ * idlFactory,
40
+ * canisterId: "rrkah-fqaaa-aaaaa-aaaaq-cai",
41
+ * })
42
+ *
43
+ * function Balance() {
44
+ * // `nat` arrives as text, ready to render.
45
+ * const { data } = useActorQuery({ functionName: "get_balance" })
46
+ * return <span>{data}</span>
47
+ * }
48
+ * ```
49
+ */
50
+ export function defineDisplayReactor<Service = BaseActor>(
51
+ params: DefineDisplayReactorOptions<Service>
52
+ ): DefineReactorResult<Service, "display", DisplayReactor<Service>> {
53
+ return defineReactorWith<Service, "display", DisplayReactor<Service>>(
54
+ "defineDisplayReactor",
55
+ params,
56
+ (config) =>
57
+ new DisplayReactor<Service>({
58
+ ...config,
59
+ validators: params.validators,
60
+ })
61
+ )
62
+ }