relay-runtime 18.2.0 → 19.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.
Files changed (81) hide show
  1. package/experimental.js +1 -1
  2. package/experimental.js.flow +8 -6
  3. package/index.js +1 -1
  4. package/index.js.flow +3 -0
  5. package/lib/experimental.js +5 -2
  6. package/lib/index.js +3 -0
  7. package/lib/multi-actor-environment/ActorSpecificEnvironment.js +1 -1
  8. package/lib/mutations/RelayRecordSourceProxy.js +2 -1
  9. package/lib/mutations/createUpdatableProxy.js +1 -1
  10. package/lib/mutations/validateMutation.js +2 -2
  11. package/lib/network/RelayObservable.js +1 -3
  12. package/lib/network/wrapNetworkWithLogObserver.js +2 -2
  13. package/lib/query/fetchQuery.js +1 -1
  14. package/lib/store/DataChecker.js +4 -5
  15. package/lib/store/OperationExecutor.js +11 -0
  16. package/lib/store/RelayModernEnvironment.js +13 -4
  17. package/lib/store/RelayModernFragmentSpecResolver.js +4 -4
  18. package/lib/store/RelayModernStore.js +43 -21
  19. package/lib/store/RelayPublishQueue.js +11 -15
  20. package/lib/store/RelayReader.js +131 -151
  21. package/lib/store/RelayReferenceMarker.js +3 -4
  22. package/lib/store/RelayResponseNormalizer.js +47 -26
  23. package/lib/store/RelayStoreSubscriptions.js +2 -2
  24. package/lib/store/RelayStoreUtils.js +8 -0
  25. package/lib/store/ResolverFragments.js +2 -2
  26. package/lib/store/createRelayLoggingContext.js +17 -0
  27. package/lib/store/generateTypenamePrefixedDataID.js +9 -0
  28. package/lib/store/live-resolvers/LiveResolverCache.js +2 -1
  29. package/lib/store/live-resolvers/resolverDataInjector.js +4 -4
  30. package/lib/store/observeFragmentExperimental.js +60 -13
  31. package/lib/store/observeQueryExperimental.js +21 -0
  32. package/lib/util/RelayFeatureFlags.js +6 -1
  33. package/lib/util/handlePotentialSnapshotErrors.js +11 -8
  34. package/multi-actor-environment/ActorSpecificEnvironment.js.flow +1 -0
  35. package/mutations/RelayRecordSourceProxy.js.flow +4 -0
  36. package/mutations/createUpdatableProxy.js.flow +1 -1
  37. package/mutations/validateMutation.js.flow +3 -3
  38. package/network/RelayNetworkTypes.js.flow +3 -0
  39. package/network/RelayObservable.js.flow +1 -5
  40. package/network/wrapNetworkWithLogObserver.js.flow +19 -1
  41. package/package.json +1 -1
  42. package/query/fetchQuery.js.flow +1 -1
  43. package/store/DataChecker.js.flow +5 -2
  44. package/store/OperationExecutor.js.flow +12 -1
  45. package/store/RelayExperimentalGraphResponseTransform.js.flow +4 -4
  46. package/store/RelayModernEnvironment.js.flow +22 -6
  47. package/store/RelayModernFragmentSpecResolver.js.flow +6 -6
  48. package/store/RelayModernSelector.js.flow +2 -0
  49. package/store/RelayModernStore.js.flow +74 -27
  50. package/store/RelayPublishQueue.js.flow +32 -21
  51. package/store/RelayReader.js.flow +159 -96
  52. package/store/RelayReferenceMarker.js.flow +3 -4
  53. package/store/RelayResponseNormalizer.js.flow +93 -67
  54. package/store/RelayStoreSubscriptions.js.flow +2 -2
  55. package/store/RelayStoreTypes.js.flow +33 -4
  56. package/store/RelayStoreUtils.js.flow +29 -0
  57. package/store/ResolverCache.js.flow +2 -2
  58. package/store/ResolverFragments.js.flow +5 -3
  59. package/store/StoreInspector.js.flow +5 -0
  60. package/store/createRelayContext.js.flow +3 -2
  61. package/store/createRelayLoggingContext.js.flow +46 -0
  62. package/store/generateTypenamePrefixedDataID.js.flow +25 -0
  63. package/store/live-resolvers/LiveResolverCache.js.flow +2 -1
  64. package/store/live-resolvers/resolverDataInjector.js.flow +10 -6
  65. package/store/observeFragmentExperimental.js.flow +82 -28
  66. package/store/observeQueryExperimental.js.flow +61 -0
  67. package/store/waitForFragmentExperimental.js.flow +4 -3
  68. package/util/NormalizationNode.js.flow +2 -1
  69. package/util/RelayConcreteNode.js.flow +2 -0
  70. package/util/RelayError.js.flow +1 -0
  71. package/util/RelayFeatureFlags.js.flow +18 -0
  72. package/util/RelayRuntimeTypes.js.flow +6 -3
  73. package/util/getPaginationVariables.js.flow +2 -0
  74. package/util/handlePotentialSnapshotErrors.js.flow +23 -11
  75. package/util/registerEnvironmentWithDevTools.js.flow +4 -2
  76. package/util/withProvidedVariables.js.flow +1 -0
  77. package/util/withStartAndDuration.js.flow +3 -0
  78. package/relay-runtime-experimental.js +0 -4
  79. package/relay-runtime-experimental.min.js +0 -9
  80. package/relay-runtime.js +0 -4
  81. package/relay-runtime.min.js +0 -9
