@apollo/client 4.3.0-alpha.6 → 4.3.0-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/__cjs/cache/core/cache.cjs +25 -1
  3. package/__cjs/cache/core/cache.cjs.map +1 -1
  4. package/__cjs/cache/core/cache.d.cts +19 -1
  5. package/__cjs/cache/inmemory/entityStore.cjs +3 -3
  6. package/__cjs/cache/inmemory/inMemoryCache.cjs +10 -0
  7. package/__cjs/cache/inmemory/inMemoryCache.cjs.map +1 -1
  8. package/__cjs/cache/inmemory/inMemoryCache.d.cts +5 -1
  9. package/__cjs/cache/inmemory/key-extractor.cjs +1 -1
  10. package/__cjs/cache/inmemory/policies.cjs +5 -5
  11. package/__cjs/cache/inmemory/readFromStore.cjs +3 -3
  12. package/__cjs/cache/inmemory/writeToStore.cjs +4 -4
  13. package/__cjs/core/ApolloClient.cjs +14 -14
  14. package/__cjs/core/ObservableQuery.cjs +173 -61
  15. package/__cjs/core/ObservableQuery.cjs.map +1 -1
  16. package/__cjs/core/ObservableQuery.d.cts +3 -6
  17. package/__cjs/core/QueryInfo.cjs +11 -125
  18. package/__cjs/core/QueryInfo.cjs.map +1 -1
  19. package/__cjs/core/QueryInfo.d.cts +0 -24
  20. package/__cjs/core/QueryManager.cjs +23 -15
  21. package/__cjs/core/QueryManager.cjs.map +1 -1
  22. package/__cjs/core/RefetchEventManager.cjs +3 -3
  23. package/__cjs/incremental/handlers/graphql17Alpha9.cjs +1 -1
  24. package/__cjs/incremental/handlers/notImplemented.cjs +1 -1
  25. package/__cjs/invariantErrorCodes.cjs +141 -115
  26. package/__cjs/link/core/ApolloLink.cjs +3 -3
  27. package/__cjs/link/http/checkFetcher.cjs +1 -1
  28. package/__cjs/link/http/parseAndCheckHttpResponse.cjs +2 -2
  29. package/__cjs/link/http/parseAndCheckHttpResponse.cjs.map +1 -1
  30. package/__cjs/link/persisted-queries/index.cjs +2 -2
  31. package/__cjs/link/ws/index.cjs +1 -1
  32. package/__cjs/local-state/LocalState.cjs +11 -11
  33. package/__cjs/masking/maskDefinition.cjs +2 -2
  34. package/__cjs/masking/maskFragment.cjs +2 -2
  35. package/__cjs/masking/maskOperation.cjs +1 -1
  36. package/__cjs/masking/utils.cjs +3 -3
  37. package/__cjs/react/context/ApolloContext.cjs +1 -1
  38. package/__cjs/react/context/ApolloProvider.cjs +1 -1
  39. package/__cjs/react/hooks/internal/validateSuspenseHookOptions.cjs +2 -2
  40. package/__cjs/react/hooks/useApolloClient.cjs +1 -1
  41. package/__cjs/react/hooks/useLazyQuery.cjs +2 -2
  42. package/__cjs/react/hooks/useLoadableQuery.cjs +2 -2
  43. package/__cjs/react/hooks/useSubscription.cjs +1 -1
  44. package/__cjs/react/hooks/useSyncExternalStore.cjs +1 -1
  45. package/__cjs/react/internal/cache/QueryReference.cjs +1 -1
  46. package/__cjs/react/ssr/prerenderStatic.cjs +2 -2
  47. package/__cjs/testing/core/mocking/mockLink.cjs +4 -4
  48. package/__cjs/utilities/graphql/DocumentTransform.cjs +1 -1
  49. package/__cjs/utilities/internal/capitalize.cjs +12 -0
  50. package/__cjs/utilities/internal/capitalize.cjs.map +1 -0
  51. package/__cjs/utilities/internal/capitalize.d.cts +7 -0
  52. package/__cjs/utilities/internal/coerceScalarFieldsToParsed.cjs +69 -0
  53. package/__cjs/utilities/internal/coerceScalarFieldsToParsed.cjs.map +1 -0
  54. package/__cjs/utilities/internal/coerceScalarFieldsToParsed.d.cts +9 -0
  55. package/__cjs/utilities/internal/getFragmentDefinition.cjs +3 -3
  56. package/__cjs/utilities/internal/getFragmentFromSelection.cjs +1 -1
  57. package/__cjs/utilities/internal/getFragmentFromSelection.cjs.map +1 -1
  58. package/__cjs/utilities/internal/getFragmentQueryDocument.cjs +2 -2
  59. package/__cjs/utilities/internal/getMainDefinition.cjs +1 -1
  60. package/__cjs/utilities/internal/getQueryDefinition.cjs +1 -1
  61. package/__cjs/utilities/internal/index.cjs +6 -2
  62. package/__cjs/utilities/internal/index.cjs.map +1 -1
  63. package/__cjs/utilities/internal/index.d.cts +2 -0
  64. package/__cjs/utilities/internal/removeDirectivesFromDocument.cjs +1 -1
  65. package/__cjs/utilities/internal/shouldInclude.cjs +4 -4
  66. package/__cjs/utilities/internal/valueToObjectRepresentation.cjs +1 -1
  67. package/__cjs/version.cjs +1 -1
  68. package/cache/core/cache.d.ts +19 -1
  69. package/cache/core/cache.js +26 -2
  70. package/cache/core/cache.js.map +1 -1
  71. package/cache/inmemory/entityStore.js +3 -3
  72. package/cache/inmemory/inMemoryCache.d.ts +5 -1
  73. package/cache/inmemory/inMemoryCache.js +10 -0
  74. package/cache/inmemory/inMemoryCache.js.map +1 -1
  75. package/cache/inmemory/key-extractor.js +1 -1
  76. package/cache/inmemory/policies.js +5 -5
  77. package/cache/inmemory/readFromStore.js +3 -3
  78. package/cache/inmemory/writeToStore.js +4 -4
  79. package/core/ApolloClient.js +14 -14
  80. package/core/ObservableQuery.d.ts +3 -6
  81. package/core/ObservableQuery.js +169 -61
  82. package/core/ObservableQuery.js.map +1 -1
  83. package/core/QueryInfo.d.ts +1 -25
  84. package/core/QueryInfo.js +11 -125
  85. package/core/QueryInfo.js.map +1 -1
  86. package/core/QueryManager.js +24 -16
  87. package/core/QueryManager.js.map +1 -1
  88. package/core/RefetchEventManager.js +3 -3
  89. package/incremental/handlers/graphql17Alpha9.js +1 -1
  90. package/incremental/handlers/notImplemented.js +1 -1
  91. package/invariantErrorCodes.js +141 -115
  92. package/link/core/ApolloLink.js +3 -3
  93. package/link/http/checkFetcher.js +1 -1
  94. package/link/http/parseAndCheckHttpResponse.js +2 -2
  95. package/link/http/parseAndCheckHttpResponse.js.map +1 -1
  96. package/link/persisted-queries/index.js +2 -2
  97. package/link/ws/index.js +1 -1
  98. package/local-state/LocalState.js +11 -11
  99. package/masking/maskDefinition.js +2 -2
  100. package/masking/maskFragment.js +2 -2
  101. package/masking/maskOperation.js +1 -1
  102. package/masking/utils.js +3 -3
  103. package/package.json +1 -1
  104. package/react/context/ApolloContext.js +1 -1
  105. package/react/context/ApolloProvider.js +1 -1
  106. package/react/hooks/internal/validateSuspenseHookOptions.js +2 -2
  107. package/react/hooks/useApolloClient.js +1 -1
  108. package/react/hooks/useLazyQuery.js +2 -2
  109. package/react/hooks/useLoadableQuery.js +2 -2
  110. package/react/hooks/useSubscription.js +1 -1
  111. package/react/hooks/useSyncExternalStore.js +1 -1
  112. package/react/hooks-compiled/internal/validateSuspenseHookOptions.js +2 -2
  113. package/react/hooks-compiled/useApolloClient.js +1 -1
  114. package/react/hooks-compiled/useLazyQuery.js +2 -2
  115. package/react/hooks-compiled/useLoadableQuery.js +2 -2
  116. package/react/hooks-compiled/useSubscription.js +1 -1
  117. package/react/hooks-compiled/useSyncExternalStore.js +1 -1
  118. package/react/internal/cache/QueryReference.js +1 -1
  119. package/react/ssr/prerenderStatic.js +2 -2
  120. package/testing/core/mocking/mockLink.js +4 -4
  121. package/utilities/graphql/DocumentTransform.js +1 -1
  122. package/utilities/internal/capitalize.d.ts +7 -0
  123. package/utilities/internal/capitalize.js +9 -0
  124. package/utilities/internal/capitalize.js.map +1 -0
  125. package/utilities/internal/coerceScalarFieldsToParsed.d.ts +9 -0
  126. package/utilities/internal/coerceScalarFieldsToParsed.js +66 -0
  127. package/utilities/internal/coerceScalarFieldsToParsed.js.map +1 -0
  128. package/utilities/internal/getFragmentDefinition.js +3 -3
  129. package/utilities/internal/getFragmentFromSelection.js +1 -1
  130. package/utilities/internal/getFragmentFromSelection.js.map +1 -1
  131. package/utilities/internal/getFragmentQueryDocument.js +2 -2
  132. package/utilities/internal/getMainDefinition.js +1 -1
  133. package/utilities/internal/getQueryDefinition.js +1 -1
  134. package/utilities/internal/index.d.ts +2 -0
  135. package/utilities/internal/index.js +2 -0
  136. package/utilities/internal/index.js.map +1 -1
  137. package/utilities/internal/removeDirectivesFromDocument.js +1 -1
  138. package/utilities/internal/shouldInclude.js +4 -4
  139. package/utilities/internal/valueToObjectRepresentation.js +1 -1
  140. package/version.js +1 -1
