@tanstack/solid-query 6.0.0-rc.0 → 6.0.0-rc.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.
@@ -1,9 +1,12 @@
1
1
  import { InfiniteQueryObserver } from '@tanstack/query-core'
2
2
  import { createMemo } from 'solid-js'
3
- import { useBaseQuery } from './useBaseQuery'
3
+ import { useBaseQueryLayer } from './useBaseQuery'
4
4
  import type {
5
5
  DefaultError,
6
+ FetchNextPageOptions,
7
+ FetchPreviousPageOptions,
6
8
  InfiniteData,
9
+ InfiniteQueryObserverResult,
7
10
  QueryKey,
8
11
  QueryObserver,
9
12
  } from '@tanstack/query-core'
@@ -19,6 +22,58 @@ import type {
19
22
  UndefinedInitialDataInfiniteOptions,
20
23
  } from './infiniteQueryOptions'
21
24
 
25
+ /**
26
+ * Page-boundary checks, mirrored from query-core's `infiniteQueryBehavior`
27
+ * (they are module-private there — not exported through the package index).
28
+ * Pure functions of options + cached data; keep byte-compatible with core.
29
+ */
30
+ interface PageParamsOptions {
31
+ getNextPageParam: (
32
+ lastPage: unknown,
33
+ pages: Array<unknown>,
34
+ lastPageParam: unknown,
35
+ pageParams: Array<unknown>,
36
+ ) => unknown
37
+ getPreviousPageParam?: (
38
+ firstPage: unknown,
39
+ pages: Array<unknown>,
40
+ firstPageParam: unknown,
41
+ pageParams: Array<unknown>,
42
+ ) => unknown
43
+ }
44
+
45
+ function hasNextPage(
46
+ options: PageParamsOptions,
47
+ data?: InfiniteData<unknown>,
48
+ ): boolean {
49
+ if (!data || data.pages.length === 0) return false
50
+ const lastIndex = data.pages.length - 1
51
+ return (
52
+ options.getNextPageParam(
53
+ data.pages[lastIndex],
54
+ data.pages,
55
+ data.pageParams[lastIndex],
56
+ data.pageParams,
57
+ ) != null
58
+ )
59
+ }
60
+
61
+ function hasPreviousPage(
62
+ options: PageParamsOptions,
63
+ data?: InfiniteData<unknown>,
64
+ ): boolean {
65
+ if (!data || data.pages.length === 0 || !options.getPreviousPageParam)
66
+ return false
67
+ return (
68
+ options.getPreviousPageParam(
69
+ data.pages[0],
70
+ data.pages,
71
+ data.pageParams[0],
72
+ data.pageParams,
73
+ ) != null
74
+ )
75
+ }
76
+
22
77
  export function useInfiniteQuery<
23
78
  TQueryFnData,
24
79
  TError = DefaultError,
@@ -68,9 +123,106 @@ export function useInfiniteQuery<
68
123
  >,
69
124
  queryClient?: Accessor<QueryClient>,
