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,36 +13,28 @@
|
|
|
13
13
|
|
|
14
14
|
'use strict';
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
import type {
|
|
17
|
+
EntryPoint,
|
|
18
|
+
EntryPointComponent,
|
|
19
|
+
EnvironmentProviderOptions,
|
|
20
|
+
IEnvironmentProvider,
|
|
21
|
+
} from './EntryPointTypes.flow';
|
|
18
22
|
|
|
19
23
|
const preloadQuery_DEPRECATED = require('./preloadQuery_DEPRECATED');
|
|
24
|
+
const ProfilerContext = require('./ProfilerContext');
|
|
20
25
|
const useRelayEnvironment = require('./useRelayEnvironment');
|
|
21
|
-
|
|
26
|
+
const React = require('react');
|
|
22
27
|
const {useContext, useEffect, useMemo} = require('react');
|
|
23
28
|
const {stableCopy} = require('relay-runtime');
|
|
24
29
|
|
|
25
30
|
type PreloadedEntryPoint<TEntryPointComponent> = $ReadOnly<{|
|
|
26
|
-
entryPoints:
|
|
27
|
-
|
|
28
|
-
'entryPoints',
|
|
29
|
-
>,
|
|
30
|
-
extraProps: $PropertyType<
|
|
31
|
-
React.ElementConfig<TEntryPointComponent>,
|
|
32
|
-
'extraProps',
|
|
33
|
-
>,
|
|
31
|
+
entryPoints: React.ElementConfig<TEntryPointComponent>['entryPoints'],
|
|
32
|
+
extraProps: React.ElementConfig<TEntryPointComponent>['extraProps'],
|
|
34
33
|
getComponent: () => TEntryPointComponent,
|
|
35
|
-
queries:
|
|
34
|
+
queries: React.ElementConfig<TEntryPointComponent>['queries'],
|
|
36
35
|
rootModuleID: string,
|
|
37
36
|
|}>;
|
|
38
37
|
|
|
39
|
-
import type {
|
|
40
|
-
EntryPoint,
|
|
41
|
-
EntryPointComponent,
|
|
42
|
-
EnvironmentProviderOptions,
|
|
43
|
-
IEnvironmentProvider,
|
|
44
|
-
} from './EntryPointTypes.flow';
|
|
45
|
-
|
|
46
38
|
type EntryPointContainerProps<
|
|
47
39
|
TEntryPointParams,
|
|
48
40
|
TPreloadedQueries,
|
|
@@ -100,12 +92,8 @@ function prepareEntryPoint<
|
|
|
100
92
|
if (queries != null) {
|
|
101
93
|
const queriesPropNames = Object.keys(queries);
|
|
102
94
|
queriesPropNames.forEach(queryPropName => {
|
|
103
|
-
const {
|
|
104
|
-
|
|
105
|
-
options,
|
|
106
|
-
parameters,
|
|
107
|
-
variables,
|
|
108
|
-
} = queries[queryPropName];
|
|
95
|
+
const {environmentProviderOptions, options, parameters, variables} =
|
|
96
|
+
queries[queryPropName];
|
|
109
97
|
|
|
110
98
|
const environment = environmentProvider.getEnvironment(
|
|
111
99
|
environmentProviderOptions,
|
|
@@ -128,10 +116,8 @@ function prepareEntryPoint<
|
|
|
128
116
|
if (entryPointDescription == null) {
|
|
129
117
|
return;
|
|
130
118
|
}
|
|
131
|
-
const {
|
|
132
|
-
|
|
133
|
-
entryPointParams: nestedParams,
|
|
134
|
-
} = entryPointDescription;
|
|
119
|
+
const {entryPoint: nestedEntryPoint, entryPointParams: nestedParams} =
|
|
120
|
+
entryPointDescription;
|
|
135
121
|
preloadedEntryPoints[entryPointPropName] = prepareEntryPoint(
|
|
136
122
|
environmentProvider,
|
|
137
123
|
nestedEntryPoint,
|
|
@@ -180,23 +166,23 @@ function LazyLoadEntryPointContainer_DEPRECATED<
|
|
|
180
166
|
// *must* be computed first to fetch the component's data-dependencies in
|
|
181
167
|
// parallel with the component itself (the code).
|
|
182
168
|
const entryPointParamsHash = stableStringify(entryPointParams);
|
|
183
|
-
const {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
169
|
+
const {getComponent, queries, entryPoints, extraProps, rootModuleID} =
|
|
170
|
+
useMemo(() => {
|
|
171
|
+
return prepareEntryPoint(
|
|
172
|
+
environmentProvider ?? {
|
|
173
|
+
getEnvironment: () => environment,
|
|
174
|
+
},
|
|
175
|
+
entryPoint,
|
|
176
|
+
entryPointParams,
|
|
177
|
+
);
|
|
178
|
+
// NOTE: stableParams encodes the information from params
|
|
179
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
180
|
+
}, [
|
|
181
|
+
environment,
|
|
182
|
+
environmentProvider,
|
|
183
|
+
getPreloadProps,
|
|
184
|
+
entryPointParamsHash,
|
|
185
|
+
]);
|
|
200
186
|
const Component = useMemo(() => {
|
|
201
187
|
return getComponent();
|
|
202
188
|
}, [getComponent]);
|
|
@@ -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.
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
'use strict';
|
|
15
15
|
|
|
16
16
|
const React = require('react');
|
|
17
|
+
|
|
17
18
|
const {useMemo} = React;
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -90,7 +91,7 @@ type TypenameOnlyPointer = {|+__typename: string|};
|
|
|
90
91
|
export type MatchPointer = {
|
|
91
92
|
+__fragmentPropName?: ?string,
|
|
92
93
|
+__module_component?: mixed,
|
|
93
|
-
+$
|
|
94
|
+
+$fragmentSpreads: mixed,
|
|
94
95
|
...
|
|
95
96
|
};
|
|
96
97
|
|
|
@@ -115,7 +116,7 @@ function MatchContainer<TProps: {...}, TFallback: React.Node | null>({
|
|
|
115
116
|
'MatchContainer: Expected `match` value to be an object or null/undefined.',
|
|
116
117
|
);
|
|
117
118
|
}
|
|
118
|
-
// NOTE: the MatchPointer type has a $
|
|
119
|
+
// NOTE: the MatchPointer type has a $fragmentSpreads field to ensure that only
|
|
119
120
|
// an object that contains a FragmentSpread can be passed. If the fragment
|
|
120
121
|
// spread matches, then the metadata fields below (__id, __fragments, etc.)
|
|
121
122
|
// will be present. But they can be missing if all the fragment spreads use
|
|
@@ -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,18 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
'use strict';
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const invariant = require('invariant');
|
|
19
|
-
|
|
20
|
-
const {isPromise} = require('relay-runtime');
|
|
21
|
-
|
|
22
|
-
const CACHE_CAPACITY = 1000;
|
|
23
|
-
|
|
24
|
-
const DEFAULT_FETCH_POLICY = 'store-or-network';
|
|
25
|
-
|
|
26
|
-
const DATA_RETENTION_TIMEOUT = 5 * 60 * 1000;
|
|
27
|
-
|
|
16
|
+
import type {Cache} from './LRUCache';
|
|
28
17
|
import type {
|
|
29
18
|
Disposable,
|
|
30
19
|
FetchPolicy,
|
|
@@ -32,13 +21,22 @@ import type {
|
|
|
32
21
|
IEnvironment,
|
|
33
22
|
Observable,
|
|
34
23
|
Observer,
|
|
24
|
+
OperationAvailability,
|
|
35
25
|
OperationDescriptor,
|
|
36
26
|
ReaderFragment,
|
|
37
27
|
RenderPolicy,
|
|
38
28
|
Snapshot,
|
|
39
29
|
Subscription,
|
|
40
30
|
} from 'relay-runtime';
|
|
41
|
-
|
|
31
|
+
|
|
32
|
+
const LRUCache = require('./LRUCache');
|
|
33
|
+
const SuspenseResource = require('./SuspenseResource');
|
|
34
|
+
const invariant = require('invariant');
|
|
35
|
+
const {RelayFeatureFlags, isPromise} = require('relay-runtime');
|
|
36
|
+
const warning = require('warning');
|
|
37
|
+
|
|
38
|
+
const CACHE_CAPACITY = 1000;
|
|
39
|
+
const DEFAULT_FETCH_POLICY = 'store-or-network';
|
|
42
40
|
|
|
43
41
|
export type QueryResource = QueryResourceImpl;
|
|
44
42
|
|
|
@@ -46,6 +44,12 @@ type QueryResourceCache = Cache<QueryResourceCacheEntry>;
|
|
|
46
44
|
type QueryResourceCacheEntry = {|
|
|
47
45
|
+id: number,
|
|
48
46
|
+cacheIdentifier: string,
|
|
47
|
+
+operationAvailability: ?OperationAvailability,
|
|
48
|
+
// The number of received payloads for the operation.
|
|
49
|
+
// We want to differentiate the initial graphql response for the operation
|
|
50
|
+
// from the incremental responses, so later we can choose how to handle errors
|
|
51
|
+
// in the incremental payloads.
|
|
52
|
+
processedPayloadsCount: number,
|
|
49
53
|
getRetainCount(): number,
|
|
50
54
|
getNetworkSubscription(): ?Subscription,
|
|
51
55
|
setNetworkSubscription(?Subscription): void,
|
|
@@ -53,8 +57,9 @@ type QueryResourceCacheEntry = {|
|
|
|
53
57
|
setValue(Error | Promise<void> | QueryResult): void,
|
|
54
58
|
temporaryRetain(environment: IEnvironment): Disposable,
|
|
55
59
|
permanentRetain(environment: IEnvironment): Disposable,
|
|
60
|
+
releaseTemporaryRetain(): void,
|
|
56
61
|
|};
|
|
57
|
-
opaque type QueryResult: {
|
|
62
|
+
export opaque type QueryResult: {
|
|
58
63
|
fragmentNode: ReaderFragment,
|
|
59
64
|
fragmentRef: mixed,
|
|
60
65
|
...
|
|
@@ -71,6 +76,10 @@ interface IMap<K, V> {
|
|
|
71
76
|
set(key: K, value: V): IMap<K, V>;
|
|
72
77
|
}
|
|
73
78
|
|
|
79
|
+
function operationIsLiveQuery(operation: OperationDescriptor): boolean {
|
|
80
|
+
return operation.request.node.params.metadata.live !== undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
74
83
|
function getQueryCacheIdentifier(
|
|
75
84
|
environment: IEnvironment,
|
|
76
85
|
operation: OperationDescriptor,
|
|
@@ -112,10 +121,113 @@ let nextID = 200000;
|
|
|
112
121
|
function createCacheEntry(
|
|
113
122
|
cacheIdentifier: string,
|
|
114
123
|
operation: OperationDescriptor,
|
|
124
|
+
operationAvailability: ?OperationAvailability,
|
|
125
|
+
value: Error | Promise<void> | QueryResult,
|
|
126
|
+
networkSubscription: ?Subscription,
|
|
127
|
+
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
|
+
): QueryResourceCacheEntry {
|
|
162
|
+
const isLiveQuery = operationIsLiveQuery(operation);
|
|
163
|
+
|
|
164
|
+
let currentValue: Error | Promise<void> | QueryResult = value;
|
|
165
|
+
let currentNetworkSubscription: ?Subscription = networkSubscription;
|
|
166
|
+
|
|
167
|
+
const suspenseResource = new SuspenseResource(environment => {
|
|
168
|
+
const retention = environment.retain(operation);
|
|
169
|
+
return {
|
|
170
|
+
dispose: () => {
|
|
171
|
+
// Normally if this entry never commits, the request would've ended by the
|
|
172
|
+
// time this timeout expires and the temporary retain is released. However,
|
|
173
|
+
// we need to do this for live queries which remain open indefinitely.
|
|
174
|
+
if (isLiveQuery && currentNetworkSubscription != null) {
|
|
175
|
+
currentNetworkSubscription.unsubscribe();
|
|
176
|
+
}
|
|
177
|
+
retention.dispose();
|
|
178
|
+
onDispose(cacheEntry);
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const cacheEntry = {
|
|
184
|
+
cacheIdentifier,
|
|
185
|
+
id: nextID++,
|
|
186
|
+
processedPayloadsCount: 0,
|
|
187
|
+
operationAvailability,
|
|
188
|
+
getValue() {
|
|
189
|
+
return currentValue;
|
|
190
|
+
},
|
|
191
|
+
setValue(val: QueryResult | Promise<void> | Error) {
|
|
192
|
+
currentValue = val;
|
|
193
|
+
},
|
|
194
|
+
getRetainCount() {
|
|
195
|
+
return suspenseResource.getRetainCount();
|
|
196
|
+
},
|
|
197
|
+
getNetworkSubscription() {
|
|
198
|
+
return currentNetworkSubscription;
|
|
199
|
+
},
|
|
200
|
+
setNetworkSubscription(subscription: ?Subscription) {
|
|
201
|
+
if (isLiveQuery && currentNetworkSubscription != null) {
|
|
202
|
+
currentNetworkSubscription.unsubscribe();
|
|
203
|
+
}
|
|
204
|
+
currentNetworkSubscription = subscription;
|
|
205
|
+
},
|
|
206
|
+
temporaryRetain(environment: IEnvironment): Disposable {
|
|
207
|
+
return suspenseResource.temporaryRetain(environment);
|
|
208
|
+
},
|
|
209
|
+
permanentRetain(environment: IEnvironment): Disposable {
|
|
210
|
+
return suspenseResource.permanentRetain(environment);
|
|
211
|
+
},
|
|
212
|
+
releaseTemporaryRetain() {
|
|
213
|
+
suspenseResource.releaseTemporaryRetain();
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
return cacheEntry;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const DATA_RETENTION_TIMEOUT = 5 * 60 * 1000;
|
|
221
|
+
function createCacheEntry_old(
|
|
222
|
+
cacheIdentifier: string,
|
|
223
|
+
operation: OperationDescriptor,
|
|
224
|
+
operationAvailability: ?OperationAvailability,
|
|
115
225
|
value: Error | Promise<void> | QueryResult,
|
|
116
226
|
networkSubscription: ?Subscription,
|
|
117
227
|
onDispose: QueryResourceCacheEntry => void,
|
|
118
228
|
): QueryResourceCacheEntry {
|
|
229
|
+
const isLiveQuery = operationIsLiveQuery(operation);
|
|
230
|
+
|
|
119
231
|
let currentValue: Error | Promise<void> | QueryResult = value;
|
|
120
232
|
let retainCount = 0;
|
|
121
233
|
let retainDisposable: ?Disposable = null;
|
|
@@ -147,10 +259,12 @@ function createCacheEntry(
|
|
|
147
259
|
const cacheEntry = {
|
|
148
260
|
cacheIdentifier,
|
|
149
261
|
id: nextID++,
|
|
262
|
+
processedPayloadsCount: 0,
|
|
263
|
+
operationAvailability,
|
|
150
264
|
getValue() {
|
|
151
265
|
return currentValue;
|
|
152
266
|
},
|
|
153
|
-
setValue(val) {
|
|
267
|
+
setValue(val: QueryResult | Promise<void> | Error) {
|
|
154
268
|
currentValue = val;
|
|
155
269
|
},
|
|
156
270
|
getRetainCount() {
|
|
@@ -160,7 +274,7 @@ function createCacheEntry(
|
|
|
160
274
|
return currentNetworkSubscription;
|
|
161
275
|
},
|
|
162
276
|
setNetworkSubscription(subscription: ?Subscription) {
|
|
163
|
-
if (currentNetworkSubscription != null) {
|
|
277
|
+
if (isLiveQuery && currentNetworkSubscription != null) {
|
|
164
278
|
currentNetworkSubscription.unsubscribe();
|
|
165
279
|
}
|
|
166
280
|
currentNetworkSubscription = subscription;
|
|
@@ -188,7 +302,11 @@ function createCacheEntry(
|
|
|
188
302
|
// Normally if this entry never commits, the request would've ended by the
|
|
189
303
|
// time this timeout expires and the temporary retain is released. However,
|
|
190
304
|
// we need to do this for live queries which remain open indefinitely.
|
|
191
|
-
if (
|
|
305
|
+
if (
|
|
306
|
+
isLiveQuery &&
|
|
307
|
+
retainCount <= 0 &&
|
|
308
|
+
currentNetworkSubscription != null
|
|
309
|
+
) {
|
|
192
310
|
currentNetworkSubscription.unsubscribe();
|
|
193
311
|
}
|
|
194
312
|
};
|
|
@@ -225,12 +343,22 @@ function createCacheEntry(
|
|
|
225
343
|
return {
|
|
226
344
|
dispose: () => {
|
|
227
345
|
disposable.dispose();
|
|
228
|
-
if (
|
|
346
|
+
if (
|
|
347
|
+
isLiveQuery &&
|
|
348
|
+
retainCount <= 0 &&
|
|
349
|
+
currentNetworkSubscription != null
|
|
350
|
+
) {
|
|
229
351
|
currentNetworkSubscription.unsubscribe();
|
|
230
352
|
}
|
|
231
353
|
},
|
|
232
354
|
};
|
|
233
355
|
},
|
|
356
|
+
releaseTemporaryRetain() {
|
|
357
|
+
if (releaseTemporaryRetain != null) {
|
|
358
|
+
releaseTemporaryRetain();
|
|
359
|
+
releaseTemporaryRetain = null;
|
|
360
|
+
}
|
|
361
|
+
},
|
|
234
362
|
};
|
|
235
363
|
|
|
236
364
|
return cacheEntry;
|
|
@@ -295,6 +423,7 @@ class QueryResourceImpl {
|
|
|
295
423
|
// it's available
|
|
296
424
|
let cacheEntry = this._cache.get(cacheIdentifier);
|
|
297
425
|
let temporaryRetainDisposable: ?Disposable = null;
|
|
426
|
+
const entryWasCached = cacheEntry != null;
|
|
298
427
|
if (cacheEntry == null) {
|
|
299
428
|
// 2. If a cached value isn't available, try fetching the operation.
|
|
300
429
|
// _fetchAndSaveQuery will update the cache with either a Promise or
|
|
@@ -332,7 +461,18 @@ class QueryResourceImpl {
|
|
|
332
461
|
temporaryRetainDisposable = cacheEntry.temporaryRetain(environment);
|
|
333
462
|
|
|
334
463
|
const cachedValue = cacheEntry.getValue();
|
|
335
|
-
if (isPromise(cachedValue)
|
|
464
|
+
if (isPromise(cachedValue)) {
|
|
465
|
+
environment.__log({
|
|
466
|
+
name: 'suspense.query',
|
|
467
|
+
fetchPolicy,
|
|
468
|
+
isPromiseCached: entryWasCached,
|
|
469
|
+
operation: operation,
|
|
470
|
+
queryAvailability: cacheEntry.operationAvailability,
|
|
471
|
+
renderPolicy,
|
|
472
|
+
});
|
|
473
|
+
throw cachedValue;
|
|
474
|
+
}
|
|
475
|
+
if (cachedValue instanceof Error) {
|
|
336
476
|
throw cachedValue;
|
|
337
477
|
}
|
|
338
478
|
return cachedValue;
|
|
@@ -349,6 +489,7 @@ class QueryResourceImpl {
|
|
|
349
489
|
const cacheEntry = this._getOrCreateCacheEntry(
|
|
350
490
|
cacheIdentifier,
|
|
351
491
|
operation,
|
|
492
|
+
null,
|
|
352
493
|
queryResult,
|
|
353
494
|
null,
|
|
354
495
|
);
|
|
@@ -366,6 +507,13 @@ class QueryResourceImpl {
|
|
|
366
507
|
};
|
|
367
508
|
}
|
|
368
509
|
|
|
510
|
+
releaseTemporaryRetain(queryResult: QueryResult) {
|
|
511
|
+
const cacheEntry = this._cache.get(queryResult.cacheIdentifier);
|
|
512
|
+
if (cacheEntry != null) {
|
|
513
|
+
cacheEntry.releaseTemporaryRetain();
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
369
517
|
TESTS_ONLY__getCacheEntry(
|
|
370
518
|
operation: OperationDescriptor,
|
|
371
519
|
maybeFetchPolicy?: ?FetchPolicy,
|
|
@@ -384,14 +532,21 @@ class QueryResourceImpl {
|
|
|
384
532
|
}
|
|
385
533
|
|
|
386
534
|
_clearCacheEntry = (cacheEntry: QueryResourceCacheEntry): void => {
|
|
387
|
-
|
|
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) {
|
|
388
538
|
this._cache.delete(cacheEntry.cacheIdentifier);
|
|
539
|
+
} else {
|
|
540
|
+
if (cacheEntry.getRetainCount() <= 0) {
|
|
541
|
+
this._cache.delete(cacheEntry.cacheIdentifier);
|
|
542
|
+
}
|
|
389
543
|
}
|
|
390
544
|
};
|
|
391
545
|
|
|
392
546
|
_getOrCreateCacheEntry(
|
|
393
547
|
cacheIdentifier: string,
|
|
394
548
|
operation: OperationDescriptor,
|
|
549
|
+
operationAvailability: ?OperationAvailability,
|
|
395
550
|
value: Error | Promise<void> | QueryResult,
|
|
396
551
|
networkSubscription: ?Subscription,
|
|
397
552
|
): QueryResourceCacheEntry {
|
|
@@ -400,6 +555,7 @@ class QueryResourceImpl {
|
|
|
400
555
|
cacheEntry = createCacheEntry(
|
|
401
556
|
cacheIdentifier,
|
|
402
557
|
operation,
|
|
558
|
+
operationAvailability,
|
|
403
559
|
value,
|
|
404
560
|
networkSubscription,
|
|
405
561
|
this._clearCacheEntry,
|
|
@@ -466,6 +622,7 @@ class QueryResourceImpl {
|
|
|
466
622
|
const cacheEntry = createCacheEntry(
|
|
467
623
|
cacheIdentifier,
|
|
468
624
|
operation,
|
|
625
|
+
queryAvailability,
|
|
469
626
|
queryResult,
|
|
470
627
|
null,
|
|
471
628
|
this._clearCacheEntry,
|
|
@@ -483,32 +640,65 @@ class QueryResourceImpl {
|
|
|
483
640
|
if (cacheEntry) {
|
|
484
641
|
cacheEntry.setNetworkSubscription(networkSubscription);
|
|
485
642
|
}
|
|
486
|
-
|
|
487
643
|
const observerStart = observer?.start;
|
|
488
|
-
|
|
644
|
+
if (observerStart) {
|
|
645
|
+
const subscriptionWithConditionalCancelation = {
|
|
646
|
+
...subscription,
|
|
647
|
+
unsubscribe: () => {
|
|
648
|
+
// Only live queries should have their network requests canceled.
|
|
649
|
+
if (operationIsLiveQuery(operation)) {
|
|
650
|
+
subscription.unsubscribe();
|
|
651
|
+
}
|
|
652
|
+
},
|
|
653
|
+
};
|
|
654
|
+
observerStart(subscriptionWithConditionalCancelation);
|
|
655
|
+
}
|
|
489
656
|
},
|
|
490
657
|
next: () => {
|
|
491
|
-
const snapshot = environment.lookup(operation.fragment);
|
|
492
658
|
const cacheEntry = this._getOrCreateCacheEntry(
|
|
493
659
|
cacheIdentifier,
|
|
494
660
|
operation,
|
|
661
|
+
queryAvailability,
|
|
495
662
|
queryResult,
|
|
496
663
|
networkSubscription,
|
|
497
664
|
);
|
|
665
|
+
cacheEntry.processedPayloadsCount += 1;
|
|
498
666
|
cacheEntry.setValue(queryResult);
|
|
499
667
|
resolveNetworkPromise();
|
|
500
668
|
|
|
501
669
|
const observerNext = observer?.next;
|
|
502
|
-
observerNext
|
|
670
|
+
if (observerNext != null) {
|
|
671
|
+
const snapshot = environment.lookup(operation.fragment);
|
|
672
|
+
observerNext(snapshot);
|
|
673
|
+
}
|
|
503
674
|
},
|
|
504
675
|
error: error => {
|
|
505
676
|
const cacheEntry = this._getOrCreateCacheEntry(
|
|
506
677
|
cacheIdentifier,
|
|
507
678
|
operation,
|
|
679
|
+
queryAvailability,
|
|
508
680
|
error,
|
|
509
681
|
networkSubscription,
|
|
510
682
|
);
|
|
511
|
-
|
|
683
|
+
|
|
684
|
+
// If, this is the first thing we receive for the query,
|
|
685
|
+
// before any other payload handled is error, we will cache and
|
|
686
|
+
// re-throw that error later.
|
|
687
|
+
|
|
688
|
+
// We will ignore errors for any incremental payloads we receive.
|
|
689
|
+
if (cacheEntry.processedPayloadsCount === 0) {
|
|
690
|
+
cacheEntry.setValue(error);
|
|
691
|
+
} else {
|
|
692
|
+
// TODO:T92030819 Remove this warning and actually throw the network error
|
|
693
|
+
// To complete this task we need to have a way of precisely tracking suspendable points
|
|
694
|
+
warning(
|
|
695
|
+
false,
|
|
696
|
+
'QueryResource: An incremental payload for query `%` returned an error: `%`:`%`.',
|
|
697
|
+
operation.fragment.node.name,
|
|
698
|
+
error.message,
|
|
699
|
+
error.stack,
|
|
700
|
+
);
|
|
701
|
+
}
|
|
512
702
|
resolveNetworkPromise();
|
|
513
703
|
|
|
514
704
|
networkSubscription = null;
|
|
@@ -543,6 +733,7 @@ class QueryResourceImpl {
|
|
|
543
733
|
cacheEntry = createCacheEntry(
|
|
544
734
|
cacheIdentifier,
|
|
545
735
|
operation,
|
|
736
|
+
queryAvailability,
|
|
546
737
|
networkPromise,
|
|
547
738
|
networkSubscription,
|
|
548
739
|
this._clearCacheEntry,
|
|
@@ -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,21 +13,31 @@
|
|
|
13
13
|
|
|
14
14
|
'use strict';
|
|
15
15
|
|
|
16
|
+
import type {IEnvironment} from 'relay-runtime';
|
|
17
|
+
import type {
|
|
18
|
+
ActorIdentifier,
|
|
19
|
+
IActorEnvironment,
|
|
20
|
+
} from 'relay-runtime/multi-actor-environment';
|
|
21
|
+
|
|
16
22
|
const React = require('react');
|
|
17
23
|
const ReactRelayContext = require('react-relay/ReactRelayContext');
|
|
18
24
|
|
|
19
|
-
import type {IEnvironment} from 'relay-runtime';
|
|
20
|
-
|
|
21
25
|
const {useMemo} = React;
|
|
22
26
|
|
|
23
27
|
type Props = $ReadOnly<{|
|
|
24
28
|
children: React.Node,
|
|
25
29
|
environment: IEnvironment,
|
|
30
|
+
getEnvironmentForActor?: ?(
|
|
31
|
+
actorIdentifier: ActorIdentifier,
|
|
32
|
+
) => IActorEnvironment,
|
|
26
33
|
|}>;
|
|
27
34
|
|
|
28
35
|
function RelayEnvironmentProvider(props: Props): React.Node {
|
|
29
|
-
const {children, environment} = props;
|
|
30
|
-
const context = useMemo(
|
|
36
|
+
const {children, environment, getEnvironmentForActor} = props;
|
|
37
|
+
const context = useMemo(
|
|
38
|
+
() => ({environment, getEnvironmentForActor}),
|
|
39
|
+
[environment, getEnvironmentForActor],
|
|
40
|
+
);
|
|
31
41
|
return (
|
|
32
42
|
<ReactRelayContext.Provider value={context}>
|
|
33
43
|
{children}
|