@ic-reactor/react 3.13.0 → 4.0.0-beta.2

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 (174) hide show
  1. package/README.md +237 -787
  2. package/dist/index.d.ts +260 -16
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +471 -23
  5. package/dist/index.js.map +1 -1
  6. package/llms.txt +82 -278
  7. package/package.json +11 -39
  8. package/src/index.tsx +612 -0
  9. package/dist/auth/auth-client-compat.d.ts +0 -122
  10. package/dist/auth/auth-client-compat.d.ts.map +0 -1
  11. package/dist/auth/auth-client-compat.js +0 -162
  12. package/dist/auth/auth-client-compat.js.map +0 -1
  13. package/dist/auth/authentication-manager.d.ts +0 -405
  14. package/dist/auth/authentication-manager.d.ts.map +0 -1
  15. package/dist/auth/authentication-manager.js +0 -1537
  16. package/dist/auth/authentication-manager.js.map +0 -1
  17. package/dist/auth/constants.d.ts +0 -24
  18. package/dist/auth/constants.d.ts.map +0 -1
  19. package/dist/auth/constants.js +0 -24
  20. package/dist/auth/constants.js.map +0 -1
  21. package/dist/auth/createIdentityAttributeHooks.d.ts +0 -14
  22. package/dist/auth/createIdentityAttributeHooks.d.ts.map +0 -1
  23. package/dist/auth/createIdentityAttributeHooks.js +0 -122
  24. package/dist/auth/createIdentityAttributeHooks.js.map +0 -1
  25. package/dist/auth/identity-attributes-manager.d.ts +0 -27
  26. package/dist/auth/identity-attributes-manager.d.ts.map +0 -1
  27. package/dist/auth/identity-attributes-manager.js +0 -191
  28. package/dist/auth/identity-attributes-manager.js.map +0 -1
  29. package/dist/auth/identity-attributes.d.ts +0 -19
  30. package/dist/auth/identity-attributes.d.ts.map +0 -1
  31. package/dist/auth/identity-attributes.js +0 -227
  32. package/dist/auth/identity-attributes.js.map +0 -1
  33. package/dist/auth/index.d.ts +0 -8
  34. package/dist/auth/index.d.ts.map +0 -1
  35. package/dist/auth/index.js +0 -8
  36. package/dist/auth/index.js.map +0 -1
  37. package/dist/auth/local-ii-probe.d.ts +0 -57
  38. package/dist/auth/local-ii-probe.d.ts.map +0 -1
  39. package/dist/auth/local-ii-probe.js +0 -121
  40. package/dist/auth/local-ii-probe.js.map +0 -1
  41. package/dist/auth/types.d.ts +0 -222
  42. package/dist/auth/types.d.ts.map +0 -1
  43. package/dist/auth/types.js +0 -2
  44. package/dist/auth/types.js.map +0 -1
  45. package/dist/createActorHooks.d.ts +0 -41
  46. package/dist/createActorHooks.d.ts.map +0 -1
  47. package/dist/createActorHooks.js +0 -17
  48. package/dist/createActorHooks.js.map +0 -1
  49. package/dist/createInfiniteQuery.d.ts +0 -185
  50. package/dist/createInfiniteQuery.d.ts.map +0 -1
  51. package/dist/createInfiniteQuery.js +0 -198
  52. package/dist/createInfiniteQuery.js.map +0 -1
  53. package/dist/createMutation.d.ts +0 -33
  54. package/dist/createMutation.d.ts.map +0 -1
  55. package/dist/createMutation.js +0 -199
  56. package/dist/createMutation.js.map +0 -1
  57. package/dist/createQuery.d.ts +0 -63
  58. package/dist/createQuery.d.ts.map +0 -1
  59. package/dist/createQuery.js +0 -204
  60. package/dist/createQuery.js.map +0 -1
  61. package/dist/createReactorProvider.d.ts +0 -158
  62. package/dist/createReactorProvider.d.ts.map +0 -1
  63. package/dist/createReactorProvider.js +0 -256
  64. package/dist/createReactorProvider.js.map +0 -1
  65. package/dist/createSuspenseInfiniteQuery.d.ts +0 -154
  66. package/dist/createSuspenseInfiniteQuery.d.ts.map +0 -1
  67. package/dist/createSuspenseInfiniteQuery.js +0 -209
  68. package/dist/createSuspenseInfiniteQuery.js.map +0 -1
  69. package/dist/createSuspenseQuery.d.ts +0 -46
  70. package/dist/createSuspenseQuery.d.ts.map +0 -1
  71. package/dist/createSuspenseQuery.js +0 -158
  72. package/dist/createSuspenseQuery.js.map +0 -1
  73. package/dist/defineDisplayReactor.d.ts +0 -43
  74. package/dist/defineDisplayReactor.d.ts.map +0 -1
  75. package/dist/defineDisplayReactor.js +0 -42
  76. package/dist/defineDisplayReactor.js.map +0 -1
  77. package/dist/defineReactor.d.ts +0 -99
  78. package/dist/defineReactor.d.ts.map +0 -1
  79. package/dist/defineReactor.js +0 -15
  80. package/dist/defineReactor.js.map +0 -1
  81. package/dist/defineReactorShared.d.ts +0 -84
  82. package/dist/defineReactorShared.d.ts.map +0 -1
  83. package/dist/defineReactorShared.js +0 -139
  84. package/dist/defineReactorShared.js.map +0 -1
  85. package/dist/hooks/createAuthHooks.d.ts +0 -50
  86. package/dist/hooks/createAuthHooks.d.ts.map +0 -1
  87. package/dist/hooks/createAuthHooks.js +0 -291
  88. package/dist/hooks/createAuthHooks.js.map +0 -1
  89. package/dist/hooks/index.d.ts +0 -21
  90. package/dist/hooks/index.d.ts.map +0 -1
  91. package/dist/hooks/index.js +0 -24
  92. package/dist/hooks/index.js.map +0 -1
  93. package/dist/hooks/useActorInfiniteQuery.d.ts +0 -67
  94. package/dist/hooks/useActorInfiniteQuery.d.ts.map +0 -1
  95. package/dist/hooks/useActorInfiniteQuery.js +0 -89
  96. package/dist/hooks/useActorInfiniteQuery.js.map +0 -1
  97. package/dist/hooks/useActorMethod.d.ts +0 -148
  98. package/dist/hooks/useActorMethod.d.ts.map +0 -1
  99. package/dist/hooks/useActorMethod.js +0 -394
  100. package/dist/hooks/useActorMethod.js.map +0 -1
  101. package/dist/hooks/useActorMutation.d.ts +0 -51
  102. package/dist/hooks/useActorMutation.d.ts.map +0 -1
  103. package/dist/hooks/useActorMutation.js +0 -70
  104. package/dist/hooks/useActorMutation.js.map +0 -1
  105. package/dist/hooks/useActorQuery.d.ts +0 -45
  106. package/dist/hooks/useActorQuery.d.ts.map +0 -1
  107. package/dist/hooks/useActorQuery.js +0 -67
  108. package/dist/hooks/useActorQuery.js.map +0 -1
  109. package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts +0 -51
  110. package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts.map +0 -1
  111. package/dist/hooks/useActorSuspenseInfiniteQuery.js +0 -76
  112. package/dist/hooks/useActorSuspenseInfiniteQuery.js.map +0 -1
  113. package/dist/hooks/useActorSuspenseQuery.d.ts +0 -32
  114. package/dist/hooks/useActorSuspenseQuery.d.ts.map +0 -1
  115. package/dist/hooks/useActorSuspenseQuery.js +0 -58
  116. package/dist/hooks/useActorSuspenseQuery.js.map +0 -1
  117. package/dist/ownedAuthentication.d.ts +0 -52
  118. package/dist/ownedAuthentication.d.ts.map +0 -1
  119. package/dist/ownedAuthentication.js +0 -49
  120. package/dist/ownedAuthentication.js.map +0 -1
  121. package/dist/server.d.ts +0 -21
  122. package/dist/server.d.ts.map +0 -1
  123. package/dist/server.js +0 -23
  124. package/dist/server.js.map +0 -1
  125. package/dist/testing.d.ts +0 -19
  126. package/dist/testing.d.ts.map +0 -1
  127. package/dist/testing.js +0 -19
  128. package/dist/testing.js.map +0 -1
  129. package/dist/types.d.ts +0 -671
  130. package/dist/types.d.ts.map +0 -1
  131. package/dist/types.js +0 -5
  132. package/dist/types.js.map +0 -1
  133. package/dist/utils.d.ts +0 -207
  134. package/dist/utils.d.ts.map +0 -1
  135. package/dist/utils.js +0 -405
  136. package/dist/utils.js.map +0 -1
  137. package/dist/validation.d.ts +0 -136
  138. package/dist/validation.d.ts.map +0 -1
  139. package/dist/validation.js +0 -144
  140. package/dist/validation.js.map +0 -1
  141. package/src/auth/auth-client-compat.ts +0 -273
  142. package/src/auth/authentication-manager.ts +0 -1682
  143. package/src/auth/constants.ts +0 -32
  144. package/src/auth/createIdentityAttributeHooks.ts +0 -169
  145. package/src/auth/identity-attributes-manager.ts +0 -226
  146. package/src/auth/identity-attributes.ts +0 -345
  147. package/src/auth/index.ts +0 -7
  148. package/src/auth/local-ii-probe.ts +0 -173
  149. package/src/auth/types.ts +0 -243
  150. package/src/createActorHooks.ts +0 -208
  151. package/src/createInfiniteQuery.ts +0 -670
  152. package/src/createMutation.ts +0 -324
  153. package/src/createQuery.ts +0 -369
  154. package/src/createReactorProvider.ts +0 -365
  155. package/src/createSuspenseInfiniteQuery.ts +0 -651
  156. package/src/createSuspenseQuery.ts +0 -304
  157. package/src/defineDisplayReactor.ts +0 -62
  158. package/src/defineReactor.ts +0 -142
  159. package/src/defineReactorShared.ts +0 -268
  160. package/src/hooks/createAuthHooks.ts +0 -371
  161. package/src/hooks/index.ts +0 -103
  162. package/src/hooks/useActorInfiniteQuery.ts +0 -278
  163. package/src/hooks/useActorMethod.ts +0 -710
  164. package/src/hooks/useActorMutation.ts +0 -205
  165. package/src/hooks/useActorQuery.ts +0 -157
  166. package/src/hooks/useActorSuspenseInfiniteQuery.ts +0 -248
  167. package/src/hooks/useActorSuspenseQuery.ts +0 -147
  168. package/src/index.ts +0 -31
  169. package/src/ownedAuthentication.ts +0 -81
  170. package/src/server.ts +0 -23
  171. package/src/testing.ts +0 -18
  172. package/src/types.ts +0 -948
  173. package/src/utils.ts +0 -505
  174. package/src/validation.ts +0 -226
