1 | import { __assign, __extends } from "tslib";
|
2 | import { invariant } from "../utilities/globals/index.js";
|
3 | import { equal } from "@wry/equality";
|
4 | import { NetworkStatus, isNetworkRequestInFlight } from "./networkStatus.js";
|
5 | import { cloneDeep, compact, getOperationDefinition, Observable, iterateObserversSafely, fixObservableSubclass, getQueryDefinition, } from "../utilities/index.js";
|
6 | import { ApolloError, isApolloError } from "../errors/index.js";
|
7 | import { equalByQuery } from "./equalByQuery.js";
|
8 | var assign = Object.assign, hasOwnProperty = Object.hasOwnProperty;
|
9 | var ObservableQuery = /** @class */ (function (_super) {
|
10 | __extends(ObservableQuery, _super);
|
11 | function ObservableQuery(_a) {
|
12 | var queryManager = _a.queryManager, queryInfo = _a.queryInfo, options = _a.options;
|
13 | var _this = _super.call(this, function (observer) {
|
14 | // Zen Observable has its own error function, so in order to log correctly
|
15 | // we need to provide a custom error callback.
|
16 | try {
|
17 | var subObserver = observer._subscription._observer;
|
18 | if (subObserver && !subObserver.error) {
|
19 | subObserver.error = defaultSubscriptionObserverErrorCallback;
|
20 | }
|
21 | }
|
22 | catch (_a) { }
|
23 | var first = !_this.observers.size;
|
24 | _this.observers.add(observer);
|
25 | // Deliver most recent error or result.
|
26 | var last = _this.last;
|
27 | if (last && last.error) {
|
28 | observer.error && observer.error(last.error);
|
29 | }
|
30 | else if (last && last.result) {
|
31 | observer.next && observer.next(last.result);
|
32 | }
|
33 | // Initiate observation of this query if it hasn't been reported to
|
34 | // the QueryManager yet.
|
35 | if (first) {
|
36 | // Blindly catching here prevents unhandled promise rejections,
|
37 | // and is safe because the ObservableQuery handles this error with
|
38 | // this.observer.error, so we're not just swallowing the error by
|
39 | // ignoring it here.
|
40 | _this.reobserve().catch(function () { });
|
41 | }
|
42 | return function () {
|
43 | if (_this.observers.delete(observer) && !_this.observers.size) {
|
44 | _this.tearDownQuery();
|
45 | }
|
46 | };
|
47 | }) || this;
|
48 | _this.observers = new Set();
|
49 | _this.subscriptions = new Set();
|
50 | // related classes
|
51 | _this.queryInfo = queryInfo;
|
52 | _this.queryManager = queryManager;
|
53 | // active state
|
54 | _this.waitForOwnResult = skipCacheDataFor(options.fetchPolicy);
|
55 | _this.isTornDown = false;
|
56 | _this.subscribeToMore = _this.subscribeToMore.bind(_this);
|
57 | var _b = queryManager.defaultOptions.watchQuery, _c = _b === void 0 ? {} : _b, _d = _c.fetchPolicy, defaultFetchPolicy = _d === void 0 ? "cache-first" : _d;
|
58 | var _e = options.fetchPolicy, fetchPolicy = _e === void 0 ? defaultFetchPolicy : _e,
|
59 | // Make sure we don't store "standby" as the initialFetchPolicy.
|
60 | _f = options.initialFetchPolicy,
|
61 | // Make sure we don't store "standby" as the initialFetchPolicy.
|
62 | initialFetchPolicy = _f === void 0 ? fetchPolicy === "standby" ? defaultFetchPolicy : (fetchPolicy) : _f;
|
63 | _this.options = __assign(__assign({}, options), {
|
64 | // Remember the initial options.fetchPolicy so we can revert back to this
|
65 | // policy when variables change. This information can also be specified
|
66 | // (or overridden) by providing options.initialFetchPolicy explicitly.
|
67 | initialFetchPolicy: initialFetchPolicy,
|
68 | // This ensures this.options.fetchPolicy always has a string value, in
|
69 | // case options.fetchPolicy was not provided.
|
70 | fetchPolicy: fetchPolicy });
|
71 | _this.queryId = queryInfo.queryId || queryManager.generateQueryId();
|
72 | var opDef = getOperationDefinition(_this.query);
|
73 | _this.queryName = opDef && opDef.name && opDef.name.value;
|
74 | return _this;
|
75 | }
|
76 | Object.defineProperty(ObservableQuery.prototype, "query", {
|
77 | // The `query` computed property will always reflect the document transformed
|
78 | // by the last run query. `this.options.query` will always reflect the raw
|
79 | // untransformed query to ensure document transforms with runtime conditionals
|
80 | // are run on the original document.
|
81 | get: function () {
|
82 | return this.lastQuery || this.options.query;
|
83 | },
|
84 | enumerable: false,
|
85 | configurable: true
|
86 | });
|
87 | Object.defineProperty(ObservableQuery.prototype, "variables", {
|
88 | // Computed shorthand for this.options.variables, preserved for
|
89 | // backwards compatibility.
|
90 | /**
|
91 | * An object containing the variables that were provided for the query.
|
92 | */
|
93 | get: function () {
|
94 | return this.options.variables;
|
95 | },
|
96 | enumerable: false,
|
97 | configurable: true
|
98 | });
|
99 | ObservableQuery.prototype.result = function () {
|
100 | var _this = this;
|
101 | return new Promise(function (resolve, reject) {
|
102 | // TODO: this code doesn’t actually make sense insofar as the observer
|
103 | // will never exist in this.observers due how zen-observable wraps observables.
|
104 | // https://github.com/zenparsing/zen-observable/blob/master/src/Observable.js#L169
|
105 | var observer = {
|
106 | next: function (result) {
|
107 | resolve(result);
|
108 | // Stop the query within the QueryManager if we can before
|
109 | // this function returns.
|
110 | //
|
111 | // We do this in order to prevent observers piling up within
|
112 | // the QueryManager. Notice that we only fully unsubscribe
|
113 | // from the subscription in a setTimeout(..., 0) call. This call can
|
114 | // actually be handled by the browser at a much later time. If queries
|
115 | // are fired in the meantime, observers that should have been removed
|
116 | // from the QueryManager will continue to fire, causing an unnecessary
|
117 | // performance hit.
|
118 | _this.observers.delete(observer);
|
119 | if (!_this.observers.size) {
|
120 | _this.queryManager.removeQuery(_this.queryId);
|
121 | }
|
122 | setTimeout(function () {
|
123 | subscription.unsubscribe();
|
124 | }, 0);
|
125 | },
|
126 | error: reject,
|
127 | };
|
128 | var subscription = _this.subscribe(observer);
|
129 | });
|
130 | };
|
131 | /** @internal */
|
132 | ObservableQuery.prototype.resetDiff = function () {
|
133 | this.queryInfo.resetDiff();
|
134 | };
|
135 | ObservableQuery.prototype.getCurrentResult = function (saveAsLastResult) {
|
136 | if (saveAsLastResult === void 0) { saveAsLastResult = true; }
|
137 | // Use the last result as long as the variables match this.variables.
|
138 | var lastResult = this.getLastResult(true);
|
139 | var networkStatus = this.queryInfo.networkStatus ||
|
140 | (lastResult && lastResult.networkStatus) ||
|
141 | NetworkStatus.ready;
|
142 | var result = __assign(__assign({}, lastResult), { loading: isNetworkRequestInFlight(networkStatus), networkStatus: networkStatus });
|
143 | var _a = this.options.fetchPolicy, fetchPolicy = _a === void 0 ? "cache-first" : _a;
|
144 | if (
|
145 | // These fetch policies should never deliver data from the cache, unless
|
146 | // redelivering a previously delivered result.
|
147 | skipCacheDataFor(fetchPolicy) ||
|
148 | // If this.options.query has @client(always: true) fields, we cannot
|
149 | // trust diff.result, since it was read from the cache without running
|
150 | // local resolvers (and it's too late to run resolvers now, since we must
|
151 | // return a result synchronously).
|
152 | this.queryManager.getDocumentInfo(this.query).hasForcedResolvers) {
|
153 | // Fall through.
|
154 | }
|
155 | else if (this.waitForOwnResult) {
|
156 | // This would usually be a part of `QueryInfo.getDiff()`.
|
157 | // which we skip in the waitForOwnResult case since we are not
|
158 | // interested in the diff.
|
159 | this.queryInfo["updateWatch"]();
|
160 | }
|
161 | else {
|
162 | var diff = this.queryInfo.getDiff();
|
163 | if (diff.complete || this.options.returnPartialData) {
|
164 | result.data = diff.result;
|
165 | }
|
166 | if (equal(result.data, {})) {
|
167 | result.data = void 0;
|
168 | }
|
169 | if (diff.complete) {
|
170 | // Similar to setting result.partial to false, but taking advantage of the
|
171 | // falsiness of missing fields.
|
172 | delete result.partial;
|
173 | // If the diff is complete, and we're using a FetchPolicy that
|
174 | // terminates after a complete cache read, we can assume the next result
|
175 | // we receive will have NetworkStatus.ready and !loading.
|
176 | if (diff.complete &&
|
177 | result.networkStatus === NetworkStatus.loading &&
|
178 | (fetchPolicy === "cache-first" || fetchPolicy === "cache-only")) {
|
179 | result.networkStatus = NetworkStatus.ready;
|
180 | result.loading = false;
|
181 | }
|
182 | }
|
183 | else {
|
184 | result.partial = true;
|
185 | }
|
186 | if (globalThis.__DEV__ !== false &&
|
187 | !diff.complete &&
|
188 | !this.options.partialRefetch &&
|
189 | !result.loading &&
|
190 | !result.data &&
|
191 | !result.error) {
|
192 | logMissingFieldErrors(diff.missing);
|
193 | }
|
194 | }
|
195 | if (saveAsLastResult) {
|
196 | this.updateLastResult(result);
|
197 | }
|
198 | return result;
|
199 | };
|
200 | // Compares newResult to the snapshot we took of this.lastResult when it was
|
201 | // first received.
|
202 | ObservableQuery.prototype.isDifferentFromLastResult = function (newResult, variables) {
|
203 | if (!this.last) {
|
204 | return true;
|
205 | }
|
206 | var resultIsDifferent = this.queryManager.getDocumentInfo(this.query).hasNonreactiveDirective ?
|
207 | !equalByQuery(this.query, this.last.result, newResult, this.variables)
|
208 | : !equal(this.last.result, newResult);
|
209 | return (resultIsDifferent || (variables && !equal(this.last.variables, variables)));
|
210 | };
|
211 | ObservableQuery.prototype.getLast = function (key, variablesMustMatch) {
|
212 | var last = this.last;
|
213 | if (last &&
|
214 | last[key] &&
|
215 | (!variablesMustMatch || equal(last.variables, this.variables))) {
|
216 | return last[key];
|
217 | }
|
218 | };
|
219 | ObservableQuery.prototype.getLastResult = function (variablesMustMatch) {
|
220 | return this.getLast("result", variablesMustMatch);
|
221 | };
|
222 | ObservableQuery.prototype.getLastError = function (variablesMustMatch) {
|
223 | return this.getLast("error", variablesMustMatch);
|
224 | };
|
225 | ObservableQuery.prototype.resetLastResults = function () {
|
226 | delete this.last;
|
227 | this.isTornDown = false;
|
228 | };
|
229 | ObservableQuery.prototype.resetQueryStoreErrors = function () {
|
230 | this.queryManager.resetErrors(this.queryId);
|
231 | };
|
232 | /**
|
233 | * Update the variables of this observable query, and fetch the new results.
|
234 | * This method should be preferred over `setVariables` in most use cases.
|
235 | *
|
236 | * @param variables - The new set of variables. If there are missing variables,
|
237 | * the previous values of those variables will be used.
|
238 | */
|
239 | ObservableQuery.prototype.refetch = function (variables) {
|
240 | var _a;
|
241 | var reobserveOptions = {
|
242 | // Always disable polling for refetches.
|
243 | pollInterval: 0,
|
244 | };
|
245 | // Unless the provided fetchPolicy always consults the network
|
246 | // (no-cache, network-only, or cache-and-network), override it with
|
247 | // network-only to force the refetch for this fetchQuery call.
|
248 | var fetchPolicy = this.options.fetchPolicy;
|
249 | if (fetchPolicy === "cache-and-network") {
|
250 | reobserveOptions.fetchPolicy = fetchPolicy;
|
251 | }
|
252 | else if (fetchPolicy === "no-cache") {
|
253 | reobserveOptions.fetchPolicy = "no-cache";
|
254 | }
|
255 | else {
|
256 | reobserveOptions.fetchPolicy = "network-only";
|
257 | }
|
258 | if (globalThis.__DEV__ !== false && variables && hasOwnProperty.call(variables, "variables")) {
|
259 | var queryDef = getQueryDefinition(this.query);
|
260 | var vars = queryDef.variableDefinitions;
|
261 | if (!vars || !vars.some(function (v) { return v.variable.name.value === "variables"; })) {
|
262 | globalThis.__DEV__ !== false && invariant.warn(
|
263 | 20,
|
264 | variables,
|
265 | ((_a = queryDef.name) === null || _a === void 0 ? void 0 : _a.value) || queryDef
|
266 | );
|
267 | }
|
268 | }
|
269 | if (variables && !equal(this.options.variables, variables)) {
|
270 | // Update the existing options with new variables
|
271 | reobserveOptions.variables = this.options.variables = __assign(__assign({}, this.options.variables), variables);
|
272 | }
|
273 | this.queryInfo.resetLastWrite();
|
274 | return this.reobserve(reobserveOptions, NetworkStatus.refetch);
|
275 | };
|
276 | /**
|
277 | * A function that helps you fetch the next set of results for a [paginated list field](https://www.apollographql.com/docs/react/pagination/core-api/).
|
278 | */
|
279 | ObservableQuery.prototype.fetchMore = function (fetchMoreOptions) {
|
280 | var _this = this;
|
281 | var combinedOptions = __assign(__assign({}, (fetchMoreOptions.query ? fetchMoreOptions : (__assign(__assign(__assign(__assign({}, this.options), { query: this.options.query }), fetchMoreOptions), { variables: __assign(__assign({}, this.options.variables), fetchMoreOptions.variables) })))), {
|
282 | // The fetchMore request goes immediately to the network and does
|
283 | // not automatically write its result to the cache (hence no-cache
|
284 | // instead of network-only), because we allow the caller of
|
285 | // fetchMore to provide an updateQuery callback that determines how
|
286 | // the data gets written to the cache.
|
287 | fetchPolicy: "no-cache" });
|
288 | combinedOptions.query = this.transformDocument(combinedOptions.query);
|
289 | var qid = this.queryManager.generateQueryId();
|
290 | // If a temporary query is passed to `fetchMore`, we don't want to store
|
291 | // it as the last query result since it may be an optimized query for
|
292 | // pagination. We will however run the transforms on the original document
|
293 | // as well as the document passed in `fetchMoreOptions` to ensure the cache
|
294 | // uses the most up-to-date document which may rely on runtime conditionals.
|
295 | this.lastQuery =
|
296 | fetchMoreOptions.query ?
|
297 | this.transformDocument(this.options.query)
|
298 | : combinedOptions.query;
|
299 | // Simulate a loading result for the original query with
|
300 | // result.networkStatus === NetworkStatus.fetchMore.
|
301 | var queryInfo = this.queryInfo;
|
302 | var originalNetworkStatus = queryInfo.networkStatus;
|
303 | queryInfo.networkStatus = NetworkStatus.fetchMore;
|
304 | if (combinedOptions.notifyOnNetworkStatusChange) {
|
305 | this.observe();
|
306 | }
|
307 | var updatedQuerySet = new Set();
|
308 | var updateQuery = fetchMoreOptions === null || fetchMoreOptions === void 0 ? void 0 : fetchMoreOptions.updateQuery;
|
309 | var isCached = this.options.fetchPolicy !== "no-cache";
|
310 | if (!isCached) {
|
311 | invariant(updateQuery, 21);
|
312 | }
|
313 | return this.queryManager
|
314 | .fetchQuery(qid, combinedOptions, NetworkStatus.fetchMore)
|
315 | .then(function (fetchMoreResult) {
|
316 | _this.queryManager.removeQuery(qid);
|
317 | if (queryInfo.networkStatus === NetworkStatus.fetchMore) {
|
318 | queryInfo.networkStatus = originalNetworkStatus;
|
319 | }
|
320 | if (isCached) {
|
321 | // Performing this cache update inside a cache.batch transaction ensures
|
322 | // any affected cache.watch watchers are notified at most once about any
|
323 | // updates. Most watchers will be using the QueryInfo class, which
|
324 | // responds to notifications by calling reobserveCacheFirst to deliver
|
325 | // fetchMore cache results back to this ObservableQuery.
|
326 | _this.queryManager.cache.batch({
|
327 | update: function (cache) {
|
328 | var updateQuery = fetchMoreOptions.updateQuery;
|
329 | if (updateQuery) {
|
330 | cache.updateQuery({
|
331 | query: _this.query,
|
332 | variables: _this.variables,
|
333 | returnPartialData: true,
|
334 | optimistic: false,
|
335 | }, function (previous) {
|
336 | return updateQuery(previous, {
|
337 | fetchMoreResult: fetchMoreResult.data,
|
338 | variables: combinedOptions.variables,
|
339 | });
|
340 | });
|
341 | }
|
342 | else {
|
343 | // If we're using a field policy instead of updateQuery, the only
|
344 | // thing we need to do is write the new data to the cache using
|
345 | // combinedOptions.variables (instead of this.variables, which is
|
346 | // what this.updateQuery uses, because it works by abusing the
|
347 | // original field value, keyed by the original variables).
|
348 | cache.writeQuery({
|
349 | query: combinedOptions.query,
|
350 | variables: combinedOptions.variables,
|
351 | data: fetchMoreResult.data,
|
352 | });
|
353 | }
|
354 | },
|
355 | onWatchUpdated: function (watch) {
|
356 | // Record the DocumentNode associated with any watched query whose
|
357 | // data were updated by the cache writes above.
|
358 | updatedQuerySet.add(watch.query);
|
359 | },
|
360 | });
|
361 | }
|
362 | else {
|
363 | // There is a possibility `lastResult` may not be set when
|
364 | // `fetchMore` is called which would cause this to crash. This should
|
365 | // only happen if we haven't previously reported a result. We don't
|
366 | // quite know what the right behavior should be here since this block
|
367 | // of code runs after the fetch result has executed on the network.
|
368 | // We plan to let it crash in the meantime.
|
369 | //
|
370 | // If we get bug reports due to the `data` property access on
|
371 | // undefined, this should give us a real-world scenario that we can
|
372 | // use to test against and determine the right behavior. If we do end
|
373 | // up changing this behavior, this may require, for example, an
|
374 | // adjustment to the types on `updateQuery` since that function
|
375 | // expects that the first argument always contains previous result
|
376 | // data, but not `undefined`.
|
377 | var lastResult = _this.getLast("result");
|
378 | var data = updateQuery(lastResult.data, {
|
379 | fetchMoreResult: fetchMoreResult.data,
|
380 | variables: combinedOptions.variables,
|
381 | });
|
382 | _this.reportResult(__assign(__assign({}, lastResult), { data: data }), _this.variables);
|
383 | }
|
384 | return fetchMoreResult;
|
385 | })
|
386 | .finally(function () {
|
387 | // In case the cache writes above did not generate a broadcast
|
388 | // notification (which would have been intercepted by onWatchUpdated),
|
389 | // likely because the written data were the same as what was already in
|
390 | // the cache, we still want fetchMore to deliver its final loading:false
|
391 | // result with the unchanged data.
|
392 | if (isCached && !updatedQuerySet.has(_this.query)) {
|
393 | reobserveCacheFirst(_this);
|
394 | }
|
395 | });
|
396 | };
|
397 | // XXX the subscription variables are separate from the query variables.
|
398 | // if you want to update subscription variables, right now you have to do that separately,
|
399 | // and you can only do it by stopping the subscription and then subscribing again with new variables.
|
400 | /**
|
401 | * A function that enables you to execute a [subscription](https://www.apollographql.com/docs/react/data/subscriptions/), usually to subscribe to specific fields that were included in the query.
|
402 | *
|
403 | * This function returns _another_ function that you can call to terminate the subscription.
|
404 | */
|
405 | ObservableQuery.prototype.subscribeToMore = function (options) {
|
406 | var _this = this;
|
407 | var subscription = this.queryManager
|
408 | .startGraphQLSubscription({
|
409 | query: options.document,
|
410 | variables: options.variables,
|
411 | context: options.context,
|
412 | })
|
413 | .subscribe({
|
414 | next: function (subscriptionData) {
|
415 | var updateQuery = options.updateQuery;
|
416 | if (updateQuery) {
|
417 | _this.updateQuery(function (previous, _a) {
|
418 | var variables = _a.variables;
|
419 | return updateQuery(previous, {
|
420 | subscriptionData: subscriptionData,
|
421 | variables: variables,
|
422 | });
|
423 | });
|
424 | }
|
425 | },
|
426 | error: function (err) {
|
427 | if (options.onError) {
|
428 | options.onError(err);
|
429 | return;
|
430 | }
|
431 | globalThis.__DEV__ !== false && invariant.error(22, err);
|
432 | },
|
433 | });
|
434 | this.subscriptions.add(subscription);
|
435 | return function () {
|
436 | if (_this.subscriptions.delete(subscription)) {
|
437 | subscription.unsubscribe();
|
438 | }
|
439 | };
|
440 | };
|
441 | ObservableQuery.prototype.setOptions = function (newOptions) {
|
442 | return this.reobserve(newOptions);
|
443 | };
|
444 | ObservableQuery.prototype.silentSetOptions = function (newOptions) {
|
445 | var mergedOptions = compact(this.options, newOptions || {});
|
446 | assign(this.options, mergedOptions);
|
447 | };
|
448 | /**
|
449 | * Update the variables of this observable query, and fetch the new results
|
450 | * if they've changed. Most users should prefer `refetch` instead of
|
451 | * `setVariables` in order to to be properly notified of results even when
|
452 | * they come from the cache.
|
453 | *
|
454 | * Note: the `next` callback will *not* fire if the variables have not changed
|
455 | * or if the result is coming from cache.
|
456 | *
|
457 | * Note: the promise will return the old results immediately if the variables
|
458 | * have not changed.
|
459 | *
|
460 | * Note: the promise will return null immediately if the query is not active
|
461 | * (there are no subscribers).
|
462 | *
|
463 | * @param variables - The new set of variables. If there are missing variables,
|
464 | * the previous values of those variables will be used.
|
465 | */
|
466 | ObservableQuery.prototype.setVariables = function (variables) {
|
467 | if (equal(this.variables, variables)) {
|
468 | // If we have no observers, then we don't actually want to make a network
|
469 | // request. As soon as someone observes the query, the request will kick
|
470 | // off. For now, we just store any changes. (See #1077)
|
471 | return this.observers.size ? this.result() : Promise.resolve();
|
472 | }
|
473 | this.options.variables = variables;
|
474 | // See comment above
|
475 | if (!this.observers.size) {
|
476 | return Promise.resolve();
|
477 | }
|
478 | return this.reobserve({
|
479 | // Reset options.fetchPolicy to its original value.
|
480 | fetchPolicy: this.options.initialFetchPolicy,
|
481 | variables: variables,
|
482 | }, NetworkStatus.setVariables);
|
483 | };
|
484 | /**
|
485 | * A function that enables you to update the query's cached result without executing a followup GraphQL operation.
|
486 | *
|
487 | * See [using updateQuery and updateFragment](https://www.apollographql.com/docs/react/caching/cache-interaction/#using-updatequery-and-updatefragment) for additional information.
|
488 | */
|
489 | ObservableQuery.prototype.updateQuery = function (mapFn) {
|
490 | var queryManager = this.queryManager;
|
491 | var result = queryManager.cache.diff({
|
492 | query: this.options.query,
|
493 | variables: this.variables,
|
494 | returnPartialData: true,
|
495 | optimistic: false,
|
496 | }).result;
|
497 | var newResult = mapFn(result, {
|
498 | variables: this.variables,
|
499 | });
|
500 | if (newResult) {
|
501 | queryManager.cache.writeQuery({
|
502 | query: this.options.query,
|
503 | data: newResult,
|
504 | variables: this.variables,
|
505 | });
|
506 | queryManager.broadcastQueries();
|
507 | }
|
508 | };
|
509 | /**
|
510 | * A function that instructs the query to begin re-executing at a specified interval (in milliseconds).
|
511 | */
|
512 | ObservableQuery.prototype.startPolling = function (pollInterval) {
|
513 | this.options.pollInterval = pollInterval;
|
514 | this.updatePolling();
|
515 | };
|
516 | /**
|
517 | * A function that instructs the query to stop polling after a previous call to `startPolling`.
|
518 | */
|
519 | ObservableQuery.prototype.stopPolling = function () {
|
520 | this.options.pollInterval = 0;
|
521 | this.updatePolling();
|
522 | };
|
523 | // Update options.fetchPolicy according to options.nextFetchPolicy.
|
524 | ObservableQuery.prototype.applyNextFetchPolicy = function (reason,
|
525 | // It's possible to use this method to apply options.nextFetchPolicy to
|
526 | // options.fetchPolicy even if options !== this.options, though that happens
|
527 | // most often when the options are temporary, used for only one request and
|
528 | // then thrown away, so nextFetchPolicy may not end up mattering.
|
529 | options) {
|
530 | if (options.nextFetchPolicy) {
|
531 | var _a = options.fetchPolicy, fetchPolicy = _a === void 0 ? "cache-first" : _a, _b = options.initialFetchPolicy, initialFetchPolicy = _b === void 0 ? fetchPolicy : _b;
|
532 | if (fetchPolicy === "standby") {
|
533 | // Do nothing, leaving options.fetchPolicy unchanged.
|
534 | }
|
535 | else if (typeof options.nextFetchPolicy === "function") {
|
536 | // When someone chooses "cache-and-network" or "network-only" as their
|
537 | // initial FetchPolicy, they often do not want future cache updates to
|
538 | // trigger unconditional network requests, which is what repeatedly
|
539 | // applying the "cache-and-network" or "network-only" policies would
|
540 | // seem to imply. Instead, when the cache reports an update after the
|
541 | // initial network request, it may be desirable for subsequent network
|
542 | // requests to be triggered only if the cache result is incomplete. To
|
543 | // that end, the options.nextFetchPolicy option provides an easy way to
|
544 | // update options.fetchPolicy after the initial network request, without
|
545 | // having to call observableQuery.setOptions.
|
546 | options.fetchPolicy = options.nextFetchPolicy(fetchPolicy, {
|
547 | reason: reason,
|
548 | options: options,
|
549 | observable: this,
|
550 | initialFetchPolicy: initialFetchPolicy,
|
551 | });
|
552 | }
|
553 | else if (reason === "variables-changed") {
|
554 | options.fetchPolicy = initialFetchPolicy;
|
555 | }
|
556 | else {
|
557 | options.fetchPolicy = options.nextFetchPolicy;
|
558 | }
|
559 | }
|
560 | return options.fetchPolicy;
|
561 | };
|
562 | ObservableQuery.prototype.fetch = function (options, newNetworkStatus, query) {
|
563 | // TODO Make sure we update the networkStatus (and infer fetchVariables)
|
564 | // before actually committing to the fetch.
|
565 | this.queryManager.setObservableQuery(this);
|
566 | return this.queryManager["fetchConcastWithInfo"](this.queryId, options, newNetworkStatus, query);
|
567 | };
|
568 | // Turns polling on or off based on this.options.pollInterval.
|
569 | ObservableQuery.prototype.updatePolling = function () {
|
570 | var _this = this;
|
571 | // Avoid polling in SSR mode
|
572 | if (this.queryManager.ssrMode) {
|
573 | return;
|
574 | }
|
575 | var _a = this, pollingInfo = _a.pollingInfo, pollInterval = _a.options.pollInterval;
|
576 | if (!pollInterval || !this.hasObservers()) {
|
577 | if (pollingInfo) {
|
578 | clearTimeout(pollingInfo.timeout);
|
579 | delete this.pollingInfo;
|
580 | }
|
581 | return;
|
582 | }
|
583 | if (pollingInfo && pollingInfo.interval === pollInterval) {
|
584 | return;
|
585 | }
|
586 | invariant(pollInterval, 23);
|
587 | var info = pollingInfo || (this.pollingInfo = {});
|
588 | info.interval = pollInterval;
|
589 | var maybeFetch = function () {
|
590 | var _a, _b;
|
591 | if (_this.pollingInfo) {
|
592 | if (!isNetworkRequestInFlight(_this.queryInfo.networkStatus) &&
|
593 | !((_b = (_a = _this.options).skipPollAttempt) === null || _b === void 0 ? void 0 : _b.call(_a))) {
|
594 | _this.reobserve({
|
595 | // Most fetchPolicy options don't make sense to use in a polling context, as
|
596 | // users wouldn't want to be polling the cache directly. However, network-only and
|
597 | // no-cache are both useful for when the user wants to control whether or not the
|
598 | // polled results are written to the cache.
|
599 | fetchPolicy: _this.options.initialFetchPolicy === "no-cache" ?
|
600 | "no-cache"
|
601 | : "network-only",
|
602 | }, NetworkStatus.poll).then(poll, poll);
|
603 | }
|
604 | else {
|
605 | poll();
|
606 | }
|
607 | }
|
608 | };
|
609 | var poll = function () {
|
610 | var info = _this.pollingInfo;
|
611 | if (info) {
|
612 | clearTimeout(info.timeout);
|
613 | info.timeout = setTimeout(maybeFetch, info.interval);
|
614 | }
|
615 | };
|
616 | poll();
|
617 | };
|
618 | ObservableQuery.prototype.updateLastResult = function (newResult, variables) {
|
619 | if (variables === void 0) { variables = this.variables; }
|
620 | var error = this.getLastError();
|
621 | // Preserve this.last.error unless the variables have changed.
|
622 | if (error && this.last && !equal(variables, this.last.variables)) {
|
623 | error = void 0;
|
624 | }
|
625 | return (this.last = __assign({ result: this.queryManager.assumeImmutableResults ?
|
626 | newResult
|
627 | : cloneDeep(newResult), variables: variables }, (error ? { error: error } : null)));
|
628 | };
|
629 | ObservableQuery.prototype.reobserveAsConcast = function (newOptions, newNetworkStatus) {
|
630 | var _this = this;
|
631 | this.isTornDown = false;
|
632 | var useDisposableConcast =
|
633 | // Refetching uses a disposable Concast to allow refetches using different
|
634 | // options/variables, without permanently altering the options of the
|
635 | // original ObservableQuery.
|
636 | newNetworkStatus === NetworkStatus.refetch ||
|
637 | // The fetchMore method does not actually call the reobserve method, but,
|
638 | // if it did, it would definitely use a disposable Concast.
|
639 | newNetworkStatus === NetworkStatus.fetchMore ||
|
640 | // Polling uses a disposable Concast so the polling options (which force
|
641 | // fetchPolicy to be "network-only" or "no-cache") won't override the original options.
|
642 | newNetworkStatus === NetworkStatus.poll;
|
643 | // Save the old variables, since Object.assign may modify them below.
|
644 | var oldVariables = this.options.variables;
|
645 | var oldFetchPolicy = this.options.fetchPolicy;
|
646 | var mergedOptions = compact(this.options, newOptions || {});
|
647 | var options = useDisposableConcast ?
|
648 | // Disposable Concast fetches receive a shallow copy of this.options
|
649 | // (merged with newOptions), leaving this.options unmodified.
|
650 | mergedOptions
|
651 | : assign(this.options, mergedOptions);
|
652 | // Don't update options.query with the transformed query to avoid
|
653 | // overwriting this.options.query when we aren't using a disposable concast.
|
654 | // We want to ensure we can re-run the custom document transforms the next
|
655 | // time a request is made against the original query.
|
656 | var query = this.transformDocument(options.query);
|
657 | this.lastQuery = query;
|
658 | if (!useDisposableConcast) {
|
659 | // We can skip calling updatePolling if we're not changing this.options.
|
660 | this.updatePolling();
|
661 | // Reset options.fetchPolicy to its original value when variables change,
|
662 | // unless a new fetchPolicy was provided by newOptions.
|
663 | if (newOptions &&
|
664 | newOptions.variables &&
|
665 | !equal(newOptions.variables, oldVariables) &&
|
666 | // Don't mess with the fetchPolicy if it's currently "standby".
|
667 | options.fetchPolicy !== "standby" &&
|
668 | // If we're changing the fetchPolicy anyway, don't try to change it here
|
669 | // using applyNextFetchPolicy. The explicit options.fetchPolicy wins.
|
670 | (options.fetchPolicy === oldFetchPolicy ||
|
671 | // A `nextFetchPolicy` function has even higher priority, though,
|
672 | // so in that case `applyNextFetchPolicy` must be called.
|
673 | typeof options.nextFetchPolicy === "function")) {
|
674 | this.applyNextFetchPolicy("variables-changed", options);
|
675 | if (newNetworkStatus === void 0) {
|
676 | newNetworkStatus = NetworkStatus.setVariables;
|
677 | }
|
678 | }
|
679 | }
|
680 | this.waitForOwnResult && (this.waitForOwnResult = skipCacheDataFor(options.fetchPolicy));
|
681 | var finishWaitingForOwnResult = function () {
|
682 | if (_this.concast === concast) {
|
683 | _this.waitForOwnResult = false;
|
684 | }
|
685 | };
|
686 | var variables = options.variables && __assign({}, options.variables);
|
687 | var _a = this.fetch(options, newNetworkStatus, query), concast = _a.concast, fromLink = _a.fromLink;
|
688 | var observer = {
|
689 | next: function (result) {
|
690 | if (equal(_this.variables, variables)) {
|
691 | finishWaitingForOwnResult();
|
692 | _this.reportResult(result, variables);
|
693 | }
|
694 | },
|
695 | error: function (error) {
|
696 | if (equal(_this.variables, variables)) {
|
697 | // Coming from `getResultsFromLink`, `error` here should always be an `ApolloError`.
|
698 | // However, calling `concast.cancel` can inject another type of error, so we have to
|
699 | // wrap it again here.
|
700 | if (!isApolloError(error)) {
|
701 | error = new ApolloError({ networkError: error });
|
702 | }
|
703 | finishWaitingForOwnResult();
|
704 | _this.reportError(error, variables);
|
705 | }
|
706 | },
|
707 | };
|
708 | if (!useDisposableConcast && (fromLink || !this.concast)) {
|
709 | // We use the {add,remove}Observer methods directly to avoid wrapping
|
710 | // observer with an unnecessary SubscriptionObserver object.
|
711 | if (this.concast && this.observer) {
|
712 | this.concast.removeObserver(this.observer);
|
713 | }
|
714 | this.concast = concast;
|
715 | this.observer = observer;
|
716 | }
|
717 | concast.addObserver(observer);
|
718 | return concast;
|
719 | };
|
720 | ObservableQuery.prototype.reobserve = function (newOptions, newNetworkStatus) {
|
721 | return this.reobserveAsConcast(newOptions, newNetworkStatus)
|
722 | .promise;
|
723 | };
|
724 | ObservableQuery.prototype.resubscribeAfterError = function () {
|
725 | var args = [];
|
726 | for (var _i = 0; _i < arguments.length; _i++) {
|
727 | args[_i] = arguments[_i];
|
728 | }
|
729 | // If `lastError` is set in the current when the subscription is re-created,
|
730 | // the subscription will immediately receive the error, which will
|
731 | // cause it to terminate again. To avoid this, we first clear
|
732 | // the last error/result from the `observableQuery` before re-starting
|
733 | // the subscription, and restore the last value afterwards so that the
|
734 | // subscription has a chance to stay open.
|
735 | var last = this.last;
|
736 | this.resetLastResults();
|
737 | var subscription = this.subscribe.apply(this, args);
|
738 | this.last = last;
|
739 | return subscription;
|
740 | };
|
741 | // (Re)deliver the current result to this.observers without applying fetch
|
742 | // policies or making network requests.
|
743 | ObservableQuery.prototype.observe = function () {
|
744 | this.reportResult(
|
745 | // Passing false is important so that this.getCurrentResult doesn't
|
746 | // save the fetchMore result as this.lastResult, causing it to be
|
747 | // ignored due to the this.isDifferentFromLastResult check in
|
748 | // this.reportResult.
|
749 | this.getCurrentResult(false), this.variables);
|
750 | };
|
751 | ObservableQuery.prototype.reportResult = function (result, variables) {
|
752 | var lastError = this.getLastError();
|
753 | var isDifferent = this.isDifferentFromLastResult(result, variables);
|
754 | // Update the last result even when isDifferentFromLastResult returns false,
|
755 | // because the query may be using the @nonreactive directive, and we want to
|
756 | // save the the latest version of any nonreactive subtrees (in case
|
757 | // getCurrentResult is called), even though we skip broadcasting changes.
|
758 | if (lastError || !result.partial || this.options.returnPartialData) {
|
759 | this.updateLastResult(result, variables);
|
760 | }
|
761 | if (lastError || isDifferent) {
|
762 | iterateObserversSafely(this.observers, "next", result);
|
763 | }
|
764 | };
|
765 | ObservableQuery.prototype.reportError = function (error, variables) {
|
766 | // Since we don't get the current result on errors, only the error, we
|
767 | // must mirror the updates that occur in QueryStore.markQueryError here
|
768 | var errorResult = __assign(__assign({}, this.getLastResult()), { error: error, errors: error.graphQLErrors, networkStatus: NetworkStatus.error, loading: false });
|
769 | this.updateLastResult(errorResult, variables);
|
770 | iterateObserversSafely(this.observers, "error", (this.last.error = error));
|
771 | };
|
772 | ObservableQuery.prototype.hasObservers = function () {
|
773 | return this.observers.size > 0;
|
774 | };
|
775 | ObservableQuery.prototype.tearDownQuery = function () {
|
776 | if (this.isTornDown)
|
777 | return;
|
778 | if (this.concast && this.observer) {
|
779 | this.concast.removeObserver(this.observer);
|
780 | delete this.concast;
|
781 | delete this.observer;
|
782 | }
|
783 | this.stopPolling();
|
784 | // stop all active GraphQL subscriptions
|
785 | this.subscriptions.forEach(function (sub) { return sub.unsubscribe(); });
|
786 | this.subscriptions.clear();
|
787 | this.queryManager.stopQuery(this.queryId);
|
788 | this.observers.clear();
|
789 | this.isTornDown = true;
|
790 | };
|
791 | ObservableQuery.prototype.transformDocument = function (document) {
|
792 | return this.queryManager.transform(document);
|
793 | };
|
794 | return ObservableQuery;
|
795 | }(Observable));
|
796 | export { ObservableQuery };
|
797 | // Necessary because the ObservableQuery constructor has a different
|
798 | // signature than the Observable constructor.
|
799 | fixObservableSubclass(ObservableQuery);
|
800 | // Reobserve with fetchPolicy effectively set to "cache-first", triggering
|
801 | // delivery of any new data from the cache, possibly falling back to the network
|
802 | // if any cache data are missing. This allows _complete_ cache results to be
|
803 | // delivered without also kicking off unnecessary network requests when
|
804 | // this.options.fetchPolicy is "cache-and-network" or "network-only". When
|
805 | // this.options.fetchPolicy is any other policy ("cache-first", "cache-only",
|
806 | // "standby", or "no-cache"), we call this.reobserve() as usual.
|
807 | export function reobserveCacheFirst(obsQuery) {
|
808 | var _a = obsQuery.options, fetchPolicy = _a.fetchPolicy, nextFetchPolicy = _a.nextFetchPolicy;
|
809 | if (fetchPolicy === "cache-and-network" || fetchPolicy === "network-only") {
|
810 | return obsQuery.reobserve({
|
811 | fetchPolicy: "cache-first",
|
812 | // Use a temporary nextFetchPolicy function that replaces itself with the
|
813 | // previous nextFetchPolicy value and returns the original fetchPolicy.
|
814 | nextFetchPolicy: function (currentFetchPolicy, context) {
|
815 | // Replace this nextFetchPolicy function in the options object with the
|
816 | // original this.options.nextFetchPolicy value.
|
817 | this.nextFetchPolicy = nextFetchPolicy;
|
818 | // If the original nextFetchPolicy value was a function, give it a
|
819 | // chance to decide what happens here.
|
820 | if (typeof this.nextFetchPolicy === "function") {
|
821 | return this.nextFetchPolicy(currentFetchPolicy, context);
|
822 | }
|
823 | // Otherwise go back to the original this.options.fetchPolicy.
|
824 | return fetchPolicy;
|
825 | },
|
826 | });
|
827 | }
|
828 | return obsQuery.reobserve();
|
829 | }
|
830 | function defaultSubscriptionObserverErrorCallback(error) {
|
831 | globalThis.__DEV__ !== false && invariant.error(24, error.message, error.stack);
|
832 | }
|
833 | export function logMissingFieldErrors(missing) {
|
834 | if (globalThis.__DEV__ !== false && missing) {
|
835 | globalThis.__DEV__ !== false && invariant.debug(25, missing);
|
836 | }
|
837 | }
|
838 | function skipCacheDataFor(fetchPolicy /* `undefined` would mean `"cache-first"` */) {
|
839 | return (fetchPolicy === "network-only" ||
|
840 | fetchPolicy === "no-cache" ||
|
841 | fetchPolicy === "standby");
|
842 | }
|
843 | //# sourceMappingURL=ObservableQuery.js.map |
\ | No newline at end of file |