UNPKG

@tanstack/query-core

Version:

The framework agnostic core that powers TanStack Query

551 lines (487 loc) 14 kB
import { functionalUpdate, hashKey, hashQueryKeyByOptions, noop, partialMatchKey, } from './utils' import { QueryCache } from './queryCache' import { MutationCache } from './mutationCache' import { focusManager } from './focusManager' import { onlineManager } from './onlineManager' import { notifyManager } from './notifyManager' import { infiniteQueryBehavior } from './infiniteQueryBehavior' import type { DataTag, NoInfer } from './types' import type { QueryState } from './query' import type { CancelOptions, DefaultError, DefaultOptions, DefaultedQueryObserverOptions, FetchInfiniteQueryOptions, FetchQueryOptions, InfiniteData, InvalidateOptions, InvalidateQueryFilters, MutationKey, MutationObserverOptions, MutationOptions, QueryClientConfig, QueryKey, QueryObserverOptions, QueryOptions, RefetchOptions, RefetchQueryFilters, ResetOptions, SetDataOptions, } from './types' import type { MutationFilters, QueryFilters, Updater } from './utils' // TYPES interface QueryDefaults { queryKey: QueryKey defaultOptions: QueryOptions<any, any, any> } interface MutationDefaults { mutationKey: MutationKey defaultOptions: MutationOptions<any, any, any, any> } // CLASS export class QueryClient { #queryCache: QueryCache #mutationCache: MutationCache #defaultOptions: DefaultOptions #queryDefaults: Map<string, QueryDefaults> #mutationDefaults: Map<string, MutationDefaults> #mountCount: number #unsubscribeFocus?: () => void #unsubscribeOnline?: () => void constructor(config: QueryClientConfig = {}) { this.#queryCache = config.queryCache || new QueryCache() this.#mutationCache = config.mutationCache || new MutationCache() this.#defaultOptions = config.defaultOptions || {} this.#queryDefaults = new Map() this.#mutationDefaults = new Map() this.#mountCount = 0 } mount(): void { this.#mountCount++ if (this.#mountCount !== 1) return this.#unsubscribeFocus = focusManager.subscribe(() => { if (focusManager.isFocused()) { this.resumePausedMutations() this.#queryCache.onFocus() } }) this.#unsubscribeOnline = onlineManager.subscribe(() => { if (onlineManager.isOnline()) { this.resumePausedMutations() this.#queryCache.onOnline() } }) } unmount(): void { this.#mountCount-- if (this.#mountCount !== 0) return this.#unsubscribeFocus?.() this.#unsubscribeFocus = undefined this.#unsubscribeOnline?.() this.#unsubscribeOnline = undefined } isFetching(filters?: QueryFilters): number { return this.#queryCache.findAll({ ...filters, fetchStatus: 'fetching' }) .length } isMutating(filters?: MutationFilters): number { return this.#mutationCache.findAll({ ...filters, status: 'pending' }).length } getQueryData< TQueryFnData = unknown, TaggedQueryKey extends QueryKey = QueryKey, TInferredQueryFnData = TaggedQueryKey extends DataTag< unknown, infer TaggedValue > ? TaggedValue : TQueryFnData, >(queryKey: TaggedQueryKey): TInferredQueryFnData | undefined getQueryData(queryKey: QueryKey) { return this.#queryCache.find({ queryKey })?.state.data } ensureQueryData< TQueryFnData, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, >( options: FetchQueryOptions<TQueryFnData, TError, TData, TQueryKey>, ): Promise<TData> { const cachedData = this.getQueryData<TData>(options.queryKey) return cachedData !== undefined ? Promise.resolve(cachedData) : this.fetchQuery(options) } getQueriesData<TQueryFnData = unknown>( filters: QueryFilters, ): Array<[QueryKey, TQueryFnData | undefined]> { return this.getQueryCache() .findAll(filters) .map(({ queryKey, state }) => { const data = state.data as TQueryFnData | undefined return [queryKey, data] }) } setQueryData< TQueryFnData = unknown, TaggedQueryKey extends QueryKey = QueryKey, TInferredQueryFnData = TaggedQueryKey extends DataTag< unknown, infer TaggedValue > ? TaggedValue : TQueryFnData, >( queryKey: TaggedQueryKey, updater: Updater< NoInfer<TInferredQueryFnData> | undefined, NoInfer<TInferredQueryFnData> | undefined >, options?: SetDataOptions, ): TInferredQueryFnData | undefined { const query = this.#queryCache.find<TInferredQueryFnData>({ queryKey }) const prevData = query?.state.data const data = functionalUpdate(updater, prevData) if (typeof data === 'undefined') { return undefined } const defaultedOptions = this.defaultQueryOptions< any, any, unknown, any, QueryKey >({ queryKey }) return this.#queryCache .build(this, defaultedOptions) .setData(data, { ...options, manual: true }) } setQueriesData<TQueryFnData>( filters: QueryFilters, updater: Updater<TQueryFnData | undefined, TQueryFnData | undefined>, options?: SetDataOptions, ): Array<[QueryKey, TQueryFnData | undefined]> { return notifyManager.batch(() => this.getQueryCache() .findAll(filters) .map(({ queryKey }) => [ queryKey, this.setQueryData<TQueryFnData>(queryKey, updater, options), ]), ) } getQueryState<TQueryFnData = unknown, TError = DefaultError>( queryKey: QueryKey, ): QueryState<TQueryFnData, TError> | undefined { return this.#queryCache.find<TQueryFnData, TError>({ queryKey })?.state } removeQueries(filters?: QueryFilters): void { const queryCache = this.#queryCache notifyManager.batch(() => { queryCache.findAll(filters).forEach((query) => { queryCache.remove(query) }) }) } resetQueries(filters?: QueryFilters, options?: ResetOptions): Promise<void> { const queryCache = this.#queryCache const refetchFilters: RefetchQueryFilters = { type: 'active', ...filters, } return notifyManager.batch(() => { queryCache.findAll(filters).forEach((query) => { query.reset() }) return this.refetchQueries(refetchFilters, options) }) } cancelQueries( filters: QueryFilters = {}, cancelOptions: CancelOptions = {}, ): Promise<void> { const defaultedCancelOptions = { revert: true, ...cancelOptions } const promises = notifyManager.batch(() => this.#queryCache .findAll(filters) .map((query) => query.cancel(defaultedCancelOptions)), ) return Promise.all(promises).then(noop).catch(noop) } invalidateQueries( filters: InvalidateQueryFilters = {}, options: InvalidateOptions = {}, ): Promise<void> { return notifyManager.batch(() => { this.#queryCache.findAll(filters).forEach((query) => { query.invalidate() }) if (filters.refetchType === 'none') { return Promise.resolve() } const refetchFilters: RefetchQueryFilters = { ...filters, type: filters.refetchType ?? filters.type ?? 'active', } return this.refetchQueries(refetchFilters, options) }) } refetchQueries( filters: RefetchQueryFilters = {}, options?: RefetchOptions, ): Promise<void> { const fetchOptions = { ...options, cancelRefetch: options?.cancelRefetch ?? true, } const promises = notifyManager.batch(() => this.#queryCache .findAll(filters) .filter((query) => !query.isDisabled()) .map((query) => { let promise = query.fetch(undefined, fetchOptions) if (!fetchOptions.throwOnError) { promise = promise.catch(noop) } return query.state.fetchStatus === 'paused' ? Promise.resolve() : promise }), ) return Promise.all(promises).then(noop) } fetchQuery< TQueryFnData, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, TPageParam = never, >( options: FetchQueryOptions< TQueryFnData, TError, TData, TQueryKey, TPageParam >, ): Promise<TData> { const defaultedOptions = this.defaultQueryOptions(options) // https://github.com/tannerlinsley/react-query/issues/652 if (typeof defaultedOptions.retry === 'undefined') { defaultedOptions.retry = false } const query = this.#queryCache.build(this, defaultedOptions) return query.isStaleByTime(defaultedOptions.staleTime) ? query.fetch(defaultedOptions) : Promise.resolve(query.state.data as TData) } prefetchQuery< TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, >( options: FetchQueryOptions<TQueryFnData, TError, TData, TQueryKey>, ): Promise<void> { return this.fetchQuery(options).then(noop).catch(noop) } fetchInfiniteQuery< TQueryFnData, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, >( options: FetchInfiniteQueryOptions< TQueryFnData, TError, TData, TQueryKey, TPageParam >, ): Promise<InfiniteData<TData, TPageParam>> { options.behavior = infiniteQueryBehavior< TQueryFnData, TError, TData, TPageParam >(options.pages) return this.fetchQuery(options) } prefetchInfiniteQuery< TQueryFnData, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, >( options: FetchInfiniteQueryOptions< TQueryFnData, TError, TData, TQueryKey, TPageParam >, ): Promise<void> { return this.fetchInfiniteQuery(options).then(noop).catch(noop) } resumePausedMutations(): Promise<unknown> { return this.#mutationCache.resumePausedMutations() } getQueryCache(): QueryCache { return this.#queryCache } getMutationCache(): MutationCache { return this.#mutationCache } getDefaultOptions(): DefaultOptions { return this.#defaultOptions } setDefaultOptions(options: DefaultOptions): void { this.#defaultOptions = options } setQueryDefaults( queryKey: QueryKey, options: Partial< Omit<QueryObserverOptions<unknown, any, any, any>, 'queryKey'> >, ): void { this.#queryDefaults.set(hashKey(queryKey), { queryKey, defaultOptions: options, }) } getQueryDefaults( queryKey: QueryKey, ): QueryObserverOptions<any, any, any, any, any> { const defaults = [...this.#queryDefaults.values()] let result: QueryObserverOptions<any, any, any, any, any> = {} defaults.forEach((queryDefault) => { if (partialMatchKey(queryKey, queryDefault.queryKey)) { result = { ...result, ...queryDefault.defaultOptions } } }) return result } setMutationDefaults( mutationKey: MutationKey, options: Omit<MutationObserverOptions<any, any, any, any>, 'mutationKey'>, ): void { this.#mutationDefaults.set(hashKey(mutationKey), { mutationKey, defaultOptions: options, }) } getMutationDefaults( mutationKey: MutationKey, ): MutationObserverOptions<any, any, any, any> { const defaults = [...this.#mutationDefaults.values()] let result: MutationObserverOptions<any, any, any, any> = {} defaults.forEach((queryDefault) => { if (partialMatchKey(mutationKey, queryDefault.mutationKey)) { result = { ...result, ...queryDefault.defaultOptions } } }) return result } defaultQueryOptions< TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, TPageParam = never, >( options?: | QueryObserverOptions< TQueryFnData, TError, TData, TQueryData, TQueryKey, TPageParam > | DefaultedQueryObserverOptions< TQueryFnData, TError, TData, TQueryData, TQueryKey >, ): DefaultedQueryObserverOptions< TQueryFnData, TError, TData, TQueryData, TQueryKey > { if (options?._defaulted) { return options as DefaultedQueryObserverOptions< TQueryFnData, TError, TData, TQueryData, TQueryKey > } const defaultedOptions = { ...this.#defaultOptions.queries, ...(options?.queryKey && this.getQueryDefaults(options.queryKey)), ...options, _defaulted: true, } if (!defaultedOptions.queryHash) { defaultedOptions.queryHash = hashQueryKeyByOptions( defaultedOptions.queryKey, defaultedOptions, ) } // dependent default values if (typeof defaultedOptions.refetchOnReconnect === 'undefined') { defaultedOptions.refetchOnReconnect = defaultedOptions.networkMode !== 'always' } if (typeof defaultedOptions.throwOnError === 'undefined') { defaultedOptions.throwOnError = !!defaultedOptions.suspense } if ( typeof defaultedOptions.networkMode === 'undefined' && defaultedOptions.persister ) { defaultedOptions.networkMode = 'offlineFirst' } return defaultedOptions as DefaultedQueryObserverOptions< TQueryFnData, TError, TData, TQueryData, TQueryKey > } defaultMutationOptions<T extends MutationOptions<any, any, any, any>>( options?: T, ): T { if (options?._defaulted) { return options } return { ...this.#defaultOptions.mutations, ...(options?.mutationKey && this.getMutationDefaults(options.mutationKey)), ...options, _defaulted: true, } as T } clear(): void { this.#queryCache.clear() this.#mutationCache.clear() } }