import { notifyManager } from './notifyManager' import { Mutation } from './mutation' import { matchMutation, noop } from './utils' import { Subscribable } from './subscribable' import type { MutationObserver } from './mutationObserver' import type { DefaultError, MutationOptions, NotifyEvent } from './types' import type { QueryClient } from './queryClient' import type { Action, MutationState } from './mutation' import type { MutationFilters } from './utils' // TYPES interface MutationCacheConfig { onError?: ( error: DefaultError, variables: unknown, context: unknown, mutation: Mutation, ) => Promise | unknown onSuccess?: ( data: unknown, variables: unknown, context: unknown, mutation: Mutation, ) => Promise | unknown onMutate?: ( variables: unknown, mutation: Mutation, ) => Promise | unknown onSettled?: ( data: unknown | undefined, error: DefaultError | null, variables: unknown, context: unknown, mutation: Mutation, ) => Promise | unknown } interface NotifyEventMutationAdded extends NotifyEvent { type: 'added' mutation: Mutation } interface NotifyEventMutationRemoved extends NotifyEvent { type: 'removed' mutation: Mutation } interface NotifyEventMutationObserverAdded extends NotifyEvent { type: 'observerAdded' mutation: Mutation observer: MutationObserver } interface NotifyEventMutationObserverRemoved extends NotifyEvent { type: 'observerRemoved' mutation: Mutation observer: MutationObserver } interface NotifyEventMutationObserverOptionsUpdated extends NotifyEvent { type: 'observerOptionsUpdated' mutation?: Mutation observer: MutationObserver } interface NotifyEventMutationUpdated extends NotifyEvent { type: 'updated' mutation: Mutation action: Action } export type MutationCacheNotifyEvent = | NotifyEventMutationAdded | NotifyEventMutationRemoved | NotifyEventMutationObserverAdded | NotifyEventMutationObserverRemoved | NotifyEventMutationObserverOptionsUpdated | NotifyEventMutationUpdated type MutationCacheListener = (event: MutationCacheNotifyEvent) => void // CLASS export class MutationCache extends Subscribable { #mutations: Map>> #mutationId: number constructor(public config: MutationCacheConfig = {}) { super() this.#mutations = new Map() this.#mutationId = Date.now() } build( client: QueryClient, options: MutationOptions, state?: MutationState, ): Mutation { const mutation = new Mutation({ mutationCache: this, mutationId: ++this.#mutationId, options: client.defaultMutationOptions(options), state, }) this.add(mutation) return mutation } add(mutation: Mutation): void { const scope = scopeFor(mutation) const mutations = this.#mutations.get(scope) ?? [] mutations.push(mutation) this.#mutations.set(scope, mutations) 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) } } } 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 } runNext(mutation: Mutation): Promise { const foundMutation = this.#mutations .get(scopeFor(mutation)) ?.find((m) => m !== mutation && m.state.isPaused) return foundMutation?.continue() ?? Promise.resolve() } clear(): void { notifyManager.batch(() => { this.getAll().forEach((mutation) => { this.remove(mutation) }) }) } getAll(): Array { return [...this.#mutations.values()].flat() } find< TData = unknown, TError = DefaultError, TVariables = any, TContext = unknown, >( filters: MutationFilters, ): Mutation | undefined { const defaultedFilters = { exact: true, ...filters } return this.getAll().find((mutation) => matchMutation(defaultedFilters, mutation), ) as Mutation | undefined } findAll(filters: MutationFilters = {}): Array { return this.getAll().filter((mutation) => matchMutation(filters, mutation)) } notify(event: MutationCacheNotifyEvent) { notifyManager.batch(() => { this.listeners.forEach((listener) => { listener(event) }) }) } resumePausedMutations(): Promise { const pausedMutations = this.getAll().filter((x) => x.state.isPaused) return notifyManager.batch(() => Promise.all( pausedMutations.map((mutation) => mutation.continue().catch(noop)), ), ) } } function scopeFor(mutation: Mutation) { return mutation.options.scope?.id ?? String(mutation.mutationId) }