UNPKG

@tanstack/query-core

Version:

The framework agnostic core that powers TanStack Query

451 lines (450 loc) 15.8 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/queryObserver.ts var queryObserver_exports = {}; __export(queryObserver_exports, { QueryObserver: () => QueryObserver }); module.exports = __toCommonJS(queryObserver_exports); var import_utils = require("./utils.cjs"); var import_notifyManager = require("./notifyManager.cjs"); var import_focusManager = require("./focusManager.cjs"); var import_subscribable = require("./subscribable.cjs"); var import_retryer = require("./retryer.cjs"); var QueryObserver = class extends import_subscribable.Subscribable { constructor(client, options) { super(); this.#currentQuery = void 0; this.#currentQueryInitialState = void 0; this.#currentResult = void 0; this.#trackedProps = /* @__PURE__ */ new Set(); this.#client = client; this.options = options; this.#selectError = null; this.bindMethods(); this.setOptions(options); } #client; #currentQuery; #currentQueryInitialState; #currentResult; #currentResultState; #currentResultOptions; #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; 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(); } 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, notifyOptions) { const prevOptions = this.options; const prevQuery = this.#currentQuery; this.options = this.#client.defaultQueryOptions(options); if (!(0, import_utils.shallowEqualObjects)(prevOptions, this.options)) { this.#client.getQueryCache().notify({ type: "observerOptionsUpdated", query: this.#currentQuery, observer: this }); } if (typeof this.options.enabled !== "undefined" && typeof this.options.enabled !== "boolean") { throw new Error("Expected enabled to be a boolean"); } if (!this.options.queryKey) { this.options.queryKey = prevOptions.queryKey; } this.#updateQuery(); const mounted = this.hasListeners(); if (mounted && shouldFetchOptionally( this.#currentQuery, prevQuery, this.options, prevOptions )) { this.#executeFetch(); } this.updateResult(notifyOptions); if (mounted && (this.#currentQuery !== prevQuery || this.options.enabled !== prevOptions.enabled || this.options.staleTime !== prevOptions.staleTime)) { this.#updateStaleTimeout(); } const nextRefetchInterval = this.#computeRefetchInterval(); if (mounted && (this.#currentQuery !== prevQuery || this.options.enabled !== prevOptions.enabled || 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) { const trackedResult = {}; Object.keys(result).forEach((key) => { Object.defineProperty(trackedResult, key, { configurable: false, enumerable: true, get: () => { this.#trackedProps.add(key); return result[key]; } }); }); return trackedResult; } 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); query.isFetchingOptimistic = true; 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(import_utils.noop); } return promise; } #updateStaleTimeout() { this.#clearStaleTimeout(); if (import_utils.isServer || this.#currentResult.isStale || !(0, import_utils.isValidTimeout)(this.options.staleTime)) { return; } const time = (0, import_utils.timeUntilStale)( this.#currentResult.dataUpdatedAt, this.options.staleTime ); const timeout = time + 1; this.#staleTimeoutId = 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 (import_utils.isServer || this.options.enabled === false || !(0, import_utils.isValidTimeout)(this.#currentRefetchInterval) || this.#currentRefetchInterval === 0) { return; } this.#refetchIntervalId = setInterval(() => { if (this.options.refetchIntervalInBackground || import_focusManager.focusManager.isFocused()) { this.#executeFetch(); } }, this.#currentRefetchInterval); } #updateTimers() { this.#updateStaleTimeout(); this.#updateRefetchInterval(this.#computeRefetchInterval()); } #clearStaleTimeout() { if (this.#staleTimeoutId) { clearTimeout(this.#staleTimeoutId); this.#staleTimeoutId = void 0; } } #clearRefetchInterval() { if (this.#refetchIntervalId) { 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 { error, errorUpdatedAt, fetchStatus, status } = 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) { fetchStatus = (0, import_retryer.canFetch)(query.options.networkMode) ? "fetching" : "paused"; if (!state.dataUpdatedAt) { status = "pending"; } } if (options._optimisticResults === "isRestoring") { fetchStatus = "idle"; } } if (options.select && typeof state.data !== "undefined") { if (prevResult && state.data === prevResultState?.data && options.select === this.#selectFn) { data = this.#selectResult; } else { try { this.#selectFn = options.select; data = options.select(state.data); data = (0, import_utils.replaceData)(prevResult?.data, data, options); this.#selectResult = data; this.#selectError = null; } catch (selectError) { this.#selectError = selectError; } } } else { data = state.data; } if (typeof options.placeholderData !== "undefined" && typeof data === "undefined" && status === "pending") { let placeholderData; if (prevResult?.isPlaceholderData && options.placeholderData === prevResultOptions?.placeholderData) { placeholderData = prevResult.data; } else { placeholderData = typeof options.placeholderData === "function" ? options.placeholderData( this.#lastQueryWithDefinedData?.state.data, this.#lastQueryWithDefinedData ) : options.placeholderData; if (options.select && typeof placeholderData !== "undefined") { try { placeholderData = options.select(placeholderData); this.#selectError = null; } catch (selectError) { this.#selectError = selectError; } } } if (typeof placeholderData !== "undefined") { status = "success"; data = (0, import_utils.replaceData)( prevResult?.data, placeholderData, options ); isPlaceholderData = true; } } if (this.#selectError) { error = this.#selectError; data = this.#selectResult; errorUpdatedAt = Date.now(); status = "error"; } const isFetching = fetchStatus === "fetching"; const isPending = status === "pending"; const isError = status === "error"; const isLoading = isPending && isFetching; const result = { status, fetchStatus, isPending, isSuccess: status === "success", isError, isInitialLoading: isLoading, isLoading, data, dataUpdatedAt: state.dataUpdatedAt, error, errorUpdatedAt, failureCount: state.fetchFailureCount, failureReason: state.fetchFailureReason, errorUpdateCount: state.errorUpdateCount, isFetched: state.dataUpdateCount > 0 || state.errorUpdateCount > 0, isFetchedAfterMount: state.dataUpdateCount > queryInitialState.dataUpdateCount || state.errorUpdateCount > queryInitialState.errorUpdateCount, isFetching, isRefetching: isFetching && !isPending, isLoadingError: isError && state.dataUpdatedAt === 0, isPaused: fetchStatus === "paused", isPlaceholderData, isRefetchError: isError && state.dataUpdatedAt !== 0, isStale: isStale(query, options), refetch: this.refetch }; return result; } updateResult(notifyOptions) { const prevResult = this.#currentResult; const nextResult = this.createResult(this.#currentQuery, this.options); this.#currentResultState = this.#currentQuery.state; this.#currentResultOptions = this.options; if ((0, import_utils.shallowEqualObjects)(nextResult, prevResult)) { return; } if (this.#currentResultState.data !== void 0) { this.#lastQueryWithDefinedData = this.#currentQuery; } this.#currentResult = nextResult; const defaultNotifyOptions = {}; const shouldNotifyListeners = () => { if (!prevResult) { return true; } const { notifyOnChangeProps } = this.options; const notifyOnChangePropsValue = typeof notifyOnChangeProps === "function" ? notifyOnChangeProps() : notifyOnChangeProps; if (notifyOnChangePropsValue === "all" || !notifyOnChangePropsValue && !this.#trackedProps.size) { return true; } const includedProps = new Set( notifyOnChangePropsValue ?? this.#trackedProps ); if (this.options.throwOnError) { includedProps.add("error"); } return Object.keys(this.#currentResult).some((key) => { const typedKey = key; const changed = this.#currentResult[typedKey] !== prevResult[typedKey]; return changed && includedProps.has(typedKey); }); }; if (notifyOptions?.listeners !== false && shouldNotifyListeners()) { defaultNotifyOptions.listeners = true; } this.#notify({ ...defaultNotifyOptions, ...notifyOptions }); } #updateQuery() { const query = this.#client.getQueryCache().build(this.#client, this.options); if (query === this.#currentQuery) { return; } const prevQuery = this.#currentQuery; this.#currentQuery = query; this.#currentQueryInitialState = query.state; if (this.hasListeners()) { prevQuery?.removeObserver(this); query.addObserver(this); } } onQueryUpdate() { this.updateResult(); if (this.hasListeners()) { this.#updateTimers(); } } #notify(notifyOptions) { import_notifyManager.notifyManager.batch(() => { if (notifyOptions.listeners) { this.listeners.forEach((listener) => { listener(this.#currentResult); }); } this.#client.getQueryCache().notify({ query: this.#currentQuery, type: "observerResultsUpdated" }); }); } }; function shouldLoadOnMount(query, options) { return options.enabled !== false && !query.state.dataUpdatedAt && !(query.state.status === "error" && options.retryOnMount === false); } function shouldFetchOnMount(query, options) { return shouldLoadOnMount(query, options) || query.state.dataUpdatedAt > 0 && shouldFetchOn(query, options, options.refetchOnMount); } function shouldFetchOn(query, options, field) { if (options.enabled !== false) { const value = typeof field === "function" ? field(query) : field; return value === "always" || value !== false && isStale(query, options); } return false; } function shouldFetchOptionally(query, prevQuery, options, prevOptions) { return options.enabled !== false && (query !== prevQuery || prevOptions.enabled === false) && (!options.suspense || query.state.status !== "error") && isStale(query, options); } function isStale(query, options) { return query.isStaleByTime(options.staleTime); } function shouldAssignObserverCurrentProperties(observer, optimisticResult) { if (!(0, import_utils.shallowEqualObjects)(observer.getCurrentResult(), optimisticResult)) { return true; } return false; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { QueryObserver }); //# sourceMappingURL=queryObserver.cjs.map