@@ -35,8 +35,8 @@ import type {DataID, Variables} from '../util/RelayRuntimeTypes';
35
35
  import type {
36
36
  ClientEdgeTraversalInfo,
37
37
  DataIDSet,
38
- ErrorResponseField,
39
- ErrorResponseFields,
38
+ FieldError,
39
+ FieldErrors,
40
40
  MissingClientEdgeRequestInfo,
41
41
  Record,
42
42
  RecordSource,
@@ -49,6 +49,7 @@ import type {
49
49
  import type {Arguments} from './RelayStoreUtils';
50
50
  import type {EvaluationResult, ResolverCache} from './ResolverCache';
51
51
 
52
+ const RelayFeatureFlags = require('../util/RelayFeatureFlags');
52
53
  const {
53
54
  isSuspenseSentinel,
54
55
  } = require('./live-resolvers/LiveResolverSuspenseSentinel');
@@ -98,7 +99,7 @@ class RelayReader {
98
99
  _missingClientEdges: Array<MissingClientEdgeRequestInfo>;
99
100
  _missingLiveResolverFields: Array<DataID>;
100
101
  _isWithinUnmatchedTypeRefinement: boolean;
101
- _errorResponseFields: ?ErrorResponseFields;
102
+ _fieldErrors: ?FieldErrors;
102
103
  _owner: RequestDescriptor;
103
104
  // Exec time resolvers are run before reaching the Relay store so the store already contains
104
105
  // the normalized data; the same as if the data were sent from the server. However, since a
@@ -128,10 +129,13 @@ class RelayReader {
128
129
  this._missingLiveResolverFields = [];
129
130
  this._isMissingData = false;
130
131
  this._isWithinUnmatchedTypeRefinement = false;
131
- this._errorResponseFields = null;
132
+ this._fieldErrors = null;
132
133
  this._owner = selector.owner;
133
134
  this._useExecTimeResolvers =
134
- this._owner.node.operation.use_exec_time_resolvers ?? false;
135
+ this._owner.node.operation.use_exec_time_resolvers ??
136
+ this._owner.node.operation.exec_time_resolvers_enabled_provider?.get() ===
137
+ true ??
138
+ false;
135
139
  this._recordSource = recordSource;
136
140
  this._seenRecords = new Set();
137
141
  this._selector = selector;
@@ -205,11 +209,11 @@ class RelayReader {
205
209
  missingLiveResolverFields: this._missingLiveResolverFields,
206
210
  seenRecords: this._seenRecords,
207
211
  selector: this._selector,
208
- errorResponseFields: this._errorResponseFields,
212
+ fieldErrors: this._fieldErrors,
209
213
  };
210
214
  }
211
215
 
212
- _maybeAddErrorResponseFields(record: Record, storageKey: string): void {
216
+ _maybeAddFieldErrors(record: Record, storageKey: string): void {
213
217
  const errors = RelayModernRecord.getErrors(record, storageKey);
214
218
 
215
219
  if (errors == null) {
@@ -217,17 +221,21 @@ class RelayReader {
217
221
  }
218
222
  const owner = this._fragmentName;
219
223
 
220
- if (this._errorResponseFields == null) {
221
- this._errorResponseFields = [];
224
+ if (this._fieldErrors == null) {
225
+ this._fieldErrors = [];
222
226
  }
223
- for (const error of errors) {
224
- this._errorResponseFields.push({
227
+ for (let i = 0; i < errors.length; i++) {
228
+ const error = errors[i];
229
+ this._fieldErrors.push({
225
230
  kind: 'relay_field_payload.error',
226
231
  owner,
227
232
  fieldPath: (error.path ?? []).join('.'),
228
233
  error,
229
234
  shouldThrow: this._selector.node.metadata?.throwOnFieldError ?? false,
230
235
  handled: false,
236
+ // the uiContext is always undefined here.
237
+ // the loggingContext is provided by hooks - and assigned to uiContext in handlePotentialSnapshotErrors
238
+ uiContext: undefined,
231
239
  });
232
240
  }
233
241
  }
@@ -236,22 +244,32 @@ class RelayReader {
236
244
  if (this._isWithinUnmatchedTypeRefinement) {
237
245
  return;
238
246
  }
239
- if (this._errorResponseFields == null) {
240
- this._errorResponseFields = [];
247
+ if (this._fieldErrors == null) {
248
+ this._fieldErrors = [];
241
249
  }
242
250
 
243
251
  // we will add the path later
244
252
  const owner = this._fragmentName;
245
253
 
246
- this._errorResponseFields.push(
254
+ this._fieldErrors.push(
247
255
  this._selector.node.metadata?.throwOnFieldError ?? false
248
256
  ? {
249
257
  kind: 'missing_expected_data.throw',
250
258
  owner,
251
259
  fieldPath: fieldName,
252
260
  handled: false,
261
+ // the uiContext is always undefined here.
262
+ // the loggingContext is provided by hooks - and assigned to uiContext in handlePotentialSnapshotErrors
263
+ uiContext: undefined,
253
264
  }
254
- : {kind: 'missing_expected_data.log', owner, fieldPath: fieldName},
265
+ : {
266
+ kind: 'missing_expected_data.log',
267
+ owner,
268
+ fieldPath: fieldName,
269
+ // the uiContext is always undefined here.
270
+ // the loggingContext is provided by hooks - and assigned to uiContext in handlePotentialSnapshotErrors
271
+ uiContext: undefined,
272
+ },
255
273
  );
256
274
 
257
275
  this._isMissingData = true;
@@ -282,6 +300,7 @@ class RelayReader {
282
300
  if (record === undefined) {
283
301
  this._markDataAsMissing('<record>');
284
302
  }
303
+ // $FlowFixMe[incompatible-return]
285
304
  return record;
286
305
  }
287
306
  const data = prevData || {};
@@ -308,8 +327,8 @@ class RelayReader {
308
327
  }
309
328
  const owner = this._fragmentName;
310
329
 
311
- if (this._errorResponseFields == null) {
312
- this._errorResponseFields = [];
330
+ if (this._fieldErrors == null) {
331
+ this._fieldErrors = [];
313
332
  }
314
333
 
315
334
  let fieldName: string;
@@ -322,18 +341,24 @@ class RelayReader {
322
341
 
323
342
  switch (selection.action) {
324
343
  case 'THROW':
325
- this._errorResponseFields.push({
344
+ this._fieldErrors.push({
326
345
  kind: 'missing_required_field.throw',
327
346
  fieldPath: fieldName,
328
347
  owner,
329
348
  handled: false,
349
+ // the uiContext is always undefined here.
350
+ // the loggingContext is provided by hooks - and assigned to uiContext in handlePotentialSnapshotErrors
351
+ uiContext: undefined,
330
352
  });
331
353
  return;
332
354
  case 'LOG':
333
- this._errorResponseFields.push({
355
+ this._fieldErrors.push({
334
356
  kind: 'missing_required_field.log',
335
357
  fieldPath: fieldName,
336
358
  owner,
359
+ // the uiContext is always undefined here.
360
+ // the loggingContext is provided by hooks - and assigned to uiContext in handlePotentialSnapshotErrors
361
+ uiContext: undefined,
337
362
  });
338
363
  return;
339
364
  default:
@@ -361,7 +386,7 @@ class RelayReader {
361
386
  * any fields within them.
362
387
  *
363
388
  * 1. Before traversing into the selection(s) marked as `@catch`, the caller
364
- * stores the previous field errors (`this._errorResponseFields`) in a
389
+ * stores the previous field errors (`this._fieldErrors`) in a
365
390
  * variable.
366
391
  * 2. After traversing into the selection(s) marked as `@catch`, the caller
367
392
  * calls this method with the resulting value, the `to` value from the
@@ -376,7 +401,7 @@ class RelayReader {
376
401
  _catchErrors<T>(
377
402
  _value: T,
378
403
  to: CatchFieldTo,
379
- previousResponseFields: ?ErrorResponseFields,
404
+ previousResponseFields: ?FieldErrors,
380
405
  ): ?T | Result<T, mixed> {
381
406
  let value: T | null | Result<T, mixed> = _value;
382
407
  switch (to) {
@@ -384,10 +409,7 @@ class RelayReader {
384
409
  value = this._asResult(_value);
385
410
  break;
386
411
  case 'NULL':
387
- if (
388
- this._errorResponseFields != null &&
389
- this._errorResponseFields.length > 0
390
- ) {
412
+ if (this._fieldErrors != null && this._fieldErrors.length > 0) {
391
413
  value = null;
392
414
  }
393
415
  break;
@@ -395,22 +417,22 @@ class RelayReader {
395
417
  (to: empty);
396
418
  }
397
419
 
398
- const childrenErrorResponseFields = this._errorResponseFields;
420
+ const childrenFieldErrors = this._fieldErrors;
399
421
 
400
- this._errorResponseFields = previousResponseFields;
422
+ this._fieldErrors = previousResponseFields;
401
423
 
402
424
  // Merge any errors encountered within the @catch with the previous field
403
425
  // errors, but mark them as "handled" first.
404
- if (childrenErrorResponseFields != null) {
405
- if (this._errorResponseFields == null) {
406
- this._errorResponseFields = [];
426
+ if (childrenFieldErrors != null) {
427
+ if (this._fieldErrors == null) {
428
+ this._fieldErrors = [];
407
429
  }
408
- for (let i = 0; i < childrenErrorResponseFields.length; i++) {
430
+ for (let i = 0; i < childrenFieldErrors.length; i++) {
409
431
  // We mark any errors encountered within the @catch as "handled"
410
432
  // to ensure that they don't cause the reader to throw, but can
411
433
  // still be logged.
412
- this._errorResponseFields.push(
413
- markFieldErrorHasHandled(childrenErrorResponseFields[i]),
434
+ this._fieldErrors.push(
435
+ markFieldErrorHasHandled(childrenFieldErrors[i]),
414
436
  );
415
437
  }
416
438
  }
@@ -419,21 +441,18 @@ class RelayReader {
419
441
 
420
442
  /**
421
443
  * Convert a value into a Result object based on the presence of errors in the
422
- * `this._errorResponseFields` array.
444
+ * `this._fieldErrors` array.
423
445
  *
424
446
  * **Note**: This method does _not_ mark errors as handled. It is the caller's
425
447
  * responsibility to ensure that errors are marked as handled.
426
448
  */
427
449
  _asResult<T>(value: T): Result<T, mixed> {
428
- if (
429
- this._errorResponseFields == null ||
430
- this._errorResponseFields.length === 0
431
- ) {
450
+ if (this._fieldErrors == null || this._fieldErrors.length === 0) {
432
451
  return {ok: true, value};
433
452
  }
434
453
 
435
454
  // TODO: Should we be hiding log level events here?
436
- const errors = this._errorResponseFields
455
+ const errors = this._fieldErrors
437
456
  .map(error => {
438
457
  switch (error.kind) {
439
458
  case 'relay_field_payload.error':
@@ -461,7 +480,7 @@ class RelayReader {
461
480
  (error.kind: empty);
462
481
  invariant(
463
482
  false,
464
- 'Unexpected error errorResponseField kind: %s',
483
+ 'Unexpected error fieldError kind: %s',
465
484
  error.kind,
466
485
  );
467
486
  }
@@ -491,9 +510,9 @@ class RelayReader {
491
510
  }
492
511
  break;
493
512
  case 'CatchField': {
494
- const previousResponseFields = this._errorResponseFields;
513
+ const previousResponseFields = this._fieldErrors;
495
514
 
496
- this._errorResponseFields = null;
515
+ this._fieldErrors = null;
497
516
 
498
517
  const catchFieldValue = this._readClientSideDirectiveField(
499
518
  selection,
@@ -662,13 +681,31 @@ class RelayReader {
662
681
  } else {
663
682
  return this._readLink(selection.field, record, data);
664
683
  }
684
+
665
685
  case 'RelayResolver':
666
- return this._readResolverField(selection.field, record, data);
667
- case 'RelayLiveResolver':
668
- return this._readResolverField(selection.field, record, data);
686
+ case 'RelayLiveResolver': {
687
+ if (this._useExecTimeResolvers) {
688
+ return this._readScalar(selection.field, record, data);
689
+ } else {
690
+ return this._readResolverField(selection.field, record, data);
691
+ }
692
+ }
669
693
  case 'ClientEdgeToClientObject':
670
694
  case 'ClientEdgeToServerObject':
671
- return this._readClientEdge(selection.field, record, data);
695
+ if (
696
+ this._useExecTimeResolvers &&
697
+ (selection.field.backingField.kind === 'RelayResolver' ||
698
+ selection.field.backingField.kind === 'RelayLiveResolver')
699
+ ) {
700
+ const {field} = selection;
701
+ if (field.linkedField.plural) {
702
+ return this._readPluralLink(field.linkedField, record, data);
703
+ } else {
704
+ return this._readLink(field.linkedField, record, data);
705
+ }
706
+ } else {
707
+ return this._readClientEdge(selection.field, record, data);
708
+ }
672
709
  case 'AliasedInlineFragmentSpread':
673
710
  return this._readAliasedInlineFragment(selection.field, record, data);
674
711
  default:
@@ -687,8 +724,8 @@ class RelayReader {
687
724
  data: SelectorData,
688
725
  ): mixed {
689
726
  const parentRecordID = RelayModernRecord.getDataID(record);
690
- const prevErrors = this._errorResponseFields;
691
- this._errorResponseFields = null;
727
+ const prevErrors = this._fieldErrors;
728
+ this._fieldErrors = null;
692
729
  const result = this._readResolverFieldImpl(field, parentRecordID);
693
730
 
694
731
  const fieldName = field.alias ?? field.name;
@@ -726,7 +763,7 @@ class RelayReader {
726
763
  return {
727
764
  data: snapshot.data,
728
765
  isMissingData: snapshot.isMissingData,
729
- errorResponseFields: snapshot.errorResponseFields,
766
+ fieldErrors: snapshot.fieldErrors,
730
767
  };
731
768
  }
732
769
 
@@ -739,7 +776,7 @@ class RelayReader {
739
776
  return {
740
777
  data: snapshot.data,
741
778
  isMissingData: snapshot.isMissingData,
742
- errorResponseFields: snapshot.errorResponseFields,
779
+ fieldErrors: snapshot.fieldErrors,
743
780
  };
744
781
  };
745
782
 
@@ -824,7 +861,8 @@ class RelayReader {
824
861
  // upwards to mimic the behavior of having traversed into that fragment directly.
825
862
  if (cachedSnapshot != null) {
826
863
  if (cachedSnapshot.missingClientEdges != null) {
827
- for (const missing of cachedSnapshot.missingClientEdges) {
864
+ for (let i = 0; i < cachedSnapshot.missingClientEdges.length; i++) {
865
+ const missing = cachedSnapshot.missingClientEdges[i];
828
866
  this._missingClientEdges.push(missing);
829
867
  }
830
868
  }
@@ -833,27 +871,34 @@ class RelayReader {
833
871
  this._isMissingData ||
834
872
  cachedSnapshot.missingLiveResolverFields.length > 0;
835
873
 
836
- for (const missingResolverField of cachedSnapshot.missingLiveResolverFields) {
874
+ for (
875
+ let i = 0;
876
+ i < cachedSnapshot.missingLiveResolverFields.length;
877
+ i++
878
+ ) {
879
+ const missingResolverField =
880
+ cachedSnapshot.missingLiveResolverFields[i];
837
881
  this._missingLiveResolverFields.push(missingResolverField);
838
882
  }
839
883
  }
840
- if (cachedSnapshot.errorResponseFields != null) {
841
- if (this._errorResponseFields == null) {
842
- this._errorResponseFields = [];
884
+ if (cachedSnapshot.fieldErrors != null) {
885
+ if (this._fieldErrors == null) {
886
+ this._fieldErrors = [];
843
887
  }
844
- for (const error of cachedSnapshot.errorResponseFields) {
888
+ for (let i = 0; i < cachedSnapshot.fieldErrors.length; i++) {
889
+ const error = cachedSnapshot.fieldErrors[i];
845
890
  if (this._selector.node.metadata?.throwOnFieldError === true) {
846
891
  // If this fragment is @throwOnFieldError, any destructive error
847
892
  // encountered inside a resolver's fragment is equivilent to the
848
893
  // resolver field having a field error, and we want that to cause this
849
894
  // fragment to throw. So, we propagate all errors as is.
850
- this._errorResponseFields.push(error);
895
+ this._fieldErrors.push(error);
851
896
  } else {
852
897
  // If this fragment is _not_ @throwOnFieldError, we will simply
853
898
  // accept that any destructive errors encountered in the resolver's
854
899
  // root fragment will cause the resolver to return null, and well
855
900
  // pass the errors along to the logger marked as "handled".
856
- this._errorResponseFields.push(markFieldErrorHasHandled(error));
901
+ this._fieldErrors.push(markFieldErrorHasHandled(error));
857
902
  }
858
903
  }
859
904
  }
@@ -864,18 +909,21 @@ class RelayReader {
864
909
  // the errors can be attached to this read's snapshot. This allows the error
865
910
  // to be logged.
866
911
  if (resolverError) {
867
- const errorEvent = {
912
+ const errorEvent: FieldError = {
868
913
  kind: 'relay_resolver.error',
869
914
  fieldPath,
870
915
  owner: this._fragmentName,
871
916
  error: resolverError,
872
917
  shouldThrow: this._selector.node.metadata?.throwOnFieldError ?? false,
873
918
  handled: false,
919
+ // the uiContext is always undefined here.
920
+ // the loggingContext is provided by hooks - and assigned to uiContext in handlePotentialSnapshotErrors
921
+ uiContext: undefined,
874
922
  };
875
- if (this._errorResponseFields == null) {
876
- this._errorResponseFields = [errorEvent];
923
+ if (this._fieldErrors == null) {
924
+ this._fieldErrors = [errorEvent];
877
925
  } else {
878
- this._errorResponseFields.push(errorEvent);
926
+ this._fieldErrors.push(errorEvent);
879
927
  }
880
928
  }
881
929
 
@@ -896,9 +944,11 @@ class RelayReader {
896
944
  this._missingLiveResolverFields.push(suspenseID);
897
945
  }
898
946
  if (updatedDataIDs != null) {
899
- for (const recordID of updatedDataIDs) {
947
+ // Iterating a Set with for of is okay
948
+ // eslint-disable-next-line relay-internal/no-for-of-loops
949
+ updatedDataIDs.forEach(recordID => {
900
950
  this._updatedDataIDs.add(recordID);
901
- }
951
+ });
902
952
  }
903
953
  }
904
954
 
@@ -1068,8 +1118,8 @@ class RelayReader {
1068
1118
  RelayModernRecord.getDataID(record),
1069
1119
  prevData,
1070
1120
  );
1071
- const prevErrors = this._errorResponseFields;
1072
- this._errorResponseFields = null;
1121
+ const prevErrors = this._fieldErrors;
1122
+ this._fieldErrors = null;
1073
1123
  const edgeValue = this._traverse(
1074
1124
  field.linkedField,
1075
1125
  storeID,
@@ -1091,8 +1141,13 @@ class RelayReader {
1091
1141
  const fieldName = field.alias ?? field.name;
1092
1142
  const storageKey = getStorageKey(field, this._variables);
1093
1143
  const value = RelayModernRecord.getValue(record, storageKey);
1094
- if (value === null) {
1095
- this._maybeAddErrorResponseFields(record, storageKey);
1144
+ if (
1145
+ value === null ||
1146
+ (RelayFeatureFlags.ENABLE_NONCOMPLIANT_ERROR_HANDLING_ON_LISTS &&
1147
+ Array.isArray(value) &&
1148
+ value.length === 0)
1149
+ ) {
1150
+ this._maybeAddFieldErrors(record, storageKey);
1096
1151
  } else if (value === undefined) {
1097
1152
  this._markDataAsMissing(fieldName);
1098
1153
  }
@@ -1111,7 +1166,7 @@ class RelayReader {
1111
1166
  if (linkedID == null) {
1112
1167
  data[fieldName] = linkedID;
1113
1168
  if (linkedID === null) {
1114
- this._maybeAddErrorResponseFields(record, storageKey);
1169
+ this._maybeAddFieldErrors(record, storageKey);
1115
1170
  } else if (linkedID === undefined) {
1116
1171
  this._markDataAsMissing(fieldName);
1117
1172
  }
@@ -1128,8 +1183,8 @@ class RelayReader {
1128
1183
  RelayModernRecord.getDataID(record),
1129
1184
  prevData,
1130
1185
  );
1131
- const prevErrors = this._errorResponseFields;
1132
- this._errorResponseFields = null;
1186
+ const prevErrors = this._fieldErrors;
1187
+ this._fieldErrors = null;
1133
1188
  // $FlowFixMe[incompatible-variance]
1134
1189
  const value = this._traverse(field, linkedID, prevData);
1135
1190
 
@@ -1139,7 +1194,7 @@ class RelayReader {
1139
1194
  }
1140
1195
 
1141
1196
  /**
1142
- * Adds a set of field errors to `this._errorResponseFields`, ensuring the
1197
+ * Adds a set of field errors to `this._fieldErrors`, ensuring the
1143
1198
  * `fieldPath` property of existing field errors are prefixed with the given
1144
1199
  * `fieldNameOrIndex`.
1145
1200
  *
@@ -1158,8 +1213,8 @@ class RelayReader {
1158
1213
  * To achieve this, named field readers must do the following to correctly
1159
1214
  * track error filePaths:
1160
1215
  *
1161
- * 1. Stash the value of `this._errorResponseFields` in a local variable
1162
- * 2. Set `this._errorResponseFields` to `null`
1216
+ * 1. Stash the value of `this._fieldErrors` in a local variable
1217
+ * 2. Set `this._fieldErrors` to `null`
1163
1218
  * 3. Traverse into the field
1164
1219
  * 4. Call this method with the stashed errors and the field's name
1165
1220
  *
@@ -1171,12 +1226,12 @@ class RelayReader {
1171
1226
  * field error paths.
1172
1227
  */
1173
1228
  _prependPreviousErrors(
1174
- prevErrors: ?Array<ErrorResponseField>,
1229
+ prevErrors: ?Array<FieldError>,
1175
1230
  fieldNameOrIndex: string | number,
1176
1231
  ): void {
1177
- if (this._errorResponseFields != null) {
1178
- for (let i = 0; i < this._errorResponseFields.length; i++) {
1179
- const event = this._errorResponseFields[i];
1232
+ if (this._fieldErrors != null) {
1233
+ for (let i = 0; i < this._fieldErrors.length; i++) {
1234
+ const event = this._fieldErrors[i];
1180
1235
  if (
1181
1236
  event.owner === this._fragmentName &&
1182
1237
  (event.kind === 'missing_expected_data.throw' ||
@@ -1188,13 +1243,13 @@ class RelayReader {
1188
1243
  }
1189
1244
  }
1190
1245
  if (prevErrors != null) {
1191
- for (let i = this._errorResponseFields.length - 1; i >= 0; i--) {
1192
- prevErrors.push(this._errorResponseFields[i]);
1246
+ for (let i = this._fieldErrors.length - 1; i >= 0; i--) {
1247
+ prevErrors.push(this._fieldErrors[i]);
1193
1248
  }
1194
- this._errorResponseFields = prevErrors;
1249
+ this._fieldErrors = prevErrors;
1195
1250
  }
1196
1251
  } else {
1197
- this._errorResponseFields = prevErrors;
1252
+ this._fieldErrors = prevErrors;
1198
1253
  }
1199
1254
  }
1200
1255
 
@@ -1215,7 +1270,7 @@ class RelayReader {
1215
1270
  if (externalRef === undefined) {
1216
1271
  this._markDataAsMissing(fieldName);
1217
1272
  } else if (externalRef === null) {
1218
- this._maybeAddErrorResponseFields(record, storageKey);
1273
+ this._maybeAddFieldErrors(record, storageKey);
1219
1274
  }
1220
1275
  return data[fieldName];
1221
1276
  }
@@ -1243,8 +1298,13 @@ class RelayReader {
1243
1298
  ): ?mixed {
1244
1299
  const storageKey = getStorageKey(field, this._variables);
1245
1300
  const linkedIDs = RelayModernRecord.getLinkedRecordIDs(record, storageKey);
1246
- if (linkedIDs === null) {
1247
- this._maybeAddErrorResponseFields(record, storageKey);
1301
+ if (
1302
+ linkedIDs === null ||
1303
+ (RelayFeatureFlags.ENABLE_NONCOMPLIANT_ERROR_HANDLING_ON_LISTS &&
1304
+ Array.isArray(linkedIDs) &&
1305
+ linkedIDs.length === 0)
1306
+ ) {
1307
+ this._maybeAddFieldErrors(record, storageKey);
1248
1308
  }
1249
1309
  return this._readLinkedIds(field, linkedIDs, record, data);
1250
1310
  }
@@ -1274,8 +1334,8 @@ class RelayReader {
1274
1334
  RelayModernRecord.getDataID(record),
1275
1335
  prevData,
1276
1336
  );
1277
- const prevErrors = this._errorResponseFields;
1278
- this._errorResponseFields = null;
1337
+ const prevErrors = this._fieldErrors;
1338
+ this._fieldErrors = null;
1279
1339
  const linkedArray = prevData || [];
1280
1340
  linkedIDs.forEach((linkedID, nextIndex) => {
1281
1341
  if (linkedID == null) {
@@ -1295,8 +1355,8 @@ class RelayReader {
1295
1355
  RelayModernRecord.getDataID(record),
1296
1356
  prevItem,
1297
1357
  );
1298
- const prevErrors = this._errorResponseFields;
1299
- this._errorResponseFields = null;
1358
+ const prevErrors = this._fieldErrors;
1359
+ this._fieldErrors = null;
1300
1360
  // $FlowFixMe[cannot-write]
1301
1361
  // $FlowFixMe[incompatible-variance]
1302
1362
  linkedArray[nextIndex] = this._traverse(field, linkedID, prevItem);
@@ -1319,10 +1379,15 @@ class RelayReader {
1319
1379
  // Determine the component module from the store: if the field is missing
1320
1380
  // it means we don't know what component to render the match with.
1321
1381
  const componentKey = getModuleComponentKey(moduleImport.documentName);
1382
+ const relayStoreComponent = RelayModernRecord.getValue(
1383
+ record,
1384
+ componentKey,
1385
+ );
1322
1386
  // componentModuleProvider is used by Client 3D for read time resolvers.
1323
1387
  const component =
1324
- moduleImport.componentModuleProvider ??
1325
- RelayModernRecord.getValue(record, componentKey);
1388
+ relayStoreComponent !== undefined
1389
+ ? relayStoreComponent
1390
+ : moduleImport.componentModuleProvider;
1326
1391
  if (component == null) {
1327
1392
  if (component === undefined) {
1328
1393
  this._markDataAsMissing('<module-import>');
@@ -1366,8 +1431,8 @@ class RelayReader {
1366
1431
  record: Record,
1367
1432
  data: SelectorData,
1368
1433
  ) {
1369
- const prevErrors = this._errorResponseFields;
1370
- this._errorResponseFields = null;
1434
+ const prevErrors = this._fieldErrors;
1435
+ this._fieldErrors = null;
1371
1436
  let fieldValue = this._readInlineFragment(
1372
1437
  aliasedInlineFragment.fragment,
1373
1438
  record,
@@ -1597,9 +1662,7 @@ class RelayReader {
1597
1662
  }
1598
1663
  }
1599
1664
 
1600
- function markFieldErrorHasHandled(
1601
- event: ErrorResponseField,
1602
- ): ErrorResponseField {
1665
+ function markFieldErrorHasHandled(event: FieldError): FieldError {
1603
1666
  switch (event.kind) {
1604
1667
  case 'missing_expected_data.throw':
1605
1668
  case 'missing_required_field.throw':
@@ -38,7 +38,8 @@ const RelayStoreUtils = require('./RelayStoreUtils');
38
38
  const {generateTypeID} = require('./TypeID');
39
39
  const invariant = require('invariant');
40
40
 
41
- const {getStorageKey, getModuleOperationKey} = RelayStoreUtils;
41
+ const {getReadTimeResolverStorageKey, getStorageKey, getModuleOperationKey} =
42
+ RelayStoreUtils;
42
43
 
43
44
  function mark(
44
45
  recordSource: RecordSource,
@@ -216,8 +217,6 @@ class RelayReferenceMarker {
216
217
  this._traverseSelections(selection.fragment.selections, record);
217
218
  break;
218
219
  case 'RelayResolver':
219
- this._traverseResolverField(selection, record);
220
- break;
221
220
  case 'RelayLiveResolver':
222
221
  this._traverseResolverField(selection, record);
223
222
  break;
@@ -291,7 +290,7 @@ class RelayReferenceMarker {
291
290
  field: NormalizationResolverField | NormalizationLiveResolverField,
292
291
  record: Record,
293
292
  ): ?DataID {
294
- const storageKey = getStorageKey(field, this._variables);
293
+ const storageKey = getReadTimeResolverStorageKey(field, this._variables);
295
294
  const dataID = RelayModernRecord.getLinkedRecordID(record, storageKey);
296
295
 
297
296
  // If the resolver value has been created, we should retain it.