react-relay 11.0.2 → 13.0.0-rc.2
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/README.md +47 -0
- package/ReactRelayContainerUtils.js.flow +1 -1
- package/ReactRelayContext.js +1 -1
- package/ReactRelayContext.js.flow +3 -4
- package/ReactRelayFragmentContainer.js.flow +25 -25
- package/ReactRelayFragmentMockRenderer.js.flow +2 -2
- package/ReactRelayLocalQueryRenderer.js.flow +7 -8
- package/ReactRelayPaginationContainer.js.flow +112 -59
- package/ReactRelayQueryFetcher.js.flow +10 -11
- package/ReactRelayQueryRenderer.js.flow +116 -82
- package/ReactRelayQueryRendererContext.js.flow +1 -1
- package/ReactRelayRefetchContainer.js.flow +42 -39
- package/ReactRelayTestMocker.js.flow +17 -15
- package/ReactRelayTypes.js.flow +11 -11
- package/RelayContext.js.flow +4 -4
- package/__flowtests__/ReactRelayFragmentContainer-flowtest.js.flow +2 -3
- package/__flowtests__/ReactRelayPaginationContainer-flowtest.js.flow +12 -8
- package/__flowtests__/ReactRelayRefetchContainer-flowtest.js.flow +11 -7
- package/__flowtests__/RelayModern-flowtest.js.flow +79 -47
- package/__flowtests__/RelayModernFlowtest_badref.graphql.js.flow +6 -5
- package/__flowtests__/RelayModernFlowtest_notref.graphql.js.flow +6 -5
- package/__flowtests__/RelayModernFlowtest_user.graphql.js.flow +5 -4
- package/__flowtests__/RelayModernFlowtest_users.graphql.js.flow +5 -4
- package/__flowtests__/__generated__/ReactRelayFragmentContainerFlowtest_viewer.graphql.js.flow +72 -0
- package/__flowtests__/__generated__/ReactRelayFragmentContainerFlowtest_viewer2.graphql.js.flow +72 -0
- package/__flowtests__/__generated__/ReactRelayPaginationContainerFlowtestQuery.graphql.js.flow +227 -0
- package/__flowtests__/__generated__/ReactRelayPaginationContainerFlowtest_viewer.graphql.js.flow +164 -0
- package/__flowtests__/__generated__/ReactRelayRefetchContainerFlowtestQuery.graphql.js.flow +227 -0
- package/__flowtests__/__generated__/ReactRelayRefetchContainerFlowtest_viewer.graphql.js.flow +164 -0
- package/__flowtests__/__generated__/RelayModernFlowtest_badref.graphql.js.flow +66 -0
- package/__flowtests__/__generated__/RelayModernFlowtest_notref.graphql.js.flow +66 -0
- package/__flowtests__/__generated__/RelayModernFlowtest_user.graphql.js.flow +59 -0
- package/__flowtests__/__generated__/RelayModernFlowtest_users.graphql.js.flow +61 -0
- package/assertFragmentMap.js.flow +3 -3
- package/buildReactRelayContainer.js.flow +12 -11
- package/getRootVariablesForFragments.js.flow +3 -5
- package/hooks.js +1 -1
- package/hooks.js.flow +6 -7
- package/index.js +1 -1
- package/index.js.flow +7 -8
- package/isRelayEnvironment.js.flow +1 -1
- package/jest-react/enqueueTask.js.flow +56 -0
- package/jest-react/index.js.flow +12 -0
- package/jest-react/internalAct.js.flow +138 -0
- package/legacy.js +1 -1
- package/legacy.js.flow +1 -1
- package/lib/ReactRelayContainerUtils.js +1 -1
- package/lib/ReactRelayContext.js +1 -1
- package/lib/ReactRelayFragmentContainer.js +22 -16
- package/lib/ReactRelayFragmentMockRenderer.js +3 -3
- package/lib/ReactRelayLocalQueryRenderer.js +8 -9
- package/lib/ReactRelayPaginationContainer.js +97 -39
- package/lib/ReactRelayQueryFetcher.js +3 -3
- package/lib/ReactRelayQueryRenderer.js +87 -54
- package/lib/ReactRelayQueryRendererContext.js +1 -1
- package/lib/ReactRelayRefetchContainer.js +39 -26
- package/lib/ReactRelayTestMocker.js +8 -9
- package/lib/ReactRelayTypes.js +1 -1
- package/lib/RelayContext.js +4 -3
- package/lib/assertFragmentMap.js +3 -2
- package/lib/buildReactRelayContainer.js +8 -8
- package/lib/getRootVariablesForFragments.js +2 -3
- package/lib/hooks.js +6 -6
- package/lib/index.js +8 -8
- package/lib/isRelayEnvironment.js +1 -1
- package/lib/jest-react/enqueueTask.js +53 -0
- package/lib/jest-react/index.js +13 -0
- package/lib/jest-react/internalAct.js +115 -0
- package/lib/legacy.js +1 -1
- package/lib/multi-actor/ActorChange.js +30 -0
- package/lib/multi-actor/index.js +11 -0
- package/lib/multi-actor/useRelayActorEnvironment.js +29 -0
- package/lib/readContext.js +1 -1
- package/lib/relay-hooks/EntryPointContainer.react.js +4 -4
- package/lib/relay-hooks/EntryPointTypes.flow.js +1 -1
- package/lib/relay-hooks/FragmentResource.js +342 -89
- package/lib/relay-hooks/InternalLogger.js +1 -1
- package/lib/relay-hooks/LRUCache.js +1 -1
- package/lib/relay-hooks/LazyLoadEntryPointContainer_DEPRECATED.react.js +5 -5
- package/lib/relay-hooks/MatchContainer.js +2 -2
- package/lib/relay-hooks/ProfilerContext.js +1 -1
- package/lib/relay-hooks/QueryResource.js +172 -29
- package/lib/relay-hooks/RelayEnvironmentProvider.js +6 -4
- package/lib/relay-hooks/SuspenseResource.js +130 -0
- package/lib/relay-hooks/loadEntryPoint.js +1 -1
- package/lib/relay-hooks/loadQuery.js +42 -20
- package/lib/relay-hooks/preloadQuery_DEPRECATED.js +25 -16
- package/lib/relay-hooks/prepareEntryPoint_DEPRECATED.js +1 -1
- package/lib/relay-hooks/useBlockingPaginationFragment.js +5 -6
- package/lib/relay-hooks/useEntryPointLoader.js +3 -3
- package/lib/relay-hooks/useFetchTrackingRef.js +3 -2
- package/lib/relay-hooks/useFragment.js +7 -7
- package/lib/relay-hooks/useFragmentNode.js +5 -5
- package/lib/relay-hooks/useIsMountedRef.js +1 -1
- package/lib/relay-hooks/useIsOperationNodeActive.js +3 -3
- package/lib/relay-hooks/useIsParentQueryActive.js +1 -1
- package/lib/relay-hooks/useLazyLoadQuery.js +4 -4
- package/lib/relay-hooks/useLazyLoadQueryNode.js +11 -5
- package/lib/relay-hooks/useLoadMoreFunction.js +9 -13
- package/lib/relay-hooks/useMemoOperationDescriptor.js +3 -3
- package/lib/relay-hooks/useMemoVariables.js +3 -3
- package/lib/relay-hooks/useMutation.js +18 -7
- package/lib/relay-hooks/usePaginationFragment.js +3 -4
- package/lib/relay-hooks/usePreloadedQuery.js +6 -6
- package/lib/relay-hooks/useQueryLoader.js +31 -11
- package/lib/relay-hooks/useRefetchableFragment.js +1 -1
- package/lib/relay-hooks/useRefetchableFragmentNode.js +14 -18
- package/lib/relay-hooks/useRelayEnvironment.js +3 -3
- package/lib/relay-hooks/useStaticFragmentNodeWarning.js +3 -3
- package/lib/relay-hooks/useSubscribeToInvalidationState.js +3 -2
- package/lib/relay-hooks/useSubscription.js +11 -8
- package/multi-actor/ActorChange.js.flow +58 -0
- package/multi-actor/index.js.flow +14 -0
- package/multi-actor/useRelayActorEnvironment.js.flow +49 -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/readContext.js.flow +1 -1
- package/relay-hooks/EntryPointContainer.react.js.flow +9 -16
- package/relay-hooks/EntryPointTypes.flow.js.flow +25 -26
- package/relay-hooks/FragmentResource.js.flow +359 -93
- package/relay-hooks/InternalLogger.js.flow +1 -1
- package/relay-hooks/LRUCache.js.flow +1 -1
- package/relay-hooks/LazyLoadEntryPointContainer_DEPRECATED.react.js.flow +33 -47
- package/relay-hooks/MatchContainer.js.flow +4 -3
- package/relay-hooks/ProfilerContext.js.flow +1 -1
- package/relay-hooks/QueryResource.js.flow +217 -26
- package/relay-hooks/RelayEnvironmentProvider.js.flow +15 -5
- package/relay-hooks/SuspenseResource.js.flow +115 -0
- package/relay-hooks/__flowtests__/EntryPointTypes/EntryPointElementConfig-flowtest.js.flow +5 -4
- package/relay-hooks/__flowtests__/EntryPointTypes/NestedEntrypoints-flowtest.js.flow +2 -2
- package/relay-hooks/__flowtests__/__generated__/useFragmentFlowtest_user.graphql.js.flow +59 -0
- package/relay-hooks/__flowtests__/__generated__/useFragmentFlowtest_users.graphql.js.flow +61 -0
- package/relay-hooks/__flowtests__/useBlockingPaginationFragment-flowtest.js.flow +11 -10
- package/relay-hooks/__flowtests__/useFragment-flowtest.js.flow +55 -32
- package/relay-hooks/__flowtests__/usePaginationFragment-flowtest.js.flow +11 -10
- package/relay-hooks/__flowtests__/useRefetchableFragment-flowtest.js.flow +11 -10
- package/relay-hooks/__flowtests__/utils.js.flow +21 -32
- package/relay-hooks/loadEntryPoint.js.flow +7 -13
- package/relay-hooks/loadQuery.js.flow +50 -32
- package/relay-hooks/preloadQuery_DEPRECATED.js.flow +31 -22
- package/relay-hooks/prepareEntryPoint_DEPRECATED.js.flow +7 -13
- package/relay-hooks/useBlockingPaginationFragment.js.flow +14 -12
- package/relay-hooks/useEntryPointLoader.js.flow +8 -11
- package/relay-hooks/useFetchTrackingRef.js.flow +3 -3
- package/relay-hooks/useFragment.js.flow +31 -62
- package/relay-hooks/useFragmentNode.js.flow +6 -8
- package/relay-hooks/useIsMountedRef.js.flow +1 -1
- package/relay-hooks/useIsOperationNodeActive.js.flow +4 -6
- package/relay-hooks/useIsParentQueryActive.js.flow +4 -5
- package/relay-hooks/useLazyLoadQuery.js.flow +14 -16
- package/relay-hooks/useLazyLoadQueryNode.js.flow +20 -14
- package/relay-hooks/useLoadMoreFunction.js.flow +21 -30
- package/relay-hooks/useMemoOperationDescriptor.js.flow +6 -8
- package/relay-hooks/useMemoVariables.js.flow +7 -7
- package/relay-hooks/useMutation.js.flow +27 -27
- package/relay-hooks/usePaginationFragment.js.flow +39 -45
- package/relay-hooks/usePreloadedQuery.js.flow +14 -20
- package/relay-hooks/useQueryLoader.js.flow +42 -23
- package/relay-hooks/useRefetchableFragment.js.flow +8 -9
- package/relay-hooks/useRefetchableFragmentNode.js.flow +25 -33
- package/relay-hooks/useRelayEnvironment.js.flow +3 -5
- package/relay-hooks/useStaticFragmentNodeWarning.js.flow +3 -4
- package/relay-hooks/useSubscribeToInvalidationState.js.flow +4 -7
- package/relay-hooks/useSubscription.js.flow +21 -11
- package/lib/relay-hooks/getPaginationMetadata.js +0 -41
- package/lib/relay-hooks/getPaginationVariables.js +0 -67
- package/lib/relay-hooks/getRefetchMetadata.js +0 -36
- package/lib/relay-hooks/getValueAtPath.js +0 -51
- package/relay-hooks/getPaginationMetadata.js.flow +0 -74
- package/relay-hooks/getPaginationVariables.js.flow +0 -110
- package/relay-hooks/getRefetchMetadata.js.flow +0 -80
- package/relay-hooks/getValueAtPath.js.flow +0 -46
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright (c)
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
3
|
*
|
|
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.
|
|
@@ -13,31 +13,46 @@
|
|
|
13
13
|
|
|
14
14
|
'use strict';
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
import type {Cache} from './LRUCache';
|
|
17
|
+
import type {QueryResource, QueryResult} from './QueryResource';
|
|
18
|
+
import type {
|
|
19
|
+
ConcreteRequest,
|
|
20
|
+
DataID,
|
|
21
|
+
Disposable,
|
|
22
|
+
IEnvironment,
|
|
23
|
+
ReaderFragment,
|
|
24
|
+
RequestDescriptor,
|
|
25
|
+
Snapshot,
|
|
26
|
+
} from 'relay-runtime';
|
|
17
27
|
|
|
28
|
+
const LRUCache = require('./LRUCache');
|
|
29
|
+
const {getQueryResourceForEnvironment} = require('./QueryResource');
|
|
30
|
+
const SuspenseResource = require('./SuspenseResource');
|
|
18
31
|
const invariant = require('invariant');
|
|
19
|
-
|
|
20
32
|
const {
|
|
21
|
-
|
|
33
|
+
RelayFeatureFlags,
|
|
34
|
+
__internal: {fetchQuery, getPromiseForActiveRequest},
|
|
35
|
+
createOperationDescriptor,
|
|
22
36
|
getFragmentIdentifier,
|
|
37
|
+
getPendingOperationsForFragment,
|
|
23
38
|
getSelector,
|
|
39
|
+
getVariablesFromFragment,
|
|
24
40
|
isPromise,
|
|
25
41
|
recycleNodesInto,
|
|
26
42
|
reportMissingRequiredFields,
|
|
27
43
|
} = require('relay-runtime');
|
|
28
44
|
|
|
29
|
-
import type {Cache} from './LRUCache';
|
|
30
|
-
import type {
|
|
31
|
-
Disposable,
|
|
32
|
-
IEnvironment,
|
|
33
|
-
ReaderFragment,
|
|
34
|
-
RequestDescriptor,
|
|
35
|
-
Snapshot,
|
|
36
|
-
} from 'relay-runtime';
|
|
37
|
-
|
|
38
45
|
export type FragmentResource = FragmentResourceImpl;
|
|
39
46
|
|
|
40
|
-
type FragmentResourceCache = Cache<
|
|
47
|
+
type FragmentResourceCache = Cache<
|
|
48
|
+
| {|
|
|
49
|
+
kind: 'pending',
|
|
50
|
+
pendingOperations: $ReadOnlyArray<RequestDescriptor>,
|
|
51
|
+
promise: Promise<mixed>,
|
|
52
|
+
result: FragmentResult,
|
|
53
|
+
|}
|
|
54
|
+
| {|kind: 'done', result: FragmentResult|},
|
|
55
|
+
>;
|
|
41
56
|
|
|
42
57
|
const WEAKMAP_SUPPORTED = typeof WeakMap === 'function';
|
|
43
58
|
interface IMap<K, V> {
|
|
@@ -46,10 +61,13 @@ interface IMap<K, V> {
|
|
|
46
61
|
}
|
|
47
62
|
|
|
48
63
|
type SingularOrPluralSnapshot = Snapshot | $ReadOnlyArray<Snapshot>;
|
|
64
|
+
|
|
49
65
|
opaque type FragmentResult: {data: mixed, ...} = {|
|
|
50
66
|
cacheKey: string,
|
|
51
67
|
data: mixed,
|
|
68
|
+
isMissingData: boolean,
|
|
52
69
|
snapshot: SingularOrPluralSnapshot | null,
|
|
70
|
+
storeEpoch: number,
|
|
53
71
|
|};
|
|
54
72
|
|
|
55
73
|
// TODO: Fix to not rely on LRU. If the number of active fragments exceeds this
|
|
@@ -60,39 +78,123 @@ const CACHE_CAPACITY = 1000000;
|
|
|
60
78
|
// this is frozen so that users don't accidentally push data into the array
|
|
61
79
|
const CONSTANT_READONLY_EMPTY_ARRAY = Object.freeze([]);
|
|
62
80
|
|
|
63
|
-
function isMissingData(snapshot: SingularOrPluralSnapshot) {
|
|
81
|
+
function isMissingData(snapshot: SingularOrPluralSnapshot): boolean {
|
|
64
82
|
if (Array.isArray(snapshot)) {
|
|
65
83
|
return snapshot.some(s => s.isMissingData);
|
|
66
84
|
}
|
|
67
85
|
return snapshot.isMissingData;
|
|
68
86
|
}
|
|
69
87
|
|
|
88
|
+
function hasMissingClientEdges(snapshot: SingularOrPluralSnapshot): boolean {
|
|
89
|
+
if (Array.isArray(snapshot)) {
|
|
90
|
+
return snapshot.some(s => (s.missingClientEdges?.length ?? 0) > 0);
|
|
91
|
+
}
|
|
92
|
+
return (snapshot.missingClientEdges?.length ?? 0) > 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function singularOrPluralForEach(
|
|
96
|
+
snapshot: SingularOrPluralSnapshot,
|
|
97
|
+
f: Snapshot => void,
|
|
98
|
+
): void {
|
|
99
|
+
if (Array.isArray(snapshot)) {
|
|
100
|
+
snapshot.forEach(f);
|
|
101
|
+
} else {
|
|
102
|
+
f(snapshot);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
70
106
|
function getFragmentResult(
|
|
71
107
|
cacheKey: string,
|
|
72
108
|
snapshot: SingularOrPluralSnapshot,
|
|
109
|
+
storeEpoch: number,
|
|
73
110
|
): FragmentResult {
|
|
74
111
|
if (Array.isArray(snapshot)) {
|
|
75
|
-
return {
|
|
112
|
+
return {
|
|
113
|
+
cacheKey,
|
|
114
|
+
snapshot,
|
|
115
|
+
data: snapshot.map(s => s.data),
|
|
116
|
+
isMissingData: isMissingData(snapshot),
|
|
117
|
+
storeEpoch,
|
|
118
|
+
};
|
|
76
119
|
}
|
|
77
|
-
return {
|
|
120
|
+
return {
|
|
121
|
+
cacheKey,
|
|
122
|
+
snapshot,
|
|
123
|
+
data: snapshot.data,
|
|
124
|
+
isMissingData: isMissingData(snapshot),
|
|
125
|
+
storeEpoch,
|
|
126
|
+
};
|
|
78
127
|
}
|
|
79
128
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
129
|
+
/**
|
|
130
|
+
* The purpose of this cache is to allow information to be passed from an
|
|
131
|
+
* initial read which suspends through to the commit that follows a subsequent
|
|
132
|
+
* successful read. Specifically, the QueryResource result for the data fetch
|
|
133
|
+
* is passed through so that that query can be retained on commit.
|
|
134
|
+
*/
|
|
135
|
+
class ClientEdgeQueryResultsCache {
|
|
136
|
+
_cache: Map<string, [Array<QueryResult>, SuspenseResource]> = new Map();
|
|
137
|
+
_retainCounts: Map<string, number> = new Map();
|
|
138
|
+
_environment: IEnvironment;
|
|
139
|
+
|
|
140
|
+
constructor(environment: IEnvironment) {
|
|
141
|
+
this._environment = environment;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
get(fragmentIdentifier: string): void | Array<QueryResult> {
|
|
145
|
+
return this._cache.get(fragmentIdentifier)?.[0] ?? undefined;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
recordQueryResults(
|
|
149
|
+
fragmentIdentifier: string,
|
|
150
|
+
value: Array<QueryResult>, // may be mutated after being passed here
|
|
151
|
+
): void {
|
|
152
|
+
const existing = this._cache.get(fragmentIdentifier);
|
|
153
|
+
if (!existing) {
|
|
154
|
+
const suspenseResource = new SuspenseResource(() =>
|
|
155
|
+
this._retain(fragmentIdentifier),
|
|
156
|
+
);
|
|
157
|
+
this._cache.set(fragmentIdentifier, [value, suspenseResource]);
|
|
158
|
+
suspenseResource.temporaryRetain(this._environment);
|
|
159
|
+
} else {
|
|
160
|
+
const [existingResults, suspenseResource] = existing;
|
|
161
|
+
value.forEach(queryResult => {
|
|
162
|
+
existingResults.push(queryResult);
|
|
163
|
+
});
|
|
164
|
+
suspenseResource.temporaryRetain(this._environment);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
_retain(id) {
|
|
169
|
+
const retainCount = (this._retainCounts.get(id) ?? 0) + 1;
|
|
170
|
+
this._retainCounts.set(id, retainCount);
|
|
171
|
+
return {
|
|
172
|
+
dispose: () => {
|
|
173
|
+
const newRetainCount = (this._retainCounts.get(id) ?? 0) - 1;
|
|
174
|
+
if (newRetainCount > 0) {
|
|
175
|
+
this._retainCounts.set(id, newRetainCount);
|
|
176
|
+
} else {
|
|
177
|
+
this._retainCounts.delete(id);
|
|
178
|
+
this._cache.delete(id);
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
87
183
|
}
|
|
88
184
|
|
|
89
185
|
class FragmentResourceImpl {
|
|
90
186
|
_environment: IEnvironment;
|
|
91
187
|
_cache: FragmentResourceCache;
|
|
188
|
+
_clientEdgeQueryResultsCache: void | ClientEdgeQueryResultsCache;
|
|
92
189
|
|
|
93
190
|
constructor(environment: IEnvironment) {
|
|
94
191
|
this._environment = environment;
|
|
95
192
|
this._cache = LRUCache.create(CACHE_CAPACITY);
|
|
193
|
+
if (RelayFeatureFlags.ENABLE_CLIENT_EDGES) {
|
|
194
|
+
this._clientEdgeQueryResultsCache = new ClientEdgeQueryResultsCache(
|
|
195
|
+
environment,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
96
198
|
}
|
|
97
199
|
|
|
98
200
|
/**
|
|
@@ -133,9 +235,17 @@ class FragmentResourceImpl {
|
|
|
133
235
|
// This is a convenience when consuming fragments via a HOC API, when the
|
|
134
236
|
// prop corresponding to the fragment ref might be passed as null.
|
|
135
237
|
if (fragmentRef == null) {
|
|
136
|
-
return {
|
|
238
|
+
return {
|
|
239
|
+
cacheKey: fragmentIdentifier,
|
|
240
|
+
data: null,
|
|
241
|
+
isMissingData: false,
|
|
242
|
+
snapshot: null,
|
|
243
|
+
storeEpoch: 0,
|
|
244
|
+
};
|
|
137
245
|
}
|
|
138
246
|
|
|
247
|
+
const storeEpoch = environment.getStore().getEpoch();
|
|
248
|
+
|
|
139
249
|
// If fragmentRef is plural, ensure that it is an array.
|
|
140
250
|
// If it's empty, return the empty array directly before doing any more work.
|
|
141
251
|
if (fragmentNode?.metadata?.plural === true) {
|
|
@@ -153,7 +263,9 @@ class FragmentResourceImpl {
|
|
|
153
263
|
return {
|
|
154
264
|
cacheKey: fragmentIdentifier,
|
|
155
265
|
data: CONSTANT_READONLY_EMPTY_ARRAY,
|
|
266
|
+
isMissingData: false,
|
|
156
267
|
snapshot: CONSTANT_READONLY_EMPTY_ARRAY,
|
|
268
|
+
storeEpoch,
|
|
157
269
|
};
|
|
158
270
|
}
|
|
159
271
|
}
|
|
@@ -163,12 +275,24 @@ class FragmentResourceImpl {
|
|
|
163
275
|
// 1. Check if there's a cached value for this fragment
|
|
164
276
|
const cachedValue = this._cache.get(fragmentIdentifier);
|
|
165
277
|
if (cachedValue != null) {
|
|
166
|
-
if (isPromise(cachedValue)) {
|
|
167
|
-
|
|
278
|
+
if (cachedValue.kind === 'pending' && isPromise(cachedValue.promise)) {
|
|
279
|
+
environment.__log({
|
|
280
|
+
name: 'suspense.fragment',
|
|
281
|
+
data: cachedValue.result.data,
|
|
282
|
+
fragment: fragmentNode,
|
|
283
|
+
isRelayHooks: true,
|
|
284
|
+
isMissingData: cachedValue.result.isMissingData,
|
|
285
|
+
isPromiseCached: true,
|
|
286
|
+
pendingOperations: cachedValue.pendingOperations,
|
|
287
|
+
});
|
|
288
|
+
throw cachedValue.promise;
|
|
168
289
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
290
|
+
|
|
291
|
+
if (cachedValue.kind === 'done' && cachedValue.result.snapshot) {
|
|
292
|
+
this._reportMissingRequiredFieldsInSnapshot(
|
|
293
|
+
cachedValue.result.snapshot,
|
|
294
|
+
);
|
|
295
|
+
return cachedValue.result;
|
|
172
296
|
}
|
|
173
297
|
}
|
|
174
298
|
|
|
@@ -200,34 +324,142 @@ class FragmentResourceImpl {
|
|
|
200
324
|
? fragmentSelector.selectors.map(s => environment.lookup(s))
|
|
201
325
|
: environment.lookup(fragmentSelector);
|
|
202
326
|
|
|
327
|
+
const fragmentResult = getFragmentResult(
|
|
328
|
+
fragmentIdentifier,
|
|
329
|
+
snapshot,
|
|
330
|
+
storeEpoch,
|
|
331
|
+
);
|
|
332
|
+
if (!fragmentResult.isMissingData) {
|
|
333
|
+
this._reportMissingRequiredFieldsInSnapshot(snapshot);
|
|
334
|
+
|
|
335
|
+
this._cache.set(fragmentIdentifier, {
|
|
336
|
+
kind: 'done',
|
|
337
|
+
result: fragmentResult,
|
|
338
|
+
});
|
|
339
|
+
return fragmentResult;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// 3. If we don't have data in the store, there's two cases where we should
|
|
343
|
+
// suspend to await the data: First if any client edges were traversed where
|
|
344
|
+
// the destination record was missing data; in that case we initiate a query
|
|
345
|
+
// here to fetch the missing data. Second, there may already be a request
|
|
346
|
+
// in flight for the fragment's parent query, or for another operation that
|
|
347
|
+
// may affect the parent's query data, such as a mutation or subscription.
|
|
348
|
+
// For any of these cases we can get a promise, which we will cache and
|
|
349
|
+
// suspend on.
|
|
350
|
+
|
|
351
|
+
// First, initiate a query for any client edges that were missing data:
|
|
352
|
+
let clientEdgeRequests: ?Array<RequestDescriptor> = null;
|
|
353
|
+
if (
|
|
354
|
+
RelayFeatureFlags.ENABLE_CLIENT_EDGES &&
|
|
355
|
+
hasMissingClientEdges(snapshot)
|
|
356
|
+
) {
|
|
357
|
+
clientEdgeRequests = [];
|
|
358
|
+
const queryResource = getQueryResourceForEnvironment(this._environment);
|
|
359
|
+
const queryResults = [];
|
|
360
|
+
singularOrPluralForEach(snapshot, snap => {
|
|
361
|
+
snap.missingClientEdges?.forEach(
|
|
362
|
+
({request, clientEdgeDestinationID}) => {
|
|
363
|
+
const {queryResult, requestDescriptor} =
|
|
364
|
+
this._performClientEdgeQuery(
|
|
365
|
+
queryResource,
|
|
366
|
+
fragmentNode,
|
|
367
|
+
fragmentRef,
|
|
368
|
+
request,
|
|
369
|
+
clientEdgeDestinationID,
|
|
370
|
+
);
|
|
371
|
+
queryResults.push(queryResult);
|
|
372
|
+
clientEdgeRequests?.push(requestDescriptor);
|
|
373
|
+
},
|
|
374
|
+
);
|
|
375
|
+
});
|
|
376
|
+
// Store the query so that it can be retained when our own fragment is
|
|
377
|
+
// subscribed to. This merges with any existing query results:
|
|
378
|
+
invariant(
|
|
379
|
+
this._clientEdgeQueryResultsCache != null,
|
|
380
|
+
'Client edge query result cache should exist when ENABLE_CLIENT_EDGES is on.',
|
|
381
|
+
);
|
|
382
|
+
this._clientEdgeQueryResultsCache.recordQueryResults(
|
|
383
|
+
fragmentIdentifier,
|
|
384
|
+
queryResults,
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
let clientEdgePromises = null;
|
|
388
|
+
if (RelayFeatureFlags.ENABLE_CLIENT_EDGES && clientEdgeRequests) {
|
|
389
|
+
clientEdgePromises = clientEdgeRequests
|
|
390
|
+
.map(request => getPromiseForActiveRequest(this._environment, request))
|
|
391
|
+
.filter(p => p != null);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Finally look for operations in flight for our parent query:
|
|
203
395
|
const fragmentOwner =
|
|
204
396
|
fragmentSelector.kind === 'PluralReaderSelector'
|
|
205
397
|
? fragmentSelector.selectors[0].owner
|
|
206
398
|
: fragmentSelector.owner;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
399
|
+
const parentQueryPromiseResult =
|
|
400
|
+
this._getAndSavePromiseForFragmentRequestInFlight(
|
|
401
|
+
fragmentIdentifier,
|
|
402
|
+
fragmentNode,
|
|
403
|
+
fragmentOwner,
|
|
404
|
+
fragmentResult,
|
|
405
|
+
);
|
|
406
|
+
const parentQueryPromiseResultPromise = parentQueryPromiseResult?.promise; // for refinement
|
|
407
|
+
|
|
408
|
+
if (
|
|
409
|
+
clientEdgePromises?.length ||
|
|
410
|
+
isPromise(parentQueryPromiseResultPromise)
|
|
411
|
+
) {
|
|
412
|
+
environment.__log({
|
|
413
|
+
name: 'suspense.fragment',
|
|
414
|
+
data: fragmentResult.data,
|
|
415
|
+
fragment: fragmentNode,
|
|
416
|
+
isRelayHooks: true,
|
|
417
|
+
isPromiseCached: false,
|
|
418
|
+
isMissingData: fragmentResult.isMissingData,
|
|
419
|
+
pendingOperations: [
|
|
420
|
+
...(parentQueryPromiseResult?.pendingOperations ?? []),
|
|
421
|
+
...(clientEdgeRequests ?? []),
|
|
422
|
+
],
|
|
423
|
+
});
|
|
424
|
+
throw clientEdgePromises?.length
|
|
425
|
+
? Promise.all([parentQueryPromiseResultPromise, ...clientEdgePromises])
|
|
426
|
+
: parentQueryPromiseResultPromise;
|
|
213
427
|
}
|
|
214
428
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
429
|
+
this._reportMissingRequiredFieldsInSnapshot(snapshot);
|
|
430
|
+
return getFragmentResult(fragmentIdentifier, snapshot, storeEpoch);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
_performClientEdgeQuery(
|
|
434
|
+
queryResource: QueryResource,
|
|
435
|
+
fragmentNode: ReaderFragment,
|
|
436
|
+
fragmentRef: mixed,
|
|
437
|
+
request: ConcreteRequest,
|
|
438
|
+
clientEdgeDestinationID: DataID,
|
|
439
|
+
) {
|
|
440
|
+
const originalVariables = getVariablesFromFragment(
|
|
222
441
|
fragmentNode,
|
|
223
|
-
|
|
442
|
+
fragmentRef,
|
|
224
443
|
);
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
444
|
+
const variables = {
|
|
445
|
+
...originalVariables,
|
|
446
|
+
id: clientEdgeDestinationID, // TODO should be a reserved name
|
|
447
|
+
};
|
|
448
|
+
const operation = createOperationDescriptor(
|
|
449
|
+
request,
|
|
450
|
+
variables,
|
|
451
|
+
{}, // TODO cacheConfig should probably inherent from parent operation
|
|
452
|
+
);
|
|
453
|
+
const fetchObservable = fetchQuery(this._environment, operation);
|
|
454
|
+
const queryResult = queryResource.prepare(
|
|
455
|
+
operation,
|
|
456
|
+
fetchObservable,
|
|
457
|
+
// TODO should inherent render policy etc. from parent operation
|
|
458
|
+
);
|
|
459
|
+
return {
|
|
460
|
+
requestDescriptor: operation.request,
|
|
461
|
+
queryResult,
|
|
462
|
+
};
|
|
231
463
|
}
|
|
232
464
|
|
|
233
465
|
_reportMissingRequiredFieldsInSnapshot(snapshot: SingularOrPluralSnapshot) {
|
|
@@ -277,9 +509,8 @@ class FragmentResourceImpl {
|
|
|
277
509
|
|
|
278
510
|
// 1. Check for any updates missed during render phase
|
|
279
511
|
// TODO(T44066760): More efficiently detect if we missed an update
|
|
280
|
-
const [didMissUpdates, currentSnapshot] =
|
|
281
|
-
fragmentResult
|
|
282
|
-
);
|
|
512
|
+
const [didMissUpdates, currentSnapshot] =
|
|
513
|
+
this.checkMissedUpdates(fragmentResult);
|
|
283
514
|
|
|
284
515
|
// 2. If an update was missed, notify the component so it updates with
|
|
285
516
|
// the latest data.
|
|
@@ -288,7 +519,7 @@ class FragmentResourceImpl {
|
|
|
288
519
|
}
|
|
289
520
|
|
|
290
521
|
// 3. Establish subscriptions on the snapshot(s)
|
|
291
|
-
const
|
|
522
|
+
const disposables = [];
|
|
292
523
|
if (Array.isArray(renderedSnapshot)) {
|
|
293
524
|
invariant(
|
|
294
525
|
Array.isArray(currentSnapshot),
|
|
@@ -296,13 +527,15 @@ class FragmentResourceImpl {
|
|
|
296
527
|
"If you're seeing this, this is likely a bug in Relay.",
|
|
297
528
|
);
|
|
298
529
|
currentSnapshot.forEach((snapshot, idx) => {
|
|
299
|
-
|
|
530
|
+
disposables.push(
|
|
300
531
|
environment.subscribe(snapshot, latestSnapshot => {
|
|
532
|
+
const storeEpoch = environment.getStore().getEpoch();
|
|
301
533
|
this._updatePluralSnapshot(
|
|
302
534
|
cacheKey,
|
|
303
535
|
currentSnapshot,
|
|
304
536
|
latestSnapshot,
|
|
305
537
|
idx,
|
|
538
|
+
storeEpoch,
|
|
306
539
|
);
|
|
307
540
|
callback();
|
|
308
541
|
}),
|
|
@@ -314,20 +547,32 @@ class FragmentResourceImpl {
|
|
|
314
547
|
'Relay: Expected snapshot to be singular. ' +
|
|
315
548
|
"If you're seeing this, this is likely a bug in Relay.",
|
|
316
549
|
);
|
|
317
|
-
|
|
550
|
+
disposables.push(
|
|
318
551
|
environment.subscribe(currentSnapshot, latestSnapshot => {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
552
|
+
const storeEpoch = environment.getStore().getEpoch();
|
|
553
|
+
this._cache.set(cacheKey, {
|
|
554
|
+
kind: 'done',
|
|
555
|
+
result: getFragmentResult(cacheKey, latestSnapshot, storeEpoch),
|
|
556
|
+
});
|
|
323
557
|
callback();
|
|
324
558
|
}),
|
|
325
559
|
);
|
|
326
560
|
}
|
|
327
561
|
|
|
562
|
+
if (RelayFeatureFlags.ENABLE_CLIENT_EDGES) {
|
|
563
|
+
const clientEdgeQueryResults =
|
|
564
|
+
this._clientEdgeQueryResultsCache?.get(cacheKey) ?? undefined;
|
|
565
|
+
if (clientEdgeQueryResults?.length) {
|
|
566
|
+
const queryResource = getQueryResourceForEnvironment(this._environment);
|
|
567
|
+
clientEdgeQueryResults.forEach(queryResult => {
|
|
568
|
+
disposables.push(queryResource.retain(queryResult));
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
328
573
|
return {
|
|
329
574
|
dispose: () => {
|
|
330
|
-
|
|
575
|
+
disposables.forEach(s => s.dispose());
|
|
331
576
|
this._cache.delete(cacheKey);
|
|
332
577
|
},
|
|
333
578
|
};
|
|
@@ -353,18 +598,25 @@ class FragmentResourceImpl {
|
|
|
353
598
|
fragmentResult: FragmentResult,
|
|
354
599
|
): [boolean, SingularOrPluralSnapshot | null] {
|
|
355
600
|
const environment = this._environment;
|
|
356
|
-
const {cacheKey} = fragmentResult;
|
|
357
601
|
const renderedSnapshot = fragmentResult.snapshot;
|
|
358
602
|
if (!renderedSnapshot) {
|
|
359
603
|
return [false, null];
|
|
360
604
|
}
|
|
361
605
|
|
|
362
|
-
let
|
|
606
|
+
let storeEpoch = null;
|
|
607
|
+
// Bail out if the store hasn't been written since last read
|
|
608
|
+
storeEpoch = environment.getStore().getEpoch();
|
|
609
|
+
if (fragmentResult.storeEpoch === storeEpoch) {
|
|
610
|
+
return [false, fragmentResult.snapshot];
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const {cacheKey} = fragmentResult;
|
|
363
614
|
|
|
364
615
|
if (Array.isArray(renderedSnapshot)) {
|
|
616
|
+
let didMissUpdates = false;
|
|
365
617
|
const currentSnapshots = [];
|
|
366
618
|
renderedSnapshot.forEach((snapshot, idx) => {
|
|
367
|
-
let currentSnapshot = environment.lookup(snapshot.selector);
|
|
619
|
+
let currentSnapshot: Snapshot = environment.lookup(snapshot.selector);
|
|
368
620
|
const renderData = snapshot.data;
|
|
369
621
|
const currentData = currentSnapshot.data;
|
|
370
622
|
const updatedData = recycleNodesInto(renderData, currentData);
|
|
@@ -374,35 +626,40 @@ class FragmentResourceImpl {
|
|
|
374
626
|
}
|
|
375
627
|
currentSnapshots[idx] = currentSnapshot;
|
|
376
628
|
});
|
|
629
|
+
// Only update the cache when the data is changed to avoid
|
|
630
|
+
// returning different `data` instances
|
|
377
631
|
if (didMissUpdates) {
|
|
378
|
-
this._cache.set(
|
|
379
|
-
|
|
380
|
-
getFragmentResult(cacheKey, currentSnapshots),
|
|
381
|
-
);
|
|
632
|
+
this._cache.set(cacheKey, {
|
|
633
|
+
kind: 'done',
|
|
634
|
+
result: getFragmentResult(cacheKey, currentSnapshots, storeEpoch),
|
|
635
|
+
});
|
|
382
636
|
}
|
|
383
637
|
return [didMissUpdates, currentSnapshots];
|
|
384
638
|
}
|
|
385
|
-
|
|
639
|
+
const currentSnapshot = environment.lookup(renderedSnapshot.selector);
|
|
386
640
|
const renderData = renderedSnapshot.data;
|
|
387
641
|
const currentData = currentSnapshot.data;
|
|
388
642
|
const updatedData = recycleNodesInto(renderData, currentData);
|
|
389
|
-
|
|
643
|
+
const updatedCurrentSnapshot: Snapshot = {
|
|
390
644
|
data: updatedData,
|
|
391
645
|
isMissingData: currentSnapshot.isMissingData,
|
|
646
|
+
missingClientEdges: currentSnapshot.missingClientEdges,
|
|
392
647
|
seenRecords: currentSnapshot.seenRecords,
|
|
393
648
|
selector: currentSnapshot.selector,
|
|
394
649
|
missingRequiredFields: currentSnapshot.missingRequiredFields,
|
|
395
650
|
};
|
|
396
651
|
if (updatedData !== renderData) {
|
|
397
|
-
this._cache.set(cacheKey,
|
|
398
|
-
|
|
652
|
+
this._cache.set(cacheKey, {
|
|
653
|
+
kind: 'done',
|
|
654
|
+
result: getFragmentResult(cacheKey, updatedCurrentSnapshot, storeEpoch),
|
|
655
|
+
});
|
|
399
656
|
}
|
|
400
|
-
return [
|
|
657
|
+
return [updatedData !== renderData, updatedCurrentSnapshot];
|
|
401
658
|
}
|
|
402
659
|
|
|
403
660
|
checkMissedUpdatesSpec(fragmentResults: {
|
|
404
661
|
[string]: FragmentResult,
|
|
405
|
-
|
|
662
|
+
...
|
|
406
663
|
}): boolean {
|
|
407
664
|
return Object.keys(fragmentResults).some(
|
|
408
665
|
key => this.checkMissedUpdates(fragmentResults[key])[0],
|
|
@@ -413,18 +670,25 @@ class FragmentResourceImpl {
|
|
|
413
670
|
cacheKey: string,
|
|
414
671
|
fragmentNode: ReaderFragment,
|
|
415
672
|
fragmentOwner: RequestDescriptor,
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
673
|
+
fragmentResult: FragmentResult,
|
|
674
|
+
): {|
|
|
675
|
+
promise: Promise<void>,
|
|
676
|
+
pendingOperations: $ReadOnlyArray<RequestDescriptor>,
|
|
677
|
+
|} | null {
|
|
678
|
+
const pendingOperationsResult = getPendingOperationsForFragment(
|
|
679
|
+
this._environment,
|
|
680
|
+
fragmentNode,
|
|
681
|
+
fragmentOwner,
|
|
682
|
+
);
|
|
683
|
+
if (pendingOperationsResult == null) {
|
|
423
684
|
return null;
|
|
424
685
|
}
|
|
686
|
+
|
|
425
687
|
// When the Promise for the request resolves, we need to make sure to
|
|
426
688
|
// update the cache with the latest data available in the store before
|
|
427
689
|
// resolving the Promise
|
|
690
|
+
const networkPromise = pendingOperationsResult.promise;
|
|
691
|
+
const pendingOperations = pendingOperationsResult.pendingOperations;
|
|
428
692
|
const promise = networkPromise
|
|
429
693
|
.then(() => {
|
|
430
694
|
this._cache.delete(cacheKey);
|
|
@@ -432,17 +696,15 @@ class FragmentResourceImpl {
|
|
|
432
696
|
.catch((error: Error) => {
|
|
433
697
|
this._cache.delete(cacheKey);
|
|
434
698
|
});
|
|
435
|
-
this._cache.set(cacheKey, promise);
|
|
436
|
-
|
|
437
|
-
const queryName = fragmentOwner.node.params.name;
|
|
438
|
-
const fragmentName = fragmentNode.name;
|
|
439
|
-
const promiseDisplayName =
|
|
440
|
-
queryName === fragmentName
|
|
441
|
-
? `Relay(${queryName})`
|
|
442
|
-
: `Relay(${queryName}:${fragmentName})`;
|
|
443
699
|
// $FlowExpectedError[prop-missing] Expando to annotate Promises.
|
|
444
|
-
promise.displayName =
|
|
445
|
-
|
|
700
|
+
promise.displayName = networkPromise.displayName;
|
|
701
|
+
this._cache.set(cacheKey, {
|
|
702
|
+
kind: 'pending',
|
|
703
|
+
pendingOperations,
|
|
704
|
+
promise,
|
|
705
|
+
result: fragmentResult,
|
|
706
|
+
});
|
|
707
|
+
return {promise, pendingOperations};
|
|
446
708
|
}
|
|
447
709
|
|
|
448
710
|
_updatePluralSnapshot(
|
|
@@ -450,6 +712,7 @@ class FragmentResourceImpl {
|
|
|
450
712
|
baseSnapshots: $ReadOnlyArray<Snapshot>,
|
|
451
713
|
latestSnapshot: Snapshot,
|
|
452
714
|
idx: number,
|
|
715
|
+
storeEpoch: number,
|
|
453
716
|
): void {
|
|
454
717
|
const currentFragmentResult = this._cache.get(cacheKey);
|
|
455
718
|
if (isPromise(currentFragmentResult)) {
|
|
@@ -457,7 +720,7 @@ class FragmentResourceImpl {
|
|
|
457
720
|
return;
|
|
458
721
|
}
|
|
459
722
|
|
|
460
|
-
const currentSnapshot = currentFragmentResult?.snapshot;
|
|
723
|
+
const currentSnapshot = currentFragmentResult?.result?.snapshot;
|
|
461
724
|
if (currentSnapshot && !Array.isArray(currentSnapshot)) {
|
|
462
725
|
reportInvalidCachedData(latestSnapshot.selector.node.name);
|
|
463
726
|
return;
|
|
@@ -467,7 +730,10 @@ class FragmentResourceImpl {
|
|
|
467
730
|
? [...currentSnapshot]
|
|
468
731
|
: [...baseSnapshots];
|
|
469
732
|
nextSnapshots[idx] = latestSnapshot;
|
|
470
|
-
this._cache.set(cacheKey,
|
|
733
|
+
this._cache.set(cacheKey, {
|
|
734
|
+
kind: 'done',
|
|
735
|
+
result: getFragmentResult(cacheKey, nextSnapshots, storeEpoch),
|
|
736
|
+
});
|
|
471
737
|
}
|
|
472
738
|
}
|
|
473
739
|
|