@ic-reactor/react 3.12.5 → 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 +7 -18
  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 +3 -0
  34. package/dist/createMutation.d.ts.map +1 -1
  35. package/dist/createMutation.js +87 -80
  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 +7 -0
  66. package/dist/hooks/createAuthHooks.d.ts.map +1 -1
  67. package/dist/hooks/createAuthHooks.js +180 -20
  68. package/dist/hooks/createAuthHooks.js.map +1 -1
  69. package/dist/hooks/useActorInfiniteQuery.d.ts +34 -6
  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 +10 -7
  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 +15 -3
  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 +416 -15
  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 +20 -32
  130. package/src/createInfiniteQuery.ts +120 -28
  131. package/src/createMutation.ts +161 -178
  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 +206 -24
  140. package/src/hooks/useActorInfiniteQuery.ts +122 -49
  141. package/src/hooks/useActorMethod.ts +295 -92
  142. package/src/hooks/useActorMutation.ts +23 -23
  143. package/src/hooks/useActorQuery.ts +43 -10
  144. package/src/hooks/useActorSuspenseInfiniteQuery.ts +93 -50
  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 +463 -14
  151. package/src/utils.ts +387 -3
  152. package/src/validation.ts +43 -19
@@ -2,8 +2,10 @@ import { useMemo } from "react"
2
2
  import {
3
3
  QueryKey,
4
4
  useQuery,
5
+ skipToken,
5
6
  QueryObserverOptions,
6
7
  UseQueryResult,
8
+ type SkipToken,
7
9
  } from "@tanstack/react-query"
