apollo-client
Version:
A simple yet functional GraphQL client.
972 lines (961 loc) • 123 kB
JavaScript
(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();