react-query
Version:
Hooks for managing, caching and syncing asynchronous and remote data in React
1,776 lines (1,471 loc) • 92.7 kB
JavaScript
(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()) {