@apollo/client 4.3.0-alpha.4 → 4.3.0-alpha.5

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 (150) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/README.md +3 -7
  3. package/__cjs/cache/core/cache.cjs +1 -1
  4. package/__cjs/cache/core/cache.cjs.map +1 -1
  5. package/__cjs/cache/core/cache.d.cts +5 -1
  6. package/__cjs/cache/inmemory/entityStore.cjs +3 -3
  7. package/__cjs/cache/inmemory/inMemoryCache.cjs +0 -4
  8. package/__cjs/cache/inmemory/inMemoryCache.cjs.map +1 -1
  9. package/__cjs/cache/inmemory/inMemoryCache.d.cts +3 -4
  10. package/__cjs/cache/inmemory/key-extractor.cjs +1 -1
  11. package/__cjs/cache/inmemory/policies.cjs +4 -4
  12. package/__cjs/cache/inmemory/readFromStore.cjs +71 -54
  13. package/__cjs/cache/inmemory/readFromStore.cjs.map +1 -1
  14. package/__cjs/cache/inmemory/readFromStore.d.cts +1 -1
  15. package/__cjs/cache/inmemory/types.d.cts +1 -2
  16. package/__cjs/cache/inmemory/writeToStore.cjs +4 -4
  17. package/__cjs/core/ApolloClient.cjs +21 -22
  18. package/__cjs/core/ApolloClient.cjs.map +1 -1
  19. package/__cjs/core/ApolloClient.d.cts +44 -10
  20. package/__cjs/core/ObservableQuery.cjs +46 -5
  21. package/__cjs/core/ObservableQuery.cjs.map +1 -1
  22. package/__cjs/core/ObservableQuery.d.cts +12 -1
  23. package/__cjs/core/QueryInfo.cjs +177 -154
  24. package/__cjs/core/QueryInfo.cjs.map +1 -1
  25. package/__cjs/core/QueryInfo.d.cts +13 -3
  26. package/__cjs/core/QueryManager.cjs +49 -27
  27. package/__cjs/core/QueryManager.cjs.map +1 -1
  28. package/__cjs/core/RefetchEventManager.cjs +3 -3
  29. package/__cjs/core/dataStateErrorCache.cjs +12 -0
  30. package/__cjs/core/dataStateErrorCache.cjs.map +1 -0
  31. package/__cjs/core/dataStateErrorCache.d.cts +3 -0
  32. package/__cjs/core/types.d.cts +2 -0
  33. package/__cjs/incremental/handlers/graphql17Alpha9.cjs +34 -11
  34. package/__cjs/incremental/handlers/graphql17Alpha9.cjs.map +1 -1
  35. package/__cjs/incremental/handlers/graphql17Alpha9.d.cts +26 -3
  36. package/__cjs/incremental/types.d.cts +41 -3
  37. package/__cjs/invariantErrorCodes.cjs +50 -31
  38. package/__cjs/react/hooks/useBackgroundQuery.cjs.map +1 -1
  39. package/__cjs/react/hooks/useBackgroundQuery.d.cts +100 -47
  40. package/__cjs/react/hooks/useLazyQuery.cjs.map +1 -1
  41. package/__cjs/react/hooks/useLazyQuery.d.cts +26 -20
  42. package/__cjs/react/hooks/useLoadableQuery.cjs.map +1 -1
  43. package/__cjs/react/hooks/useLoadableQuery.d.cts +23 -15
  44. package/__cjs/react/hooks/useMutation.cjs.map +1 -1
  45. package/__cjs/react/hooks/useMutation.d.cts +10 -7
  46. package/__cjs/react/hooks/useQuery.cjs.map +1 -1
  47. package/__cjs/react/hooks/useQuery.d.cts +74 -32
  48. package/__cjs/react/hooks/useQueryRefHandlers.cjs.map +1 -1
  49. package/__cjs/react/hooks/useSuspenseQuery.cjs.map +1 -1
  50. package/__cjs/react/hooks/useSuspenseQuery.d.cts +83 -34
  51. package/__cjs/react/internal/cache/QueryReference.cjs.map +1 -1
  52. package/__cjs/react/internal/types.d.cts +14 -3
  53. package/__cjs/react/query-preloader/createQueryPreloader.cjs.map +1 -1
  54. package/__cjs/react/query-preloader/createQueryPreloader.d.cts +20 -5
  55. package/__cjs/testing/core/mocking/mockLink.cjs +21 -1
  56. package/__cjs/testing/core/mocking/mockLink.cjs.map +1 -1
  57. package/__cjs/testing/core/mocking/mockLink.d.cts +1 -2
  58. package/__cjs/utilities/internal/addDeferFragmentLabels.cjs +33 -0
  59. package/__cjs/utilities/internal/addDeferFragmentLabels.cjs.map +1 -0
  60. package/__cjs/utilities/internal/addDeferFragmentLabels.d.cts +3 -0
  61. package/__cjs/utilities/internal/getDirectiveArgValue.cjs +19 -0
  62. package/__cjs/utilities/internal/getDirectiveArgValue.cjs.map +1 -0
  63. package/__cjs/utilities/internal/getDirectiveArgValue.d.cts +9 -0
  64. package/__cjs/utilities/internal/index.cjs +6 -2
  65. package/__cjs/utilities/internal/index.cjs.map +1 -1
  66. package/__cjs/utilities/internal/index.d.cts +2 -0
  67. package/__cjs/version.cjs +1 -1
  68. package/cache/core/cache.d.ts +5 -1
  69. package/cache/core/cache.js +1 -1
  70. package/cache/core/cache.js.map +1 -1
  71. package/cache/inmemory/entityStore.js +3 -3
  72. package/cache/inmemory/inMemoryCache.d.ts +3 -4
  73. package/cache/inmemory/inMemoryCache.js +1 -5
  74. package/cache/inmemory/inMemoryCache.js.map +1 -1
  75. package/cache/inmemory/key-extractor.js +1 -1
  76. package/cache/inmemory/policies.js +4 -4
  77. package/cache/inmemory/readFromStore.d.ts +1 -1
  78. package/cache/inmemory/readFromStore.js +72 -55
  79. package/cache/inmemory/readFromStore.js.map +1 -1
  80. package/cache/inmemory/types.d.ts +1 -2
  81. package/cache/inmemory/types.js.map +1 -1
  82. package/cache/inmemory/writeToStore.js +4 -4
  83. package/core/ApolloClient.d.ts +44 -10
  84. package/core/ApolloClient.js +21 -22
  85. package/core/ApolloClient.js.map +1 -1
  86. package/core/ObservableQuery.d.ts +12 -1
  87. package/core/ObservableQuery.js +46 -5
  88. package/core/ObservableQuery.js.map +1 -1
  89. package/core/QueryInfo.d.ts +13 -3
  90. package/core/QueryInfo.js +173 -154
  91. package/core/QueryInfo.js.map +1 -1
  92. package/core/QueryManager.js +49 -27
  93. package/core/QueryManager.js.map +1 -1
  94. package/core/RefetchEventManager.js +3 -3
  95. package/core/dataStateErrorCache.d.ts +3 -0
  96. package/core/dataStateErrorCache.js +9 -0
  97. package/core/dataStateErrorCache.js.map +1 -0
  98. package/core/types.d.ts +2 -0
  99. package/core/types.js.map +1 -1
  100. package/incremental/handlers/graphql17Alpha9.d.ts +26 -3
  101. package/incremental/handlers/graphql17Alpha9.js +35 -12
  102. package/incremental/handlers/graphql17Alpha9.js.map +1 -1
  103. package/incremental/types.d.ts +41 -3
  104. package/incremental/types.js.map +1 -1
  105. package/invariantErrorCodes.js +50 -31
  106. package/package.json +1 -1
  107. package/react/hooks/useBackgroundQuery.d.ts +100 -47
  108. package/react/hooks/useBackgroundQuery.js.map +1 -1
  109. package/react/hooks/useLazyQuery.d.ts +26 -20
  110. package/react/hooks/useLazyQuery.js.map +1 -1
  111. package/react/hooks/useLoadableQuery.d.ts +23 -15
  112. package/react/hooks/useLoadableQuery.js.map +1 -1
  113. package/react/hooks/useMutation.d.ts +10 -7
  114. package/react/hooks/useMutation.js.map +1 -1
  115. package/react/hooks/useQuery.d.ts +74 -32
  116. package/react/hooks/useQuery.js.map +1 -1
  117. package/react/hooks/useQueryRefHandlers.js.map +1 -1
  118. package/react/hooks/useSuspenseQuery.d.ts +83 -34
  119. package/react/hooks/useSuspenseQuery.js.map +1 -1
  120. package/react/hooks-compiled/useBackgroundQuery.d.ts +100 -47
  121. package/react/hooks-compiled/useBackgroundQuery.js.map +1 -1
  122. package/react/hooks-compiled/useLazyQuery.d.ts +26 -20
  123. package/react/hooks-compiled/useLazyQuery.js.map +1 -1
  124. package/react/hooks-compiled/useLoadableQuery.d.ts +23 -15
  125. package/react/hooks-compiled/useLoadableQuery.js.map +1 -1
  126. package/react/hooks-compiled/useMutation.d.ts +10 -7
  127. package/react/hooks-compiled/useMutation.js.map +1 -1
  128. package/react/hooks-compiled/useQuery.d.ts +74 -32
  129. package/react/hooks-compiled/useQuery.js.map +1 -1
  130. package/react/hooks-compiled/useQueryRefHandlers.js.map +1 -1
  131. package/react/hooks-compiled/useSuspenseQuery.d.ts +83 -34
  132. package/react/hooks-compiled/useSuspenseQuery.js.map +1 -1
  133. package/react/internal/cache/QueryReference.js.map +1 -1
  134. package/react/internal/types.d.ts +15 -4
  135. package/react/internal/types.js.map +1 -1
  136. package/react/query-preloader/createQueryPreloader.d.ts +20 -5
  137. package/react/query-preloader/createQueryPreloader.js.map +1 -1
  138. package/testing/core/mocking/mockLink.d.ts +1 -2
  139. package/testing/core/mocking/mockLink.js +21 -1
  140. package/testing/core/mocking/mockLink.js.map +1 -1
  141. package/utilities/internal/addDeferFragmentLabels.d.ts +3 -0
  142. package/utilities/internal/addDeferFragmentLabels.js +30 -0
  143. package/utilities/internal/addDeferFragmentLabels.js.map +1 -0
  144. package/utilities/internal/getDirectiveArgValue.d.ts +9 -0
  145. package/utilities/internal/getDirectiveArgValue.js +16 -0
  146. package/utilities/internal/getDirectiveArgValue.js.map +1 -0
  147. package/utilities/internal/index.d.ts +2 -0
  148. package/utilities/internal/index.js +2 -0
  149. package/utilities/internal/index.js.map +1 -1
  150. package/version.js +1 -1
