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.
Files changed (70) hide show
  1. package/handlers/RelayDefaultHandlerProvider.js.flow +6 -0
  2. package/handlers/connection/MutationHandlers.js.flow +114 -3
  3. package/index.js +1 -1
  4. package/index.js.flow +16 -1
  5. package/lib/handlers/RelayDefaultHandlerProvider.js +9 -0
  6. package/lib/handlers/connection/MutationHandlers.js +138 -12
  7. package/lib/index.js +7 -0
  8. package/lib/mutations/RelayDeclarativeMutationConfig.js +2 -2
  9. package/lib/mutations/commitMutation.js +1 -4
  10. package/lib/mutations/validateMutation.js +27 -7
  11. package/lib/network/RelayQueryResponseCache.js +2 -2
  12. package/lib/query/GraphQLTag.js +2 -1
  13. package/lib/query/fetchQuery.js +2 -3
  14. package/lib/query/fetchQueryInternal.js +2 -3
  15. package/lib/store/DataChecker.js +82 -5
  16. package/lib/store/RelayModernEnvironment.js +18 -6
  17. package/lib/store/RelayModernFragmentSpecResolver.js +10 -1
  18. package/lib/store/RelayModernOperationDescriptor.js +6 -5
  19. package/lib/store/RelayModernQueryExecutor.js +44 -23
  20. package/lib/store/RelayModernStore.js +25 -14
  21. package/lib/store/RelayOperationTracker.js +2 -2
  22. package/lib/store/RelayPublishQueue.js +1 -1
  23. package/lib/store/RelayReader.js +196 -33
  24. package/lib/store/RelayRecordSourceMapImpl.js +2 -2
  25. package/lib/store/RelayReferenceMarker.js +89 -5
  26. package/lib/store/RelayResponseNormalizer.js +119 -19
  27. package/lib/store/RelayStoreReactFlightUtils.js +47 -0
  28. package/lib/store/defaultRequiredFieldLogger.js +18 -0
  29. package/lib/store/normalizeRelayPayload.js +1 -1
  30. package/lib/subscription/requestSubscription.js +2 -3
  31. package/lib/util/NormalizationNode.js +1 -5
  32. package/lib/util/RelayConcreteNode.js +2 -0
  33. package/lib/util/RelayFeatureFlags.js +5 -2
  34. package/lib/util/getFragmentIdentifier.js +12 -3
  35. package/lib/util/getOperation.js +33 -0
  36. package/lib/util/isEmptyObject.js +25 -0
  37. package/lib/util/recycleNodesInto.js +4 -1
  38. package/lib/util/reportMissingRequiredFields.js +48 -0
  39. package/mutations/commitMutation.js.flow +1 -2
  40. package/mutations/validateMutation.js.flow +34 -5
  41. package/network/RelayNetworkTypes.js.flow +22 -0
  42. package/package.json +2 -2
  43. package/query/GraphQLTag.js.flow +3 -1
  44. package/query/fetchQuery.js.flow +2 -2
  45. package/query/fetchQueryInternal.js.flow +0 -5
  46. package/relay-runtime.js +2 -2
  47. package/relay-runtime.min.js +2 -2
  48. package/store/DataChecker.js.flow +68 -2
  49. package/store/RelayModernEnvironment.js.flow +29 -9
  50. package/store/RelayModernFragmentSpecResolver.js.flow +13 -1
  51. package/store/RelayModernOperationDescriptor.js.flow +5 -1
  52. package/store/RelayModernQueryExecutor.js.flow +47 -23
  53. package/store/RelayModernStore.js.flow +31 -15
  54. package/store/RelayPublishQueue.js.flow +1 -1
  55. package/store/RelayReader.js.flow +180 -15
  56. package/store/RelayReferenceMarker.js.flow +72 -5
  57. package/store/RelayResponseNormalizer.js.flow +130 -19
  58. package/store/RelayStoreReactFlightUtils.js.flow +64 -0
  59. package/store/RelayStoreTypes.js.flow +90 -31
  60. package/store/defaultRequiredFieldLogger.js.flow +23 -0
  61. package/subscription/requestSubscription.js.flow +5 -2
  62. package/util/NormalizationNode.js.flow +17 -2
  63. package/util/ReaderNode.js.flow +20 -1
  64. package/util/RelayConcreteNode.js.flow +6 -0
  65. package/util/RelayFeatureFlags.js.flow +8 -1
  66. package/util/getFragmentIdentifier.js.flow +33 -9
  67. package/util/getOperation.js.flow +40 -0
  68. package/util/isEmptyObject.js.flow +25 -0
  69. package/util/recycleNodesInto.js.flow +11 -0
  70. 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._scheduleGC();
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._scheduleGC();
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(source, subscription);
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._scheduleGC();
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 = hasOverlappingIDs(
435
- snapshot.seenRecords,
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._scheduleGC();
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
- _scheduleGC() {
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 marc records as invalidated at the current write epoch
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
- const nextRecord = RelayModernRecord.update(targetRecord, sourceRecord);
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
- 'make sure new store updates arent being executed within an ' +
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(node.selections, record, data);
172
- return data;
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
- ): void {
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(selection.selections, record, data);
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(selection.selections, record, data);
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(selection.selections, record, data);
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
- case STREAM:
269
- this._traverseSelections(selection.selections, record, data);
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
- ): void {
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
- ): void {
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
- data[applicationName] = this._traverse(field, linkedID, prevData);
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
- ): void {
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 operation = operationLoader.get(operationReference);
224
- if (operation != null) {
225
- this._traverseSelections(operation.selections, record);
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};