UNPKG

@apollo/client

Version:

A fully-featured caching GraphQL client.

930 lines 47.4 kB
import { __assign, __extends } from "tslib"; import { invariant } from "../utilities/globals/index.js"; import { equal } from "@wry/equality"; import { NetworkStatus, isNetworkRequestInFlight } from "./networkStatus.js"; import { cloneDeep, compact, getOperationDefinition, Observable, iterateObserversSafely, fixObservableSubclass, getQueryDefinition, preventUnhandledRejection, } from "../utilities/index.js"; import { ApolloError, isApolloError } from "../errors/index.js"; import { equalByQuery } from "./equalByQuery.js"; import { Slot } from "optimism"; var assign = Object.assign, hasOwnProperty = Object.hasOwnProperty; var ObservableQuery = /** @class */ (function (_super) { __extends(ObservableQuery, _super); function ObservableQuery(_a) { var queryManager = _a.queryManager, queryInfo = _a.queryInfo, options = _a.options; var _this = this; var startedInactive = ObservableQuery.inactiveOnCreation.getValue(); _this = _super.call(this, function (observer) { if (startedInactive) { queryManager["queries"].set(_this.queryId, queryInfo); startedInactive = false; } // Zen Observable has its own error function, so in order to log correctly // we need to provide a custom error callback. try { var subObserver = observer._subscription._observer; if (subObserver && !subObserver.error) { subObserver.error = defaultSubscriptionObserverErrorCallback; } } catch (_a) { } var first = !_this.observers.size; _this.observers.add(observer); // Deliver most recent error or result. var last = _this.last; if (last && last.error) { observer.error && observer.error(last.error); } else if (last && last.result) { observer.next && observer.next(_this.maskResult(last.result)); } // Initiate observation of this query if it hasn't been reported to // the QueryManager yet. if (first) { // Blindly catching here prevents unhandled promise rejections, // and is safe because the ObservableQuery handles this error with // this.observer.error, so we're not just swallowing the error by // ignoring it here. _this.reobserve().catch(function () { }); } return function () { if (_this.observers.delete(observer) && !_this.observers.size) { _this.tearDownQuery(); } }; }) || this; _this.observers = new Set(); _this.subscriptions = new Set(); _this.dirty = false; // related classes _this.queryInfo = queryInfo; _this.queryManager = queryManager; // active state _this.waitForOwnResult = skipCacheDataFor(options.fetchPolicy); _this.isTornDown = false; _this.subscribeToMore = _this.subscribeToMore.bind(_this); _this.maskResult = _this.maskResult.bind(_this); var _b = queryManager.defaultOptions.watchQuery, _c = _b === void 0 ? {} : _b, _d = _c.fetchPolicy, defaultFetchPolicy = _d === void 0 ? "cache-first" : _d; var _e = options.fetchPolicy, fetchPolicy = _e === void 0 ? defaultFetchPolicy : _e, // Make sure we don't store "standby" as the initialFetchPolicy. _f = options.initialFetchPolicy, // Make sure we don't store "standby" as the initialFetchPolicy. initialFetchPolicy = _f === void 0 ? fetchPolicy === "standby" ? defaultFetchPolicy : (fetchPolicy) : _f; _this.options = __assign(__assign({}, options), { // Remember the initial options.fetchPolicy so we can revert back to this // policy when variables change. This information can also be specified // (or overridden) by providing options.initialFetchPolicy explicitly. initialFetchPolicy: initialFetchPolicy, // This ensures this.options.fetchPolicy always has a string value, in // case options.fetchPolicy was not provided. fetchPolicy: fetchPolicy }); _this.queryId = queryInfo.queryId || queryManager.generateQueryId(); var opDef = getOperationDefinition(_this.query); _this.queryName = opDef && opDef.name && opDef.name.value; return _this; } Object.defineProperty(ObservableQuery.prototype, "query", { // The `query` computed property will always reflect the document transformed // by the last run query. `this.options.query` will always reflect the raw // untransformed query to ensure document transforms with runtime conditionals // are run on the original document. get: function () { return this.lastQuery || this.options.query; }, enumerable: false, configurable: true }); Object.defineProperty(ObservableQuery.prototype, "variables", { // Computed shorthand for this.options.variables, preserved for // backwards compatibility. /** * An object containing the variables that were provided for the query. */ get: function () { return this.options.variables; }, enumerable: false, configurable: true }); ObservableQuery.prototype.result = function () { var _this = this; return new Promise(function (resolve, reject) { // TODO: this code doesn’t actually make sense insofar as the observer // will never exist in this.observers due how zen-observable wraps observables. // https://github.com/zenparsing/zen-observable/blob/master/src/Observable.js#L169 var observer = { next: function (result) { resolve(result); // Stop the query within the QueryManager if we can before // this function returns. // // We do this in order to prevent observers piling up within // the QueryManager. Notice that we only fully unsubscribe // from the subscription in a setTimeout(..., 0) call. This call can // actually be handled by the browser at a much later time. If queries // are fired in the meantime, observers that should have been removed // from the QueryManager will continue to fire, causing an unnecessary // performance hit. _this.observers.delete(observer); if (!_this.observers.size) { _this.queryManager.removeQuery(_this.queryId); } setTimeout(function () { subscription.unsubscribe(); }, 0); }, error: reject, }; var subscription = _this.subscribe(observer); }); }; /** @internal */ ObservableQuery.prototype.resetDiff = function () { this.queryInfo.resetDiff(); }; ObservableQuery.prototype.getCurrentFullResult = function (saveAsLastResult) { if (saveAsLastResult === void 0) { saveAsLastResult = true; } // Use the last result as long as the variables match this.variables. var lastResult = this.getLastResult(true); var networkStatus = this.queryInfo.networkStatus || (lastResult && lastResult.networkStatus) || NetworkStatus.ready; var result = __assign(__assign({}, lastResult), { loading: isNetworkRequestInFlight(networkStatus), networkStatus: networkStatus }); var _a = this.options.fetchPolicy, fetchPolicy = _a === void 0 ? "cache-first" : _a; if ( // These fetch policies should never deliver data from the cache, unless // redelivering a previously delivered result. skipCacheDataFor(fetchPolicy) || // If this.options.query has @client(always: true) fields, we cannot // trust diff.result, since it was read from the cache without running // local resolvers (and it's too late to run resolvers now, since we must // return a result synchronously). this.queryManager.getDocumentInfo(this.query).hasForcedResolvers) { // Fall through. } else if (this.waitForOwnResult) { // This would usually be a part of `QueryInfo.getDiff()`. // which we skip in the waitForOwnResult case since we are not // interested in the diff. this.queryInfo["updateWatch"](); } else { var diff = this.queryInfo.getDiff(); if (diff.complete || this.options.returnPartialData) { result.data = diff.result; } if (equal(result.data, {})) { result.data = void 0; } if (diff.complete) { // Similar to setting result.partial to false, but taking advantage of the // falsiness of missing fields. delete result.partial; // If the diff is complete, and we're using a FetchPolicy that // terminates after a complete cache read, we can assume the next result // we receive will have NetworkStatus.ready and !loading. if (diff.complete && result.networkStatus === NetworkStatus.loading && (fetchPolicy === "cache-first" || fetchPolicy === "cache-only")) { result.networkStatus = NetworkStatus.ready; result.loading = false; } } else { result.partial = true; } // We need to check for both both `error` and `errors` field because there // are cases where sometimes `error` is set, but not `errors` and // vice-versa. This will be updated in the next major version when // `errors` is deprecated in favor of `error`. if (result.networkStatus === NetworkStatus.ready && (result.error || result.errors)) { result.networkStatus = NetworkStatus.error; } if (globalThis.__DEV__ !== false && !diff.complete && !this.options.partialRefetch && !result.loading && !result.data && !result.error) { logMissingFieldErrors(diff.missing); } } if (saveAsLastResult) { this.updateLastResult(result); } return result; }; ObservableQuery.prototype.getCurrentResult = function (saveAsLastResult) { if (saveAsLastResult === void 0) { saveAsLastResult = true; } return this.maskResult(this.getCurrentFullResult(saveAsLastResult)); }; // Compares newResult to the snapshot we took of this.lastResult when it was // first received. ObservableQuery.prototype.isDifferentFromLastResult = function (newResult, variables) { if (!this.last) { return true; } var documentInfo = this.queryManager.getDocumentInfo(this.query); var dataMasking = this.queryManager.dataMasking; var query = dataMasking ? documentInfo.nonReactiveQuery : this.query; var resultIsDifferent = dataMasking || documentInfo.hasNonreactiveDirective ? !equalByQuery(query, this.last.result, newResult, this.variables) : !equal(this.last.result, newResult); return (resultIsDifferent || (variables && !equal(this.last.variables, variables))); }; ObservableQuery.prototype.getLast = function (key, variablesMustMatch) { var last = this.last; if (last && last[key] && (!variablesMustMatch || equal(last.variables, this.variables))) { return last[key]; } }; ObservableQuery.prototype.getLastResult = function (variablesMustMatch) { return this.getLast("result", variablesMustMatch); }; ObservableQuery.prototype.getLastError = function (variablesMustMatch) { return this.getLast("error", variablesMustMatch); }; ObservableQuery.prototype.resetLastResults = function () { delete this.last; this.isTornDown = false; }; ObservableQuery.prototype.resetQueryStoreErrors = function () { this.queryManager.resetErrors(this.queryId); }; /** * Update the variables of this observable query, and fetch the new results. * This method should be preferred over `setVariables` in most use cases. * * @param variables - The new set of variables. If there are missing variables, * the previous values of those variables will be used. */ ObservableQuery.prototype.refetch = function (variables) { var _a; var reobserveOptions = { // Always disable polling for refetches. pollInterval: 0, }; // Unless the provided fetchPolicy always consults the network // (no-cache, network-only, or cache-and-network), override it with // network-only to force the refetch for this fetchQuery call. var fetchPolicy = this.options.fetchPolicy; if (fetchPolicy === "no-cache") { reobserveOptions.fetchPolicy = "no-cache"; } else { reobserveOptions.fetchPolicy = "network-only"; } if (globalThis.__DEV__ !== false && variables && hasOwnProperty.call(variables, "variables")) { var queryDef = getQueryDefinition(this.query); var vars = queryDef.variableDefinitions; if (!vars || !vars.some(function (v) { return v.variable.name.value === "variables"; })) { globalThis.__DEV__ !== false && invariant.warn( 21, variables, ((_a = queryDef.name) === null || _a === void 0 ? void 0 : _a.value) || queryDef ); } } if (variables && !equal(this.options.variables, variables)) { // Update the existing options with new variables reobserveOptions.variables = this.options.variables = __assign(__assign({}, this.options.variables), variables); } this.queryInfo.resetLastWrite(); return this.reobserve(reobserveOptions, NetworkStatus.refetch); }; /** * A function that helps you fetch the next set of results for a [paginated list field](https://www.apollographql.com/docs/react/pagination/core-api/). */ ObservableQuery.prototype.fetchMore = function (fetchMoreOptions) { var _this = this; var combinedOptions = __assign(__assign({}, (fetchMoreOptions.query ? fetchMoreOptions : (__assign(__assign(__assign(__assign({}, this.options), { query: this.options.query }), fetchMoreOptions), { variables: __assign(__assign({}, this.options.variables), fetchMoreOptions.variables) })))), { // The fetchMore request goes immediately to the network and does // not automatically write its result to the cache (hence no-cache // instead of network-only), because we allow the caller of // fetchMore to provide an updateQuery callback that determines how // the data gets written to the cache. fetchPolicy: "no-cache" }); combinedOptions.query = this.transformDocument(combinedOptions.query); var qid = this.queryManager.generateQueryId(); // If a temporary query is passed to `fetchMore`, we don't want to store // it as the last query result since it may be an optimized query for // pagination. We will however run the transforms on the original document // as well as the document passed in `fetchMoreOptions` to ensure the cache // uses the most up-to-date document which may rely on runtime conditionals. this.lastQuery = fetchMoreOptions.query ? this.transformDocument(this.options.query) : combinedOptions.query; // Simulate a loading result for the original query with // result.networkStatus === NetworkStatus.fetchMore. var queryInfo = this.queryInfo; var originalNetworkStatus = queryInfo.networkStatus; queryInfo.networkStatus = NetworkStatus.fetchMore; if (combinedOptions.notifyOnNetworkStatusChange) { this.observe(); } var updatedQuerySet = new Set(); var updateQuery = fetchMoreOptions === null || fetchMoreOptions === void 0 ? void 0 : fetchMoreOptions.updateQuery; var isCached = this.options.fetchPolicy !== "no-cache"; if (!isCached) { invariant(updateQuery, 22); } return this.queryManager .fetchQuery(qid, combinedOptions, NetworkStatus.fetchMore) .then(function (fetchMoreResult) { _this.queryManager.removeQuery(qid); if (queryInfo.networkStatus === NetworkStatus.fetchMore) { queryInfo.networkStatus = originalNetworkStatus; } if (isCached) { // Performing this cache update inside a cache.batch transaction ensures // any affected cache.watch watchers are notified at most once about any // updates. Most watchers will be using the QueryInfo class, which // responds to notifications by calling reobserveCacheFirst to deliver // fetchMore cache results back to this ObservableQuery. _this.queryManager.cache.batch({ update: function (cache) { var updateQuery = fetchMoreOptions.updateQuery; if (updateQuery) { cache.updateQuery({ query: _this.query, variables: _this.variables, returnPartialData: true, optimistic: false, }, function (previous) { return updateQuery(previous, { fetchMoreResult: fetchMoreResult.data, variables: combinedOptions.variables, }); }); } else { // If we're using a field policy instead of updateQuery, the only // thing we need to do is write the new data to the cache using // combinedOptions.variables (instead of this.variables, which is // what this.updateQuery uses, because it works by abusing the // original field value, keyed by the original variables). cache.writeQuery({ query: combinedOptions.query, variables: combinedOptions.variables, data: fetchMoreResult.data, }); } }, onWatchUpdated: function (watch) { // Record the DocumentNode associated with any watched query whose // data were updated by the cache writes above. updatedQuerySet.add(watch.query); }, }); } else { // There is a possibility `lastResult` may not be set when // `fetchMore` is called which would cause this to crash. This should // only happen if we haven't previously reported a result. We don't // quite know what the right behavior should be here since this block // of code runs after the fetch result has executed on the network. // We plan to let it crash in the meantime. // // If we get bug reports due to the `data` property access on // undefined, this should give us a real-world scenario that we can // use to test against and determine the right behavior. If we do end // up changing this behavior, this may require, for example, an // adjustment to the types on `updateQuery` since that function // expects that the first argument always contains previous result // data, but not `undefined`. var lastResult = _this.getLast("result"); var data = updateQuery(lastResult.data, { fetchMoreResult: fetchMoreResult.data, variables: combinedOptions.variables, }); _this.reportResult(__assign(__assign({}, lastResult), { networkStatus: originalNetworkStatus, loading: isNetworkRequestInFlight(originalNetworkStatus), data: data }), _this.variables); } return _this.maskResult(fetchMoreResult); }) .finally(function () { // In case the cache writes above did not generate a broadcast // notification (which would have been intercepted by onWatchUpdated), // likely because the written data were the same as what was already in // the cache, we still want fetchMore to deliver its final loading:false // result with the unchanged data. if (isCached && !updatedQuerySet.has(_this.query)) { _this.reobserveCacheFirst(); } }); }; // XXX the subscription variables are separate from the query variables. // if you want to update subscription variables, right now you have to do that separately, // and you can only do it by stopping the subscription and then subscribing again with new variables. /** * A function that enables you to execute a [subscription](https://www.apollographql.com/docs/react/data/subscriptions/), usually to subscribe to specific fields that were included in the query. * * This function returns _another_ function that you can call to terminate the subscription. */ ObservableQuery.prototype.subscribeToMore = function (options) { var _this = this; var subscription = this.queryManager .startGraphQLSubscription({ query: options.document, variables: options.variables, context: options.context, }) .subscribe({ next: function (subscriptionData) { var updateQuery = options.updateQuery; if (updateQuery) { _this.updateQuery(function (previous, updateOptions) { return updateQuery(previous, __assign({ subscriptionData: subscriptionData }, updateOptions)); }); } }, error: function (err) { if (options.onError) { options.onError(err); return; } globalThis.__DEV__ !== false && invariant.error(23, err); }, }); this.subscriptions.add(subscription); return function () { if (_this.subscriptions.delete(subscription)) { subscription.unsubscribe(); } }; }; ObservableQuery.prototype.setOptions = function (newOptions) { return this.reobserve(newOptions); }; ObservableQuery.prototype.silentSetOptions = function (newOptions) { var mergedOptions = compact(this.options, newOptions || {}); assign(this.options, mergedOptions); }; /** * Update the variables of this observable query, and fetch the new results * if they've changed. Most users should prefer `refetch` instead of * `setVariables` in order to to be properly notified of results even when * they come from the cache. * * Note: the `next` callback will *not* fire if the variables have not changed * or if the result is coming from cache. * * Note: the promise will return the old results immediately if the variables * have not changed. * * Note: the promise will return null immediately if the query is not active * (there are no subscribers). * * @param variables - The new set of variables. If there are missing variables, * the previous values of those variables will be used. */ ObservableQuery.prototype.setVariables = function (variables) { if (equal(this.variables, variables)) { // If we have no observers, then we don't actually want to make a network // request. As soon as someone observes the query, the request will kick // off. For now, we just store any changes. (See #1077) return this.observers.size ? this.result() : Promise.resolve(); } this.options.variables = variables; // See comment above if (!this.observers.size) { return Promise.resolve(); } return this.reobserve({ // Reset options.fetchPolicy to its original value. fetchPolicy: this.options.initialFetchPolicy, variables: variables, }, NetworkStatus.setVariables); }; /** * A function that enables you to update the query's cached result without executing a followup GraphQL operation. * * See [using updateQuery and updateFragment](https://www.apollographql.com/docs/react/caching/cache-interaction/#using-updatequery-and-updatefragment) for additional information. */ ObservableQuery.prototype.updateQuery = function (mapFn) { var queryManager = this.queryManager; var _a = queryManager.cache.diff({ query: this.options.query, variables: this.variables, returnPartialData: true, optimistic: false, }), result = _a.result, complete = _a.complete; var newResult = mapFn(result, { variables: this.variables, complete: !!complete, previousData: result, }); if (newResult) { queryManager.cache.writeQuery({ query: this.options.query, data: newResult, variables: this.variables, }); queryManager.broadcastQueries(); } }; /** * A function that instructs the query to begin re-executing at a specified interval (in milliseconds). */ ObservableQuery.prototype.startPolling = function (pollInterval) { this.options.pollInterval = pollInterval; this.updatePolling(); }; /** * A function that instructs the query to stop polling after a previous call to `startPolling`. */ ObservableQuery.prototype.stopPolling = function () { this.options.pollInterval = 0; this.updatePolling(); }; // Update options.fetchPolicy according to options.nextFetchPolicy. ObservableQuery.prototype.applyNextFetchPolicy = function (reason, // It's possible to use this method to apply options.nextFetchPolicy to // options.fetchPolicy even if options !== this.options, though that happens // most often when the options are temporary, used for only one request and // then thrown away, so nextFetchPolicy may not end up mattering. options) { if (options.nextFetchPolicy) { var _a = options.fetchPolicy, fetchPolicy = _a === void 0 ? "cache-first" : _a, _b = options.initialFetchPolicy, initialFetchPolicy = _b === void 0 ? fetchPolicy : _b; if (fetchPolicy === "standby") { // Do nothing, leaving options.fetchPolicy unchanged. } else if (typeof options.nextFetchPolicy === "function") { // When someone chooses "cache-and-network" or "network-only" as their // initial FetchPolicy, they often do not want future cache updates to // trigger unconditional network requests, which is what repeatedly // applying the "cache-and-network" or "network-only" policies would // seem to imply. Instead, when the cache reports an update after the // initial network request, it may be desirable for subsequent network // requests to be triggered only if the cache result is incomplete. To // that end, the options.nextFetchPolicy option provides an easy way to // update options.fetchPolicy after the initial network request, without // having to call observableQuery.setOptions. options.fetchPolicy = options.nextFetchPolicy(fetchPolicy, { reason: reason, options: options, observable: this, initialFetchPolicy: initialFetchPolicy, }); } else if (reason === "variables-changed") { options.fetchPolicy = initialFetchPolicy; } else { options.fetchPolicy = options.nextFetchPolicy; } } return options.fetchPolicy; }; ObservableQuery.prototype.fetch = function (options, newNetworkStatus, query) { // TODO Make sure we update the networkStatus (and infer fetchVariables) // before actually committing to the fetch. var queryInfo = this.queryManager.getOrCreateQuery(this.queryId); queryInfo.setObservableQuery(this); return this.queryManager["fetchConcastWithInfo"](queryInfo, options, newNetworkStatus, query); }; // Turns polling on or off based on this.options.pollInterval. ObservableQuery.prototype.updatePolling = function () { var _this = this; // Avoid polling in SSR mode if (this.queryManager.ssrMode) { return; } var _a = this, pollingInfo = _a.pollingInfo, pollInterval = _a.options.pollInterval; if (!pollInterval || !this.hasObservers()) { if (pollingInfo) { clearTimeout(pollingInfo.timeout); delete this.pollingInfo; } return; } if (pollingInfo && pollingInfo.interval === pollInterval) { return; } invariant(pollInterval, 24); var info = pollingInfo || (this.pollingInfo = {}); info.interval = pollInterval; var maybeFetch = function () { var _a, _b; if (_this.pollingInfo) { if (!isNetworkRequestInFlight(_this.queryInfo.networkStatus) && !((_b = (_a = _this.options).skipPollAttempt) === null || _b === void 0 ? void 0 : _b.call(_a))) { _this.reobserve({ // Most fetchPolicy options don't make sense to use in a polling context, as // users wouldn't want to be polling the cache directly. However, network-only and // no-cache are both useful for when the user wants to control whether or not the // polled results are written to the cache. fetchPolicy: _this.options.initialFetchPolicy === "no-cache" ? "no-cache" : "network-only", }, NetworkStatus.poll).then(poll, poll); } else { poll(); } } }; var poll = function () { var info = _this.pollingInfo; if (info) { clearTimeout(info.timeout); info.timeout = setTimeout(maybeFetch, info.interval); } }; poll(); }; ObservableQuery.prototype.updateLastResult = function (newResult, variables) { if (variables === void 0) { variables = this.variables; } var error = this.getLastError(); // Preserve this.last.error unless the variables have changed. if (error && this.last && !equal(variables, this.last.variables)) { error = void 0; } return (this.last = __assign({ result: this.queryManager.assumeImmutableResults ? newResult : cloneDeep(newResult), variables: variables }, (error ? { error: error } : null))); }; ObservableQuery.prototype.reobserveAsConcast = function (newOptions, newNetworkStatus) { var _this = this; this.isTornDown = false; var useDisposableConcast = // Refetching uses a disposable Concast to allow refetches using different // options/variables, without permanently altering the options of the // original ObservableQuery. newNetworkStatus === NetworkStatus.refetch || // The fetchMore method does not actually call the reobserve method, but, // if it did, it would definitely use a disposable Concast. newNetworkStatus === NetworkStatus.fetchMore || // Polling uses a disposable Concast so the polling options (which force // fetchPolicy to be "network-only" or "no-cache") won't override the original options. newNetworkStatus === NetworkStatus.poll; // Save the old variables, since Object.assign may modify them below. var oldVariables = this.options.variables; var oldFetchPolicy = this.options.fetchPolicy; var mergedOptions = compact(this.options, newOptions || {}); var options = useDisposableConcast ? // Disposable Concast fetches receive a shallow copy of this.options // (merged with newOptions), leaving this.options unmodified. mergedOptions : assign(this.options, mergedOptions); // Don't update options.query with the transformed query to avoid // overwriting this.options.query when we aren't using a disposable concast. // We want to ensure we can re-run the custom document transforms the next // time a request is made against the original query. var query = this.transformDocument(options.query); this.lastQuery = query; if (!useDisposableConcast) { // We can skip calling updatePolling if we're not changing this.options. this.updatePolling(); // Reset options.fetchPolicy to its original value when variables change, // unless a new fetchPolicy was provided by newOptions. if (newOptions && newOptions.variables && !equal(newOptions.variables, oldVariables) && // Don't mess with the fetchPolicy if it's currently "standby". options.fetchPolicy !== "standby" && // If we're changing the fetchPolicy anyway, don't try to change it here // using applyNextFetchPolicy. The explicit options.fetchPolicy wins. (options.fetchPolicy === oldFetchPolicy || // A `nextFetchPolicy` function has even higher priority, though, // so in that case `applyNextFetchPolicy` must be called. typeof options.nextFetchPolicy === "function")) { this.applyNextFetchPolicy("variables-changed", options); if (newNetworkStatus === void 0) { newNetworkStatus = NetworkStatus.setVariables; } } } this.waitForOwnResult && (this.waitForOwnResult = skipCacheDataFor(options.fetchPolicy)); var finishWaitingForOwnResult = function () { if (_this.concast === concast) { _this.waitForOwnResult = false; } }; var variables = options.variables && __assign({}, options.variables); var _a = this.fetch(options, newNetworkStatus, query), concast = _a.concast, fromLink = _a.fromLink; var observer = { next: function (result) { if (equal(_this.variables, variables)) { finishWaitingForOwnResult(); _this.reportResult(result, variables); } }, error: function (error) { if (equal(_this.variables, variables)) { // Coming from `getResultsFromLink`, `error` here should always be an `ApolloError`. // However, calling `concast.cancel` can inject another type of error, so we have to // wrap it again here. if (!isApolloError(error)) { error = new ApolloError({ networkError: error }); } finishWaitingForOwnResult(); _this.reportError(error, variables); } }, }; if (!useDisposableConcast && (fromLink || !this.concast)) { // We use the {add,remove}Observer methods directly to avoid wrapping // observer with an unnecessary SubscriptionObserver object. if (this.concast && this.observer) { this.concast.removeObserver(this.observer); } this.concast = concast; this.observer = observer; } concast.addObserver(observer); return concast; }; ObservableQuery.prototype.reobserve = function (newOptions, newNetworkStatus) { return preventUnhandledRejection(this.reobserveAsConcast(newOptions, newNetworkStatus).promise.then(this.maskResult)); }; ObservableQuery.prototype.resubscribeAfterError = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } // If `lastError` is set in the current when the subscription is re-created, // the subscription will immediately receive the error, which will // cause it to terminate again. To avoid this, we first clear // the last error/result from the `observableQuery` before re-starting // the subscription, and restore the last value afterwards so that the // subscription has a chance to stay open. var last = this.last; this.resetLastResults(); var subscription = this.subscribe.apply(this, args); this.last = last; return subscription; }; // (Re)deliver the current result to this.observers without applying fetch // policies or making network requests. ObservableQuery.prototype.observe = function () { this.reportResult( // Passing false is important so that this.getCurrentResult doesn't // save the fetchMore result as this.lastResult, causing it to be // ignored due to the this.isDifferentFromLastResult check in // this.reportResult. this.getCurrentFullResult(false), this.variables); }; ObservableQuery.prototype.reportResult = function (result, variables) { var lastError = this.getLastError(); var isDifferent = this.isDifferentFromLastResult(result, variables); // Update the last result even when isDifferentFromLastResult returns false, // because the query may be using the @nonreactive directive, and we want to // save the the latest version of any nonreactive subtrees (in case // getCurrentResult is called), even though we skip broadcasting changes. if (lastError || !result.partial || this.options.returnPartialData) { this.updateLastResult(result, variables); } if (lastError || isDifferent) { iterateObserversSafely(this.observers, "next", this.maskResult(result)); } }; ObservableQuery.prototype.reportError = function (error, variables) { // Since we don't get the current result on errors, only the error, we // must mirror the updates that occur in QueryStore.markQueryError here var errorResult = __assign(__assign({}, this.getLastResult()), { error: error, errors: error.graphQLErrors, networkStatus: NetworkStatus.error, loading: false }); this.updateLastResult(errorResult, variables); iterateObserversSafely(this.observers, "error", (this.last.error = error)); }; ObservableQuery.prototype.hasObservers = function () { return this.observers.size > 0; }; ObservableQuery.prototype.tearDownQuery = function () { if (this.isTornDown) return; if (this.concast && this.observer) { this.concast.removeObserver(this.observer); delete this.concast; delete this.observer; } this.stopPolling(); // stop all active GraphQL subscriptions this.subscriptions.forEach(function (sub) { return sub.unsubscribe(); }); this.subscriptions.clear(); this.queryManager.stopQuery(this.queryId); this.observers.clear(); this.isTornDown = true; }; ObservableQuery.prototype.transformDocument = function (document) { return this.queryManager.transform(document); }; ObservableQuery.prototype.maskResult = function (result) { return result && "data" in result ? __assign(__assign({}, result), { data: this.queryManager.maskOperation({ document: this.query, data: result.data, fetchPolicy: this.options.fetchPolicy, id: this.queryId, }) }) : result; }; /** @internal */ ObservableQuery.prototype.resetNotifications = function () { this.cancelNotifyTimeout(); this.dirty = false; }; ObservableQuery.prototype.cancelNotifyTimeout = function () { if (this.notifyTimeout) { clearTimeout(this.notifyTimeout); this.notifyTimeout = void 0; } }; /** @internal */ ObservableQuery.prototype.scheduleNotify = function () { var _this = this; if (this.dirty) return; this.dirty = true; if (!this.notifyTimeout) { this.notifyTimeout = setTimeout(function () { return _this.notify(); }, 0); } }; /** @internal */ ObservableQuery.prototype.notify = function () { this.cancelNotifyTimeout(); if (this.dirty) { if (this.options.fetchPolicy == "cache-only" || this.options.fetchPolicy == "cache-and-network" || !isNetworkRequestInFlight(this.queryInfo.networkStatus)) { var diff = this.queryInfo.getDiff(); if (diff.fromOptimisticTransaction) { // If this diff came from an optimistic transaction, deliver the // current cache data to the ObservableQuery, but don't perform a // reobservation, since oq.reobserveCacheFirst might make a network // request, and we never want to trigger network requests in the // middle of optimistic updates. this.observe(); } else { // Otherwise, make the ObservableQuery "reobserve" the latest data // using a temporary fetch policy of "cache-first", so complete cache // results have a chance to be delivered without triggering additional // network requests, even when options.fetchPolicy is "network-only" // or "cache-and-network". All other fetch policies are preserved by // this method, and are handled by calling oq.reobserve(). If this // reobservation is spurious, isDifferentFromLastResult still has a // chance to catch it before delivery to ObservableQuery subscribers. this.reobserveCacheFirst(); } } } this.dirty = false; }; // Reobserve with fetchPolicy effectively set to "cache-first", triggering // delivery of any new data from the cache, possibly falling back to the network // if any cache data are missing. This allows _complete_ cache results to be // delivered without also kicking off unnecessary network requests when // this.options.fetchPolicy is "cache-and-network" or "network-only". When // this.options.fetchPolicy is any other policy ("cache-first", "cache-only", // "standby", or "no-cache"), we call this.reobserve() as usual. ObservableQuery.prototype.reobserveCacheFirst = function () { var _a = this.options, fetchPolicy = _a.fetchPolicy, nextFetchPolicy = _a.nextFetchPolicy; if (fetchPolicy === "cache-and-network" || fetchPolicy === "network-only") { return this.reobserve({ fetchPolicy: "cache-first", // Use a temporary nextFetchPolicy function that replaces itself with the // previous nextFetchPolicy value and returns the original fetchPolicy. nextFetchPolicy: function (currentFetchPolicy, context) { // Replace this nextFetchPolicy function in the options object with the // original this.options.nextFetchPolicy value. this.nextFetchPolicy = nextFetchPolicy; // If the original nextFetchPolicy value was a function, give it a // chance to decide what happens here. if (typeof this.nextFetchPolicy === "function") { return this.nextFetchPolicy(currentFetchPolicy, context); } // Otherwise go back to the original this.options.fetchPolicy. return fetchPolicy; }, }); } return this.reobserve(); }; /** * @internal * A slot used by the `useQuery` hook to indicate that `client.watchQuery` * should not register the query immediately, but instead wait for the query to * be started registered with the `QueryManager` when `useSyncExternalStore` * actively subscribes to it. */ ObservableQuery.inactiveOnCreation = new Slot(); return ObservableQuery; }(Observable)); export { ObservableQuery }; // Necessary because the ObservableQuery constructor has a different // signature than the Observable constructor. fixObservableSubclass(ObservableQuery); function defaultSubscriptionObserverErrorCallback(error) { globalThis.__DEV__ !== false && invariant.error(25, error.message, error.stack); } export function logMissingFieldErrors(missing) { if (globalThis.__DEV__ !== false && missing) { globalThis.__DEV__ !== false && invariant.debug(26, missing); } } function skipCacheDataFor(fetchPolicy /* `undefined` would mean `"cache-first"` */) { return (fetchPolicy === "network-only" || fetchPolicy === "no-cache" || fetchPolicy === "standby"); } //# sourceMappingURL=ObservableQuery.js.map