import { Mutation } from './mutation' import { Action, MutationInfo, MutationInfoOptions, MutationStatus, createMutationInfo, } from './mutationInfo' import { QueryClient } from './queryClient' import { DeepPartial, NotifyEvent } from './typeUtils' import { UNDEFINED, getFullKey, hashKey, partialMatchKey } from './utils' export interface MutationCache extends ReturnType {} export interface MutationCacheConfig { onSuccess?: ( data: unknown, variables: unknown, mutationInfo: MutationInfo ) => Promise | unknown onError?: ( error: unknown, variables: unknown, mutationInfo: MutationInfo ) => Promise | unknown onSettled?: ( data: unknown, error: unknown | null, variables: unknown, mutationInfo: MutationInfo ) => Promise | unknown } interface NotifyEventMutationAdded extends NotifyEvent { type: 'added' mutationInfo: MutationInfo } interface NotifyEventMutationRemoved extends NotifyEvent { type: 'removed' mutationInfo: MutationInfo } interface NotifyEventMutationUpdated extends NotifyEvent { type: 'updated' mutationInfo: MutationInfo action: Action } export type MutationCacheNotifyEvent = | NotifyEventMutationAdded | NotifyEventMutationRemoved | NotifyEventMutationUpdated export type MutationCacheListener = ( event: MutationCacheNotifyEvent ) => void export const createMutationCache = (config: MutationCacheConfig = {}) => { let mutations: MutationInfo[] = [] let lastUpdated = 0 const listeners = new Set>() function subscribe( filters: MutationInfoFilters, listener: MutationCacheListener ): () => void function subscribe( listener: MutationCacheListener ): () => void function subscribe(...args: any) { const [filters, listener] = args.length === 2 ? args : [UNDEFINED, args[0]] const wrappedListener = ( event: MutationCacheNotifyEvent ) => { if (!filters || matchMutationInfo(filters, event.mutationInfo)) { listener(event) } } listeners.add(wrappedListener) return () => { listeners.delete(wrappedListener) } } const notify = ( event: MutationCacheNotifyEvent ) => { lastUpdated = Date.now() listeners.forEach(listener => listener(event)) } const build = ( client: QueryClient, options: MutationInfoOptions ): MutationInfo => { const mutationInfo = createMutationInfo({ cache, options: client.defaultMutationOptions(options), }) mutations.push(mutationInfo) notify({ type: 'added', mutationInfo, }) return mutationInfo } const remove = (mutationInfo: MutationInfo): void => { mutations = mutations.filter(x => x !== mutationInfo) notify({ type: 'removed', mutationInfo, }) } const clear = (): void => { mutations.forEach(mutationInfo => { notify({ type: 'removed', mutationInfo, }) }) mutations = [] } const getAll = (): MutationInfo[] => { return mutations } const find = ( filters: MutationInfoFilters ): MutationInfo | undefined => { return mutations.find(mutationInfo => matchMutationInfo(filters, mutationInfo) ) as MutationInfo | undefined } const findAll = ( filters: MutationInfoFilters = {} ): MutationInfo[] => { return mutations.filter(mutation => matchMutationInfo(filters, mutation) ) as unknown as MutationInfo[] } const cache = { build, remove, clear, getAll, find, findAll, subscribe, notify, config, get lastUpdated() { return lastUpdated }, } return cache } export interface MutationInfoFilters< TData = unknown, TVars = unknown, TError = Error > { /** * Include mutations matching this predicate function */ predicate?: (mutationInfo: MutationInfo) => boolean /** * Include mutations matching this mutation key */ mutation?: Mutation /** * Filter by mutation status */ status?: MutationStatus /** * Filter by mutation variables */ variables?: DeepPartial /** * Match mutation key exactly */ exact?: boolean } const matchMutationInfo = ( filters: MutationInfoFilters, mutationInfo: MutationInfo ): boolean => { const { status, variables, predicate, mutation, exact } = filters if (mutation) { if (exact) { if ( hashKey(getFullKey(mutation.key, variables)) !== hashKey( getFullKey(mutationInfo.mutation.key, mutationInfo.state.variables) ) ) { return false } } else if ( !partialMatchKey( getFullKey(mutationInfo.mutation.key, mutationInfo.state.variables), getFullKey(mutation.key, variables) ) ) { return false } } if (status && mutationInfo.state.status !== status) { return false } if (predicate && !predicate(mutationInfo)) { return false } return true }