diff --git a/packages/query-core/src/mutationCache.ts b/packages/query-core/src/mutationCache.ts index 1663b50cef..6ab95fbfed 100644 --- a/packages/query-core/src/mutationCache.ts +++ b/packages/query-core/src/mutationCache.ts @@ -82,13 +82,15 @@ type MutationCacheListener = (event: MutationCacheNotifyEvent) => void // CLASS export class MutationCache extends Subscribable { - #mutations: Map>> + #mutations: Set> + #scopes: Map>> #mutationId: number constructor(public config: MutationCacheConfig = {}) { super() - this.#mutations = new Map() - this.#mutationId = Date.now() + this.#mutations = new Set() + this.#scopes = new Map() + this.#mutationId = 0 } build( @@ -109,59 +111,84 @@ export class MutationCache extends Subscribable { } add(mutation: Mutation): void { + this.#mutations.add(mutation) const scope = scopeFor(mutation) - const mutations = this.#mutations.get(scope) ?? [] - mutations.push(mutation) - this.#mutations.set(scope, mutations) + if (typeof scope === 'string') { + const scopedMutations = this.#scopes.get(scope) + if (scopedMutations) { + scopedMutations.push(mutation) + } else { + this.#scopes.set(scope, [mutation]) + } + } this.notify({ type: 'added', mutation }) } remove(mutation: Mutation): void { - const scope = scopeFor(mutation) - if (this.#mutations.has(scope)) { - const mutations = this.#mutations - .get(scope) - ?.filter((x) => x !== mutation) - if (mutations) { - if (mutations.length === 0) { - this.#mutations.delete(scope) - } else { - this.#mutations.set(scope, mutations) + if (this.#mutations.delete(mutation)) { + const scope = scopeFor(mutation) + if (typeof scope === 'string') { + const scopedMutations = this.#scopes.get(scope) + if (scopedMutations) { + if (scopedMutations.length > 1) { + const index = scopedMutations.indexOf(mutation) + if (index !== -1) { + scopedMutations.splice(index, 1) + } + } else if (scopedMutations[0] === mutation) { + this.#scopes.delete(scope) + } } } } + // Currently we notify the removal even if the mutation was already removed. + // Consider making this an error or not notifying of the removal depending on the desired semantics. this.notify({ type: 'removed', mutation }) } canRun(mutation: Mutation): boolean { - const firstPendingMutation = this.#mutations - .get(scopeFor(mutation)) - ?.find((m) => m.state.status === 'pending') - - // we can run if there is no current pending mutation (start use-case) - // or if WE are the first pending mutation (continue use-case) - return !firstPendingMutation || firstPendingMutation === mutation + const scope = scopeFor(mutation) + if (typeof scope === 'string') { + const mutationsWithSameScope = this.#scopes.get(scope) + const firstPendingMutation = mutationsWithSameScope?.find( + (m) => m.state.status === 'pending', + ) + // we can run if there is no current pending mutation (start use-case) + // or if WE are the first pending mutation (continue use-case) + return !firstPendingMutation || firstPendingMutation === mutation + } else { + // For unscoped mutations there are never any pending mutations in front of the + // current mutation + return true + } } runNext(mutation: Mutation): Promise { - const foundMutation = this.#mutations - .get(scopeFor(mutation)) - ?.find((m) => m !== mutation && m.state.isPaused) + const scope = scopeFor(mutation) + if (typeof scope === 'string') { + const foundMutation = this.#scopes + .get(scope) + ?.find((m) => m !== mutation && m.state.isPaused) - return foundMutation?.continue() ?? Promise.resolve() + return foundMutation?.continue() ?? Promise.resolve() + } else { + return Promise.resolve() + } } clear(): void { notifyManager.batch(() => { - this.getAll().forEach((mutation) => { - this.remove(mutation) + this.#mutations.forEach((mutation) => { + this.notify({ type: 'removed', mutation }) }) + this.#mutations.clear() + this.#scopes.clear() }) } getAll(): Array { - return [...this.#mutations.values()].flat() + return Array.from(this.#mutations) } find< @@ -203,5 +230,5 @@ export class MutationCache extends Subscribable { } function scopeFor(mutation: Mutation) { - return mutation.options.scope?.id ?? String(mutation.mutationId) + return mutation.options.scope?.id }