react-relay 13.0.3 → 13.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. package/ReactRelayContext.js +1 -1
  2. package/ReactRelayLocalQueryRenderer.js.flow +1 -1
  3. package/ReactRelayQueryRenderer.js.flow +1 -4
  4. package/__flowtests__/__generated__/ReactRelayFragmentContainerFlowtest_viewer.graphql.js.flow +1 -3
  5. package/__flowtests__/__generated__/ReactRelayFragmentContainerFlowtest_viewer2.graphql.js.flow +1 -3
  6. package/__flowtests__/__generated__/ReactRelayPaginationContainerFlowtestQuery.graphql.js.flow +2 -4
  7. package/__flowtests__/__generated__/ReactRelayPaginationContainerFlowtest_viewer.graphql.js.flow +1 -3
  8. package/__flowtests__/__generated__/ReactRelayRefetchContainerFlowtestQuery.graphql.js.flow +2 -4
  9. package/__flowtests__/__generated__/ReactRelayRefetchContainerFlowtest_viewer.graphql.js.flow +1 -3
  10. package/__flowtests__/__generated__/RelayModernFlowtest_badref.graphql.js.flow +1 -3
  11. package/__flowtests__/__generated__/RelayModernFlowtest_notref.graphql.js.flow +1 -3
  12. package/__flowtests__/__generated__/RelayModernFlowtest_user.graphql.js.flow +1 -3
  13. package/__flowtests__/__generated__/RelayModernFlowtest_users.graphql.js.flow +1 -3
  14. package/hooks.js +1 -1
  15. package/index.js +1 -1
  16. package/jest-react/internalAct.js.flow +25 -9
  17. package/legacy.js +1 -1
  18. package/lib/ReactRelayQueryRenderer.js +1 -1
  19. package/lib/jest-react/internalAct.js +24 -4
  20. package/lib/relay-hooks/FragmentResource.js +10 -13
  21. package/lib/relay-hooks/QueryResource.js +2 -165
  22. package/lib/relay-hooks/preloadQuery_DEPRECATED.js +7 -11
  23. package/lib/relay-hooks/react-cache/RelayReactCache.js +37 -0
  24. package/lib/relay-hooks/react-cache/getQueryResultOrFetchQuery_REACT_CACHE.js +197 -0
  25. package/lib/relay-hooks/react-cache/useFragmentInternal_REACT_CACHE.js +395 -0
  26. package/lib/relay-hooks/react-cache/useLazyLoadQuery_REACT_CACHE.js +45 -0
  27. package/package.json +3 -3
  28. package/react-relay-hooks.js +2 -2
  29. package/react-relay-hooks.min.js +2 -2
  30. package/react-relay-legacy.js +2 -2
  31. package/react-relay-legacy.min.js +2 -2
  32. package/react-relay.js +2 -2
  33. package/react-relay.min.js +2 -2
  34. package/relay-hooks/FragmentResource.js.flow +17 -18
  35. package/relay-hooks/QueryResource.js.flow +4 -201
  36. package/relay-hooks/__flowtests__/__generated__/useFragmentFlowtest_user.graphql.js.flow +1 -3
  37. package/relay-hooks/__flowtests__/__generated__/useFragmentFlowtest_users.graphql.js.flow +1 -3
  38. package/relay-hooks/preloadQuery_DEPRECATED.js.flow +7 -14
  39. package/relay-hooks/react-cache/RelayReactCache.js.flow +42 -0
  40. package/relay-hooks/react-cache/getQueryResultOrFetchQuery_REACT_CACHE.js.flow +243 -0
  41. package/relay-hooks/react-cache/useFragmentInternal_REACT_CACHE.js.flow +416 -0
  42. package/relay-hooks/react-cache/useLazyLoadQuery_REACT_CACHE.js.flow +66 -0
