relay-runtime 11.0.2 → 12.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/index.js +1 -1
- package/index.js.flow +16 -1
- package/lib/index.js +15 -0
- package/lib/multi-actor-environment/ActorIdentifier.js +11 -1
- package/lib/multi-actor-environment/ActorSpecificEnvironment.js +59 -19
- package/lib/multi-actor-environment/ActorUtils.js +27 -0
- package/lib/multi-actor-environment/MultiActorEnvironment.js +305 -55
- package/lib/multi-actor-environment/index.js +5 -1
- package/lib/mutations/RelayRecordSourceSelectorProxy.js +6 -1
- package/lib/mutations/commitMutation.js +4 -1
- package/lib/mutations/validateMutation.js +6 -1
- package/lib/network/RelayObservable.js +3 -1
- package/lib/network/RelayQueryResponseCache.js +19 -3
- package/lib/network/wrapNetworkWithLogObserver.js +78 -0
- package/lib/store/DataChecker.js +110 -40
- package/lib/store/OperationExecutor.js +478 -204
- package/lib/store/RelayConcreteVariables.js +21 -0
- package/lib/store/RelayModernEnvironment.js +41 -85
- package/lib/store/RelayModernFragmentSpecResolver.js +48 -22
- package/lib/store/RelayModernRecord.js +35 -1
- package/lib/store/RelayModernStore.js +48 -14
- package/lib/store/RelayOperationTracker.js +33 -23
- package/lib/store/RelayPublishQueue.js +23 -5
- package/lib/store/RelayReader.js +138 -44
- package/lib/store/RelayRecordSource.js +87 -3
- package/lib/store/RelayReferenceMarker.js +28 -15
- package/lib/store/RelayResponseNormalizer.js +164 -91
- package/lib/store/RelayStoreReactFlightUtils.js +1 -7
- package/lib/store/RelayStoreSubscriptions.js +8 -5
- package/lib/store/RelayStoreUtils.js +7 -2
- package/lib/store/ResolverCache.js +213 -0
- package/lib/store/ResolverFragments.js +1 -1
- package/lib/store/createRelayContext.js +1 -1
- package/lib/subscription/requestSubscription.js +27 -29
- package/lib/util/RelayConcreteNode.js +1 -0
- package/lib/util/RelayFeatureFlags.js +3 -5
- package/lib/util/RelayReplaySubject.js +21 -6
- package/lib/util/getPaginationMetadata.js +41 -0
- package/lib/util/getPaginationVariables.js +67 -0
- package/lib/util/getPendingOperationsForFragment.js +55 -0
- package/lib/util/getRefetchMetadata.js +36 -0
- package/lib/util/getValueAtPath.js +51 -0
- package/lib/util/isEmptyObject.js +1 -1
- package/lib/util/registerEnvironmentWithDevTools.js +26 -0
- package/lib/util/withDuration.js +31 -0
- package/multi-actor-environment/ActorIdentifier.js.flow +17 -1
- package/multi-actor-environment/ActorSpecificEnvironment.js.flow +72 -44
- package/multi-actor-environment/ActorUtils.js.flow +33 -0
- package/multi-actor-environment/MultiActorEnvironment.js.flow +332 -80
- package/multi-actor-environment/MultiActorEnvironmentTypes.js.flow +61 -12
- package/multi-actor-environment/index.js.flow +3 -0
- package/mutations/RelayRecordSourceSelectorProxy.js.flow +7 -2
- package/mutations/commitMutation.js.flow +2 -0
- package/mutations/validateMutation.js.flow +8 -0
- package/network/RelayObservable.js.flow +2 -0
- package/network/RelayQueryResponseCache.js.flow +31 -18
- package/network/wrapNetworkWithLogObserver.js.flow +99 -0
- package/package.json +1 -1
- package/relay-runtime.js +2 -2
- package/relay-runtime.min.js +2 -2
- package/store/ClientID.js.flow +5 -1
- package/store/DataChecker.js.flow +126 -35
- package/store/OperationExecutor.js.flow +528 -265
- package/store/RelayConcreteVariables.js.flow +26 -1
- package/store/RelayModernEnvironment.js.flow +41 -94
- package/store/RelayModernFragmentSpecResolver.js.flow +40 -14
- package/store/RelayModernOperationDescriptor.js.flow +9 -3
- package/store/RelayModernRecord.js.flow +49 -0
- package/store/RelayModernStore.js.flow +50 -12
- package/store/RelayOperationTracker.js.flow +56 -34
- package/store/RelayPublishQueue.js.flow +31 -8
- package/store/RelayReader.js.flow +148 -42
- package/store/RelayRecordSource.js.flow +72 -6
- package/store/RelayReferenceMarker.js.flow +29 -12
- package/store/RelayResponseNormalizer.js.flow +164 -48
- package/store/RelayStoreReactFlightUtils.js.flow +1 -7
- package/store/RelayStoreSubscriptions.js.flow +10 -3
- package/store/RelayStoreTypes.js.flow +128 -12
- package/store/RelayStoreUtils.js.flow +17 -3
- package/store/ResolverCache.js.flow +247 -0
- package/store/ResolverFragments.js.flow +6 -3
- package/store/createRelayContext.js.flow +1 -1
- package/subscription/requestSubscription.js.flow +41 -29
- package/util/NormalizationNode.js.flow +10 -3
- package/util/ReaderNode.js.flow +15 -1
- package/util/RelayConcreteNode.js.flow +1 -0
- package/util/RelayFeatureFlags.js.flow +8 -10
- package/util/RelayReplaySubject.js.flow +7 -6
- package/util/getPaginationMetadata.js.flow +74 -0
- package/util/getPaginationVariables.js.flow +112 -0
- package/util/getPendingOperationsForFragment.js.flow +62 -0
- package/util/getRefetchMetadata.js.flow +80 -0
- package/util/getValueAtPath.js.flow +46 -0
- package/util/isEmptyObject.js.flow +1 -0
- package/util/registerEnvironmentWithDevTools.js.flow +33 -0
- package/util/withDuration.js.flow +32 -0
- package/lib/store/RelayRecordSourceMapImpl.js +0 -107
- package/lib/store/RelayStoreSubscriptionsUsingMapByID.js +0 -318
- package/store/RelayRecordSourceMapImpl.js.flow +0 -91
- package/store/RelayStoreSubscriptionsUsingMapByID.js.flow +0 -283
|
@@ -17,17 +17,21 @@ const invariant = require('invariant');
|
|
|
17
17
|
import type {RequestDescriptor} from './RelayStoreTypes';
|
|
18
18
|
|
|
19
19
|
class RelayOperationTracker {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
_ownersToPendingOperations: Map<string, Map<string, RequestDescriptor>>;
|
|
21
|
+
_pendingOperationsToOwners: Map<string, Set<string>>;
|
|
22
|
+
_ownersToPendingPromise: Map<
|
|
23
23
|
string,
|
|
24
|
-
{|
|
|
24
|
+
{|
|
|
25
|
+
promise: Promise<void>,
|
|
26
|
+
resolve: () => void,
|
|
27
|
+
pendingOperations: $ReadOnlyArray<RequestDescriptor>,
|
|
28
|
+
|},
|
|
25
29
|
>;
|
|
26
30
|
|
|
27
31
|
constructor() {
|
|
28
|
-
this.
|
|
29
|
-
this.
|
|
30
|
-
this.
|
|
32
|
+
this._ownersToPendingOperations = new Map();
|
|
33
|
+
this._pendingOperationsToOwners = new Map();
|
|
34
|
+
this._ownersToPendingPromise = new Map();
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
/**
|
|
@@ -45,7 +49,7 @@ class RelayOperationTracker {
|
|
|
45
49
|
const newlyAffectedOwnersIdentifier = new Set();
|
|
46
50
|
for (const owner of affectedOwners) {
|
|
47
51
|
const ownerIdentifier = owner.identifier;
|
|
48
|
-
const pendingOperationsAffectingOwner = this.
|
|
52
|
+
const pendingOperationsAffectingOwner = this._ownersToPendingOperations.get(
|
|
49
53
|
ownerIdentifier,
|
|
50
54
|
);
|
|
51
55
|
if (pendingOperationsAffectingOwner != null) {
|
|
@@ -53,14 +57,17 @@ class RelayOperationTracker {
|
|
|
53
57
|
// We just need to detect, is it the same operation that we already
|
|
54
58
|
// have in the list, or it's a new operation
|
|
55
59
|
if (!pendingOperationsAffectingOwner.has(pendingOperationIdentifier)) {
|
|
56
|
-
pendingOperationsAffectingOwner.
|
|
60
|
+
pendingOperationsAffectingOwner.set(
|
|
61
|
+
pendingOperationIdentifier,
|
|
62
|
+
pendingOperation,
|
|
63
|
+
);
|
|
57
64
|
newlyAffectedOwnersIdentifier.add(ownerIdentifier);
|
|
58
65
|
}
|
|
59
66
|
} else {
|
|
60
67
|
// This is a new `ownerIdentifier` that is affected by the operation
|
|
61
|
-
this.
|
|
68
|
+
this._ownersToPendingOperations.set(
|
|
62
69
|
ownerIdentifier,
|
|
63
|
-
new
|
|
70
|
+
new Map([[pendingOperationIdentifier, pendingOperation]]),
|
|
64
71
|
);
|
|
65
72
|
newlyAffectedOwnersIdentifier.add(ownerIdentifier);
|
|
66
73
|
}
|
|
@@ -72,19 +79,18 @@ class RelayOperationTracker {
|
|
|
72
79
|
}
|
|
73
80
|
|
|
74
81
|
// But, if some owners were affected we need to add them to
|
|
75
|
-
// the `
|
|
76
|
-
const
|
|
77
|
-
this.
|
|
78
|
-
|
|
79
|
-
) || new Set();
|
|
82
|
+
// the `_pendingOperationsToOwners` set
|
|
83
|
+
const ownersAffectedByPendingOperation =
|
|
84
|
+
this._pendingOperationsToOwners.get(pendingOperationIdentifier) ||
|
|
85
|
+
new Set();
|
|
80
86
|
|
|
81
87
|
for (const ownerIdentifier of newlyAffectedOwnersIdentifier) {
|
|
82
88
|
this._resolveOwnerResolvers(ownerIdentifier);
|
|
83
|
-
|
|
89
|
+
ownersAffectedByPendingOperation.add(ownerIdentifier);
|
|
84
90
|
}
|
|
85
|
-
this.
|
|
91
|
+
this._pendingOperationsToOwners.set(
|
|
86
92
|
pendingOperationIdentifier,
|
|
87
|
-
|
|
93
|
+
ownersAffectedByPendingOperation,
|
|
88
94
|
);
|
|
89
95
|
}
|
|
90
96
|
|
|
@@ -94,7 +100,7 @@ class RelayOperationTracker {
|
|
|
94
100
|
*/
|
|
95
101
|
complete(pendingOperation: RequestDescriptor): void {
|
|
96
102
|
const pendingOperationIdentifier = pendingOperation.identifier;
|
|
97
|
-
const affectedOwnersIdentifier = this.
|
|
103
|
+
const affectedOwnersIdentifier = this._pendingOperationsToOwners.get(
|
|
98
104
|
pendingOperationIdentifier,
|
|
99
105
|
);
|
|
100
106
|
if (affectedOwnersIdentifier == null) {
|
|
@@ -107,7 +113,7 @@ class RelayOperationTracker {
|
|
|
107
113
|
// and some other operations
|
|
108
114
|
const updatedOwnersIdentifier = new Set();
|
|
109
115
|
for (const ownerIdentifier of affectedOwnersIdentifier) {
|
|
110
|
-
const pendingOperationsAffectingOwner = this.
|
|
116
|
+
const pendingOperationsAffectingOwner = this._ownersToPendingOperations.get(
|
|
111
117
|
ownerIdentifier,
|
|
112
118
|
);
|
|
113
119
|
if (!pendingOperationsAffectingOwner) {
|
|
@@ -124,7 +130,7 @@ class RelayOperationTracker {
|
|
|
124
130
|
// Complete subscriptions for all owners, affected by `pendingOperationIdentifier`
|
|
125
131
|
for (const ownerIdentifier of completedOwnersIdentifier) {
|
|
126
132
|
this._resolveOwnerResolvers(ownerIdentifier);
|
|
127
|
-
this.
|
|
133
|
+
this._ownersToPendingOperations.delete(ownerIdentifier);
|
|
128
134
|
}
|
|
129
135
|
|
|
130
136
|
// Update all ownerIdentifier that were updated by `pendingOperationIdentifier` but still
|
|
@@ -134,31 +140,42 @@ class RelayOperationTracker {
|
|
|
134
140
|
}
|
|
135
141
|
|
|
136
142
|
// Finally, remove pending operation identifier
|
|
137
|
-
this.
|
|
138
|
-
pendingOperationIdentifier,
|
|
139
|
-
);
|
|
143
|
+
this._pendingOperationsToOwners.delete(pendingOperationIdentifier);
|
|
140
144
|
}
|
|
141
145
|
|
|
142
146
|
_resolveOwnerResolvers(ownerIdentifier: string): void {
|
|
143
|
-
const promiseEntry = this.
|
|
147
|
+
const promiseEntry = this._ownersToPendingPromise.get(ownerIdentifier);
|
|
144
148
|
if (promiseEntry != null) {
|
|
145
149
|
promiseEntry.resolve();
|
|
146
150
|
}
|
|
147
|
-
this.
|
|
151
|
+
this._ownersToPendingPromise.delete(ownerIdentifier);
|
|
148
152
|
}
|
|
149
153
|
|
|
150
|
-
|
|
154
|
+
getPendingOperationsAffectingOwner(
|
|
151
155
|
owner: RequestDescriptor,
|
|
152
|
-
):
|
|
156
|
+
): {|
|
|
157
|
+
promise: Promise<void>,
|
|
158
|
+
pendingOperations: $ReadOnlyArray<RequestDescriptor>,
|
|
159
|
+
|} | null {
|
|
153
160
|
const ownerIdentifier = owner.identifier;
|
|
154
|
-
|
|
161
|
+
const pendingOperationsForOwner = this._ownersToPendingOperations.get(
|
|
162
|
+
ownerIdentifier,
|
|
163
|
+
);
|
|
164
|
+
if (
|
|
165
|
+
pendingOperationsForOwner == null ||
|
|
166
|
+
pendingOperationsForOwner.size === 0
|
|
167
|
+
) {
|
|
155
168
|
return null;
|
|
156
169
|
}
|
|
157
|
-
|
|
170
|
+
|
|
171
|
+
const cachedPromiseEntry = this._ownersToPendingPromise.get(
|
|
158
172
|
ownerIdentifier,
|
|
159
173
|
);
|
|
160
174
|
if (cachedPromiseEntry != null) {
|
|
161
|
-
return
|
|
175
|
+
return {
|
|
176
|
+
promise: cachedPromiseEntry.promise,
|
|
177
|
+
pendingOperations: cachedPromiseEntry.pendingOperations,
|
|
178
|
+
};
|
|
162
179
|
}
|
|
163
180
|
let resolve;
|
|
164
181
|
const promise = new Promise(r => {
|
|
@@ -169,8 +186,13 @@ class RelayOperationTracker {
|
|
|
169
186
|
'RelayOperationTracker: Expected resolver to be defined. If you' +
|
|
170
187
|
'are seeing this, it is likely a bug in Relay.',
|
|
171
188
|
);
|
|
172
|
-
|
|
173
|
-
|
|
189
|
+
const pendingOperations = Array.from(pendingOperationsForOwner.values());
|
|
190
|
+
this._ownersToPendingPromise.set(ownerIdentifier, {
|
|
191
|
+
promise,
|
|
192
|
+
resolve,
|
|
193
|
+
pendingOperations,
|
|
194
|
+
});
|
|
195
|
+
return {promise, pendingOperations};
|
|
174
196
|
}
|
|
175
197
|
}
|
|
176
198
|
|
|
@@ -55,7 +55,7 @@ type PendingUpdater = {|
|
|
|
55
55
|
|};
|
|
56
56
|
|
|
57
57
|
const applyWithGuard =
|
|
58
|
-
global
|
|
58
|
+
global?.ErrorUtils?.applyWithGuard ??
|
|
59
59
|
((callback, context, args, onError, name) => callback.apply(context, args));
|
|
60
60
|
|
|
61
61
|
/**
|
|
@@ -185,7 +185,20 @@ class RelayPublishQueue implements PublishQueue {
|
|
|
185
185
|
run(
|
|
186
186
|
sourceOperation?: OperationDescriptor,
|
|
187
187
|
): $ReadOnlyArray<RequestDescriptor> {
|
|
188
|
+
const runWillClearGcHold =
|
|
189
|
+
this._appliedOptimisticUpdates === 0 && !!this._gcHold;
|
|
190
|
+
const runIsANoop =
|
|
191
|
+
// this._pendingBackupRebase is true if an applied optimistic
|
|
192
|
+
// update has potentially been reverted or if this._pendingData is not empty.
|
|
193
|
+
!this._pendingBackupRebase &&
|
|
194
|
+
this._pendingOptimisticUpdates.size === 0 &&
|
|
195
|
+
!runWillClearGcHold;
|
|
196
|
+
|
|
188
197
|
if (__DEV__) {
|
|
198
|
+
warning(
|
|
199
|
+
!runIsANoop,
|
|
200
|
+
'RelayPublishQueue.run was called, but the call would have been a noop.',
|
|
201
|
+
);
|
|
189
202
|
warning(
|
|
190
203
|
this._isRunning !== true,
|
|
191
204
|
'A store update was detected within another store update. Please ' +
|
|
@@ -194,6 +207,14 @@ class RelayPublishQueue implements PublishQueue {
|
|
|
194
207
|
);
|
|
195
208
|
this._isRunning = true;
|
|
196
209
|
}
|
|
210
|
+
|
|
211
|
+
if (runIsANoop) {
|
|
212
|
+
if (__DEV__) {
|
|
213
|
+
this._isRunning = false;
|
|
214
|
+
}
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
|
|
197
218
|
if (this._pendingBackupRebase) {
|
|
198
219
|
if (this._hasStoreSnapshot) {
|
|
199
220
|
this._store.restore();
|
|
@@ -347,17 +368,19 @@ class RelayPublishQueue implements PublishQueue {
|
|
|
347
368
|
} else {
|
|
348
369
|
const {operation, payload, updater} = optimisticUpdate;
|
|
349
370
|
const {source, fieldPayloads} = payload;
|
|
350
|
-
const recordSourceSelectorProxy = new RelayRecordSourceSelectorProxy(
|
|
351
|
-
mutator,
|
|
352
|
-
recordSourceProxy,
|
|
353
|
-
operation.fragment,
|
|
354
|
-
);
|
|
355
|
-
let selectorData;
|
|
356
371
|
if (source) {
|
|
357
372
|
recordSourceProxy.publishSource(source, fieldPayloads);
|
|
358
|
-
selectorData = lookupSelector(source, operation.fragment);
|
|
359
373
|
}
|
|
360
374
|
if (updater) {
|
|
375
|
+
let selectorData;
|
|
376
|
+
if (source) {
|
|
377
|
+
selectorData = lookupSelector(source, operation.fragment);
|
|
378
|
+
}
|
|
379
|
+
const recordSourceSelectorProxy = new RelayRecordSourceSelectorProxy(
|
|
380
|
+
mutator,
|
|
381
|
+
recordSourceProxy,
|
|
382
|
+
operation.fragment,
|
|
383
|
+
);
|
|
361
384
|
applyWithGuard(
|
|
362
385
|
updater,
|
|
363
386
|
null,
|
|
@@ -12,12 +12,14 @@
|
|
|
12
12
|
|
|
13
13
|
'use strict';
|
|
14
14
|
|
|
15
|
+
const ClientID = require('./ClientID');
|
|
15
16
|
const RelayFeatureFlags = require('../util/RelayFeatureFlags');
|
|
16
17
|
const RelayModernRecord = require('./RelayModernRecord');
|
|
17
18
|
|
|
18
19
|
const invariant = require('invariant');
|
|
19
20
|
|
|
20
21
|
const {
|
|
22
|
+
ACTOR_CHANGE,
|
|
21
23
|
CLIENT_EXTENSION,
|
|
22
24
|
CONDITION,
|
|
23
25
|
DEFER,
|
|
@@ -45,6 +47,7 @@ const {
|
|
|
45
47
|
getStorageKey,
|
|
46
48
|
getModuleComponentKey,
|
|
47
49
|
} = require('./RelayStoreUtils');
|
|
50
|
+
const {NoopResolverCache} = require('./ResolverCache');
|
|
48
51
|
const {withResolverContext} = require('./ResolverFragments');
|
|
49
52
|
const {generateTypeID} = require('./TypeID');
|
|
50
53
|
|
|
@@ -54,6 +57,7 @@ import type {
|
|
|
54
57
|
ReaderFragmentSpread,
|
|
55
58
|
ReaderInlineDataFragmentSpread,
|
|
56
59
|
ReaderLinkedField,
|
|
60
|
+
ReaderActorChange,
|
|
57
61
|
ReaderModuleImport,
|
|
58
62
|
ReaderNode,
|
|
59
63
|
ReaderRelayResolver,
|
|
@@ -72,12 +76,18 @@ import type {
|
|
|
72
76
|
MissingRequiredFields,
|
|
73
77
|
DataIDSet,
|
|
74
78
|
} from './RelayStoreTypes';
|
|
79
|
+
import type {ResolverCache} from './ResolverCache';
|
|
75
80
|
|
|
76
81
|
function read(
|
|
77
82
|
recordSource: RecordSource,
|
|
78
83
|
selector: SingularReaderSelector,
|
|
84
|
+
resolverCache?: ResolverCache,
|
|
79
85
|
): Snapshot {
|
|
80
|
-
const reader = new RelayReader(
|
|
86
|
+
const reader = new RelayReader(
|
|
87
|
+
recordSource,
|
|
88
|
+
selector,
|
|
89
|
+
resolverCache ?? new NoopResolverCache(),
|
|
90
|
+
);
|
|
81
91
|
return reader.read();
|
|
82
92
|
}
|
|
83
93
|
|
|
@@ -93,8 +103,13 @@ class RelayReader {
|
|
|
93
103
|
_seenRecords: DataIDSet;
|
|
94
104
|
_selector: SingularReaderSelector;
|
|
95
105
|
_variables: Variables;
|
|
106
|
+
_resolverCache: ResolverCache;
|
|
96
107
|
|
|
97
|
-
constructor(
|
|
108
|
+
constructor(
|
|
109
|
+
recordSource: RecordSource,
|
|
110
|
+
selector: SingularReaderSelector,
|
|
111
|
+
resolverCache: ResolverCache,
|
|
112
|
+
) {
|
|
98
113
|
this._isMissingData = false;
|
|
99
114
|
this._isWithinUnmatchedTypeRefinement = false;
|
|
100
115
|
this._missingRequiredFields = null;
|
|
@@ -103,6 +118,7 @@ class RelayReader {
|
|
|
103
118
|
this._seenRecords = new Set();
|
|
104
119
|
this._selector = selector;
|
|
105
120
|
this._variables = selector.variables;
|
|
121
|
+
this._resolverCache = resolverCache;
|
|
106
122
|
}
|
|
107
123
|
|
|
108
124
|
read(): Snapshot {
|
|
@@ -126,7 +142,18 @@ class RelayReader {
|
|
|
126
142
|
// match, then no data is expected to be present.
|
|
127
143
|
if (isDataExpectedToBePresent && abstractKey == null && record != null) {
|
|
128
144
|
const recordType = RelayModernRecord.getType(record);
|
|
129
|
-
if (
|
|
145
|
+
if (
|
|
146
|
+
recordType !== node.type &&
|
|
147
|
+
// The root record type is a special `__Root` type and may not match the
|
|
148
|
+
// type on the ast, so ignore type mismatches at the root.
|
|
149
|
+
// We currently detect whether we're at the root by checking against ROOT_ID,
|
|
150
|
+
// but this does not work for mutations/subscriptions which generate unique
|
|
151
|
+
// root ids. This is acceptable in practice as we don't read data for mutations/
|
|
152
|
+
// subscriptions in a situation where we would use isMissingData to decide whether
|
|
153
|
+
// to suspend or not.
|
|
154
|
+
// TODO T96653810: Correctly detect reading from root of mutation/subscription
|
|
155
|
+
dataID !== ROOT_ID
|
|
156
|
+
) {
|
|
130
157
|
isDataExpectedToBePresent = false;
|
|
131
158
|
}
|
|
132
159
|
}
|
|
@@ -135,12 +162,7 @@ class RelayReader {
|
|
|
135
162
|
// then data is only expected to be present if the record type is known to
|
|
136
163
|
// implement the interface. If we aren't sure whether the record implements
|
|
137
164
|
// the interface, that itself constitutes "expected" data being missing.
|
|
138
|
-
if (
|
|
139
|
-
isDataExpectedToBePresent &&
|
|
140
|
-
abstractKey != null &&
|
|
141
|
-
record != null &&
|
|
142
|
-
RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT
|
|
143
|
-
) {
|
|
165
|
+
if (isDataExpectedToBePresent && abstractKey != null && record != null) {
|
|
144
166
|
const recordType = RelayModernRecord.getType(record);
|
|
145
167
|
const typeID = generateTypeID(recordType);
|
|
146
168
|
const typeRecord = this._recordSource.get(typeID);
|
|
@@ -267,7 +289,9 @@ class RelayReader {
|
|
|
267
289
|
}
|
|
268
290
|
break;
|
|
269
291
|
case CONDITION:
|
|
270
|
-
const conditionValue =
|
|
292
|
+
const conditionValue = Boolean(
|
|
293
|
+
this._getVariableValue(selection.condition),
|
|
294
|
+
);
|
|
271
295
|
if (conditionValue === selection.passingValue) {
|
|
272
296
|
const hasExpectedData = this._traverseSelections(
|
|
273
297
|
selection.selections,
|
|
@@ -294,7 +318,7 @@ class RelayReader {
|
|
|
294
318
|
return false;
|
|
295
319
|
}
|
|
296
320
|
}
|
|
297
|
-
} else
|
|
321
|
+
} else {
|
|
298
322
|
// Similar to the logic in read(): data is only expected to be present
|
|
299
323
|
// if the record is known to conform to the interface. If we don't know
|
|
300
324
|
// whether the type conforms or not, that constitutes missing data.
|
|
@@ -324,10 +348,6 @@ class RelayReader {
|
|
|
324
348
|
// Don't know if the type implements the interface or not
|
|
325
349
|
this._isMissingData = true;
|
|
326
350
|
}
|
|
327
|
-
} else {
|
|
328
|
-
// legacy behavior for abstract refinements: always read even
|
|
329
|
-
// if the type doesn't conform and don't reset isMissingData
|
|
330
|
-
this._traverseSelections(selection.selections, record, data);
|
|
331
351
|
}
|
|
332
352
|
break;
|
|
333
353
|
}
|
|
@@ -383,6 +403,9 @@ class RelayReader {
|
|
|
383
403
|
throw new Error('Flight fields are not yet supported.');
|
|
384
404
|
}
|
|
385
405
|
break;
|
|
406
|
+
case ACTOR_CHANGE:
|
|
407
|
+
this._readActorChange(selection, record, data);
|
|
408
|
+
break;
|
|
386
409
|
default:
|
|
387
410
|
(selection: empty);
|
|
388
411
|
invariant(
|
|
@@ -409,6 +432,12 @@ class RelayReader {
|
|
|
409
432
|
} else {
|
|
410
433
|
return this._readLink(selection.field, record, data);
|
|
411
434
|
}
|
|
435
|
+
case RELAY_RESOLVER:
|
|
436
|
+
if (!RelayFeatureFlags.ENABLE_RELAY_RESOLVERS) {
|
|
437
|
+
throw new Error('Relay Resolver fields are not yet supported.');
|
|
438
|
+
}
|
|
439
|
+
this._readResolverField(selection.field, record, data);
|
|
440
|
+
break;
|
|
412
441
|
default:
|
|
413
442
|
(selection.field.kind: empty);
|
|
414
443
|
invariant(
|
|
@@ -420,40 +449,84 @@ class RelayReader {
|
|
|
420
449
|
}
|
|
421
450
|
|
|
422
451
|
_readResolverField(
|
|
423
|
-
|
|
452
|
+
field: ReaderRelayResolver,
|
|
424
453
|
record: Record,
|
|
425
454
|
data: SelectorData,
|
|
426
455
|
): ?mixed {
|
|
427
|
-
const {
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
456
|
+
const {resolverModule, fragment} = field;
|
|
457
|
+
const storageKey = getStorageKey(field, this._variables);
|
|
458
|
+
const resolverID = ClientID.generateClientID(
|
|
459
|
+
RelayModernRecord.getDataID(record),
|
|
460
|
+
storageKey,
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
// Found when reading the resolver fragment, which can happen either when
|
|
464
|
+
// evaluating the resolver and it calls readFragment, or when checking if the
|
|
465
|
+
// inputs have changed since a previous evaluation:
|
|
466
|
+
let fragmentValue;
|
|
467
|
+
let fragmentReaderSelector;
|
|
468
|
+
const fragmentSeenRecordIDs = new Set();
|
|
469
|
+
|
|
470
|
+
const getDataForResolverFragment = singularReaderSelector => {
|
|
471
|
+
if (fragmentValue != null) {
|
|
472
|
+
// It was already read when checking for input staleness; no need to read it again.
|
|
473
|
+
// Note that the variables like fragmentSeenRecordIDs in the outer closure will have
|
|
474
|
+
// already been set and will still be used in this case.
|
|
475
|
+
return fragmentValue;
|
|
476
|
+
}
|
|
477
|
+
fragmentReaderSelector = singularReaderSelector;
|
|
478
|
+
const existingSeenRecords = this._seenRecords;
|
|
479
|
+
try {
|
|
480
|
+
this._seenRecords = fragmentSeenRecordIDs;
|
|
437
481
|
const resolverFragmentData = {};
|
|
438
482
|
this._createInlineDataOrResolverFragmentPointer(
|
|
439
483
|
singularReaderSelector.node,
|
|
440
484
|
record,
|
|
441
485
|
resolverFragmentData,
|
|
442
486
|
);
|
|
443
|
-
|
|
487
|
+
fragmentValue = resolverFragmentData[FRAGMENTS_KEY]?.[fragment.name];
|
|
444
488
|
invariant(
|
|
445
|
-
typeof
|
|
489
|
+
typeof fragmentValue === 'object' && fragmentValue !== null,
|
|
446
490
|
`Expected reader data to contain a __fragments property with a property for the fragment named ${fragment.name}, but it is missing.`,
|
|
447
491
|
);
|
|
448
|
-
return
|
|
449
|
-
}
|
|
492
|
+
return fragmentValue;
|
|
493
|
+
} finally {
|
|
494
|
+
this._seenRecords = existingSeenRecords;
|
|
495
|
+
}
|
|
450
496
|
};
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
497
|
+
const resolverContext = {getDataForResolverFragment};
|
|
498
|
+
|
|
499
|
+
const [result, seenRecord] = this._resolverCache.readFromCacheOrEvaluate(
|
|
500
|
+
record,
|
|
501
|
+
field,
|
|
502
|
+
this._variables,
|
|
503
|
+
() => {
|
|
504
|
+
const key = {
|
|
505
|
+
__id: RelayModernRecord.getDataID(record),
|
|
506
|
+
__fragmentOwner: this._owner,
|
|
507
|
+
__fragments: {
|
|
508
|
+
[fragment.name]: {}, // Arguments to this fragment; not yet supported.
|
|
509
|
+
},
|
|
510
|
+
};
|
|
511
|
+
return withResolverContext(resolverContext, () => {
|
|
512
|
+
// $FlowFixMe[prop-missing] - resolver module's type signature is a lie
|
|
513
|
+
const resolverResult = resolverModule(key);
|
|
514
|
+
return {
|
|
515
|
+
resolverResult,
|
|
516
|
+
fragmentValue,
|
|
517
|
+
resolverID,
|
|
518
|
+
seenRecordIDs: fragmentSeenRecordIDs,
|
|
519
|
+
readerSelector: fragmentReaderSelector,
|
|
520
|
+
};
|
|
521
|
+
});
|
|
522
|
+
},
|
|
523
|
+
getDataForResolverFragment,
|
|
454
524
|
);
|
|
455
|
-
|
|
456
|
-
|
|
525
|
+
if (seenRecord != null) {
|
|
526
|
+
this._seenRecords.add(seenRecord);
|
|
527
|
+
}
|
|
528
|
+
data[storageKey] = result;
|
|
529
|
+
return result;
|
|
457
530
|
}
|
|
458
531
|
|
|
459
532
|
_readFlightField(
|
|
@@ -538,6 +611,42 @@ class RelayReader {
|
|
|
538
611
|
return value;
|
|
539
612
|
}
|
|
540
613
|
|
|
614
|
+
_readActorChange(
|
|
615
|
+
field: ReaderActorChange,
|
|
616
|
+
record: Record,
|
|
617
|
+
data: SelectorData,
|
|
618
|
+
): ?mixed {
|
|
619
|
+
const applicationName = field.alias ?? field.name;
|
|
620
|
+
const storageKey = getStorageKey(field, this._variables);
|
|
621
|
+
const externalRef = RelayModernRecord.getActorLinkedRecordID(
|
|
622
|
+
record,
|
|
623
|
+
storageKey,
|
|
624
|
+
);
|
|
625
|
+
|
|
626
|
+
if (externalRef == null) {
|
|
627
|
+
data[applicationName] = externalRef;
|
|
628
|
+
if (externalRef === undefined) {
|
|
629
|
+
this._isMissingData = true;
|
|
630
|
+
}
|
|
631
|
+
return data[applicationName];
|
|
632
|
+
}
|
|
633
|
+
const [actorIdentifier, dataID] = externalRef;
|
|
634
|
+
|
|
635
|
+
const fragmentRef = {};
|
|
636
|
+
this._createFragmentPointer(
|
|
637
|
+
field.fragmentSpread,
|
|
638
|
+
{
|
|
639
|
+
__id: dataID,
|
|
640
|
+
},
|
|
641
|
+
fragmentRef,
|
|
642
|
+
);
|
|
643
|
+
data[applicationName] = {
|
|
644
|
+
__fragmentRef: fragmentRef,
|
|
645
|
+
__viewer: actorIdentifier,
|
|
646
|
+
};
|
|
647
|
+
return data[applicationName];
|
|
648
|
+
}
|
|
649
|
+
|
|
541
650
|
_readPluralLink(
|
|
542
651
|
field: ReaderLinkedField,
|
|
543
652
|
record: Record,
|
|
@@ -620,7 +729,7 @@ class RelayReader {
|
|
|
620
729
|
{
|
|
621
730
|
kind: 'FragmentSpread',
|
|
622
731
|
name: moduleImport.fragmentName,
|
|
623
|
-
args:
|
|
732
|
+
args: moduleImport.args,
|
|
624
733
|
},
|
|
625
734
|
record,
|
|
626
735
|
data,
|
|
@@ -651,12 +760,9 @@ class RelayReader {
|
|
|
651
760
|
? getArgumentValues(fragmentSpread.args, this._variables)
|
|
652
761
|
: {};
|
|
653
762
|
data[FRAGMENT_OWNER_KEY] = this._owner;
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
IS_WITHIN_UNMATCHED_TYPE_REFINEMENT
|
|
658
|
-
] = this._isWithinUnmatchedTypeRefinement;
|
|
659
|
-
}
|
|
763
|
+
data[
|
|
764
|
+
IS_WITHIN_UNMATCHED_TYPE_REFINEMENT
|
|
765
|
+
] = this._isWithinUnmatchedTypeRefinement;
|
|
660
766
|
}
|
|
661
767
|
|
|
662
768
|
_createInlineDataOrResolverFragmentPointer(
|
|
@@ -12,17 +12,83 @@
|
|
|
12
12
|
|
|
13
13
|
'use strict';
|
|
14
14
|
|
|
15
|
-
const
|
|
15
|
+
const RelayRecordState = require('./RelayRecordState');
|
|
16
16
|
|
|
17
|
-
import type {
|
|
17
|
+
import type {DataID} from '../util/RelayRuntimeTypes';
|
|
18
|
+
import type {RecordState} from './RelayRecordState';
|
|
19
|
+
import type {
|
|
20
|
+
MutableRecordSource,
|
|
21
|
+
Record,
|
|
22
|
+
RecordObjectMap,
|
|
23
|
+
} from './RelayStoreTypes';
|
|
18
24
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
const {EXISTENT, NONEXISTENT, UNKNOWN} = RelayRecordState;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* An implementation of the `MutableRecordSource` interface (defined in
|
|
29
|
+
* `RelayStoreTypes`) that holds all records in memory (JS Map).
|
|
30
|
+
*/
|
|
31
|
+
class RelayRecordSource implements MutableRecordSource {
|
|
32
|
+
_records: Map<DataID, ?Record>;
|
|
33
|
+
|
|
34
|
+
constructor(records?: RecordObjectMap) {
|
|
35
|
+
this._records = new Map();
|
|
36
|
+
if (records != null) {
|
|
37
|
+
Object.keys(records).forEach(key => {
|
|
38
|
+
this._records.set(key, records[key]);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
22
41
|
}
|
|
23
42
|
|
|
24
43
|
static create(records?: RecordObjectMap): MutableRecordSource {
|
|
25
|
-
return new
|
|
44
|
+
return new RelayRecordSource(records);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
clear(): void {
|
|
48
|
+
this._records = new Map();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
delete(dataID: DataID): void {
|
|
52
|
+
this._records.set(dataID, null);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get(dataID: DataID): ?Record {
|
|
56
|
+
return this._records.get(dataID);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
getRecordIDs(): Array<DataID> {
|
|
60
|
+
return Array.from(this._records.keys());
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
getStatus(dataID: DataID): RecordState {
|
|
64
|
+
if (!this._records.has(dataID)) {
|
|
65
|
+
return UNKNOWN;
|
|
66
|
+
}
|
|
67
|
+
return this._records.get(dataID) == null ? NONEXISTENT : EXISTENT;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
has(dataID: DataID): boolean {
|
|
71
|
+
return this._records.has(dataID);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
remove(dataID: DataID): void {
|
|
75
|
+
this._records.delete(dataID);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
set(dataID: DataID, record: Record): void {
|
|
79
|
+
this._records.set(dataID, record);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
size(): number {
|
|
83
|
+
return this._records.size;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
toJSON(): {[DataID]: ?Record, ...} {
|
|
87
|
+
const obj = {};
|
|
88
|
+
for (const [key, value] of this._records) {
|
|
89
|
+
obj[key] = value;
|
|
90
|
+
}
|
|
91
|
+
return obj;
|
|
26
92
|
}
|
|
27
93
|
}
|
|
28
94
|
|