UNPKG

@tanstack/query-core

Version:

The framework agnostic core that powers TanStack Query

342 lines (290 loc) 7.95 kB
import type { Mutation } from './mutation' import type { Query } from './query' import type { FetchStatus, MutationKey, MutationStatus, QueryKey, QueryOptions, } from './types' // TYPES export interface QueryFilters { /** * Filter to active queries, inactive queries or all queries */ type?: QueryTypeFilter /** * Match query key exactly */ exact?: boolean /** * Include queries matching this predicate function */ predicate?: (query: Query) => boolean /** * Include queries matching this query key */ queryKey?: QueryKey /** * Include or exclude stale queries */ stale?: boolean /** * Include queries matching their fetchStatus */ fetchStatus?: FetchStatus } export interface MutationFilters { /** * Match mutation key exactly */ exact?: boolean /** * Include mutations matching this predicate function */ predicate?: (mutation: Mutation<any, any, any>) => boolean /** * Include mutations matching this mutation key */ mutationKey?: MutationKey /** * Filter by mutation status */ status?: MutationStatus } export type Updater<TInput, TOutput> = TOutput | ((input: TInput) => TOutput) export type QueryTypeFilter = 'all' | 'active' | 'inactive' // UTILS export const isServer = typeof window === 'undefined' || 'Deno' in window export function noop(): undefined { return undefined } export function functionalUpdate<TInput, TOutput>( updater: Updater<TInput, TOutput>, input: TInput, ): TOutput { return typeof updater === 'function' ? (updater as (_: TInput) => TOutput)(input) : updater } export function isValidTimeout(value: unknown): value is number { return typeof value === 'number' && value >= 0 && value !== Infinity } export function timeUntilStale(updatedAt: number, staleTime?: number): number { return Math.max(updatedAt + (staleTime || 0) - Date.now(), 0) } export function matchQuery( filters: QueryFilters, query: Query<any, any, any, any>, ): boolean { const { type = 'all', exact, fetchStatus, predicate, queryKey, stale, } = filters if (queryKey) { if (exact) { if (query.queryHash !== hashQueryKeyByOptions(queryKey, query.options)) { return false } } else if (!partialMatchKey(query.queryKey, queryKey)) { return false } } if (type !== 'all') { const isActive = query.isActive() if (type === 'active' && !isActive) { return false } if (type === 'inactive' && isActive) { return false } } if (typeof stale === 'boolean' && query.isStale() !== stale) { return false } if ( typeof fetchStatus !== 'undefined' && fetchStatus !== query.state.fetchStatus ) { return false } if (predicate && !predicate(query)) { return false } return true } export function matchMutation( filters: MutationFilters, mutation: Mutation<any, any>, ): boolean { const { exact, status, predicate, mutationKey } = filters if (mutationKey) { if (!mutation.options.mutationKey) { return false } if (exact) { if (hashKey(mutation.options.mutationKey) !== hashKey(mutationKey)) { return false } } else if (!partialMatchKey(mutation.options.mutationKey, mutationKey)) { return false } } if (status && mutation.state.status !== status) { return false } if (predicate && !predicate(mutation)) { return false } return true } export function hashQueryKeyByOptions<TQueryKey extends QueryKey = QueryKey>( queryKey: TQueryKey, options?: QueryOptions<any, any, any, TQueryKey>, ): string { const hashFn = options?.queryKeyHashFn || hashKey return hashFn(queryKey) } /** * Default query & mutation keys hash function. * Hashes the value into a stable hash. */ export function hashKey(queryKey: QueryKey | MutationKey): string { return JSON.stringify(queryKey, (_, val) => isPlainObject(val) ? Object.keys(val) .sort() .reduce((result, key) => { result[key] = val[key] return result }, {} as any) : val, ) } /** * Checks if key `b` partially matches with key `a`. */ export function partialMatchKey(a: QueryKey, b: QueryKey): boolean export function partialMatchKey(a: any, b: any): boolean { if (a === b) { return true } if (typeof a !== typeof b) { return false } if (a && b && typeof a === 'object' && typeof b === 'object') { return !Object.keys(b).some((key) => !partialMatchKey(a[key], b[key])) } return false } /** * This function returns `a` if `b` is deeply equal. * If not, it will replace any deeply equal children of `b` with those of `a`. * This can be used for structural sharing between JSON values for example. */ export function replaceEqualDeep<T>(a: unknown, b: T): T export function replaceEqualDeep(a: any, b: any): any { if (a === b) { return a } const array = isPlainArray(a) && isPlainArray(b) if (array || (isPlainObject(a) && isPlainObject(b))) { const aSize = array ? a.length : Object.keys(a).length const bItems = array ? b : Object.keys(b) const bSize = bItems.length const copy: any = array ? [] : {} let equalItems = 0 for (let i = 0; i < bSize; i++) { const key = array ? i : bItems[i] copy[key] = replaceEqualDeep(a[key], b[key]) if (copy[key] === a[key]) { equalItems++ } } return aSize === bSize && equalItems === aSize ? a : copy } return b } /** * Shallow compare objects. Only works with objects that always have the same properties. */ export function shallowEqualObjects<T>(a: T, b: T): boolean { if ((a && !b) || (b && !a)) { return false } for (const key in a) { if (a[key] !== b[key]) { return false } } return true } export function isPlainArray(value: unknown) { return Array.isArray(value) && value.length === Object.keys(value).length } // Copied from: https://github.com/jonschlinkert/is-plain-object export function isPlainObject(o: any): o is Object { if (!hasObjectPrototype(o)) { return false } // If has modified constructor const ctor = o.constructor if (typeof ctor === 'undefined') { return true } // If has modified prototype const prot = ctor.prototype if (!hasObjectPrototype(prot)) { return false } // If constructor does not have an Object-specific method if (!prot.hasOwnProperty('isPrototypeOf')) { return false } // Most likely a plain Object return true } function hasObjectPrototype(o: any): boolean { return Object.prototype.toString.call(o) === '[object Object]' } export function sleep(timeout: number): Promise<void> { return new Promise((resolve) => { setTimeout(resolve, timeout) }) } /** * Schedules a microtask. * This can be useful to schedule state updates after rendering. */ export function scheduleMicrotask(callback: () => void) { sleep(0).then(callback) } export function replaceData< TData, TOptions extends QueryOptions<any, any, any, any>, >(prevData: TData | undefined, data: TData, options: TOptions): TData { if (typeof options.structuralSharing === 'function') { return options.structuralSharing(prevData, data) } else if (options.structuralSharing !== false) { // Structurally share data between prev and new data if needed return replaceEqualDeep(prevData, data) } return data } export function keepPreviousData<T>( previousData: T | undefined, ): T | undefined { return previousData } export function addToEnd<T>(items: Array<T>, item: T, max = 0): Array<T> { const newItems = [...items, item] return max && newItems.length > max ? newItems.slice(1) : newItems } export function addToStart<T>(items: Array<T>, item: T, max = 0): Array<T> { const newItems = [item, ...items] return max && newItems.length > max ? newItems.slice(0, -1) : newItems }