@apollo/client
Version:
A fully-featured caching GraphQL client.
922 lines • 60.5 kB
JavaScript
import { __assign, __awaiter, __generator } from "tslib";
import { invariant, newInvariantError } from "../utilities/globals/index.js";
import { equal } from "@wry/equality";
import { execute } from "../link/core/index.js";
import { hasDirectives, isExecutionPatchIncrementalResult, isExecutionPatchResult, removeDirectivesFromDocument, } from "../utilities/index.js";
import { canonicalStringify } from "../cache/index.js";
import { getDefaultValues, getOperationDefinition, getOperationName, hasClientExports, graphQLResultHasError, getGraphQLErrorsFromResult, Observable, asyncMap, isNonEmptyArray, Concast, makeUniqueId, isDocumentNode, isNonNullObject, DocumentTransform, } from "../utilities/index.js";
import { mergeIncrementalData } from "../utilities/common/incrementalResult.js";
import { ApolloError, isApolloError, graphQLResultHasProtocolErrors, } from "../errors/index.js";
import { ObservableQuery, logMissingFieldErrors } from "./ObservableQuery.js";
import { NetworkStatus, isNetworkRequestInFlight } from "./networkStatus.js";
import { QueryInfo, shouldWriteResult, } from "./QueryInfo.js";
import { PROTOCOL_ERRORS_SYMBOL } from "../errors/index.js";
import { print } from "../utilities/index.js";
var hasOwnProperty = Object.prototype.hasOwnProperty;
var IGNORE = Object.create(null);
import { Trie } from "@wry/trie";
import { AutoCleanedWeakCache, cacheSizes } from "../utilities/index.js";
var QueryManager = /** @class */ (function () {
function QueryManager(options) {
var _this = this;
this.clientAwareness = {};
// All the queries that the QueryManager is currently managing (not
// including mutations and subscriptions).
this.queries = new Map();
// Maps from queryId strings to Promise rejection functions for
// currently active queries and fetches.
// Use protected instead of private field so
// @apollo/experimental-nextjs-app-support can access type info.
this.fetchCancelFns = new Map();
this.transformCache = new AutoCleanedWeakCache(cacheSizes["queryManager.getDocumentInfo"] ||
2000 /* defaultCacheSizes["queryManager.getDocumentInfo"] */);
this.queryIdCounter = 1;
this.requestIdCounter = 1;
this.mutationIdCounter = 1;
// Use protected instead of private field so
// @apollo/experimental-nextjs-app-support can access type info.
this.inFlightLinkObservables = new Trie(false);
var defaultDocumentTransform = new DocumentTransform(function (document) { return _this.cache.transformDocument(document); },
// Allow the apollo cache to manage its own transform caches
{ cache: false });
this.cache = options.cache;
this.link = options.link;
this.defaultOptions = options.defaultOptions;
this.queryDeduplication = options.queryDeduplication;
this.clientAwareness = options.clientAwareness;
this.localState = options.localState;
this.ssrMode = options.ssrMode;
this.assumeImmutableResults = options.assumeImmutableResults;
var documentTransform = options.documentTransform;
this.documentTransform =
documentTransform ?
defaultDocumentTransform
.concat(documentTransform)
// The custom document transform may add new fragment spreads or new
// field selections, so we want to give the cache a chance to run
// again. For example, the InMemoryCache adds __typename to field
// selections and fragments from the fragment registry.
.concat(defaultDocumentTransform)
: defaultDocumentTransform;
this.defaultContext = options.defaultContext || Object.create(null);
if ((this.onBroadcast = options.onBroadcast)) {
this.mutationStore = Object.create(null);
}
}
/**
* Call this method to terminate any active query processes, making it safe
* to dispose of this QueryManager instance.
*/
QueryManager.prototype.stop = function () {
var _this = this;
this.queries.forEach(function (_info, queryId) {
_this.stopQueryNoBroadcast(queryId);
});
this.cancelPendingFetches(newInvariantError(26));
};
QueryManager.prototype.cancelPendingFetches = function (error) {
this.fetchCancelFns.forEach(function (cancel) { return cancel(error); });
this.fetchCancelFns.clear();
};
QueryManager.prototype.mutate = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var mutationId, hasClientExports, mutationStoreValue, isOptimistic, self;
var _c, _d;
var mutation = _b.mutation, variables = _b.variables, optimisticResponse = _b.optimisticResponse, updateQueries = _b.updateQueries, _e = _b.refetchQueries, refetchQueries = _e === void 0 ? [] : _e, _f = _b.awaitRefetchQueries, awaitRefetchQueries = _f === void 0 ? false : _f, updateWithProxyFn = _b.update, onQueryUpdated = _b.onQueryUpdated, _g = _b.fetchPolicy, fetchPolicy = _g === void 0 ? ((_c = this.defaultOptions.mutate) === null || _c === void 0 ? void 0 : _c.fetchPolicy) || "network-only" : _g, _h = _b.errorPolicy, errorPolicy = _h === void 0 ? ((_d = this.defaultOptions.mutate) === null || _d === void 0 ? void 0 : _d.errorPolicy) || "none" : _h, keepRootFields = _b.keepRootFields, context = _b.context;
return __generator(this, function (_j) {
switch (_j.label) {
case 0:
invariant(mutation, 27);
invariant(fetchPolicy === "network-only" || fetchPolicy === "no-cache", 28);
mutationId = this.generateMutationId();
mutation = this.cache.transformForLink(this.transform(mutation));
hasClientExports = this.getDocumentInfo(mutation).hasClientExports;
variables = this.getVariables(mutation, variables);
if (!hasClientExports) return [3 /*break*/, 2];
return [4 /*yield*/, this.localState.addExportedVariables(mutation, variables, context)];
case 1:
variables = (_j.sent());
_j.label = 2;
case 2:
mutationStoreValue = this.mutationStore &&
(this.mutationStore[mutationId] = {
mutation: mutation,
variables: variables,
loading: true,
error: null,
});
isOptimistic = optimisticResponse &&
this.markMutationOptimistic(optimisticResponse, {
mutationId: mutationId,
document: mutation,
variables: variables,
fetchPolicy: fetchPolicy,
errorPolicy: errorPolicy,
context: context,
updateQueries: updateQueries,
update: updateWithProxyFn,
keepRootFields: keepRootFields,
});
this.broadcastQueries();
self = this;
return [2 /*return*/, new Promise(function (resolve, reject) {
return asyncMap(self.getObservableFromLink(mutation, __assign(__assign({}, context), { optimisticResponse: isOptimistic ? optimisticResponse : void 0 }), variables, {}, false), function (result) {
if (graphQLResultHasError(result) && errorPolicy === "none") {
throw new ApolloError({
graphQLErrors: getGraphQLErrorsFromResult(result),
});
}
if (mutationStoreValue) {
mutationStoreValue.loading = false;
mutationStoreValue.error = null;
}
var storeResult = __assign({}, result);
if (typeof refetchQueries === "function") {
refetchQueries = refetchQueries(storeResult);
}
if (errorPolicy === "ignore" && graphQLResultHasError(storeResult)) {
delete storeResult.errors;
}
return self.markMutationResult({
mutationId: mutationId,
result: storeResult,
document: mutation,
variables: variables,
fetchPolicy: fetchPolicy,
errorPolicy: errorPolicy,
context: context,
update: updateWithProxyFn,
updateQueries: updateQueries,
awaitRefetchQueries: awaitRefetchQueries,
refetchQueries: refetchQueries,
removeOptimistic: isOptimistic ? mutationId : void 0,
onQueryUpdated: onQueryUpdated,
keepRootFields: keepRootFields,
});
}).subscribe({
next: function (storeResult) {
self.broadcastQueries();
// Since mutations might receive multiple payloads from the
// ApolloLink chain (e.g. when used with @defer),
// we resolve with a SingleExecutionResult or after the final
// ExecutionPatchResult has arrived and we have assembled the
// multipart response into a single result.
if (!("hasNext" in storeResult) || storeResult.hasNext === false) {
resolve(storeResult);
}
},
error: function (err) {
if (mutationStoreValue) {
mutationStoreValue.loading = false;
mutationStoreValue.error = err;
}
if (isOptimistic) {
self.cache.removeOptimistic(mutationId);
}
self.broadcastQueries();
reject(err instanceof ApolloError ? err : (new ApolloError({
networkError: err,
})));
},
});
})];
}
});
});
};
QueryManager.prototype.markMutationResult = function (mutation, cache) {
var _this = this;
if (cache === void 0) { cache = this.cache; }
var result = mutation.result;
var cacheWrites = [];
var skipCache = mutation.fetchPolicy === "no-cache";
if (!skipCache && shouldWriteResult(result, mutation.errorPolicy)) {
if (!isExecutionPatchIncrementalResult(result)) {
cacheWrites.push({
result: result.data,
dataId: "ROOT_MUTATION",
query: mutation.document,
variables: mutation.variables,
});
}
if (isExecutionPatchIncrementalResult(result) &&
isNonEmptyArray(result.incremental)) {
var diff = cache.diff({
id: "ROOT_MUTATION",
// The cache complains if passed a mutation where it expects a
// query, so we transform mutations and subscriptions to queries
// (only once, thanks to this.transformCache).
query: this.getDocumentInfo(mutation.document).asQuery,
variables: mutation.variables,
optimistic: false,
returnPartialData: true,
});
var mergedData = void 0;
if (diff.result) {
mergedData = mergeIncrementalData(diff.result, result);
}
if (typeof mergedData !== "undefined") {
// cast the ExecutionPatchResult to FetchResult here since
// ExecutionPatchResult never has `data` when returned from the server
result.data = mergedData;
cacheWrites.push({
result: mergedData,
dataId: "ROOT_MUTATION",
query: mutation.document,
variables: mutation.variables,
});
}
}
var updateQueries_1 = mutation.updateQueries;
if (updateQueries_1) {
this.queries.forEach(function (_a, queryId) {
var observableQuery = _a.observableQuery;
var queryName = observableQuery && observableQuery.queryName;
if (!queryName || !hasOwnProperty.call(updateQueries_1, queryName)) {
return;
}
var updater = updateQueries_1[queryName];
var _b = _this.queries.get(queryId), document = _b.document, variables = _b.variables;
// Read the current query result from the store.
var _c = cache.diff({
query: document,
variables: variables,
returnPartialData: true,
optimistic: false,
}), currentQueryResult = _c.result, complete = _c.complete;
if (complete && currentQueryResult) {
// Run our reducer using the current query result and the mutation result.
var nextQueryResult = updater(currentQueryResult, {
mutationResult: result,
queryName: (document && getOperationName(document)) || void 0,
queryVariables: variables,
});
// Write the modified result back into the store if we got a new result.
if (nextQueryResult) {
cacheWrites.push({
result: nextQueryResult,
dataId: "ROOT_QUERY",
query: document,
variables: variables,
});
}
}
});
}
}
if (cacheWrites.length > 0 ||
(mutation.refetchQueries || "").length > 0 ||
mutation.update ||
mutation.onQueryUpdated ||
mutation.removeOptimistic) {
var results_1 = [];
this.refetchQueries({
updateCache: function (cache) {
if (!skipCache) {
cacheWrites.forEach(function (write) { return cache.write(write); });
}
// If the mutation has some writes associated with it then we need to
// apply those writes to the store by running this reducer again with
// a write action.
var update = mutation.update;
// Determine whether result is a SingleExecutionResult,
// or the final ExecutionPatchResult.
var isFinalResult = !isExecutionPatchResult(result) ||
(isExecutionPatchIncrementalResult(result) && !result.hasNext);
if (update) {
if (!skipCache) {
// Re-read the ROOT_MUTATION data we just wrote into the cache
// (the first cache.write call in the cacheWrites.forEach loop
// above), so field read functions have a chance to run for
// fields within mutation result objects.
var diff = cache.diff({
id: "ROOT_MUTATION",
// The cache complains if passed a mutation where it expects a
// query, so we transform mutations and subscriptions to queries
// (only once, thanks to this.transformCache).
query: _this.getDocumentInfo(mutation.document).asQuery,
variables: mutation.variables,
optimistic: false,
returnPartialData: true,
});
if (diff.complete) {
result = __assign(__assign({}, result), { data: diff.result });
if ("incremental" in result) {
delete result.incremental;
}
if ("hasNext" in result) {
delete result.hasNext;
}
}
}
// If we've received the whole response,
// either a SingleExecutionResult or the final ExecutionPatchResult,
// call the update function.
if (isFinalResult) {
update(cache, result, {
context: mutation.context,
variables: mutation.variables,
});
}
}
// TODO Do this with cache.evict({ id: 'ROOT_MUTATION' }) but make it
// shallow to allow rolling back optimistic evictions.
if (!skipCache && !mutation.keepRootFields && isFinalResult) {
cache.modify({
id: "ROOT_MUTATION",
fields: function (value, _a) {
var fieldName = _a.fieldName, DELETE = _a.DELETE;
return fieldName === "__typename" ? value : DELETE;
},
});
}
},
include: mutation.refetchQueries,
// Write the final mutation.result to the root layer of the cache.
optimistic: false,
// Remove the corresponding optimistic layer at the same time as we
// write the final non-optimistic result.
removeOptimistic: mutation.removeOptimistic,
// Let the caller of client.mutate optionally determine the refetching
// behavior for watched queries after the mutation.update function runs.
// If no onQueryUpdated function was provided for this mutation, pass
// null instead of undefined to disable the default refetching behavior.
onQueryUpdated: mutation.onQueryUpdated || null,
}).forEach(function (result) { return results_1.push(result); });
if (mutation.awaitRefetchQueries || mutation.onQueryUpdated) {
// Returning a promise here makes the mutation await that promise, so we
// include results in that promise's work if awaitRefetchQueries or an
// onQueryUpdated function was specified.
return Promise.all(results_1).then(function () { return result; });
}
}
return Promise.resolve(result);
};
QueryManager.prototype.markMutationOptimistic = function (optimisticResponse, mutation) {
var _this = this;
var data = typeof optimisticResponse === "function" ?
optimisticResponse(mutation.variables, { IGNORE: IGNORE })
: optimisticResponse;
if (data === IGNORE) {
return false;
}
this.cache.recordOptimisticTransaction(function (cache) {
try {
_this.markMutationResult(__assign(__assign({}, mutation), { result: { data: data } }), cache);
}
catch (error) {
globalThis.__DEV__ !== false && invariant.error(error);
}
}, mutation.mutationId);
return true;
};
QueryManager.prototype.fetchQuery = function (queryId, options, networkStatus) {
return this.fetchConcastWithInfo(queryId, options, networkStatus).concast
.promise;
};
QueryManager.prototype.getQueryStore = function () {
var store = Object.create(null);
this.queries.forEach(function (info, queryId) {
store[queryId] = {
variables: info.variables,
networkStatus: info.networkStatus,
networkError: info.networkError,
graphQLErrors: info.graphQLErrors,
};
});
return store;
};
QueryManager.prototype.resetErrors = function (queryId) {
var queryInfo = this.queries.get(queryId);
if (queryInfo) {
queryInfo.networkError = undefined;
queryInfo.graphQLErrors = [];
}
};
QueryManager.prototype.transform = function (document) {
return this.documentTransform.transformDocument(document);
};
QueryManager.prototype.getDocumentInfo = function (document) {
var transformCache = this.transformCache;
if (!transformCache.has(document)) {
var cacheEntry = {
// TODO These three calls (hasClientExports, shouldForceResolvers, and
// usesNonreactiveDirective) are performing independent full traversals
// of the transformed document. We should consider merging these
// traversals into a single pass in the future, though the work is
// cached after the first time.
hasClientExports: hasClientExports(document),
hasForcedResolvers: this.localState.shouldForceResolvers(document),
hasNonreactiveDirective: hasDirectives(["nonreactive"], document),
clientQuery: this.localState.clientQuery(document),
serverQuery: removeDirectivesFromDocument([
{ name: "client", remove: true },
{ name: "connection" },
{ name: "nonreactive" },
], document),
defaultVars: getDefaultValues(getOperationDefinition(document)),
// Transform any mutation or subscription operations to query operations
// so we can read/write them from/to the cache.
asQuery: __assign(__assign({}, document), { definitions: document.definitions.map(function (def) {
if (def.kind === "OperationDefinition" &&
def.operation !== "query") {
return __assign(__assign({}, def), { operation: "query" });
}
return def;
}) }),
};
transformCache.set(document, cacheEntry);
}
return transformCache.get(document);
};
QueryManager.prototype.getVariables = function (document, variables) {
return __assign(__assign({}, this.getDocumentInfo(document).defaultVars), variables);
};
QueryManager.prototype.watchQuery = function (options) {
var query = this.transform(options.query);
// assign variable default values if supplied
// NOTE: We don't modify options.query here with the transformed query to
// ensure observable.options.query is set to the raw untransformed query.
options = __assign(__assign({}, options), { variables: this.getVariables(query, options.variables) });
if (typeof options.notifyOnNetworkStatusChange === "undefined") {
options.notifyOnNetworkStatusChange = false;
}
var queryInfo = new QueryInfo(this);
var observable = new ObservableQuery({
queryManager: this,
queryInfo: queryInfo,
options: options,
});
observable["lastQuery"] = query;
this.queries.set(observable.queryId, queryInfo);
// We give queryInfo the transformed query to ensure the first cache diff
// uses the transformed query instead of the raw query
queryInfo.init({
document: query,
observableQuery: observable,
variables: observable.variables,
});
return observable;
};
QueryManager.prototype.query = function (options, queryId) {
var _this = this;
if (queryId === void 0) { queryId = this.generateQueryId(); }
invariant(options.query, 29);
invariant(options.query.kind === "Document", 30);
invariant(!options.returnPartialData, 31);
invariant(!options.pollInterval, 32);
return this.fetchQuery(queryId, __assign(__assign({}, options), { query: this.transform(options.query) })).finally(function () { return _this.stopQuery(queryId); });
};
QueryManager.prototype.generateQueryId = function () {
return String(this.queryIdCounter++);
};
QueryManager.prototype.generateRequestId = function () {
return this.requestIdCounter++;
};
QueryManager.prototype.generateMutationId = function () {
return String(this.mutationIdCounter++);
};
QueryManager.prototype.stopQueryInStore = function (queryId) {
this.stopQueryInStoreNoBroadcast(queryId);
this.broadcastQueries();
};
QueryManager.prototype.stopQueryInStoreNoBroadcast = function (queryId) {
var queryInfo = this.queries.get(queryId);
if (queryInfo)
queryInfo.stop();
};
QueryManager.prototype.clearStore = function (options) {
if (options === void 0) { options = {
discardWatches: true,
}; }
// Before we have sent the reset action to the store, we can no longer
// rely on the results returned by in-flight requests since these may
// depend on values that previously existed in the data portion of the
// store. So, we cancel the promises and observers that we have issued
// so far and not yet resolved (in the case of queries).
this.cancelPendingFetches(newInvariantError(33));
this.queries.forEach(function (queryInfo) {
if (queryInfo.observableQuery) {
// Set loading to true so listeners don't trigger unless they want
// results with partial data.
queryInfo.networkStatus = NetworkStatus.loading;
}
else {
queryInfo.stop();
}
});
if (this.mutationStore) {
this.mutationStore = Object.create(null);
}
// begin removing data from the store
return this.cache.reset(options);
};
QueryManager.prototype.getObservableQueries = function (include) {
var _this = this;
if (include === void 0) { include = "active"; }
var queries = new Map();
var queryNamesAndDocs = new Map();
var legacyQueryOptions = new Set();
if (Array.isArray(include)) {
include.forEach(function (desc) {
if (typeof desc === "string") {
queryNamesAndDocs.set(desc, false);
}
else if (isDocumentNode(desc)) {
queryNamesAndDocs.set(_this.transform(desc), false);
}
else if (isNonNullObject(desc) && desc.query) {
legacyQueryOptions.add(desc);
}
});
}
this.queries.forEach(function (_a, queryId) {
var oq = _a.observableQuery, document = _a.document;
if (oq) {
if (include === "all") {
queries.set(queryId, oq);
return;
}
var queryName = oq.queryName, fetchPolicy = oq.options.fetchPolicy;
if (fetchPolicy === "standby" ||
(include === "active" && !oq.hasObservers())) {
return;
}
if (include === "active" ||
(queryName && queryNamesAndDocs.has(queryName)) ||
(document && queryNamesAndDocs.has(document))) {
queries.set(queryId, oq);
if (queryName)
queryNamesAndDocs.set(queryName, true);
if (document)
queryNamesAndDocs.set(document, true);
}
}
});
if (legacyQueryOptions.size) {
legacyQueryOptions.forEach(function (options) {
// We will be issuing a fresh network request for this query, so we
// pre-allocate a new query ID here, using a special prefix to enable
// cleaning up these temporary queries later, after fetching.
var queryId = makeUniqueId("legacyOneTimeQuery");
var queryInfo = _this.getQuery(queryId).init({
document: options.query,
variables: options.variables,
});
var oq = new ObservableQuery({
queryManager: _this,
queryInfo: queryInfo,
options: __assign(__assign({}, options), { fetchPolicy: "network-only" }),
});
invariant(oq.queryId === queryId);
queryInfo.setObservableQuery(oq);
queries.set(queryId, oq);
});
}
if (globalThis.__DEV__ !== false && queryNamesAndDocs.size) {
queryNamesAndDocs.forEach(function (included, nameOrDoc) {
if (!included) {
globalThis.__DEV__ !== false && invariant.warn(typeof nameOrDoc === "string" ? 34 : 35, nameOrDoc);
}
});
}
return queries;
};
QueryManager.prototype.reFetchObservableQueries = function (includeStandby) {
var _this = this;
if (includeStandby === void 0) { includeStandby = false; }
var observableQueryPromises = [];
this.getObservableQueries(includeStandby ? "all" : "active").forEach(function (observableQuery, queryId) {
var fetchPolicy = observableQuery.options.fetchPolicy;
observableQuery.resetLastResults();
if (includeStandby ||
(fetchPolicy !== "standby" && fetchPolicy !== "cache-only")) {
observableQueryPromises.push(observableQuery.refetch());
}
_this.getQuery(queryId).setDiff(null);
});
this.broadcastQueries();
return Promise.all(observableQueryPromises);
};
QueryManager.prototype.setObservableQuery = function (observableQuery) {
this.getQuery(observableQuery.queryId).setObservableQuery(observableQuery);
};
QueryManager.prototype.startGraphQLSubscription = function (_a) {
var _this = this;
var query = _a.query, fetchPolicy = _a.fetchPolicy, _b = _a.errorPolicy, errorPolicy = _b === void 0 ? "none" : _b, variables = _a.variables, _c = _a.context, context = _c === void 0 ? {} : _c, _d = _a.extensions, extensions = _d === void 0 ? {} : _d;
query = this.transform(query);
variables = this.getVariables(query, variables);
var makeObservable = function (variables) {
return _this.getObservableFromLink(query, context, variables, extensions).map(function (result) {
if (fetchPolicy !== "no-cache") {
// the subscription interface should handle not sending us results we no longer subscribe to.
// XXX I don't think we ever send in an object with errors, but we might in the future...
if (shouldWriteResult(result, errorPolicy)) {
_this.cache.write({
query: query,
result: result.data,
dataId: "ROOT_SUBSCRIPTION",
variables: variables,
});
}
_this.broadcastQueries();
}
var hasErrors = graphQLResultHasError(result);
var hasProtocolErrors = graphQLResultHasProtocolErrors(result);
if (hasErrors || hasProtocolErrors) {
var errors = {};
if (hasErrors) {
errors.graphQLErrors = result.errors;
}
if (hasProtocolErrors) {
errors.protocolErrors = result.extensions[PROTOCOL_ERRORS_SYMBOL];
}
// `errorPolicy` is a mechanism for handling GraphQL errors, according
// to our documentation, so we throw protocol errors regardless of the
// set error policy.
if (errorPolicy === "none" || hasProtocolErrors) {
throw new ApolloError(errors);
}
}
if (errorPolicy === "ignore") {
delete result.errors;
}
return result;
});
};
if (this.getDocumentInfo(query).hasClientExports) {
var observablePromise_1 = this.localState
.addExportedVariables(query, variables, context)
.then(makeObservable);
return new Observable(function (observer) {
var sub = null;
observablePromise_1.then(function (observable) { return (sub = observable.subscribe(observer)); }, observer.error);
return function () { return sub && sub.unsubscribe(); };
});
}
return makeObservable(variables);
};
QueryManager.prototype.stopQuery = function (queryId) {
this.stopQueryNoBroadcast(queryId);
this.broadcastQueries();
};
QueryManager.prototype.stopQueryNoBroadcast = function (queryId) {
this.stopQueryInStoreNoBroadcast(queryId);
this.removeQuery(queryId);
};
QueryManager.prototype.removeQuery = function (queryId) {
// teardown all links
// Both `QueryManager.fetchRequest` and `QueryManager.query` create separate promises
// that each add their reject functions to fetchCancelFns.
// A query created with `QueryManager.query()` could trigger a `QueryManager.fetchRequest`.
// The same queryId could have two rejection fns for two promises
this.fetchCancelFns.delete(queryId);
if (this.queries.has(queryId)) {
this.getQuery(queryId).stop();
this.queries.delete(queryId);
}
};
QueryManager.prototype.broadcastQueries = function () {
if (this.onBroadcast)
this.onBroadcast();
this.queries.forEach(function (info) { return info.notify(); });
};
QueryManager.prototype.getLocalState = function () {
return this.localState;
};
QueryManager.prototype.getObservableFromLink = function (query, context, variables, extensions,
// Prefer context.queryDeduplication if specified.
deduplication) {
var _this = this;
var _a;
if (deduplication === void 0) { deduplication = (_a = context === null || context === void 0 ? void 0 : context.queryDeduplication) !== null && _a !== void 0 ? _a : this.queryDeduplication; }
var observable;
var _b = this.getDocumentInfo(query), serverQuery = _b.serverQuery, clientQuery = _b.clientQuery;
if (serverQuery) {
var _c = this, inFlightLinkObservables_1 = _c.inFlightLinkObservables, link = _c.link;
var operation = {
query: serverQuery,
variables: variables,
operationName: getOperationName(serverQuery) || void 0,
context: this.prepareContext(__assign(__assign({}, context), { forceFetch: !deduplication })),
extensions: extensions,
};
context = operation.context;
if (deduplication) {
var printedServerQuery_1 = print(serverQuery);
var varJson_1 = canonicalStringify(variables);
var entry = inFlightLinkObservables_1.lookup(printedServerQuery_1, varJson_1);
observable = entry.observable;
if (!observable) {
var concast = new Concast([
execute(link, operation),
]);
observable = entry.observable = concast;
concast.beforeNext(function () {
inFlightLinkObservables_1.remove(printedServerQuery_1, varJson_1);
});
}
}
else {
observable = new Concast([
execute(link, operation),
]);
}
}
else {
observable = new Concast([Observable.of({ data: {} })]);
context = this.prepareContext(context);
}
if (clientQuery) {
observable = asyncMap(observable, function (result) {
return _this.localState.runResolvers({
document: clientQuery,
remoteResult: result,
context: context,
variables: variables,
});
});
}
return observable;
};
QueryManager.prototype.getResultsFromLink = function (queryInfo, cacheWriteBehavior, options) {
var requestId = (queryInfo.lastRequestId = this.generateRequestId());
// Performing transformForLink here gives this.cache a chance to fill in
// missing fragment definitions (for example) before sending this document
// through the link chain.
var linkDocument = this.cache.transformForLink(options.query);
return asyncMap(this.getObservableFromLink(linkDocument, options.context, options.variables), function (result) {
var graphQLErrors = getGraphQLErrorsFromResult(result);
var hasErrors = graphQLErrors.length > 0;
var errorPolicy = options.errorPolicy;
// If we interrupted this request by calling getResultsFromLink again
// with the same QueryInfo object, we ignore the old results.
if (requestId >= queryInfo.lastRequestId) {
if (hasErrors && errorPolicy === "none") {
// Throwing here effectively calls observer.error.
throw queryInfo.markError(new ApolloError({
graphQLErrors: graphQLErrors,
}));
}
// Use linkDocument rather than queryInfo.document so the
// operation/fragments used to write the result are the same as the
// ones used to obtain it from the link.
queryInfo.markResult(result, linkDocument, options, cacheWriteBehavior);
queryInfo.markReady();
}
var aqr = {
data: result.data,
loading: false,
networkStatus: NetworkStatus.ready,
};
// In the case we start multiple network requests simulatenously, we
// want to ensure we properly set `data` if we're reporting on an old
// result which will not be caught by the conditional above that ends up
// throwing the markError result.
if (hasErrors && errorPolicy === "none") {
aqr.data = void 0;
}
if (hasErrors && errorPolicy !== "ignore") {
aqr.errors = graphQLErrors;
aqr.networkStatus = NetworkStatus.error;
}
return aqr;
}, function (networkError) {
var error = isApolloError(networkError) ? networkError : (new ApolloError({ networkError: networkError }));
// Avoid storing errors from older interrupted queries.
if (requestId >= queryInfo.lastRequestId) {
queryInfo.markError(error);
}
throw error;
});
};
QueryManager.prototype.fetchConcastWithInfo = function (queryId, options,
// The initial networkStatus for this fetch, most often
// NetworkStatus.loading, but also possibly fetchMore, poll, refetch,
// or setVariables.
networkStatus, query) {
var _this = this;
if (networkStatus === void 0) { networkStatus = NetworkStatus.loading; }
if (query === void 0) { query = options.query; }
var variables = this.getVariables(query, options.variables);
var queryInfo = this.getQuery(queryId);
var defaults = this.defaultOptions.watchQuery;
var _a = options.fetchPolicy, fetchPolicy = _a === void 0 ? (defaults && defaults.fetchPolicy) || "cache-first" : _a, _b = options.errorPolicy, errorPolicy = _b === void 0 ? (defaults && defaults.errorPolicy) || "none" : _b, _c = options.returnPartialData, returnPartialData = _c === void 0 ? false : _c, _d = options.notifyOnNetworkStatusChange, notifyOnNetworkStatusChange = _d === void 0 ? false : _d, _e = options.context, context = _e === void 0 ? {} : _e;
var normalized = Object.assign({}, options, {
query: query,
variables: variables,
fetchPolicy: fetchPolicy,
errorPolicy: errorPolicy,
returnPartialData: returnPartialData,
notifyOnNetworkStatusChange: notifyOnNetworkStatusChange,
context: context,
});
var fromVariables = function (variables) {
// Since normalized is always a fresh copy of options, it's safe to
// modify its properties here, rather than creating yet another new
// WatchQueryOptions object.
normalized.variables = variables;
var sourcesWithInfo = _this.fetchQueryByPolicy(queryInfo, normalized, networkStatus);
if (
// If we're in standby, postpone advancing options.fetchPolicy using
// applyNextFetchPolicy.
normalized.fetchPolicy !== "standby" &&
// The "standby" policy currently returns [] from fetchQueryByPolicy, so
// this is another way to detect when nothing was done/fetched.
sourcesWithInfo.sources.length > 0 &&
queryInfo.observableQuery) {
queryInfo.observableQuery["applyNextFetchPolicy"]("after-fetch", options);
}
return sourcesWithInfo;
};
// This cancel function needs to be set before the concast is created,
// in case concast creation synchronously cancels the request.
var cleanupCancelFn = function () { return _this.fetchCancelFns.delete(queryId); };
this.fetchCancelFns.set(queryId, function (reason) {
cleanupCancelFn();
// This delay ensures the concast variable has been initialized.
setTimeout(function () { return concast.cancel(reason); });
});
var concast, containsDataFromLink;
// If the query has @export(as: ...) directives, then we need to
// process those directives asynchronously. When there are no
// @export directives (the common case), we deliberately avoid
// wrapping the result of this.fetchQueryByPolicy in a Promise,
// since the timing of result delivery is (unfortunately) important
// for backwards compatibility. TODO This code could be simpler if
// we deprecated and removed LocalState.
if (this.getDocumentInfo(normalized.query).hasClientExports) {
concast = new Concast(this.localState
.addExportedVariables(normalized.query, normalized.variables, normalized.context)
.then(fromVariables)
.then(function (sourcesWithInfo) { return sourcesWithInfo.sources; }));
// there is just no way we can synchronously get the *right* value here,
// so we will assume `true`, which is the behaviour before the bug fix in
// #10597. This means that bug is not fixed in that case, and is probably
// un-fixable with reasonable effort for the edge case of @export as
// directives.
containsDataFromLink = true;
}
else {
var sourcesWithInfo = fromVariables(normalized.variables);
containsDataFromLink = sourcesWithInfo.fromLink;
concast = new Concast(sourcesWithInfo.sources);
}
concast.promise.then(cleanupCancelFn, cleanupCancelFn);
return {
concast: concast,
fromLink: containsDataFromLink,
};
};
QueryManager.prototype.refetchQueries = function (_a) {
var _this = this;
var updateCache = _a.updateCache, include = _a.include, _b = _a.optimistic, optimistic = _b === void 0 ? false : _b, _c = _a.removeOptimistic, removeOptimistic = _c === void 0 ? optimistic ? makeUniqueId("refetchQueries") : void 0 : _c, onQueryUpdated = _a.onQueryUpdated;
var includedQueriesById = new Map();
if (include) {
this.getObservableQueries(include).forEach(function (oq, queryId) {
includedQueriesById.set(queryId, {
oq: oq,
lastDiff: _this.getQuery(queryId).getDiff(),
});
});
}
var results = new Map();
if (updateCache) {
this.cache.batch({
update: updateCache,
// Since you can perform any combination of cache reads and/or writes in
// the cache.batch update function, its optimistic option can be either
// a boolean or a string, representing three distinct modes of
// operation:
//
// * false: read/write only the root layer
// * true: read/write the topmost layer
// * string: read/write a fresh optimistic layer with that ID string
//
// When typeof optimistic === "string", a new optimistic layer will be
// temporarily created within cache.batch with that string as its ID. If
// we then pass that same string as the removeOptimistic option, we can
// make cache.batch immediately remove the optimistic layer after
// running the updateCache function, triggering only one broadcast.
//
// However, the refetchQueries method accepts only true or false for its
// optimistic option (not string). We interpret true to mean a temporary
// optimistic layer should be created, to allow efficiently rolling back
// the effect of the updateCache function, which involves passing a
// string instead of true as the optimistic option to cache.batch, when
// refetchQueries receives optimistic: true.
//
// In other words, we are deliberately not supporting the use case of
// writing to an *existing* optimistic layer (using the refetchQueries
// updateCache function), since that would potentially interfere with
// other optimistic updates in progress. Instead, you can read/write
// only the root layer by passing optimistic: false to refetchQueries,
// or you can read/write a brand new optimistic layer that will be
// automatically removed by passing optimistic: true.
optimistic: (optimistic && removeOptimistic) || false,
// The removeOptimistic option can also be provided by itself, even if
// optimistic === false, to remove some previously-added optimistic
// layer safely and efficiently, like we do in markMutationResult.
//
// If an explicit removeOptimistic string is provided with optimistic:
// true, the removeOptimistic string will determine the