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