relay-runtime 10.0.1 → 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 +6 -0
- package/handlers/connection/MutationHandlers.js.flow +114 -3
- package/index.js +1 -1
- package/index.js.flow +16 -1
- package/lib/handlers/RelayDefaultHandlerProvider.js +9 -0
- package/lib/handlers/connection/MutationHandlers.js +138 -12
- package/lib/index.js +7 -0
- package/lib/mutations/RelayDeclarativeMutationConfig.js +2 -2
- package/lib/mutations/commitMutation.js +1 -4
- package/lib/mutations/validateMutation.js +27 -7
- package/lib/network/RelayQueryResponseCache.js +2 -2
- package/lib/query/GraphQLTag.js +2 -1
- package/lib/query/fetchQuery.js +2 -3
- package/lib/query/fetchQueryInternal.js +2 -3
- package/lib/store/DataChecker.js +82 -5
- package/lib/store/RelayModernEnvironment.js +18 -6
- package/lib/store/RelayModernFragmentSpecResolver.js +10 -1
- package/lib/store/RelayModernOperationDescriptor.js +6 -5
- package/lib/store/RelayModernQueryExecutor.js +44 -23
- package/lib/store/RelayModernStore.js +25 -14
- package/lib/store/RelayOperationTracker.js +2 -2
- package/lib/store/RelayPublishQueue.js +1 -1
- package/lib/store/RelayReader.js +196 -33
- package/lib/store/RelayRecordSourceMapImpl.js +2 -2
- package/lib/store/RelayReferenceMarker.js +89 -5
- package/lib/store/RelayResponseNormalizer.js +119 -19
- package/lib/store/RelayStoreReactFlightUtils.js +47 -0
- package/lib/store/defaultRequiredFieldLogger.js +18 -0
- package/lib/store/normalizeRelayPayload.js +1 -1
- package/lib/subscription/requestSubscription.js +2 -3
- package/lib/util/NormalizationNode.js +1 -5
- package/lib/util/RelayConcreteNode.js +2 -0
- package/lib/util/RelayFeatureFlags.js +5 -2
- package/lib/util/getFragmentIdentifier.js +12 -3
- package/lib/util/getOperation.js +33 -0
- package/lib/util/isEmptyObject.js +25 -0
- package/lib/util/recycleNodesInto.js +4 -1
- package/lib/util/reportMissingRequiredFields.js +48 -0
- package/mutations/commitMutation.js.flow +1 -2
- package/mutations/validateMutation.js.flow +34 -5
- package/network/RelayNetworkTypes.js.flow +22 -0
- package/package.json +2 -2
- package/query/GraphQLTag.js.flow +3 -1
- package/query/fetchQuery.js.flow +2 -2
- package/query/fetchQueryInternal.js.flow +0 -5
- package/relay-runtime.js +2 -2
- package/relay-runtime.min.js +2 -2
- package/store/DataChecker.js.flow +68 -2
- package/store/RelayModernEnvironment.js.flow +29 -9
- package/store/RelayModernFragmentSpecResolver.js.flow +13 -1
- package/store/RelayModernOperationDescriptor.js.flow +5 -1
- package/store/RelayModernQueryExecutor.js.flow +47 -23
- package/store/RelayModernStore.js.flow +31 -15
- package/store/RelayPublishQueue.js.flow +1 -1
- package/store/RelayReader.js.flow +180 -15
- package/store/RelayReferenceMarker.js.flow +72 -5
- package/store/RelayResponseNormalizer.js.flow +130 -19
- package/store/RelayStoreReactFlightUtils.js.flow +64 -0
- package/store/RelayStoreTypes.js.flow +90 -31
- package/store/defaultRequiredFieldLogger.js.flow +23 -0
- package/subscription/requestSubscription.js.flow +5 -2
- package/util/NormalizationNode.js.flow +17 -2
- package/util/ReaderNode.js.flow +20 -1
- package/util/RelayConcreteNode.js.flow +6 -0
- package/util/RelayFeatureFlags.js.flow +8 -1
- package/util/getFragmentIdentifier.js.flow +33 -9
- package/util/getOperation.js.flow +40 -0
- package/util/isEmptyObject.js.flow +25 -0
- package/util/recycleNodesInto.js.flow +11 -0
- package/util/reportMissingRequiredFields.js.flow +51 -0
|
@@ -18,12 +18,14 @@ const RelayOptimisticRecordSource = require('./RelayOptimisticRecordSource');
|
|
|
18
18
|
const RelayProfiler = require('../util/RelayProfiler');
|
|
19
19
|
const RelayReader = require('./RelayReader');
|
|
20
20
|
const RelayReferenceMarker = require('./RelayReferenceMarker');
|
|
21
|
+
const RelayStoreReactFlightUtils = require('./RelayStoreReactFlightUtils');
|
|
21
22
|
const RelayStoreUtils = require('./RelayStoreUtils');
|
|
22
23
|
|
|
23
24
|
const deepFreeze = require('../util/deepFreeze');
|
|
24
25
|
const defaultGetDataID = require('./defaultGetDataID');
|
|
25
26
|
const hasOverlappingIDs = require('./hasOverlappingIDs');
|
|
26
27
|
const invariant = require('invariant');
|
|
28
|
+
const isEmptyObject = require('../util/isEmptyObject');
|
|
27
29
|
const recycleNodesInto = require('../util/recycleNodesInto');
|
|
28
30
|
const resolveImmediate = require('../util/resolveImmediate');
|
|
29
31
|
|
|
@@ -53,13 +55,12 @@ export opaque type InvalidationState = {|
|
|
|
53
55
|
invalidations: Map<DataID, ?number>,
|
|
54
56
|
|};
|
|
55
57
|
|
|
56
|
-
type Subscription = {
|
|
58
|
+
type Subscription = {|
|
|
57
59
|
callback: (snapshot: Snapshot) => void,
|
|
58
60
|
snapshot: Snapshot,
|
|
59
61
|
stale: boolean,
|
|
60
62
|
backup: ?Snapshot,
|
|
61
|
-
|
|
62
|
-
};
|
|
63
|
+
|};
|
|
63
64
|
|
|
64
65
|
type InvalidationSubscription = {|
|
|
65
66
|
callback: () => void,
|
|
@@ -232,7 +233,7 @@ class RelayModernStore implements Store {
|
|
|
232
233
|
|
|
233
234
|
if (rootEntryIsStale) {
|
|
234
235
|
this._roots.delete(id);
|
|
235
|
-
this.
|
|
236
|
+
this.scheduleGC();
|
|
236
237
|
} else {
|
|
237
238
|
this._releaseBuffer.push(id);
|
|
238
239
|
|
|
@@ -242,7 +243,7 @@ class RelayModernStore implements Store {
|
|
|
242
243
|
if (this._releaseBuffer.length > this._gcReleaseBufferSize) {
|
|
243
244
|
const _id = this._releaseBuffer.shift();
|
|
244
245
|
this._roots.delete(_id);
|
|
245
|
-
this.
|
|
246
|
+
this.scheduleGC();
|
|
246
247
|
}
|
|
247
248
|
}
|
|
248
249
|
}
|
|
@@ -302,8 +303,13 @@ class RelayModernStore implements Store {
|
|
|
302
303
|
|
|
303
304
|
const source = this.getSource();
|
|
304
305
|
const updatedOwners = [];
|
|
306
|
+
const hasUpdatedRecords = !isEmptyObject(this._updatedRecordIDs);
|
|
305
307
|
this._subscriptions.forEach(subscription => {
|
|
306
|
-
const owner = this._updateSubscription(
|
|
308
|
+
const owner = this._updateSubscription(
|
|
309
|
+
source,
|
|
310
|
+
subscription,
|
|
311
|
+
hasUpdatedRecords,
|
|
312
|
+
);
|
|
307
313
|
if (owner != null) {
|
|
308
314
|
updatedOwners.push(owner);
|
|
309
315
|
}
|
|
@@ -407,7 +413,7 @@ class RelayModernStore implements Store {
|
|
|
407
413
|
if (this._gcHoldCounter > 0) {
|
|
408
414
|
this._gcHoldCounter--;
|
|
409
415
|
if (this._gcHoldCounter === 0 && this._shouldScheduleGC) {
|
|
410
|
-
this.
|
|
416
|
+
this.scheduleGC();
|
|
411
417
|
this._shouldScheduleGC = false;
|
|
412
418
|
}
|
|
413
419
|
}
|
|
@@ -429,12 +435,12 @@ class RelayModernStore implements Store {
|
|
|
429
435
|
_updateSubscription(
|
|
430
436
|
source: RecordSource,
|
|
431
437
|
subscription: Subscription,
|
|
438
|
+
hasUpdatedRecords: boolean,
|
|
432
439
|
): ?RequestDescriptor {
|
|
433
440
|
const {backup, callback, snapshot, stale} = subscription;
|
|
434
|
-
const hasOverlappingUpdates =
|
|
435
|
-
|
|
436
|
-
this._updatedRecordIDs
|
|
437
|
-
);
|
|
441
|
+
const hasOverlappingUpdates =
|
|
442
|
+
hasUpdatedRecords &&
|
|
443
|
+
hasOverlappingIDs(snapshot.seenRecords, this._updatedRecordIDs);
|
|
438
444
|
if (!stale && !hasOverlappingUpdates) {
|
|
439
445
|
return;
|
|
440
446
|
}
|
|
@@ -448,6 +454,7 @@ class RelayModernStore implements Store {
|
|
|
448
454
|
isMissingData: nextSnapshot.isMissingData,
|
|
449
455
|
seenRecords: nextSnapshot.seenRecords,
|
|
450
456
|
selector: nextSnapshot.selector,
|
|
457
|
+
missingRequiredFields: nextSnapshot.missingRequiredFields,
|
|
451
458
|
}: Snapshot);
|
|
452
459
|
if (__DEV__) {
|
|
453
460
|
deepFreeze(nextSnapshot);
|
|
@@ -585,7 +592,7 @@ class RelayModernStore implements Store {
|
|
|
585
592
|
}
|
|
586
593
|
this._optimisticSource = null;
|
|
587
594
|
if (this._shouldScheduleGC) {
|
|
588
|
-
this.
|
|
595
|
+
this.scheduleGC();
|
|
589
596
|
}
|
|
590
597
|
this._subscriptions.forEach(subscription => {
|
|
591
598
|
const backup = subscription.backup;
|
|
@@ -599,6 +606,7 @@ class RelayModernStore implements Store {
|
|
|
599
606
|
isMissingData: backup.isMissingData,
|
|
600
607
|
seenRecords: backup.seenRecords,
|
|
601
608
|
selector: backup.selector,
|
|
609
|
+
missingRequiredFields: backup.missingRequiredFields,
|
|
602
610
|
};
|
|
603
611
|
} else {
|
|
604
612
|
subscription.stale = true;
|
|
@@ -606,7 +614,7 @@ class RelayModernStore implements Store {
|
|
|
606
614
|
});
|
|
607
615
|
}
|
|
608
616
|
|
|
609
|
-
|
|
617
|
+
scheduleGC() {
|
|
610
618
|
if (this._gcHoldCounter > 0) {
|
|
611
619
|
this._shouldScheduleGC = true;
|
|
612
620
|
return;
|
|
@@ -701,7 +709,7 @@ function initializeRecordSource(target: MutableRecordSource) {
|
|
|
701
709
|
/**
|
|
702
710
|
* Updates the target with information from source, also updating a mapping of
|
|
703
711
|
* which records in the target were changed as a result.
|
|
704
|
-
* Additionally, will
|
|
712
|
+
* Additionally, will mark records as invalidated at the current write epoch
|
|
705
713
|
* given the set of record ids marked as stale in this update.
|
|
706
714
|
*/
|
|
707
715
|
function updateTargetFromSource(
|
|
@@ -770,7 +778,15 @@ function updateTargetFromSource(
|
|
|
770
778
|
}
|
|
771
779
|
}
|
|
772
780
|
if (sourceRecord && targetRecord) {
|
|
773
|
-
|
|
781
|
+
// ReactFlightClientResponses are lazy and only materialize when readRoot
|
|
782
|
+
// is called when we read the field, so if the record is a Flight field
|
|
783
|
+
// we always use the new record's data regardless of whether
|
|
784
|
+
// it actually changed. Let React take care of reconciliation instead.
|
|
785
|
+
const nextRecord =
|
|
786
|
+
RelayModernRecord.getType(targetRecord) ===
|
|
787
|
+
RelayStoreReactFlightUtils.REACT_FLIGHT_TYPE_NAME
|
|
788
|
+
? sourceRecord
|
|
789
|
+
: RelayModernRecord.update(targetRecord, sourceRecord);
|
|
774
790
|
if (nextRecord !== targetRecord) {
|
|
775
791
|
// Prevent mutation of a record from outside the store.
|
|
776
792
|
if (__DEV__) {
|
|
@@ -186,7 +186,7 @@ class RelayPublishQueue implements PublishQueue {
|
|
|
186
186
|
warning(
|
|
187
187
|
this._isRunning !== true,
|
|
188
188
|
'A store update was detected within another store update. Please ' +
|
|
189
|
-
|
|
189
|
+
"make sure new store updates aren't being executed within an " +
|
|
190
190
|
'updater function for a different update.',
|
|
191
191
|
);
|
|
192
192
|
this._isRunning = true;
|
|
@@ -21,14 +21,17 @@ const {
|
|
|
21
21
|
CLIENT_EXTENSION,
|
|
22
22
|
CONDITION,
|
|
23
23
|
DEFER,
|
|
24
|
+
FLIGHT_FIELD,
|
|
24
25
|
FRAGMENT_SPREAD,
|
|
25
26
|
INLINE_DATA_FRAGMENT_SPREAD,
|
|
26
27
|
INLINE_FRAGMENT,
|
|
27
28
|
LINKED_FIELD,
|
|
28
29
|
MODULE_IMPORT,
|
|
30
|
+
REQUIRED_FIELD,
|
|
29
31
|
SCALAR_FIELD,
|
|
30
32
|
STREAM,
|
|
31
33
|
} = require('../util/RelayConcreteNode');
|
|
34
|
+
const {getReactFlightClientResponse} = require('./RelayStoreReactFlightUtils');
|
|
32
35
|
const {
|
|
33
36
|
FRAGMENTS_KEY,
|
|
34
37
|
FRAGMENT_OWNER_KEY,
|
|
@@ -44,11 +47,13 @@ const {
|
|
|
44
47
|
const {generateTypeID} = require('./TypeID');
|
|
45
48
|
|
|
46
49
|
import type {
|
|
50
|
+
ReaderFlightField,
|
|
47
51
|
ReaderFragmentSpread,
|
|
48
52
|
ReaderInlineDataFragmentSpread,
|
|
49
53
|
ReaderLinkedField,
|
|
50
54
|
ReaderModuleImport,
|
|
51
55
|
ReaderNode,
|
|
56
|
+
ReaderRequiredField,
|
|
52
57
|
ReaderScalarField,
|
|
53
58
|
ReaderSelection,
|
|
54
59
|
} from '../util/ReaderNode';
|
|
@@ -60,6 +65,7 @@ import type {
|
|
|
60
65
|
SelectorData,
|
|
61
66
|
SingularReaderSelector,
|
|
62
67
|
Snapshot,
|
|
68
|
+
MissingRequiredFields,
|
|
63
69
|
} from './RelayStoreTypes';
|
|
64
70
|
|
|
65
71
|
function read(
|
|
@@ -76,6 +82,7 @@ function read(
|
|
|
76
82
|
class RelayReader {
|
|
77
83
|
_isMissingData: boolean;
|
|
78
84
|
_isWithinUnmatchedTypeRefinement: boolean;
|
|
85
|
+
_missingRequiredFields: ?MissingRequiredFields;
|
|
79
86
|
_owner: RequestDescriptor;
|
|
80
87
|
_recordSource: RecordSource;
|
|
81
88
|
_seenRecords: {[dataID: DataID]: ?Record, ...};
|
|
@@ -85,6 +92,7 @@ class RelayReader {
|
|
|
85
92
|
constructor(recordSource: RecordSource, selector: SingularReaderSelector) {
|
|
86
93
|
this._isMissingData = false;
|
|
87
94
|
this._isWithinUnmatchedTypeRefinement = false;
|
|
95
|
+
this._missingRequiredFields = null;
|
|
88
96
|
this._owner = selector.owner;
|
|
89
97
|
this._recordSource = recordSource;
|
|
90
98
|
this._seenRecords = {};
|
|
@@ -151,6 +159,7 @@ class RelayReader {
|
|
|
151
159
|
isMissingData: this._isMissingData && isDataExpectedToBePresent,
|
|
152
160
|
seenRecords: this._seenRecords,
|
|
153
161
|
selector: this._selector,
|
|
162
|
+
missingRequiredFields: this._missingRequiredFields,
|
|
154
163
|
};
|
|
155
164
|
}
|
|
156
165
|
|
|
@@ -168,8 +177,12 @@ class RelayReader {
|
|
|
168
177
|
return record;
|
|
169
178
|
}
|
|
170
179
|
const data = prevData || {};
|
|
171
|
-
this._traverseSelections(
|
|
172
|
-
|
|
180
|
+
const hadRequiredData = this._traverseSelections(
|
|
181
|
+
node.selections,
|
|
182
|
+
record,
|
|
183
|
+
data,
|
|
184
|
+
);
|
|
185
|
+
return hadRequiredData ? data : null;
|
|
173
186
|
}
|
|
174
187
|
|
|
175
188
|
_getVariableValue(name: string): mixed {
|
|
@@ -181,14 +194,62 @@ class RelayReader {
|
|
|
181
194
|
return this._variables[name];
|
|
182
195
|
}
|
|
183
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
|
+
|
|
184
225
|
_traverseSelections(
|
|
185
226
|
selections: $ReadOnlyArray<ReaderSelection>,
|
|
186
227
|
record: Record,
|
|
187
228
|
data: SelectorData,
|
|
188
|
-
):
|
|
229
|
+
): boolean /* had all expected data */ {
|
|
189
230
|
for (let i = 0; i < selections.length; i++) {
|
|
190
231
|
const selection = selections[i];
|
|
191
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;
|
|
192
253
|
case SCALAR_FIELD:
|
|
193
254
|
this._readScalar(selection, record, data);
|
|
194
255
|
break;
|
|
@@ -202,7 +263,14 @@ class RelayReader {
|
|
|
202
263
|
case CONDITION:
|
|
203
264
|
const conditionValue = this._getVariableValue(selection.condition);
|
|
204
265
|
if (conditionValue === selection.passingValue) {
|
|
205
|
-
this._traverseSelections(
|
|
266
|
+
const hasExpectedData = this._traverseSelections(
|
|
267
|
+
selection.selections,
|
|
268
|
+
record,
|
|
269
|
+
data,
|
|
270
|
+
);
|
|
271
|
+
if (!hasExpectedData) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
206
274
|
}
|
|
207
275
|
break;
|
|
208
276
|
case INLINE_FRAGMENT: {
|
|
@@ -211,7 +279,14 @@ class RelayReader {
|
|
|
211
279
|
// concrete type refinement: only read data if the type exactly matches
|
|
212
280
|
const typeName = RelayModernRecord.getType(record);
|
|
213
281
|
if (typeName != null && typeName === selection.type) {
|
|
214
|
-
this._traverseSelections(
|
|
282
|
+
const hasExpectedData = this._traverseSelections(
|
|
283
|
+
selection.selections,
|
|
284
|
+
record,
|
|
285
|
+
data,
|
|
286
|
+
);
|
|
287
|
+
if (!hasExpectedData) {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
215
290
|
}
|
|
216
291
|
} else if (RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT) {
|
|
217
292
|
// Similar to the logic in read(): data is only expected to be present
|
|
@@ -260,13 +335,36 @@ class RelayReader {
|
|
|
260
335
|
this._createInlineDataFragmentPointer(selection, record, data);
|
|
261
336
|
break;
|
|
262
337
|
case DEFER:
|
|
263
|
-
case CLIENT_EXTENSION:
|
|
338
|
+
case CLIENT_EXTENSION: {
|
|
264
339
|
const isMissingData = this._isMissingData;
|
|
265
|
-
this._traverseSelections(
|
|
340
|
+
const hasExpectedData = this._traverseSelections(
|
|
341
|
+
selection.selections,
|
|
342
|
+
record,
|
|
343
|
+
data,
|
|
344
|
+
);
|
|
266
345
|
this._isMissingData = isMissingData;
|
|
346
|
+
if (!hasExpectedData) {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
267
349
|
break;
|
|
268
|
-
|
|
269
|
-
|
|
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
|
+
}
|
|
270
368
|
break;
|
|
271
369
|
default:
|
|
272
370
|
(selection: empty);
|
|
@@ -277,13 +375,76 @@ class RelayReader {
|
|
|
277
375
|
);
|
|
278
376
|
}
|
|
279
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;
|
|
280
441
|
}
|
|
281
442
|
|
|
282
443
|
_readScalar(
|
|
283
444
|
field: ReaderScalarField,
|
|
284
445
|
record: Record,
|
|
285
446
|
data: SelectorData,
|
|
286
|
-
):
|
|
447
|
+
): ?mixed {
|
|
287
448
|
const applicationName = field.alias ?? field.name;
|
|
288
449
|
const storageKey = getStorageKey(field, this._variables);
|
|
289
450
|
const value = RelayModernRecord.getValue(record, storageKey);
|
|
@@ -291,13 +452,14 @@ class RelayReader {
|
|
|
291
452
|
this._isMissingData = true;
|
|
292
453
|
}
|
|
293
454
|
data[applicationName] = value;
|
|
455
|
+
return value;
|
|
294
456
|
}
|
|
295
457
|
|
|
296
458
|
_readLink(
|
|
297
459
|
field: ReaderLinkedField,
|
|
298
460
|
record: Record,
|
|
299
461
|
data: SelectorData,
|
|
300
|
-
):
|
|
462
|
+
): ?mixed {
|
|
301
463
|
const applicationName = field.alias ?? field.name;
|
|
302
464
|
const storageKey = getStorageKey(field, this._variables);
|
|
303
465
|
const linkedID = RelayModernRecord.getLinkedRecordID(record, storageKey);
|
|
@@ -306,7 +468,7 @@ class RelayReader {
|
|
|
306
468
|
if (linkedID === undefined) {
|
|
307
469
|
this._isMissingData = true;
|
|
308
470
|
}
|
|
309
|
-
return;
|
|
471
|
+
return linkedID;
|
|
310
472
|
}
|
|
311
473
|
|
|
312
474
|
const prevData = data[applicationName];
|
|
@@ -319,14 +481,16 @@ class RelayReader {
|
|
|
319
481
|
prevData,
|
|
320
482
|
);
|
|
321
483
|
// $FlowFixMe[incompatible-variance]
|
|
322
|
-
|
|
484
|
+
const value = this._traverse(field, linkedID, prevData);
|
|
485
|
+
data[applicationName] = value;
|
|
486
|
+
return value;
|
|
323
487
|
}
|
|
324
488
|
|
|
325
489
|
_readPluralLink(
|
|
326
490
|
field: ReaderLinkedField,
|
|
327
491
|
record: Record,
|
|
328
492
|
data: SelectorData,
|
|
329
|
-
):
|
|
493
|
+
): ?mixed {
|
|
330
494
|
const applicationName = field.alias ?? field.name;
|
|
331
495
|
const storageKey = getStorageKey(field, this._variables);
|
|
332
496
|
const linkedIDs = RelayModernRecord.getLinkedRecordIDs(record, storageKey);
|
|
@@ -336,7 +500,7 @@ class RelayReader {
|
|
|
336
500
|
if (linkedIDs === undefined) {
|
|
337
501
|
this._isMissingData = true;
|
|
338
502
|
}
|
|
339
|
-
return;
|
|
503
|
+
return linkedIDs;
|
|
340
504
|
}
|
|
341
505
|
|
|
342
506
|
const prevData = data[applicationName];
|
|
@@ -372,6 +536,7 @@ class RelayReader {
|
|
|
372
536
|
linkedArray[nextIndex] = this._traverse(field, linkedID, prevItem);
|
|
373
537
|
});
|
|
374
538
|
data[applicationName] = linkedArray;
|
|
539
|
+
return linkedArray;
|
|
375
540
|
}
|
|
376
541
|
|
|
377
542
|
/**
|
|
@@ -15,14 +15,18 @@
|
|
|
15
15
|
const RelayConcreteNode = require('../util/RelayConcreteNode');
|
|
16
16
|
const RelayFeatureFlags = require('../util/RelayFeatureFlags');
|
|
17
17
|
const RelayModernRecord = require('./RelayModernRecord');
|
|
18
|
+
const RelayStoreReactFlightUtils = require('./RelayStoreReactFlightUtils');
|
|
18
19
|
const RelayStoreUtils = require('./RelayStoreUtils');
|
|
19
20
|
|
|
20
21
|
const cloneRelayHandleSourceField = require('./cloneRelayHandleSourceField');
|
|
22
|
+
const getOperation = require('../util/getOperation');
|
|
21
23
|
const invariant = require('invariant');
|
|
22
24
|
|
|
23
25
|
const {generateTypeID} = require('./TypeID');
|
|
24
26
|
|
|
27
|
+
import type {ReactFlightPayloadQuery} from '../network/RelayNetworkTypes';
|
|
25
28
|
import type {
|
|
29
|
+
NormalizationFlightField,
|
|
26
30
|
NormalizationLinkedField,
|
|
27
31
|
NormalizationModuleImport,
|
|
28
32
|
NormalizationNode,
|
|
@@ -40,6 +44,7 @@ const {
|
|
|
40
44
|
CONDITION,
|
|
41
45
|
CLIENT_EXTENSION,
|
|
42
46
|
DEFER,
|
|
47
|
+
FLIGHT_FIELD,
|
|
43
48
|
FRAGMENT_SPREAD,
|
|
44
49
|
INLINE_FRAGMENT,
|
|
45
50
|
LINKED_FIELD,
|
|
@@ -50,7 +55,7 @@ const {
|
|
|
50
55
|
STREAM,
|
|
51
56
|
TYPE_DISCRIMINATOR,
|
|
52
57
|
} = RelayConcreteNode;
|
|
53
|
-
const {getStorageKey, getModuleOperationKey} = RelayStoreUtils;
|
|
58
|
+
const {ROOT_ID, getStorageKey, getModuleOperationKey} = RelayStoreUtils;
|
|
54
59
|
|
|
55
60
|
function mark(
|
|
56
61
|
recordSource: RecordSource,
|
|
@@ -73,6 +78,7 @@ function mark(
|
|
|
73
78
|
*/
|
|
74
79
|
class RelayReferenceMarker {
|
|
75
80
|
_operationLoader: OperationLoader | null;
|
|
81
|
+
_operationName: ?string;
|
|
76
82
|
_recordSource: RecordSource;
|
|
77
83
|
_references: Set<DataID>;
|
|
78
84
|
_variables: Variables;
|
|
@@ -84,12 +90,16 @@ class RelayReferenceMarker {
|
|
|
84
90
|
operationLoader: ?OperationLoader,
|
|
85
91
|
) {
|
|
86
92
|
this._operationLoader = operationLoader ?? null;
|
|
93
|
+
this._operationName = null;
|
|
87
94
|
this._recordSource = recordSource;
|
|
88
95
|
this._references = references;
|
|
89
96
|
this._variables = variables;
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
mark(node: NormalizationNode, dataID: DataID): void {
|
|
100
|
+
if (node.kind === 'Operation' || node.kind === 'SplitOperation') {
|
|
101
|
+
this._operationName = node.name;
|
|
102
|
+
}
|
|
93
103
|
this._traverse(node, dataID);
|
|
94
104
|
}
|
|
95
105
|
|
|
@@ -146,6 +156,7 @@ class RelayReferenceMarker {
|
|
|
146
156
|
this._traverseSelections(selection.selections, record);
|
|
147
157
|
}
|
|
148
158
|
break;
|
|
159
|
+
// $FlowFixMe[incompatible-type]
|
|
149
160
|
case FRAGMENT_SPREAD:
|
|
150
161
|
invariant(
|
|
151
162
|
false,
|
|
@@ -195,6 +206,13 @@ class RelayReferenceMarker {
|
|
|
195
206
|
case CLIENT_EXTENSION:
|
|
196
207
|
this._traverseSelections(selection.selections, record);
|
|
197
208
|
break;
|
|
209
|
+
case FLIGHT_FIELD:
|
|
210
|
+
if (RelayFeatureFlags.ENABLE_REACT_FLIGHT_COMPONENT_FIELD) {
|
|
211
|
+
this._traverseFlightField(selection, record);
|
|
212
|
+
} else {
|
|
213
|
+
throw new Error('Flight fields are not yet supported.');
|
|
214
|
+
}
|
|
215
|
+
break;
|
|
198
216
|
default:
|
|
199
217
|
(selection: empty);
|
|
200
218
|
invariant(
|
|
@@ -213,16 +231,20 @@ class RelayReferenceMarker {
|
|
|
213
231
|
const operationLoader = this._operationLoader;
|
|
214
232
|
invariant(
|
|
215
233
|
operationLoader !== null,
|
|
216
|
-
'RelayReferenceMarker: Expected an operationLoader to be configured when using `@module`.'
|
|
234
|
+
'RelayReferenceMarker: Expected an operationLoader to be configured when using `@module`. ' +
|
|
235
|
+
'Could not load fragment `%s` in operation `%s`.',
|
|
236
|
+
moduleImport.fragmentName,
|
|
237
|
+
this._operationName ?? '(unknown)',
|
|
217
238
|
);
|
|
218
239
|
const operationKey = getModuleOperationKey(moduleImport.documentName);
|
|
219
240
|
const operationReference = RelayModernRecord.getValue(record, operationKey);
|
|
220
241
|
if (operationReference == null) {
|
|
221
242
|
return;
|
|
222
243
|
}
|
|
223
|
-
const
|
|
224
|
-
if (
|
|
225
|
-
|
|
244
|
+
const normalizationRootNode = operationLoader.get(operationReference);
|
|
245
|
+
if (normalizationRootNode != null) {
|
|
246
|
+
const selections = getOperation(normalizationRootNode).selections;
|
|
247
|
+
this._traverseSelections(selections, record);
|
|
226
248
|
}
|
|
227
249
|
// Otherwise, if the operation is not available, we assume that the data
|
|
228
250
|
// cannot have been processed yet and therefore isn't in the store to
|
|
@@ -252,6 +274,51 @@ class RelayReferenceMarker {
|
|
|
252
274
|
}
|
|
253
275
|
});
|
|
254
276
|
}
|
|
277
|
+
|
|
278
|
+
_traverseFlightField(field: NormalizationFlightField, record: Record): void {
|
|
279
|
+
const storageKey = getStorageKey(field, this._variables);
|
|
280
|
+
const linkedID = RelayModernRecord.getLinkedRecordID(record, storageKey);
|
|
281
|
+
if (linkedID == null) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
this._references.add(linkedID);
|
|
285
|
+
|
|
286
|
+
const reactFlightClientResponseRecord = this._recordSource.get(linkedID);
|
|
287
|
+
|
|
288
|
+
if (reactFlightClientResponseRecord == null) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const reachableQueries = RelayModernRecord.getValue(
|
|
293
|
+
reactFlightClientResponseRecord,
|
|
294
|
+
RelayStoreReactFlightUtils.REACT_FLIGHT_QUERIES_STORAGE_KEY,
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
if (!Array.isArray(reachableQueries)) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const operationLoader = this._operationLoader;
|
|
302
|
+
invariant(
|
|
303
|
+
operationLoader !== null,
|
|
304
|
+
'DataChecker: Expected an operationLoader to be configured when using ' +
|
|
305
|
+
'React Flight',
|
|
306
|
+
);
|
|
307
|
+
// In Flight, the variables that are in scope for reachable queries aren't
|
|
308
|
+
// the same as what's in scope for the outer query.
|
|
309
|
+
const prevVariables = this._variables;
|
|
310
|
+
// $FlowFixMe[incompatible-cast]
|
|
311
|
+
for (const query of (reachableQueries: Array<ReactFlightPayloadQuery>)) {
|
|
312
|
+
this._variables = query.variables;
|
|
313
|
+
const operationReference = query.module;
|
|
314
|
+
const normalizationRootNode = operationLoader.get(operationReference);
|
|
315
|
+
if (normalizationRootNode != null) {
|
|
316
|
+
const operation = getOperation(normalizationRootNode);
|
|
317
|
+
this._traverse(operation, ROOT_ID);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
this._variables = prevVariables;
|
|
321
|
+
}
|
|
255
322
|
}
|
|
256
323
|
|
|
257
324
|
module.exports = {mark};
|