@@ -1,5 +1,6 @@
1
1
  import type { DocumentNode, FormattedExecutionResult } from "graphql";
2
2
  import type { Cache } from "@apollo/client/cache";
3
+ import type { Incremental } from "@apollo/client/incremental";
3
4
  import type { ApolloLink } from "@apollo/client/link";
4
5
  import type { Unmasked } from "@apollo/client/masking";
5
6
  import type { ExtensionsWithStreamInfo } from "@apollo/client/utilities/internal";
@@ -19,6 +20,7 @@ interface LastWrite {
19
20
  result: FormattedExecutionResult<any, ExtensionsWithStreamInfo>;
20
21
  variables: ApolloClient.WatchQueryOptions["variables"];
21
22
  dmCount: number | undefined;
23
+ hasNext: boolean;
22
24
  }
23
25
  interface OperationInfo<TData, TVariables extends OperationVariables, AllowedCacheWriteBehavior = CacheWriteBehavior> {
24
26
  document: DocumentNode | TypedDocumentNode<TData, TVariables>;
@@ -39,8 +41,14 @@ export declare class QueryInfo<TData, TVariables extends OperationVariables = Op
39
41
  constructor(queryManager: QueryManager, observableQuery?: ObservableQuery<any, any>);
40
42
  /**
41
43
  * @internal
42
- * For feud-preventing behaviour, `lastWrite` should be shared by all `QueryInfo` instances of an `ObservableQuery`.
43
- * In the case of a standalone `QueryInfo`, we will keep a local version.
44
+ * Tracks the last result written to the cache so that `shouldWrite` can skip
45
+ * an identical write. Since a `QueryInfo` only ever represents a single
46
+ * network request, this is shared by all `QueryInfo` instances of an
47
+ * `ObservableQuery`. A standalone `QueryInfo` keeps a local version.
48
+ *
49
+ * A network result that was explicitly asked for always takes precedence over
50
+ * what is already cached, so `ObservableQuery.refetch` and polling clear this
51
+ * value before starting their request.
44
52
  *
45
53
  * @deprecated This is an internal API and should not be used directly. This can be removed or changed at any time.
46
54
  */
@@ -50,10 +58,12 @@ export declare class QueryInfo<TData, TVariables extends OperationVariables = Op
50
58
  resetLastWrite(): void;
51
59
  private shouldWrite;
52
60
  get hasNext(): boolean;
61
+ get incrementalHandler(): Incremental.Handler<Record<string, unknown>>;
53
62
  private maybeHandleIncrementalResult;
54
- markQueryResult(incoming: ApolloLink.Result<TData>, { document: query, variables, errorPolicy, cacheWriteBehavior, returnPartialData, fetchPolicy, networkStatus, }: OperationInfo<TData, TVariables> & {
63
+ markQueryResult(incoming: ApolloLink.Result<TData>, { document: query, variables, errorPolicy, cacheWriteBehavior, returnPartialData, fetchPolicy, networkStatus, prunePendingDeferFragments: prune, }: OperationInfo<TData, TVariables> & {
55
64
  fetchPolicy: WatchQueryFetchPolicy;
56
65
  networkStatus: NetworkStatus;
66
+ prunePendingDeferFragments: boolean;
57
67
  }): MarkQueryResult<DataValue.Complete<TData> | DataValue.Streaming<TData>, ExtensionsWithStreamInfo>;
58
68
  private getIncrementalInfo;
59
69
  private getDiff;
package/core/QueryInfo.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { equal } from "@wry/equality";
2
2
  import { Trie } from "@wry/trie";
3
- import { getOperationName, graphQLResultHasError, handleIncrementalSymbol, hasDirectives, streamInfoSymbol, } from "@apollo/client/utilities/internal";
3
+ import { __DEV__ } from "@apollo/client/utilities/environment";
4
+ import { getOperationName, graphQLResultHasError, handleIncrementalSymbol, hasDirectives, } from "@apollo/client/utilities/internal";
4
5
  import { invariant } from "@apollo/client/utilities/invariant";
5
6
  import { NetworkStatus } from "./networkStatus.js";
6
7
  const IGNORE = {};
@@ -41,10 +42,10 @@ export class QueryInfo {
41
42
  this.observableQuery = observableQuery;
42
43
  this.queryManager = queryManager;
43
44
  // Track how often cache.evict is called, since we want eviction to
44
- // override the feud-stopping logic in the markQueryResult method, by
45
- // causing shouldWrite to return true. Wrapping the cache.evict method
46
- // is a bit of a hack, but it saves us from having to make eviction
47
- // counting an official part of the ApolloCache API.
45
+ // override the write-skipping logic in `shouldWrite`, by causing it to
46
+ // return true. Wrapping the cache.evict method is a bit of a hack, but it
47
+ // saves us from having to make eviction counting an official part of the
48
+ // ApolloCache API.
48
49
  if (!destructiveMethodCounts.has(cache)) {
49
50
  destructiveMethodCounts.set(cache, 0);
50
51
  wrapDestructiveCacheMethod(cache, "evict");
@@ -54,8 +55,14 @@ export class QueryInfo {
54
55
  }
55
56
  /**
56
57
  * @internal
57
- * For feud-preventing behaviour, `lastWrite` should be shared by all `QueryInfo` instances of an `ObservableQuery`.
58
- * In the case of a standalone `QueryInfo`, we will keep a local version.
58
+ * Tracks the last result written to the cache so that `shouldWrite` can skip
59
+ * an identical write. Since a `QueryInfo` only ever represents a single
60
+ * network request, this is shared by all `QueryInfo` instances of an
61
+ * `ObservableQuery`. A standalone `QueryInfo` keeps a local version.
62
+ *
63
+ * A network result that was explicitly asked for always takes precedence over
64
+ * what is already cached, so `ObservableQuery.refetch` and polling clear this
65
+ * value before starting their request.
59
66
  *
60
67
  * @deprecated This is an internal API and should not be used directly. This can be removed or changed at any time.
61
68
  */
@@ -71,33 +78,34 @@ export class QueryInfo {
71
78
  }
72
79
  shouldWrite(result, variables) {
73
80
  const { lastWrite } = this;
74
- return !(lastWrite &&
81
+ return (!lastWrite ||
75
82
  // If cache.evict has been called since the last time we wrote this
76
83
  // data into the cache, there's a chance writing this result into
77
84
  // the cache will repair what was evicted.
78
- lastWrite.dmCount === destructiveMethodCounts.get(this.cache) &&
79
- equal(variables, lastWrite.variables) &&
80
- equal(result.data, lastWrite.result.data) &&
85
+ lastWrite.dmCount !== destructiveMethodCounts.get(this.cache) ||
86
+ !equal(variables, lastWrite.variables) ||
87
+ !equal(result.data, lastWrite.result.data) ||
81
88
  // We have to compare these values because its possible the final chunk
82
89
  // emitted in the incremental result is just `hasNext: false`. This
83
90
  // ensures we trigger a cache write when we get `isLastChunk: true`.
84
- result.extensions?.[streamInfoSymbol] ===
85
- lastWrite.result.extensions?.[streamInfoSymbol]);
91
+ lastWrite.hasNext !== this.hasNext);
86
92
  }
87
93
  get hasNext() {
88
94
  return this.incremental ? this.incremental.hasNext : false;
89
95
  }
96
+ get incrementalHandler() {
97
+ return this.queryManager.incrementalHandler;
98
+ }
90
99
  maybeHandleIncrementalResult(cacheData, incoming, query) {
91
- const { incrementalHandler } = this.queryManager;
92
- if (incrementalHandler.isIncrementalResult(incoming)) {
93
- this.incremental ||= incrementalHandler.startRequest({
100
+ if (this.incrementalHandler.isIncrementalResult(incoming)) {
101
+ this.incremental ||= this.incrementalHandler.startRequest({
94
102
  query,
95
103
  });
96
104
  return this.incremental.handle(cacheData, incoming);
97
105
  }
98
106
  return incoming;
99
107
  }
100
- markQueryResult(incoming, { document: query, variables, errorPolicy, cacheWriteBehavior, returnPartialData, fetchPolicy, networkStatus, }) {
108
+ markQueryResult(incoming, { document: query, variables, errorPolicy, cacheWriteBehavior, returnPartialData, fetchPolicy, networkStatus, prunePendingDeferFragments: prune, }) {
101
109
  const diffOptions = {
102
110
  query,
103
111
  variables,
@@ -107,154 +115,160 @@ export class QueryInfo {
107
115
  // requests. To allow future notify timeouts, diff and dirty are reset as well.
108
116
  this.observableQuery?.["resetNotifications"]();
109
117
  const skipCache = cacheWriteBehavior === 0 /* CacheWriteBehavior.FORBID */;
110
- const lastDiff = skipCache ? undefined : (this.getDiff({
118
+ const diff = skipCache ? undefined : (this.getDiff({
111
119
  ...diffOptions,
112
- // Always request partial data to ensure the network incremental
120
+ // We usually request partial data to ensure the network incremental
113
121
  // result is merged with all existing data (especially true to
114
- // maintain @stream arrays with partial list items in the right order)
115
- returnPartialData: true,
116
- }));
117
- const incrementalResult = this.maybeHandleIncrementalResult(lastDiff?.result, incoming, query);
122
+ // maintain @stream arrays with partial list items in the right order
123
+ // or when chunk might otherwise replace a partial non-normalized
124
+ // object), but if we are about to throw away the result anyways due
125
+ // to the error policy (which early returns below), prune any
126
+ // pending boundaries so that CombinedGraphQLErrors contains the
127
+ // right `data` value.
128
+ returnPartialData: errorPolicy !== "none" ||
129
+ !this.incrementalHandler.extractErrors(incoming)?.length,
130
+ }, this.getIncrementalInfo({ prune })));
131
+ const incrementalResult = this.maybeHandleIncrementalResult(diff?.result, incoming, query);
118
132
  let result = {
119
133
  ...incrementalResult,
120
134
  dataState: incrementalResult.data == null ? "empty" : "complete",
121
135
  };
136
+ const hasPendingDefer = this.incremental
137
+ ?.getPendingWithInfo?.()
138
+ .some((pending) => pending.type === "defer" && !pending.delivered);
139
+ if (hasPendingDefer ||
140
+ // The Defer20220824Handler cannot track pending/completed incremental
141
+ // chunks due to its data format so we naively set dataState to
142
+ // streaming if we are still processing chunks. The only case where
143
+ // streaming is incorrect and should actually be complete is when
144
+ // both a @defer and @stream boundary is present and the @defer chunk
145
+ // has completed before the `@stream` array.
146
+ //
147
+ // Assigning the naive "streaming" value avoids a much more expensive
148
+ // pass over `result.data` that would otherwise need to traverse the
149
+ // selection sets and evaluate the data object at each defer boundary
150
+ // to see if it fulfills the selection set. For such a narrow case where
151
+ // its incorrect on a format that is now outdated is not worth the
152
+ // fix so we are ok with reporting a `streaming` here.
153
+ (!this.incremental?.getPendingWithInfo &&
154
+ this.hasNext &&
155
+ hasDirectives(["defer"], query))) {
156
+ result.dataState = "streaming";
157
+ }
122
158
  if (skipCache) {
123
- const hasPendingDefer = this.incremental?.pending?.some((pending) => this.incremental?.getPendingType?.(pending.id) === "defer");
124
- if (hasPendingDefer ||
125
- // The Defer20220824Handler cannot track pending/completed incremental
126
- // chunks due to its data format so we naively set dataState to
127
- // streaming if we are still processing chunks. The only case where
128
- // streaming is incorrect and should actually be complete is when
129
- // both a @defer and @stream boundary is present and the @defer chunk
130
- // has completed before the `@stream` array.
131
- //
132
- // Assigning the naive "streaming" value avoids a much more expensive
133
- // pass over `result.data` that would otherwise need to traverse the
134
- // selection sets and evaluate the data object at each defer boundary
135
- // to see if it fulfills the selection set. For such a narrow case where
136
- // its incorrect on a format that is now outdated is not worth the
137
- // fix so we are ok with reporting a `streaming` here.
138
- (!this.incremental?.pending &&
139
- this.hasNext &&
140
- hasDirectives(["defer"], query))) {
141
- result.dataState = "streaming";
142
- }
143
159
  return result;
144
160
  }
145
- if (shouldWriteResult(result, errorPolicy)) {
146
- // Using a transaction here so we have a chance to read the result
147
- // back from the cache before the watch callback fires as a result
148
- // of writeQuery, so we can store the new diff quietly and ignore
149
- // it when we receive it redundantly from the watch callback.
150
- this.cache.batch({
151
- onWatchUpdated: (
152
- // all additional options on ObservableQuery.CacheWatchOptions are
153
- // optional so we can use the type here
154
- watch, diff) => {
155
- if (watch.watcher === this.observableQuery) {
156
- // see comment on `lastOwnDiff` for explanation
157
- watch.lastOwnDiff = diff;
158
- }
159
- },
160
- update: (cache) => {
161
- const shouldWrite = this.shouldWrite(result, variables);
162
- if (shouldWrite) {
163
- cache.writeQuery({
164
- query,
165
- data: result.data,
166
- variables,
167
- overwrite: cacheWriteBehavior === 1 /* CacheWriteBehavior.OVERWRITE */,
168
- extensions: result.extensions,
169
- });
170
- this.lastWrite = {
171
- result,
172
- variables,
173
- dmCount: destructiveMethodCounts.get(this.cache),
174
- };
175
- }
176
- else {
177
- // If result is the same as the last result we received from
178
- // the network (and the variables match too), avoid writing
179
- // result into the cache again. The wisdom of skipping this
180
- // cache write is far from obvious, since any cache write
181
- // could be the one that puts the cache back into a desired
182
- // state, fixing corruption or missing data. However, if we
183
- // always write every network result into the cache, we enable
184
- // feuds between queries competing to update the same data in
185
- // incompatible ways, which can lead to an endless cycle of
186
- // cache broadcasts and useless network requests. As with any
187
- // feud, eventually one side must step back from the brink,
188
- // letting the other side(s) have the last word(s). There may
189
- // be other points where we could break this cycle, such as
190
- // silencing the broadcast for cache.writeQuery (not a good
191
- // idea, since it just delays the feud a bit) or somehow
192
- // avoiding the network request that just happened (also bad,
193
- // because the server could return useful new data). All
194
- // options considered, skipping this cache write seems to be
195
- // the least damaging place to break the cycle, because it
196
- // reflects the intuition that we recently wrote this exact
197
- // result into the cache, so the cache *should* already/still
198
- // contain this data. If some other query has clobbered that
199
- // data in the meantime, that's too bad, but there will be no
200
- // winners if every query blindly reverts to its own version
201
- // of the data. This approach also gives the network a chance
202
- // to return new data, which will be written into the cache as
203
- // usual, notifying only those queries that are directly
204
- // affected by the cache updates, as usual. In the future, an
205
- // even more sophisticated cache could perhaps prevent or
206
- // mitigate the clobbering somehow, but that would make this
207
- // particular cache write even less important, and thus
208
- // skipping it would be even safer than it is today.
209
- if (lastDiff && lastDiff.complete) {
210
- // Reuse data from the last good (complete) diff that we
211
- // received, when possible.
212
- result = {
213
- ...result,
214
- data: lastDiff.result,
215
- dataState: "complete",
216
- };
217
- return;
218
- }
219
- // If the previous this.diff was incomplete, fall through to
220
- // re-reading the latest data with cache.diff, below.
221
- }
222
- const isNetworkOnly = fetchPolicy === "network-only" &&
223
- networkStatus !== NetworkStatus.refetch;
224
- const { dataState, result: diffResult } = this.getDiff({
225
- ...diffOptions,
226
- // Never deliver partial data for network-only requests
227
- returnPartialData: returnPartialData && !isNetworkOnly,
228
- }, this.getIncrementalInfo(result, { isNetworkOnly }));
229
- if (dataState === "complete" ||
230
- (returnPartialData && dataState === "partial" && shouldWrite) ||
231
- (this.hasNext && dataState === "streaming")) {
232
- result = { ...result, data: diffResult, dataState };
233
- }
234
- },
235
- });
236
- }
237
- else {
161
+ if (!shouldWriteResult(result, errorPolicy)) {
238
162
  this.lastWrite = void 0;
163
+ return result;
239
164
  }
165
+ let written = false;
166
+ // Using a transaction here so we have a chance to read the result
167
+ // back from the cache before the watch callback fires as a result
168
+ // of writeQuery, so we can store the new diff quietly and ignore
169
+ // it when we receive it redundantly from the watch callback.
170
+ this.cache.batch({
171
+ onWatchUpdated: (
172
+ // all additional options on ObservableQuery.CacheWatchOptions are
173
+ // optional so we can use the type here
174
+ watch, diff) => {
175
+ if (watch.watcher === this.observableQuery) {
176
+ // see comment on `lastOwnDiff` for explanation
177
+ watch.lastOwnDiff = diff;
178
+ }
179
+ },
180
+ update: (cache) => {
181
+ const shouldWrite = this.shouldWrite(result, variables);
182
+ // If result is the same as the last result we received from
183
+ // the network (and the variables match too), avoid writing
184
+ // result into the cache again. The wisdom of skipping this
185
+ // cache write is far from obvious, since any cache write
186
+ // could be the one that puts the cache back into a desired
187
+ // state, fixing corruption or missing data. However, if we
188
+ // always write every network result into the cache, we enable
189
+ // feuds between queries competing to update the same data in
190
+ // incompatible ways, which can lead to an endless cycle of
191
+ // cache broadcasts and useless network requests. As with any
192
+ // feud, eventually one side must step back from the brink,
193
+ // letting the other side(s) have the last word(s). There may
194
+ // be other points where we could break this cycle, such as
195
+ // silencing the broadcast for cache.writeQuery (not a good
196
+ // idea, since it just delays the feud a bit) or somehow
197
+ // avoiding the network request that just happened (also bad,
198
+ // because the server could return useful new data). All
199
+ // options considered, skipping this cache write seems to be
200
+ // the least damaging place to break the cycle, because it
201
+ // reflects the intuition that we recently wrote this exact
202
+ // result into the cache, so the cache *should* already/still
203
+ // contain this data. If some other query has clobbered that
204
+ // data in the meantime, that's too bad, but there will be no
205
+ // winners if every query blindly reverts to its own version
206
+ // of the data. This approach also gives the network a chance
207
+ // to return new data, which will be written into the cache as
208
+ // usual, notifying only those queries that are directly
209
+ // affected by the cache updates, as usual. In the future, an
210
+ // even more sophisticated cache could perhaps prevent or
211
+ // mitigate the clobbering somehow, but that would make this
212
+ // particular cache write even less important, and thus
213
+ // skipping it would be even safer than it is today.
214
+ if (shouldWrite) {
215
+ cache.writeQuery({
216
+ query,
217
+ data: result.data,
218
+ variables,
219
+ overwrite: cacheWriteBehavior === 1 /* CacheWriteBehavior.OVERWRITE */,
220
+ extensions: result.extensions,
221
+ });
222
+ this.lastWrite = {
223
+ result,
224
+ variables,
225
+ dmCount: destructiveMethodCounts.get(this.cache),
226
+ hasNext: this.hasNext,
227
+ };
228
+ written = true;
229
+ }
230
+ const { dataState, result: diffResult } = this.getDiff({
231
+ ...diffOptions,
232
+ returnPartialData: returnPartialData &&
233
+ // Never deliver partial data for network-only requests
234
+ (fetchPolicy !== "network-only" ||
235
+ networkStatus === NetworkStatus.refetch),
236
+ }, this.getIncrementalInfo({ prune }));
237
+ if (dataState === "complete" ||
238
+ dataState === "streaming" ||
239
+ (returnPartialData && dataState === "partial" && shouldWrite)) {
240
+ result = { ...result, data: diffResult, dataState };
241
+ }
242
+ else if (__DEV__ &&
243
+ written &&
244
+ // A result that is still streaming is expected to read back
245
+ // incomplete until the remaining chunks arrive.
246
+ !this.hasNext) {
247
+ warnAboutPartialCacheResult(query, result.data,
248
+ // Always show the partial result for debugging, otherwise the user
249
+ // sees `null` when `returnPartialData` is false which isn't helpful
250
+ // for figuring out where the problem is.
251
+ cache.diff({ ...diffOptions, returnPartialData: true }));
252
+ }
253
+ },
254
+ });
240
255
  return result;
241
256
  }
242
- getIncrementalInfo(result, { isNetworkOnly }) {
243
- const pending = this.incremental?.pending ?? [];
244
- const streamInfo = result.extensions?.[streamInfoSymbol]?.deref();
257
+ getIncrementalInfo({ prune }) {
258
+ const pending = this.incremental?.getPendingWithInfo?.() ?? [];
259
+ const streamInfo = this.incremental?.streamInfo;
245
260
  const incrementalInfo = { streamInfo };
246
261
  // We don't want to deliver stream items or complete defer boundaries
247
262
  // for a network-only request if they haven't yet streamed from the
248
263
  // network. We record all the still-pending paths so that cache.diff
249
264
  // can prune complete defer/stream boundaries at those paths.
250
- if (isNetworkOnly) {
265
+ if (prune) {
251
266
  for (const item of pending) {
252
- const type = this.incremental?.getPendingType?.(item.id);
253
- if (type === "defer") {
267
+ if (item.type === "defer" && !item.delivered) {
254
268
  incrementalInfo.deferInfo ||= new Trie(true, () => true);
255
- incrementalInfo.deferInfo.lookupArray(item.path);
269
+ incrementalInfo.deferInfo.lookupArray(item.path.concat(item.label || []));
256
270
  }
257
- else if (streamInfo && type === "stream") {
271
+ else if (streamInfo && item.type === "stream") {
258
272
  streamInfo.lookupArray(item.path).state.truncate = true;
259
273
  }
260
274
  }
@@ -262,17 +276,13 @@ export class QueryInfo {
262
276
  return incrementalInfo;
263
277
  }
264
278
  getDiff(options, incrementalInfo) {
265
- if (this.cache[handleIncrementalSymbol]) {
266
- return this.cache.diff({
267
- ...options,
268
- [handleIncrementalSymbol]: incrementalInfo || true,
269
- });
279
+ const diff = this.cache.diff({
280
+ ...options,
281
+ [handleIncrementalSymbol]: incrementalInfo,
282
+ });
283
+ if ("dataState" in diff) {
284
+ return diff;
270
285
  }
271
- // returnPartialData is overridden for backwards compatibility with caches
272
- // that don't handle incremental results. Without this, in-flight
273
- // incremental cache data would come back null when returnPartialData is
274
- // false due to the partial result.
275
- const diff = this.cache.diff({ ...options, returnPartialData: true });
276
286
  return {
277
287
  ...diff,
278
288
  dataState: diff.complete ? "complete"
@@ -474,6 +484,15 @@ export class QueryInfo {
474
484
  }
475
485
  }
476
486
  }
487
+ function warnAboutPartialCacheResult(query, networkResult, diff) {
488
+ __DEV__ && invariant.warn(
489
+ 89,
490
+ getOperationName(query, "(anonymous)"),
491
+ diff.missing?.missing,
492
+ networkResult,
493
+ diff.result
494
+ );
495
+ }
477
496
  function shouldWriteResult(result, errorPolicy = "none") {
478
497
  const ignoreErrors = errorPolicy === "ignore" || errorPolicy === "all";
479
498
  let writeWithErrors = !graphQLResultHasError(result);