70
125
  ): UseInfiniteQueryResult<TData, TError> {
71
- return useBaseQuery(
72
- createMemo(() => options()),
126
+ /**
127
+ * Pagers ride the same entry as every other read: the base layer owns
128
+ * the data node, meta projection, and lifecycle observer (an
129
+ * `InfiniteQueryObserver`, so mount policy and refetches use the
130
+ * infinite behavior). This layer adds the page surface on top.
131
+ */
132
+ const layer = useBaseQueryLayer(
133
+ // `_type` is how query-core stamps a cache entry as infinite
134
+ // (`Query.setOptions` → `#queryType`), which makes every fetch attach
135
+ // `infiniteQueryBehavior`. Core only sets it inside
136
+ // `InfiniteQueryObserver.setOptions`, but the adapter's pull path can
137
+ // build and fetch the entry first (reactive key change recomputes the
138
+ // data memo before the deferred observer effect runs) — without the
139
+ // stamp that fetch would run as a plain query and corrupt the entry.
140
+ createMemo(() => ({ ...options(), _type: 'infinite' }) as any),
73
141
  InfiniteQueryObserver as typeof QueryObserver,
74
142
  queryClient,
75
- ) as UseInfiniteQueryResult<TData, TError>
143
+ )
144
+ const observer = layer.observer as unknown as InfiniteQueryObserver<
145
+ TQueryFnData,
146
+ TError,
147
+ TData,
148
+ TQueryKey,
149
+ TPageParam
150
+ >
151
+
152
+ /** Committed fetch direction — set when a page fetch dispatches,
153
+ * version-tracked through the layer's query accessor. */
154
+ const direction = () =>
155
+ (layer.query().state.fetchMeta as any)?.fetchMore?.direction
156
+
157
+ const pageOptions = () =>
158
+ layer.defaultedOptions() as unknown as PageParamsOptions
159
+ const infiniteData = () =>
160
+ layer.query().state.data as InfiniteData<unknown> | undefined
161
+
162
+ const isFetchingNextPage = () =>
163
+ layer.isFetching() && direction() === 'forward'
164
+ const isFetchingPreviousPage = () =>
165
+ layer.isFetching() && direction() === 'backward'
166
+ const isError = () => layer.status() === 'error'
167
+
168
+ // The base result's getters transfer as-is; the infinite surface layers
169
+ // over them, and isRefetching/isRefetchError narrow to exclude page
170
+ // fetches (matching core's InfiniteQueryObserver result shape).
171
+ const result = Object.defineProperties(
172
+ {},
173
+ {
174
+ ...Object.getOwnPropertyDescriptors(layer.result),
175
+ fetchNextPage: {
176
+ value: (fetchOptions?: FetchNextPageOptions) =>
177
+ observer.fetchNextPage(fetchOptions),
178
+ enumerable: true,
179
+ },
180
+ fetchPreviousPage: {
181
+ value: (fetchOptions?: FetchPreviousPageOptions) =>
182
+ observer.fetchPreviousPage(fetchOptions),
183
+ enumerable: true,
184
+ },
185
+ hasNextPage: {
186
+ get: () => hasNextPage(pageOptions(), infiniteData()),
187
+ enumerable: true,
188
+ },
189
+ hasPreviousPage: {
190
+ get: () => hasPreviousPage(pageOptions(), infiniteData()),
191
+ enumerable: true,
192
+ },
193
+ isFetchingNextPage: {
194
+ get: isFetchingNextPage,
195
+ enumerable: true,
196
+ },
197
+ isFetchingPreviousPage: {
198
+ get: isFetchingPreviousPage,
199
+ enumerable: true,
200
+ },
201
+ isFetchNextPageError: {
202
+ get: () => isError() && direction() === 'forward',
203
+ enumerable: true,
204
+ },
205
+ isFetchPreviousPageError: {
206
+ get: () => isError() && direction() === 'backward',
207
+ enumerable: true,
208
+ },
209
+ isRefetching: {
210
+ get: () =>
211
+ layer.isFetching() &&
212
+ layer.status() !== 'pending' &&
213
+ !isFetchingNextPage() &&
214
+ !isFetchingPreviousPage(),
215
+ enumerable: true,
216
+ },
217
+ isRefetchError: {
218
+ get: () => {
219
+ const errored = isError() && layer.query().state.dataUpdatedAt !== 0
220
+ return errored && direction() === undefined
221
+ },
222
+ enumerable: true,
223
+ },
224
+ },
225
+ ) as InfiniteQueryObserverResult<TData, TError>
226
+
227
+ return result as unknown as UseInfiniteQueryResult<TData, TError>
76
228
  }
@@ -1,4 +1,5 @@
1
- import { createMemo, createSignal, onCleanup, untrack } from 'solid-js'
1
+ import { createMemo } from 'solid-js'
2
+ import { createCacheAggregate } from './cacheAggregate'
2
3
  import { useQueryClient } from './QueryClientProvider'
3
4
  import type { QueryFilters } from '@tanstack/query-core'
4
5
  import type { QueryClient } from './QueryClient'
