relay-runtime 18.2.0 → 20.0.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/experimental.js +1 -1
- package/experimental.js.flow +8 -6
- package/index.js +1 -1
- package/index.js.flow +3 -0
- package/lib/experimental.js +5 -2
- package/lib/index.js +3 -0
- package/lib/multi-actor-environment/ActorSpecificEnvironment.js +1 -1
- package/lib/mutations/RelayRecordSourceProxy.js +2 -1
- package/lib/mutations/createUpdatableProxy.js +1 -1
- package/lib/mutations/validateMutation.js +2 -2
- package/lib/network/RelayObservable.js +1 -3
- package/lib/network/wrapNetworkWithLogObserver.js +2 -2
- package/lib/query/fetchQuery.js +1 -1
- package/lib/store/DataChecker.js +12 -8
- package/lib/store/OperationExecutor.js +93 -43
- package/lib/store/RelayModernEnvironment.js +13 -4
- package/lib/store/RelayModernFragmentSpecResolver.js +4 -4
- package/lib/store/RelayModernStore.js +49 -24
- package/lib/store/RelayPublishQueue.js +11 -15
- package/lib/store/RelayReader.js +134 -151
- package/lib/store/RelayReferenceMarker.js +14 -7
- package/lib/store/RelayResponseNormalizer.js +57 -31
- package/lib/store/RelayStoreSubscriptions.js +2 -2
- package/lib/store/RelayStoreUtils.js +8 -0
- package/lib/store/ResolverFragments.js +2 -2
- package/lib/store/createRelayLoggingContext.js +17 -0
- package/lib/store/generateTypenamePrefixedDataID.js +9 -0
- package/lib/store/live-resolvers/LiveResolverCache.js +4 -2
- package/lib/store/live-resolvers/resolverDataInjector.js +4 -4
- package/lib/store/normalizeResponse.js +2 -2
- package/lib/store/observeFragmentExperimental.js +60 -13
- package/lib/store/observeQueryExperimental.js +21 -0
- package/lib/util/RelayFeatureFlags.js +7 -1
- package/lib/util/handlePotentialSnapshotErrors.js +11 -8
- package/multi-actor-environment/ActorSpecificEnvironment.js.flow +1 -0
- package/mutations/RelayRecordSourceProxy.js.flow +4 -0
- package/mutations/createUpdatableProxy.js.flow +1 -1
- package/mutations/validateMutation.js.flow +3 -3
- package/network/RelayNetworkTypes.js.flow +3 -0
- package/network/RelayObservable.js.flow +1 -5
- package/network/wrapNetworkWithLogObserver.js.flow +19 -1
- package/package.json +1 -1
- package/query/fetchQuery.js.flow +1 -1
- package/store/DataChecker.js.flow +16 -4
- package/store/OperationExecutor.js.flow +101 -15
- package/store/RelayExperimentalGraphResponseTransform.js.flow +4 -4
- package/store/RelayModernEnvironment.js.flow +22 -6
- package/store/RelayModernFragmentSpecResolver.js.flow +6 -6
- package/store/RelayModernSelector.js.flow +2 -0
- package/store/RelayModernStore.js.flow +86 -27
- package/store/RelayPublishQueue.js.flow +32 -21
- package/store/RelayReader.js.flow +168 -97
- package/store/RelayReferenceMarker.js.flow +15 -5
- package/store/RelayResponseNormalizer.js.flow +104 -69
- package/store/RelayStoreSubscriptions.js.flow +2 -2
- package/store/RelayStoreTypes.js.flow +34 -4
- package/store/RelayStoreUtils.js.flow +29 -0
- package/store/ResolverCache.js.flow +2 -2
- package/store/ResolverFragments.js.flow +5 -3
- package/store/StoreInspector.js.flow +5 -0
- package/store/createRelayContext.js.flow +3 -2
- package/store/createRelayLoggingContext.js.flow +46 -0
- package/store/generateTypenamePrefixedDataID.js.flow +25 -0
- package/store/live-resolvers/LiveResolverCache.js.flow +7 -2
- package/store/live-resolvers/resolverDataInjector.js.flow +10 -6
- package/store/normalizeResponse.js.flow +2 -0
- package/store/observeFragmentExperimental.js.flow +82 -28
- package/store/observeQueryExperimental.js.flow +61 -0
- package/store/waitForFragmentExperimental.js.flow +4 -3
- package/util/NormalizationNode.js.flow +2 -1
- package/util/RelayConcreteNode.js.flow +2 -0
- package/util/RelayError.js.flow +1 -0
- package/util/RelayFeatureFlags.js.flow +28 -0
- package/util/RelayRuntimeTypes.js.flow +6 -3
- package/util/getPaginationVariables.js.flow +2 -0
- package/util/handlePotentialSnapshotErrors.js.flow +23 -11
- package/util/registerEnvironmentWithDevTools.js.flow +4 -2
- package/util/withProvidedVariables.js.flow +1 -0
- package/util/withStartAndDuration.js.flow +3 -0
- package/relay-runtime-experimental.js +0 -4
- package/relay-runtime-experimental.min.js +0 -9
- package/relay-runtime.js +0 -4
- package/relay-runtime.min.js +0 -9
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
'use strict';
|
|
13
13
|
|
|
14
14
|
import type {RelayContext} from './RelayStoreTypes.js';
|
|
15
|
+
import type {Context} from 'react';
|
|
15
16
|
import typeof {createContext} from 'react';
|
|
16
17
|
|
|
17
18
|
const invariant = require('invariant');
|
|
@@ -24,10 +25,10 @@ type React = $ReadOnly<{
|
|
|
24
25
|
...
|
|
25
26
|
}>;
|
|
26
27
|
|
|
27
|
-
let relayContext: ?
|
|
28
|
+
let relayContext: ?Context<RelayContext | null>;
|
|
28
29
|
let firstReact: ?React;
|
|
29
30
|
|
|
30
|
-
function createRelayContext(react: React):
|
|
31
|
+
function createRelayContext(react: React): Context<RelayContext | null> {
|
|
31
32
|
if (!relayContext) {
|
|
32
33
|
relayContext = react.createContext(null);
|
|
33
34
|
if (__DEV__) {
|
|
@@ -0,0 +1,46 @@
|
|
|
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
|
+
* @format
|
|
9
|
+
* @oncall relay
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
import type {Context} from 'react';
|
|
15
|
+
import typeof {createContext} from 'react';
|
|
16
|
+
|
|
17
|
+
const invariant = require('invariant');
|
|
18
|
+
|
|
19
|
+
// Ideally, we'd just import the type of the react module, but this causes Flow
|
|
20
|
+
// problems.
|
|
21
|
+
type React = $ReadOnly<{
|
|
22
|
+
createContext: createContext<mixed | null>,
|
|
23
|
+
version: string,
|
|
24
|
+
...
|
|
25
|
+
}>;
|
|
26
|
+
|
|
27
|
+
let relayLoggingContext: ?Context<mixed | null>;
|
|
28
|
+
let firstReact: ?React;
|
|
29
|
+
|
|
30
|
+
function createRelayLoggingContext(react: React): Context<mixed | null> {
|
|
31
|
+
if (!relayLoggingContext) {
|
|
32
|
+
relayLoggingContext = react.createContext(null);
|
|
33
|
+
if (__DEV__) {
|
|
34
|
+
relayLoggingContext.displayName = 'RelayLoggingContext';
|
|
35
|
+
}
|
|
36
|
+
firstReact = react;
|
|
37
|
+
}
|
|
38
|
+
invariant(
|
|
39
|
+
react === firstReact,
|
|
40
|
+
'[createRelayLoggingContext]: You are passing a different instance of React',
|
|
41
|
+
react.version,
|
|
42
|
+
);
|
|
43
|
+
return relayLoggingContext;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = createRelayLoggingContext;
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
* @format
|
|
9
|
+
* @oncall relay
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
import type {DataID} from 'relay-runtime/util/RelayRuntimeTypes';
|
|
15
|
+
|
|
16
|
+
const TYPENAME_PREFIX = '__type:';
|
|
17
|
+
|
|
18
|
+
function generateTypenamePrefixedDataID(
|
|
19
|
+
typeName: string,
|
|
20
|
+
dataID: DataID,
|
|
21
|
+
): DataID {
|
|
22
|
+
return `${TYPENAME_PREFIX}${typeName}:${dataID}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = {generateTypenamePrefixedDataID};
|
|
@@ -48,6 +48,7 @@ const {
|
|
|
48
48
|
RELAY_RESOLVER_OUTPUT_TYPE_RECORD_IDS,
|
|
49
49
|
RELAY_RESOLVER_SNAPSHOT_KEY,
|
|
50
50
|
RELAY_RESOLVER_VALUE_KEY,
|
|
51
|
+
getReadTimeResolverStorageKey,
|
|
51
52
|
getStorageKey,
|
|
52
53
|
} = require('../RelayStoreUtils');
|
|
53
54
|
const getOutputTypeRecordIDs = require('./getOutputTypeRecordIDs');
|
|
@@ -129,7 +130,7 @@ class LiveResolverCache implements ResolverCache {
|
|
|
129
130
|
// resolvers on this parent record.
|
|
130
131
|
const record = expectRecord(recordSource, recordID);
|
|
131
132
|
|
|
132
|
-
const storageKey =
|
|
133
|
+
const storageKey = getReadTimeResolverStorageKey(field, variables);
|
|
133
134
|
let linkedID = RelayModernRecord.getLinkedRecordID(record, storageKey);
|
|
134
135
|
let linkedRecord = linkedID == null ? null : recordSource.get(linkedID);
|
|
135
136
|
|
|
@@ -753,7 +754,8 @@ class LiveResolverCache implements ResolverCache {
|
|
|
753
754
|
outputTypeDataID,
|
|
754
755
|
variables,
|
|
755
756
|
);
|
|
756
|
-
|
|
757
|
+
// LiveResolverCache is only used by read time resolvers, so this flag should be hardcoded as false.
|
|
758
|
+
const useExecTimeResolvers = false;
|
|
757
759
|
const normalizationOptions =
|
|
758
760
|
this._store.__getNormalizationOptions(fieldPath);
|
|
759
761
|
// The resulted `source` is the normalized version of the
|
|
@@ -761,6 +763,7 @@ class LiveResolverCache implements ResolverCache {
|
|
|
761
763
|
// All records in the `source` should have IDs that
|
|
762
764
|
// is "prefix-ed" with the parent resolver record `ID`
|
|
763
765
|
// and they don't expect to have a "strong" identifier.
|
|
766
|
+
|
|
764
767
|
return normalize(
|
|
765
768
|
source,
|
|
766
769
|
selector,
|
|
@@ -770,6 +773,8 @@ class LiveResolverCache implements ResolverCache {
|
|
|
770
773
|
// $FlowFixMe[incompatible-variance]
|
|
771
774
|
value,
|
|
772
775
|
normalizationOptions,
|
|
776
|
+
undefined,
|
|
777
|
+
useExecTimeResolvers,
|
|
773
778
|
).source;
|
|
774
779
|
}
|
|
775
780
|
// For weak models we have a simpler case. We simply need to update a
|
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
'use strict';
|
|
13
13
|
|
|
14
14
|
import type {Fragment} from '../../util/RelayRuntimeTypes';
|
|
15
|
-
import type {FragmentType} from '../RelayStoreTypes';
|
|
15
|
+
import type {FragmentType, ResolverContext} from '../RelayStoreTypes';
|
|
16
16
|
|
|
17
17
|
const {readFragment} = require('../ResolverFragments');
|
|
18
18
|
const invariant = require('invariant');
|
|
19
19
|
|
|
20
|
-
type ResolverFn = ($FlowFixMe, ?$FlowFixMe) => mixed;
|
|
20
|
+
type ResolverFn = ($FlowFixMe, ?$FlowFixMe, ResolverContext) => mixed;
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
*
|
|
@@ -40,7 +40,11 @@ function resolverDataInjector<TFragmentType: FragmentType, TData: ?{...}>(
|
|
|
40
40
|
isRequiredField?: boolean,
|
|
41
41
|
): (fragmentKey: TFragmentType, args: mixed) => mixed {
|
|
42
42
|
const resolverFn: ResolverFn = _resolverFn;
|
|
43
|
-
return (
|
|
43
|
+
return (
|
|
44
|
+
fragmentKey: TFragmentType,
|
|
45
|
+
args: mixed,
|
|
46
|
+
resolverContext: ResolverContext,
|
|
47
|
+
): mixed => {
|
|
44
48
|
const data = readFragment(fragment, fragmentKey);
|
|
45
49
|
if (fieldName != null) {
|
|
46
50
|
if (data == null) {
|
|
@@ -52,7 +56,7 @@ function resolverDataInjector<TFragmentType: FragmentType, TData: ?{...}>(
|
|
|
52
56
|
fragment.name,
|
|
53
57
|
);
|
|
54
58
|
} else {
|
|
55
|
-
return resolverFn(null, args);
|
|
59
|
+
return resolverFn(null, args, resolverContext); // TODO: This statement does not seem to be covered by a test?
|
|
56
60
|
}
|
|
57
61
|
}
|
|
58
62
|
|
|
@@ -70,7 +74,7 @@ function resolverDataInjector<TFragmentType: FragmentType, TData: ?{...}>(
|
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
// $FlowFixMe[invalid-computed-prop]
|
|
73
|
-
return resolverFn(data[fieldName], args);
|
|
77
|
+
return resolverFn(data[fieldName], args, resolverContext);
|
|
74
78
|
} else {
|
|
75
79
|
// If both `data` and `fieldName` is available, we expect the
|
|
76
80
|
// `fieldName` field in the `data` object.
|
|
@@ -83,7 +87,7 @@ function resolverDataInjector<TFragmentType: FragmentType, TData: ?{...}>(
|
|
|
83
87
|
}
|
|
84
88
|
} else {
|
|
85
89
|
// By default we will pass the full set of the fragment data to the resolver
|
|
86
|
-
return resolverFn(
|
|
90
|
+
return resolverFn(null, args, resolverContext); // TODO: This statement does not seem to be covered by a test?
|
|
87
91
|
}
|
|
88
92
|
};
|
|
89
93
|
}
|
|
@@ -27,6 +27,7 @@ function normalizeResponse(
|
|
|
27
27
|
selector: NormalizationSelector,
|
|
28
28
|
typeName: string,
|
|
29
29
|
options: NormalizationOptions,
|
|
30
|
+
useExecTimeResolvers: boolean,
|
|
30
31
|
): RelayResponsePayload {
|
|
31
32
|
const {data, errors} = response;
|
|
32
33
|
const source = RelayRecordSource.create();
|
|
@@ -38,6 +39,7 @@ function normalizeResponse(
|
|
|
38
39
|
data,
|
|
39
40
|
options,
|
|
40
41
|
errors,
|
|
42
|
+
useExecTimeResolvers,
|
|
41
43
|
);
|
|
42
44
|
return {
|
|
43
45
|
...relayPayload,
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* @oncall relay
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import type {RequestDescriptor} from './RelayStoreTypes';
|
|
12
|
+
import type {PluralReaderSelector, RequestDescriptor} from './RelayStoreTypes';
|
|
13
13
|
import type {
|
|
14
14
|
Fragment,
|
|
15
15
|
FragmentType,
|
|
@@ -20,8 +20,8 @@ import type {
|
|
|
20
20
|
} from 'relay-runtime';
|
|
21
21
|
|
|
22
22
|
const Observable = require('../network/RelayObservable');
|
|
23
|
+
const {getObservableForActiveRequest} = require('../query/fetchQueryInternal');
|
|
23
24
|
const {getFragment} = require('../query/GraphQLTag');
|
|
24
|
-
const getPendingOperationsForFragment = require('../util/getPendingOperationsForFragment');
|
|
25
25
|
const {
|
|
26
26
|
handlePotentialSnapshotErrors,
|
|
27
27
|
} = require('../util/handlePotentialSnapshotErrors');
|
|
@@ -47,8 +47,7 @@ export type HasSpread<TFragmentType> = {
|
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
* EXPERIMENTAL: This API is experimental and does not yet support all Relay
|
|
50
|
-
* features. Notably, it does not
|
|
51
|
-
* features of Relay Resolvers.
|
|
50
|
+
* features. Notably, it does not correctly handle some features of Relay Resolvers.
|
|
52
51
|
*
|
|
53
52
|
* Given a fragment and a fragment reference, returns a promise that resolves
|
|
54
53
|
* once the fragment data is available, or rejects if the fragment has an error.
|
|
@@ -63,7 +62,9 @@ export type HasSpread<TFragmentType> = {
|
|
|
63
62
|
async function waitForFragmentData<TFragmentType: FragmentType, TData>(
|
|
64
63
|
environment: IEnvironment,
|
|
65
64
|
fragment: Fragment<TFragmentType, TData>,
|
|
66
|
-
fragmentRef:
|
|
65
|
+
fragmentRef:
|
|
66
|
+
| HasSpread<TFragmentType>
|
|
67
|
+
| $ReadOnlyArray<HasSpread<TFragmentType>>,
|
|
67
68
|
): Promise<TData> {
|
|
68
69
|
let subscription: ?Subscription;
|
|
69
70
|
|
|
@@ -94,13 +95,14 @@ async function waitForFragmentData<TFragmentType: FragmentType, TData>(
|
|
|
94
95
|
declare function observeFragment<TFragmentType: FragmentType, TData>(
|
|
95
96
|
environment: IEnvironment,
|
|
96
97
|
fragment: Fragment<TFragmentType, TData>,
|
|
97
|
-
fragmentRef:
|
|
98
|
+
fragmentRef:
|
|
99
|
+
| HasSpread<TFragmentType>
|
|
100
|
+
| $ReadOnlyArray<HasSpread<TFragmentType>>,
|
|
98
101
|
): Observable<FragmentState<TData>>;
|
|
99
102
|
|
|
100
103
|
/**
|
|
101
104
|
* EXPERIMENTAL: This API is experimental and does not yet support all Relay
|
|
102
|
-
* features. Notably, it does not
|
|
103
|
-
* features of Relay Resolvers.
|
|
105
|
+
* features. Notably, it does not correctly handle some features of Relay Resolvers.
|
|
104
106
|
*
|
|
105
107
|
* Given a fragment and a fragment reference, returns an observable that emits
|
|
106
108
|
* the state of the fragment over time. The observable will emit the following
|
|
@@ -114,7 +116,7 @@ function observeFragment<TFragmentType: FragmentType, TData>(
|
|
|
114
116
|
environment: IEnvironment,
|
|
115
117
|
fragment: Fragment<TFragmentType, TData>,
|
|
116
118
|
fragmentRef: mixed,
|
|
117
|
-
):
|
|
119
|
+
): mixed {
|
|
118
120
|
const fragmentNode = getFragment(fragment);
|
|
119
121
|
const fragmentSelector = getSelector(fragmentNode, fragmentRef);
|
|
120
122
|
invariant(
|
|
@@ -124,24 +126,19 @@ function observeFragment<TFragmentType: FragmentType, TData>(
|
|
|
124
126
|
invariant(fragmentSelector != null, 'Expected a selector, got null.');
|
|
125
127
|
switch (fragmentSelector.kind) {
|
|
126
128
|
case 'SingularReaderSelector':
|
|
127
|
-
return
|
|
129
|
+
return observeSingularSelector(environment, fragment, fragmentSelector);
|
|
128
130
|
case 'PluralReaderSelector': {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
// lets you subscribe at a singular fragment granularity. This makes it
|
|
135
|
-
// hard to batch updates such that when a store update causes multiple
|
|
136
|
-
// fragments to change, we can only publish a single update to the
|
|
137
|
-
// fragment owner.
|
|
138
|
-
invariant(false, 'Plural fragments are not supported');
|
|
131
|
+
return observePluralSelector(
|
|
132
|
+
environment,
|
|
133
|
+
(fragment: $FlowFixMe),
|
|
134
|
+
fragmentSelector,
|
|
135
|
+
);
|
|
139
136
|
}
|
|
140
137
|
}
|
|
141
138
|
invariant(false, 'Unsupported fragment selector kind');
|
|
142
139
|
}
|
|
143
140
|
|
|
144
|
-
function
|
|
141
|
+
function observeSingularSelector<TFragmentType: FragmentType, TData>(
|
|
145
142
|
environment: IEnvironment,
|
|
146
143
|
fragmentNode: Fragment<TFragmentType, TData>,
|
|
147
144
|
fragmentSelector: SingularReaderSelector,
|
|
@@ -173,6 +170,49 @@ function observeSelector<TFragmentType: FragmentType, TData>(
|
|
|
173
170
|
});
|
|
174
171
|
}
|
|
175
172
|
|
|
173
|
+
function observePluralSelector<
|
|
174
|
+
TFragmentType: FragmentType,
|
|
175
|
+
TData: Array<mixed>,
|
|
176
|
+
>(
|
|
177
|
+
environment: IEnvironment,
|
|
178
|
+
fragmentNode: Fragment<TFragmentType, TData>,
|
|
179
|
+
fragmentSelector: PluralReaderSelector,
|
|
180
|
+
): Observable<FragmentState<TData>> {
|
|
181
|
+
const snapshots = fragmentSelector.selectors.map(selector =>
|
|
182
|
+
environment.lookup(selector),
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
return Observable.create(sink => {
|
|
186
|
+
// This array is mutable since each subscription updates the array in place.
|
|
187
|
+
const states = snapshots.map((snapshot, index) =>
|
|
188
|
+
snapshotToFragmentState(
|
|
189
|
+
environment,
|
|
190
|
+
fragmentNode,
|
|
191
|
+
fragmentSelector.selectors[index].owner,
|
|
192
|
+
snapshot,
|
|
193
|
+
),
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
sink.next((mergeFragmentStates(states): $FlowFixMe));
|
|
197
|
+
|
|
198
|
+
const subscriptions = snapshots.map((snapshot, index) =>
|
|
199
|
+
environment.subscribe(snapshot, latestSnapshot => {
|
|
200
|
+
states[index] = snapshotToFragmentState(
|
|
201
|
+
environment,
|
|
202
|
+
fragmentNode,
|
|
203
|
+
fragmentSelector.selectors[index].owner,
|
|
204
|
+
latestSnapshot,
|
|
205
|
+
);
|
|
206
|
+
// This doesn't batch updates, so it will notify the subscriber multiple times
|
|
207
|
+
// if a store update impacting multiple items in the list is published.
|
|
208
|
+
sink.next((mergeFragmentStates(states): $FlowFixMe));
|
|
209
|
+
}),
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
return () => subscriptions.forEach(subscription => subscription.dispose());
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
176
216
|
function snapshotToFragmentState<TFragmentType: FragmentType, TData>(
|
|
177
217
|
environment: IEnvironment,
|
|
178
218
|
fragmentNode: Fragment<TFragmentType, TData>,
|
|
@@ -201,18 +241,18 @@ function snapshotToFragmentState<TFragmentType: FragmentType, TData>(
|
|
|
201
241
|
}
|
|
202
242
|
|
|
203
243
|
if (snapshot.isMissingData) {
|
|
204
|
-
|
|
205
|
-
environment,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
244
|
+
if (
|
|
245
|
+
getObservableForActiveRequest(environment, owner) != null ||
|
|
246
|
+
environment
|
|
247
|
+
.getOperationTracker()
|
|
248
|
+
.getPendingOperationsAffectingOwner(owner) != null
|
|
249
|
+
) {
|
|
210
250
|
return {state: 'loading'};
|
|
211
251
|
}
|
|
212
252
|
}
|
|
213
253
|
|
|
214
254
|
try {
|
|
215
|
-
handlePotentialSnapshotErrors(environment, snapshot.
|
|
255
|
+
handlePotentialSnapshotErrors(environment, snapshot.fieldErrors);
|
|
216
256
|
} catch (error) {
|
|
217
257
|
return {error, state: 'error'};
|
|
218
258
|
}
|
|
@@ -229,6 +269,20 @@ function snapshotToFragmentState<TFragmentType: FragmentType, TData>(
|
|
|
229
269
|
return {state: 'ok', value: (snapshot.data: $FlowFixMe)};
|
|
230
270
|
}
|
|
231
271
|
|
|
272
|
+
function mergeFragmentStates<T>(
|
|
273
|
+
states: $ReadOnlyArray<FragmentState<T>>,
|
|
274
|
+
): FragmentState<Array<T>> {
|
|
275
|
+
const value = [];
|
|
276
|
+
for (const state of states) {
|
|
277
|
+
if (state.state === 'ok') {
|
|
278
|
+
value.push(state.value);
|
|
279
|
+
} else {
|
|
280
|
+
return state;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return {state: 'ok', value};
|
|
284
|
+
}
|
|
285
|
+
|
|
232
286
|
module.exports = {
|
|
233
287
|
observeFragment,
|
|
234
288
|
waitForFragmentData,
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
* @format
|
|
9
|
+
* @oncall relay
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
import type RelayObservable from '../network/RelayObservable';
|
|
15
|
+
import type {FragmentState} from './observeFragmentExperimental';
|
|
16
|
+
import type {OperationDescriptor} from './RelayStoreTypes';
|
|
17
|
+
import type {Fragment, IEnvironment, Query, Variables} from 'relay-runtime';
|
|
18
|
+
|
|
19
|
+
const {observeFragment} = require('./observeFragmentExperimental');
|
|
20
|
+
const {createOperationDescriptor} = require('./RelayModernOperationDescriptor');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* This function returns an observable that can be used to subscribe to the data
|
|
24
|
+
* contained in a query. It does not return the full response shape, but rather
|
|
25
|
+
* the contents of the query body minus any fragment spreads. If you wish to
|
|
26
|
+
* read the contents of a fragment spread into this query you may pass the
|
|
27
|
+
* object into which the fragment was spread to `observeFragment`.
|
|
28
|
+
*
|
|
29
|
+
* NOTE: `observeQuery` assumes that you have already fetched and retained the
|
|
30
|
+
* query via some other means, such as `fetchQuery`.
|
|
31
|
+
*
|
|
32
|
+
* This feature is still experimental and does not properly handle some resolver
|
|
33
|
+
* features such as client-to-server edges.
|
|
34
|
+
*/
|
|
35
|
+
function observeQuery<TVariables: Variables, TData>(
|
|
36
|
+
environment: IEnvironment,
|
|
37
|
+
gqlQuery: Query<TVariables, TData>,
|
|
38
|
+
variables: TVariables,
|
|
39
|
+
): RelayObservable<FragmentState<TData>> {
|
|
40
|
+
const operation: OperationDescriptor = createOperationDescriptor(
|
|
41
|
+
gqlQuery,
|
|
42
|
+
variables,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const rootFragmentRef: $FlowFixMe = {
|
|
46
|
+
__id: operation.fragment.dataID,
|
|
47
|
+
__fragments: {
|
|
48
|
+
[operation.fragment.node.name]: operation.request.variables,
|
|
49
|
+
},
|
|
50
|
+
__fragmentOwner: operation.request,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const fragmentNode: Fragment<$FlowFixMe, TData> = (operation.request.node
|
|
54
|
+
.fragment: $FlowFixMe);
|
|
55
|
+
|
|
56
|
+
return observeFragment(environment, fragmentNode, rootFragmentRef);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = {
|
|
60
|
+
observeQuery,
|
|
61
|
+
};
|
|
@@ -21,8 +21,7 @@ const {observeFragment} = require('./observeFragmentExperimental');
|
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* EXPERIMENTAL: This API is experimental and does not yet support all Relay
|
|
24
|
-
* features. Notably, it does not
|
|
25
|
-
* features of Relay Resolvers.
|
|
24
|
+
* features. Notably, it does not correctly handle some features of Relay Resolvers.
|
|
26
25
|
*
|
|
27
26
|
* Given a fragment and a fragment reference, returns a promise that resolves
|
|
28
27
|
* once the fragment data is available, or rejects if the fragment has an error.
|
|
@@ -37,7 +36,9 @@ const {observeFragment} = require('./observeFragmentExperimental');
|
|
|
37
36
|
async function waitForFragmentData<TFragmentType: FragmentType, TData>(
|
|
38
37
|
environment: IEnvironment,
|
|
39
38
|
fragment: Fragment<TFragmentType, TData>,
|
|
40
|
-
fragmentRef:
|
|
39
|
+
fragmentRef:
|
|
40
|
+
| HasSpread<TFragmentType>
|
|
41
|
+
| $ReadOnlyArray<HasSpread<TFragmentType>>,
|
|
41
42
|
): Promise<TData> {
|
|
42
43
|
let subscription: ?Subscription;
|
|
43
44
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
'use strict';
|
|
13
13
|
|
|
14
14
|
import type {ResolverFunction, ResolverModule} from './ReaderNode';
|
|
15
|
-
import type {ConcreteRequest} from './RelayConcreteNode';
|
|
15
|
+
import type {ConcreteRequest, ProvidedVariableType} from './RelayConcreteNode';
|
|
16
16
|
import type {JSResourceReference} from 'JSResourceReference';
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -28,6 +28,7 @@ export type NormalizationOperation = {
|
|
|
28
28
|
+[string]: $ReadOnlyArray<string>,
|
|
29
29
|
},
|
|
30
30
|
+use_exec_time_resolvers?: boolean,
|
|
31
|
+
+exec_time_resolvers_enabled_provider?: ProvidedVariableType,
|
|
31
32
|
};
|
|
32
33
|
|
|
33
34
|
export type NormalizationHandle =
|
package/util/RelayError.js.flow
CHANGED
|
@@ -26,6 +26,7 @@ export type FeatureFlags = {
|
|
|
26
26
|
STRING_INTERN_LEVEL: number,
|
|
27
27
|
LOG_MISSING_RECORDS_IN_PROD: boolean,
|
|
28
28
|
ENABLE_RELAY_OPERATION_TRACKER_SUSPENSE: boolean,
|
|
29
|
+
ENABLE_UI_CONTEXT_ON_RELAY_LOGGER: boolean,
|
|
29
30
|
|
|
30
31
|
// Some GraphQL servers are noncompliant with the GraphQL specification and
|
|
31
32
|
// return an empty list instead of null when there is a field error on a list.
|
|
@@ -58,6 +59,27 @@ export type FeatureFlags = {
|
|
|
58
59
|
// Adds a prefix to the storage key of read time resolvers. This is used to
|
|
59
60
|
// disambiguate the same resolver being used at both read time and exec time.
|
|
60
61
|
ENABLE_READ_TIME_RESOLVER_STORAGE_KEY_PREFIX: boolean,
|
|
62
|
+
|
|
63
|
+
// Enable the fix for usePaginationFragment stucking in loading state
|
|
64
|
+
ENABLE_USE_PAGINATION_IS_LOADING_FIX: boolean,
|
|
65
|
+
|
|
66
|
+
// Enable logging an ID collision in the Relay store
|
|
67
|
+
ENABLE_STORE_ID_COLLISION_LOGGING: boolean,
|
|
68
|
+
|
|
69
|
+
// Throw on nested store updates
|
|
70
|
+
DISALLOW_NESTED_UPDATES: boolean,
|
|
71
|
+
|
|
72
|
+
// Enable prefixing of DataID in the store with __typename
|
|
73
|
+
ENABLE_TYPENAME_PREFIXED_DATA_ID: boolean,
|
|
74
|
+
|
|
75
|
+
// Relay previously had a bug where it would fail to check for missing client
|
|
76
|
+
// edge to server data in fragments nested within client edge Relay Resolver
|
|
77
|
+
// fields. This feature flag fixes the behavior but comes with a perf cost.
|
|
78
|
+
// This flag is here to allow us to gradually rollout the fix and track the perf
|
|
79
|
+
// impact.
|
|
80
|
+
//
|
|
81
|
+
// See https://github.com/facebook/relay/issues/4882
|
|
82
|
+
CHECK_ALL_FRAGMENTS_FOR_MISSING_CLIENT_EDGES: boolean,
|
|
61
83
|
};
|
|
62
84
|
|
|
63
85
|
const RelayFeatureFlags: FeatureFlags = {
|
|
@@ -72,6 +94,7 @@ const RelayFeatureFlags: FeatureFlags = {
|
|
|
72
94
|
MAX_DATA_ID_LENGTH: null,
|
|
73
95
|
STRING_INTERN_LEVEL: 0,
|
|
74
96
|
LOG_MISSING_RECORDS_IN_PROD: false,
|
|
97
|
+
ENABLE_STORE_ID_COLLISION_LOGGING: false,
|
|
75
98
|
ENABLE_NONCOMPLIANT_ERROR_HANDLING_ON_LISTS: false,
|
|
76
99
|
ENABLE_LOOSE_SUBSCRIPTION_ATTRIBUTION: false,
|
|
77
100
|
ENABLE_OPERATION_TRACKER_OPTIMISTIC_UPDATES: false,
|
|
@@ -81,6 +104,11 @@ const RelayFeatureFlags: FeatureFlags = {
|
|
|
81
104
|
ENABLE_CYLE_DETECTION_IN_VARIABLES: false,
|
|
82
105
|
ENABLE_ACTIVITY_COMPATIBILITY: false,
|
|
83
106
|
ENABLE_READ_TIME_RESOLVER_STORAGE_KEY_PREFIX: true,
|
|
107
|
+
ENABLE_USE_PAGINATION_IS_LOADING_FIX: false,
|
|
108
|
+
DISALLOW_NESTED_UPDATES: false,
|
|
109
|
+
ENABLE_TYPENAME_PREFIXED_DATA_ID: false,
|
|
110
|
+
ENABLE_UI_CONTEXT_ON_RELAY_LOGGER: false,
|
|
111
|
+
CHECK_ALL_FRAGMENTS_FOR_MISSING_CLIENT_EDGES: false,
|
|
84
112
|
};
|
|
85
113
|
|
|
86
114
|
module.exports = RelayFeatureFlags;
|
|
@@ -53,9 +53,10 @@ export type VariablesOf<T: OperationType> = T['variables'];
|
|
|
53
53
|
* state of any configured response cache.
|
|
54
54
|
* - `poll`: causes a query to live update by polling at the specified interval
|
|
55
55
|
* in milliseconds. (This value will be passed to setTimeout.)
|
|
56
|
-
* - `liveConfigId`:
|
|
57
|
-
*
|
|
58
|
-
* - `
|
|
56
|
+
* - `liveConfigId`: Makes a query live by sending through RTI stack.
|
|
57
|
+
* - `onSubscribe`: Callback to be called when a live query stream is started.
|
|
58
|
+
* - `onPause`: Callback to be called when a live query stream is paused, e.g. due to a network disconnection.
|
|
59
|
+
* - `onResume`: Callback to be called when a live query stream is resumed, e.g. from a network disconnection.
|
|
59
60
|
* - `metadata`: user-supplied metadata.
|
|
60
61
|
* - `transactionId`: a user-supplied value, intended for use as a unique id for
|
|
61
62
|
* a given instance of executing an operation.
|
|
@@ -65,6 +66,8 @@ export type CacheConfig = {
|
|
|
65
66
|
poll?: ?number,
|
|
66
67
|
liveConfigId?: ?string,
|
|
67
68
|
onSubscribe?: () => void,
|
|
69
|
+
onResume?: (pauseTimeMs: number) => void,
|
|
70
|
+
onPause?: (mqttConnectionIsOk: boolean, internetIsOk: boolean) => void,
|
|
68
71
|
metadata?: {[key: string]: mixed, ...},
|
|
69
72
|
transactionId?: ?string,
|
|
70
73
|
};
|
|
@@ -56,6 +56,7 @@ function getPaginationVariables(
|
|
|
56
56
|
...baseVariables,
|
|
57
57
|
...extraVariables,
|
|
58
58
|
[backwardMetadata.cursor]: cursor,
|
|
59
|
+
// $FlowFixMe[incompatible-type]
|
|
59
60
|
[backwardMetadata.count]: count,
|
|
60
61
|
};
|
|
61
62
|
if (forwardMetadata && forwardMetadata.cursor) {
|
|
@@ -92,6 +93,7 @@ function getPaginationVariables(
|
|
|
92
93
|
...baseVariables,
|
|
93
94
|
...extraVariables,
|
|
94
95
|
[forwardMetadata.cursor]: cursor,
|
|
96
|
+
// $FlowFixMe[incompatible-type]
|
|
95
97
|
[forwardMetadata.count]: count,
|
|
96
98
|
};
|
|
97
99
|
if (backwardMetadata && backwardMetadata.cursor) {
|