@gaddario98/react-queries
Version:
```bash npm install @gaddario98/base-queries ```
1,620 lines • 153 kB
JavaScript
'use strict';var jotai=require('jotai'),utils=require('jotai/utils'),reactState=require('@gaddario98/react-state'),axios=require('axios'),React=require('react'),jsxRuntime=require('react/jsx-runtime'),compilerRuntime=require('react/compiler-runtime'),equal=require('fast-deep-equal');function _interopNamespaceDefault(e){var n=Object.create(null);if(e){Object.keys(e).forEach(function(k){if(k!=='default'){var d=Object.getOwnPropertyDescriptor(e,k);Object.defineProperty(n,k,d.get?d:{enumerable:true,get:function(){return e[k]}});}})}n.default=e;return Object.freeze(n)}var React__namespace=/*#__PURE__*/_interopNamespaceDefault(React);// ============================================================================
// Default Values
// ============================================================================
const DEFAULT_QUERY_ENTRY = Object.freeze({
data: undefined,
isLoading: false,
isLoadingMapped: false,
isFetching: false,
isPending: false,
isSuccess: false,
isError: false,
isStale: false,
error: null,
dataUpdatedAt: 0,
errorUpdatedAt: 0,
fetchStatus: 'idle',
refetch: () => Promise.resolve()
});
const DEFAULT_MUTATION_ENTRY = Object.freeze({
data: undefined,
status: 'idle',
error: null,
variables: undefined,
submittedAt: 0,
isIdle: true,
isPending: false,
isSuccess: false,
isError: false,
mutate: () => {},
mutateAsync: async () => Promise.resolve(undefined),
reset: () => {},
context: 0,
failureCount: 0,
failureReason: null,
isPaused: false
});
// ============================================================================
// Global Atoms (single atom for all queries, single atom for all mutations)
// ============================================================================
/**
* Global atom storing all query results.
* Key format: "scopeId:queryKey"
*/
const queriesAtom = utils.atomWithStorage('queries-atom', {}, utils.createJSONStorage(() => reactState.storage), {
getOnInit: true
});
/**
* Global atom storing all mutation results.
* Key format: "scopeId:mutationKey"
*/
const mutationsAtom = jotai.atom({});
// ============================================================================
// Helper to generate composite keys
// ============================================================================
const getCompositeKey = (scopeId, key) => `${scopeId}:${key}`;
// ============================================================================
// Derived Atoms for specific scope access
// ============================================================================
/**
* Creates a derived atom for accessing queries of a specific scope.
*/
const createScopeQueriesAtom = scopeId => jotai.atom(get => {
const allQueries = get(queriesAtom);
const prefix = `${scopeId}:`;
const scopeQueries = {};
for (const [key, value] of Object.entries(allQueries)) {
if (key.startsWith(prefix)) {
scopeQueries[key.slice(prefix.length)] = value;
}
}
return scopeQueries;
}, (get, set, update) => {
const allQueries = get(queriesAtom);
const prefix = `${scopeId}:`;
const newQueries = Object.assign({}, allQueries);
// Remove old scope entries
for (const key of Object.keys(newQueries)) {
if (key.startsWith(prefix)) {
delete newQueries[key];
}
}
// Add new scope entries
for (const [key, value] of Object.entries(update)) {
newQueries[`${prefix}${key}`] = value;
}
set(queriesAtom, newQueries);
});
/**
* Creates a derived atom for accessing mutations of a specific scope.
*/
const createScopeMutationsAtom = scopeId => jotai.atom(get => {
const allMutations = get(mutationsAtom);
const prefix = `${scopeId}:`;
const scopeMutations = {};
for (const [key, value] of Object.entries(allMutations)) {
if (key.startsWith(prefix)) {
scopeMutations[key.slice(prefix.length)] = value;
}
}
return scopeMutations;
}, (get, set, update) => {
const allMutations = get(mutationsAtom);
const prefix = `${scopeId}:`;
const newMutations = Object.assign({}, allMutations);
// Remove old scope entries
for (const key of Object.keys(newMutations)) {
if (key.startsWith(prefix)) {
delete newMutations[key];
}
}
// Add new scope entries
for (const [key, value] of Object.entries(update)) {
newMutations[`${prefix}${key}`] = value;
}
set(mutationsAtom, newMutations);
});
// ============================================================================
// Selectors for single query/mutation access
// ============================================================================
const createQuerySelector = (scopeId, queryKey) => {
const compositeKey = getCompositeKey(scopeId, queryKey);
return utils.selectAtom(queriesAtom, queries => {
const entry = queries[compositeKey];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
return entry !== null && entry !== void 0 ? entry : DEFAULT_QUERY_ENTRY;
}, (a, b) => a === b || a.data === b.data && a.isLoading === b.isLoading && a.isFetching === b.isFetching && a.error === b.error);
};
const createMutationSelector = (scopeId, mutationKey) => {
const compositeKey = getCompositeKey(scopeId, mutationKey);
return utils.selectAtom(mutationsAtom, mutations => {
const entry = mutations[compositeKey];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
return entry !== null && entry !== void 0 ? entry : DEFAULT_MUTATION_ENTRY;
}, (a, b) => a === b || a.data === b.data && a.isPending === b.isPending && a.error === b.error);
};// src/subscribable.ts
var Subscribable = class {
constructor() {
this.listeners = /* @__PURE__ */new Set();
this.subscribe = this.subscribe.bind(this);
}
subscribe(listener) {
this.listeners.add(listener);
this.onSubscribe();
return () => {
this.listeners.delete(listener);
this.onUnsubscribe();
};
}
hasListeners() {
return this.listeners.size > 0;
}
onSubscribe() {}
onUnsubscribe() {}
};// src/timeoutManager.ts
var defaultTimeoutProvider = {
// We need the wrapper function syntax below instead of direct references to
// global setTimeout etc.
//
// BAD: `setTimeout: setTimeout`
// GOOD: `setTimeout: (cb, delay) => setTimeout(cb, delay)`
//
// If we use direct references here, then anything that wants to spy on or
// replace the global setTimeout (like tests) won't work since we'll already
// have a hard reference to the original implementation at the time when this
// file was imported.
setTimeout: (callback, delay) => setTimeout(callback, delay),
clearTimeout: timeoutId => clearTimeout(timeoutId),
setInterval: (callback, delay) => setInterval(callback, delay),
clearInterval: intervalId => clearInterval(intervalId)
};
var TimeoutManager = class {
// We cannot have TimeoutManager<T> as we must instantiate it with a concrete
// type at app boot; and if we leave that type, then any new timer provider
// would need to support ReturnType<typeof setTimeout>, which is infeasible.
//
// We settle for type safety for the TimeoutProvider type, and accept that
// this class is unsafe internally to allow for extension.
#provider = defaultTimeoutProvider;
#providerCalled = false;
setTimeoutProvider(provider) {
if (process.env.NODE_ENV !== "production") {
if (this.#providerCalled && provider !== this.#provider) {
console.error(`[timeoutManager]: Switching provider after calls to previous provider might result in unexpected behavior.`, {
previous: this.#provider,
provider
});
}
}
this.#provider = provider;
if (process.env.NODE_ENV !== "production") {
this.#providerCalled = false;
}
}
setTimeout(callback, delay) {
if (process.env.NODE_ENV !== "production") {
this.#providerCalled = true;
}
return this.#provider.setTimeout(callback, delay);
}
clearTimeout(timeoutId) {
this.#provider.clearTimeout(timeoutId);
}
setInterval(callback, delay) {
if (process.env.NODE_ENV !== "production") {
this.#providerCalled = true;
}
return this.#provider.setInterval(callback, delay);
}
clearInterval(intervalId) {
this.#provider.clearInterval(intervalId);
}
};
var timeoutManager = new TimeoutManager();
function systemSetTimeoutZero(callback) {
setTimeout(callback, 0);
}// src/utils.ts
var isServer = typeof window === "undefined" || "Deno" in globalThis;
function noop() {}
function functionalUpdate(updater, input) {
return typeof updater === "function" ? updater(input) : updater;
}
function isValidTimeout(value) {
return typeof value === "number" && value >= 0 && value !== Infinity;
}
function timeUntilStale(updatedAt, staleTime) {
return Math.max(updatedAt + (staleTime || 0) - Date.now(), 0);
}
function resolveStaleTime(staleTime, query) {
return typeof staleTime === "function" ? staleTime(query) : staleTime;
}
function resolveEnabled(enabled, query) {
return typeof enabled === "function" ? enabled(query) : enabled;
}
function matchQuery(filters, query) {
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 (fetchStatus && fetchStatus !== query.state.fetchStatus) {
return false;
}
if (predicate && !predicate(query)) {
return false;
}
return true;
}
function matchMutation(filters, mutation) {
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;
}
function hashQueryKeyByOptions(queryKey, options) {
const hashFn = options?.queryKeyHashFn || hashKey;
return hashFn(queryKey);
}
function hashKey(queryKey) {
return JSON.stringify(queryKey, (_, val) => isPlainObject(val) ? Object.keys(val).sort().reduce((result, key) => {
result[key] = val[key];
return result;
}, {}) : val);
}
function partialMatchKey(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).every(key => partialMatchKey(a[key], b[key]));
}
return false;
}
var hasOwn = Object.prototype.hasOwnProperty;
function replaceEqualDeep(a, b) {
if (a === b) {
return a;
}
const array = isPlainArray(a) && isPlainArray(b);
if (!array && !(isPlainObject(a) && isPlainObject(b))) return b;
const aItems = array ? a : Object.keys(a);
const aSize = aItems.length;
const bItems = array ? b : Object.keys(b);
const bSize = bItems.length;
const copy = array ? new Array(bSize) : {};
let equalItems = 0;
for (let i = 0; i < bSize; i++) {
const key = array ? i : bItems[i];
const aItem = a[key];
const bItem = b[key];
if (aItem === bItem) {
copy[key] = aItem;
if (array ? i < aSize : hasOwn.call(a, key)) equalItems++;
continue;
}
if (aItem === null || bItem === null || typeof aItem !== "object" || typeof bItem !== "object") {
copy[key] = bItem;
continue;
}
const v = replaceEqualDeep(aItem, bItem);
copy[key] = v;
if (v === aItem) equalItems++;
}
return aSize === bSize && equalItems === aSize ? a : copy;
}
function shallowEqualObjects(a, b) {
if (!b || Object.keys(a).length !== Object.keys(b).length) {
return false;
}
for (const key in a) {
if (a[key] !== b[key]) {
return false;
}
}
return true;
}
function isPlainArray(value) {
return Array.isArray(value) && value.length === Object.keys(value).length;
}
function isPlainObject(o) {
if (!hasObjectPrototype(o)) {
return false;
}
const ctor = o.constructor;
if (ctor === void 0) {
return true;
}
const prot = ctor.prototype;
if (!hasObjectPrototype(prot)) {
return false;
}
if (!prot.hasOwnProperty("isPrototypeOf")) {
return false;
}
if (Object.getPrototypeOf(o) !== Object.prototype) {
return false;
}
return true;
}
function hasObjectPrototype(o) {
return Object.prototype.toString.call(o) === "[object Object]";
}
function sleep(timeout) {
return new Promise(resolve => {
timeoutManager.setTimeout(resolve, timeout);
});
}
function replaceData(prevData, data, options) {
if (typeof options.structuralSharing === "function") {
return options.structuralSharing(prevData, data);
} else if (options.structuralSharing !== false) {
if (process.env.NODE_ENV !== "production") {
try {
return replaceEqualDeep(prevData, data);
} catch (error) {
console.error(`Structural sharing requires data to be JSON serializable. To fix this, turn off structuralSharing or return JSON-serializable data from your queryFn. [${options.queryHash}]: ${error}`);
throw error;
}
}
return replaceEqualDeep(prevData, data);
}
return data;
}
function addToEnd(items, item, max = 0) {
const newItems = [...items, item];
return max && newItems.length > max ? newItems.slice(1) : newItems;
}
function addToStart(items, item, max = 0) {
const newItems = [item, ...items];
return max && newItems.length > max ? newItems.slice(0, -1) : newItems;
}
var skipToken = Symbol();
function ensureQueryFn(options, fetchOptions) {
if (process.env.NODE_ENV !== "production") {
if (options.queryFn === skipToken) {
console.error(`Attempted to invoke queryFn when set to skipToken. This is likely a configuration error. Query hash: '${options.queryHash}'`);
}
}
if (!options.queryFn && fetchOptions?.initialPromise) {
return () => fetchOptions.initialPromise;
}
if (!options.queryFn || options.queryFn === skipToken) {
return () => Promise.reject(new Error(`Missing queryFn: '${options.queryHash}'`));
}
return options.queryFn;
}
function shouldThrowError(throwOnError, params) {
if (typeof throwOnError === "function") {
return throwOnError(...params);
}
return !!throwOnError;
}// src/focusManager.ts
var FocusManager = class extends Subscribable {
#focused;
#cleanup;
#setup;
constructor() {
super();
this.#setup = onFocus => {
if (!isServer && window.addEventListener) {
const listener = () => onFocus();
window.addEventListener("visibilitychange", listener, false);
return () => {
window.removeEventListener("visibilitychange", listener);
};
}
return;
};
}
onSubscribe() {
if (!this.#cleanup) {
this.setEventListener(this.#setup);
}
}
onUnsubscribe() {
if (!this.hasListeners()) {
this.#cleanup?.();
this.#cleanup = void 0;
}
}
setEventListener(setup) {
this.#setup = setup;
this.#cleanup?.();
this.#cleanup = setup(focused => {
if (typeof focused === "boolean") {
this.setFocused(focused);
} else {
this.onFocus();
}
});
}
setFocused(focused) {
const changed = this.#focused !== focused;
if (changed) {
this.#focused = focused;
this.onFocus();
}
}
onFocus() {
const isFocused = this.isFocused();
this.listeners.forEach(listener => {
listener(isFocused);
});
}
isFocused() {
if (typeof this.#focused === "boolean") {
return this.#focused;
}
return globalThis.document?.visibilityState !== "hidden";
}
};
var focusManager = new FocusManager();// src/thenable.ts
function pendingThenable() {
let resolve;
let reject;
const thenable = new Promise((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
thenable.status = "pending";
thenable.catch(() => {});
function finalize(data) {
Object.assign(thenable, data);
delete thenable.resolve;
delete thenable.reject;
}
thenable.resolve = value => {
finalize({
status: "fulfilled",
value
});
resolve(value);
};
thenable.reject = reason => {
finalize({
status: "rejected",
reason
});
reject(reason);
};
return thenable;
}
function tryResolveSync(promise) {
let data;
promise.then(result => {
data = result;
return result;
}, noop)?.catch(noop);
if (data !== void 0) {
return {
data
};
}
return void 0;
}// src/hydration.ts
function defaultTransformerFn(data) {
return data;
}
function dehydrateMutation(mutation) {
return {
mutationKey: mutation.options.mutationKey,
state: mutation.state,
...(mutation.options.scope && {
scope: mutation.options.scope
}),
...(mutation.meta && {
meta: mutation.meta
})
};
}
function dehydrateQuery(query, serializeData, shouldRedactErrors) {
const promise = query.promise?.then(serializeData).catch(error => {
if (!shouldRedactErrors(error)) {
return Promise.reject(error);
}
if (process.env.NODE_ENV !== "production") {
console.error(`A query that was dehydrated as pending ended up rejecting. [${query.queryHash}]: ${error}; The error will be redacted in production builds`);
}
return Promise.reject(new Error("redacted"));
});
promise?.catch(noop);
return {
dehydratedAt: Date.now(),
state: {
...query.state,
...(query.state.data !== void 0 && {
data: serializeData(query.state.data)
})
},
queryKey: query.queryKey,
queryHash: query.queryHash,
...(query.state.status === "pending" && {
promise
}),
...(query.meta && {
meta: query.meta
})
};
}
function defaultShouldDehydrateMutation(mutation) {
return mutation.state.isPaused;
}
function defaultShouldDehydrateQuery(query) {
return query.state.status === "success";
}
function defaultShouldRedactErrors(_) {
return true;
}
function dehydrate(client, options = {}) {
const filterMutation = options.shouldDehydrateMutation ?? client.getDefaultOptions().dehydrate?.shouldDehydrateMutation ?? defaultShouldDehydrateMutation;
const mutations = client.getMutationCache().getAll().flatMap(mutation => filterMutation(mutation) ? [dehydrateMutation(mutation)] : []);
const filterQuery = options.shouldDehydrateQuery ?? client.getDefaultOptions().dehydrate?.shouldDehydrateQuery ?? defaultShouldDehydrateQuery;
const shouldRedactErrors = options.shouldRedactErrors ?? client.getDefaultOptions().dehydrate?.shouldRedactErrors ?? defaultShouldRedactErrors;
const serializeData = options.serializeData ?? client.getDefaultOptions().dehydrate?.serializeData ?? defaultTransformerFn;
const queries = client.getQueryCache().getAll().flatMap(query => filterQuery(query) ? [dehydrateQuery(query, serializeData, shouldRedactErrors)] : []);
return {
mutations,
queries
};
}
function hydrate(client, dehydratedState, options) {
if (typeof dehydratedState !== "object" || dehydratedState === null) {
return;
}
const mutationCache = client.getMutationCache();
const queryCache = client.getQueryCache();
const deserializeData = options?.defaultOptions?.deserializeData ?? client.getDefaultOptions().hydrate?.deserializeData ?? defaultTransformerFn;
const mutations = dehydratedState.mutations || [];
const queries = dehydratedState.queries || [];
mutations.forEach(({
state,
...mutationOptions
}) => {
mutationCache.build(client, {
...client.getDefaultOptions().hydrate?.mutations,
...options?.defaultOptions?.mutations,
...mutationOptions
}, state);
});
queries.forEach(({
queryKey,
state,
queryHash,
meta,
promise,
dehydratedAt
}) => {
const syncData = promise ? tryResolveSync(promise) : void 0;
const rawData = state.data === void 0 ? syncData?.data : state.data;
const data = rawData === void 0 ? rawData : deserializeData(rawData);
let query = queryCache.get(queryHash);
const existingQueryIsPending = query?.state.status === "pending";
const existingQueryIsFetching = query?.state.fetchStatus === "fetching";
if (query) {
const hasNewerSyncData = syncData &&
// We only need this undefined check to handle older dehydration
// payloads that might not have dehydratedAt
dehydratedAt !== void 0 && dehydratedAt > query.state.dataUpdatedAt;
if (state.dataUpdatedAt > query.state.dataUpdatedAt || hasNewerSyncData) {
const {
fetchStatus: _ignored,
...serializedState
} = state;
query.setState({
...serializedState,
data
});
}
} else {
query = queryCache.build(client, {
...client.getDefaultOptions().hydrate?.queries,
...options?.defaultOptions?.queries,
queryKey,
queryHash,
meta
},
// Reset fetch status to idle to avoid
// query being stuck in fetching state upon hydration
{
...state,
data,
fetchStatus: "idle",
status: data !== void 0 ? "success" : state.status
});
}
if (promise && !existingQueryIsPending && !existingQueryIsFetching && (
// Only hydrate if dehydration is newer than any existing data,
// this is always true for new queries
dehydratedAt === void 0 || dehydratedAt > query.state.dataUpdatedAt)) {
query.fetch(void 0, {
// RSC transformed promises are not thenable
initialPromise: Promise.resolve(promise).then(deserializeData)
}).catch(noop);
}
});
}// src/notifyManager.ts
var defaultScheduler = systemSetTimeoutZero;
function createNotifyManager() {
let queue = [];
let transactions = 0;
let notifyFn = callback => {
callback();
};
let batchNotifyFn = callback => {
callback();
};
let scheduleFn = defaultScheduler;
const schedule = callback => {
if (transactions) {
queue.push(callback);
} else {
scheduleFn(() => {
notifyFn(callback);
});
}
};
const flush = () => {
const originalQueue = queue;
queue = [];
if (originalQueue.length) {
scheduleFn(() => {
batchNotifyFn(() => {
originalQueue.forEach(callback => {
notifyFn(callback);
});
});
});
}
};
return {
batch: callback => {
let result;
transactions++;
try {
result = callback();
} finally {
transactions--;
if (!transactions) {
flush();
}
}
return result;
},
/**
* All calls to the wrapped function will be batched.
*/
batchCalls: callback => {
return (...args) => {
schedule(() => {
callback(...args);
});
};
},
schedule,
/**
* Use this method to set a custom notify function.
* This can be used to for example wrap notifications with `React.act` while running tests.
*/
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.
*/
setBatchNotifyFunction: fn => {
batchNotifyFn = fn;
},
setScheduler: fn => {
scheduleFn = fn;
}
};
}
var notifyManager = createNotifyManager();// src/onlineManager.ts
var OnlineManager = class extends Subscribable {
#online = true;
#cleanup;
#setup;
constructor() {
super();
this.#setup = onOnline => {
if (!isServer && window.addEventListener) {
const onlineListener = () => onOnline(true);
const offlineListener = () => onOnline(false);
window.addEventListener("online", onlineListener, false);
window.addEventListener("offline", offlineListener, false);
return () => {
window.removeEventListener("online", onlineListener);
window.removeEventListener("offline", offlineListener);
};
}
return;
};
}
onSubscribe() {
if (!this.#cleanup) {
this.setEventListener(this.#setup);
}
}
onUnsubscribe() {
if (!this.hasListeners()) {
this.#cleanup?.();
this.#cleanup = void 0;
}
}
setEventListener(setup) {
this.#setup = setup;
this.#cleanup?.();
this.#cleanup = setup(this.setOnline.bind(this));
}
setOnline(online) {
const changed = this.#online !== online;
if (changed) {
this.#online = online;
this.listeners.forEach(listener => {
listener(online);
});
}
}
isOnline() {
return this.#online;
}
};
var onlineManager = new OnlineManager();// src/retryer.ts
function defaultRetryDelay(failureCount) {
return Math.min(1e3 * 2 ** failureCount, 3e4);
}
function canFetch(networkMode) {
return (networkMode ?? "online") === "online" ? onlineManager.isOnline() : true;
}
var CancelledError = class extends Error {
constructor(options) {
super("CancelledError");
this.revert = options?.revert;
this.silent = options?.silent;
}
};
function createRetryer(config) {
let isRetryCancelled = false;
let failureCount = 0;
let continueFn;
const thenable = pendingThenable();
const isResolved = () => thenable.status !== "pending";
const cancel = cancelOptions => {
if (!isResolved()) {
const error = new CancelledError(cancelOptions);
reject(error);
config.onCancel?.(error);
}
};
const cancelRetry = () => {
isRetryCancelled = true;
};
const continueRetry = () => {
isRetryCancelled = false;
};
const canContinue = () => focusManager.isFocused() && (config.networkMode === "always" || onlineManager.isOnline()) && config.canRun();
const canStart = () => canFetch(config.networkMode) && config.canRun();
const resolve = value => {
if (!isResolved()) {
continueFn?.();
thenable.resolve(value);
}
};
const reject = value => {
if (!isResolved()) {
continueFn?.();
thenable.reject(value);
}
};
const pause = () => {
return new Promise(continueResolve => {
continueFn = value => {
if (isResolved() || canContinue()) {
continueResolve(value);
}
};
config.onPause?.();
}).then(() => {
continueFn = void 0;
if (!isResolved()) {
config.onContinue?.();
}
});
};
const run = () => {
if (isResolved()) {
return;
}
let promiseOrValue;
const initialPromise = failureCount === 0 ? config.initialPromise : void 0;
try {
promiseOrValue = initialPromise ?? config.fn();
} catch (error) {
promiseOrValue = Promise.reject(error);
}
Promise.resolve(promiseOrValue).then(resolve).catch(error => {
if (isResolved()) {
return;
}
const retry = config.retry ?? (isServer ? 0 : 3);
const retryDelay = 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) {
reject(error);
return;
}
failureCount++;
config.onFail?.(failureCount, error);
sleep(delay).then(() => {
return canContinue() ? void 0 : pause();
}).then(() => {
if (isRetryCancelled) {
reject(error);
} else {
run();
}
});
});
};
return {
promise: thenable,
status: () => thenable.status,
cancel,
continue: () => {
continueFn?.();
return thenable;
},
cancelRetry,
continueRetry,
canStart,
start: () => {
if (canStart()) {
run();
} else {
pause().then(run);
}
return thenable;
}
};
}// src/removable.ts
var Removable = class {
#gcTimeout;
destroy() {
this.clearGcTimeout();
}
scheduleGc() {
this.clearGcTimeout();
if (isValidTimeout(this.gcTime)) {
this.#gcTimeout = timeoutManager.setTimeout(() => {
this.optionalRemove();
}, this.gcTime);
}
}
updateGcTime(newGcTime) {
this.gcTime = Math.max(this.gcTime || 0, newGcTime ?? (isServer ? Infinity : 5 * 60 * 1e3));
}
clearGcTimeout() {
if (this.#gcTimeout) {
timeoutManager.clearTimeout(this.#gcTimeout);
this.#gcTimeout = void 0;
}
}
};// src/query.ts
var Query = class extends Removable {
#initialState;
#revertState;
#cache;
#client;
#retryer;
#defaultOptions;
#abortSignalConsumed;
constructor(config) {
super();
this.#abortSignalConsumed = false;
this.#defaultOptions = config.defaultOptions;
this.setOptions(config.options);
this.observers = [];
this.#client = config.client;
this.#cache = this.#client.getQueryCache();
this.queryKey = config.queryKey;
this.queryHash = config.queryHash;
this.#initialState = getDefaultState$1(this.options);
this.state = config.state ?? this.#initialState;
this.scheduleGc();
}
get meta() {
return this.options.meta;
}
get promise() {
return this.#retryer?.promise;
}
setOptions(options) {
this.options = {
...this.#defaultOptions,
...options
};
this.updateGcTime(this.options.gcTime);
if (this.state && this.state.data === void 0) {
const defaultState = getDefaultState$1(this.options);
if (defaultState.data !== void 0) {
this.setState(successState(defaultState.data, defaultState.dataUpdatedAt));
this.#initialState = defaultState;
}
}
}
optionalRemove() {
if (!this.observers.length && this.state.fetchStatus === "idle") {
this.#cache.remove(this);
}
}
setData(newData, options) {
const data = replaceData(this.state.data, newData, this.options);
this.#dispatch({
data,
type: "success",
dataUpdatedAt: options?.updatedAt,
manual: options?.manual
});
return data;
}
setState(state, setStateOptions) {
this.#dispatch({
type: "setState",
state,
setStateOptions
});
}
cancel(options) {
const promise = this.#retryer?.promise;
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 => resolveEnabled(observer.options.enabled, this) !== false);
}
isDisabled() {
if (this.getObserversCount() > 0) {
return !this.isActive();
}
return this.options.queryFn === skipToken || this.state.dataUpdateCount + this.state.errorUpdateCount === 0;
}
isStatic() {
if (this.getObserversCount() > 0) {
return this.observers.some(observer => resolveStaleTime(observer.options.staleTime, this) === "static");
}
return false;
}
isStale() {
if (this.getObserversCount() > 0) {
return this.observers.some(observer => observer.getCurrentResult().isStale);
}
return this.state.data === void 0 || this.state.isInvalidated;
}
isStaleByTime(staleTime = 0) {
if (this.state.data === void 0) {
return true;
}
if (staleTime === "static") {
return false;
}
if (this.state.isInvalidated) {
return true;
}
return !timeUntilStale(this.state.dataUpdatedAt, staleTime);
}
onFocus() {
const observer = this.observers.find(x => x.shouldFetchOnWindowFocus());
observer?.refetch({
cancelRefetch: false
});
this.#retryer?.continue();
}
onOnline() {
const observer = this.observers.find(x => x.shouldFetchOnReconnect());
observer?.refetch({
cancelRefetch: false
});
this.#retryer?.continue();
}
addObserver(observer) {
if (!this.observers.includes(observer)) {
this.observers.push(observer);
this.clearGcTimeout();
this.#cache.notify({
type: "observerAdded",
query: this,
observer
});
}
}
removeObserver(observer) {
if (this.observers.includes(observer)) {
this.observers = this.observers.filter(x => x !== observer);
if (!this.observers.length) {
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"
});
}
}
async fetch(options, fetchOptions) {
if (this.state.fetchStatus !== "idle" &&
// If the promise in the retyer is already rejected, we have to definitely
// re-start the fetch; there is a chance that the query is still in a
// pending state when that happens
this.#retryer?.status() !== "rejected") {
if (this.state.data !== void 0 && fetchOptions?.cancelRefetch) {
this.cancel({
silent: true
});
} else if (this.#retryer) {
this.#retryer.continueRetry();
return this.#retryer.promise;
}
}
if (options) {
this.setOptions(options);
}
if (!this.options.queryFn) {
const observer = this.observers.find(x => x.options.queryFn);
if (observer) {
this.setOptions(observer.options);
}
}
if (process.env.NODE_ENV !== "production") {
if (!Array.isArray(this.options.queryKey)) {
console.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 = new AbortController();
const addSignalProperty = object => {
Object.defineProperty(object, "signal", {
enumerable: true,
get: () => {
this.#abortSignalConsumed = true;
return abortController.signal;
}
});
};
const fetchFn = () => {
const queryFn = ensureQueryFn(this.options, fetchOptions);
const createQueryFnContext = () => {
const queryFnContext2 = {
client: this.#client,
queryKey: this.queryKey,
meta: this.meta
};
addSignalProperty(queryFnContext2);
return queryFnContext2;
};
const queryFnContext = createQueryFnContext();
this.#abortSignalConsumed = false;
if (this.options.persister) {
return this.options.persister(queryFn, queryFnContext, this);
}
return queryFn(queryFnContext);
};
const createFetchContext = () => {
const context2 = {
fetchOptions,
options: this.options,
queryKey: this.queryKey,
client: this.#client,
state: this.state,
fetchFn
};
addSignalProperty(context2);
return context2;
};
const context = createFetchContext();
this.options.behavior?.onFetch(context, this);
this.#revertState = this.state;
if (this.state.fetchStatus === "idle" || this.state.fetchMeta !== context.fetchOptions?.meta) {
this.#dispatch({
type: "fetch",
meta: context.fetchOptions?.meta
});
}
this.#retryer = createRetryer({
initialPromise: fetchOptions?.initialPromise,
fn: context.fetchFn,
onCancel: error => {
if (error instanceof CancelledError && error.revert) {
this.setState({
...this.#revertState,
fetchStatus: "idle"
});
}
abortController.abort();
},
onFail: (failureCount, error) => {
this.#dispatch({
type: "failed",
failureCount,
error
});
},
onPause: () => {
this.#dispatch({
type: "pause"
});
},
onContinue: () => {
this.#dispatch({
type: "continue"
});
},
retry: context.options.retry,
retryDelay: context.options.retryDelay,
networkMode: context.options.networkMode,
canRun: () => true
});
try {
const data = await this.#retryer.start();
if (data === void 0) {
if (process.env.NODE_ENV !== "production") {
console.error(`Query data cannot be undefined. Please make sure to return a value other than undefined from your query function. Affected query key: ${this.queryHash}`);
}
throw new Error(`${this.queryHash} data is undefined`);
}
this.setData(data);
this.#cache.config.onSuccess?.(data, this);
this.#cache.config.onSettled?.(data, this.state.error, this);
return data;
} catch (error) {
if (error instanceof CancelledError) {
if (error.silent) {
return this.#retryer.promise;
} else if (error.revert) {
if (this.state.data === void 0) {
throw error;
}
return this.state.data;
}
}
this.#dispatch({
type: "error",
error
});
this.#cache.config.onError?.(error, this);
this.#cache.config.onSettled?.(this.state.data, error, this);
throw error;
} finally {
this.scheduleGc();
}
}
#dispatch(action) {
const reducer = state => {
switch (action.type) {
case "failed":
return {
...state,
fetchFailureCount: action.failureCount,
fetchFailureReason: action.error
};
case "pause":
return {
...state,
fetchStatus: "paused"
};
case "continue":
return {
...state,
fetchStatus: "fetching"
};
case "fetch":
return {
...state,
...fetchState(state.data, this.options),
fetchMeta: action.meta ?? null
};
case "success":
const newState = {
...state,
...successState(action.data, action.dataUpdatedAt),
dataUpdateCount: state.dataUpdateCount + 1,
...(!action.manual && {
fetchStatus: "idle",
fetchFailureCount: 0,
fetchFailureReason: null
})
};
this.#revertState = action.manual ? newState : void 0;
return newState;
case "error":
const error = action.error;
return {
...state,
error,
errorUpdateCount: state.errorUpdateCount + 1,
errorUpdatedAt: Date.now(),
fetchFailureCount: state.fetchFailureCount + 1,
fetchFailureReason: error,
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();
});
this.#cache.notify({
query: this,
type: "updated",
action
});
});
}
};
function fetchState(data, options) {
return {
fetchFailureCount: 0,
fetchFailureReason: null,
fetchStatus: canFetch(options.networkMode) ? "fetching" : "paused",
...(data === void 0 && {
error: null,
status: "pending"
})
};
}
function successState(data, dataUpdatedAt) {
return {
data,
dataUpdatedAt: dataUpdatedAt ?? Date.now(),
error: null,
isInvalidated: false,
status: "success"
};
}
function getDefaultState$1(options) {
const data = typeof options.initialData === "function" ? options.initialData() : options.initialData;
const hasData = data !== void 0;
const initialDataUpdatedAt = hasData ? typeof options.initialDataUpdatedAt === "function" ? options.initialDataUpdatedAt() : options.initialDataUpdatedAt : 0;
return {
data,
dataUpdateCount: 0,
dataUpdatedAt: hasData ? initialDataUpdatedAt ?? Date.now() : 0,
error: null,
errorUpdateCount: 0,
errorUpdatedAt: 0,
fetchFailureCount: 0,
fetchFailureReason: null,
fetchMeta: null,
isInvalidated: false,
status: hasData ? "success" : "pending",
fetchStatus: "idle"
};
}// src/queryObserver.ts
var QueryObserver = class extends Subscribable {
constructor(client, options) {
super();
this.options = options;
this.#client = client;
this.#selectError = null;
this.#currentThenable = pendingThenable();
this.bindMethods();
this.setOptions(options);
}
#client;
#currentQuery = void 0;
#currentQueryInitialState = void 0;
#currentResult = void 0;
#currentResultState;
#currentResultOptions;
#currentThenable;
#selectError;
#selectFn;
#selectResult;
// This property keeps track of the last query with defined data.
// It will be used to pass the previous data and query to the placeholder function between renders.
#lastQueryWithDefinedData;
#staleTimeoutId;
#refetchIntervalId;
#currentRefetchInterval;
#trackedProps = /* @__PURE__ */new Set();
bindMethods() {
this.refetch = this.refetch.bind(this);
}
onSubscribe() {
if (this.listeners.size === 1) {
this.#currentQuery.addObserver(this);
if (shouldFetchOnMount(this.#currentQuery, this.options)) {
this.#executeFetch();
} else {
this.updateResult();
}
this.#updateTimers();
}
}
onUnsubscribe() {
if (!this.hasListeners()) {
this.destroy();
}
}
shouldFetchOnReconnect() {
return shouldFetchOn(this.#currentQuery, this.options, this.options.refetchOnReconnect);
}
shouldFetchOnWindowFocus() {
return shouldFetchOn(this.#currentQuery, this.options, this.options.refetchOnWindowFocus);
}
destroy() {
this.listeners = /* @__PURE__ */new Set();
this.#clearStaleTimeout();
this.#clearRefetchInterval();
this.#currentQuery.removeObserver(this);
}
setOptions(options) {
const prevOptions = this.options;
const prevQuery = this.#currentQuery;
this.options = this.#client.defaultQueryOptions(options);
if (this.options.enabled !== void 0 && typeof this.options.enabled !== "boolean" && typeof this.options.enabled !== "function" && typeof resolveEnabled(this.options.enabled, this.#currentQuery) !== "boolean") {
throw new Error("Expected enabled to be a boolean or a callback that returns a boolean");
}
this.#updateQuery();
this.#currentQuery.setOptions(this.options);
if (prevOptions._defaulted && !shallowEqualObjects(this.options, prevOptions)) {
this.#client.getQueryCache().notify({
type: "observerOptionsUpdated",
query: this.#currentQuery,
observer: this
});
}
const mounted = this.hasListeners();
if (mounted && shouldFetchOptionally(this.#currentQuery, prevQuery, this.options, prevOptions)) {
this.#executeFetch();
}
this.updateResult();
if (mounted && (this.#currentQuery !== prevQuery || resolveEnabled(this.options.enabled, this.#currentQuery) !== resolveEnabled(prevOptions.enabled, this.#currentQuery) || resolveStaleTime(this.options.staleTime, this.#currentQuery) !== resolveStaleTime(prevOptions.staleTime, this.#currentQuery))) {
this.#updateStaleTimeout();
}
const nextRefetchInterval = this.#computeRefetchInterval();
if (mounted && (this.#currentQuery !== prevQuery || resolveEnabled(this.options.enabled, this.#currentQuery) !== resolveEnabled(prevOptions.enabled, this.#currentQuery) || nextRefetchInterval !== this.#currentRefetchInterval)) {
this.#updateRefetchInterval(nextRefetchInterval);
}
}
getOptimisticResult(options) {
const query = this.#client.getQueryCache().build(this.#client, options);
const result = this.createResult(query, options);
if (shouldAssignObserverCurrentProperties(this, result)) {
this.#currentResult = result;
this.#currentResultOptions = this.options;
this.#currentResultState = this.#currentQuery.state;
}
return result;
}
getCurrentResult() {
return this.#currentResult;
}
trackResult(result, onPropTracked) {
return new Proxy(result, {
get: (target, key) => {
this.trackProp(key);
onPropTracked?.(key);
if (key === "promise") {
this.trackProp("data");
if (!this.options.experimental_prefetchInRender && this.#currentThenable.status === "pending") {
this.#currentThenable.reject(new Error("experimental_prefetchInRender feature flag is not enabled"));
}
}
return Reflect.get(target, key);
}
});
}
trackProp(key) {
this.#trackedProps.add(key);
}
getCurrentQuery() {
return this.#currentQuery;
}
refetch({
...options
} = {}) {
return this.fetch({
...options
});
}
fetchOptimistic(options) {
const defaultedOptions = this.#client.defaultQueryOptions(options);
const query = this.#client.getQueryCache().build(this.#client, defaultedOptions);
return query.fetch().then(() => this.createResult(query, defaultedOptions));
}
fetch(fetchOptions) {
return this.#executeFetch({
...fetchOptions,
cancelRefetch: fetchOptions.cancelRefetch ?? true
}).then(() => {
this.updateResult();
return this.#currentResult;
});
}
#executeFetch(fetchOptions) {
this.#updateQuery();
let promise = this.#currentQuery.fetch(this.options, fetchOptions);
if (!fetchOptions?.throwOnError) {
promise = promise.catch(noop);
}
return promise;
}
#updateStaleTimeout() {
this.#clearStaleTimeout();
const staleTime = resolveStaleTime(this.options.staleTime, this.#currentQuery);
if (isServer || this.#currentResult.isStale || !isValidTimeout(staleTime)) {
return;
}
const time = timeUntilStale(this.#currentResult.dataUpdatedAt, staleTime);
const timeout = time + 1;
this.#staleTimeoutId = timeoutManager.setTimeout(() => {
if (!this.#currentResult.isStale) {
this.updateResult();
}
}, timeout);
}
#computeRefetchInterval() {
return (typeof this.options.refetchInterval === "function" ? this.options.refetchInterval(this.#currentQuery) : this.options.refetchInterval) ?? false;
}
#updateRefetchInterval(nextInterval) {
this.#clearRefetchInterval();
this.#currentRefetchInterval = nextInterval;
if (isServer || resolveEnabled(this.options.enabled, this.#currentQuery) === false || !isValidTimeout(this.#currentRefetchInterval) || this.#currentRefetchInterval === 0) {
return;
}
this.#refetchIntervalId = timeoutManager.setInterval(() => {
if (this.options.refetchIntervalInBackground || focusManager.isFocused()) {
this.#executeFetch();
}
}, this.#currentRefetchInterval);
}
#updateTimers() {
this.#updateStaleTimeout();
this.#updateRefetchInterval(this.#computeRefetchInterval());
}
#clearStaleTimeout() {
if (this.#staleTimeoutId) {
timeoutManager.clearTimeout(this.#staleTimeoutId);
this.#staleTimeoutId = void 0;
}
}
#clearRefetchInterval() {
if (this.#refetchIntervalId) {
timeoutManager.clearInterval(this.#refetchIntervalId);
this.#refetchIntervalId = void 0;
}
}
createResult(query, options) {
const prevQuery = this.#currentQuery;
const prevOptions = this.options;
const prevResult = this.#currentResult;
const prevResultState = this.#currentResultState;
const prevResultOptions = this.#currentResultOptions;
const queryChange = query !== prevQuery;
const queryInitialState = queryChange ? query.state : this.#currentQueryInitialState;
const {
state
} = query;
let newState = {
...state
};
let isPlaceholderData = false;
let data;
if (options._optimisticResults) {
const mounted = this.hasListeners();
const fetchOnMount = !mounted && shouldFetchOnMount(query, options);
const fetchOptionally = mounted && shouldFetchOptionally(query, prevQuery, options, prevOptions);
if (fetchOnMount || fetchOptionally) {
newState = {
...newState,
...fetchState(state.data, query.options)
};
}
if (options._optimisticResults === "isRestoring") {
newState.fetchStatus = "idle";
}
}
let {
error,
errorUpdatedAt,
status
} = newState;
data = newState.data;
let skipSelect = false;
if (options.placeholderData !== void 0 && data === void 0 && status === "pending") {
let placeholderData;
if (prevResult?.isPlaceholderData && options.placeholderData === prevResultOptions?.placeholderData) {
placeholderData = prevResult.data;
skipSelect = true;
} else {
placeholderData = typeof options.place