relay-runtime 9.0.0 → 10.1.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/handlers/RelayDefaultHandlerProvider.js.flow +47 -0
- package/handlers/connection/ConnectionHandler.js.flow +549 -0
- package/handlers/connection/ConnectionInterface.js.flow +92 -0
- package/handlers/connection/MutationHandlers.js.flow +199 -0
- package/index.js +1 -1
- package/index.js.flow +335 -0
- package/lib/handlers/RelayDefaultHandlerProvider.js +20 -0
- package/lib/handlers/connection/ConnectionHandler.js +1 -3
- package/lib/handlers/connection/MutationHandlers.js +212 -0
- package/lib/index.js +14 -2
- package/lib/mutations/RelayDeclarativeMutationConfig.js +22 -45
- package/lib/mutations/RelayRecordProxy.js +1 -3
- package/lib/mutations/RelayRecordSourceMutator.js +1 -3
- package/lib/mutations/RelayRecordSourceProxy.js +1 -3
- package/lib/mutations/RelayRecordSourceSelectorProxy.js +1 -3
- package/lib/mutations/commitMutation.js +2 -3
- package/lib/mutations/validateMutation.js +40 -9
- package/lib/network/RelayObservable.js +9 -9
- package/lib/network/RelayQueryResponseCache.js +8 -6
- package/lib/query/GraphQLTag.js +2 -1
- package/lib/query/PreloadableQueryRegistry.js +70 -0
- package/lib/query/fetchQuery.js +2 -3
- package/lib/query/fetchQueryInternal.js +5 -14
- package/lib/store/DataChecker.js +200 -71
- package/lib/store/RelayConcreteVariables.js +6 -2
- package/lib/store/RelayModernEnvironment.js +124 -65
- package/lib/store/RelayModernFragmentSpecResolver.js +19 -14
- package/lib/store/RelayModernOperationDescriptor.js +6 -5
- package/lib/store/RelayModernQueryExecutor.js +122 -73
- package/lib/store/RelayModernRecord.js +14 -9
- package/lib/store/RelayModernSelector.js +6 -2
- package/lib/store/RelayModernStore.js +281 -131
- package/lib/store/RelayOperationTracker.js +35 -78
- package/lib/store/RelayOptimisticRecordSource.js +7 -5
- package/lib/store/RelayPublishQueue.js +2 -4
- package/lib/store/RelayReader.js +304 -52
- package/lib/store/RelayRecordSource.js +1 -3
- package/lib/store/RelayRecordSourceMapImpl.js +13 -18
- package/lib/store/RelayReferenceMarker.js +125 -14
- package/lib/store/RelayResponseNormalizer.js +261 -66
- package/lib/store/RelayStoreReactFlightUtils.js +47 -0
- package/lib/store/RelayStoreUtils.js +1 -0
- package/lib/store/StoreInspector.js +8 -8
- package/lib/store/TypeID.js +28 -0
- package/lib/store/cloneRelayScalarHandleSourceField.js +44 -0
- package/lib/store/defaultRequiredFieldLogger.js +18 -0
- package/lib/store/normalizeRelayPayload.js +6 -2
- package/lib/store/readInlineData.js +1 -1
- package/lib/subscription/requestSubscription.js +4 -3
- package/lib/util/NormalizationNode.js +1 -5
- package/lib/util/RelayConcreteNode.js +11 -6
- package/lib/util/RelayError.js +39 -9
- package/lib/util/RelayFeatureFlags.js +6 -3
- package/lib/util/RelayReplaySubject.js +3 -3
- package/lib/util/createPayloadFor3DField.js +7 -2
- package/lib/util/getFragmentIdentifier.js +12 -3
- package/lib/util/getOperation.js +33 -0
- package/lib/util/getRequestIdentifier.js +2 -2
- package/lib/util/isEmptyObject.js +25 -0
- package/lib/util/recycleNodesInto.js +6 -7
- package/lib/util/reportMissingRequiredFields.js +48 -0
- package/mutations/RelayDeclarativeMutationConfig.js.flow +380 -0
- package/mutations/RelayRecordProxy.js.flow +165 -0
- package/mutations/RelayRecordSourceMutator.js.flow +238 -0
- package/mutations/RelayRecordSourceProxy.js.flow +164 -0
- package/mutations/RelayRecordSourceSelectorProxy.js.flow +119 -0
- package/mutations/applyOptimisticMutation.js.flow +76 -0
- package/mutations/commitLocalUpdate.js.flow +24 -0
- package/mutations/commitMutation.js.flow +181 -0
- package/mutations/validateMutation.js.flow +242 -0
- package/network/ConvertToExecuteFunction.js.flow +49 -0
- package/network/RelayNetwork.js.flow +84 -0
- package/network/RelayNetworkTypes.js.flow +145 -0
- package/network/RelayObservable.js.flow +634 -0
- package/network/RelayQueryResponseCache.js.flow +111 -0
- package/package.json +2 -2
- package/query/GraphQLTag.js.flow +168 -0
- package/query/PreloadableQueryRegistry.js.flow +65 -0
- package/query/fetchQuery.js.flow +47 -0
- package/query/fetchQueryInternal.js.flow +343 -0
- package/relay-runtime.js +2 -2
- package/relay-runtime.min.js +2 -2
- package/store/ClientID.js.flow +43 -0
- package/store/DataChecker.js.flow +568 -0
- package/store/RelayConcreteVariables.js.flow +96 -0
- package/store/RelayModernEnvironment.js.flow +571 -0
- package/store/RelayModernFragmentSpecResolver.js.flow +438 -0
- package/store/RelayModernOperationDescriptor.js.flow +92 -0
- package/store/RelayModernQueryExecutor.js.flow +1345 -0
- package/store/RelayModernRecord.js.flow +403 -0
- package/store/RelayModernSelector.js.flow +455 -0
- package/store/RelayModernStore.js.flow +858 -0
- package/store/RelayOperationTracker.js.flow +164 -0
- package/store/RelayOptimisticRecordSource.js.flow +119 -0
- package/store/RelayPublishQueue.js.flow +401 -0
- package/store/RelayReader.js.flow +638 -0
- package/store/RelayRecordSource.js.flow +29 -0
- package/store/RelayRecordSourceMapImpl.js.flow +87 -0
- package/store/RelayRecordState.js.flow +37 -0
- package/store/RelayReferenceMarker.js.flow +324 -0
- package/store/RelayResponseNormalizer.js.flow +791 -0
- package/store/RelayStoreReactFlightUtils.js.flow +64 -0
- package/store/RelayStoreTypes.js.flow +958 -0
- package/store/RelayStoreUtils.js.flow +219 -0
- package/store/StoreInspector.js.flow +171 -0
- package/store/TypeID.js.flow +28 -0
- package/store/ViewerPattern.js.flow +26 -0
- package/store/cloneRelayHandleSourceField.js.flow +66 -0
- package/store/cloneRelayScalarHandleSourceField.js.flow +62 -0
- package/store/createFragmentSpecResolver.js.flow +55 -0
- package/store/createRelayContext.js.flow +44 -0
- package/store/defaultGetDataID.js.flow +27 -0
- package/store/defaultRequiredFieldLogger.js.flow +23 -0
- package/store/hasOverlappingIDs.js.flow +34 -0
- package/store/isRelayModernEnvironment.js.flow +27 -0
- package/store/normalizeRelayPayload.js.flow +51 -0
- package/store/readInlineData.js.flow +75 -0
- package/subscription/requestSubscription.js.flow +103 -0
- package/util/JSResourceTypes.flow.js.flow +20 -0
- package/util/NormalizationNode.js.flow +213 -0
- package/util/ReaderNode.js.flow +227 -0
- package/util/RelayConcreteNode.js.flow +99 -0
- package/util/RelayDefaultHandleKey.js.flow +17 -0
- package/util/RelayError.js.flow +62 -0
- package/util/RelayFeatureFlags.js.flow +37 -0
- package/util/RelayProfiler.js.flow +284 -0
- package/util/RelayReplaySubject.js.flow +135 -0
- package/util/RelayRuntimeTypes.js.flow +72 -0
- package/util/createPayloadFor3DField.js.flow +43 -0
- package/util/deepFreeze.js.flow +36 -0
- package/util/generateID.js.flow +21 -0
- package/util/getFragmentIdentifier.js.flow +76 -0
- package/util/getOperation.js.flow +40 -0
- package/util/getRelayHandleKey.js.flow +41 -0
- package/util/getRequestIdentifier.js.flow +42 -0
- package/util/isEmptyObject.js.flow +25 -0
- package/util/isPromise.js.flow +21 -0
- package/util/isScalarAndEqual.js.flow +26 -0
- package/util/recycleNodesInto.js.flow +87 -0
- package/util/reportMissingRequiredFields.js.flow +51 -0
- package/util/resolveImmediate.js.flow +30 -0
- package/util/stableCopy.js.flow +35 -0
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its 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
|
+
*/
|
|
10
|
+
|
|
11
|
+
// flowlint ambiguous-object-type:error
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const RelayFeatureFlags = require('../util/RelayFeatureFlags');
|
|
16
|
+
const RelayModernRecord = require('./RelayModernRecord');
|
|
17
|
+
|
|
18
|
+
const invariant = require('invariant');
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
CLIENT_EXTENSION,
|
|
22
|
+
CONDITION,
|
|
23
|
+
DEFER,
|
|
24
|
+
FLIGHT_FIELD,
|
|
25
|
+
FRAGMENT_SPREAD,
|
|
26
|
+
INLINE_DATA_FRAGMENT_SPREAD,
|
|
27
|
+
INLINE_FRAGMENT,
|
|
28
|
+
LINKED_FIELD,
|
|
29
|
+
MODULE_IMPORT,
|
|
30
|
+
REQUIRED_FIELD,
|
|
31
|
+
SCALAR_FIELD,
|
|
32
|
+
STREAM,
|
|
33
|
+
} = require('../util/RelayConcreteNode');
|
|
34
|
+
const {getReactFlightClientResponse} = require('./RelayStoreReactFlightUtils');
|
|
35
|
+
const {
|
|
36
|
+
FRAGMENTS_KEY,
|
|
37
|
+
FRAGMENT_OWNER_KEY,
|
|
38
|
+
FRAGMENT_PROP_NAME_KEY,
|
|
39
|
+
ID_KEY,
|
|
40
|
+
IS_WITHIN_UNMATCHED_TYPE_REFINEMENT,
|
|
41
|
+
MODULE_COMPONENT_KEY,
|
|
42
|
+
ROOT_ID,
|
|
43
|
+
getArgumentValues,
|
|
44
|
+
getStorageKey,
|
|
45
|
+
getModuleComponentKey,
|
|
46
|
+
} = require('./RelayStoreUtils');
|
|
47
|
+
const {generateTypeID} = require('./TypeID');
|
|
48
|
+
|
|
49
|
+
import type {
|
|
50
|
+
ReaderFlightField,
|
|
51
|
+
ReaderFragmentSpread,
|
|
52
|
+
ReaderInlineDataFragmentSpread,
|
|
53
|
+
ReaderLinkedField,
|
|
54
|
+
ReaderModuleImport,
|
|
55
|
+
ReaderNode,
|
|
56
|
+
ReaderRequiredField,
|
|
57
|
+
ReaderScalarField,
|
|
58
|
+
ReaderSelection,
|
|
59
|
+
} from '../util/ReaderNode';
|
|
60
|
+
import type {DataID, Variables} from '../util/RelayRuntimeTypes';
|
|
61
|
+
import type {
|
|
62
|
+
Record,
|
|
63
|
+
RecordSource,
|
|
64
|
+
RequestDescriptor,
|
|
65
|
+
SelectorData,
|
|
66
|
+
SingularReaderSelector,
|
|
67
|
+
Snapshot,
|
|
68
|
+
MissingRequiredFields,
|
|
69
|
+
} from './RelayStoreTypes';
|
|
70
|
+
|
|
71
|
+
function read(
|
|
72
|
+
recordSource: RecordSource,
|
|
73
|
+
selector: SingularReaderSelector,
|
|
74
|
+
): Snapshot {
|
|
75
|
+
const reader = new RelayReader(recordSource, selector);
|
|
76
|
+
return reader.read();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @private
|
|
81
|
+
*/
|
|
82
|
+
class RelayReader {
|
|
83
|
+
_isMissingData: boolean;
|
|
84
|
+
_isWithinUnmatchedTypeRefinement: boolean;
|
|
85
|
+
_missingRequiredFields: ?MissingRequiredFields;
|
|
86
|
+
_owner: RequestDescriptor;
|
|
87
|
+
_recordSource: RecordSource;
|
|
88
|
+
_seenRecords: {[dataID: DataID]: ?Record, ...};
|
|
89
|
+
_selector: SingularReaderSelector;
|
|
90
|
+
_variables: Variables;
|
|
91
|
+
|
|
92
|
+
constructor(recordSource: RecordSource, selector: SingularReaderSelector) {
|
|
93
|
+
this._isMissingData = false;
|
|
94
|
+
this._isWithinUnmatchedTypeRefinement = false;
|
|
95
|
+
this._missingRequiredFields = null;
|
|
96
|
+
this._owner = selector.owner;
|
|
97
|
+
this._recordSource = recordSource;
|
|
98
|
+
this._seenRecords = {};
|
|
99
|
+
this._selector = selector;
|
|
100
|
+
this._variables = selector.variables;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
read(): Snapshot {
|
|
104
|
+
const {node, dataID, isWithinUnmatchedTypeRefinement} = this._selector;
|
|
105
|
+
const {abstractKey} = node;
|
|
106
|
+
const record = this._recordSource.get(dataID);
|
|
107
|
+
|
|
108
|
+
// Relay historically allowed child fragments to be read even if the root object
|
|
109
|
+
// did not match the type of the fragment: either the root object has a different
|
|
110
|
+
// concrete type than the fragment (for concrete fragments) or the root object does
|
|
111
|
+
// not conform to the interface/union for abstract fragments.
|
|
112
|
+
// For suspense purposes, however, we want to accurately compute whether any data
|
|
113
|
+
// is missing: but if the fragment type doesn't match (or a parent type didn't
|
|
114
|
+
// match), then no data is expected to be present.
|
|
115
|
+
|
|
116
|
+
// By default data is expected to be present unless this selector was read out
|
|
117
|
+
// from within a non-matching type refinement in a parent fragment:
|
|
118
|
+
let isDataExpectedToBePresent = !isWithinUnmatchedTypeRefinement;
|
|
119
|
+
|
|
120
|
+
// If this is a concrete fragment and the concrete type of the record does not
|
|
121
|
+
// match, then no data is expected to be present.
|
|
122
|
+
if (isDataExpectedToBePresent && abstractKey == null && record != null) {
|
|
123
|
+
const recordType = RelayModernRecord.getType(record);
|
|
124
|
+
if (recordType !== node.type && dataID !== ROOT_ID) {
|
|
125
|
+
isDataExpectedToBePresent = false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// If this is an abstract fragment (and the precise refinement GK is enabled)
|
|
130
|
+
// then data is only expected to be present if the record type is known to
|
|
131
|
+
// implement the interface. If we aren't sure whether the record implements
|
|
132
|
+
// the interface, that itself constitutes "expected" data being missing.
|
|
133
|
+
if (
|
|
134
|
+
isDataExpectedToBePresent &&
|
|
135
|
+
abstractKey != null &&
|
|
136
|
+
record != null &&
|
|
137
|
+
RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT
|
|
138
|
+
) {
|
|
139
|
+
const recordType = RelayModernRecord.getType(record);
|
|
140
|
+
const typeID = generateTypeID(recordType);
|
|
141
|
+
const typeRecord = this._recordSource.get(typeID);
|
|
142
|
+
const implementsInterface =
|
|
143
|
+
typeRecord != null
|
|
144
|
+
? RelayModernRecord.getValue(typeRecord, abstractKey)
|
|
145
|
+
: null;
|
|
146
|
+
if (implementsInterface === false) {
|
|
147
|
+
// Type known to not implement the interface
|
|
148
|
+
isDataExpectedToBePresent = false;
|
|
149
|
+
} else if (implementsInterface == null) {
|
|
150
|
+
// Don't know if the type implements the interface or not
|
|
151
|
+
this._isMissingData = true;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
this._isWithinUnmatchedTypeRefinement = !isDataExpectedToBePresent;
|
|
156
|
+
const data = this._traverse(node, dataID, null);
|
|
157
|
+
return {
|
|
158
|
+
data,
|
|
159
|
+
isMissingData: this._isMissingData && isDataExpectedToBePresent,
|
|
160
|
+
seenRecords: this._seenRecords,
|
|
161
|
+
selector: this._selector,
|
|
162
|
+
missingRequiredFields: this._missingRequiredFields,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
_traverse(
|
|
167
|
+
node: ReaderNode,
|
|
168
|
+
dataID: DataID,
|
|
169
|
+
prevData: ?SelectorData,
|
|
170
|
+
): ?SelectorData {
|
|
171
|
+
const record = this._recordSource.get(dataID);
|
|
172
|
+
this._seenRecords[dataID] = record;
|
|
173
|
+
if (record == null) {
|
|
174
|
+
if (record === undefined) {
|
|
175
|
+
this._isMissingData = true;
|
|
176
|
+
}
|
|
177
|
+
return record;
|
|
178
|
+
}
|
|
179
|
+
const data = prevData || {};
|
|
180
|
+
const hadRequiredData = this._traverseSelections(
|
|
181
|
+
node.selections,
|
|
182
|
+
record,
|
|
183
|
+
data,
|
|
184
|
+
);
|
|
185
|
+
return hadRequiredData ? data : null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
_getVariableValue(name: string): mixed {
|
|
189
|
+
invariant(
|
|
190
|
+
this._variables.hasOwnProperty(name),
|
|
191
|
+
'RelayReader(): Undefined variable `%s`.',
|
|
192
|
+
name,
|
|
193
|
+
);
|
|
194
|
+
return this._variables[name];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
_maybeReportUnexpectedNull(
|
|
198
|
+
fieldPath: string,
|
|
199
|
+
action: 'LOG' | 'THROW',
|
|
200
|
+
record: Record,
|
|
201
|
+
) {
|
|
202
|
+
if (this._missingRequiredFields?.action === 'THROW') {
|
|
203
|
+
// Chained @required directives may cause a parent `@required(action:
|
|
204
|
+
// THROW)` field to become null, so the first missing field we
|
|
205
|
+
// encounter is likely to be the root cause of the error.
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const owner = this._selector.node.name;
|
|
209
|
+
|
|
210
|
+
switch (action) {
|
|
211
|
+
case 'THROW':
|
|
212
|
+
this._missingRequiredFields = {action, field: {path: fieldPath, owner}};
|
|
213
|
+
return;
|
|
214
|
+
case 'LOG':
|
|
215
|
+
if (this._missingRequiredFields == null) {
|
|
216
|
+
this._missingRequiredFields = {action, fields: []};
|
|
217
|
+
}
|
|
218
|
+
this._missingRequiredFields.fields.push({path: fieldPath, owner});
|
|
219
|
+
return;
|
|
220
|
+
default:
|
|
221
|
+
(action: empty);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
_traverseSelections(
|
|
226
|
+
selections: $ReadOnlyArray<ReaderSelection>,
|
|
227
|
+
record: Record,
|
|
228
|
+
data: SelectorData,
|
|
229
|
+
): boolean /* had all expected data */ {
|
|
230
|
+
for (let i = 0; i < selections.length; i++) {
|
|
231
|
+
const selection = selections[i];
|
|
232
|
+
switch (selection.kind) {
|
|
233
|
+
case REQUIRED_FIELD:
|
|
234
|
+
invariant(
|
|
235
|
+
RelayFeatureFlags.ENABLE_REQUIRED_DIRECTIVES,
|
|
236
|
+
'RelayReader(): Encountered a `@required` directive at path "%s" in `%s` without the `ENABLE_REQUIRED_DIRECTIVES` feature flag enabled.',
|
|
237
|
+
selection.path,
|
|
238
|
+
this._selector.node.name,
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const fieldValue = this._readRequiredField(selection, record, data);
|
|
242
|
+
if (fieldValue == null) {
|
|
243
|
+
const {action} = selection;
|
|
244
|
+
if (action !== 'NONE') {
|
|
245
|
+
this._maybeReportUnexpectedNull(selection.path, action, record);
|
|
246
|
+
}
|
|
247
|
+
// We are going to throw, or our parent is going to get nulled out.
|
|
248
|
+
// Either way, sibling values are going to be ignored, so we can
|
|
249
|
+
// bail early here as an optimization.
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
break;
|
|
253
|
+
case SCALAR_FIELD:
|
|
254
|
+
this._readScalar(selection, record, data);
|
|
255
|
+
break;
|
|
256
|
+
case LINKED_FIELD:
|
|
257
|
+
if (selection.plural) {
|
|
258
|
+
this._readPluralLink(selection, record, data);
|
|
259
|
+
} else {
|
|
260
|
+
this._readLink(selection, record, data);
|
|
261
|
+
}
|
|
262
|
+
break;
|
|
263
|
+
case CONDITION:
|
|
264
|
+
const conditionValue = this._getVariableValue(selection.condition);
|
|
265
|
+
if (conditionValue === selection.passingValue) {
|
|
266
|
+
const hasExpectedData = this._traverseSelections(
|
|
267
|
+
selection.selections,
|
|
268
|
+
record,
|
|
269
|
+
data,
|
|
270
|
+
);
|
|
271
|
+
if (!hasExpectedData) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
break;
|
|
276
|
+
case INLINE_FRAGMENT: {
|
|
277
|
+
const {abstractKey} = selection;
|
|
278
|
+
if (abstractKey == null) {
|
|
279
|
+
// concrete type refinement: only read data if the type exactly matches
|
|
280
|
+
const typeName = RelayModernRecord.getType(record);
|
|
281
|
+
if (typeName != null && typeName === selection.type) {
|
|
282
|
+
const hasExpectedData = this._traverseSelections(
|
|
283
|
+
selection.selections,
|
|
284
|
+
record,
|
|
285
|
+
data,
|
|
286
|
+
);
|
|
287
|
+
if (!hasExpectedData) {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
} else if (RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT) {
|
|
292
|
+
// Similar to the logic in read(): data is only expected to be present
|
|
293
|
+
// if the record is known to conform to the interface. If we don't know
|
|
294
|
+
// whether the type conforms or not, that constitutes missing data.
|
|
295
|
+
|
|
296
|
+
// store flags to reset after reading
|
|
297
|
+
const parentIsMissingData = this._isMissingData;
|
|
298
|
+
const parentIsWithinUnmatchedTypeRefinement = this
|
|
299
|
+
._isWithinUnmatchedTypeRefinement;
|
|
300
|
+
|
|
301
|
+
const typeName = RelayModernRecord.getType(record);
|
|
302
|
+
const typeID = generateTypeID(typeName);
|
|
303
|
+
const typeRecord = this._recordSource.get(typeID);
|
|
304
|
+
const implementsInterface =
|
|
305
|
+
typeRecord != null
|
|
306
|
+
? RelayModernRecord.getValue(typeRecord, abstractKey)
|
|
307
|
+
: null;
|
|
308
|
+
this._isWithinUnmatchedTypeRefinement =
|
|
309
|
+
parentIsWithinUnmatchedTypeRefinement ||
|
|
310
|
+
implementsInterface === false;
|
|
311
|
+
this._traverseSelections(selection.selections, record, data);
|
|
312
|
+
this._isWithinUnmatchedTypeRefinement = parentIsWithinUnmatchedTypeRefinement;
|
|
313
|
+
|
|
314
|
+
if (implementsInterface === false) {
|
|
315
|
+
// Type known to not implement the interface, no data expected
|
|
316
|
+
this._isMissingData = parentIsMissingData;
|
|
317
|
+
} else if (implementsInterface == null) {
|
|
318
|
+
// Don't know if the type implements the interface or not
|
|
319
|
+
this._isMissingData = true;
|
|
320
|
+
}
|
|
321
|
+
} else {
|
|
322
|
+
// legacy behavior for abstract refinements: always read even
|
|
323
|
+
// if the type doesn't conform and don't reset isMissingData
|
|
324
|
+
this._traverseSelections(selection.selections, record, data);
|
|
325
|
+
}
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
case FRAGMENT_SPREAD:
|
|
329
|
+
this._createFragmentPointer(selection, record, data);
|
|
330
|
+
break;
|
|
331
|
+
case MODULE_IMPORT:
|
|
332
|
+
this._readModuleImport(selection, record, data);
|
|
333
|
+
break;
|
|
334
|
+
case INLINE_DATA_FRAGMENT_SPREAD:
|
|
335
|
+
this._createInlineDataFragmentPointer(selection, record, data);
|
|
336
|
+
break;
|
|
337
|
+
case DEFER:
|
|
338
|
+
case CLIENT_EXTENSION: {
|
|
339
|
+
const isMissingData = this._isMissingData;
|
|
340
|
+
const hasExpectedData = this._traverseSelections(
|
|
341
|
+
selection.selections,
|
|
342
|
+
record,
|
|
343
|
+
data,
|
|
344
|
+
);
|
|
345
|
+
this._isMissingData = isMissingData;
|
|
346
|
+
if (!hasExpectedData) {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
case STREAM: {
|
|
352
|
+
const hasExpectedData = this._traverseSelections(
|
|
353
|
+
selection.selections,
|
|
354
|
+
record,
|
|
355
|
+
data,
|
|
356
|
+
);
|
|
357
|
+
if (!hasExpectedData) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
case FLIGHT_FIELD:
|
|
363
|
+
if (RelayFeatureFlags.ENABLE_REACT_FLIGHT_COMPONENT_FIELD) {
|
|
364
|
+
this._readFlightField(selection, record, data);
|
|
365
|
+
} else {
|
|
366
|
+
throw new Error('Flight fields are not yet supported.');
|
|
367
|
+
}
|
|
368
|
+
break;
|
|
369
|
+
default:
|
|
370
|
+
(selection: empty);
|
|
371
|
+
invariant(
|
|
372
|
+
false,
|
|
373
|
+
'RelayReader(): Unexpected ast kind `%s`.',
|
|
374
|
+
selection.kind,
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
_readRequiredField(
|
|
382
|
+
selection: ReaderRequiredField,
|
|
383
|
+
record: Record,
|
|
384
|
+
data: SelectorData,
|
|
385
|
+
): ?mixed {
|
|
386
|
+
switch (selection.field.kind) {
|
|
387
|
+
case SCALAR_FIELD:
|
|
388
|
+
return this._readScalar(selection.field, record, data);
|
|
389
|
+
case LINKED_FIELD:
|
|
390
|
+
if (selection.field.plural) {
|
|
391
|
+
return this._readPluralLink(selection.field, record, data);
|
|
392
|
+
} else {
|
|
393
|
+
return this._readLink(selection.field, record, data);
|
|
394
|
+
}
|
|
395
|
+
default:
|
|
396
|
+
(selection.field.kind: empty);
|
|
397
|
+
invariant(
|
|
398
|
+
false,
|
|
399
|
+
'RelayReader(): Unexpected ast kind `%s`.',
|
|
400
|
+
selection.kind,
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
_readFlightField(
|
|
406
|
+
field: ReaderFlightField,
|
|
407
|
+
record: Record,
|
|
408
|
+
data: SelectorData,
|
|
409
|
+
): ?mixed {
|
|
410
|
+
const applicationName = field.alias ?? field.name;
|
|
411
|
+
const storageKey = getStorageKey(field, this._variables);
|
|
412
|
+
const reactFlightClientResponseRecordID = RelayModernRecord.getLinkedRecordID(
|
|
413
|
+
record,
|
|
414
|
+
storageKey,
|
|
415
|
+
);
|
|
416
|
+
if (reactFlightClientResponseRecordID == null) {
|
|
417
|
+
data[applicationName] = reactFlightClientResponseRecordID;
|
|
418
|
+
if (reactFlightClientResponseRecordID === undefined) {
|
|
419
|
+
this._isMissingData = true;
|
|
420
|
+
}
|
|
421
|
+
return reactFlightClientResponseRecordID;
|
|
422
|
+
}
|
|
423
|
+
const reactFlightClientResponseRecord = this._recordSource.get(
|
|
424
|
+
reactFlightClientResponseRecordID,
|
|
425
|
+
);
|
|
426
|
+
this._seenRecords[
|
|
427
|
+
reactFlightClientResponseRecordID
|
|
428
|
+
] = reactFlightClientResponseRecord;
|
|
429
|
+
if (reactFlightClientResponseRecord == null) {
|
|
430
|
+
data[applicationName] = reactFlightClientResponseRecord;
|
|
431
|
+
if (reactFlightClientResponseRecord === undefined) {
|
|
432
|
+
this._isMissingData = true;
|
|
433
|
+
}
|
|
434
|
+
return reactFlightClientResponseRecord;
|
|
435
|
+
}
|
|
436
|
+
const clientResponse = getReactFlightClientResponse(
|
|
437
|
+
reactFlightClientResponseRecord,
|
|
438
|
+
);
|
|
439
|
+
data[applicationName] = clientResponse;
|
|
440
|
+
return clientResponse;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
_readScalar(
|
|
444
|
+
field: ReaderScalarField,
|
|
445
|
+
record: Record,
|
|
446
|
+
data: SelectorData,
|
|
447
|
+
): ?mixed {
|
|
448
|
+
const applicationName = field.alias ?? field.name;
|
|
449
|
+
const storageKey = getStorageKey(field, this._variables);
|
|
450
|
+
const value = RelayModernRecord.getValue(record, storageKey);
|
|
451
|
+
if (value === undefined) {
|
|
452
|
+
this._isMissingData = true;
|
|
453
|
+
}
|
|
454
|
+
data[applicationName] = value;
|
|
455
|
+
return value;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
_readLink(
|
|
459
|
+
field: ReaderLinkedField,
|
|
460
|
+
record: Record,
|
|
461
|
+
data: SelectorData,
|
|
462
|
+
): ?mixed {
|
|
463
|
+
const applicationName = field.alias ?? field.name;
|
|
464
|
+
const storageKey = getStorageKey(field, this._variables);
|
|
465
|
+
const linkedID = RelayModernRecord.getLinkedRecordID(record, storageKey);
|
|
466
|
+
if (linkedID == null) {
|
|
467
|
+
data[applicationName] = linkedID;
|
|
468
|
+
if (linkedID === undefined) {
|
|
469
|
+
this._isMissingData = true;
|
|
470
|
+
}
|
|
471
|
+
return linkedID;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const prevData = data[applicationName];
|
|
475
|
+
invariant(
|
|
476
|
+
prevData == null || typeof prevData === 'object',
|
|
477
|
+
'RelayReader(): Expected data for field `%s` on record `%s` ' +
|
|
478
|
+
'to be an object, got `%s`.',
|
|
479
|
+
applicationName,
|
|
480
|
+
RelayModernRecord.getDataID(record),
|
|
481
|
+
prevData,
|
|
482
|
+
);
|
|
483
|
+
// $FlowFixMe[incompatible-variance]
|
|
484
|
+
const value = this._traverse(field, linkedID, prevData);
|
|
485
|
+
data[applicationName] = value;
|
|
486
|
+
return value;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
_readPluralLink(
|
|
490
|
+
field: ReaderLinkedField,
|
|
491
|
+
record: Record,
|
|
492
|
+
data: SelectorData,
|
|
493
|
+
): ?mixed {
|
|
494
|
+
const applicationName = field.alias ?? field.name;
|
|
495
|
+
const storageKey = getStorageKey(field, this._variables);
|
|
496
|
+
const linkedIDs = RelayModernRecord.getLinkedRecordIDs(record, storageKey);
|
|
497
|
+
|
|
498
|
+
if (linkedIDs == null) {
|
|
499
|
+
data[applicationName] = linkedIDs;
|
|
500
|
+
if (linkedIDs === undefined) {
|
|
501
|
+
this._isMissingData = true;
|
|
502
|
+
}
|
|
503
|
+
return linkedIDs;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const prevData = data[applicationName];
|
|
507
|
+
invariant(
|
|
508
|
+
prevData == null || Array.isArray(prevData),
|
|
509
|
+
'RelayReader(): Expected data for field `%s` on record `%s` ' +
|
|
510
|
+
'to be an array, got `%s`.',
|
|
511
|
+
applicationName,
|
|
512
|
+
RelayModernRecord.getDataID(record),
|
|
513
|
+
prevData,
|
|
514
|
+
);
|
|
515
|
+
const linkedArray = prevData || [];
|
|
516
|
+
linkedIDs.forEach((linkedID, nextIndex) => {
|
|
517
|
+
if (linkedID == null) {
|
|
518
|
+
if (linkedID === undefined) {
|
|
519
|
+
this._isMissingData = true;
|
|
520
|
+
}
|
|
521
|
+
// $FlowFixMe[cannot-write]
|
|
522
|
+
linkedArray[nextIndex] = linkedID;
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const prevItem = linkedArray[nextIndex];
|
|
526
|
+
invariant(
|
|
527
|
+
prevItem == null || typeof prevItem === 'object',
|
|
528
|
+
'RelayReader(): Expected data for field `%s` on record `%s` ' +
|
|
529
|
+
'to be an object, got `%s`.',
|
|
530
|
+
applicationName,
|
|
531
|
+
RelayModernRecord.getDataID(record),
|
|
532
|
+
prevItem,
|
|
533
|
+
);
|
|
534
|
+
// $FlowFixMe[cannot-write]
|
|
535
|
+
// $FlowFixMe[incompatible-variance]
|
|
536
|
+
linkedArray[nextIndex] = this._traverse(field, linkedID, prevItem);
|
|
537
|
+
});
|
|
538
|
+
data[applicationName] = linkedArray;
|
|
539
|
+
return linkedArray;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Reads a ReaderModuleImport, which was generated from using the @module
|
|
544
|
+
* directive.
|
|
545
|
+
*/
|
|
546
|
+
_readModuleImport(
|
|
547
|
+
moduleImport: ReaderModuleImport,
|
|
548
|
+
record: Record,
|
|
549
|
+
data: SelectorData,
|
|
550
|
+
): void {
|
|
551
|
+
// Determine the component module from the store: if the field is missing
|
|
552
|
+
// it means we don't know what component to render the match with.
|
|
553
|
+
const componentKey = getModuleComponentKey(moduleImport.documentName);
|
|
554
|
+
const component = RelayModernRecord.getValue(record, componentKey);
|
|
555
|
+
if (component == null) {
|
|
556
|
+
if (component === undefined) {
|
|
557
|
+
this._isMissingData = true;
|
|
558
|
+
}
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Otherwise, read the fragment and module associated to the concrete
|
|
563
|
+
// type, and put that data with the result:
|
|
564
|
+
// - For the matched fragment, create the relevant fragment pointer and add
|
|
565
|
+
// the expected fragmentPropName
|
|
566
|
+
// - For the matched module, create a reference to the module
|
|
567
|
+
this._createFragmentPointer(
|
|
568
|
+
{
|
|
569
|
+
kind: 'FragmentSpread',
|
|
570
|
+
name: moduleImport.fragmentName,
|
|
571
|
+
args: null,
|
|
572
|
+
},
|
|
573
|
+
record,
|
|
574
|
+
data,
|
|
575
|
+
);
|
|
576
|
+
data[FRAGMENT_PROP_NAME_KEY] = moduleImport.fragmentPropName;
|
|
577
|
+
data[MODULE_COMPONENT_KEY] = component;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
_createFragmentPointer(
|
|
581
|
+
fragmentSpread: ReaderFragmentSpread,
|
|
582
|
+
record: Record,
|
|
583
|
+
data: SelectorData,
|
|
584
|
+
): void {
|
|
585
|
+
let fragmentPointers = data[FRAGMENTS_KEY];
|
|
586
|
+
if (fragmentPointers == null) {
|
|
587
|
+
fragmentPointers = data[FRAGMENTS_KEY] = {};
|
|
588
|
+
}
|
|
589
|
+
invariant(
|
|
590
|
+
typeof fragmentPointers === 'object' && fragmentPointers != null,
|
|
591
|
+
'RelayReader: Expected fragment spread data to be an object, got `%s`.',
|
|
592
|
+
fragmentPointers,
|
|
593
|
+
);
|
|
594
|
+
if (data[ID_KEY] == null) {
|
|
595
|
+
data[ID_KEY] = RelayModernRecord.getDataID(record);
|
|
596
|
+
}
|
|
597
|
+
// $FlowFixMe[cannot-write] - writing into read-only field
|
|
598
|
+
fragmentPointers[fragmentSpread.name] = fragmentSpread.args
|
|
599
|
+
? getArgumentValues(fragmentSpread.args, this._variables)
|
|
600
|
+
: {};
|
|
601
|
+
data[FRAGMENT_OWNER_KEY] = this._owner;
|
|
602
|
+
|
|
603
|
+
if (RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT) {
|
|
604
|
+
data[
|
|
605
|
+
IS_WITHIN_UNMATCHED_TYPE_REFINEMENT
|
|
606
|
+
] = this._isWithinUnmatchedTypeRefinement;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
_createInlineDataFragmentPointer(
|
|
611
|
+
inlineDataFragmentSpread: ReaderInlineDataFragmentSpread,
|
|
612
|
+
record: Record,
|
|
613
|
+
data: SelectorData,
|
|
614
|
+
): void {
|
|
615
|
+
let fragmentPointers = data[FRAGMENTS_KEY];
|
|
616
|
+
if (fragmentPointers == null) {
|
|
617
|
+
fragmentPointers = data[FRAGMENTS_KEY] = {};
|
|
618
|
+
}
|
|
619
|
+
invariant(
|
|
620
|
+
typeof fragmentPointers === 'object' && fragmentPointers != null,
|
|
621
|
+
'RelayReader: Expected fragment spread data to be an object, got `%s`.',
|
|
622
|
+
fragmentPointers,
|
|
623
|
+
);
|
|
624
|
+
if (data[ID_KEY] == null) {
|
|
625
|
+
data[ID_KEY] = RelayModernRecord.getDataID(record);
|
|
626
|
+
}
|
|
627
|
+
const inlineData = {};
|
|
628
|
+
this._traverseSelections(
|
|
629
|
+
inlineDataFragmentSpread.selections,
|
|
630
|
+
record,
|
|
631
|
+
inlineData,
|
|
632
|
+
);
|
|
633
|
+
// $FlowFixMe[cannot-write] - writing into read-only field
|
|
634
|
+
fragmentPointers[inlineDataFragmentSpread.name] = inlineData;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
module.exports = {read};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its 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
|
+
*/
|
|
10
|
+
|
|
11
|
+
// flowlint ambiguous-object-type:error
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const RelayRecordSourceMapImpl = require('./RelayRecordSourceMapImpl');
|
|
16
|
+
|
|
17
|
+
import type {MutableRecordSource, RecordMap} from './RelayStoreTypes';
|
|
18
|
+
|
|
19
|
+
class RelayRecordSource {
|
|
20
|
+
constructor(records?: RecordMap): MutableRecordSource {
|
|
21
|
+
return RelayRecordSource.create(records);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static create(records?: RecordMap): MutableRecordSource {
|
|
25
|
+
return new RelayRecordSourceMapImpl(records);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = RelayRecordSource;
|