UNPKG

apollo-client

Version:
972 lines (961 loc) 123 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('apollo-link'), require('symbol-observable'), require('apollo-utilities'), require('graphql/language/printer'), require('apollo-link-dedup')) : typeof define === 'function' && define.amd ? define(['exports', 'apollo-link', 'symbol-observable', 'apollo-utilities', 'graphql/language/printer', 'apollo-link-dedup'], factory) : (factory((global.apollo = global.apollo || {}, global.apollo.core = {}),global.apolloLink.core,null,global.apollo.utilities,null,global.apolloLink.dedup)); }(this, (function (exports,apolloLink,$$observable,apolloUtilities,printer,apolloLinkDedup) { 'use strict'; $$observable = $$observable && $$observable.hasOwnProperty('default') ? $$observable['default'] : $$observable; /** * The current status of a query’s execution in our system. */ (function (NetworkStatus) { /** * The query has never been run before and the query is now currently running. A query will still * have this network status even if a partial data result was returned from the cache, but a * query was dispatched anyway. */ NetworkStatus[NetworkStatus["loading"] = 1] = "loading"; /** * If `setVariables` was called and a query was fired because of that then the network status * will be `setVariables` until the result of that query comes back. */ NetworkStatus[NetworkStatus["setVariables"] = 2] = "setVariables"; /** * Indicates that `fetchMore` was called on this query and that the query created is currently in * flight. */ NetworkStatus[NetworkStatus["fetchMore"] = 3] = "fetchMore"; /** * Similar to the `setVariables` network status. It means that `refetch` was called on a query * and the refetch request is currently in flight. */ NetworkStatus[NetworkStatus["refetch"] = 4] = "refetch"; /** * Indicates that a polling query is currently in flight. So for example if you are polling a * query every 10 seconds then the network status will switch to `poll` every 10 seconds whenever * a poll request has been sent but not resolved. */ NetworkStatus[NetworkStatus["poll"] = 6] = "poll"; /** * No request is in flight for this query, and no errors happened. Everything is OK. */ NetworkStatus[NetworkStatus["ready"] = 7] = "ready"; /** * No request is in flight for this query, but one or more errors were detected. */ NetworkStatus[NetworkStatus["error"] = 8] = "error"; })(exports.NetworkStatus || (exports.NetworkStatus = {})); /** * Returns true if there is currently a network request in flight according to a given network * status. */ function isNetworkRequestInFlight(networkStatus) { return networkStatus < 7; } var __extends = (undefined && undefined.__extends) || (function () { var extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); // rxjs interopt var Observable = /** @class */ (function (_super) { __extends(Observable, _super); function Observable() { return _super !== null && _super.apply(this, arguments) || this; } Observable.prototype[$$observable] = function () { return this; }; Observable.prototype['@@observable'] = function () { return this; }; return Observable; }(apolloLink.Observable)); var __extends$1 = (undefined && undefined.__extends) || (function () { var extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); function isApolloError(err) { return err.hasOwnProperty('graphQLErrors'); } // Sets the error message on this error according to the // the GraphQL and network errors that are present. // If the error message has already been set through the // constructor or otherwise, this function is a nop. var generateErrorMessage = function (err) { var message = ''; // If we have GraphQL errors present, add that to the error message. if (Array.isArray(err.graphQLErrors) && err.graphQLErrors.length !== 0) { err.graphQLErrors.forEach(function (graphQLError) { var errorMessage = graphQLError ? graphQLError.message : 'Error message not found.'; message += "GraphQL error: " + errorMessage + "\n"; }); } if (err.networkError) { message += 'Network error: ' + err.networkError.message + '\n'; } // strip newline from the end of the message message = message.replace(/\n$/, ''); return message; }; var ApolloError = /** @class */ (function (_super) { __extends$1(ApolloError, _super); // Constructs an instance of ApolloError given a GraphQLError // or a network error. Note that one of these has to be a valid // value or the constructed error will be meaningless. function ApolloError(_a) { var graphQLErrors = _a.graphQLErrors, networkError = _a.networkError, errorMessage = _a.errorMessage, extraInfo = _a.extraInfo; var _this = _super.call(this, errorMessage) || this; _this.graphQLErrors = graphQLErrors || []; _this.networkError = networkError || null; if (!errorMessage) { _this.message = generateErrorMessage(_this); } else { _this.message = errorMessage; } _this.extraInfo = extraInfo; // We're not using `Object.setPrototypeOf` here as it isn't fully // supported on Android (see issue #3236). _this.__proto__ = ApolloError.prototype; return _this; } return ApolloError; }(Error)); (function (FetchType) { FetchType[FetchType["normal"] = 1] = "normal"; FetchType[FetchType["refetch"] = 2] = "refetch"; FetchType[FetchType["poll"] = 3] = "poll"; })(exports.FetchType || (exports.FetchType = {})); var __extends$2 = (undefined && undefined.__extends) || (function () { var extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __assign = (undefined && undefined.__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 hasError = function (storeValue, policy) { if (policy === void 0) { policy = 'none'; } return storeValue && ((storeValue.graphQLErrors && storeValue.graphQLErrors.length > 0 && policy === 'none') || storeValue.networkError); }; var ObservableQuery = /** @class */ (function (_super) { __extends$2(ObservableQuery, _super); function ObservableQuery(_a) { var scheduler = _a.scheduler, options = _a.options, _b = _a.shouldSubscribe, shouldSubscribe = _b === void 0 ? true : _b; var _this = _super.call(this, function (observer) { return _this.onSubscribe(observer); }) || this; // active state _this.isCurrentlyPolling = false; _this.isTornDown = false; // query information _this.options = options; _this.variables = options.variables || {}; _this.queryId = scheduler.queryManager.generateQueryId(); _this.shouldSubscribe = shouldSubscribe; // related classes _this.scheduler = scheduler; _this.queryManager = scheduler.queryManager; // interal data stores _this.observers = []; _this.subscriptionHandles = []; return _this; } ObservableQuery.prototype.result = function () { var that = this; return new Promise(function (resolve, reject) { var subscription; 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. if (!that.observers.some(function (obs) { return obs !== observer; })) { that.queryManager.removeQuery(that.queryId); } setTimeout(function () { subscription.unsubscribe(); }, 0); }, error: function (error) { reject(error); }, }; subscription = that.subscribe(observer); }); }; /** * Return the result of the query from the local cache as well as some fetching status * `loading` and `networkStatus` allow to know if a request is in flight * `partial` lets you know if the result from the local cache is complete or partial * @return {data: Object, error: ApolloError, loading: boolean, networkStatus: number, partial: boolean} */ ObservableQuery.prototype.currentResult = function () { if (this.isTornDown) { return { data: this.lastError ? {} : this.lastResult ? this.lastResult.data : {}, error: this.lastError, loading: false, networkStatus: exports.NetworkStatus.error, }; } var queryStoreValue = this.queryManager.queryStore.get(this.queryId); if (hasError(queryStoreValue, this.options.errorPolicy)) { return { data: {}, loading: false, networkStatus: queryStoreValue.networkStatus, error: new ApolloError({ graphQLErrors: queryStoreValue.graphQLErrors, networkError: queryStoreValue.networkError, }), }; } var _a = this.queryManager.getCurrentQueryResult(this), data = _a.data, partial = _a.partial; var queryLoading = !queryStoreValue || queryStoreValue.networkStatus === exports.NetworkStatus.loading; // We need to be careful about the loading state we show to the user, to try // and be vaguely in line with what the user would have seen from .subscribe() // but to still provide useful information synchronously when the query // will not end up hitting the server. // See more: https://github.com/apollostack/apollo-client/issues/707 // Basically: is there a query in flight right now (modolo the next tick)? var loading = (this.options.fetchPolicy === 'network-only' && queryLoading) || (partial && this.options.fetchPolicy !== 'cache-only'); // if there is nothing in the query store, it means this query hasn't fired yet or it has been cleaned up. Therefore the // network status is dependent on queryLoading. var networkStatus; if (queryStoreValue) { networkStatus = queryStoreValue.networkStatus; } else { networkStatus = loading ? exports.NetworkStatus.loading : exports.NetworkStatus.ready; } var result = { data: data, loading: isNetworkRequestInFlight(networkStatus), networkStatus: networkStatus, }; if (queryStoreValue && queryStoreValue.graphQLErrors && this.options.errorPolicy === 'all') { result.errors = queryStoreValue.graphQLErrors; } if (!partial) { var stale = false; this.lastResult = __assign({}, result, { stale: stale }); } return __assign({}, result, { partial: partial }); }; // Returns the last result that observer.next was called with. This is not the same as // currentResult! If you're not sure which you need, then you probably need currentResult. ObservableQuery.prototype.getLastResult = function () { return this.lastResult; }; ObservableQuery.prototype.getLastError = function () { return this.lastError; }; ObservableQuery.prototype.resetLastResults = function () { delete this.lastResult; delete this.lastError; this.isTornDown = false; }; ObservableQuery.prototype.refetch = function (variables) { var fetchPolicy = this.options.fetchPolicy; // early return if trying to read from cache during refetch if (fetchPolicy === 'cache-only') { return Promise.reject(new Error('cache-only fetchPolicy option should not be used together with query refetch.')); } if (!apolloUtilities.isEqual(this.variables, variables)) { // update observable variables this.variables = Object.assign({}, this.variables, variables); } if (!apolloUtilities.isEqual(this.options.variables, this.variables)) { // Update the existing options with new variables this.options.variables = Object.assign({}, this.options.variables, this.variables); } // Override fetchPolicy for this call only // only network-only and no-cache are safe to use var isNetworkFetchPolicy = fetchPolicy === 'network-only' || fetchPolicy === 'no-cache'; var combinedOptions = __assign({}, this.options, { fetchPolicy: isNetworkFetchPolicy ? fetchPolicy : 'network-only' }); return this.queryManager .fetchQuery(this.queryId, combinedOptions, exports.FetchType.refetch) .then(function (result) { return apolloUtilities.maybeDeepFreeze(result); }); }; ObservableQuery.prototype.fetchMore = function (fetchMoreOptions) { var _this = this; // early return if no update Query if (!fetchMoreOptions.updateQuery) { throw new Error('updateQuery option is required. This function defines how to update the query data with the new results.'); } var combinedOptions; return Promise.resolve() .then(function () { var qid = _this.queryManager.generateQueryId(); if (fetchMoreOptions.query) { // fetch a new query combinedOptions = fetchMoreOptions; } else { // fetch the same query with a possibly new variables combinedOptions = __assign({}, _this.options, fetchMoreOptions, { variables: Object.assign({}, _this.variables, fetchMoreOptions.variables) }); } combinedOptions.fetchPolicy = 'network-only'; return _this.queryManager.fetchQuery(qid, combinedOptions, exports.FetchType.normal, _this.queryId); }) .then(function (fetchMoreResult) { _this.updateQuery(function (previousResult) { return fetchMoreOptions.updateQuery(previousResult, { fetchMoreResult: fetchMoreResult.data, variables: combinedOptions.variables, }); }); return fetchMoreResult; }); }; // 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. ObservableQuery.prototype.subscribeToMore = function (options) { var _this = this; var subscription = this.queryManager .startGraphQLSubscription({ query: options.document, variables: options.variables, }) .subscribe({ next: function (data) { if (options.updateQuery) { _this.updateQuery(function (previous, _a) { var variables = _a.variables; return options.updateQuery(previous, { subscriptionData: data, variables: variables, }); }); } }, error: function (err) { if (options.onError) { options.onError(err); return; } console.error('Unhandled GraphQL subscription error', err); }, }); this.subscriptionHandles.push(subscription); return function () { var i = _this.subscriptionHandles.indexOf(subscription); if (i >= 0) { _this.subscriptionHandles.splice(i, 1); subscription.unsubscribe(); } }; }; // Note: if the query is not active (there are no subscribers), the promise // will return null immediately. ObservableQuery.prototype.setOptions = function (opts) { var oldOptions = this.options; this.options = Object.assign({}, this.options, opts); if (opts.pollInterval) { this.startPolling(opts.pollInterval); } else if (opts.pollInterval === 0) { this.stopPolling(); } // If fetchPolicy went from cache-only to something else, or from something else to network-only var tryFetch = (oldOptions.fetchPolicy !== 'network-only' && opts.fetchPolicy === 'network-only') || (oldOptions.fetchPolicy === 'cache-only' && opts.fetchPolicy !== 'cache-only') || (oldOptions.fetchPolicy === 'standby' && opts.fetchPolicy !== 'standby') || false; return this.setVariables(this.options.variables, tryFetch, opts.fetchResults); }; /** * Update the variables of this observable query, and fetch the new results * if they've changed. If you want to force new results, use `refetch`. * * Note: if the variables have not changed, the promise will return the old * results immediately, and the `next` callback will *not* fire. * * Note: if the query is not active (there are no subscribers), the promise * will return null immediately. * * @param variables: The new set of variables. If there are missing variables, * the previous values of those variables will be used. * * @param tryFetch: Try and fetch new results even if the variables haven't * changed (we may still just hit the store, but if there's nothing in there * this will refetch) * * @param fetchResults: Option to ignore fetching results when updating variables * */ ObservableQuery.prototype.setVariables = function (variables, tryFetch, fetchResults) { if (tryFetch === void 0) { tryFetch = false; } if (fetchResults === void 0) { fetchResults = true; } // since setVariables restarts the subscription, we reset the tornDown status this.isTornDown = false; var newVariables = variables ? variables : this.variables; if (apolloUtilities.isEqual(newVariables, this.variables) && !tryFetch) { // 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) if (this.observers.length === 0 || !fetchResults) { return new Promise(function (resolve) { return resolve(); }); } return this.result(); } else { this.variables = newVariables; this.options.variables = newVariables; // See comment above if (this.observers.length === 0) { return new Promise(function (resolve) { return resolve(); }); } // Use the same options as before, but with new variables return this.queryManager .fetchQuery(this.queryId, __assign({}, this.options, { variables: this.variables })) .then(function (result) { return apolloUtilities.maybeDeepFreeze(result); }); } }; ObservableQuery.prototype.updateQuery = function (mapFn) { var _a = this.queryManager.getQueryWithPreviousResult(this.queryId), previousResult = _a.previousResult, variables = _a.variables, document = _a.document; var newResult = apolloUtilities.tryFunctionOrLogError(function () { return mapFn(previousResult, { variables: variables }); }); if (newResult) { this.queryManager.dataStore.markUpdateQueryResult(document, variables, newResult); this.queryManager.broadcastQueries(); } }; ObservableQuery.prototype.stopPolling = function () { if (this.isCurrentlyPolling) { this.scheduler.stopPollingQuery(this.queryId); this.options.pollInterval = undefined; this.isCurrentlyPolling = false; } }; ObservableQuery.prototype.startPolling = function (pollInterval) { if (this.options.fetchPolicy === 'cache-first' || this.options.fetchPolicy === 'cache-only') { throw new Error('Queries that specify the cache-first and cache-only fetchPolicies cannot also be polling queries.'); } if (this.isCurrentlyPolling) { this.scheduler.stopPollingQuery(this.queryId); this.isCurrentlyPolling = false; } this.options.pollInterval = pollInterval; this.isCurrentlyPolling = true; this.scheduler.startPollingQuery(this.options, this.queryId); }; ObservableQuery.prototype.onSubscribe = function (observer) { var _this = this; // Zen Observable has its own error function, in order to log correctly // we need to declare a custom error if nothing is passed if (observer._subscription && observer._subscription._observer && !observer._subscription._observer.error) { observer._subscription._observer.error = function (error) { console.error('Unhandled error', error.message, error.stack); }; } this.observers.push(observer); // Deliver initial result if (observer.next && this.lastResult) observer.next(this.lastResult); if (observer.error && this.lastError) observer.error(this.lastError); // setup the query if it hasn't been done before if (this.observers.length === 1) this.setUpQuery(); return function () { _this.observers = _this.observers.filter(function (obs) { return obs !== observer; }); if (_this.observers.length === 0) { _this.tearDownQuery(); } }; }; ObservableQuery.prototype.setUpQuery = function () { var _this = this; if (this.shouldSubscribe) { this.queryManager.addObservableQuery(this.queryId, this); } if (!!this.options.pollInterval) { if (this.options.fetchPolicy === 'cache-first' || this.options.fetchPolicy === 'cache-only') { throw new Error('Queries that specify the cache-first and cache-only fetchPolicies cannot also be polling queries.'); } this.isCurrentlyPolling = true; this.scheduler.startPollingQuery(this.options, this.queryId); } var observer = { next: function (result) { _this.lastResult = result; _this.observers.forEach(function (obs) { return obs.next && obs.next(result); }); }, error: function (error) { _this.lastError = error; _this.observers.forEach(function (obs) { return obs.error && obs.error(error); }); }, }; this.queryManager.startQuery(this.queryId, this.options, this.queryManager.queryListenerForObserver(this.queryId, this.options, observer)); }; ObservableQuery.prototype.tearDownQuery = function () { this.isTornDown = true; if (this.isCurrentlyPolling) { this.scheduler.stopPollingQuery(this.queryId); this.isCurrentlyPolling = false; } // stop all active GraphQL subscriptions this.subscriptionHandles.forEach(function (sub) { return sub.unsubscribe(); }); this.subscriptionHandles = []; this.queryManager.removeObservableQuery(this.queryId); this.queryManager.stopQuery(this.queryId); this.observers = []; }; return ObservableQuery; }(Observable)); // The QueryScheduler is supposed to be a mechanism that schedules polling queries such that // they are clustered into the time slots of the QueryBatcher and are batched together. It // also makes sure that for a given polling query, if one instance of the query is inflight, // another instance will not be fired until the query returns or times out. We do this because // another query fires while one is already in flight, the data will stay in the "loading" state // even after the first query has returned. var __assign$1 = (undefined && undefined.__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 QueryScheduler = /** @class */ (function () { function QueryScheduler(_a) { var queryManager = _a.queryManager, ssrMode = _a.ssrMode; // Map going from queryIds to query options that are in flight. this.inFlightQueries = {}; // Map going from query ids to the query options associated with those queries. Contains all of // the queries, both in flight and not in flight. this.registeredQueries = {}; // Map going from polling interval with to the query ids that fire on that interval. // These query ids are associated with a set of options in the this.registeredQueries. this.intervalQueries = {}; // Map going from polling interval widths to polling timers. this.pollingTimers = {}; this.ssrMode = false; this.queryManager = queryManager; this.ssrMode = ssrMode || false; } QueryScheduler.prototype.checkInFlight = function (queryId) { var query = this.queryManager.queryStore.get(queryId); return (query && query.networkStatus !== exports.NetworkStatus.ready && query.networkStatus !== exports.NetworkStatus.error); }; QueryScheduler.prototype.fetchQuery = function (queryId, options, fetchType) { var _this = this; return new Promise(function (resolve, reject) { _this.queryManager .fetchQuery(queryId, options, fetchType) .then(function (result) { resolve(result); }) .catch(function (error) { reject(error); }); }); }; QueryScheduler.prototype.startPollingQuery = function (options, queryId, listener) { if (!options.pollInterval) { throw new Error('Attempted to start a polling query without a polling interval.'); } // Do not poll in SSR mode if (this.ssrMode) return queryId; this.registeredQueries[queryId] = options; if (listener) { this.queryManager.addQueryListener(queryId, listener); } this.addQueryOnInterval(queryId, options); return queryId; }; QueryScheduler.prototype.stopPollingQuery = function (queryId) { // Remove the query options from one of the registered queries. // The polling function will then take care of not firing it anymore. delete this.registeredQueries[queryId]; }; // Fires the all of the queries on a particular interval. Called on a setInterval. QueryScheduler.prototype.fetchQueriesOnInterval = function (interval) { var _this = this; // XXX this "filter" here is nasty, because it does two things at the same time. // 1. remove queries that have stopped polling // 2. call fetchQueries for queries that are polling and not in flight. // TODO: refactor this to make it cleaner this.intervalQueries[interval] = this.intervalQueries[interval].filter(function (queryId) { // If queryOptions can't be found from registeredQueries or if it has a // different interval, it means that this queryId is no longer registered // and should be removed from the list of queries firing on this interval. // // We don't remove queries from intervalQueries immediately in // stopPollingQuery so that we can keep the timer consistent when queries // are removed and replaced, and to avoid quadratic behavior when stopping // many queries. if (!(_this.registeredQueries.hasOwnProperty(queryId) && _this.registeredQueries[queryId].pollInterval === interval)) { return false; } // Don't fire this instance of the polling query is one of the instances is already in // flight. if (_this.checkInFlight(queryId)) { return true; } var queryOptions = _this.registeredQueries[queryId]; var pollingOptions = __assign$1({}, queryOptions); pollingOptions.fetchPolicy = 'network-only'; // don't let unhandled rejections happen _this.fetchQuery(queryId, pollingOptions, exports.FetchType.poll).catch(function () { }); return true; }); if (this.intervalQueries[interval].length === 0) { clearInterval(this.pollingTimers[interval]); delete this.intervalQueries[interval]; } }; // Adds a query on a particular interval to this.intervalQueries and then fires // that query with all the other queries executing on that interval. Note that the query id // and query options must have been added to this.registeredQueries before this function is called. QueryScheduler.prototype.addQueryOnInterval = function (queryId, queryOptions) { var _this = this; var interval = queryOptions.pollInterval; if (!interval) { throw new Error("A poll interval is required to start polling query with id '" + queryId + "'."); } // If there are other queries on this interval, this query will just fire with those // and we don't need to create a new timer. if (this.intervalQueries.hasOwnProperty(interval.toString()) && this.intervalQueries[interval].length > 0) { this.intervalQueries[interval].push(queryId); } else { this.intervalQueries[interval] = [queryId]; // set up the timer for the function that will handle this interval this.pollingTimers[interval] = setInterval(function () { _this.fetchQueriesOnInterval(interval); }, interval); } }; // Used only for unit testing. QueryScheduler.prototype.registerPollingQuery = function (queryOptions) { if (!queryOptions.pollInterval) { throw new Error('Attempted to register a non-polling query with the scheduler.'); } return new ObservableQuery({ scheduler: this, options: queryOptions, }); }; return QueryScheduler; }()); var MutationStore = /** @class */ (function () { function MutationStore() { this.store = {}; } MutationStore.prototype.getStore = function () { return this.store; }; MutationStore.prototype.get = function (mutationId) { return this.store[mutationId]; }; MutationStore.prototype.initMutation = function (mutationId, mutationString, variables) { this.store[mutationId] = { mutationString: mutationString, variables: variables || {}, loading: true, error: null, }; }; MutationStore.prototype.markMutationError = function (mutationId, error) { var mutation = this.store[mutationId]; if (!mutation) { return; } mutation.loading = false; mutation.error = error; }; MutationStore.prototype.markMutationResult = function (mutationId) { var mutation = this.store[mutationId]; if (!mutation) { return; } mutation.loading = false; mutation.error = null; }; MutationStore.prototype.reset = function () { this.store = {}; }; return MutationStore; }()); var __assign$2 = (undefined && undefined.__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 QueryStore = /** @class */ (function () { function QueryStore() { this.store = {}; } QueryStore.prototype.getStore = function () { return this.store; }; QueryStore.prototype.get = function (queryId) { return this.store[queryId]; }; QueryStore.prototype.initQuery = function (query) { var previousQuery = this.store[query.queryId]; if (previousQuery && previousQuery.document !== query.document && printer.print(previousQuery.document) !== printer.print(query.document)) { // XXX we're throwing an error here to catch bugs where a query gets overwritten by a new one. // we should implement a separate action for refetching so that QUERY_INIT may never overwrite // an existing query (see also: https://github.com/apollostack/apollo-client/issues/732) throw new Error('Internal Error: may not update existing query string in store'); } var isSetVariables = false; var previousVariables = null; if (query.storePreviousVariables && previousQuery && previousQuery.networkStatus !== exports.NetworkStatus.loading // if the previous query was still loading, we don't want to remember it at all. ) { if (!apolloUtilities.isEqual(previousQuery.variables, query.variables)) { isSetVariables = true; previousVariables = previousQuery.variables; } } // TODO break this out into a separate function var networkStatus; if (isSetVariables) { networkStatus = exports.NetworkStatus.setVariables; } else if (query.isPoll) { networkStatus = exports.NetworkStatus.poll; } else if (query.isRefetch) { networkStatus = exports.NetworkStatus.refetch; // TODO: can we determine setVariables here if it's a refetch and the variables have changed? } else { networkStatus = exports.NetworkStatus.loading; } var graphQLErrors = []; if (previousQuery && previousQuery.graphQLErrors) { graphQLErrors = previousQuery.graphQLErrors; } // XXX right now if QUERY_INIT is fired twice, like in a refetch situation, we just overwrite // the store. We probably want a refetch action instead, because I suspect that if you refetch // before the initial fetch is done, you'll get an error. this.store[query.queryId] = { document: query.document, variables: query.variables, previousVariables: previousVariables, networkError: null, graphQLErrors: graphQLErrors, networkStatus: networkStatus, metadata: query.metadata, }; // If the action had a `moreForQueryId` property then we need to set the // network status on that query as well to `fetchMore`. // // We have a complement to this if statement in the query result and query // error action branch, but importantly *not* in the client result branch. // This is because the implementation of `fetchMore` *always* sets // `fetchPolicy` to `network-only` so we would never have a client result. if (typeof query.fetchMoreForQueryId === 'string' && this.store[query.fetchMoreForQueryId]) { this.store[query.fetchMoreForQueryId].networkStatus = exports.NetworkStatus.fetchMore; } }; QueryStore.prototype.markQueryResult = function (queryId, result, fetchMoreForQueryId) { if (!this.store[queryId]) return; this.store[queryId].networkError = null; this.store[queryId].graphQLErrors = result.errors && result.errors.length ? result.errors : []; this.store[queryId].previousVariables = null; this.store[queryId].networkStatus = exports.NetworkStatus.ready; // If we have a `fetchMoreForQueryId` then we need to update the network // status for that query. See the branch for query initialization for more // explanation about this process. if (typeof fetchMoreForQueryId === 'string' && this.store[fetchMoreForQueryId]) { this.store[fetchMoreForQueryId].networkStatus = exports.NetworkStatus.ready; } }; QueryStore.prototype.markQueryError = function (queryId, error, fetchMoreForQueryId) { if (!this.store[queryId]) return; this.store[queryId].networkError = error; this.store[queryId].networkStatus = exports.NetworkStatus.error; // If we have a `fetchMoreForQueryId` then we need to update the network // status for that query. See the branch for query initialization for more // explanation about this process. if (typeof fetchMoreForQueryId === 'string') { this.markQueryResultClient(fetchMoreForQueryId, true); } }; QueryStore.prototype.markQueryResultClient = function (queryId, complete) { if (!this.store[queryId]) return; this.store[queryId].networkError = null; this.store[queryId].previousVariables = null; this.store[queryId].networkStatus = complete ? exports.NetworkStatus.ready : exports.NetworkStatus.loading; }; QueryStore.prototype.stopQuery = function (queryId) { delete this.store[queryId]; }; QueryStore.prototype.reset = function (observableQueryIds) { var _this = this; // keep only the queries with query ids that are associated with observables this.store = Object.keys(this.store) .filter(function (queryId) { return observableQueryIds.indexOf(queryId) > -1; }) .reduce(function (res, key) { // XXX set loading to true so listeners don't trigger unless they want results with partial data res[key] = __assign$2({}, _this.store[key], { networkStatus: exports.NetworkStatus.loading }); return res; }, {}); }; return QueryStore; }()); var __assign$3 = (undefined && undefined.__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 = (undefined && undefined.__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 = (undefined && undefined.__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 }; } }; 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.ApolloLink.from([new apolloLinkDedup.DedupLink(), 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();