@tanstack/query-core 5.29.0 → 5.31.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 (116) hide show
  1. package/build/legacy/hydration.cjs +4 -4
  2. package/build/legacy/hydration.cjs.map +1 -1
  3. package/build/legacy/hydration.d.cts +2 -1
  4. package/build/legacy/hydration.d.ts +2 -1
  5. package/build/legacy/hydration.js +4 -4
  6. package/build/legacy/hydration.js.map +1 -1
  7. package/build/legacy/index.d.cts +1 -1
  8. package/build/legacy/index.d.ts +1 -1
  9. package/build/legacy/infiniteQueryBehavior.d.cts +1 -1
  10. package/build/legacy/infiniteQueryBehavior.d.ts +1 -1
  11. package/build/legacy/infiniteQueryObserver.d.cts +1 -1
  12. package/build/legacy/infiniteQueryObserver.d.ts +1 -1
  13. package/build/legacy/mutation.cjs +31 -32
  14. package/build/legacy/mutation.cjs.map +1 -1
  15. package/build/legacy/mutation.d.cts +1 -1
  16. package/build/legacy/mutation.d.ts +1 -1
  17. package/build/legacy/mutation.js +32 -33
  18. package/build/legacy/mutation.js.map +1 -1
  19. package/build/legacy/mutationCache.cjs +43 -25
  20. package/build/legacy/mutationCache.cjs.map +1 -1
  21. package/build/legacy/mutationCache.d.cts +1 -1
  22. package/build/legacy/mutationCache.d.ts +1 -1
  23. package/build/legacy/mutationCache.js +43 -25
  24. package/build/legacy/mutationCache.js.map +1 -1
  25. package/build/legacy/mutationObserver.d.cts +1 -1
  26. package/build/legacy/mutationObserver.d.ts +1 -1
  27. package/build/legacy/queriesObserver.d.cts +1 -1
  28. package/build/legacy/queriesObserver.d.ts +1 -1
  29. package/build/legacy/query.cjs +3 -2
  30. package/build/legacy/query.cjs.map +1 -1
  31. package/build/legacy/query.d.cts +1 -1
  32. package/build/legacy/query.d.ts +1 -1
  33. package/build/legacy/query.js +3 -2
  34. package/build/legacy/query.js.map +1 -1
  35. package/build/legacy/queryCache.d.cts +1 -1
  36. package/build/legacy/queryCache.d.ts +1 -1
  37. package/build/legacy/queryClient.d.cts +1 -1
  38. package/build/legacy/queryClient.d.ts +1 -1
  39. package/build/legacy/queryObserver.d.cts +1 -1
  40. package/build/legacy/queryObserver.d.ts +1 -1
  41. package/build/legacy/retryer.cjs +16 -16
  42. package/build/legacy/retryer.cjs.map +1 -1
  43. package/build/legacy/retryer.d.cts +1 -1
  44. package/build/legacy/retryer.d.ts +1 -1
  45. package/build/legacy/retryer.js +16 -16
  46. package/build/legacy/retryer.js.map +1 -1
  47. package/build/legacy/{queryClient--tFV-sQG.d.cts → types-5uTf3z3y.d.cts} +275 -266
  48. package/build/legacy/{queryClient-K0zFyarY.d.ts → types-EkjjZlVm.d.ts} +275 -266
  49. package/build/legacy/types.cjs.map +1 -1
  50. package/build/legacy/types.d.cts +1 -1
  51. package/build/legacy/types.d.ts +1 -1
  52. package/build/legacy/utils.d.cts +1 -1
  53. package/build/legacy/utils.d.ts +1 -1
  54. package/build/modern/hydration.cjs +4 -4
  55. package/build/modern/hydration.cjs.map +1 -1
  56. package/build/modern/hydration.d.cts +2 -1
  57. package/build/modern/hydration.d.ts +2 -1
  58. package/build/modern/hydration.js +4 -4
  59. package/build/modern/hydration.js.map +1 -1
  60. package/build/modern/index.d.cts +1 -1
  61. package/build/modern/index.d.ts +1 -1
  62. package/build/modern/infiniteQueryBehavior.d.cts +1 -1
  63. package/build/modern/infiniteQueryBehavior.d.ts +1 -1
  64. package/build/modern/infiniteQueryObserver.d.cts +1 -1
  65. package/build/modern/infiniteQueryObserver.d.ts +1 -1
  66. package/build/modern/mutation.cjs +30 -30
  67. package/build/modern/mutation.cjs.map +1 -1
  68. package/build/modern/mutation.d.cts +1 -1
  69. package/build/modern/mutation.d.ts +1 -1
  70. package/build/modern/mutation.js +31 -31
  71. package/build/modern/mutation.js.map +1 -1
  72. package/build/modern/mutationCache.cjs +38 -23
  73. package/build/modern/mutationCache.cjs.map +1 -1
  74. package/build/modern/mutationCache.d.cts +1 -1
  75. package/build/modern/mutationCache.d.ts +1 -1
  76. package/build/modern/mutationCache.js +38 -23
  77. package/build/modern/mutationCache.js.map +1 -1
  78. package/build/modern/mutationObserver.d.cts +1 -1
  79. package/build/modern/mutationObserver.d.ts +1 -1
  80. package/build/modern/queriesObserver.d.cts +1 -1
  81. package/build/modern/queriesObserver.d.ts +1 -1
  82. package/build/modern/query.cjs +3 -2
  83. package/build/modern/query.cjs.map +1 -1
  84. package/build/modern/query.d.cts +1 -1
  85. package/build/modern/query.d.ts +1 -1
  86. package/build/modern/query.js +3 -2
  87. package/build/modern/query.js.map +1 -1
  88. package/build/modern/queryCache.d.cts +1 -1
  89. package/build/modern/queryCache.d.ts +1 -1
  90. package/build/modern/queryClient.d.cts +1 -1
  91. package/build/modern/queryClient.d.ts +1 -1
  92. package/build/modern/queryObserver.d.cts +1 -1
  93. package/build/modern/queryObserver.d.ts +1 -1
  94. package/build/modern/retryer.cjs +16 -16
  95. package/build/modern/retryer.cjs.map +1 -1
  96. package/build/modern/retryer.d.cts +1 -1
  97. package/build/modern/retryer.d.ts +1 -1
  98. package/build/modern/retryer.js +16 -16
  99. package/build/modern/retryer.js.map +1 -1
  100. package/build/modern/{queryClient--tFV-sQG.d.cts → types-5uTf3z3y.d.cts} +275 -266
  101. package/build/modern/{queryClient-K0zFyarY.d.ts → types-EkjjZlVm.d.ts} +275 -266
  102. package/build/modern/types.cjs.map +1 -1
  103. package/build/modern/types.d.cts +1 -1
  104. package/build/modern/types.d.ts +1 -1
  105. package/build/modern/utils.d.cts +1 -1
  106. package/build/modern/utils.d.ts +1 -1
  107. package/package.json +1 -1
  108. package/src/__tests__/hydration.test.tsx +34 -0
  109. package/src/__tests__/mutations.test.tsx +191 -0
  110. package/src/__tests__/queryClient.test.tsx +96 -4
  111. package/src/hydration.ts +8 -6
  112. package/src/mutation.ts +32 -33
  113. package/src/mutationCache.ts +54 -28
  114. package/src/query.ts +2 -1
  115. package/src/retryer.ts +24 -20
  116. package/src/types.ts +5 -0
