@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
@@ -9,6 +9,9 @@
9
9
  * @example
10
10
  * const transferMutation = createMutation(reactor, {
11
11
  * functionName: "transfer",
12
+ * // Refetched before onSuccess runs: a query factory covers every args
13
+ * // instance, a `{ functionName }` every query of that method
14
+ * invalidateQueries: [getBalance, { functionName: "get_history" }],
12
15
  * onSuccess: () => console.log("Success!"),
13
16
  * })
14
17
  *
@@ -28,6 +31,7 @@
28
31
  import {
29
32
  useMutation,
30
33
  type MutationFunctionContext,
34
+ type UseMutationOptions,
31
35
  } from "@tanstack/react-query"
32
36
  import type {
33
37
  Reactor,
@@ -35,6 +39,7 @@ import type {
35
39
  ReactorArgs,
36
40
  TransformKey,
37
41
  ReactorReturnOk,
42
+ ReactorReturnErr,
38
43
  } from "@ic-reactor/core"
39
44
  import { isCanisterError } from "@ic-reactor/core"
40
45
  import type {
@@ -43,22 +48,7 @@ import type {
43
48
  MutationHookOptions,
44
49
  NoInfer,
45
50
  } from "./types.js"
46
-
47
- // ============================================================================
48
- // Internal helpers
49
- // ============================================================================
50
-
51
- /** Invalidate a list of query keys in parallel, filtering out undefineds. */
52
- async function invalidateAll(
53
- queryClient: Reactor<any, any>["queryClient"],
54
- keys: (import("@tanstack/react-query").QueryKey | undefined)[]
55
- ): Promise<void> {
56
- await Promise.all(
57
- keys.map((queryKey) =>
58
- queryKey ? queryClient.invalidateQueries({ queryKey }) : Promise.resolve()
59
- )
60
- )
61
- }
51
+ import { invalidateTargets, useMountQueryClient } from "./utils.js"
62
52
 
63
53
  // ============================================================================
64
54
  // Internal Implementation
@@ -68,10 +58,20 @@ const createMutationImpl = <
68
58
  Service,
69
59
  Method extends FunctionName<Service> = FunctionName<Service>,
70
60
  Transform extends TransformKey = "candid",
61
+ TOnMutateResult = unknown,
71
62
  >(
72
63
  reactor: Reactor<Service, Transform>,
73
- config: MutationConfig<Service, Method, Transform>
64
+ config: MutationConfig<
65
+ Service,
66
+ Method,
67
+ Transform,
68
+ TOnMutateResult | undefined
69
+ >
74
70
  ): MutationResult<Service, Method, Transform> => {
71
+ type TData = ReactorReturnOk<Service, Method, Transform>
72
+ type TError = ReactorReturnErr<Service, Method, Transform>
73
+ type TVariables = ReactorArgs<Service, Method, Transform>
74
+
75
75
  const {
76
76
  functionName,
77
77
  callConfig,
@@ -89,136 +89,202 @@ const createMutationImpl = <
89
89
  * Used as mutationFn so that onSuccess handles all post-mutation work
90
90
  * and there is no double-invalidation.
91
91
  */
92
- const callFn = (
93
- args: ReactorArgs<Service, Method, Transform>
94
- ): Promise<ReactorReturnOk<Service, Method, Transform>> =>
92
+ const callFn = (args: TVariables): Promise<TData> =>
95
93
  reactor.callMethod({ functionName, args, callConfig })
96
94
 
95
+ /**
96
+ * The factory's own `onMutate` result for each call.
97
+ *
98
+ * TanStack Query stores one `onMutate` result per mutation and hands it to
99
+ * every callback. That slot holds the hook's result, which the mutation also
100
+ * exposes as `context`, so this map holds the factory's. Each key is the
101
+ * context object TanStack Query creates for a call and passes to every
102
+ * callback of that call.
103
+ */
104
+ const factoryOnMutateResults = new WeakMap<
105
+ MutationFunctionContext,
106
+ TOnMutateResult | undefined
107
+ >()
108
+
109
+ /**
110
+ * The `onMutate` result a factory callback receives. A factory without its
111
+ * own `onMutate` gets the hook's, as it always has.
112
+ */
113
+ const factoryOnMutateResult = (
114
+ onMutateResult: unknown,
115
+ context: MutationFunctionContext | undefined
116
+ ) =>
117
+ (factoryOnMutate && context
118
+ ? factoryOnMutateResults.get(context)
119
+ : onMutateResult) as TOnMutateResult | undefined
120
+
121
+ /**
122
+ * The options a mutation of this factory runs with, on both call paths: the
123
+ * factory's config, the hook's options on top when there is a hook, and the
124
+ * callbacks of both levels chained, factory first. `useMutation()` hands
125
+ * them to TanStack Query's `useMutation`, and `execute()` builds a mutation
126
+ * from them in the QueryClient's MutationCache.
127
+ */
128
+ const mutationOptions = <THookOnMutateResult = unknown>(
129
+ options?: MutationHookOptions<
130
+ Service,
131
+ Method,
132
+ Transform,
133
+ THookOnMutateResult
134
+ >
135
+ ): UseMutationOptions<TData, TError, TVariables, THookOnMutateResult> => {
136
+ const {
137
+ invalidateQueries: hookInvalidateQueries,
138
+ onCanisterError: hookOnCanisterError,
139
+ ...restOptions
140
+ } = options ?? {}
141
+
142
+ return {
143
+ mutationKey: reactor.getQueryOptions({ functionName }).queryKey,
144
+ ...factoryOptions,
145
+ ...restOptions,
146
+ // Use callFn (not execute) to avoid double-invalidation:
147
+ // factoryInvalidateQueries are handled in onSuccess below.
148
+ mutationFn: callFn,
149
+ onSuccess: async (data, variables, onMutateResult, context) => {
150
+ // 1. Factory-level invalidation
151
+ await invalidateTargets(reactor, factoryInvalidateQueries, callConfig)
152
+ // 2. Hook-level invalidation
153
+ await invalidateTargets(reactor, hookInvalidateQueries, callConfig)
154
+ // 3. Factory onSuccess
155
+ await factoryOnSuccess?.(
156
+ data,
157
+ variables,
158
+ factoryOnMutateResult(onMutateResult, context),
159
+ context
160
+ )
161
+ // 4. Hook onSuccess
162
+ await restOptions.onSuccess?.(data, variables, onMutateResult, context)
163
+ },
164
+ onError: async (error, variables, onMutateResult, context) => {
165
+ if (isCanisterError(error)) {
166
+ factoryOnCanisterError?.(error, variables)
167
+ hookOnCanisterError?.(error, variables)
168
+ }
169
+ // Awaited in order, like `onSuccess`. TanStack Query holds
170
+ // `onSettled` and the settled state until this promise resolves, so
171
+ // an async `onError` must be part of it.
172
+ await factoryOnError?.(
173
+ error,
174
+ variables,
175
+ factoryOnMutateResult(onMutateResult, context),
176
+ context
177
+ )
178
+ await restOptions.onError?.(error, variables, onMutateResult, context)
179
+ },
180
+ // `onMutate` and `onSettled` are composed like `onSuccess`/`onError`
181
+ // above. They used to arrive through the `...restOptions` spread, so a
182
+ // hook-level one silently replaced the factory's — factory teardown,
183
+ // telemetry or logging simply vanished the moment any call site passed
184
+ // its own, with no warning and no type error. Chaining is what a
185
+ // reader who has seen `onSuccess` chain already expects.
186
+ onMutate: async (variables, context) => {
187
+ if (factoryOnMutate) {
188
+ const result = await factoryOnMutate(variables, context)
189
+ if (context) factoryOnMutateResults.set(context, result)
190
+ }
191
+ // Without a hook-level `onMutate` this is `undefined`, and
192
+ // THookOnMutateResult is then `unknown`.
193
+ return (await restOptions.onMutate?.(
194
+ variables,
195
+ context
196
+ )) as THookOnMutateResult
197
+ },
198
+ onSettled: async (data, error, variables, onMutateResult, context) => {
199
+ await factoryOnSettled?.(
200
+ data,
201
+ error,
202
+ variables,
203
+ factoryOnMutateResult(onMutateResult, context),
204
+ context
205
+ )
206
+ await restOptions.onSettled?.(
207
+ data,
208
+ error,
209
+ variables,
210
+ onMutateResult,
211
+ context
212
+ )
213
+ },
214
+ }
215
+ }
216
+
97
217
  /**
98
218
  * Imperative execution for non-React usage.
99
219
  *
100
- * Runs the same factory-level chain the hook does — invalidation, then
101
- * `onSuccess`, or `onCanisterError`/`onError` on failure — so a mutation
102
- * object behaves the same through both call paths. Only hook-level callbacks
103
- * are absent, because there is no hook here to supply them.
220
+ * Builds the mutation in the QueryClient's MutationCache and runs it, as
221
+ * TanStack Query's `useMutation` does, with the options the hook path uses
222
+ * minus the hook's own. It used to call the canister and the factory's
223
+ * callbacks itself, so the MutationCache never saw it: its global
224
+ * `onError`, `onSuccess` and `onSettled` did not run, `useIsMutating` did
225
+ * not count it, and neither the factory's `retry` or `networkMode` nor the
226
+ * same options in the QueryClient's mutation defaults applied to the call.
227
+ * The factory's `onMutate` and `onSettled` did not run either.
104
228
  *
105
- * The callbacks get the same `{ client, meta, mutationKey }` context
106
- * TanStack Query passes them on the hook path.
229
+ * Now the factory's chain runs as it does through `useMutation()`:
230
+ * `onMutate`, then the factory's invalidation and `onSuccess`, or
231
+ * `onCanisterError` and `onError` on failure, then `onSettled`. Each factory
232
+ * callback gets the result of the factory's `onMutate` for this call. Only
233
+ * hook-level callbacks are absent, because there is no hook here to supply
234
+ * them.
107
235
  *
108
- * The error is rethrown after the callbacks run, so `await execute(...)`
109
- * still rejects for the caller.
236
+ * The QueryClient's mutation defaults apply as well, so a `mutations.retry`
237
+ * there re-sends a failed call here too. Each retry of an update method is
238
+ * a new call the canister runs; set `retry` on the factory (`false`, or
239
+ * `reactorUpdateRetry` to retry only a SysTransient rejection) to decide it
240
+ * per method.
241
+ *
242
+ * It resolves with the method's result. On failure it rejects with the
243
+ * call's error once the callbacks have run, so `await execute(...)` still
244
+ * rejects for the caller.
110
245
  *
111
246
  * Use this in route loaders, scripts, or server-side code.
112
247
  */
113
- const execute = async (
114
- args: ReactorArgs<Service, Method, Transform>
115
- ): Promise<ReactorReturnOk<Service, Method, Transform>> => {
116
- // TanStack Query builds this for the hook path, and the callback types
117
- // declare it as always present. None of it is mutation state, so this path
118
- // builds the same object. Passing `undefined` made a callback that reads
119
- // `context.client` throw here and nowhere else.
120
- //
121
- // The hook path takes `meta` and `mutationKey` from the client's defaulted
122
- // options, so this runs the same spread through `defaultMutationOptions`.
123
- // Reading `factoryOptions` alone dropped mutation defaults registered on
124
- // the QueryClient (`defaultOptions.mutations`, `setMutationDefaults`).
125
- const effectiveOptions = reactor.queryClient.defaultMutationOptions({
126
- mutationKey: reactor.getQueryOptions({ functionName }).queryKey,
127
- ...factoryOptions,
128
- })
129
- const context: MutationFunctionContext = {
130
- client: reactor.queryClient,
131
- meta: effectiveOptions.meta,
132
- mutationKey: effectiveOptions.mutationKey,
133
- }
134
-
135
- let result: ReactorReturnOk<Service, Method, Transform>
248
+ const execute = async (args: TVariables): Promise<TData> => {
249
+ const { queryClient } = reactor
250
+ const options = mutationOptions()
251
+ const { onError } = options
252
+ // What the factory's `onError` or `onCanisterError` throws, if either
253
+ // does. execute() has always rejected with it, and a TanStack Query
254
+ // mutation at the 5.90.2 peer floor does too. Later releases reject with
255
+ // the call's error and report the thrown one as an unhandled rejection,
256
+ // which ends a Node script, so it is caught here and rethrown below.
257
+ let thrownByOnError: { error: unknown } | undefined
258
+ const mutation = queryClient
259
+ .getMutationCache()
260
+ .build<TData, TError, TVariables, unknown>(queryClient, {
261
+ ...options,
262
+ onError: async (error, variables, onMutateResult, context) => {
263
+ try {
264
+ await onError?.(error, variables, onMutateResult, context)
265
+ } catch (thrown) {
266
+ thrownByOnError = { error: thrown }
267
+ }
268
+ },
269
+ })
136
270
  try {
137
- result = await callFn(args)
271
+ return await mutation.execute(args)
138
272
  } catch (error) {
139
- if (isCanisterError(error)) {
140
- factoryOnCanisterError?.(error, args)
141
- }
142
- // `onMutate` does not run on this path, so there is no result to pass.
143
- // Awaited like `onSuccess` below and like the hook path, so an async
144
- // rollback or report finishes before `execute()` rejects.
145
- await factoryOnError?.(
146
- error as Parameters<NonNullable<typeof factoryOnError>>[0],
147
- args,
148
- undefined,
149
- context
150
- )
151
- throw error
273
+ throw thrownByOnError ? thrownByOnError.error : error
152
274
  }
153
-
154
- if (factoryInvalidateQueries) {
155
- await invalidateAll(reactor.queryClient, factoryInvalidateQueries)
156
- }
157
- await factoryOnSuccess?.(result, args, undefined, context)
158
- return result
159
275
  }
160
276
 
161
277
  // Hook implementation
162
- const useMutationHook = (
163
- options?: MutationHookOptions<Service, Method, Transform>
278
+ const useMutationHook = <THookOnMutateResult = unknown>(
279
+ options?: MutationHookOptions<
280
+ Service,
281
+ Method,
282
+ Transform,
283
+ THookOnMutateResult
284
+ >
164
285
  ) => {
165
- const baseOptions = reactor.getQueryOptions({ functionName })
166
- const {
167
- invalidateQueries: hookInvalidateQueries,
168
- onCanisterError: hookOnCanisterError,
169
- ...restOptions
170
- } = options ?? {}
171
-
172
- return useMutation(
173
- {
174
- mutationKey: baseOptions.queryKey,
175
- ...factoryOptions,
176
- ...restOptions,
177
- // Use callFn (not execute) to avoid double-invalidation:
178
- // factoryInvalidateQueries are handled in onSuccess below.
179
- mutationFn: callFn,
180
- onSuccess: async (...args) => {
181
- // 1. Factory-level invalidation
182
- if (factoryInvalidateQueries) {
183
- await invalidateAll(reactor.queryClient, factoryInvalidateQueries)
184
- }
185
- // 2. Hook-level invalidation
186
- if (hookInvalidateQueries) {
187
- await invalidateAll(reactor.queryClient, hookInvalidateQueries)
188
- }
189
- // 3. Factory onSuccess
190
- await factoryOnSuccess?.(...args)
191
- // 4. Hook onSuccess
192
- await restOptions.onSuccess?.(...args)
193
- },
194
- onError: async (error, variables, context, mutation) => {
195
- if (isCanisterError(error)) {
196
- factoryOnCanisterError?.(error, variables)
197
- hookOnCanisterError?.(error, variables)
198
- }
199
- // Awaited in order, like `onSuccess`. TanStack Query holds
200
- // `onSettled` and the settled state until this promise resolves, so
201
- // an async `onError` must be part of it.
202
- await factoryOnError?.(error, variables, context, mutation)
203
- await restOptions.onError?.(error, variables, context, mutation)
204
- },
205
- // `onMutate` and `onSettled` are composed like `onSuccess`/`onError`
206
- // above. They used to arrive through the `...restOptions` spread, so a
207
- // hook-level one silently replaced the factory's — factory teardown,
208
- // telemetry or logging simply vanished the moment any call site passed
209
- // its own, with no warning and no type error. Chaining is what a
210
- // reader who has seen `onSuccess` chain already expects.
211
- onMutate: async (...params) => {
212
- await factoryOnMutate?.(...params)
213
- return await restOptions.onMutate?.(...params)
214
- },
215
- onSettled: async (...params) => {
216
- await factoryOnSettled?.(...params)
217
- await restOptions.onSettled?.(...params)
218
- },
219
- },
220
- reactor.queryClient
221
- )
286
+ useMountQueryClient(reactor.queryClient)
287
+ return useMutation(mutationOptions(options), reactor.queryClient)
222
288
  }
223
289
 
224
290
  return { useMutation: useMutationHook, execute }
@@ -232,12 +298,27 @@ export function createMutation<
232
298
  Service,
233
299
  Transform extends TransformKey,
234
300
  Method extends FunctionName<Service> = FunctionName<Service>,
301
+ TOnMutateResult = unknown,
235
302
  >(
236
303
  reactor: Reactor<Service, Transform>,
237
- config: MutationConfig<NoInfer<Service>, Method, Transform>
304
+ // A factory `onError` or `onSettled` gets no `onMutate` result when
305
+ // `onMutate` throws, so the result can also be `undefined`. `onSuccess`
306
+ // shares the type argument. It kept the `undefined` from when `execute()`
307
+ // ran no `onMutate`, although it now always gets a result.
308
+ config: MutationConfig<
309
+ NoInfer<Service>,
310
+ Method,
311
+ Transform,
312
+ TOnMutateResult | undefined
313
+ >
238
314
  ): MutationResult<Service, Method, Transform> {
239
315
  return createMutationImpl(
240
316
  reactor,
241
- config as MutationConfig<Service, Method, Transform>
317
+ config as MutationConfig<
318
+ Service,
319
+ Method,
320
+ Transform,
321
+ TOnMutateResult | undefined
322
+ >
242
323
  )
243
324
  }