@@ -32,7 +32,7 @@ import type {
32
32
  const LRUCache = require('./LRUCache');
33
33
  const SuspenseResource = require('./SuspenseResource');
34
34
  const invariant = require('invariant');
35
- const {RelayFeatureFlags, isPromise} = require('relay-runtime');
35
+ const {isPromise} = require('relay-runtime');
36
36
  const warning = require('warning');
37
37
 
38
38
  const CACHE_CAPACITY = 1000;
@@ -50,8 +50,6 @@ type QueryResourceCacheEntry = {|
50
50
  // from the incremental responses, so later we can choose how to handle errors
51
51
  // in the incremental payloads.
52
52
  processedPayloadsCount: number,
53
- getRetainCount(): number,
54
- getNetworkSubscription(): ?Subscription,
55
53
  setNetworkSubscription(?Subscription): void,
56
54
  getValue(): Error | Promise<void> | QueryResult,
57
55
  setValue(Error | Promise<void> | QueryResult): void,
@@ -125,39 +123,6 @@ function createCacheEntry(
125
123
  value: Error | Promise<void> | QueryResult,
126
124
  networkSubscription: ?Subscription,
127
125
  onDispose: QueryResourceCacheEntry => void,
128
- ): QueryResourceCacheEntry {
129
- // There should be no behavior difference between createCacheEntry_new and
130
- // createCacheEntry_old, and it doesn't directly relate to Client Edges.
131
- // It was just a refactoring that was needed for Client Edges but that
132
- // is behind the feature flag just in case there is any accidental breakage.
133
- if (RelayFeatureFlags.REFACTOR_SUSPENSE_RESOURCE) {
134
- return createCacheEntry_new(
135
- cacheIdentifier,
136
- operation,
137
- operationAvailability,
138
- value,
139
- networkSubscription,
140
- onDispose,
141
- );
142
- } else {
143
- return createCacheEntry_old(
144
- cacheIdentifier,
145
- operation,
146
- operationAvailability,
147
- value,
148
- networkSubscription,
149
- onDispose,
150
- );
151
- }
152
- }
153
-
154
- function createCacheEntry_new(
155
- cacheIdentifier: string,
156
- operation: OperationDescriptor,
157
- operationAvailability: ?OperationAvailability,
158
- value: Error | Promise<void> | QueryResult,
159
- networkSubscription: ?Subscription,
160
- onDispose: QueryResourceCacheEntry => void,
161
126
  ): QueryResourceCacheEntry {
162
127
  const isLiveQuery = operationIsLiveQuery(operation);
163
128
 
@@ -191,12 +156,6 @@ function createCacheEntry_new(
191
156
  setValue(val: QueryResult | Promise<void> | Error) {
192
157
  currentValue = val;
193
158
  },
194
- getRetainCount() {
195
- return suspenseResource.getRetainCount();
196
- },
197
- getNetworkSubscription() {
198
- return currentNetworkSubscription;
199
- },
200
159
  setNetworkSubscription(subscription: ?Subscription) {
201
160
  if (isLiveQuery && currentNetworkSubscription != null) {
202
161
  currentNetworkSubscription.unsubscribe();
@@ -217,153 +176,6 @@ function createCacheEntry_new(
217
176
  return cacheEntry;
218
177
  }
219
178
 
220
- const DATA_RETENTION_TIMEOUT = 5 * 60 * 1000;
221
- function createCacheEntry_old(
222
- cacheIdentifier: string,
223
- operation: OperationDescriptor,
224
- operationAvailability: ?OperationAvailability,
225
- value: Error | Promise<void> | QueryResult,
226
- networkSubscription: ?Subscription,
227
- onDispose: QueryResourceCacheEntry => void,
228
- ): QueryResourceCacheEntry {
229
- const isLiveQuery = operationIsLiveQuery(operation);
230
-
231
- let currentValue: Error | Promise<void> | QueryResult = value;
232
- let retainCount = 0;
233
- let retainDisposable: ?Disposable = null;
234
- let releaseTemporaryRetain: ?() => void = null;
235
- let currentNetworkSubscription: ?Subscription = networkSubscription;
236
-
237
- const retain = (environment: IEnvironment) => {
238
- retainCount++;
239
- if (retainCount === 1) {
240
- retainDisposable = environment.retain(operation);
241
- }
242
- return {
243
- dispose: () => {
244
- retainCount = Math.max(0, retainCount - 1);
245
- if (retainCount === 0) {
246
- invariant(
247
- retainDisposable != null,
248
- 'Relay: Expected disposable to release query to be defined.' +
249
- "If you're seeing this, this is likely a bug in Relay.",
250
- );
251
- retainDisposable.dispose();
252
- retainDisposable = null;
253
- }
254
- onDispose(cacheEntry);
255
- },
256
- };
257
- };
258
-
259
- const cacheEntry = {
260
- cacheIdentifier,
261
- id: nextID++,
262
- processedPayloadsCount: 0,
263
- operationAvailability,
264
- getValue() {
265
- return currentValue;
266
- },
267
- setValue(val: QueryResult | Promise<void> | Error) {
268
- currentValue = val;
269
- },
270
- getRetainCount() {
271
- return retainCount;
272
- },
273
- getNetworkSubscription() {
274
- return currentNetworkSubscription;
275
- },
276
- setNetworkSubscription(subscription: ?Subscription) {
277
- if (isLiveQuery && currentNetworkSubscription != null) {
278
- currentNetworkSubscription.unsubscribe();
279
- }
280
- currentNetworkSubscription = subscription;
281
- },
282
- temporaryRetain(environment: IEnvironment): Disposable {
283
- // NOTE: If we're executing in a server environment, there's no need
284
- // to create temporary retains, since the component will never commit.
285
- if (environment.isServer()) {
286
- return {dispose: () => {}};
287
- }
288
-
289
- // NOTE: temporaryRetain is called during the render phase. However,
290
- // given that we can't tell if this render will eventually commit or not,
291
- // we create a timer to autodispose of this retain in case the associated
292
- // component never commits.
293
- // If the component /does/ commit, permanentRetain will clear this timeout
294
- // and permanently retain the data.
295
- const disposable = retain(environment);
296
- let releaseQueryTimeout = null;
297
- const localReleaseTemporaryRetain = () => {
298
- clearTimeout(releaseQueryTimeout);
299
- releaseQueryTimeout = null;
300
- releaseTemporaryRetain = null;
301
- disposable.dispose();
302
- // Normally if this entry never commits, the request would've ended by the
303
- // time this timeout expires and the temporary retain is released. However,
304
- // we need to do this for live queries which remain open indefinitely.
305
- if (
306
- isLiveQuery &&
307
- retainCount <= 0 &&
308
- currentNetworkSubscription != null
309
- ) {
310
- currentNetworkSubscription.unsubscribe();
311
- }
312
- };
313
- releaseQueryTimeout = setTimeout(
314
- localReleaseTemporaryRetain,
315
- DATA_RETENTION_TIMEOUT,
316
- );
317
-
318
- // NOTE: Since temporaryRetain can be called multiple times, we release
319
- // the previous temporary retain after we re-establish a new one, since
320
- // we only ever need a single temporary retain until the permanent retain is
321
- // established.
322
- // temporaryRetain may be called multiple times by React during the render
323
- // phase, as well as multiple times by other query components that are
324
- // rendering the same query/variables.
325
- if (releaseTemporaryRetain != null) {
326
- releaseTemporaryRetain();
327
- }
328
- releaseTemporaryRetain = localReleaseTemporaryRetain;
329
-
330
- return {
331
- dispose: () => {
332
- releaseTemporaryRetain && releaseTemporaryRetain();
333
- },
334
- };
335
- },
336
- permanentRetain(environment: IEnvironment): Disposable {
337
- const disposable = retain(environment);
338
- if (releaseTemporaryRetain != null) {
339
- releaseTemporaryRetain();
340
- releaseTemporaryRetain = null;
341
- }
342
-
343
- return {
344
- dispose: () => {
345
- disposable.dispose();
346
- if (
347
- isLiveQuery &&
348
- retainCount <= 0 &&
349
- currentNetworkSubscription != null
350
- ) {
351
- currentNetworkSubscription.unsubscribe();
352
- }
353
- },
354
- };
355
- },
356
- releaseTemporaryRetain() {
357
- if (releaseTemporaryRetain != null) {
358
- releaseTemporaryRetain();
359
- releaseTemporaryRetain = null;
360
- }
361
- },
362
- };
363
-
364
- return cacheEntry;
365
- }
366
-
367
179
  class QueryResourceImpl {
368
180
  _environment: IEnvironment;
369
181
  _cache: QueryResourceCache;
@@ -532,15 +344,7 @@ class QueryResourceImpl {
532
344
  }
533
345
 
534
346
  _clearCacheEntry = (cacheEntry: QueryResourceCacheEntry): void => {
535
- // The new code does this retainCount <= 0 check within SuspenseResource
536
- // before calling _clearCacheEntry, whereas with the old code we do it here.
537
- if (RelayFeatureFlags.REFACTOR_SUSPENSE_RESOURCE) {
538
- this._cache.delete(cacheEntry.cacheIdentifier);
539
- } else {
540
- if (cacheEntry.getRetainCount() <= 0) {
541
- this._cache.delete(cacheEntry.cacheIdentifier);
542
- }
543
- }
347
+ this._cache.delete(cacheEntry.cacheIdentifier);
544
348
  };
545
349
 
546
350
  _getOrCreateCacheEntry(
@@ -693,10 +497,9 @@ class QueryResourceImpl {
693
497
  // To complete this task we need to have a way of precisely tracking suspendable points
694
498
  warning(
695
499
  false,
696
- 'QueryResource: An incremental payload for query `%` returned an error: `%`:`%`.',
500
+ 'QueryResource: An incremental payload for query `%s` returned an error: `%s`.',
697
501
  operation.fragment.node.name,
698
- error.message,
699
- error.stack,
502
+ String(error.message),
700
503
  );
701
504
  }
702
505
  resolveNetworkPromise();
@@ -4,7 +4,7 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  *
7
- * @generated SignedSource<<57e35620e0558adaa97ee6b06423e705>>
7
+ * @generated SignedSource<<6bdf42be355b40305d9006059aa56d70>>
8
8
  * @flow
9
9
  * @lightSyntaxTransform
10
10
  * @nogrep
@@ -18,12 +18,10 @@
18
18
  import type { Fragment, ReaderFragment } from 'relay-runtime';
19
19
  import type { FragmentType } from "relay-runtime";
20
20
  declare export opaque type useFragmentFlowtest_user$fragmentType: FragmentType;
21
- export type useFragmentFlowtest_user$ref = useFragmentFlowtest_user$fragmentType;
22
21
  export type useFragmentFlowtest_user$data = {|
23
22
  +id: string,
24
23
  +$fragmentType: useFragmentFlowtest_user$fragmentType,
25
24
  |};
26
- export type useFragmentFlowtest_user = useFragmentFlowtest_user$data;
27
25
  export type useFragmentFlowtest_user$key = {
28
26
  +$data?: useFragmentFlowtest_user$data,
29
27
  +$fragmentSpreads: useFragmentFlowtest_user$fragmentType,
@@ -4,7 +4,7 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  *
7
- * @generated SignedSource<<e21003870360e15cc93c1cee7aa239ad>>
7
+ * @generated SignedSource<<22f34b875909574b411e656fc3e592db>>
8
8
  * @flow
9
9
  * @lightSyntaxTransform
10
10
  * @nogrep
@@ -18,12 +18,10 @@
18
18
  import type { Fragment, ReaderFragment } from 'relay-runtime';
19
19
  import type { FragmentType } from "relay-runtime";
20
20
  declare export opaque type useFragmentFlowtest_users$fragmentType: FragmentType;
21
- export type useFragmentFlowtest_users$ref = useFragmentFlowtest_users$fragmentType;
22
21
  export type useFragmentFlowtest_users$data = $ReadOnlyArray<{|
23
22
  +id: string,
24
23
  +$fragmentType: useFragmentFlowtest_users$fragmentType,
25
24
  |}>;
26
- export type useFragmentFlowtest_users = useFragmentFlowtest_users$data;
27
25
  export type useFragmentFlowtest_users$key = $ReadOnlyArray<{
28
26
  +$data?: useFragmentFlowtest_users$data,
29
27
  +$fragmentSpreads: useFragmentFlowtest_users$fragmentType,
@@ -115,20 +115,13 @@ function preloadQuery<TQuery: OperationType, TEnvironmentProviderOptions>(
115
115
  if (environment.isServer()) {
116
116
  return;
117
117
  }
118
- if (
119
- RelayFeatureFlags.DELAY_CLEANUP_OF_PENDING_PRELOAD_QUERIES ===
120
- true
121
- ) {
122
- setTimeout(() => {
123
- // Clear the cache entry after the default timeout
124
- // null-check for Flow
125
- if (queryEntry != null) {
126
- cleanup(pendingQueries, queryEntry);
127
- }
128
- }, DEFAULT_PREFETCH_TIMEOUT);
129
- } else {
130
- cleanup(pendingQueries, queryEntry);
131
- }
118
+ setTimeout(() => {
119
+ // Clear the cache entry after the default timeout
120
+ // null-check for Flow
121
+ if (queryEntry != null) {
122
+ cleanup(pendingQueries, queryEntry);
123
+ }
124
+ }, DEFAULT_PREFETCH_TIMEOUT);
132
125
  };
133
126
  })
134
127
  : null;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict-local
8
+ * @emails oncall+relay
9
+ * @format
10
+ */
11
+
12
+ // flowlint ambiguous-object-type:error
13
+
14
+ 'use strict';
15
+
16
+ const invariant = require('invariant');
17
+ // $FlowFixMe[prop-missing] These exist in experimental builds but aren't in React's types yet.
18
+ const {unstable_getCacheForType, unstable_getCacheSignal} = require('react');
19
+ const {RelayFeatureFlags} = require('relay-runtime');
20
+
21
+ function getCacheForType<T>(factory: () => T): T {
22
+ invariant(
23
+ typeof unstable_getCacheForType === 'function' &&
24
+ RelayFeatureFlags.USE_REACT_CACHE,
25
+ 'RelayReactCache.getCacheForType should only be called when the USE_REACT_CACHE feature flag is enabled and when on an experimental React build that supports it.',
26
+ );
27
+ return unstable_getCacheForType(factory);
28
+ }
29
+
30
+ function getCacheSignal(): AbortSignal {
31
+ invariant(
32
+ typeof unstable_getCacheSignal === 'function' &&
33
+ RelayFeatureFlags.USE_REACT_CACHE,
34
+ 'RelayReactCache.getCacheSignal should only be called when the USE_REACT_CACHE feature flag is enabled and when on an experimental React build that supports it.',
35
+ );
36
+ return unstable_getCacheSignal();
37
+ }
38
+
39
+ module.exports = {
40
+ getCacheForType,
41
+ getCacheSignal,
42
+ };
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict-local
8
+ * @emails oncall+relay
9
+ * @format
10
+ */
11
+
12
+ // flowlint ambiguous-object-type:error
13
+
14
+ 'use strict';
15
+
16
+ import type {
17
+ FetchPolicy,
18
+ IEnvironment,
19
+ OperationDescriptor,
20
+ ReaderFragment,
21
+ RenderPolicy,
22
+ } from 'relay-runtime';
23
+
24
+ const {getCacheForType, getCacheSignal} = require('./RelayReactCache');
25
+ const invariant = require('invariant');
26
+ const {
27
+ __internal: {fetchQuery: fetchQueryInternal},
28
+ } = require('relay-runtime');
29
+ const warning = require('warning');
30
+
31
+ type QueryResult = {|
32
+ fragmentNode: ReaderFragment,
33
+ fragmentRef: mixed,
34
+ |};
35
+
36
+ // Note that the status of a cache entry will be 'resolved' when partial
37
+ // rendering is allowed, even if a fetch is ongoing. The pending status
38
+ // is specifically to indicate that we should suspend.
39
+ type QueryCacheEntry =
40
+ | {|status: 'resolved', result: QueryResult|}
41
+ | {|status: 'pending', promise: Promise<void>|}
42
+ | {|status: 'rejected', error: Error|};
43
+
44
+ type QueryCache = Map<string, QueryCacheEntry>;
45
+
46
+ const DEFAULT_FETCH_POLICY = 'store-or-network';
47
+
48
+ function createQueryCache(): QueryCache {
49
+ return new Map();
50
+ }
51
+
52
+ function getQueryCacheKey(
53
+ operation: OperationDescriptor,
54
+ fetchPolicy: FetchPolicy,
55
+ renderPolicy: RenderPolicy,
56
+ ): string {
57
+ const cacheIdentifier = `${fetchPolicy}-${renderPolicy}-${operation.request.identifier}`;
58
+ return cacheIdentifier;
59
+ }
60
+
61
+ function constructQueryResult(operation: OperationDescriptor): QueryResult {
62
+ const rootFragmentRef = {
63
+ __id: operation.fragment.dataID,
64
+ __fragments: {
65
+ [operation.fragment.node.name]: operation.request.variables,
66
+ },
67
+ __fragmentOwner: operation.request,
68
+ };
69
+ return {
70
+ fragmentNode: operation.request.node.fragment,
71
+ fragmentRef: rootFragmentRef,
72
+ };
73
+ }
74
+
75
+ function getQueryResultOrFetchQuery_REACT_CACHE(
76
+ environment: IEnvironment,
77
+ queryOperationDescriptor: OperationDescriptor,
78
+ fetchPolicy: FetchPolicy = DEFAULT_FETCH_POLICY,
79
+ maybeRenderPolicy?: RenderPolicy,
80
+ ): QueryResult {
81
+ const renderPolicy =
82
+ maybeRenderPolicy ?? environment.UNSTABLE_getDefaultRenderPolicy();
83
+
84
+ const cache = getCacheForType(createQueryCache);
85
+
86
+ const cacheKey = getQueryCacheKey(
87
+ queryOperationDescriptor,
88
+ fetchPolicy,
89
+ renderPolicy,
90
+ );
91
+
92
+ let entry = cache.get(cacheKey);
93
+ if (entry === undefined) {
94
+ // Initiate a query to fetch the data if needed:
95
+ entry = onCacheMiss(
96
+ environment,
97
+ queryOperationDescriptor,
98
+ fetchPolicy,
99
+ renderPolicy,
100
+ newCacheEntry => {
101
+ cache.set(cacheKey, newCacheEntry);
102
+ },
103
+ );
104
+ cache.set(cacheKey, entry);
105
+
106
+ // Since this is the first time rendering, retain the query. React will
107
+ // trigger the abort signal when this cache entry is no longer needed.
108
+ const retention = environment.retain(queryOperationDescriptor);
109
+ const abortSignal = getCacheSignal();
110
+ abortSignal.addEventListener(
111
+ 'abort',
112
+ () => {
113
+ retention.dispose();
114
+ cache.delete(cacheKey);
115
+ },
116
+ {once: true},
117
+ );
118
+ }
119
+
120
+ switch (entry.status) {
121
+ case 'pending':
122
+ throw entry.promise;
123
+ case 'rejected':
124
+ throw entry.error;
125
+ case 'resolved':
126
+ return entry.result;
127
+ }
128
+ invariant(false, 'switch statement should be exhaustive');
129
+ }
130
+
131
+ function onCacheMiss(
132
+ environment: IEnvironment,
133
+ operation: OperationDescriptor,
134
+ fetchPolicy: FetchPolicy,
135
+ renderPolicy: RenderPolicy,
136
+ updateCache: QueryCacheEntry => void,
137
+ ): QueryCacheEntry {
138
+ // NB: Besides checking if the data is available, calling `check` will write missing
139
+ // data to the store using any missing data handlers specified in the environment.
140
+ const queryAvailability = environment.check(operation);
141
+ const queryStatus = queryAvailability.status;
142
+ const hasFullQuery = queryStatus === 'available';
143
+ const canPartialRender =
144
+ hasFullQuery || (renderPolicy === 'partial' && queryStatus !== 'stale');
145
+
146
+ let shouldFetch;
147
+ let shouldRenderNow;
148
+ switch (fetchPolicy) {
149
+ case 'store-only': {
150
+ shouldFetch = false;
151
+ shouldRenderNow = true;
152
+ break;
153
+ }
154
+ case 'store-or-network': {
155
+ shouldFetch = !hasFullQuery;
156
+ shouldRenderNow = canPartialRender;
157
+ break;
158
+ }
159
+ case 'store-and-network': {
160
+ shouldFetch = true;
161
+ shouldRenderNow = canPartialRender;
162
+ break;
163
+ }
164
+ case 'network-only':
165
+ default: {
166
+ shouldFetch = true;
167
+ shouldRenderNow = false;
168
+ break;
169
+ }
170
+ }
171
+
172
+ const promise = shouldFetch
173
+ ? executeOperationAndKeepUpToDate(environment, operation, updateCache)
174
+ : undefined;
175
+ if (shouldRenderNow) {
176
+ return {status: 'resolved', result: constructQueryResult(operation)};
177
+ } else {
178
+ invariant(
179
+ promise,
180
+ 'Should either fetch or render (or both), otherwise we would suspend forever.',
181
+ );
182
+ return {status: 'pending', promise: promise};
183
+ }
184
+ }
185
+
186
+ function executeOperationAndKeepUpToDate(
187
+ environment: IEnvironment,
188
+ operation: OperationDescriptor,
189
+ updateCache: QueryCacheEntry => void,
190
+ ): Promise<void> {
191
+ let resolvePromise;
192
+ const promise = new Promise(r => {
193
+ resolvePromise = r;
194
+ });
195
+ // $FlowExpectedError[prop-missing] Expando to annotate Promises.
196
+ promise.displayName = 'Relay(' + operation.request.node.operation.name + ')';
197
+
198
+ let isFirstPayload = true;
199
+
200
+ // FIXME We may still need to cancel network requests for live queries.
201
+ const fetchObservable = fetchQueryInternal(environment, operation);
202
+ fetchObservable.subscribe({
203
+ start: subscription => {},
204
+ error: error => {
205
+ if (isFirstPayload) {
206
+ updateCache({status: 'rejected', error});
207
+ } else {
208
+ // TODO:T92030819 Remove this warning and actually throw the network error
209
+ // To complete this task we need to have a way of precisely tracking suspendable points
210
+ warning(
211
+ false,
212
+ 'getQueryResultOrFetchQuery: An incremental payload for query `%` returned an error: `%`:`%`.',
213
+ operation.request.node.operation.name,
214
+ error.message,
215
+ error.stack,
216
+ );
217
+ }
218
+ resolvePromise();
219
+ isFirstPayload = false;
220
+ },
221
+ next: response => {
222
+ // Stop suspending on the first payload because of streaming, defer, etc.
223
+ updateCache({
224
+ status: 'resolved',
225
+ result: constructQueryResult(operation),
226
+ });
227
+ resolvePromise();
228
+ isFirstPayload = false;
229
+ },
230
+ complete: () => {
231
+ updateCache({
232
+ status: 'resolved',
233
+ result: constructQueryResult(operation),
234
+ });
235
+ resolvePromise();
236
+ isFirstPayload = false;
237
+ },
238
+ });
239
+
240
+ return promise;
241
+ }
242
+
243
+ module.exports = getQueryResultOrFetchQuery_REACT_CACHE;