package/src/mutation.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { notifyManager } from './notifyManager'
2
2
  import { Removable } from './removable'
3
- import { canFetch, createRetryer } from './retryer'
3
+ import { createRetryer } from './retryer'
4
4
  import type {
5
5
  DefaultError,
6
6
  MutationMeta,
@@ -17,7 +17,6 @@ interface MutationConfig<TData, TError, TVariables, TContext> {
17
17
  mutationId: number
18
18
  mutationCache: MutationCache
19
19
  options: MutationOptions<TData, TError, TVariables, TContext>
20
- defaultOptions?: MutationOptions<TData, TError, TVariables, TContext>
21
20
  state?: MutationState<TData, TError, TVariables, TContext>
22
21
  }
23
22
 
@@ -46,6 +45,7 @@ interface FailedAction<TError> {
46
45
 
47
46
  interface PendingAction<TVariables, TContext> {
48
47
  type: 'pending'
48
+ isPaused: boolean
49
49
  variables?: TVariables
50
50
  context?: TContext
51
51
  }
@@ -89,7 +89,6 @@ export class Mutation<
89
89
  readonly mutationId: number
90
90
 
91
91
  #observers: Array<MutationObserver<TData, TError, TVariables, TContext>>
92
- #defaultOptions?: MutationOptions<TData, TError, TVariables, TContext>
93
92
  #mutationCache: MutationCache
94
93
  #retryer?: Retryer<TData>
95
94
 
@@ -97,7 +96,6 @@ export class Mutation<
97
96
  super()
98
97
 
99
98
  this.mutationId = config.mutationId
100
- this.#defaultOptions = config.defaultOptions
101
99
  this.#mutationCache = config.mutationCache
102
100
  this.#observers = []
103
101
  this.state = config.state || getDefaultState()
@@ -107,9 +105,9 @@ export class Mutation<
107
105
  }
108
106
 
109
107
  setOptions(
110
- options?: MutationOptions<TData, TError, TVariables, TContext>,
108
+ options: MutationOptions<TData, TError, TVariables, TContext>,
111
109
  ): void {
112
- this.options = { ...this.#defaultOptions, ...options }
110
+ this.options = options
113
111
 
114
112
  this.updateGcTime(this.options.gcTime)
115
113
  }
@@ -164,36 +162,34 @@ export class Mutation<
164
162
  }
165
163
 
166
164
  async execute(variables: TVariables): Promise<TData> {
167
- const executeMutation = () => {
168
- this.#retryer = createRetryer({
169
- fn: () => {
170
- if (!this.options.mutationFn) {
171
- return Promise.reject(new Error('No mutationFn found'))
172
- }
173
- return this.options.mutationFn(variables)
174
- },
175
- onFail: (failureCount, error) => {
176
- this.#dispatch({ type: 'failed', failureCount, error })
177
- },
178
- onPause: () => {
179
- this.#dispatch({ type: 'pause' })
180
- },
181
- onContinue: () => {
182
- this.#dispatch({ type: 'continue' })
183
- },
184
- retry: this.options.retry ?? 0,
185
- retryDelay: this.options.retryDelay,
186
- networkMode: this.options.networkMode,
187
- })
188
-
189
- return this.#retryer.promise
190
- }
165
+ this.#retryer = createRetryer({
166
+ fn: () => {
167
+ if (!this.options.mutationFn) {
168
+ return Promise.reject(new Error('No mutationFn found'))
169
+ }
170
+ return this.options.mutationFn(variables)
171
+ },
172
+ onFail: (failureCount, error) => {
173
+ this.#dispatch({ type: 'failed', failureCount, error })
174
+ },
175
+ onPause: () => {
176
+ this.#dispatch({ type: 'pause' })
177
+ },
178
+ onContinue: () => {
179
+ this.#dispatch({ type: 'continue' })
180
+ },
181
+ retry: this.options.retry ?? 0,
182
+ retryDelay: this.options.retryDelay,
183
+ networkMode: this.options.networkMode,
184
+ canRun: () => this.#mutationCache.canRun(this),
185
+ })
191
186
 
