react-relay 13.1.1 → 13.2.0
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.
- package/ReactRelayContext.js +1 -1
- package/ReactRelayLocalQueryRenderer.js.flow +1 -1
- package/ReactRelayQueryRenderer.js.flow +1 -4
- package/hooks.js +1 -1
- package/index.js +1 -1
- package/jest-react/internalAct.js.flow +25 -9
- package/legacy.js +1 -1
- package/lib/ReactRelayQueryRenderer.js +1 -1
- package/lib/jest-react/internalAct.js +24 -4
- package/lib/relay-hooks/FragmentResource.js +10 -13
- package/lib/relay-hooks/QueryResource.js +2 -165
- package/lib/relay-hooks/preloadQuery_DEPRECATED.js +7 -11
- package/lib/relay-hooks/react-cache/RelayReactCache.js +37 -0
- package/lib/relay-hooks/react-cache/getQueryResultOrFetchQuery_REACT_CACHE.js +197 -0
- package/lib/relay-hooks/react-cache/useFragmentInternal_REACT_CACHE.js +395 -0
- package/lib/relay-hooks/react-cache/useLazyLoadQuery_REACT_CACHE.js +45 -0
- package/package.json +3 -3
- package/react-relay-hooks.js +2 -2
- package/react-relay-hooks.min.js +2 -2
- package/react-relay-legacy.js +2 -2
- package/react-relay-legacy.min.js +2 -2
- package/react-relay.js +2 -2
- package/react-relay.min.js +2 -2
- package/relay-hooks/FragmentResource.js.flow +17 -18
- package/relay-hooks/QueryResource.js.flow +4 -201
- package/relay-hooks/preloadQuery_DEPRECATED.js.flow +7 -14
- package/relay-hooks/react-cache/RelayReactCache.js.flow +42 -0
- package/relay-hooks/react-cache/getQueryResultOrFetchQuery_REACT_CACHE.js.flow +243 -0
- package/relay-hooks/react-cache/useFragmentInternal_REACT_CACHE.js.flow +416 -0
- 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 {
|
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
|
-
|
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
|
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();
|
@@ -115,20 +115,13 @@ function preloadQuery<TQuery: OperationType, TEnvironmentProviderOptions>(
|
|
115
115
|
if (environment.isServer()) {
|
116
116
|
return;
|
117
117
|
}
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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;
|