UNPKG

react-query

Version:

Hooks for managing, caching and syncing asynchronous and remote data in React

1,776 lines (1,471 loc) 92.7 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ReactQueryCore = {})); })(this, (function (exports) { 'use strict'; class Subscribable { constructor() { this.listeners = []; this.subscribe = this.subscribe.bind(this); } subscribe(listener) { this.listeners.push(listener); this.onSubscribe(); return () => { this.listeners = this.listeners.filter(x => x !== listener); this.onUnsubscribe(); }; } hasListeners() { return this.listeners.length > 0; } onSubscribe() {// Do nothing } onUnsubscribe() {// Do nothing } } // TYPES // UTILS const isServer = typeof window === 'undefined'; function noop() { return undefined; } function functionalUpdate(updater, input) { return typeof updater === 'function' ? updater(input) : updater; } function isValidTimeout(value) { return typeof value === 'number' && value >= 0 && value !== Infinity; } function difference(array1, array2) { return array1.filter(x => array2.indexOf(x) === -1); } function replaceAt(array, index, value) { const copy = array.slice(0); copy[index] = value; return copy; } function timeUntilStale(updatedAt, staleTime) { return Math.max(updatedAt + (staleTime || 0) - Date.now(), 0); } function parseQueryArgs(arg1, arg2, arg3) { if (!isQueryKey(arg1)) { return arg1; } if (typeof arg2 === 'function') { return { ...arg3, queryKey: arg1, queryFn: arg2 }; } return { ...arg2, queryKey: arg1 }; } function parseFilterArgs(arg1, arg2, arg3) { return isQueryKey(arg1) ? [{ ...arg2, queryKey: arg1 }, arg3] : [arg1 || {}, arg2]; } function matchQuery(filters, query) { const { type = 'all', exact, fetchStatus, predicate, queryKey, stale } = filters; if (isQueryKey(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; } function matchMutation(filters, mutation) { const { exact, fetching, predicate, mutationKey } = filters; if (isQueryKey(mutationKey)) { if (!mutation.options.mutationKey) { return false; } if (exact) { if (hashQueryKey(mutation.options.mutationKey) !== hashQueryKey(mutationKey)) { return false; } } else if (!partialMatchKey(mutation.options.mutationKey, mutationKey)) { return false; } } if (typeof fetching === 'boolean' && mutation.state.status === 'loading' !== fetching) { return false; } if (predicate && !predicate(mutation)) { return false; } return true; } function hashQueryKeyByOptions(queryKey, options) { const hashFn = (options == null ? void 0 : options.queryKeyHashFn) || hashQueryKey; return hashFn(queryKey); } /** * Default query keys hash function. * Hashes the value into a stable hash. */ function hashQueryKey(queryKey) { return JSON.stringify(queryKey, (_, val) => isPlainObject(val) ? Object.keys(val).sort().reduce((result, key) => { result[key] = val[key]; return result; }, {}) : val); } /** * Checks if key `b` partially matches with key `a`. */ function partialMatchKey(a, b) { return partialDeepEqual(a, b); } /** * Checks if `b` partially matches with `a`. */ function partialDeepEqual(a, b) { 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 => !partialDeepEqual(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. */ function replaceEqualDeep(a, b) { if (a === b) { return a; } const array = Array.isArray(a) && Array.isArray(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 = 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. */ function shallowEqualObjects(a, b) { if (a && !b || b && !a) { return false; } for (const key in a) { if (a[key] !== b[key]) { return false; } } return true; } // Copied from: https://github.com/jonschlinkert/is-plain-object function isPlainObject(o) { 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) { return Object.prototype.toString.call(o) === '[object Object]'; } function isQueryKey(value) { return Array.isArray(value); } function isError(value) { return value instanceof Error; } function sleep(timeout) { return new Promise(resolve => { setTimeout(resolve, timeout); }); } /** * Schedules a microtask. * This can be useful to schedule state updates after rendering. */ function scheduleMicrotask(callback) { sleep(0).then(callback); } function getAbortController() { if (typeof AbortController === 'function') { return new AbortController(); } } class FocusManager extends Subscribable { constructor() { super(); this.setup = onFocus => { // addEventListener does not exist in React Native, but window does // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!isServer && window.addEventListener) { const listener = () => onFocus(); // Listen to visibillitychange and focus window.addEventListener('visibilitychange', listener, false); window.addEventListener('focus', listener, false); return () => { // Be sure to unsubscribe if a new handler is set window.removeEventListener('visibilitychange', listener); window.removeEventListener('focus', listener); }; } }; } onSubscribe() { if (!this.cleanup) { this.setEventListener(this.setup); } } onUnsubscribe() { if (!this.hasListeners()) { var _this$cleanup; (_this$cleanup = this.cleanup) == null ? void 0 : _this$cleanup.call(this); this.cleanup = undefined; } } setEventListener(setup) { var _this$cleanup2; this.setup = setup; (_this$cleanup2 = this.cleanup) == null ? void 0 : _this$cleanup2.call(this); this.cleanup = setup(focused => { if (typeof focused === 'boolean') { this.setFocused(focused); } else { this.onFocus(); } }); } setFocused(focused) { this.focused = focused; if (focused) { this.onFocus(); } } onFocus() { this.listeners.forEach(listener => { listener(); }); } isFocused() { if (typeof this.focused === 'boolean') { return this.focused; } // document global can be unavailable in react native if (typeof document === 'undefined') { return true; } return [undefined, 'visible', 'prerender'].includes(document.visibilityState); } } const focusManager = new FocusManager(); class OnlineManager extends Subscribable { constructor() { super(); this.setup = onOnline => { // addEventListener does not exist in React Native, but window does // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!isServer && window.addEventListener) { const listener = () => onOnline(); // Listen to online window.addEventListener('online', listener, false); window.addEventListener('offline', listener, false); return () => { // Be sure to unsubscribe if a new handler is set window.removeEventListener('online', listener); window.removeEventListener('offline', listener); }; } }; } onSubscribe() { if (!this.cleanup) { this.setEventListener(this.setup); } } onUnsubscribe() { if (!this.hasListeners()) { var _this$cleanup; (_this$cleanup = this.cleanup) == null ? void 0 : _this$cleanup.call(this); this.cleanup = undefined; } } setEventListener(setup) { var _this$cleanup2; this.setup = setup; (_this$cleanup2 = this.cleanup) == null ? void 0 : _this$cleanup2.call(this); this.cleanup = setup(online => { if (typeof online === 'boolean') { this.setOnline(online); } else { this.onOnline(); } }); } setOnline(online) { this.online = online; if (online) { this.onOnline(); } } onOnline() { this.listeners.forEach(listener => { listener(); }); } isOnline() { if (typeof this.online === 'boolean') { return this.online; } if (typeof navigator === 'undefined' || typeof navigator.onLine === 'undefined') { return true; } return navigator.onLine; } } const onlineManager = new OnlineManager(); function defaultRetryDelay(failureCount) { return Math.min(1000 * 2 ** failureCount, 30000); } function canFetch(networkMode) { return (networkMode != null ? networkMode : 'online') === 'online' ? onlineManager.isOnline() : true; } class CancelledError { constructor(options) { this.revert = options == null ? void 0 : options.revert; this.silent = options == null ? void 0 : options.silent; } } function isCancelledError(value) { return value instanceof CancelledError; } function createRetryer(config) { let isRetryCancelled = false; let failureCount = 0; let isResolved = false; let continueFn; let promiseResolve; let promiseReject; const promise = new Promise((outerResolve, outerReject) => { promiseResolve = outerResolve; promiseReject = outerReject; }); const cancel = cancelOptions => { if (!isResolved) { reject(new CancelledError(cancelOptions)); config.abort == null ? void 0 : config.abort(); } }; const cancelRetry = () => { isRetryCancelled = true; }; const continueRetry = () => { isRetryCancelled = false; }; const shouldPause = () => !focusManager.isFocused() || config.networkMode !== 'always' && !onlineManager.isOnline(); const resolve = value => { if (!isResolved) { isResolved = true; config.onSuccess == null ? void 0 : config.onSuccess(value); continueFn == null ? void 0 : continueFn(); promiseResolve(value); } }; const reject = value => { if (!isResolved) { isResolved = true; config.onError == null ? void 0 : config.onError(value); continueFn == null ? void 0 : continueFn(); promiseReject(value); } }; const pause = () => { return new Promise(continueResolve => { continueFn = value => { if (isResolved || !shouldPause()) { return continueResolve(value); } }; config.onPause == null ? void 0 : config.onPause(); }).then(() => { continueFn = undefined; if (!isResolved) { config.onContinue == null ? void 0 : config.onContinue(); } }); }; // Create loop function const run = () => { // Do nothing if already resolved if (isResolved) { return; } let promiseOrValue; // Execute query try { promiseOrValue = config.fn(); } catch (error) { promiseOrValue = Promise.reject(error); } Promise.resolve(promiseOrValue).then(resolve).catch(error => { var _config$retry, _config$retryDelay; // Stop if the fetch is already resolved if (isResolved) { return; } // Do we need to retry the request? const retry = (_config$retry = config.retry) != null ? _config$retry : 3; const retryDelay = (_config$retryDelay = config.retryDelay) != null ? _config$retryDelay : defaultRetryDelay; const delay = typeof retryDelay === 'function' ? retryDelay(failureCount, error) : retryDelay; const shouldRetry = retry === true || typeof retry === 'number' && failureCount < retry || typeof retry === 'function' && retry(failureCount, error); if (isRetryCancelled || !shouldRetry) { // We are done if the query does not need to be retried reject(error); return; } failureCount++; // Notify on fail config.onFail == null ? void 0 : config.onFail(failureCount, error); // Delay sleep(delay) // Pause if the document is not visible or when the device is offline .then(() => { if (shouldPause()) { return pause(); } }).then(() => { if (isRetryCancelled) { reject(error); } else { run(); } }); }); }; // Start loop if (canFetch(config.networkMode)) { run(); } else { pause().then(run); } return { promise, cancel, continue: () => { continueFn == null ? void 0 : continueFn(); }, cancelRetry, continueRetry }; } const defaultLogger = console; function createNotifyManager() { let queue = []; let transactions = 0; let notifyFn = callback => { callback(); }; let batchNotifyFn = callback => { callback(); }; const batch = callback => { let result; transactions++; try { result = callback(); } finally { transactions--; if (!transactions) { flush(); } } return result; }; const schedule = callback => { if (transactions) { queue.push(callback); } else { scheduleMicrotask(() => { notifyFn(callback); }); } }; /** * All calls to the wrapped function will be batched. */ const batchCalls = callback => { return (...args) => { schedule(() => { callback(...args); }); }; }; const flush = () => { const originalQueue = queue; queue = []; if (originalQueue.length) { scheduleMicrotask(() => { batchNotifyFn(() => { originalQueue.forEach(callback => { notifyFn(callback); }); }); }); } }; /** * Use this method to set a custom notify function. * This can be used to for example wrap notifications with `React.act` while running tests. */ const setNotifyFunction = fn => { notifyFn = fn; }; /** * Use this method to set a custom function to batch notifications together into a single tick. * By default React Query will use the batch function provided by ReactDOM or React Native. */ const setBatchNotifyFunction = fn => { batchNotifyFn = fn; }; return { batch, batchCalls, schedule, setNotifyFunction, setBatchNotifyFunction }; } // SINGLETON const notifyManager = createNotifyManager(); class Removable { destroy() { this.clearGcTimeout(); } scheduleGc() { this.clearGcTimeout(); if (isValidTimeout(this.cacheTime)) { this.gcTimeout = setTimeout(() => { this.optionalRemove(); }, this.cacheTime); } } updateCacheTime(newCacheTime) { // Default to 5 minutes (Infinity for server-side) if no cache time is set this.cacheTime = Math.max(this.cacheTime || 0, newCacheTime != null ? newCacheTime : isServer ? Infinity : 5 * 60 * 1000); } clearGcTimeout() { clearTimeout(this.gcTimeout); this.gcTimeout = undefined; } } // CLASS class Query extends Removable { constructor(config) { super(); this.abortSignalConsumed = false; this.defaultOptions = config.defaultOptions; this.setOptions(config.options); this.observers = []; this.cache = config.cache; this.logger = config.logger || defaultLogger; this.queryKey = config.queryKey; this.queryHash = config.queryHash; this.initialState = config.state || getDefaultState$1(this.options); this.state = this.initialState; this.meta = config.meta; } setOptions(options) { this.options = { ...this.defaultOptions, ...options }; this.meta = options == null ? void 0 : options.meta; this.updateCacheTime(this.options.cacheTime); } optionalRemove() { if (!this.observers.length && this.state.fetchStatus === 'idle') { this.cache.remove(this); } } setData(data, options) { var _this$options$isDataE, _this$options; const prevData = this.state.data; // Use prev data if an isDataEqual function is defined and returns `true` if ((_this$options$isDataE = (_this$options = this.options).isDataEqual) != null && _this$options$isDataE.call(_this$options, prevData, data)) { data = prevData; } else if (this.options.structuralSharing !== false) { // Structurally share data between prev and new data if needed data = replaceEqualDeep(prevData, data); } // Set data and mark it as cached this.dispatch({ data, type: 'success', dataUpdatedAt: options == null ? void 0 : options.updatedAt, notifySuccess: options == null ? void 0 : options.notifySuccess }); return data; } setState(state, setStateOptions) { this.dispatch({ type: 'setState', state, setStateOptions }); } cancel(options) { var _this$retryer; const promise = this.promise; (_this$retryer = this.retryer) == null ? void 0 : _this$retryer.cancel(options); return promise ? promise.then(noop).catch(noop) : Promise.resolve(); } destroy() { super.destroy(); this.cancel({ silent: true }); } reset() { this.destroy(); this.setState(this.initialState); } isActive() { return this.observers.some(observer => observer.options.enabled !== false); } isDisabled() { return this.getObserversCount() > 0 && !this.isActive(); } isStale() { return this.state.isInvalidated || !this.state.dataUpdatedAt || this.observers.some(observer => observer.getCurrentResult().isStale); } isStaleByTime(staleTime = 0) { return this.state.isInvalidated || !this.state.dataUpdatedAt || !timeUntilStale(this.state.dataUpdatedAt, staleTime); } onFocus() { var _this$retryer2; const observer = this.observers.find(x => x.shouldFetchOnWindowFocus()); if (observer) { observer.refetch({ cancelRefetch: false }); } // Continue fetch if currently paused (_this$retryer2 = this.retryer) == null ? void 0 : _this$retryer2.continue(); } onOnline() { var _this$retryer3; const observer = this.observers.find(x => x.shouldFetchOnReconnect()); if (observer) { observer.refetch({ cancelRefetch: false }); } // Continue fetch if currently paused (_this$retryer3 = this.retryer) == null ? void 0 : _this$retryer3.continue(); } addObserver(observer) { if (this.observers.indexOf(observer) === -1) { this.observers.push(observer); // Stop the query from being garbage collected this.clearGcTimeout(); this.cache.notify({ type: 'observerAdded', query: this, observer }); } } removeObserver(observer) { if (this.observers.indexOf(observer) !== -1) { this.observers = this.observers.filter(x => x !== observer); if (!this.observers.length) { // If the transport layer does not support cancellation // we'll let the query continue so the result can be cached if (this.retryer) { if (this.abortSignalConsumed) { this.retryer.cancel({ revert: true }); } else { this.retryer.cancelRetry(); } } this.scheduleGc(); } this.cache.notify({ type: 'observerRemoved', query: this, observer }); } } getObserversCount() { return this.observers.length; } invalidate() { if (!this.state.isInvalidated) { this.dispatch({ type: 'invalidate' }); } } fetch(options, fetchOptions) { var _this$options$behavio, _context$fetchOptions; if (this.state.fetchStatus !== 'idle') { if (this.state.dataUpdatedAt && fetchOptions != null && fetchOptions.cancelRefetch) { // Silently cancel current fetch if the user wants to cancel refetches this.cancel({ silent: true }); } else if (this.promise) { var _this$retryer4; // make sure that retries that were potentially cancelled due to unmounts can continue (_this$retryer4 = this.retryer) == null ? void 0 : _this$retryer4.continueRetry(); // Return current promise if we are already fetching return this.promise; } } // Update config if passed, otherwise the config from the last execution is used if (options) { this.setOptions(options); } // Use the options from the first observer with a query function if no function is found. // This can happen when the query is hydrated or created with setQueryData. if (!this.options.queryFn) { const observer = this.observers.find(x => x.options.queryFn); if (observer) { this.setOptions(observer.options); } } if (!Array.isArray(this.options.queryKey)) { if (process.env.NODE_ENV !== 'production') { this.logger.error("As of v4, queryKey needs to be an Array. If you are using a string like 'repoData', please change it to an Array, e.g. ['repoData']"); } } const abortController = getAbortController(); // Create query function context const queryFnContext = { queryKey: this.queryKey, pageParam: undefined, meta: this.meta }; // Adds an enumerable signal property to the object that // which sets abortSignalConsumed to true when the signal // is read. const addSignalProperty = object => { Object.defineProperty(object, 'signal', { enumerable: true, get: () => { if (abortController) { this.abortSignalConsumed = true; return abortController.signal; } return undefined; } }); }; addSignalProperty(queryFnContext); // Create fetch function const fetchFn = () => { if (!this.options.queryFn) { return Promise.reject('Missing queryFn'); } this.abortSignalConsumed = false; return this.options.queryFn(queryFnContext); }; // Trigger behavior hook const context = { fetchOptions, options: this.options, queryKey: this.queryKey, state: this.state, fetchFn, meta: this.meta }; addSignalProperty(context); (_this$options$behavio = this.options.behavior) == null ? void 0 : _this$options$behavio.onFetch(context); // Store state in case the current fetch needs to be reverted this.revertState = this.state; // Set to fetching state if not already in it if (this.state.fetchStatus === 'idle' || this.state.fetchMeta !== ((_context$fetchOptions = context.fetchOptions) == null ? void 0 : _context$fetchOptions.meta)) { var _context$fetchOptions2; this.dispatch({ type: 'fetch', meta: (_context$fetchOptions2 = context.fetchOptions) == null ? void 0 : _context$fetchOptions2.meta }); } const onError = error => { // Optimistically update state if needed if (!(isCancelledError(error) && error.silent)) { this.dispatch({ type: 'error', error: error }); } if (!isCancelledError(error)) { var _this$cache$config$on, _this$cache$config; // Notify cache callback (_this$cache$config$on = (_this$cache$config = this.cache.config).onError) == null ? void 0 : _this$cache$config$on.call(_this$cache$config, error, this); if (process.env.NODE_ENV !== 'production') { this.logger.error(error); } } if (!this.isFetchingOptimistic) { // Schedule query gc after fetching this.scheduleGc(); } this.isFetchingOptimistic = false; }; // Try to fetch the data this.retryer = createRetryer({ fn: context.fetchFn, abort: abortController == null ? void 0 : abortController.abort.bind(abortController), onSuccess: data => { var _this$cache$config$on2, _this$cache$config2; if (typeof data === 'undefined') { onError(new Error('Query data cannot be undefined')); return; } this.setData(data); // Notify cache callback (_this$cache$config$on2 = (_this$cache$config2 = this.cache.config).onSuccess) == null ? void 0 : _this$cache$config$on2.call(_this$cache$config2, data, this); if (!this.isFetchingOptimistic) { // Schedule query gc after fetching this.scheduleGc(); } this.isFetchingOptimistic = false; }, onError, onFail: () => { this.dispatch({ type: 'failed' }); }, onPause: () => { this.dispatch({ type: 'pause' }); }, onContinue: () => { this.dispatch({ type: 'continue' }); }, retry: context.options.retry, retryDelay: context.options.retryDelay, networkMode: context.options.networkMode }); this.promise = this.retryer.promise; return this.promise; } dispatch(action) { const reducer = state => { var _action$meta, _action$dataUpdatedAt; switch (action.type) { case 'failed': return { ...state, fetchFailureCount: state.fetchFailureCount + 1 }; case 'pause': return { ...state, fetchStatus: 'paused' }; case 'continue': return { ...state, fetchStatus: 'fetching' }; case 'fetch': return { ...state, fetchFailureCount: 0, fetchMeta: (_action$meta = action.meta) != null ? _action$meta : null, fetchStatus: canFetch(this.options.networkMode) ? 'fetching' : 'paused', ...(!state.dataUpdatedAt && { error: null, status: 'loading' }) }; case 'success': return { ...state, data: action.data, dataUpdateCount: state.dataUpdateCount + 1, dataUpdatedAt: (_action$dataUpdatedAt = action.dataUpdatedAt) != null ? _action$dataUpdatedAt : Date.now(), error: null, fetchFailureCount: 0, isInvalidated: false, fetchStatus: 'idle', status: 'success' }; case 'error': const error = action.error; if (isCancelledError(error) && error.revert && this.revertState) { return { ...this.revertState }; } return { ...state, error: error, errorUpdateCount: state.errorUpdateCount + 1, errorUpdatedAt: Date.now(), fetchFailureCount: state.fetchFailureCount + 1, fetchStatus: 'idle', status: 'error' }; case 'invalidate': return { ...state, isInvalidated: true }; case 'setState': return { ...state, ...action.state }; } }; this.state = reducer(this.state); notifyManager.batch(() => { this.observers.forEach(observer => { observer.onQueryUpdate(action); }); this.cache.notify({ query: this, type: 'updated', action }); }); } } function getDefaultState$1(options) { const data = typeof options.initialData === 'function' ? options.initialData() : options.initialData; const hasInitialData = typeof options.initialData !== 'undefined'; const initialDataUpdatedAt = hasInitialData ? typeof options.initialDataUpdatedAt === 'function' ? options.initialDataUpdatedAt() : options.initialDataUpdatedAt : 0; const hasData = typeof data !== 'undefined'; return { data, dataUpdateCount: 0, dataUpdatedAt: hasData ? initialDataUpdatedAt != null ? initialDataUpdatedAt : Date.now() : 0, error: null, errorUpdateCount: 0, errorUpdatedAt: 0, fetchFailureCount: 0, fetchMeta: null, isInvalidated: false, status: hasData ? 'success' : 'loading', fetchStatus: 'idle' }; } // CLASS class QueryCache extends Subscribable { constructor(config) { super(); this.config = config || {}; this.queries = []; this.queriesMap = {}; } build(client, options, state) { var _options$queryHash; const queryKey = options.queryKey; const queryHash = (_options$queryHash = options.queryHash) != null ? _options$queryHash : hashQueryKeyByOptions(queryKey, options); let query = this.get(queryHash); if (!query) { query = new Query({ cache: this, logger: client.getLogger(), queryKey, queryHash, options: client.defaultQueryOptions(options), state, defaultOptions: client.getQueryDefaults(queryKey), meta: options.meta }); this.add(query); } return query; } add(query) { if (!this.queriesMap[query.queryHash]) { this.queriesMap[query.queryHash] = query; this.queries.push(query); this.notify({ type: 'added', query }); } } remove(query) { const queryInMap = this.queriesMap[query.queryHash]; if (queryInMap) { query.destroy(); this.queries = this.queries.filter(x => x !== query); if (queryInMap === query) { delete this.queriesMap[query.queryHash]; } this.notify({ type: 'removed', query }); } } clear() { notifyManager.batch(() => { this.queries.forEach(query => { this.remove(query); }); }); } get(queryHash) { return this.queriesMap[queryHash]; } getAll() { return this.queries; } find(arg1, arg2) { const [filters] = parseFilterArgs(arg1, arg2); if (typeof filters.exact === 'undefined') { filters.exact = true; } return this.queries.find(query => matchQuery(filters, query)); } findAll(arg1, arg2) { const [filters] = parseFilterArgs(arg1, arg2); return Object.keys(filters).length > 0 ? this.queries.filter(query => matchQuery(filters, query)) : this.queries; } notify(event) { notifyManager.batch(() => { this.listeners.forEach(listener => { listener(event); }); }); } onFocus() { notifyManager.batch(() => { this.queries.forEach(query => { query.onFocus(); }); }); } onOnline() { notifyManager.batch(() => { this.queries.forEach(query => { query.onOnline(); }); }); } } // CLASS class Mutation extends Removable { constructor(config) { super(); this.options = { ...config.defaultOptions, ...config.options }; this.mutationId = config.mutationId; this.mutationCache = config.mutationCache; this.logger = config.logger || defaultLogger; this.observers = []; this.state = config.state || getDefaultState(); this.meta = config.meta; this.updateCacheTime(this.options.cacheTime); this.scheduleGc(); } setState(state) { this.dispatch({ type: 'setState', state }); } addObserver(observer) { if (this.observers.indexOf(observer) === -1) { this.observers.push(observer); // Stop the mutation from being garbage collected this.clearGcTimeout(); this.mutationCache.notify({ type: 'observerAdded', mutation: this, observer }); } } removeObserver(observer) { this.observers = this.observers.filter(x => x !== observer); this.scheduleGc(); this.mutationCache.notify({ type: 'observerRemoved', mutation: this, observer }); } optionalRemove() { if (!this.observers.length) { if (this.state.status === 'loading') { this.scheduleGc(); } else { this.mutationCache.remove(this); } } } continue() { if (this.retryer) { this.retryer.continue(); return this.retryer.promise; } return this.execute(); } async execute() { const executeMutation = () => { var _this$options$retry; this.retryer = createRetryer({ fn: () => { if (!this.options.mutationFn) { return Promise.reject('No mutationFn found'); } return this.options.mutationFn(this.state.variables); }, onFail: () => { this.dispatch({ type: 'failed' }); }, onPause: () => { this.dispatch({ type: 'pause' }); }, onContinue: () => { this.dispatch({ type: 'continue' }); }, retry: (_this$options$retry = this.options.retry) != null ? _this$options$retry : 0, retryDelay: this.options.retryDelay, networkMode: this.options.networkMode }); return this.retryer.promise; }; const restored = this.state.status === 'loading'; try { var _this$mutationCache$c3, _this$mutationCache$c4, _this$options$onSucce, _this$options2, _this$options$onSettl, _this$options3; if (!restored) { var _this$mutationCache$c, _this$mutationCache$c2, _this$options$onMutat, _this$options; this.dispatch({ type: 'loading', variables: this.options.variables }); // Notify cache callback (_this$mutationCache$c = (_this$mutationCache$c2 = this.mutationCache.config).onMutate) == null ? void 0 : _this$mutationCache$c.call(_this$mutationCache$c2, this.state.variables, this); const context = await ((_this$options$onMutat = (_this$options = this.options).onMutate) == null ? void 0 : _this$options$onMutat.call(_this$options, this.state.variables)); if (context !== this.state.context) { this.dispatch({ type: 'loading', context, variables: this.state.variables }); } } const data = await executeMutation(); // Notify cache callback (_this$mutationCache$c3 = (_this$mutationCache$c4 = this.mutationCache.config).onSuccess) == null ? void 0 : _this$mutationCache$c3.call(_this$mutationCache$c4, data, this.state.variables, this.state.context, this); await ((_this$options$onSucce = (_this$options2 = this.options).onSuccess) == null ? void 0 : _this$options$onSucce.call(_this$options2, data, this.state.variables, this.state.context)); await ((_this$options$onSettl = (_this$options3 = this.options).onSettled) == null ? void 0 : _this$options$onSettl.call(_this$options3, data, null, this.state.variables, this.state.context)); this.dispatch({ type: 'success', data }); return data; } catch (error) { try { var _this$mutationCache$c5, _this$mutationCache$c6, _this$options$onError, _this$options4, _this$options$onSettl2, _this$options5; // Notify cache callback (_this$mutationCache$c5 = (_this$mutationCache$c6 = this.mutationCache.config).onError) == null ? void 0 : _this$mutationCache$c5.call(_this$mutationCache$c6, error, this.state.variables, this.state.context, this); if (process.env.NODE_ENV !== 'production') { this.logger.error(error); } await ((_this$options$onError = (_this$options4 = this.options).onError) == null ? void 0 : _this$options$onError.call(_this$options4, error, this.state.variables, this.state.context)); await ((_this$options$onSettl2 = (_this$options5 = this.options).onSettled) == null ? void 0 : _this$options$onSettl2.call(_this$options5, undefined, error, this.state.variables, this.state.context)); throw error; } finally { this.dispatch({ type: 'error', error: error }); } } } dispatch(action) { const reducer = state => { switch (action.type) { case 'failed': return { ...state, failureCount: state.failureCount + 1 }; case 'pause': return { ...state, isPaused: true }; case 'continue': return { ...state, isPaused: false }; case 'loading': return { ...state, context: action.context, data: undefined, error: null, isPaused: !canFetch(this.options.networkMode), status: 'loading', variables: action.variables }; case 'success': return { ...state, data: action.data, error: null, status: 'success', isPaused: false }; case 'error': return { ...state, data: undefined, error: action.error, failureCount: state.failureCount + 1, isPaused: false, status: 'error' }; case 'setState': return { ...state, ...action.state }; } }; this.state = reducer(this.state); notifyManager.batch(() => { this.observers.forEach(observer => { observer.onMutationUpdate(action); }); this.mutationCache.notify({ mutation: this, type: 'updated', action }); }); } } function getDefaultState() { return { context: undefined, data: undefined, error: null, failureCount: 0, isPaused: false, status: 'idle', variables: undefined }; } // CLASS class MutationCache extends Subscribable { constructor(config) { super(); this.config = config || {}; this.mutations = []; this.mutationId = 0; } build(client, options, state) { const mutation = new Mutation({ mutationCache: this, logger: client.getLogger(), mutationId: ++this.mutationId, options: client.defaultMutationOptions(options), state, defaultOptions: options.mutationKey ? client.getMutationDefaults(options.mutationKey) : undefined, meta: options.meta }); this.add(mutation); return mutation; } add(mutation) { this.mutations.push(mutation); this.notify({ type: 'added', mutation }); } remove(mutation) { this.mutations = this.mutations.filter(x => x !== mutation); this.notify({ type: 'removed', mutation }); } clear() { notifyManager.batch(() => { this.mutations.forEach(mutation => { this.remove(mutation); }); }); } getAll() { return this.mutations; } find(filters) { if (typeof filters.exact === 'undefined') { filters.exact = true; } return this.mutations.find(mutation => matchMutation(filters, mutation)); } findAll(filters) { return this.mutations.filter(mutation => matchMutation(filters, mutation)); } notify(event) { notifyManager.batch(() => { this.listeners.forEach(listener => { listener(event); }); }); } resumePausedMutations() { const pausedMutations = this.mutations.filter(x => x.state.isPaused); return notifyManager.batch(() => pausedMutations.reduce((promise, mutation) => promise.then(() => mutation.continue().catch(noop)), Promise.resolve())); } } function infiniteQueryBehavior() { return { onFetch: context => { context.fetchFn = () => { var _context$fetchOptions, _context$fetchOptions2, _context$fetchOptions3, _context$fetchOptions4, _context$state$data, _context$state$data2, _context$signal; const refetchPage = (_context$fetchOptions = context.fetchOptions) == null ? void 0 : (_context$fetchOptions2 = _context$fetchOptions.meta) == null ? void 0 : _context$fetchOptions2.refetchPage; const fetchMore = (_context$fetchOptions3 = context.fetchOptions) == null ? void 0 : (_context$fetchOptions4 = _context$fetchOptions3.meta) == null ? void 0 : _context$fetchOptions4.fetchMore; const pageParam = fetchMore == null ? void 0 : fetchMore.pageParam; const isFetchingNextPage = (fetchMore == null ? void 0 : fetchMore.direction) === 'forward'; const isFetchingPreviousPage = (fetchMore == null ? void 0 : fetchMore.direction) === 'backward'; const oldPages = ((_context$state$data = context.state.data) == null ? void 0 : _context$state$data.pages) || []; const oldPageParams = ((_context$state$data2 = context.state.data) == null ? void 0 : _context$state$data2.pageParams) || []; const abortController = getAbortController(); const abortSignal = abortController == null ? void 0 : abortController.signal; let newPageParams = oldPageParams; let cancelled = false; // Get query function const queryFn = context.options.queryFn || (() => Promise.reject('Missing queryFn')); const buildNewPages = (pages, param, page, previous) => { newPageParams = previous ? [param, ...newPageParams] : [...newPageParams, param]; return previous ? [page, ...pages] : [...pages, page]; }; // Create function to fetch a page const fetchPage = (pages, manual, param, previous) => { if (cancelled) { return Promise.reject('Cancelled'); } if (typeof param === 'undefined' && !manual && pages.length) { return Promise.resolve(pages); } const queryFnContext = { queryKey: context.queryKey, signal: abortSignal, pageParam: param, meta: context.meta }; const queryFnResult = queryFn(queryFnContext); const promise = Promise.resolve(queryFnResult).then(page => buildNewPages(pages, param, page, previous)); return promise; }; let promise; // Fetch first page? if (!oldPages.length) { promise = fetchPage([]); } // Fetch next page? else if (isFetchingNextPage) { const manual = typeof pageParam !== 'undefined'; const param = manual ? pageParam : getNextPageParam(context.options, oldPages); promise = fetchPage(oldPages, manual, param); } // Fetch previous page? else if (isFetchingPreviousPage) { const manual = typeof pageParam !== 'undefined'; const param = manual ? pageParam : getPreviousPageParam(context.options, oldPages); promise = fetchPage(oldPages, manual, param, true); } // Refetch pages else { newPageParams = []; const manual = typeof context.options.getNextPageParam === 'undefined'; const shouldFetchFirstPage = refetchPage && oldPages[0] ? refetchPage(oldPages[0], 0, oldPages) : true; // Fetch first page promise = shouldFetchFirstPage ? fetchPage([], manual, oldPageParams[0]) : Promise.resolve(buildNewPages([], oldPageParams[0], oldPages[0])); // Fetch remaining pages for (let i = 1; i < oldPages.length; i++) { promise = promise.then(pages => { const shouldFetchNextPage = refetchPage && oldPages[i] ? refetchPage(oldPages[i], i, oldPages) : true; if (shouldFetchNextPage) { const param = manual ? oldPageParams[i] : getNextPageParam(context.options, pages); return fetchPage(pages, manual, param); } return Promise.resolve(buildNewPages(pages, oldPageParams[i], oldPages[i])); }); } } const finalPromise = promise.then(pages => ({ pages, pageParams: newPageParams })); (_context$signal = context.signal) == null ? void 0 : _context$signal.addEventListener('abort', () => { cancelled = true; abortController == null ? void 0 : abortController.abort(); }); return finalPromise; }; } }; } function getNextPageParam(options, pages) { return options.getNextPageParam == null ? void 0 : options.getNextPageParam(pages[pages.length - 1], pages); } function getPreviousPageParam(options, pages) { return options.getPreviousPageParam == null ? void 0 : options.getPreviousPageParam(pages[0], pages); } /** * Checks if there is a next page. * Returns `undefined` if it cannot be determined. */ function hasNextPage(options, pages) { if (options.getNextPageParam && Array.isArray(pages)) { const nextPageParam = getNextPageParam(options, pages); return typeof nextPageParam !== 'undefined' && nextPageParam !== null && nextPageParam !== false; } } /** * Checks if there is a previous page. * Returns `undefined` if it cannot be determined. */ function hasPreviousPage(options, pages) { if (options.getPreviousPageParam && Array.isArray(pages)) { const previousPageParam = getPreviousPageParam(options, pages); return typeof previousPageParam !== 'undefined' && previousPageParam !== null && previousPageParam !== false; } } // CLASS class QueryClient { constructor(config = {}) { this.queryCache = config.queryCache || new QueryCache(); this.mutationCache = config.mutationCache || new MutationCache(); this.logger = config.logger || defaultLogger; this.defaultOptions = config.defaultOptions || {}; this.queryDefaults = []; this.mutationDefaults = []; } mount() { this.unsubscribeFocus = focusManager.subscribe(() => { if (focusManager.isFocused()) {