192
187
  const restored = this.state.status === 'pending'
188
+ const isPaused = !this.#retryer.canStart()
193
189
 
194
190
  try {
195
191
  if (!restored) {
196
- this.#dispatch({ type: 'pending', variables })
192
+ this.#dispatch({ type: 'pending', variables, isPaused })
197
193
  // Notify cache callback
198
194
  await this.#mutationCache.config.onMutate?.(
199
195
  variables,
@@ -205,10 +201,11 @@ export class Mutation<
205
201
  type: 'pending',
206
202
  context,
207
203
  variables,
204
+ isPaused,
208
205
  })
209
206
  }
210
207
  }
211
- const data = await executeMutation()
208
+ const data = await this.#retryer.start()
212
209
 
213
210
  // Notify cache callback
214
211
  await this.#mutationCache.config.onSuccess?.(
@@ -268,6 +265,8 @@ export class Mutation<
268
265
  } finally {
269
266
  this.#dispatch({ type: 'error', error: error as TError })
270
267
  }
268
+ } finally {
269
+ this.#mutationCache.runNext(this)
271
270
  }
272
271
  }
273
272
 
@@ -300,7 +299,7 @@ export class Mutation<
300
299
  failureCount: 0,
301
300
  failureReason: null,
302
301
  error: null,
