UNPKG

@gaddario98/react-queries

Version:

```bash npm install @gaddario98/base-queries ```

1,624 lines 151 kB
import {atom,useSetAtom,useAtomValue}from'jotai';import {atomWithStorage,createJSONStorage,selectAtom}from'jotai/utils';import {storage,atomStateGenerator}from'@gaddario98/react-state';import axios from'axios';import*as React from'react';import {useMemo,useCallback,useRef,useEffect,useReducer,useState}from'react';import {jsx}from'react/jsx-runtime';import {c}from'react/compiler-runtime';import equal from'fast-deep-equal';// ============================================================================ // 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 = atomWithStorage('queries-atom', {}, createJSONStorage(() => storage), { getOnInit: true }); /** * Global atom storing all mutation results. * Key format: "scopeId:mutationKey" */ const mutationsAtom = 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 => 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 => 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 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 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.placeholderData === "function" ? options.placeholderData(this.#lastQueryWithDefinedData?.state.data, this.#lastQueryWithDefinedData) : options.placeholderData; } if (placeholderData !== void 0) { status = "success"; data = replaceData(prevRe