UNPKG

apollo-client

Version:
974 lines 49.5 kB
var __assign = (this && this.__assign) || Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; import { execute, ApolloLink } from 'apollo-link'; import { print } from 'graphql/language/printer'; import { DedupLink as Deduplicator } from 'apollo-link-dedup'; import { assign, getDefaultValues, getMutationDefinition, getOperationDefinition, getOperationName, getQueryDefinition, isProduction, maybeDeepFreeze, hasDirectives, } from 'apollo-utilities'; import { QueryScheduler } from '../scheduler/scheduler'; import { isApolloError, ApolloError } from '../errors/ApolloError'; import { Observable } from '../util/Observable'; import { MutationStore } from '../data/mutations'; import { QueryStore } from '../data/queries'; import { ObservableQuery } from './ObservableQuery'; import { NetworkStatus, isNetworkRequestInFlight } from './networkStatus'; import { FetchType, } from './types'; import { graphQLResultHasError } from 'apollo-utilities'; var defaultQueryInfo = { listeners: [], invalidated: false, document: null, newData: null, lastRequestId: null, observableQuery: null, subscriptions: [], }; var QueryManager = /** @class */ (function () { function QueryManager(_a) { var link = _a.link, _b = _a.queryDeduplication, queryDeduplication = _b === void 0 ? false : _b, store = _a.store, _c = _a.onBroadcast, onBroadcast = _c === void 0 ? function () { return undefined; } : _c, _d = _a.ssrMode, ssrMode = _d === void 0 ? false : _d; this.mutationStore = new MutationStore(); this.queryStore = new QueryStore(); // let's not start at zero to avoid pain with bad checks this.idCounter = 1; // XXX merge with ObservableQuery but that needs to be expanded to support mutations and // subscriptions as well this.queries = new Map(); // A map going from a requestId to a promise that has not yet been resolved. We use this to keep // track of queries that are inflight and reject them in case some // destabalizing action occurs (e.g. reset of the Apollo store). this.fetchQueryPromises = new Map(); // A map going from the name of a query to an observer issued for it by watchQuery. This is // generally used to refetches for refetchQueries and to update mutation results through // updateQueries. this.queryIdsByName = {}; this.link = link; this.deduplicator = ApolloLink.from([new Deduplicator(), link]); this.queryDeduplication = queryDeduplication; this.dataStore = store; this.onBroadcast = onBroadcast; this.scheduler = new QueryScheduler({ queryManager: this, ssrMode: ssrMode }); } QueryManager.prototype.mutate = function (_a) { var _this = this; var mutation = _a.mutation, variables = _a.variables, optimisticResponse = _a.optimisticResponse, updateQueriesByName = _a.updateQueries, _b = _a.refetchQueries, refetchQueries = _b === void 0 ? [] : _b, _c = _a.awaitRefetchQueries, awaitRefetchQueries = _c === void 0 ? false : _c, updateWithProxyFn = _a.update, _d = _a.errorPolicy, errorPolicy = _d === void 0 ? 'none' : _d, fetchPolicy = _a.fetchPolicy, _e = _a.context, context = _e === void 0 ? {} : _e; if (!mutation) { throw new Error('mutation option is required. You must specify your GraphQL document in the mutation option.'); } if (fetchPolicy && fetchPolicy !== 'no-cache') { throw new Error("fetchPolicy for mutations currently only supports the 'no-cache' policy"); } var mutationId = this.generateQueryId(); var cache = this.dataStore.getCache(); (mutation = cache.transformDocument(mutation)), (variables = assign({}, getDefaultValues(getMutationDefinition(mutation)), variables)); var mutationString = print(mutation); this.setQuery(mutationId, function () { return ({ document: mutation }); }); // Create a map of update queries by id to the query instead of by name. var generateUpdateQueriesInfo = function () { var ret = {}; if (updateQueriesByName) { Object.keys(updateQueriesByName).forEach(function (queryName) { return (_this.queryIdsByName[queryName] || []).forEach(function (queryId) { ret[queryId] = { updater: updateQueriesByName[queryName], query: _this.queryStore.get(queryId), }; }); }); } return ret; }; this.mutationStore.initMutation(mutationId, mutationString, variables); this.dataStore.markMutationInit({ mutationId: mutationId, document: mutation, variables: variables || {}, updateQueries: generateUpdateQueriesInfo(), update: updateWithProxyFn, optimisticResponse: optimisticResponse, }); this.broadcastQueries(); return new Promise(function (resolve, reject) { var storeResult; var error; var operation = _this.buildOperationForLink(mutation, variables, __assign({}, context, { optimisticResponse: optimisticResponse })); var completeMutation = function () { return __awaiter(_this, void 0, void 0, function () { var refetchQueryPromises, _i, refetchQueries_1, refetchQuery, promise; return __generator(this, function (_a) { switch (_a.label) { case 0: if (error) { this.mutationStore.markMutationError(mutationId, error); } this.dataStore.markMutationComplete({ mutationId: mutationId, optimisticResponse: optimisticResponse, }); this.broadcastQueries(); if (error) { throw error; } // allow for conditional refetches // XXX do we want to make this the only API one day? if (typeof refetchQueries === 'function') { refetchQueries = refetchQueries(storeResult); } refetchQueryPromises = []; for (_i = 0, refetchQueries_1 = refetchQueries; _i < refetchQueries_1.length; _i++) { refetchQuery = refetchQueries_1[_i]; if (typeof refetchQuery === 'string') { promise = this.refetchQueryByName(refetchQuery); if (promise) { refetchQueryPromises.push(promise); } continue; } refetchQueryPromises.push(this.query({ query: refetchQuery.query, variables: refetchQuery.variables, fetchPolicy: 'network-only', })); } if (!awaitRefetchQueries) return [3 /*break*/, 2]; return [4 /*yield*/, Promise.all(refetchQueryPromises)]; case 1: _a.sent(); _a.label = 2; case 2: this.setQuery(mutationId, function () { return ({ document: undefined }); }); if (errorPolicy === 'ignore' && storeResult && graphQLResultHasError(storeResult)) { delete storeResult.errors; } return [2 /*return*/, storeResult]; } }); }); }; execute(_this.link, operation).subscribe({ next: function (result) { if (graphQLResultHasError(result) && errorPolicy === 'none') { error = new ApolloError({ graphQLErrors: result.errors, }); return; } _this.mutationStore.markMutationResult(mutationId); if (fetchPolicy !== 'no-cache') { _this.dataStore.markMutationResult({ mutationId: mutationId, result: result, document: mutation, variables: variables || {}, updateQueries: generateUpdateQueriesInfo(), update: updateWithProxyFn, }); } storeResult = result; }, error: function (err) { _this.mutationStore.markMutationError(mutationId, err); _this.dataStore.markMutationComplete({ mutationId: mutationId, optimisticResponse: optimisticResponse, }); _this.broadcastQueries(); _this.setQuery(mutationId, function () { return ({ document: undefined }); }); reject(new ApolloError({ networkError: err, })); }, complete: function () { return completeMutation().then(resolve, reject); }, }); }); }; QueryManager.prototype.fetchQuery = function (queryId, options, fetchType, // This allows us to track if this is a query spawned by a `fetchMore` // call for another query. We need this data to compute the `fetchMore` // network status for the query this is fetching for. fetchMoreForQueryId) { var _this = this; var _a = options.variables, variables = _a === void 0 ? {} : _a, _b = options.metadata, metadata = _b === void 0 ? null : _b, _c = options.fetchPolicy, fetchPolicy = _c === void 0 ? 'cache-first' : _c; var cache = this.dataStore.getCache(); var query = cache.transformDocument(options.query); var storeResult; var needToFetch = fetchPolicy === 'network-only' || fetchPolicy === 'no-cache'; // If this is not a force fetch, we want to diff the query against the // store before we fetch it from the network interface. // TODO we hit the cache even if the policy is network-first. This could be unnecessary if the network is up. if (fetchType !== FetchType.refetch && fetchPolicy !== 'network-only' && fetchPolicy !== 'no-cache') { var _d = this.dataStore.getCache().diff({ query: query, variables: variables, returnPartialData: true, optimistic: false, }), complete = _d.complete, result = _d.result; // If we're in here, only fetch if we have missing fields needToFetch = !complete || fetchPolicy === 'cache-and-network'; storeResult = result; } var shouldFetch = needToFetch && fetchPolicy !== 'cache-only' && fetchPolicy !== 'standby'; // we need to check to see if this is an operation that uses the @live directive if (hasDirectives(['live'], query)) shouldFetch = true; var requestId = this.generateRequestId(); // set up a watcher to listen to cache updates var cancel = this.updateQueryWatch(queryId, query, options); // Initialize query in store with unique requestId this.setQuery(queryId, function () { return ({ document: query, lastRequestId: requestId, invalidated: true, cancel: cancel, }); }); this.invalidate(true, fetchMoreForQueryId); this.queryStore.initQuery({ queryId: queryId, document: query, storePreviousVariables: shouldFetch, variables: variables, isPoll: fetchType === FetchType.poll, isRefetch: fetchType === FetchType.refetch, metadata: metadata, fetchMoreForQueryId: fetchMoreForQueryId, }); this.broadcastQueries(); // If there is no part of the query we need to fetch from the server (or, // fetchPolicy is cache-only), we just write the store result as the final result. var shouldDispatchClientResult = !shouldFetch || fetchPolicy === 'cache-and-network'; if (shouldDispatchClientResult) { this.queryStore.markQueryResultClient(queryId, !shouldFetch); this.invalidate(true, queryId, fetchMoreForQueryId); this.broadcastQueries(); } if (shouldFetch) { var networkResult = this.fetchRequest({ requestId: requestId, queryId: queryId, document: query, options: options, fetchMoreForQueryId: fetchMoreForQueryId, }).catch(function (error) { // This is for the benefit of `refetch` promises, which currently don't get their errors // through the store like watchQuery observers do if (isApolloError(error)) { throw error; } else { var lastRequestId = _this.getQuery(queryId).lastRequestId; if (requestId >= (lastRequestId || 1)) { _this.queryStore.markQueryError(queryId, error, fetchMoreForQueryId); _this.invalidate(true, queryId, fetchMoreForQueryId); _this.broadcastQueries(); } _this.removeFetchQueryPromise(requestId); throw new ApolloError({ networkError: error }); } }); // we don't return the promise for cache-and-network since it is already // returned below from the cache if (fetchPolicy !== 'cache-and-network') { return networkResult; } else { // however we need to catch the error so it isn't unhandled in case of // network error networkResult.catch(function () { }); } } // If we have no query to send to the server, we should return the result // found within the store. return Promise.resolve({ data: storeResult }); }; // Returns a query listener that will update the given observer based on the // results (or lack thereof) for a particular query. QueryManager.prototype.queryListenerForObserver = function (queryId, options, observer) { var _this = this; var previouslyHadError = false; return function (queryStoreValue, newData) { // we're going to take a look at the data, so the query is no longer invalidated _this.invalidate(false, queryId); // The query store value can be undefined in the event of a store // reset. if (!queryStoreValue) return; var observableQuery = _this.getQuery(queryId).observableQuery; var fetchPolicy = observableQuery ? observableQuery.options.fetchPolicy : options.fetchPolicy; // don't watch the store for queries on standby if (fetchPolicy === 'standby') return; var errorPolicy = observableQuery ? observableQuery.options.errorPolicy : options.errorPolicy; var lastResult = observableQuery ? observableQuery.getLastResult() : null; var lastError = observableQuery ? observableQuery.getLastError() : null; var shouldNotifyIfLoading = (!newData && queryStoreValue.previousVariables != null) || fetchPolicy === 'cache-only' || fetchPolicy === 'cache-and-network'; // if this caused by a cache broadcast but the query is still in flight // don't notify the observer // if ( // isCacheBroadcast && // isNetworkRequestInFlight(queryStoreValue.networkStatus) // ) { // shouldNotifyIfLoading = false; // } var networkStatusChanged = Boolean(lastResult && queryStoreValue.networkStatus !== lastResult.networkStatus); var errorStatusChanged = errorPolicy && (lastError && lastError.graphQLErrors) !== queryStoreValue.graphQLErrors && errorPolicy !== 'none'; if (!isNetworkRequestInFlight(queryStoreValue.networkStatus) || (networkStatusChanged && options.notifyOnNetworkStatusChange) || shouldNotifyIfLoading) { // If we have either a GraphQL error or a network error, we create // an error and tell the observer about it. if (((!errorPolicy || errorPolicy === 'none') && queryStoreValue.graphQLErrors && queryStoreValue.graphQLErrors.length > 0) || queryStoreValue.networkError) { var apolloError_1 = new ApolloError({ graphQLErrors: queryStoreValue.graphQLErrors, networkError: queryStoreValue.networkError, }); previouslyHadError = true; if (observer.error) { try { observer.error(apolloError_1); } catch (e) { // Throw error outside this control flow to avoid breaking Apollo's state setTimeout(function () { throw e; }, 0); } } else { // Throw error outside this control flow to avoid breaking Apollo's state setTimeout(function () { throw apolloError_1; }, 0); if (!isProduction()) { /* tslint:disable-next-line */ console.info('An unhandled error was thrown because no error handler is registered ' + 'for the query ' + print(queryStoreValue.document)); } } return; } try { var data = void 0; var isMissing = void 0; if (newData) { // clear out the latest new data, since we're now using it _this.setQuery(queryId, function () { return ({ newData: null }); }); data = newData.result; isMissing = !newData.complete || false; } else { if (lastResult && lastResult.data && !errorStatusChanged) { data = lastResult.data; isMissing = false; } else { var document_1 = _this.getQuery(queryId).document; var readResult = _this.dataStore.getCache().diff({ query: document_1, variables: queryStoreValue.previousVariables || queryStoreValue.variables, optimistic: true, }); data = readResult.result; isMissing = !readResult.complete; } } var resultFromStore = void 0; // If there is some data missing and the user has told us that they // do not tolerate partial data then we want to return the previous // result and mark it as stale. if (isMissing && fetchPolicy !== 'cache-only') { resultFromStore = { data: lastResult && lastResult.data, loading: isNetworkRequestInFlight(queryStoreValue.networkStatus), networkStatus: queryStoreValue.networkStatus, stale: true, }; } else { resultFromStore = { data: data, loading: isNetworkRequestInFlight(queryStoreValue.networkStatus), networkStatus: queryStoreValue.networkStatus, stale: false, }; } // if the query wants updates on errors we need to add it to the result if (errorPolicy === 'all' && queryStoreValue.graphQLErrors && queryStoreValue.graphQLErrors.length > 0) { resultFromStore.errors = queryStoreValue.graphQLErrors; } if (observer.next) { var isDifferentResult = !(lastResult && resultFromStore && lastResult.networkStatus === resultFromStore.networkStatus && lastResult.stale === resultFromStore.stale && // We can do a strict equality check here because we include a `previousResult` // with `readQueryFromStore`. So if the results are the same they will be // referentially equal. lastResult.data === resultFromStore.data); if (isDifferentResult || previouslyHadError) { try { observer.next(maybeDeepFreeze(resultFromStore)); } catch (e) { // Throw error outside this control flow to avoid breaking Apollo's state setTimeout(function () { throw e; }, 0); } } } previouslyHadError = false; } catch (error) { previouslyHadError = true; if (observer.error) observer.error(new ApolloError({ networkError: error })); return; } } }; }; // The shouldSubscribe option is a temporary fix that tells us whether watchQuery was called // directly (i.e. through ApolloClient) or through the query method within QueryManager. // Currently, the query method uses watchQuery in order to handle non-network errors correctly // but we don't want to keep track observables issued for the query method since those aren't // supposed to be refetched in the event of a store reset. Once we unify error handling for // network errors and non-network errors, the shouldSubscribe option will go away. QueryManager.prototype.watchQuery = function (options, shouldSubscribe) { if (shouldSubscribe === void 0) { shouldSubscribe = true; } if (options.fetchPolicy === 'standby') { throw new Error('client.watchQuery cannot be called with fetchPolicy set to "standby"'); } // get errors synchronously var queryDefinition = getQueryDefinition(options.query); // assign variable default values if supplied if (queryDefinition.variableDefinitions && queryDefinition.variableDefinitions.length) { var defaultValues = getDefaultValues(queryDefinition); options.variables = assign({}, defaultValues, options.variables); } if (typeof options.notifyOnNetworkStatusChange === 'undefined') { options.notifyOnNetworkStatusChange = false; } var transformedOptions = __assign({}, options); return new ObservableQuery({ scheduler: this.scheduler, options: transformedOptions, shouldSubscribe: shouldSubscribe, }); }; QueryManager.prototype.query = function (options) { var _this = this; if (!options.query) { throw new Error('query option is required. You must specify your GraphQL document ' + 'in the query option.'); } if (options.query.kind !== 'Document') { throw new Error('You must wrap the query string in a "gql" tag.'); } if (options.returnPartialData) { throw new Error('returnPartialData option only supported on watchQuery.'); } if (options.pollInterval) { throw new Error('pollInterval option only supported on watchQuery.'); } var requestId = this.idCounter; return new Promise(function (resolve, reject) { _this.addFetchQueryPromise(requestId, resolve, reject); return _this.watchQuery(options, false) .result() .then(function (result) { _this.removeFetchQueryPromise(requestId); resolve(result); }) .catch(function (error) { _this.removeFetchQueryPromise(requestId); reject(error); }); }); }; QueryManager.prototype.generateQueryId = function () { var queryId = this.idCounter.toString(); this.idCounter++; return queryId; }; QueryManager.prototype.stopQueryInStore = function (queryId) { this.queryStore.stopQuery(queryId); this.invalidate(true, queryId); this.broadcastQueries(); }; QueryManager.prototype.addQueryListener = function (queryId, listener) { this.setQuery(queryId, function (_a) { var _b = _a.listeners, listeners = _b === void 0 ? [] : _b; return ({ listeners: listeners.concat([listener]), invalidate: false, }); }); }; QueryManager.prototype.updateQueryWatch = function (queryId, document, options) { var _this = this; var cancel = this.getQuery(queryId).cancel; if (cancel) cancel(); var previousResult = function () { var previousResult = null; var observableQuery = _this.getQuery(queryId).observableQuery; if (observableQuery) { var lastResult = observableQuery.getLastResult(); if (lastResult) { previousResult = lastResult.data; } } return previousResult; }; return this.dataStore.getCache().watch({ query: document, variables: options.variables, optimistic: true, previousResult: previousResult, callback: function (newData) { _this.setQuery(queryId, function () { return ({ invalidated: true, newData: newData }); }); }, }); }; // Adds a promise to this.fetchQueryPromises for a given request ID. QueryManager.prototype.addFetchQueryPromise = function (requestId, resolve, reject) { this.fetchQueryPromises.set(requestId.toString(), { resolve: resolve, reject: reject, }); }; // Removes the promise in this.fetchQueryPromises for a particular request ID. QueryManager.prototype.removeFetchQueryPromise = function (requestId) { this.fetchQueryPromises.delete(requestId.toString()); }; // Adds an ObservableQuery to this.observableQueries and to this.observableQueriesByName. QueryManager.prototype.addObservableQuery = function (queryId, observableQuery) { this.setQuery(queryId, function () { return ({ observableQuery: observableQuery }); }); // Insert the ObservableQuery into this.observableQueriesByName if the query has a name var queryDef = getQueryDefinition(observableQuery.options.query); if (queryDef.name && queryDef.name.value) { var queryName = queryDef.name.value; // XXX we may we want to warn the user about query name conflicts in the future this.queryIdsByName[queryName] = this.queryIdsByName[queryName] || []; this.queryIdsByName[queryName].push(observableQuery.queryId); } }; QueryManager.prototype.removeObservableQuery = function (queryId) { var _a = this.getQuery(queryId), observableQuery = _a.observableQuery, cancel = _a.cancel; if (cancel) cancel(); if (!observableQuery) return; var definition = getQueryDefinition(observableQuery.options.query); var queryName = definition.name ? definition.name.value : null; this.setQuery(queryId, function () { return ({ observableQuery: null }); }); if (queryName) { this.queryIdsByName[queryName] = this.queryIdsByName[queryName].filter(function (val) { return !(observableQuery.queryId === val); }); } }; QueryManager.prototype.clearStore = function () { // 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.fetchQueryPromises.forEach(function (_a) { var reject = _a.reject; reject(new Error('Store reset while query was in flight(not completed in link chain)')); }); var resetIds = []; this.queries.forEach(function (_a, queryId) { var observableQuery = _a.observableQuery; if (observableQuery) resetIds.push(queryId); }); this.queryStore.reset(resetIds); this.mutationStore.reset(); // begin removing data from the store var reset = this.dataStore.reset(); return reset; }; QueryManager.prototype.resetStore = function () { var _this = this; // Similarly, we have to have to refetch each of the queries currently being // observed. We refetch instead of error'ing on these since the assumption is that // resetting the store doesn't eliminate the need for the queries currently being // watched. If there is an existing query in flight when the store is reset, // the promise for it will be rejected and its results will not be written to the // store. return this.clearStore().then(function () { return _this.reFetchObservableQueries(); }); }; QueryManager.prototype.getObservableQueryPromises = function (includeStandby) { var _this = this; var observableQueryPromises = []; this.queries.forEach(function (_a, queryId) { var observableQuery = _a.observableQuery; if (!observableQuery) return; var fetchPolicy = observableQuery.options.fetchPolicy; observableQuery.resetLastResults(); if (fetchPolicy !== 'cache-only' && (includeStandby || fetchPolicy !== 'standby')) { observableQueryPromises.push(observableQuery.refetch()); } _this.setQuery(queryId, function () { return ({ newData: null }); }); _this.invalidate(true, queryId); }); return observableQueryPromises; }; QueryManager.prototype.reFetchObservableQueries = function (includeStandby) { var observableQueryPromises = this.getObservableQueryPromises(includeStandby); this.broadcastQueries(); return Promise.all(observableQueryPromises); }; QueryManager.prototype.startQuery = function (queryId, options, listener) { this.addQueryListener(queryId, listener); this.fetchQuery(queryId, options) // `fetchQuery` returns a Promise. In case of a failure it should be caucht or else the // console will show an `Uncaught (in promise)` message. Ignore the error for now. .catch(function () { return undefined; }); return queryId; }; QueryManager.prototype.startGraphQLSubscription = function (options) { var _this = this; var query = options.query; var cache = this.dataStore.getCache(); var transformedDoc = cache.transformDocument(query); var variables = assign({}, getDefaultValues(getOperationDefinition(query)), options.variables); var sub; var observers = []; return new Observable(function (observer) { observers.push(observer); // If this is the first observer, actually initiate the network subscription if (observers.length === 1) { var handler = { next: function (result) { _this.dataStore.markSubscriptionResult(result, transformedDoc, variables); _this.broadcastQueries(); // It's slightly awkward that the data for subscriptions doesn't come from the store. observers.forEach(function (obs) { // XXX I'd prefer a different way to handle errors for subscriptions if (obs.next) obs.next(result); }); }, error: function (error) { observers.forEach(function (obs) { if (obs.error) obs.error(error); }); }, }; // TODO: Should subscriptions also accept a `context` option to pass // through to links? var operation = _this.buildOperationForLink(transformedDoc, variables); sub = execute(_this.link, operation).subscribe(handler); } return function () { observers = observers.filter(function (obs) { return obs !== observer; }); // If we removed the last observer, tear down the network subscription if (observers.length === 0 && sub) { sub.unsubscribe(); } }; }); }; QueryManager.prototype.stopQuery = function (queryId) { this.stopQueryInStore(queryId); this.removeQuery(queryId); }; QueryManager.prototype.removeQuery = function (queryId) { var subscriptions = this.getQuery(queryId).subscriptions; // teardown all links subscriptions.forEach(function (x) { return x.unsubscribe(); }); this.queries.delete(queryId); }; QueryManager.prototype.getCurrentQueryResult = function (observableQuery, optimistic) { if (optimistic === void 0) { optimistic = true; } var _a = observableQuery.options, variables = _a.variables, query = _a.query; var lastResult = observableQuery.getLastResult(); var newData = this.getQuery(observableQuery.queryId).newData; // XXX test this if (newData) { return maybeDeepFreeze({ data: newData.result, partial: false }); } else { try { // the query is brand new, so we read from the store to see if anything is there var data = this.dataStore.getCache().read({ query: query, variables: variables, previousResult: lastResult ? lastResult.data : undefined, optimistic: optimistic, }); return maybeDeepFreeze({ data: data, partial: false }); } catch (e) { return maybeDeepFreeze({ data: {}, partial: true }); } } }; QueryManager.prototype.getQueryWithPreviousResult = function (queryIdOrObservable) { var observableQuery; if (typeof queryIdOrObservable === 'string') { var foundObserveableQuery = this.getQuery(queryIdOrObservable).observableQuery; if (!foundObserveableQuery) { throw new Error("ObservableQuery with this id doesn't exist: " + queryIdOrObservable); } observableQuery = foundObserveableQuery; } else { observableQuery = queryIdOrObservable; } var _a = observableQuery.options, variables = _a.variables, query = _a.query; var data = this.getCurrentQueryResult(observableQuery, false).data; return { previousResult: data, variables: variables, document: query, }; }; QueryManager.prototype.broadcastQueries = function () { var _this = this; this.onBroadcast(); this.queries.forEach(function (info, id) { if (!info.invalidated || !info.listeners) return; info.listeners // it's possible for the listener to be undefined if the query is being stopped // See here for more detail: https://github.com/apollostack/apollo-client/issues/231 .filter(function (x) { return !!x; }) .forEach(function (listener) { listener(_this.queryStore.get(id), info.newData); }); }); }; // Takes a request id, query id, a query document and information associated with the query // and send it to the network interface. Returns // a promise for the result associated with that request. QueryManager.prototype.fetchRequest = function (_a) { var _this = this; var requestId = _a.requestId, queryId = _a.queryId, document = _a.document, options = _a.options, fetchMoreForQueryId = _a.fetchMoreForQueryId; var variables = options.variables, context = options.context, _b = options.errorPolicy, errorPolicy = _b === void 0 ? 'none' : _b, fetchPolicy = options.fetchPolicy; var operation = this.buildOperationForLink(document, variables, __assign({}, context, { // TODO: Should this be included for all entry points via // buildOperationForLink? forceFetch: !this.queryDeduplication })); var resultFromStore; var errorsFromStore; return new Promise(function (resolve, reject) { _this.addFetchQueryPromise(requestId, resolve, reject); var subscription = execute(_this.deduplicator, operation).subscribe({ next: function (result) { // default the lastRequestId to 1 var lastRequestId = _this.getQuery(queryId).lastRequestId; if (requestId >= (lastRequestId || 1)) { if (fetchPolicy !== 'no-cache') { try { _this.dataStore.markQueryResult(result, document, variables, fetchMoreForQueryId, errorPolicy === 'ignore' || errorPolicy === 'all'); } catch (e) { reject(e); return; } } else { _this.setQuery(queryId, function () { return ({ newData: { result: result.data, complete: true }, }); }); } _this.queryStore.markQueryResult(queryId, result, fetchMoreForQueryId); _this.invalidate(true, queryId, fetchMoreForQueryId); _this.broadcastQueries(); } if (result.errors && errorPolicy === 'none') { reject(new ApolloError({ graphQLErrors: result.errors, })); return; } else if (errorPolicy === 'all') { errorsFromStore = result.errors; } if (fetchMoreForQueryId || fetchPolicy === 'no-cache') { // We don't write fetchMore results to the store because this would overwrite // the original result in case an @connection directive is used. resultFromStore = result.data; } else { try { // ensure result is combined with data already in store resultFromStore = _this.dataStore.getCache().read({ variables: variables, query: document, optimistic: false, }); // this will throw an error if there are missing fields in // the results which can happen with errors from the server. // tslint:disable-next-line } catch (e) { } } }, error: function (error) { _this.removeFetchQueryPromise(requestId); _this.setQuery(queryId, function (_a) { var subscriptions = _a.subscriptions; return ({ subscriptions: subscriptions.filter(function (x) { return x !== subscription; }), }); }); reject(error); }, complete: function () { _this.removeFetchQueryPromise(requestId); _this.setQuery(queryId, function (_a) { var subscriptions = _a.subscriptions; return ({ subscriptions: subscriptions.filter(function (x) { return x !== subscription; }), }); }); resolve({ data: resultFromStore, errors: errorsFromStore, loading: false, networkStatus: NetworkStatus.ready, stale: false, }); }, }); _this.setQuery(queryId, function (_a) { var subscriptions = _a.subscriptions; return ({ subscriptions: subscriptions.concat([subscription]), }); }); }); }; // Refetches a query given that query's name. Refetches // all ObservableQuery instances associated with the query name. QueryManager.prototype.refetchQueryByName = function (queryName) { var _this = this; var refetchedQueries = this.queryIdsByName[queryName]; // early return if the query named does not exist (not yet fetched) // this used to warn but it may be inteneded behavoir to try and refetch // un called queries because they could be on different routes if (refetchedQueries === undefined) return; return Promise.all(refetchedQueries .map(function (id) { return _this.getQuery(id).observableQuery; }) .filter(function (x) { return !!x; }) .map(function (x) { return x.refetch(); })); }; QueryManager.prototype.generateRequestId = function () { var requestId = this.idCounter; this.idCounter++; return requestId; }; QueryManager.prototype.getQuery = function (queryId) { return this.queries.get(queryId) || __assign({}, defaultQueryInfo); }; QueryManager.prototype.setQuery = function (queryId, updater) { var prev = this.getQuery(queryId); var newInfo = __assign({}, prev, updater(prev)); this.queries.set(queryId, newInfo); }; QueryManager.prototype.invalidate = function (invalidated, queryId, fetchMoreForQueryId) { if (queryId) this.setQuery(queryId, function () { return ({ invalidated: invalidated }); }); if (fetchMoreForQueryId) { this.setQuery(fetchMoreForQueryId, function () { return ({ invalidated: invalidated }); }); } }; QueryManager.prototype.buildOperationForLink = function (document, variables, extraContext) { var cache = this.dataStore.getCache(); return { query: cache.transformForLink ? cache.transformForLink(document) : document, variables: variables, operationName: getOperationName(document) || undefined, context: __assign({}, extraContext, { cache: cache, // getting an entry's cache key is useful for cacheResolvers & state-link getCacheKey: function (obj) { if (cache.config) { // on the link, we just want the id string, not the full id value from toIdValue return cache.config.dataIdFromObject(obj); } else { throw new Error('To use context.getCacheKey, you need to use a cache that has a configurable dataIdFromObject, like apollo-cache-inmemory.'); } } }), }; }; return QueryManager; }()); export { QueryManager }; //# sourceMappingURL=QueryManager.js.map