303
- isPaused: !canFetch(this.options.networkMode),
302
+ isPaused: action.isPaused,
304
303
  status: 'pending',
305
304
  variables: action.variables,
306
305
  submittedAt: Date.now(),
@@ -82,14 +82,13 @@ type MutationCacheListener = (event: MutationCacheNotifyEvent) => void
82
82
  // CLASS
83
83
 
84
84
  export class MutationCache extends Subscribable<MutationCacheListener> {
85
- #mutations: Array<Mutation<any, any, any, any>>
85
+ #mutations: Map<string, Array<Mutation<any, any, any, any>>>
86
86
  #mutationId: number
87
- #resuming: Promise<unknown> | undefined
88
87
 
89
88
  constructor(public config: MutationCacheConfig = {}) {
90
89
  super()
91
- this.#mutations = []
92
- this.#mutationId = 0
90
+ this.#mutations = new Map()
91
+ this.#mutationId = Date.now()
93
92
  }
94
93
 
95
94
  build<TData, TError, TVariables, TContext>(
@@ -110,25 +109,59 @@ export class MutationCache extends Subscribable<MutationCacheListener> {
110
109
  }
111
110
 
112
111
  add(mutation: Mutation<any, any, any, any>): void {
113
- this.#mutations.push(mutation)
112
+ const scope = scopeFor(mutation)
113
+ const mutations = this.#mutations.get(scope) ?? []
114
+ mutations.push(mutation)
115
+ this.#mutations.set(scope, mutations)
114
116
  this.notify({ type: 'added', mutation })
115
117
  }
116
118
 
117
119
  remove(mutation: Mutation<any, any, any, any>): void {
118
- this.#mutations = this.#mutations.filter((x) => x !== mutation)
120
+ const scope = scopeFor(mutation)
121
+ if (this.#mutations.has(scope)) {
122
+ const mutations = this.#mutations
123
+ .get(scope)
124
+ ?.filter((x) => x !== mutation)
125
+ if (mutations) {
126
+ if (mutations.length === 0) {
127
+ this.#mutations.delete(scope)
128
+ } else {
129
+ this.#mutations.set(scope, mutations)
130
+ }
131
+ }
132
+ }
133
+
119
134
  this.notify({ type: 'removed', mutation })
120
135
  }
121
136
 
137
+ canRun(mutation: Mutation<any, any, any, any>): boolean {
138
+ const firstPendingMutation = this.#mutations
139
+ .get(scopeFor(mutation))
140
+ ?.find((m) => m.state.status === 'pending')
141
+
142
+ // we can run if there is no current pending mutation (start use-case)
143
+ // or if WE are the first pending mutation (continue use-case)
144
+ return !firstPendingMutation || firstPendingMutation === mutation
145
+ }
146
+
147
+ runNext(mutation: Mutation<any, any, any, any>): Promise<unknown> {
148
+ const foundMutation = this.#mutations
149
+ .get(scopeFor(mutation))
150
+ ?.find((m) => m !== mutation && m.state.isPaused)
151
+
152
+ return foundMutation?.continue() ?? Promise.resolve()
153
+ }
154
+
122
155
  clear(): void {
123
156
  notifyManager.batch(() => {
124
- this.#mutations.forEach((mutation) => {
157
+ this.getAll().forEach((mutation) => {
125
158
  this.remove(mutation)
126
159
  })
127
160
  })
128
161
  }
129
162
 
130
163
  getAll(): Array<Mutation> {
131
- return this.#mutations
164
+ return [...this.#mutations.values()].flat()
132
165
  }
133
166
 
134
167
  find<
@@ -141,15 +174,13 @@ export class MutationCache extends Subscribable<MutationCacheListener> {
141
174
  ): Mutation<TData, TError, TVariables, TContext> | undefined {
142
175
  const defaultedFilters = { exact: true, ...filters }
143
176
 
144
- return this.#mutations.find((mutation) =>
177
+ return this.getAll().find((mutation) =>
145
178
  matchMutation(defaultedFilters, mutation),
146
- )
179
+ ) as Mutation<TData, TError, TVariables, TContext> | undefined
147
180
  }
148
181
 
149
182
  findAll(filters: MutationFilters = {}): Array<Mutation> {
150
- return this.#mutations.filter((mutation) =>
151
- matchMutation(filters, mutation),
152
- )
183
+ return this.getAll().filter((mutation) => matchMutation(filters, mutation))
153
184
  }
154
185
 
155
186
  notify(event: MutationCacheNotifyEvent) {
@@ -161,21 +192,16 @@ export class MutationCache extends Subscribable<MutationCacheListener> {
161
192
  }
162
193
 
163
194
  resumePausedMutations(): Promise<unknown> {
164
- this.#resuming = (this.#resuming ?? Promise.resolve())
165
- .then(() => {
166
- const pausedMutations = this.#mutations.filter((x) => x.state.isPaused)
167
- return notifyManager.batch(() =>
168
- pausedMutations.reduce(
169
- (promise, mutation) =>
170
- promise.then(() => mutation.continue().catch(noop)),
171
- Promise.resolve() as Promise<unknown>,
172
- ),
173
- )
174
- })
175
- .then(() => {
176
- this.#resuming = undefined
177
- })
195
+ const pausedMutations = this.getAll().filter((x) => x.state.isPaused)
178
196
 
179
- return this.#resuming
197
+ return notifyManager.batch(() =>
198
+ Promise.all(
199
+ pausedMutations.map((mutation) => mutation.continue().catch(noop)),
200
+ ),
201
+ )
180
202
  }
181
203
  }
204
+
205
+ function scopeFor(mutation: Mutation<any, any, any, any>) {
206
+ return mutation.options.scope?.id ?? String(mutation.mutationId)
207
+ }
package/src/query.ts CHANGED
@@ -527,9 +527,10 @@ export class Query<
527
527
  retry: context.options.retry,
528
528
  retryDelay: context.options.retryDelay,
529
529
  networkMode: context.options.networkMode,
530
+ canRun: () => true,
530
531
  })