@@ -242,7 +242,7 @@ export class Policies {
242
242
  const rootId = "ROOT_" + which.toUpperCase();
243
243
  const old = this.rootTypenamesById[rootId];
244
244
  if (typename !== old) {
245
- invariant(!old || old === which, 109, which);
245
+ invariant(!old || old === which, 111, which);
246
246
  // First, delete any old __typename associated with this rootId from
247
247
  // rootIdsByTypename.
248
248
  if (old)
@@ -385,7 +385,7 @@ export class Policies {
385
385
  if (supertypeSet.has(supertype)) {
386
386
  if (!typenameSupertypeSet.has(supertype)) {
387
387
  if (checkingFuzzySubtypes) {
388
- __DEV__ && invariant.warn(110, typename, supertype);
388
+ __DEV__ && invariant.warn(112, typename, supertype);
389
389
  }
390
390
  // Record positive results for faster future lookup.
391
391
  // Unfortunately, we cannot safely cache negative results,
@@ -641,7 +641,7 @@ export function normalizeReadFieldOptions(readFieldArgs, objectOrReference, vari
641
641
  }
642
642
  }
643
643
  if (__DEV__ && options.from === void 0) {
644
- __DEV__ && invariant.warn(111, stringifyForDisplay(Array.from(readFieldArgs)));
644
+ __DEV__ && invariant.warn(113, stringifyForDisplay(Array.from(readFieldArgs)));
645
645
  }
646
646
  if (void 0 === options.variables) {
647
647
  options.variables = variables;
@@ -651,7 +651,7 @@ export function normalizeReadFieldOptions(readFieldArgs, objectOrReference, vari
651
651
  function makeMergeObjectsFunction(store) {
652
652
  return function mergeObjects(existing, incoming) {
653
653
  if (isArray(existing) || isArray(incoming)) {
654
- throw newInvariantError(112);
654
+ throw newInvariantError(114);
655
655
  }
656
656
  // These dynamic checks are necessary because the parameters of a
657
657
  // custom merge function can easily have the any type, so the type
@@ -688,6 +688,6 @@ function makeMergeObjectsFunction(store) {
688
688
  };
689
689
  }
690
690
  function warnAboutScalarConfig(typename, fieldName, scalar, kind) {
691
- __DEV__ && invariant.warn(113, `${typename}.${fieldName}`, scalar, kind);
691
+ __DEV__ && invariant.warn(115, `${typename}.${fieldName}`, scalar, kind);
692
692
  }
693
693
  //# sourceMappingURL=policies.js.map
@@ -282,7 +282,7 @@ export class StoreReader {
282
282
  if (typename) {
283
283
  const policy = policies["getFieldPolicy"](typename, fieldName);
284
284
  if (policy?.scalar) {
285
- __DEV__ && invariant.warn(114, `${typename}.${fieldName}`, policy.scalar);
285
+ __DEV__ && invariant.warn(116, `${typename}.${fieldName}`, policy.scalar);
286
286
  }
287
287
  }
288
288
  }
@@ -319,7 +319,7 @@ export class StoreReader {
319
319
  else {
320
320
  const fragment = getFragmentFromSelection(selection, context.lookupFragment);
321
321
  if (!fragment && selection.kind === Kind.FRAGMENT_SPREAD) {
322
- throw newInvariantError(115, selection.name.value);
322
+ throw newInvariantError(117, selection.name.value);
323
323
  }
324
324
  if (fragment && policies.fragmentMatches(fragment, typename)) {
325
325
  const isDeferBoundary = isDeferredFragment(selection, context.variables);
@@ -616,7 +616,7 @@ function assertSelectionSetForIdValue(store, field, fieldValue) {
616
616
  if (isNonNullObject(value)) {
617
617
  invariant(
618
618
  !isReference(value),
619
- 116,
619
+ 118,
620
620
  getTypenameFromStoreObject(store, value),
621
621
  field.name.value
622
622
  );
@@ -68,7 +68,7 @@ export class StoreWriter {
68
68
  path: [],
69
69
  });
70
70
  if (!isReference(ref)) {
71
- throw newInvariantError(117, result);
71
+ throw newInvariantError(119, result);
72
72
  }
73
73
  // So far, the store has not been modified, so now it's time to process
74
74
  // context.incomingById and merge those incoming fields into context.store.
@@ -230,7 +230,7 @@ export class StoreWriter {
230
230
  // provide a default value, so its absence from the written data should
231
231
  // not be cause for alarm.
232
232
  !policies.getReadFunction(typename, field.name.value)) {
233
- invariant.error(118, resultKeyNameFromField(field), result);
233
+ invariant.error(120, resultKeyNameFromField(field), result);
234
234
  }
235
235
  });
236
236
  // Identify the result object, even if dataId was already provided,
@@ -378,7 +378,7 @@ export class StoreWriter {
378
378
  else {
379
379
  const fragment = getFragmentFromSelection(selection, context.lookupFragment);
380
380
  if (!fragment && selection.kind === Kind.FRAGMENT_SPREAD) {
381
- throw newInvariantError(119, selection.name.value);
381
+ throw newInvariantError(121, selection.name.value);
382
382
  }
383
383
  if (fragment &&
384
384
  policies.fragmentMatches(fragment, typename, result, context.variables)) {
@@ -550,7 +550,7 @@ function warnAboutDataLoss(existingRef, incomingObj, storeFieldName, store) {
550
550
  }
551
551
  });
552
552
  }
553
- __DEV__ && invariant.warn(120, fieldName, parentType, childTypenames.length ?
553
+ __DEV__ && invariant.warn(122, fieldName, parentType, childTypenames.length ?
554
554
  "either ensure all objects of type " +
555
555
  childTypenames.join(" and ") +
556
556
  " have an ID or a custom merge function, or "
@@ -71,8 +71,8 @@ export class ApolloClient {
71
71
  */
72
72
  constructor(options) {
73
73
  if (__DEV__) {
74
- invariant(options.cache, 68);
75
- invariant(options.link, 69);
74
+ invariant(options.cache, 69);
75
+ invariant(options.link, 70);
76
76
  }
77
77
  const { cache, documentTransform, ssrMode = false, ssrForceFetchDelay = 0, queryDeduplication = true, defaultOptions, defaultContext, assumeImmutableResults = cache.assumeImmutableResults, localState, devtools, dataMasking, link, incrementalHandler = new NotImplementedHandler(), experiments = [], refetchEventManager, } = options;
78
78
  this.link = link;
@@ -260,12 +260,12 @@ export class ApolloClient {
260
260
  if (refetchOn) {
261
261
  const operationName = getOperationName(query, "(anonymous)");
262
262
  if (!refetchEventManager) {
263
- __DEV__ && invariant.warn(70, operationName);
263
+ __DEV__ && invariant.warn(71, operationName);
264
264
  }
265
265
  else if (typeof refetchOn === "object") {
266
266
  Object.keys(refetchOn).forEach((source) => {
267
267
  if (!refetchEventManager.hasSource(source)) {
268
- __DEV__ && invariant.warn(71, source, operationName);
268
+ __DEV__ && invariant.warn(72, source, operationName);
269
269
  }
270
270
  });
271
271
  }
@@ -287,13 +287,13 @@ export class ApolloClient {
287
287
  options = mergeOptions(this.defaultOptions.query, options);
288
288
  }
289
289
  if (__DEV__) {
290
- invariant(options.fetchPolicy !== "cache-and-network", 72);
291
- invariant(options.fetchPolicy !== "standby", 73);
292
- invariant(options.query, 74);
293
- invariant(options.query.kind === "Document", 75);
294
- invariant(!options.returnPartialData, 76);
295
- invariant(!options.pollInterval, 77);
296
- invariant(!options.notifyOnNetworkStatusChange, 78);
290
+ invariant(options.fetchPolicy !== "cache-and-network", 73);
291
+ invariant(options.fetchPolicy !== "standby", 74);
292
+ invariant(options.query, 75);
293
+ invariant(options.query.kind === "Document", 76);
294
+ invariant(!options.returnPartialData, 77);
295
+ invariant(!options.pollInterval, 78);
296
+ invariant(!options.notifyOnNetworkStatusChange, 79);
297
297
  }
298
298
  return this.queryManager.query(options);
299
299
  };
@@ -311,9 +311,9 @@ export class ApolloClient {
311
311
  errorPolicy: "none",
312
312
  }, this.defaultOptions.mutate), options);
313
313
  if (__DEV__) {
314
- invariant(optionsWithDefaults.mutation, 79);
314
+ invariant(optionsWithDefaults.mutation, 80);
315
315
  invariant(optionsWithDefaults.fetchPolicy === "network-only" ||
316
- optionsWithDefaults.fetchPolicy === "no-cache", 80);
316
+ optionsWithDefaults.fetchPolicy === "no-cache", 81);
317
317
  }
318
318
  checkDocument(optionsWithDefaults.mutation, OperationTypeNode.MUTATION);
319
319
  return this.queryManager.mutate(optionsWithDefaults);
@@ -519,7 +519,7 @@ export class ApolloClient {
519
519
  // result.queries and result.results instead, you shouldn't have to worry
520
520
  // about preventing uncaught rejections for the Promise.all result.
521
521
  result.catch((error) => {
522
- __DEV__ && invariant.debug(81, error);
522
+ __DEV__ && invariant.debug(82, error);
523
523
  });
524
524
  return result;
525
525
  }
@@ -278,12 +278,8 @@ export declare class ObservableQuery<TData = unknown, TVariables extends Operati
278
278
  readonly options: ObservableQuery.Options<TData, TVariables>;
279
279
  readonly queryName?: string;
280
280
  private variablesUnknown;
281
- /**
282
- * @internal will be read and written from `QueryInfo`
283
- *
284
- * @deprecated This is an internal API and should not be used directly. This can be removed or changed at any time.
285
- */
286
- _lastWrite?: unknown;
281
+ private didWarnOnFeud;
282
+ private lastMissing?;
287
283
  get query(): TypedDocumentNode<TData, TVariables>;
288
284
  /**
289
285
  * An object containing the variables that were provided for the query.
@@ -469,6 +465,7 @@ export declare class ObservableQuery<TData = unknown, TVariables extends Operati
469
465
  * @deprecated This is an internal API and should not be used directly. This can be removed or changed at any time.
470
466
  */
471
467
  notify(scheduled?: boolean): void;
468
+ private deliverCacheDiff;
472
469
  private activeOperations;
473
470
  private pushOperation;
474
471
  private calculateNetworkStatus;
@@ -21,16 +21,29 @@ const empty = {
21
21
  dataState: "empty",
22
22
  partial: true,
23
23
  };
24
+ const destructiveMethodCounts = new WeakMap();
25
+ function wrapDestructiveCacheMethod(cache, methodName) {
26
+ const original = cache[methodName];
27
+ if (typeof original === "function") {
28
+ // @ts-expect-error this is just too generic to be typed correctly
29
+ cache[methodName] = function () {
30
+ destructiveMethodCounts.set(cache,
31
+ // The %1e15 allows the count to wrap around to 0 safely every
32
+ // quadrillion evictions, so there's no risk of overflow. To be
33
+ // clear, this is more of a pedantic principle than something
34
+ // that matters in any conceivable practical scenario.
35
+ (destructiveMethodCounts.get(cache) + 1) % 1e15);
36
+ // @ts-expect-error this is just too generic to be typed correctly
37
+ return original.apply(this, arguments);
38
+ };
39
+ }
40
+ }
24
41
  export class ObservableQuery {
25
42
  options;
26
43
  queryName;
27
44
  variablesUnknown = false;
28
- /**
29
- * @internal will be read and written from `QueryInfo`
30
- *
31
- * @deprecated This is an internal API and should not be used directly. This can be removed or changed at any time.
32
- */
33
- _lastWrite;
45
+ didWarnOnFeud = false;
46
+ lastMissing;
34
47
  // The `query` computed property will always reflect the document transformed
35
48
  // by the last run query. `this.options.query` will always reflect the raw
36
49
  // untransformed query to ensure document transforms with runtime conditionals
@@ -67,6 +80,18 @@ export class ObservableQuery {
67
80
  }
68
81
  constructor({ queryManager, options, transformedQuery = queryManager.transform(options.query), }) {
69
82
  this.queryManager = queryManager;
83
+ // Track how often destructive cache methods are called, since we want
84
+ // eviction to override the feud-stopping logic in `shouldAutoRefetch`,
85
+ // by causing it to return true. Wrapping these cache methods is a bit of a
86
+ // hack, but it saves us from having to make eviction counting an official
87
+ // part of the ApolloCache API.
88
+ const { cache } = queryManager;
89
+ if (!destructiveMethodCounts.has(cache)) {
90
+ destructiveMethodCounts.set(cache, 0);
91
+ wrapDestructiveCacheMethod(cache, "evict");
92
+ wrapDestructiveCacheMethod(cache, "modify");
93
+ wrapDestructiveCacheMethod(cache, "reset");
94
+ }
70
95
  // active state
71
96
  this.waitForNetworkResult = options.fetchPolicy === "network-only";
72
97
  this.isTornDown = false;
@@ -77,7 +102,7 @@ export class ObservableQuery {
77
102
  // Make sure we don't store "standby" as the initialFetchPolicy.
78
103
  initialFetchPolicy = fetchPolicy === "standby" ? defaultFetchPolicy : (fetchPolicy), } = options;
79
104
  if (options[variablesUnknownSymbol]) {
80
- invariant(fetchPolicy === "standby", 82);
105
+ invariant(fetchPolicy === "standby", 83);
81
106
  this.variablesUnknown = true;
82
107
  }
83
108
  this.lastQuery = transformedQuery;
@@ -386,7 +411,7 @@ export class ObservableQuery {
386
411
  const queryDef = getQueryDefinition(this.query);
387
412
  const vars = queryDef.variableDefinitions;
388
413
  if (!vars || !vars.some((v) => v.variable.name.value === "variables")) {
389
- __DEV__ && invariant.warn(83, variables, queryDef.name?.value || queryDef);
414
+ __DEV__ && invariant.warn(84, variables, queryDef.name?.value || queryDef);
390
415
  }
391
416
  }
392
417
  if (variables && !equal(this.variables, variables)) {
@@ -394,7 +419,6 @@ export class ObservableQuery {
394
419
  reobserveOptions.variables = this.options.variables =
395
420
  this.getVariablesWithDefaults({ ...this.variables, ...variables });
396
421
  }
397
- this._lastWrite = undefined;
398
422
  return this._reobserve(reobserveOptions, {
399
423
  newNetworkStatus: NetworkStatus.refetch,
400
424
  });
@@ -402,7 +426,7 @@ export class ObservableQuery {
402
426
  fetchMore({ query, variables, context, errorPolicy, updateQuery, }) {
403
427
  invariant(
404
428
  this.options.fetchPolicy !== "cache-only",
405
- 84,
429
+ 85,
406
430
  getOperationName(this.query, "(anonymous)")
407
431
  );
408
432
  const combinedOptions = {
@@ -436,7 +460,7 @@ export class ObservableQuery {
436
460
  let wasUpdated = false;
437
461
  const isCached = this.options.fetchPolicy !== "no-cache";
438
462
  if (!isCached) {
439
- invariant(updateQuery, 85);
463
+ invariant(updateQuery, 86);
440
464
  }
441
465
  const { finalize, pushNotification } = this.pushOperation(NetworkStatus.fetchMore);
442
466
  pushNotification({
@@ -610,7 +634,7 @@ export class ObservableQuery {
610
634
  onError(error);
611
635
  }
612
636
  else {
613
- invariant.error(86, error);
637
+ invariant.error(87, error);
614
638
  }
615
639
  return;
616
640
  }
@@ -861,7 +885,7 @@ export class ObservableQuery {
861
885
  if (!this.didWarnCacheOnlyPolling &&
862
886
  pollInterval &&
863
887
  fetchPolicy === "cache-only") {
864
- __DEV__ && invariant.warn(87, getOperationName(this.query, "(anonymous)"));
888
+ __DEV__ && invariant.warn(88, getOperationName(this.query, "(anonymous)"));
865
889
  this.didWarnCacheOnlyPolling = true;
866
890
  }
867
891
  }
@@ -882,7 +906,6 @@ export class ObservableQuery {
882
906
  if (this.pollingInfo) {
883
907
  if (!isNetworkRequestInFlight(this.networkStatus) &&
884
908
  !this.options.skipPollAttempt?.()) {
885
- this._lastWrite = undefined;
886
909
  this._reobserve({
887
910
  // Most fetchPolicy options don't make sense to use in a polling context, as
888
911
  // users wouldn't want to be polling the cache directly. However, network-only and
@@ -928,7 +951,10 @@ export class ObservableQuery {
928
951
  }
929
952
  _reobserve(newOptions, internalOptions) {
930
953
  this.isTornDown = false;
931
- let { newNetworkStatus } = internalOptions || {};
954
+ let { newNetworkStatus, keepLastMissing } = internalOptions || {};
955
+ if (!keepLastMissing) {
956
+ this.lastMissing = undefined;
957
+ }
932
958
  this.queryManager.obsQueries.add(this);
933
959
  const useDisposableObservable =
934
960
  // Refetching uses a disposable Observable to allow refetches using different
@@ -1069,7 +1095,7 @@ export class ObservableQuery {
1069
1095
  this.queryManager.obsQueries.delete(this);
1070
1096
  this.isTornDown = true;
1071
1097
  this.abortActiveOperations();
1072
- this._lastWrite = undefined;
1098
+ this.lastMissing = undefined;
1073
1099
  }
1074
1100
  transformDocument(document) {
1075
1101
  return this.queryManager.transform(document);
@@ -1127,51 +1153,126 @@ export class ObservableQuery {
1127
1153
  return;
1128
1154
  }
1129
1155
  }
1130
- const { dirty } = this;
1156
+ const { dirty, lastMissing } = this;
1157
+ const { fetchPolicy } = this.options;
1131
1158
  this.resetNotifications();
1132
- if (dirty &&
1133
- (this.options.fetchPolicy === "cache-only" ||
1134
- this.options.fetchPolicy === "cache-and-network" ||
1135
- !this.activeOperations.size)) {
1136
- const diff = this.getCacheDiff();
1137
- if (
1138
- // `fromOptimisticTransaction` is not available through the `cache.diff`
1139
- // code path, so we need to check it this way
1140
- equal(diff.result, this.getCacheDiff({ optimistic: false }).result)) {
1141
- //If this diff did not come from an optimistic transaction
1142
- // make the ObservableQuery "reobserve" the latest data
1143
- // using a temporary fetch policy of "cache-first", so complete cache
1144
- // results have a chance to be delivered without triggering additional
1145
- // network requests, even when options.fetchPolicy is "network-only"
1146
- // or "cache-and-network". All other fetch policies are preserved by
1147
- // this method, and are handled by calling oq.reobserve(). If this
1148
- // reobservation is spurious, distinctUntilChanged still has a
1149
- // chance to catch it before delivery to ObservableQuery subscribers.
1150
- this.reobserveCacheFirst();
1159
+ if (!dirty ||
1160
+ (fetchPolicy !== "cache-only" &&
1161
+ fetchPolicy !== "cache-and-network" &&
1162
+ this.activeOperations.size)) {
1163
+ return;
1164
+ }
1165
+ const diff = this.getCacheDiff();
1166
+ const current = this.getCurrentResult();
1167
+ // `fromOptimisticTransaction` is not available through the `cache.diff`
1168
+ // code path, so we need to check whether the cache result is an optimistic
1169
+ // result this way.
1170
+ const isOptimistic = !equal(diff.result, this.getCacheDiff({ optimistic: false }).result);
1171
+ if (
1172
+ // When this diff came from an optimistic transaction, deliver the
1173
+ // current cache data to the ObservableQuery, but don't perform a
1174
+ // reobservation, since oq.reobserveCacheFirst might make a network
1175
+ // request, and we never want to trigger network requests in the
1176
+ // middle of optimistic updates.
1177
+ isOptimistic ||
1178
+ // If we get a cache update in the middle of streaming (possible with
1179
+ // cache-and-network fetch policy), just deliver the cache value without
1180
+ // going through the full reobserve which would otherwise trigger another
1181
+ // request (deduplication should kick in, but doing so replays any
1182
+ // previous emits from the link chain, which get rewritten into the cache
1183
+ // and might clobber this cache update)
1184
+ (!diff.complete && current.networkStatus === NetworkStatus.streaming)) {
1185
+ this.deliverCacheDiff(diff);
1186
+ return;
1187
+ }
1188
+ if (diff.complete) {
1189
+ this.lastMissing = undefined;
1190
+ }
1191
+ else if (!lastMissing ||
1192
+ // If a destructive cache method has been called since the last recorded
1193
+ // incomplete result, there's a chance fetching this data again will
1194
+ // restore what was evicted, even though the cache result looks the same
1195
+ // as before.
1196
+ lastMissing.dmCount !== destructiveMethodCounts.get(this.cache) ||
1197
+ !equal(lastMissing.variables, this.variables) ||
1198
+ !equal(lastMissing.missing, diff.missing?.missing)) {
1199
+ this.didWarnOnFeud = false;
1200
+ this.lastMissing = {
1201
+ variables: this.variables,
1202
+ missing: diff.missing?.missing,
1203
+ dmCount: destructiveMethodCounts.get(this.cache),
1204
+ };
1205
+ }
1206
+ else if (
1207
+ // reobserveCacheFirst with cache-only fetch policy only calls
1208
+ // reobserve which never fetches from the network so we are ok
1209
+ // allowing cache-only queries to fallthrough to reobserveCacheFirst.
1210
+ // This also prevents the feud warning which would be confusing for a
1211
+ // cache-only query anyways.
1212
+ fetchPolicy !== "cache-only") {
1213
+ // If we've fallen through to this case, a cache emit has returned the
1214
+ // same missing fields which means at least one value on the fields we've
1215
+ // already delivered have changed. We are ok delivering the updated
1216
+ // partial result in this case to keep the result as fresh as possible. We
1217
+ // NEVER want to downgrade this query from a complete query to a partial
1218
+ // query though, so we also make sure we only deliver if the previous
1219
+ // result was also partial.
1220
+ if (current.dataState === "partial") {
1221
+ this.deliverCacheDiff(diff);
1151
1222
  }
1152
- else {
1153
- // If this diff came from an optimistic transaction, deliver the
1154
- // current cache data to the ObservableQuery, but don't perform a
1155
- // reobservation, since oq.reobserveCacheFirst might make a network
1156
- // request, and we never want to trigger network requests in the
1157
- // middle of optimistic updates.
1158
- this.input.next({
1159
- kind: "N",
1160
- value: {
1161
- data: diff.result,
1162
- dataState: diff.dataState,
1163
- networkStatus: NetworkStatus.ready,
1164
- loading: false,
1165
- error: undefined,
1166
- partial: !diff.complete,
1167
- },
1168
- source: "cache",
1169
- query: this.query,
1170
- variables: this.variables,
1171
- meta: {},
1172
- });
1223
+ // If the (partial) result is the same as the last partial result
1224
+ // we recorded from a previous broadcast (and the variables match
1225
+ // too), avoid calling reobserveCacheFirst to refetch this query
1226
+ // again. If we allow refetching anytime this result becomes partial,
1227
+ // we risk feuds between queries competing to update the same data in
1228
+ // incompatible ways, which can lead to an endless cycle of cache
1229
+ // broadcasts and useless network requests. As with any
1230
+ // feud, eventually one side must step back from the brink,
1231
+ // letting the other side(s) have the last word(s). There may
1232
+ // be other points where we could break this cycle, such as
1233
+ // silencing the broadcast for cache.writeQuery (not a good
1234
+ // idea, since it just delays the feud a bit) or somehow
1235
+ // avoiding the network request that just happened (also bad,
1236
+ // because the server could return useful new data). All
1237
+ // options considered, returning early and stopping the
1238
+ // reobserveCacheFirst cycle seems to be the least damaging place to
1239
+ // break the cycle because it allows read functions/custom scalars to
1240
+ // be applied to the feuding query while avoiding the endless cycle of
1241
+ // requests.
1242
+ if (__DEV__ && !this.didWarnOnFeud) {
1243
+ this.didWarnOnFeud = true;
1244
+ warnOnFeud(this.query, diff);
1173
1245
  }
1246
+ return;
1174
1247
  }
1248
+ //If this diff did not come from an optimistic transaction
1249
+ // make the ObservableQuery "reobserve" the latest data
1250
+ // using a temporary fetch policy of "cache-first", so complete cache
1251
+ // results have a chance to be delivered without triggering additional
1252
+ // network requests, even when options.fetchPolicy is "network-only"
1253
+ // or "cache-and-network". All other fetch policies are preserved by
1254
+ // this method, and are handled by calling oq.reobserve(). If this
1255
+ // reobservation is spurious, distinctUntilChanged still has a
1256
+ // chance to catch it before delivery to ObservableQuery subscribers.
1257
+ this.reobserveCacheFirst();
1258
+ }
1259
+ deliverCacheDiff(diff) {
1260
+ const current = this.getCurrentResult();
1261
+ this.input.next({
1262
+ kind: "N",
1263
+ value: {
1264
+ data: diff.result,
1265
+ dataState: diff.dataState,
1266
+ networkStatus: current.networkStatus,
1267
+ loading: current.loading,
1268
+ error: undefined,
1269
+ partial: !diff.complete,
1270
+ },
1271
+ source: "cache",
1272
+ query: this.query,
1273
+ variables: this.variables,
1274
+ meta: {},
1275
+ });
1175
1276
  }
1176
1277
  activeOperations = new Set();
1177
1278
  pushOperation(networkStatus) {
@@ -1234,6 +1335,7 @@ export class ObservableQuery {
1234
1335
  // exception for cache-only queries - we reset them into a "ready" state
1235
1336
  // as we won't trigger a refetch for them
1236
1337
  const resetToEmpty = this.options.fetchPolicy === "cache-only";
1338
+ this.lastMissing = undefined;
1237
1339
  this.setResult(resetToEmpty ? empty : uninitialized, {
1238
1340
  shouldEmit: resetToEmpty ? 1 /* EmitBehavior.force */ : 2 /* EmitBehavior.never */,
1239
1341
  });
@@ -1378,7 +1480,10 @@ export class ObservableQuery {
1378
1480
  reobserveCacheFirst() {
1379
1481
  const { fetchPolicy, nextFetchPolicy } = this.options;
1380
1482
  if (fetchPolicy === "cache-and-network" || fetchPolicy === "network-only") {
1381
- this.reobserve({
1483
+ // Preserve this.lastMissing so a cache update that triggers this
1484
+ // reobserve doesn't reset feud detection. All user-initiated calls to
1485
+ // reobserve (refetch/poll/reobserve, etc) should clear it.
1486
+ this._reobserve({
1382
1487
  fetchPolicy: "cache-first",
1383
1488
  // Use a temporary nextFetchPolicy function that replaces itself with the
1384
1489
  // previous nextFetchPolicy value and returns the original fetchPolicy.
@@ -1394,10 +1499,10 @@ export class ObservableQuery {
1394
1499
  // Otherwise go back to the original this.options.fetchPolicy.
1395
1500
  return fetchPolicy;
1396
1501
  },
1397
- });
1502
+ }, { keepLastMissing: true });
1398
1503
  }
1399
1504
  else {
1400
- this.reobserve();
1505
+ this._reobserve(undefined, { keepLastMissing: true });
1401
1506
  }
1402
1507
  }
1403
1508
  getVariablesWithDefaults(variables) {
@@ -1406,7 +1511,7 @@ export class ObservableQuery {
1406
1511
  }
1407
1512
  export function logMissingFieldErrors(missing) {
1408
1513
  if (__DEV__ && missing) {
1409
- __DEV__ && invariant.debug(88, missing);
1514
+ __DEV__ && invariant.debug(89, missing);
1410
1515
  }
1411
1516
  }
1412
1517
  function isEqualQuery(a, b) {
@@ -1449,4 +1554,7 @@ function getTrackingOperatorPromise(defaultValue) {
1449
1554
  });
1450
1555
  return { promise, operator };
1451
1556
  }
1557
+ function warnOnFeud(query, diff) {
1558
+ __DEV__ && invariant.warn(90, getOperationName(query, "(anonymous)"), diff.missing?.missing);
1559
+ }
1452
1560
  //# sourceMappingURL=ObservableQuery.js.map