package/src/types.ts DELETED
@@ -1,948 +0,0 @@
1
- /**
2
- * Shared type definitions for query factories (createQuery, createSuspenseQuery, etc.)
3
- */
4
-
5
- import type {
6
- FunctionName,
7
- ReactorReturnOk,
8
- ReactorQueryData,
9
- ReactorReturnErr,
10
- ReactorArgs,
11
- BaseActor,
12
- TransformKey,
13
- TransformReturnRegistry,
14
- ErrResult,
15
- ActorMethodReturnType,
16
- } from "@ic-reactor/core"
17
- import { CanisterError } from "@ic-reactor/core"
18
- import { CallConfig } from "@icp-sdk/core/agent"
19
- import {
20
- QueryKey,
21
- QueryObserverOptions,
22
- UseQueryOptions,
23
- UseQueryResult,
24
- UseSuspenseQueryOptions,
25
- UseSuspenseQueryResult,
26
- UseMutationOptions,
27
- UseMutationResult,
28
- SkipToken,
29
- } from "@tanstack/react-query"
30
-
31
- // ============================================================================
32
- // Utility Types
33
- // ============================================================================
34
-
35
- // NoInfer prevents TypeScript from inferring a type parameter from a particular position
36
- // This is available in TypeScript 5.4+ natively, but we define it for compatibility
37
- export type NoInfer<T> = [T][T extends any ? 0 : never]
38
- // ============================================================================
39
- // Base Query Data Types
40
- // ============================================================================
41
-
42
- /** The raw data type returned by the query function (before select) */
43
- export type QueryFnData<
44
- Service = BaseActor,
45
- Method extends FunctionName<Service> = FunctionName<Service>,
46
- Transform extends TransformKey = "candid",
47
- > = ReactorQueryData<ReactorReturnOk<Service, Method, Transform>>
48
-
49
- /** The error type for queries */
50
- export type QueryError<
51
- Service = BaseActor,
52
- Method extends FunctionName<Service> = FunctionName<Service>,
53
- Transform extends TransformKey = "candid",
54
- > = ReactorReturnErr<Service, Method, Transform>
55
-
56
- // ============================================================================
57
- // Base Query Configuration
58
- // ============================================================================
59
-
60
- /**
61
- * Base configuration for query wrappers (shared between regular and suspense).
62
- *
63
- * @template Service - The actor interface type
64
- * @template Method - The method name on the actor
65
- * @template Transform - The transformation key (identity, display, etc.)
66
- * @template Selected - The type returned after select transformation
67
- */
68
- export interface BaseQueryConfig<
69
- Service = BaseActor,
70
- Method extends FunctionName<Service> = FunctionName<Service>,
71
- Transform extends TransformKey = "candid",
72
- Selected = QueryFnData<Service, Method, Transform>,
73
- > extends Omit<
74
- QueryObserverOptions<
75
- QueryFnData<Service, Method, Transform>,
76
- ReactorReturnErr<Service, Method, Transform>,
77
- Selected,
78
- QueryFnData<Service, Method, Transform>,
79
- QueryKey
80
- >,
81
- "queryFn" | "queryKey"
82
- > {
83
- /** The method to call on the canister */
84
- functionName: Method
85
- /** Arguments to pass to the method (if any) */
86
- args?: ReactorArgs<Service, Method, Transform>
87
- /**
88
- * Call configuration for the method, as `Reactor.callMethod` takes it: a
89
- * `canisterId` sends the query to another canister of the same interface,
90
- * an `agent` sends it through another agent, `effectiveCanisterId` routes
91
- * it. The query key carries what it sets, as the hooks' keys do, so the
92
- * answer is cached apart from the reactor's own canister and agent, and
93
- * `getQueryKey()`, `invalidate()` and the other cache controls act on that
94
- * entry.
95
- *
96
- * @example
97
- * ```typescript
98
- * // The same ledger interface, another token's canister
99
- * const ckbtcSymbol = createQuery(ledger, {
100
- * functionName: "icrc1_symbol",
101
- * callConfig: { canisterId: "mxzaz-hqaaa-aaaar-qaada-cai" },
102
- * })
103
- * ```
104
- */
105
- callConfig?: CallConfig
106
- /** The query key to use for this query */
107
- queryKey?: QueryKey
108
- /**
109
- * How long data stays fresh before refetching, in milliseconds.
110
- *
111
- * `createQuery`, `createSuspenseQuery` and their factories default to 5
112
- * minutes. The bound `useActorQuery` and `useActorSuspenseQuery` hooks (from
113
- * `createActorHooks` or `defineReactor`) set no default and leave it to
114
- * TanStack Query, which reads the QueryClient's
115
- * `defaultOptions.queries.staleTime`. With that unset too, `useActorQuery`
116
- * uses 0 and `useActorSuspenseQuery` uses 1 second, TanStack Query's
117
- * fallback for suspense queries.
118
- */
119
- staleTime?: number
120
- /** Transform the raw result before returning */
121
- select?: (data: QueryFnData<Service, Method, Transform>) => Selected
122
- }
123
-
124
- /**
125
- * Configuration for createQuery (regular useQuery).
126
- * Alias for BaseQueryConfig for clarity.
127
- */
128
- export type QueryConfig<
129
- Service = BaseActor,
130
- Method extends FunctionName<Service> = FunctionName<Service>,
131
- Transform extends TransformKey = "candid",
132
- Selected = QueryFnData<Service, Method, Transform>,
133
- > = BaseQueryConfig<Service, Method, Transform, Selected>
134
-
135
- /**
136
- * Configuration for the non-suspense query hook of `createActorHooks` and
137
- * `defineReactor` (`useActorQuery`): a {@link QueryConfig} whose `args` may
138
- * also be TanStack Query's `skipToken`, for a query whose arguments are not
139
- * known yet.
140
- *
141
- * A skipped query does not fetch. It has an entry of its own under its
142
- * method's key (at the canister and agent `callConfig` names), which no
143
- * call's key shares, so it shows no data until the arguments arrive, not
144
- * even that of a call made without arguments. Once `args` holds arguments,
145
- * the query is keyed and fetched as usual. Its `refetch()` has nothing to
146
- * run: TanStack Query answers it with a "Missing queryFn" error, so offer a
147
- * refresh only once the arguments exist.
148
- *
149
- * The suspense hooks do not take `skipToken`: TanStack Query has no way to
150
- * suspend on a query that cannot run.
151
- *
152
- * @example
153
- * ```typescript
154
- * import { skipToken } from "@ic-reactor/react"
155
- *
156
- * function Balance({ owner }: { owner?: string }) {
157
- * // No `!`, no placeholder account, no `enabled`
158
- * const { data } = useActorQuery({
159
- * functionName: "icrc1_balance_of",
160
- * args: owner ? [{ owner }] : skipToken,
161
- * })
162
- * }
163
- * ```
164
- */
165
- export interface SkippableQueryConfig<
166
- Service = BaseActor,
167
- Method extends FunctionName<Service> = FunctionName<Service>,
168
- Transform extends TransformKey = "candid",
169
- Selected = QueryFnData<Service, Method, Transform>,
170
- > extends Omit<QueryConfig<Service, Method, Transform, Selected>, "args"> {
171
- /**
172
- * Arguments to pass to the method, or `skipToken` while they are not
173
- * known: the query then waits without fetching.
174
- */
175
- args?: ReactorArgs<Service, Method, Transform> | SkipToken
176
- }
177
-
178
- /**
179
- * Configuration for createSuspenseQuery (useSuspenseQuery).
180
- * Alias for BaseQueryConfig for clarity.
181
- */
182
- export type SuspenseQueryConfig<
183
- Service = BaseActor,
184
- Method extends FunctionName<Service> = FunctionName<Service>,
185
- Transform extends TransformKey = "candid",
186
- Selected = QueryFnData<Service, Method, Transform>,
187
- > = BaseQueryConfig<Service, Method, Transform, Selected>
188
-
189
- // ============================================================================
190
- // Factory Configuration (without args)
191
- // ============================================================================
192
-
193
- /**
194
- * Configuration for createQueryFactory (args are provided at call time).
195
- */
196
- export type QueryFactoryConfig<
197
- Service = BaseActor,
198
- Method extends FunctionName<Service> = FunctionName<Service>,
199
- Transform extends TransformKey = "candid",
200
- Selected = QueryFnData<Service, Method, Transform>,
201
- > = Omit<QueryConfig<Service, Method, Transform, Selected>, "args">
202
-
203
- /**
204
- * Configuration for createSuspenseQueryFactory (args are provided at call time).
205
- */
206
- export type SuspenseQueryFactoryConfig<
207
- Service = BaseActor,
208
- Method extends FunctionName<Service> = FunctionName<Service>,
209
- Transform extends TransformKey = "candid",
210
- Selected = QueryFnData<Service, Method, Transform>,
211
- > = Omit<SuspenseQueryConfig<Service, Method, Transform, Selected>, "args">
212
-
213
- // ============================================================================
214
- // Hook Interfaces with Chained Select Support
215
- // ============================================================================
216
-
217
- /**
218
- * useQuery hook with chained select support.
219
- * - Without select: returns TSelected (from config.select)
220
- * - With select: chains on top and returns TFinal
221
- *
222
- * Accepts all useQuery options from React Query documentation.
223
- * Select is special: it chains on top of config.select.
224
- */
225
- export interface UseQueryWithSelect<
226
- TQueryFnData,
227
- TSelected = TQueryFnData,
228
- TError = Error,
229
- > {
230
- // Overload 1: Without select - returns TSelected
231
- // Note: select is included as optional (never type) to enable autocomplete suggestions
232
- (
233
- options?: Omit<
234
- UseQueryOptions<TQueryFnData, TError, TSelected>,
235
- "queryKey" | "queryFn"
236
- > & {
237
- select?: undefined
238
- }
239
- ): UseQueryResult<TSelected, TError>
240
-
241
- // Overload 2: With select - chains on top of config.select and returns TFinal
242
- <TFinal = TSelected>(
243
- options: Omit<
244
- UseQueryOptions<TQueryFnData, TError, TFinal>,
245
- "queryKey" | "queryFn" | "select"
246
- > & {
247
- select: (data: TSelected) => TFinal
248
- }
249
- ): UseQueryResult<TFinal, TError>
250
- }
251
-
252
- /**
253
- * useSuspenseQuery hook with chained select support.
254
- * - Without select: returns TSelected (from config.select)
255
- * - With select: chains on top and returns TFinal
256
- *
257
- * Accepts all useSuspenseQuery options from React Query documentation.
258
- * Select is special: it chains on top of config.select.
259
- * Data is always defined (never undefined).
260
- * Does NOT support `enabled` option.
261
- */
262
- export interface UseSuspenseQueryWithSelect<
263
- TQueryFnData,
264
- TSelected = TQueryFnData,
265
- TError = Error,
266
- > {
267
- // Overload 1: Without select - returns TSelected
268
- // Note: select is included as optional (never type) to enable autocomplete suggestions
269
- (
270
- options?: Omit<
271
- UseSuspenseQueryOptions<TQueryFnData, TError, TSelected>,
272
- "queryKey" | "queryFn"
273
- > & {
274
- select?: undefined
275
- }
276
- ): UseSuspenseQueryResult<TSelected, TError>
277
-
278
- // Overload 2: With select - chains on top of config.select and returns TFinal
279
- <TFinal = TSelected>(
280
- options: Omit<
281
- UseSuspenseQueryOptions<TQueryFnData, TError, TFinal>,
282
- "queryKey" | "queryFn" | "select"
283
- > & {
284
- select: (data: TSelected) => TFinal
285
- }
286
- ): UseSuspenseQueryResult<TFinal, TError>
287
- }
288
-
289
- // ============================================================================
290
- // Cache Controls
291
- // ============================================================================
292
-
293
- /**
294
- * What `optimisticUpdate()` resolves with: the way back to the value the
295
- * cache held before the update.
296
- *
297
- * @example
298
- * ```typescript
299
- * const update = await postQuery.optimisticUpdate((post) => ({
300
- * ...post,
301
- * likes: post.likes + 1n,
302
- * }))
303
- * // The call failed: show the post as it was
304
- * update.rollback()
305
- * ```
306
- */
307
- export interface OptimisticRollback {
308
- /**
309
- * Write back the value the cache held before the update, with the time it
310
- * was fetched, so it is as fresh or as stale as it was. A value that was
311
- * invalidated is invalidated again, so a mounted query refetches it, as
312
- * the refetch the update cancelled would have.
313
- *
314
- * It does nothing when the update wrote nothing, or when another principal
315
- * has signed in or out since: the value was the previous principal's, and
316
- * the sign-in has already removed or refetched it. It restores that value
317
- * even if a fetch or another update has written since; invalidate the
318
- * query afterwards when the canister's current value matters.
319
- */
320
- rollback: () => void
321
- }
322
-
323
- /**
324
- * The operations every query object has on its own cache entry, on the
325
- * reactor's QueryClient. They act on that one entry: other args of the same
326
- * method, and other queries under the same key prefix, are left alone.
327
- *
328
- * @template TQueryFnData - The raw (pre-`select`) data the entry holds
329
- */
330
- export interface QueryCacheControls<TQueryFnData> {
331
- /**
332
- * Cancel this query's fetch in flight, if there is one. The entry keeps
333
- * the value it held before that fetch started, and a later refetch runs as
334
- * usual.
335
- *
336
- * @example
337
- * ```typescript
338
- * // Before writing to the cache, so an older answer cannot land on top
339
- * await postQuery.cancel()
340
- * postQuery.setData(draft)
341
- * ```
342
- */
343
- cancel: () => Promise<void>
344
-
345
- /**
346
- * Reset this query's entry to its initial state, as TanStack Query's
347
- * `resetQueries` does: its data is cleared, or goes back to `initialData`
348
- * when one was given. A mounted hook then fetches it again, and a suspense
349
- * hook suspends until it has. It resolves once that fetch settles.
350
- *
351
- * @example
352
- * ```typescript
353
- * // A reload button that shows the Suspense fallback again
354
- * <button onClick={() => void statsQuery.reset()}>Reload</button>
355
- * ```
356
- */
357
- reset: () => Promise<void>
358
-
359
- /**
360
- * Replace this query's cached value for the duration of a mutation, and
361
- * get back a rollback for when it fails.
362
- *
363
- * It cancels the query's fetch in flight, so an answer from before the
364
- * mutation cannot overwrite the new value, then writes what `updater`
365
- * returns for the cached value. `updater` gets and returns the raw,
366
- * pre-`select` data. When nothing is cached yet it is not called, nothing
367
- * is cancelled or written, and `rollback()` does nothing: there is no
368
- * value on screen to update, and the query's own fetch will bring one. The
369
- * same goes when another principal signs in or out while it cancels, since
370
- * the cached value is then the previous principal's.
371
- *
372
- * The fetch it cancels may be a refetch an invalidation or a sign-in
373
- * started, so refetch the query once the mutation settles, with
374
- * `invalidate()` in `onSettled` or the query in `invalidateQueries`.
375
- *
376
- * Return it from `onMutate`, so the rollback reaches `onError`.
377
- *
378
- * @param updater - The new value, from the cached one. Do not mutate the
379
- * cached value in place; return a new one.
380
- *
381
- * @example
382
- * ```typescript
383
- * const getPost = createQueryFactory(backend, { functionName: "getPost" })
384
- * const likePost = createMutation(backend, { functionName: "likePost" })
385
- *
386
- * const { mutate } = likePost.useMutation({
387
- * onMutate: ([postId]) =>
388
- * getPost([postId]).optimisticUpdate((post) => ({
389
- * ...post,
390
- * likes: post.likes + 1n,
391
- * })),
392
- * onError: (_error, _args, update) => update?.rollback(),
393
- * // Refetch either way: a call that failed in transit may still have run
394
- * onSettled: (_data, _error, [postId]) => getPost([postId]).invalidate(),
395
- * })
396
- * ```
397
- */
398
- optimisticUpdate: (
399
- updater: (old: TQueryFnData) => TQueryFnData
400
- ) => Promise<OptimisticRollback>
401
- }
402
-
403
- // ============================================================================
404
- // Result Interfaces
405
- // ============================================================================
406
-
407
- /**
408
- * Base result interface shared between createQuery and createSuspenseQuery.
409
- *
410
- * @template TQueryFnData - The raw data type
411
- * @template TSelected - The type after select transformation
412
- * @template TError - The error type
413
- */
414
- export interface BaseQueryResult<
415
- TQueryFnData,
416
- TSelected = TQueryFnData,
417
- _TError = Error,
418
- > extends QueryCacheControls<TQueryFnData> {
419
- /** Fetch data in loader (uses ensureQueryData for cache-first) */
420
- fetch: () => Promise<TSelected>
421
-
422
- /**
423
- * Eagerly prefetch data into the cache without blocking.
424
- * Useful for preloading data before navigating to a route.
425
- *
426
- * Unlike `fetch()`, this returns a void promise so it can be fire-and-forget.
427
- * It never rejects: after a failed fetch the cached data is left as it was.
428
- *
429
- * A sign-in or sign-out while it is in flight cancels the fetch, so the
430
- * previous principal's answer is never cached, and it runs again for the
431
- * principal signed in, as `fetch()` does. When that run succeeds, the cache
432
- * holds that principal's answer by the time the promise resolves.
433
- *
434
- * @example
435
- * // In a route hover handler
436
- * button.addEventListener("mouseenter", () => userQuery.prefetch())
437
- */
438
- prefetch: () => Promise<void>
439
-
440
- /** Invalidate the cache (refetches if query is active) */
441
- invalidate: () => Promise<void>
442
-
443
- /** Get query key (for advanced React Query usage) */
444
- getQueryKey: () => QueryKey
445
-
446
- /**
447
- * Read data directly from cache without fetching.
448
- * Returns undefined if data is not in cache.
449
- *
450
- * @template TFinal - Type returned after optional select transformation
451
- * @param select - Optional select function to transform cached data further
452
- * @returns Cached data with select applied, or undefined if not in cache
453
- *
454
- * @example
455
- * // Just get the cached data
456
- * const user = userQuery.getCacheData()
457
- *
458
- * // With additional select transformation
459
- * const name = userQuery.getCacheData((user) => user.name)
460
- */
461
- getCacheData: {
462
- (): TSelected | undefined
463
- <TFinal>(select: (data: TSelected) => TFinal): TFinal | undefined
464
- }
465
-
466
- /**
467
- * Write raw data directly into the cache (useful for optimistic updates).
468
- * Accepts a new value or an updater function that receives the current cached raw data.
469
- *
470
- * Note: The value is stored as raw (pre-select) data. Any active `select`
471
- * transformations are automatically re-applied by React Query on the next render.
472
- *
473
- * @example
474
- * // Optimistic update before a mutation
475
- * userQuery.setData({ id: "1", name: "Alice" })
476
- *
477
- * // Functional update
478
- * counterQuery.setData((prev) => (prev ?? 0) + 1)
479
- */
480
- setData: (
481
- updater:
482
- | TQueryFnData
483
- | ((old: TQueryFnData | undefined) => TQueryFnData | undefined)
484
- ) => TQueryFnData | undefined
485
- }
486
-
487
- /**
488
- * Result from createQuery
489
- *
490
- * Includes useQuery hook that:
491
- * - Supports `enabled` option for conditional fetching
492
- * - Data may be `undefined` during loading
493
- * - Uses regular `useQuery` with manual loading state handling
494
- *
495
- * @template TQueryFnData - The raw data type
496
- * @template TSelected - The type after select transformation
497
- * @template TError - The error type
498
- */
499
- export interface QueryResult<
500
- TQueryFnData,
501
- TSelected = TQueryFnData,
502
- TError = Error,
503
- > extends BaseQueryResult<TQueryFnData, TSelected, TError> {
504
- /** React hook for components - supports chained select and enabled option */
505
- useQuery: UseQueryWithSelect<TQueryFnData, TSelected, TError>
506
- }
507
-
508
- /**
509
- * Result from createSuspenseQuery
510
- *
511
- * Includes useSuspenseQuery hook that:
512
- * - Requires wrapping in <Suspense> boundary
513
- * - Data is always defined (no undefined checks)
514
- * - Does NOT support `enabled` option
515
- *
516
- * @template TQueryFnData - The raw data type
517
- * @template TSelected - The type after select transformation
518
- * @template TError - The error type
519
- */
520
- export interface SuspenseQueryResult<
521
- TQueryFnData,
522
- TSelected = TQueryFnData,
523
- TError = Error,
524
- > extends BaseQueryResult<TQueryFnData, TSelected, TError> {
525
- /** React hook for components - data is always defined (wrap in Suspense) */
526
- useSuspenseQuery: UseSuspenseQueryWithSelect<TQueryFnData, TSelected, TError>
527
- }
528
-
529
- // ============================================================================
530
- // Query Factory Functions
531
- // ============================================================================
532
-
533
- /**
534
- * The members every args-late query factory function carries
535
- * (`createQueryFactory`, `createSuspenseQueryFactory`,
536
- * `createInfiniteQueryFactory`, `createSuspenseInfiniteQueryFactory`).
537
- *
538
- * A factory makes one query per set of args, so there was no key to name all
539
- * of them: invalidating a list after a mutation meant keeping the args of each
540
- * instance around. These address every query the factory returns at once.
541
- */
542
- export interface QueryFactoryMethods {
543
- /**
544
- * The key prefix every query of this factory shares, whatever its args: the
545
- * canister (the config's `callConfig.canisterId`, else the reactor's) and
546
- * the method, plus the reactor's transform segment and any agent or
547
- * effective-target segment the config's `callConfig` adds, and for an
548
- * infinite factory its config `queryKey`. TanStack Query matches keys by
549
- * prefix, so the prefix covers every args instance and every infinite page
550
- * set. Queries of the same method made elsewhere share it too.
551
- *
552
- * @example
553
- * ```typescript
554
- * const getBalance = createQueryFactory(ledger, {
555
- * functionName: "icrc1_balance_of",
556
- * })
557
- *
558
- * // Every cached balance, whatever the account
559
- * ledger.queryClient.getQueriesData({ queryKey: getBalance.getQueryKey() })
560
- * ```
561
- */
562
- getQueryKey: () => QueryKey
563
- /**
564
- * Invalidate every query of this factory, whatever its args, on the
565
- * reactor's QueryClient. It resolves once the active ones have refetched; a
566
- * refetch that fails does not reject it.
567
- *
568
- * @example
569
- * ```typescript
570
- * // After a transfer, refresh every balance on screen
571
- * await getBalance.invalidate()
572
- * ```
573
- */
574
- invalidate: () => Promise<void>
575
- }
576
-
577
- /**
578
- * The function `createQueryFactory` and `createSuspenseQueryFactory` return:
579
- * called with args it returns the query object for them, the same object for
580
- * the same args, and it also carries {@link QueryFactoryMethods}.
581
- *
582
- * @template TArgs - The method's arguments
583
- * @template TQuery - The query object it returns
584
- *
585
- * @example
586
- * ```typescript
587
- * const getPost = createQueryFactory(backend, { functionName: "get_post" })
588
- *
589
- * // One post's query object
590
- * const { data } = getPost([postId]).useQuery()
591
- *
592
- * // Every post's, whatever its args
593
- * await getPost.invalidate()
594
- * ```
595
- */
596
- export interface QueryFactoryFn<TArgs, TQuery> extends QueryFactoryMethods {
597
- (args: TArgs): TQuery
598
- }
599
-
600
- /**
601
- * What a query factory returns for `skipToken`: the query's `useQuery` hook
602
- * alone, which renders a query that waits without fetching.
603
- *
604
- * The imperative members are left out because there is no call to make or
605
- * entry to read until the args are known: narrow to the args first to reach
606
- * `fetch()`, `invalidate()` or the cache controls.
607
- *
608
- * @template TQuery - The query object the factory returns for args
609
- *
610
- * @example
611
- * ```typescript
612
- * const getBalance = createQueryFactory(ledger, {
613
- * functionName: "icrc1_balance_of",
614
- * })
615
- *
616
- * // A SkippedQuery: only useQuery(), which does not fetch
617
- * const { data } = getBalance(skipToken).useQuery()
618
- * ```
619
- */
620
- export type SkippedQuery<TQuery extends { useQuery: unknown }> = Pick<
621
- TQuery,
622
- "useQuery"
623
- >
624
-
625
- /**
626
- * The function `createQueryFactory` returns: a {@link QueryFactoryFn} that
627
- * also takes TanStack Query's `skipToken` in place of args, for a component
628
- * whose args are not known yet. For `skipToken` it returns a
629
- * {@link SkippedQuery}, whose `useQuery()` waits without fetching, in an
630
- * entry of its own under the factory's `getQueryKey()` prefix. Given
631
- * `args ? [args] : skipToken`, it returns either, and `useQuery()` can be
632
- * called on the result directly.
633
- *
634
- * `createSuspenseQueryFactory` returns a plain {@link QueryFactoryFn}: a
635
- * suspense query cannot wait on `skipToken`.
636
- *
637
- * @template TArgs - The method's arguments
638
- * @template TQuery - The query object it returns for args
639
- *
640
- * @example
641
- * ```typescript
642
- * const getBalance = createQueryFactory(ledger, {
643
- * functionName: "icrc1_balance_of",
644
- * })
645
- *
646
- * function Balance({ owner }: { owner?: string }) {
647
- * const { data } = getBalance(owner ? [{ owner }] : skipToken).useQuery()
648
- * }
649
- * ```
650
- */
651
- export interface SkippableQueryFactoryFn<
652
- TArgs,
653
- TQuery extends { useQuery: unknown },
654
- > extends QueryFactoryMethods {
655
- // Args first: a factory called with args must resolve to the full query
656
- // object, not to the union the args-or-skipToken signature returns.
657
- (args: TArgs): TQuery
658
- (args: SkipToken): SkippedQuery<TQuery>
659
- (args: TArgs | SkipToken): TQuery | SkippedQuery<TQuery>
660
- // And args last as well: TypeScript reads an overloaded function's last
661
- // signature for `ReturnType`, `Parameters` and inference, so
662
- // `ReturnType<typeof getBalance>` stays the full query object, and a
663
- // factory passed where a `QueryFactoryFn<A, Q>` is inferred still gives
664
- // its args and query, as they did before `skipToken`. With the union
665
- // signature last, all three widened to include the skipped query.
666
- (args: TArgs): TQuery
667
- }
668
-
669
- // ============================================================================
670
- // Invalidation Targets
671
- // ============================================================================
672
-
673
- /**
674
- * Anything that knows the key of its queries: a query object from
675
- * `createQuery`, `createSuspenseQuery`, `createInfiniteQuery` or
676
- * `createSuspenseInfiniteQuery` (factory instances included), or a query
677
- * factory function, whose key covers every query it returns.
678
- *
679
- * @example
680
- * ```typescript
681
- * const postsQuery = createQuery(backend, { functionName: "get_posts" })
682
- * const getPost = createQueryFactory(backend, { functionName: "get_post" })
683
- *
684
- * // Both are key sources, so both go straight into invalidateQueries
685
- * createMutation(backend, {
686
- * functionName: "create_post",
687
- * invalidateQueries: [postsQuery, getPost],
688
- * })
689
- * ```
690
- */
691
- export interface QueryKeySource {
692
- /** The key, or key prefix, of the queries it names. */
693
- getQueryKey: () => QueryKey
694
- /**
695
- * Invalidate those queries on the QueryClient they are cached in.
696
- * `invalidateQueries` calls it when it is there, so a query of another
697
- * reactor with a QueryClient of its own is invalidated in that client. The
698
- * key goes to the mutation's reactor's QueryClient otherwise.
699
- */
700
- invalidate?: () => Promise<void>
701
- }
702
-
703
- /**
704
- * A method of the mutation's own reactor, and optionally one set of its
705
- * arguments: the same shape `Reactor.invalidateQueries` takes. Its key is
706
- * built by the reactor's `generateQueryKey` when the mutation succeeds, so it
707
- * follows a `setCanisterId` and carries the reactor's transform segment. It
708
- * is rooted at the canister the mutation was sent to: the reactor's, or the
709
- * one the mutation's `callConfig.canisterId` names.
710
- *
711
- * Without `args` it names every query of the method, whatever its args,
712
- * infinite queries included. With `args` it names the queries made with those
713
- * args by `createQuery`, a query factory or the hooks, and `args: []` names a
714
- * method without parameters as no `args` does. An infinite query keys its
715
- * page set by its first page's args in another form, which `args` does not
716
- * match, and a query of another canister than the mutation's is keyed apart:
717
- * name either by its query object or key instead.
718
- *
719
- * @example
720
- * ```typescript
721
- * invalidateQueries: [
722
- * { functionName: "get_posts" },
723
- * { functionName: "get_post", args: [postId] },
724
- * ]
725
- * ```
726
- */
727
- export type QueryDescriptor<
728
- Service = BaseActor,
729
- Transform extends TransformKey = "candid",
730
- > = {
731
- [Method in FunctionName<Service>]: {
732
- /** The method whose queries to name */
733
- functionName: Method
734
- /** The arguments of the one query to name; omit for every query of the method */
735
- args?: ReactorArgs<Service, Method, Transform>
736
- }
737
- }[FunctionName<Service>]
738
-
739
- /**
740
- * One entry of `invalidateQueries`: which queries a successful mutation
741
- * invalidates.
742
- *
743
- * - a query key, as `generateQueryKey` or `getQueryKey()` builds it;
744
- * - a query object or query factory ({@link QueryKeySource});
745
- * - a method of the mutation's own reactor, with or without args
746
- * ({@link QueryDescriptor});
747
- * - `undefined`, which is skipped, so `[maybeQuery]` and
748
- * `[maybeQuery?.getQueryKey()]` are safe when the query is absent.
749
- *
750
- * TanStack Query matches each key by prefix.
751
- *
752
- * @example
753
- * ```typescript
754
- * createMutation(backend, {
755
- * functionName: "create_post",
756
- * invalidateQueries: [
757
- * postsQuery, // a query object
758
- * getPost, // a query factory: every post, whatever its args
759
- * { functionName: "get_posts_count" }, // a method of this reactor
760
- * ],
761
- * })
762
- * ```
763
- */
764
- export type InvalidationTarget<
765
- Service = BaseActor,
766
- Transform extends TransformKey = "candid",
767
- > = QueryKey | QueryKeySource | QueryDescriptor<Service, Transform> | undefined
768
-
769
- // ============================================================================
770
- // Actor Mutation Types
771
- // ============================================================================
772
-
773
- /**
774
- * Configuration for createMutation and useActorMutation.
775
- *
776
- * @template TOnMutateResult - The value `onMutate` returns, which `onSuccess`,
777
- * `onError` and `onSettled` receive. TypeScript infers it from `onMutate`.
778
- */
779
- export interface MutationConfig<
780
- Service = BaseActor,
781
- Method extends FunctionName<Service> = FunctionName<Service>,
782
- Transform extends TransformKey = "candid",
783
- TOnMutateResult = unknown,
784
- > extends Omit<
785
- UseMutationOptions<
786
- ReactorReturnOk<Service, Method, Transform>,
787
- ReactorReturnErr<Service, Method, Transform>,
788
- ReactorArgs<Service, Method, Transform>,
789
- TOnMutateResult
790
- >,
791
- "mutationFn"
792
- > {
793
- /** The method to call on the canister */
794
- functionName: Method
795
- /** Call configuration for the actor method */
796
- callConfig?: CallConfig
797
- /**
798
- * Queries to invalidate upon successful mutation, before `onSuccess` runs.
799
- * The mutation stays pending until the invalidated queries in use have
800
- * refetched, so `onSuccess` reads the refetched data.
801
- *
802
- * Each entry is a query key, a query object or query factory, or a
803
- * `{ functionName, args? }` method of this mutation's reactor; see
804
- * {@link InvalidationTarget}. `undefined` entries are skipped, so
805
- * `[maybeQuery]` is safe when the optional query object is absent.
806
- *
807
- * @example
808
- * ```typescript
809
- * invalidateQueries: [getPosts, { functionName: "get_posts_count" }]
810
- * ```
811
- */
812
- invalidateQueries?: InvalidationTarget<Service, Transform>[]
813
- /**
814
- * Callback for canister-level business logic errors.
815
- * Called when the canister returns a Result { Err: E } variant.
816
- *
817
- * This is separate from `onError` which handles all errors including
818
- * network failures, agent errors, etc.
819
- *
820
- * @param error - The CanisterError containing the typed error value
821
- * @param variables - The arguments passed to the mutation
822
- *
823
- * @example
824
- * ```typescript
825
- * createMutation(reactor, {
826
- * functionName: "transfer",
827
- * onCanisterError: (error, variables) => {
828
- * // error.err contains the typed Err value
829
- * // error.code contains the variant key (e.g., "InsufficientFunds")
830
- * console.error(`Transfer failed: ${error.code}`, error.err)
831
- * },
832
- * })
833
- * ```
834
- */
835
- onCanisterError?: (
836
- error: CanisterError<
837
- TransformReturnRegistry<
838
- ErrResult<ActorMethodReturnType<Service[Method]>>,
839
- Service
840
- >[Transform]
841
- >,
842
- variables: ReactorArgs<Service, Method, Transform>
843
- ) => void
844
- }
845
-
846
- /**
847
- * Configuration for createMutationFactory.
848
- */
849
- export type MutationFactoryConfig<
850
- Service = BaseActor,
851
- Method extends FunctionName<Service> = FunctionName<Service>,
852
- Transform extends TransformKey = "candid",
853
- TOnMutateResult = unknown,
854
- > = Omit<
855
- MutationConfig<Service, Method, Transform, TOnMutateResult>,
856
- "onSuccess"
857
- >
858
-
859
- /**
860
- * Options for useMutation hook.
861
- * Extends React Query's UseMutationOptions with invalidateQueries support.
862
- *
863
- * @template TOnMutateResult - The value `onMutate` returns, which `onSuccess`,
864
- * `onError` and `onSettled` receive. TypeScript infers it from `onMutate`.
865
- */
866
- export interface MutationHookOptions<
867
- Service = BaseActor,
868
- Method extends FunctionName<Service> = FunctionName<Service>,
869
- Transform extends TransformKey = "candid",
870
- TOnMutateResult = unknown,
871
- > extends Omit<
872
- UseMutationOptions<
873
- ReactorReturnOk<Service, Method, Transform>,
874
- ReactorReturnErr<Service, Method, Transform>,
875
- ReactorArgs<Service, Method, Transform>,
876
- TOnMutateResult
877
- >,
878
- "mutationFn"
879
- > {
880
- /**
881
- * Queries to invalidate upon successful mutation, after the factory's own
882
- * `invalidateQueries` and before `onSuccess`. Takes the same entries:
883
- * a query key, a query object or query factory, or a
884
- * `{ functionName, args? }` method of the mutation's reactor; see
885
- * {@link InvalidationTarget}.
886
- *
887
- * @example
888
- * const balanceQuery = getIcpBalance([account])
889
- * useMutation({
890
- * invalidateQueries: [balanceQuery],
891
- * })
892
- */
893
- invalidateQueries?: InvalidationTarget<Service, Transform>[]
894
- /**
895
- * Callback for canister-level business logic errors.
896
- * Called when the canister returns a Result { Err: E } variant.
897
- *
898
- * @param error - The CanisterError containing the typed error value
899
- * @param variables - The arguments passed to the mutation
900
- */
901
- onCanisterError?: (
902
- error: CanisterError<
903
- TransformReturnRegistry<
904
- ErrResult<ActorMethodReturnType<Service[Method]>>,
905
- Service
906
- >[Transform]
907
- >,
908
- variables: ReactorArgs<Service, Method, Transform>
909
- ) => void
910
- }
911
-
912
- /**
913
- * Result from createMutation.
914
- */
915
- export interface MutationResult<
916
- Service = BaseActor,
917
- Method extends FunctionName<Service> = FunctionName<Service>,
918
- Transform extends TransformKey = "candid",
919
- > {
920
- /**
921
- * React hook for the mutation.
922
- * Accepts options to override/extend the factory config.
923
- *
924
- * @example
925
- * // With invalidateQueries to auto-update balance after transfer
926
- * const { mutate } = icpTransferMutation.useMutation({
927
- * invalidateQueries: [userBalanceQuery], // Auto-invalidate after success!
928
- * })
929
- */
930
- useMutation: <TOnMutateResult = unknown>(
931
- options?: MutationHookOptions<Service, Method, Transform, TOnMutateResult>
932
- ) => UseMutationResult<
933
- ReactorReturnOk<Service, Method, Transform>,
934
- ReactorReturnErr<Service, Method, Transform>,
935
- ReactorArgs<Service, Method, Transform>,
936
- TOnMutateResult
937
- >
938
-
939
- /**
940
- * Execute the update call outside React. It runs in the QueryClient's
941
- * MutationCache with the factory's options and callbacks, as `useMutation()`
942
- * does without hook options. It resolves with the method's result and
943
- * rejects with the call's error.
944
- */
945
- execute: (
946
- args: ReactorArgs<Service, Method, Transform>
947
- ) => Promise<ReactorReturnOk<Service, Method, Transform>>
948
- }