531
532
 
532
- return this.#retryer.promise
533
+ return this.#retryer.start()
533
534
  }
534
535
 
535
536
  #dispatch(action: Action<TData, TError>): void {
package/src/retryer.ts CHANGED
@@ -16,6 +16,7 @@ interface RetryerConfig<TData = unknown, TError = DefaultError> {
16
16
  retry?: RetryValue<TError>
17
17
  retryDelay?: RetryDelayValue<TError>
18
18
  networkMode: NetworkMode | undefined
19
+ canRun: () => boolean
19
20
  }
20
21
 
21
22
  export interface Retryer<TData = unknown> {
@@ -24,6 +25,8 @@ export interface Retryer<TData = unknown> {
24
25
  continue: () => Promise<unknown>
25
26
  cancelRetry: () => void
26
27
  continueRetry: () => void
28
+ canStart: () => boolean
29
+ start: () => Promise<TData>
27
30
  }
28
31
 
29
32
  export type RetryValue<TError> = boolean | number | ShouldRetryFunction<TError>
@@ -69,7 +72,7 @@ export function createRetryer<TData = unknown, TError = DefaultError>(
69
72
  let isRetryCancelled = false
70
73
  let failureCount = 0
71
74
  let isResolved = false
72
- let continueFn: ((value?: unknown) => boolean) | undefined
75
+ let continueFn: ((value?: unknown) => void) | undefined
73
76
  let promiseResolve: (data: TData) => void
74
77
  let promiseReject: (error: TError) => void
75
78
 
@@ -93,9 +96,12 @@ export function createRetryer<TData = unknown, TError = DefaultError>(
93
96
  isRetryCancelled = false
94
97
  }
95
98
 
96
- const shouldPause = () =>
97
- !focusManager.isFocused() ||
98
- (config.networkMode !== 'always' && !onlineManager.isOnline())
99
+ const canContinue = () =>
100
+ focusManager.isFocused() &&
101
+ (config.networkMode === 'always' || onlineManager.isOnline()) &&
102
+ config.canRun()
103
+
104
+ const canStart = () => canFetch(config.networkMode) && config.canRun()
99
105
 
100
106
  const resolve = (value: any) => {
101
107
  if (!isResolved) {
@@ -118,11 +124,9 @@ export function createRetryer<TData = unknown, TError = DefaultError>(
118
124
  const pause = () => {
119
125
  return new Promise((continueResolve) => {
120
126
  continueFn = (value) => {
121
- const canContinue = isResolved || !shouldPause()
122
- if (canContinue) {
127
+ if (isResolved || canContinue()) {
123
128
  continueResolve(value)
124
129
  }
125
- return canContinue
126
130
  }
127
131
  config.onPause?.()
128
132
  }).then(() => {
@@ -184,10 +188,7 @@ export function createRetryer<TData = unknown, TError = DefaultError>(
184
188
  sleep(delay)
185
189
  // Pause if the document is not visible or when the device is offline
186
190
  .then(() => {
187
- if (shouldPause()) {
188
- return pause()
189
- }
190
- return
191
+ return canContinue() ? undefined : pause()
191
192
  })
192
193
  .then(() => {
193
194
  if (isRetryCancelled) {
@@ -199,21 +200,24 @@ export function createRetryer<TData = unknown, TError = DefaultError>(
199
200
  })
200
201
  }
201
202
 
202
- // Start loop
203
- if (canFetch(config.networkMode)) {
204
- run()
205
- } else {
206
- pause().then(run)
207
- }
208
-
209
203
  return {
210
204
  promise,
211
205
  cancel,
212
206
  continue: () => {
213
- const didContinue = continueFn?.()
214
- return didContinue ? promise : Promise.resolve()
207
+ continueFn?.()
208
+ return promise
215
209
  },
216
210
  cancelRetry,
217
211
  continueRetry,
212
+ canStart,
213
+ start: () => {
214
+ // Start loop
215
+ if (canStart()) {
216
+ run()
217
+ } else {
218
+ pause().then(run)
219
+ }
220
+ return promise
221
+ },
218
222
  }
219
223
  }
package/src/types.ts CHANGED
@@ -717,6 +717,10 @@ export type MutationKey = ReadonlyArray<unknown>
717
717
 
718
718
  export type MutationStatus = 'idle' | 'pending' | 'success' | 'error'
719
719
 
720
+ export type MutationScope = {
721
+ id: string
722
+ }
723
+
720
724
  export type MutationMeta = Register extends {
721
725
  mutationMeta: infer TMutationMeta
722
726
  }
@@ -762,6 +766,7 @@ export interface MutationOptions<
762
766
  gcTime?: number
763
767
  _defaulted?: boolean
764
768
  meta?: MutationMeta
769
+ scope?: MutationScope
765
770
  }
766
771
 
767
772
  export interface MutationObserverOptions<