8
10
  import {
9
11
  FunctionName,
@@ -15,6 +17,7 @@ import {
15
17
  ReactorReturnErr,
16
18
  } from "@ic-reactor/core"
17
19
  import { CallConfig } from "@icp-sdk/core/agent"
20
+ import { retryOption, skippedQueryKey, useMountQueryClient } from "../utils.js"
18
21
 
19
22
  export interface UseActorQueryParameters<
20
23
  Service,
@@ -33,7 +36,13 @@ export interface UseActorQueryParameters<
33
36
  > {
34
37
  reactor: Reactor<Service, Transform>
35
38
  functionName: Method
36
- args?: ReactorArgs<Service, Method, Transform>
39
+ /**
40
+ * The method's arguments, or TanStack Query's `skipToken` while they are
41
+ * not known: the query then waits without fetching, in an entry of its own
42
+ * under its method's key, which no call's key shares, so it shows no data
43
+ * until the arguments arrive.
44
+ */
45
+ args?: ReactorArgs<Service, Method, Transform> | SkipToken
37
46
  callConfig?: CallConfig
38
47
  queryKey?: QueryKey
39
48
  }
@@ -59,19 +68,26 @@ export type UseActorQueryResult<
59
68
  * Hook for executing query calls on a canister.
60
69
  *
61
70
  * @example
62
- * const { data, isLoading } = useActorQuery({
71
+ * const { data, isLoading } = useReactorQuery({
63
72
  * reactor,
64
73
  * functionName: "getUser",
65
74
  * args: ["user-123"],
66
75
  * })
67
76
  *
68
77
  * // With select transformation
69
- * const { data } = useActorQuery({
78
+ * const { data } = useReactorQuery({
70
79
  * reactor,
71
80
  * functionName: "getUser",
72
81
  * args: ["user-123"],
73
82
  * select: (user) => user.name,
74
83
  * })
84
+ *
85
+ * // Wait for the args: no fetch until userId is known
86
+ * const { data } = useReactorQuery({
87
+ * reactor,
88
+ * functionName: "getUser",
89
+ * args: userId ? [userId] : skipToken,
90
+ * })
75
91
  */
76
92
  export const useActorQuery = <
77
93
  Service,
@@ -91,15 +107,31 @@ export const useActorQuery = <
91
107
  Transform,
92
108
  Selected
93
109
  >): UseActorQueryResult<Service, Method, Transform, Selected> => {
110
+ useMountQueryClient(reactor.queryClient)
111
+
94
112
  // Memoize query options to prevent unnecessary re-computations
95
- const { queryKey, queryFn } = useMemo(
113
+ // For an update method, the options also carry its default `retry`; see
114
+ // `Reactor.getQueryRetry`.
115
+ const { queryKey, queryFn, retry } = useMemo(
96
116
  () =>
97
- reactor.getQueryOptions<Method>({
98
- callConfig,
99
- functionName,
100
- args,
101
- queryKey: defaultQueryKey,
102
- }),
117
+ args === skipToken
118
+ ? // Waiting for its args: an entry of its own under the method's key,
119
+ // with nothing to run until then; see skippedQueryKey.
120
+ {
121
+ queryKey: skippedQueryKey(
122
+ reactor.generateQueryKey({ functionName }, callConfig),
123
+ "query"
124
+ ),
125
+ // Kept as the unique symbol, which an object literal widens.
126
+ queryFn: skipToken as SkipToken,
127
+ retry: undefined,
128
+ }
129
+ : reactor.getQueryOptions<Method>({
130
+ callConfig,
131
+ functionName,
132
+ args,
133
+ queryKey: defaultQueryKey,
134
+ }),
103
135
  // `canisterId` is mutable reactor state that `setCanisterId` can change,
104
136
  // while `reactor` itself stays the same object — so it has to be a
105
137
  // dependency in its own right or the key stays pinned to the old canister.
@@ -118,6 +150,7 @@ export const useActorQuery = <
118
150
  queryFn,
119
151
  ...options,
120
152
  queryKey,
153
+ ...retryOption(options.retry, retry),
121
154
  },
122
155
  reactor.queryClient
123
156
  )
@@ -16,7 +16,14 @@ import {
16
16
  ReactorReturnErr,
17
17
  } from "@ic-reactor/core"
18
18
  import { CallConfig } from "@icp-sdk/core/agent"
19
- import { mergeFactoryQueryKey, normalizeQueryData } from "../utils.js"
19
+ import {
20
+ callConfigForKey,
21
+ mergeFactoryQueryKey,
22
+ mountWhileSuspended,
23
+ normalizeQueryData,
24
+ retryOption,
25
+ useMountQueryClient,
26
+ } from "../utils.js"
20
27
 
21
28
  /**
22
29
  * Parameters for useActorSuspenseInfiniteQuery hook.
@@ -47,6 +54,18 @@ export interface UseActorSuspenseInfiniteQueryParameters<
47
54
  functionName: Method
48
55
  /** Function to get args from page parameter */
49
56
  getArgs: (pageParam: TPageParam) => ReactorArgs<Service, Method, Transform>
57
+ /**
58
+ * Narrows what the cache key derives from the call arguments.
59
+ *
60
+ * By default the key is scoped by `getArgs(initialPageParam)`, so two
61
+ * infinite queries on the same method with different arguments stay in
62
+ * separate cache entries. Supply this when those args embed the cursor and
63
+ * only part of them identifies the query — return the stable, serializable
64
+ * portion (typically everything except the pagination field). It is required
65
+ * when `initialPageParam` changes between renders, as `Date.now()` does:
66
+ * without it every render keys a new query.
67
+ */
68
+ getKeyArgs?: (args: ReactorArgs<Service, Method, Transform>) => unknown
50
69
  /** Agent call configuration (effectiveCanisterId, etc.) */
51
70
  callConfig?: CallConfig
52
71
  /** Custom query key (auto-generated if not provided) */
@@ -100,10 +119,10 @@ export type UseActorSuspenseInfiniteQueryResult<
100
119
  * Hook for executing suspense-enabled infinite/paginated query calls on a canister.
101
120
  *
102
121
  * @example
103
- * const { data, fetchNextPage, hasNextPage } = useActorSuspenseInfiniteQuery({
122
+ * const { data, fetchNextPage, hasNextPage } = useReactorSuspenseInfiniteQuery({
104
123
  * reactor,
105
124
  * functionName: "getItems",
106
- * getArgs: (pageParam) => [{ offset: pageParam, limit: 10 }],
125
+ * getArgs: (pageParam) => [{ offset: pageParam, limit: 10 }] as const,
107
126
  * initialPageParam: 0,
108
127
  * getNextPageParam: (lastPage) => lastPage.nextOffset,
109
128
  * })
@@ -121,6 +140,7 @@ export const useActorSuspenseInfiniteQuery = <
121
140
  reactor,
122
141
  functionName,
123
142
  getArgs,
143
+ getKeyArgs,
124
144
  callConfig,
125
145
  queryKey,
126
146
  ...options
@@ -137,49 +157,59 @@ export const useActorSuspenseInfiniteQuery = <
137
157
  TPageParam,
138
158
  Selected
139
159
  > => {
160
+ useMountQueryClient(reactor.queryClient)
161
+
140
162
  // Always pass queryKey through generateQueryKey so it is merged with the
141
163
  // reactor/function identity. Using the custom key verbatim would cause cache
142
164
  // collisions if two different actors or methods share the same key string.
143
- const baseQueryKey = useMemo(
144
- () =>
145
- reactor.generateQueryKey(
146
- {
147
- functionName,
148
- // Fold the call arguments into the key. They live in the `getArgs`
149
- // closure rather than in the config, so without this two hooks on the
150
- // same method with different arguments share one cache entry and
151
- // serve each other's pages.
152
- queryKey: mergeFactoryQueryKey(
153
- queryKey,
154
- undefined,
155
- getArgs(options.initialPageParam)
156
- ),
157
- },
158
- callConfig
159
- ),
160
- [
161
- queryKey,
162
- reactor,
163
- // `canisterId` is mutable reactor state that `setCanisterId` can change,
164
- // while `reactor` itself stays the same object — so it has to be a
165
- // dependency in its own right or the key stays pinned to the old canister
166
- // while the queryFn already calls the new one.
167
- reactor.canisterId?.toString(),
168
- functionName,
169
- callConfig,
170
- getArgs,
171
- options.initialPageParam,
172
- ]
173
- )
165
+ const baseQueryKey = useMemo(() => {
166
+ // Fold the call arguments into the key. They live in the `getArgs`
167
+ // closure rather than in the config, so without this two hooks on the
168
+ // same method with different arguments share one cache entry and serve
169
+ // each other's pages. `getKeyArgs` narrows them exactly as it does for the
170
+ // factories, which the bound hook's config type always accepted: it used
171
+ // to be ignored here, so the cursor stayed in the key, and an
172
+ // `initialPageParam` that changes every render (`Date.now()`) keyed a new
173
+ // query on every render — an endless loop of first-page fetches.
174
+ const initialArgs = getArgs(options.initialPageParam)
175
+ const keyArgs = getKeyArgs?.(initialArgs) ?? initialArgs
176
+
177
+ return reactor.generateQueryKey(
178
+ {
179
+ functionName,
180
+ queryKey: mergeFactoryQueryKey(queryKey, undefined, keyArgs),
181
+ },
182
+ callConfig
183
+ )
184
+ }, [
185
+ queryKey,
186
+ reactor,
187
+ // `canisterId` is mutable reactor state that `setCanisterId` can change,
188
+ // while `reactor` itself stays the same object — so it has to be a
189
+ // dependency in its own right or the key stays pinned to the old canister
190
+ // while the queryFn already calls the new one.
191
+ reactor.canisterId?.toString(),
192
+ functionName,
193
+ callConfig,
194
+ getArgs,
195
+ getKeyArgs,
196
+ options.initialPageParam,
197
+ ])
174
198
 
175
199
  // Memoize queryFn to prevent recreation on every render
176
200
  const queryFn = useCallback(
177
- async ({ pageParam }: { pageParam: TPageParam }) => {
201
+ async ({
202
+ pageParam,
203
+ queryKey: fetchedKey,
204
+ }: {
205
+ pageParam: TPageParam
206
+ queryKey: QueryKey
207
+ }) => {
178
208
  const args = getArgs(pageParam)
179
209
  const result = await reactor.callMethod({
180
210
  functionName,
181
211
  args,
182
- callConfig,
212
+ callConfig: callConfigForKey(fetchedKey, callConfig),
183
213
  })
184
214
  return normalizeQueryData<ReactorReturnOk<Service, Method, Transform>>(
185
215
  result as ReactorReturnOk<Service, Method, Transform>
@@ -188,18 +218,31 @@ export const useActorSuspenseInfiniteQuery = <
188
218
  [reactor, functionName, getArgs, callConfig]
189
219
  )
190
220
 
191
- return useSuspenseInfiniteQuery(
192
- {
193
- queryKey: baseQueryKey,
194
- queryFn,
195
- ...options,
196
- } as any,
197
- reactor.queryClient
198
- ) as UseActorSuspenseInfiniteQueryResult<
199
- Service,
200
- Method,
201
- Transform,
202
- TPageParam,
203
- Selected
204
- >
221
+ // The method's default `retry`: for an update method, only failures that
222
+ // prove the canister never ran the call; see `Reactor.getQueryRetry`.
223
+ const defaultRetry = useMemo(
224
+ () => reactor.getQueryRetry(functionName, baseQueryKey),
225
+ [reactor, functionName, baseQueryKey]
226
+ )
227
+
228
+ try {
229
+ return useSuspenseInfiniteQuery(
230
+ {
231
+ queryKey: baseQueryKey,
232
+ queryFn,
233
+ ...options,
234
+ ...retryOption(options.retry, defaultRetry),
235
+ } as any,
236
+ reactor.queryClient
237
+ ) as UseActorSuspenseInfiniteQueryResult<
238
+ Service,
239
+ Method,
240
+ Transform,
241
+ TPageParam,
242
+ Selected
243
+ >
244
+ } catch (thrown) {
245
+ mountWhileSuspended(reactor.queryClient, thrown)
246
+ throw thrown
247
+ }
205
248
  }
@@ -16,6 +16,11 @@ import {
16
16
  ReactorReturnErr,
17
17
  } from "@ic-reactor/core"
18
18
  import { CallConfig } from "@icp-sdk/core/agent"
19
+ import {
20
+ mountWhileSuspended,
21
+ retryOption,
22
+ useMountQueryClient,
23
+ } from "../utils.js"
19
24
 
20
25
  export interface UseActorSuspenseQueryParameters<
21
26
  Service,
@@ -63,14 +68,14 @@ export type UseActorSuspenseQueryResult<
63
68
  * Hook for executing suspense-enabled query calls on a canister.
64
69
  *
65
70
  * @example
66
- * const { data } = useActorSuspenseQuery({
71
+ * const { data } = useReactorSuspenseQuery({
67
72
  * reactor,
68
73
  * functionName: "getUser",
69
74
  * args: ["user-123"],
70
75
  * })
71
76
  *
72
77
  * // With select transformation
73
- * const { data } = useActorSuspenseQuery({
78
+ * const { data } = useReactorSuspenseQuery({
74
79
  * reactor,
75
80
  * functionName: "getUser",
76
81
  * args: ["user-123"],
@@ -95,8 +100,12 @@ export const useActorSuspenseQuery = <
95
100
  Transform,
96
101
  Selected
97
102
  >): UseActorSuspenseQueryResult<Service, Method, Transform, Selected> => {
103
+ useMountQueryClient(reactor.queryClient)
104
+
98
105
  // Memoize query options to prevent unnecessary re-computations
99
- const { queryKey, queryFn } = useMemo(
106
+ // For an update method, the options also carry its default `retry`; see
107
+ // `Reactor.getQueryRetry`.
108
+ const { queryKey, queryFn, retry } = useMemo(
100
109
  () =>
101
110
  reactor.getQueryOptions<Method>({
102
111
  callConfig,
@@ -117,16 +126,22 @@ export const useActorSuspenseQuery = <
117
126
  ]
118
127
  )
119
128
 
120
- return useSuspenseQuery(
121
- {
122
- // Suspense queries don't support skipToken, so cast the queryFn
123
- queryFn: queryFn as QueryFunction<
124
- ReactorQueryData<ReactorReturnOk<Service, Method, Transform>>,
125
- QueryKey
126
- >,
127
- ...options,
128
- queryKey,
129
- },
130
- reactor.queryClient
131
- )
129
+ try {
130
+ return useSuspenseQuery(
131
+ {
132
+ // Suspense queries don't support skipToken, so cast the queryFn
133
+ queryFn: queryFn as QueryFunction<
134
+ ReactorQueryData<ReactorReturnOk<Service, Method, Transform>>,
135
+ QueryKey
136
+ >,
137
+ ...options,
138
+ queryKey,
139
+ ...retryOption(options.retry, retry),
140
+ },
141
+ reactor.queryClient
142
+ )
143
+ } catch (thrown) {
144
+ mountWhileSuspended(reactor.queryClient, thrown)
145
+ throw thrown
146
+ }
132
147
  }
package/src/index.ts CHANGED
@@ -11,6 +11,8 @@ export * from "./validation.js"
11
11
  // React-specific exports
12
12
  export * from "./createActorHooks.js"
13
13
  export * from "./defineReactor.js"
14
+ export * from "./defineDisplayReactor.js"
15
+ export * from "./createReactorProvider.js"
14
16
 
15
17
  export * from "./createQuery.js"
16
18
  export * from "./createSuspenseQuery.js"
@@ -21,3 +23,9 @@ export * from "./createMutation.js"
21
23
  export * from "./auth/index.js"
22
24
 
23
25
  export * from "./types.js"
26
+
27
+ // TanStack Query's marker for a query whose args are not known yet, which the
28
+ // non-suspense query hooks and createQueryFactory take in place of args. It is
29
+ // the same symbol as @tanstack/react-query's, so either import works.
30
+ export { skipToken } from "@tanstack/react-query"
31
+ export type { SkipToken } from "@tanstack/react-query"
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Which `AuthenticationManager`s a `createReactorProvider` value owns, for the
3
+ * provider to dispose when its tree unmounts. Not part of the public API.
4
+ *
5
+ * A value owns the managers built for it: those constructed while its factory
6
+ * ran, and those a `defineReactor` or `defineDisplayReactor` result in it
7
+ * builds later, on first use. A manager built elsewhere and handed in, such as
8
+ * an app-wide one every provider shares, belongs to whoever built it, and
9
+ * disposing it on each remount would stop its client following the session.
10
+ *
11
+ * A result's `authentication` is a getter that builds the manager on first
12
+ * read, and building one loads the optional `@icp-sdk/auth` peer in a browser.
13
+ * A provider that read it to release the manager would build one only to
14
+ * dispose it, for every reactor whose tree never signed in. Each result
15
+ * registers a reader here instead, which answers without building anything.
16
+ *
17
+ * Kept apart from `defineReactorShared.ts` so that the provider does not pull
18
+ * that module's reactor setup into an app that builds its reactors by hand.
19
+ */
20
+ import type { AuthenticationManager } from "./auth/authentication-manager.js"
21
+
22
+ const readers = new WeakMap<object, () => AuthenticationManager | undefined>()
23
+
24
+ /** The managers constructed during a running {@link collectAuthentication}. */
25
+ let collecting: Set<AuthenticationManager> | undefined
26
+
27
+ /**
28
+ * Records how to read the manager `owner` built, once it has built one.
29
+ *
30
+ * @internal
31
+ */
32
+ export function registerAuthentication(
33
+ owner: object,
34
+ read: () => AuthenticationManager | undefined
35
+ ): void {
36
+ readers.set(owner, read)
37
+ }
38
+
39
+ /**
40
+ * The manager a registered result built, or `undefined` for one that has not
41
+ * built one yet, for one that was handed a manager, and for anything that was
42
+ * never registered.
43
+ *
44
+ * @internal
45
+ */
46
+ export function authenticationOf(
47
+ owner: object
48
+ ): AuthenticationManager | undefined {
49
+ return readers.get(owner)?.()
50
+ }
51
+
52
+ /**
53
+ * Called by each `AuthenticationManager` as it is constructed.
54
+ *
55
+ * @internal
56
+ */
57
+ export function recordAuthentication(manager: AuthenticationManager): void {
58
+ collecting?.add(manager)
59
+ }
60
+
61
+ /**
62
+ * Runs `build` and returns what it returned, with every manager constructed
63
+ * while it ran. `build` runs synchronously, so a server rendering several
64
+ * requests at once cannot interleave another request's managers.
65
+ *
66
+ * @internal
67
+ */
68
+ export function collectAuthentication<T>(build: () => T): {
69
+ value: T
70
+ built: AuthenticationManager[]
71
+ } {
72
+ const outer = collecting
73
+ const built = new Set<AuthenticationManager>()
74
+ collecting = built
75
+ try {
76
+ const value = build()
77
+ return { value, built: [...built] }
78
+ } finally {
79
+ collecting = outer
80
+ }
81
+ }
package/src/server.ts ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The `react-server` entry of `@ic-reactor/react`.
3
+ *
4
+ * React Server Components are bundled with the `react-server` export
5
+ * condition, and `package.json` routes that condition here instead of to
6
+ * `index.ts`. The main entry also loads every hook, and a bundler rejects a
7
+ * hook import anywhere in the server graph, so a server component that
8
+ * imported only `Reactor` from it failed to build.
9
+ *
10
+ * This entry carries the core runtime (`Reactor`, `DisplayReactor`,
11
+ * `ClientManager`, the error classes and utilities) and the framework-free
12
+ * validation helpers, and nothing that imports React. A hook or a hook factory
13
+ * imported in a server component is therefore a missing export at build time,
14
+ * which is the error to get: those only run in a `"use client"` module.
15
+ *
16
+ * TypeScript does not read the `react-server` condition, so types still come
17
+ * from the main entry.
18
+ */
19
+ export * from "@ic-reactor/core"
20
+
21
+ // Plain functions over `ValidationError`, useful in a server action that maps
22
+ // a rejected call onto form field errors.
23
+ export * from "./validation.js"
package/src/testing.ts ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `@ic-reactor/react/testing`: the testing utilities of
3
+ * `@ic-reactor/core/testing`, re-exported for an app that depends on
4
+ * `@ic-reactor/react` alone. The main entry re-exports `@ic-reactor/core` the
5
+ * same way, but a package manager that installs strictly does not let such an
6
+ * app import `@ic-reactor/core/testing` itself.
7
+ *
8
+ * Nothing here imports React, and neither the main entry nor the
9
+ * `react-server` entry imports this one, so none of it reaches an app bundle.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { createTestCanister, installFakeReplica } from "@ic-reactor/react/testing"
14
+ * ```
15
+ *
16
+ * @packageDocumentation
17
+ */
18
+ export * from "@ic-reactor/core/testing"