@@ -9,20 +10,9 @@ export function useIsFetching(
9
10
  queryClient?: Accessor<QueryClient>,
10
11
  ): Accessor<number> {
11
12
  const client = createMemo(() => useQueryClient(queryClient?.()))
12
- const queryCache = createMemo(() => client().getQueryCache())
13
-
14
- const [fetches, setFetches] = createSignal(
15
- untrack(() => client().isFetching(filters?.())),
16
- { ownedWrite: true },
17
- )
18
-
19
- const unsubscribe = untrack(() =>
20
- queryCache().subscribe(() => {
21
- setFetches(client().isFetching(filters?.()))
22
- }),
13
+ return createCacheAggregate(
14
+ (onEvent) => client().getQueryCache().subscribe(onEvent),
15
+ () => client().isFetching(filters?.()),
16
+ 0,
23
17
  )
24
-
25
- onCleanup(unsubscribe)
26
-
27
- return fetches
28
18
  }
@@ -1,4 +1,5 @@
1
- import { createMemo, createSignal, onCleanup, untrack } from 'solid-js'
1
+ import { createMemo } from 'solid-js'
2
+ import { createCacheAggregate } from './cacheAggregate'
2
3
  import { useQueryClient } from './QueryClientProvider'
3
4
  import type { MutationFilters } from '@tanstack/query-core'
4
5
  import type { QueryClient } from './QueryClient'
@@ -9,20 +10,9 @@ export function useIsMutating(
9
10
  queryClient?: Accessor<QueryClient>,
10
11
  ): Accessor<number> {
11
12
  const client = createMemo(() => useQueryClient(queryClient?.()))
12
- const mutationCache = createMemo(() => client().getMutationCache())
13
-
14
- const [mutations, setMutations] = createSignal(
15
- untrack(() => client().isMutating(filters?.())),
16
- { ownedWrite: true },
17
- )
18
-
19
- const unsubscribe = untrack(() =>
20
- mutationCache().subscribe((_result) => {
21
- setMutations(client().isMutating(filters?.()))
22
- }),
13
+ return createCacheAggregate(
14
+ (onEvent) => client().getMutationCache().subscribe(onEvent),
15
+ () => client().isMutating(filters?.()),
16
+ 0,
23
17
  )
24
-
25
- onCleanup(unsubscribe)
26
-
27
- return mutations
28
18
  }
@@ -1,14 +1,19 @@
1
- import { MutationObserver, noop, shouldThrowError } from '@tanstack/query-core'
1
+ import { noop, shouldThrowError } from '@tanstack/query-core'
2
2
  import {
3
+ action,
3
4
  createMemo,
5
+ createOptimistic,
4
6
  createRenderEffect,
5
- createStore,
7
+ createSignal,
6
8
  onCleanup,
7
- runWithOwner,
8
9
  untrack,
9
10
  } from 'solid-js'
10
11
  import { useQueryClient } from './QueryClientProvider'
11
- import type { DefaultError } from '@tanstack/query-core'
12
+ import type {
13
+ DefaultError,
14
+ Mutation,
15
+ MutationFunctionContext,
16
+ } from '@tanstack/query-core'
12
17
  import type { QueryClient } from './QueryClient'
13
18
  import type {
14
19
  UseMutateFunction,
@@ -17,7 +22,74 @@ import type {
17
22
  } from './types'
18
23
  import type { Accessor } from 'solid-js'
19
24
 
20
- // HOOK
25
+ const isServer = typeof window === 'undefined'
26
+
27
+ /**
28
+ * Durable mutation state: written post-`yield` inside the action's
29
+ * transaction, so it commits atomically with everything else the settle
30
+ * carries (invalidation-triggered refetches included).
31
+ */
32
+ interface SettledState<TData, TError, TVariables> {
33
+ status: 'idle' | 'success' | 'error'
34
+ data: TData | undefined
35
+ error: TError | null
36
+ variables: TVariables | undefined
37
+ submittedAt: number
38
+ }
39
+
40
+ const IDLE: SettledState<any, any, any> = {
41
+ status: 'idle',
42
+ data: undefined,
43
+ error: null,
44
+ variables: undefined,
45
+ submittedAt: 0,
46
+ }
47
+
48
+ /**
49
+ * Drive the mutation generator without transaction semantics — the server
50
+ * has no interactive paint to keep atomic. Yielded thenable values are
51
+ * awaited and their results passed back in, matching what `action` does
52
+ * minus the transition bookkeeping.
53
+ */
54
+ async function drain<TReturn>(
55
+ iterator: AsyncGenerator<unknown, TReturn, unknown>,
56
+ ): Promise<TReturn> {
57
+ let input: unknown
58
+ for (;;) {
59
+ const result = await iterator.next(input)
60
+ if (result.done) return result.value
61
+ input = result.value != null ? await result.value : undefined
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Mutations ride core `action`: each `mutate()` call is one transaction.
67
+ *
68
+ * - Transient flight state (`isPending`, in-flight `variables`) is a
69
+ * `createOptimistic` overlay written at the top of the action — visible
70
+ * immediately, dropped automatically when the transition settles. The
71
+ * engine's revert-on-settle IS the submission state machine.
72
+ * - `options.onMutate(variables)` runs inside the same window: it is the
73
+ * place to apply the caller's own optimistic overlays
74
+ * (`createOptimistic` / `createOptimisticStore` writes). No context
75
+ * object, no rollback plumbing — reverting is the engine's job.
76
+ * - The fetch itself goes through query-core:
77
+ * `mutationCache.build().execute()` supplies retry/backoff, offline
78
+ * pausing, scoped serial execution, and cache-level lifecycle callbacks.
79
+ * No `MutationObserver` — the adapter reads the Mutation's state
80
+ * directly where flight metadata (failureCount, isPaused) is needed.
81
+ * - Options-level callbacks are stripped from the built mutation and
82
+ * re-run *post-`yield`*, inside the transaction: an
83
+ * `onSuccess → invalidateQueries` chain issues version bumps whose
84
+ * refetches hold this very transition, so mutation success and fresh
85
+ * query data land in one atomic paint (and a single-flight payload has
86
+ * already primed the cache by the time `execute` resolves).
87
+ * - One `mutate`, returning a safe-to-ignore promise: rejection is also
88
+ * routed into reactive state, and a no-op catch keeps the ignored
89
+ * branch from surfacing as an unhandled rejection. The
90
+ * `mutate`/`mutateAsync` split existed for React's callback-vs-promise
91
+ * error routing; it has no space here.
92
+ */
21
93
  export function useMutation<
22
94
  TData = unknown,
23
95
  TError = DefaultError,
@@ -29,74 +101,263 @@ export function useMutation<
29
101
  ): UseMutationResult<TData, TError, TVariables, TOnMutateResult> {
30
102
  const client = createMemo(() => useQueryClient(queryClient?.()))
31
103
 
32
- const observer = untrack(
33
- () =>
34
- new MutationObserver<TData, TError, TVariables, TOnMutateResult>(
35
- client(),
36
- options(),
37
- ),
38
- )
104
+ /** Optimistic overlay: non-null exactly while a mutation is in flight. */
105
+ const [flight, setFlight] = createOptimistic<{
106
+ variables: TVariables
107
+ } | null>(null)
39
108
 
40
- // Track options changes and update observer
41
- createRenderEffect(
42
- () => options(),
43
- (opts) => {
44
- observer.setOptions(opts)
45
- },
46
- )
109
+ const [settled, setSettled] = createSignal<
110
+ SettledState<TData, TError, TVariables>
111
+ >(IDLE as SettledState<TData, TError, TVariables>)
112
+
113
+ /**
114
+ * Flight metadata (retry failures, offline pause) lives on the Mutation
115
+ * instance and changes during the await gap; a version bump per cache
116
+ * event for the active mutation keeps reads current without cloning
117
+ * observer results. The subscription is per-flight, created in `run`
118
+ * against the same client the mutation is built on: nothing reactive is
119
+ * read at setup (no client read to untrack), the listener and the
120
+ * mutation can never sit on different clients, and an idle hook holds no
121
+ * cache subscription at all.
122
+ */
123
+ let activeMutation: Mutation<TData, TError, TVariables> | null = null
124
+ let unsubscribeFlight: (() => void) | null = null
125
+ const [flightVersion, setFlightVersion] = createSignal(0)
126
+ if (!isServer)
127
+ onCleanup(() => {
128
+ unsubscribeFlight?.()
129
+ unsubscribeFlight = null
130
+ })
131
+
132
+ async function* run(
133
+ variables: TVariables,
134
+ ): AsyncGenerator<unknown, TData, unknown> {
135
+ const opts = untrack(options)
136
+ const callbackContext: MutationFunctionContext = {
137
+ client: untrack(client),
138
+ meta: opts.meta,
139
+ mutationKey: opts.mutationKey,
140
+ }
141
+ // Server render is pure (rc.3 deprecates server setter calls): the
142
+ // drain path only needs the return value/throw, so all reactive
143
+ // bookkeeping — flight overlay and durable settle — is client-only.
144
+ if (!isServer) setFlight({ variables })
145
+ opts.onMutate?.(variables, callbackContext)
146
+
147
+ const c = untrack(client)
148
+ const mutation = c.getMutationCache().build(c, {
149
+ ...opts,
150
+ // Options-level callbacks re-run below, inside the transaction —
151
+ // executing them here (inside the await gap) would let their
152
+ // cache writes and invalidations escape the atomic settle.
153
+ onMutate: undefined,
154
+ onSuccess: undefined,
155
+ onError: undefined,
156
+ onSettled: undefined,
157
+ }) as unknown as Mutation<TData, TError, TVariables>
158
+ activeMutation = mutation
159
+ if (!isServer) {
160
+ // A rapid re-mutate replaces the previous flight's listener.
161
+ unsubscribeFlight?.()
162
+ unsubscribeFlight = c.getMutationCache().subscribe((event) => {
163
+ if ('mutation' in event && event.mutation === activeMutation) {
164
+ setFlightVersion((v) => v + 1)
165
+ }
166
+ })
167
+ }
168
+ const settleFlight = () => {
169
+ if (activeMutation !== mutation) return
170
+ activeMutation = null
171
+ unsubscribeFlight?.()
172
+ unsubscribeFlight = null
173
+ }
174
+
175
+ /**
176
+ * Failure settle, shared by both paths. Callback routing mirrors
177
+ * query-core's `Mutation.execute` (which never sees these callbacks —
178
+ * they are stripped from the built mutation and re-run here, inside
179
+ * the transaction): `onError` and an error-shaped `onSettled` run,
180
+ * their own failures are reported as unhandled rejections but never
181
+ * displace `error`, then the durable error state commits and the
182
+ * mutate promise rejects with `error`.
183
+ */
184
+ // Must be an async generator: async `yield` awaits its value in place,
185
+ // so a rejecting callback promise throws at the yield inside the local
186
+ // try/catch. A sync generator delegated from an async one gets its
187
+ // values unwrapped by the async-from-sync adapter *outside* the inner
188
+ // frame — the rejection would bypass these catches and kill the run.
189
+ // eslint-disable-next-line @typescript-eslint/require-await -- async for yield-await semantics, not await expressions
190
+ async function* settleFailure(
191
+ error: TError,
192
+ ): AsyncGenerator<unknown, never, unknown> {
193
+ try {
194
+ if (opts.onError)
195
+ yield opts.onError(error, variables, undefined, callbackContext)
196
+ } catch (callbackError) {
197
+ void Promise.reject(callbackError)
198
+ }
199
+ try {
200
+ if (opts.onSettled)
201
+ yield opts.onSettled(
202
+ undefined,
203
+ error,
204
+ variables,
205
+ undefined,
206
+ callbackContext,
207
+ )
208
+ } catch (callbackError) {
209
+ void Promise.reject(callbackError)
210
+ }
211
+ if (!isServer)
212
+ setSettled({
213
+ status: 'error',
214
+ data: undefined,
215
+ error,
216
+ variables,
217
+ submittedAt: mutation.state.submittedAt,
218
+ })
219
+ settleFlight()
220
+ throw error
221
+ }
222
+
223
+ let result: TData
224
+ try {
225
+ result = await mutation.execute(variables)
226
+ } catch (error) {
227
+ yield // re-enter the transaction before writing
228
+ yield* settleFailure(error as TError)
229
+ // Unreachable — settleFailure always throws. Explicit for TS's
230
+ // definite-assignment analysis of `result` (yield* delegation is
231
+ // not narrowed as never-returning).
232
+ throw error
233
+ }
234
+ yield // re-enter the transaction before writing
235
+ try {
236
+ if (opts.onSuccess)
237
+ yield opts.onSuccess(
238
+ result,
239
+ variables,
240
+ undefined as TOnMutateResult,
241
+ callbackContext,
242
+ )
243
+ if (opts.onSettled)
244
+ yield opts.onSettled(
245
+ result,
246
+ null,
247
+ variables,
248
+ undefined,
249
+ callbackContext,
250
+ )
251
+ } catch (callbackError) {
252
+ // Mirror core: a success-path callback failure fails the mutation —
253
+ // even though the cache entry committed success, the hook settles
254
+ // to error with the callback error, and awaiting mutate rejects.
255
+ yield* settleFailure(callbackError as TError)
256
+ // Unreachable — settleFailure always throws (see above).
257
+ throw callbackError
258
+ }
259
+ if (!isServer)
260
+ setSettled({
261
+ status: 'success',
262
+ data: result,
263
+ error: null,
264
+ variables,
265
+ submittedAt: mutation.state.submittedAt,
266
+ })
267
+ settleFlight()
268
+ return result
269
+ }
270
+
271
+ const invoke: (variables: TVariables) => Promise<TData> = isServer
272
+ ? (variables) => drain(run(variables))
273
+ : action(run)
47
274
 
48
275
  const mutate: UseMutateFunction<
49
276
  TData,
50
277
  TError,
51
278
  TVariables,
52
279
  TOnMutateResult
53
- > = (variables, mutateOptions) => {
54
- observer.mutate(variables, mutateOptions).catch(noop)
280
+ > = (variables) => {
281
+ const promise = invoke(variables)
282
+ // Errors also land in reactive state; ignoring the promise must not
283
+ // surface an unhandled rejection. Awaiting it still rejects.
284
+ promise.catch(noop)
285
+ return promise
55
286
  }
56
287
 
57
- const initialResult = untrack(() => observer.getCurrentResult())
58
- const [state, setState] = createStore<
59
- UseMutationResult<TData, TError, TVariables, TOnMutateResult>
60
- >({
61
- ...initialResult,
62
- mutate,
63
- mutateAsync: initialResult.mutate,
64
- })
65
-
66
- const unsubscribe = observer.subscribe((result) => {
67
- // observer.mutate may be invoked synchronously from an owned scope (e.g.
68
- // during render or inside an effect), which triggers this subscriber
69
- // synchronously. Store writes inside an owned scope throw in Solid v2, so
70
- // escape the owner to allow the setState write — matching useBaseQuery.
71
- runWithOwner(null, () => {
72
- setState(() => ({
73
- ...result,
74
- mutate,
75
- mutateAsync: result.mutate,
76
- }))
77
- })
78
- })
79
-
80
- onCleanup(unsubscribe)
288
+ const status = () => (flight() !== null ? 'pending' : settled().status)
289
+ const isPending = () => flight() !== null
81
290
 
82
- // Use createRenderEffect to throw errors when throwOnError is set.
83
- // The throw must happen in the compute function (first arg), not the effect
84
- // function, so that the error goes through notifyStatus and gets wrapped as
85
- // a StatusError with a source — which is required for <Errored> boundaries
86
- // to capture it via CollectionQueue.notify.
291
+ /**
292
+ * Errors surface to `<Errored>` when `throwOnError` opts in. The throw
293
+ * must happen in the compute half so it routes through notifyStatus and
294
+ * boundary accounting.
295
+ */
87
296
  createRenderEffect(
88
297
  () => {
89
- const isError = state.isError
90
- const error = state.error
298
+ const state = settled()
91
299
  if (
92
- isError &&
93
- shouldThrowError(observer.options.throwOnError, [error as TError])
300
+ state.status === 'error' &&
301
+ shouldThrowError(untrack(options).throwOnError, [state.error as TError])
94
302
  ) {
95
- throw error
303
+ throw state.error
96
304
  }
97
305
  },
98
306
  () => {},
99
307
  )
100
308
 
101
- return state
309
+ const result = {
310
+ mutate,
311
+ get data() {
312
+ return settled().data
313
+ },
314
+ get error() {
315
+ return flight() !== null ? null : settled().error
316
+ },
317
+ get variables() {
318
+ return flight()?.variables ?? settled().variables
319
+ },
320
+ get status() {
321
+ return status()
322
+ },
323
+ get isPending() {
324
+ return isPending()
325
+ },
326
+ get isIdle() {
327
+ return status() === 'idle'
328
+ },
329
+ get isSuccess() {
330
+ return status() === 'success'
331
+ },
332
+ get isError() {
333
+ return status() === 'error'
334
+ },
335
+ get submittedAt() {
336
+ return flight() !== null
337
+ ? (flightVersion(), activeMutation?.state.submittedAt ?? 0)
338
+ : settled().submittedAt
339
+ },
340
+ get failureCount() {
341
+ flightVersion()
342
+ return activeMutation?.state.failureCount ?? 0
343
+ },
344
+ get failureReason() {
345
+ flightVersion()
346
+ return (activeMutation?.state.failureReason ?? null) as TError | null
347
+ },
348
+ get isPaused() {
349
+ flightVersion()
350
+ return activeMutation?.state.isPaused ?? false
351
+ },
352
+ reset: () => {
353
+ setSettled(IDLE as SettledState<TData, TError, TVariables>)
354
+ },
355
+ }
356
+
357
+ return result as unknown as UseMutationResult<
358
+ TData,
359
+ TError,
360
+ TVariables,
361
+ TOnMutateResult
362
+ >
102
363
  }