relay-runtime 18.2.0 → 20.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 (83) 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 +12 -8
  15. package/lib/store/OperationExecutor.js +93 -43
  16. package/lib/store/RelayModernEnvironment.js +13 -4
  17. package/lib/store/RelayModernFragmentSpecResolver.js +4 -4
  18. package/lib/store/RelayModernStore.js +49 -24
  19. package/lib/store/RelayPublishQueue.js +11 -15
  20. package/lib/store/RelayReader.js +134 -151
  21. package/lib/store/RelayReferenceMarker.js +14 -7
  22. package/lib/store/RelayResponseNormalizer.js +57 -31
  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 +4 -2
  29. package/lib/store/live-resolvers/resolverDataInjector.js +4 -4
  30. package/lib/store/normalizeResponse.js +2 -2
  31. package/lib/store/observeFragmentExperimental.js +60 -13
  32. package/lib/store/observeQueryExperimental.js +21 -0
  33. package/lib/util/RelayFeatureFlags.js +7 -1
  34. package/lib/util/handlePotentialSnapshotErrors.js +11 -8
  35. package/multi-actor-environment/ActorSpecificEnvironment.js.flow +1 -0
  36. package/mutations/RelayRecordSourceProxy.js.flow +4 -0
  37. package/mutations/createUpdatableProxy.js.flow +1 -1
  38. package/mutations/validateMutation.js.flow +3 -3
  39. package/network/RelayNetworkTypes.js.flow +3 -0
  40. package/network/RelayObservable.js.flow +1 -5
  41. package/network/wrapNetworkWithLogObserver.js.flow +19 -1
  42. package/package.json +1 -1
  43. package/query/fetchQuery.js.flow +1 -1
  44. package/store/DataChecker.js.flow +16 -4
  45. package/store/OperationExecutor.js.flow +101 -15
  46. package/store/RelayExperimentalGraphResponseTransform.js.flow +4 -4
  47. package/store/RelayModernEnvironment.js.flow +22 -6
  48. package/store/RelayModernFragmentSpecResolver.js.flow +6 -6
  49. package/store/RelayModernSelector.js.flow +2 -0
  50. package/store/RelayModernStore.js.flow +86 -27
  51. package/store/RelayPublishQueue.js.flow +32 -21
  52. package/store/RelayReader.js.flow +168 -97
  53. package/store/RelayReferenceMarker.js.flow +15 -5
  54. package/store/RelayResponseNormalizer.js.flow +104 -69
  55. package/store/RelayStoreSubscriptions.js.flow +2 -2
  56. package/store/RelayStoreTypes.js.flow +34 -4
  57. package/store/RelayStoreUtils.js.flow +29 -0
  58. package/store/ResolverCache.js.flow +2 -2
  59. package/store/ResolverFragments.js.flow +5 -3
  60. package/store/StoreInspector.js.flow +5 -0
  61. package/store/createRelayContext.js.flow +3 -2
  62. package/store/createRelayLoggingContext.js.flow +46 -0
  63. package/store/generateTypenamePrefixedDataID.js.flow +25 -0
  64. package/store/live-resolvers/LiveResolverCache.js.flow +7 -2
  65. package/store/live-resolvers/resolverDataInjector.js.flow +10 -6
  66. package/store/normalizeResponse.js.flow +2 -0
  67. package/store/observeFragmentExperimental.js.flow +82 -28
  68. package/store/observeQueryExperimental.js.flow +61 -0
  69. package/store/waitForFragmentExperimental.js.flow +4 -3
  70. package/util/NormalizationNode.js.flow +2 -1
  71. package/util/RelayConcreteNode.js.flow +2 -0
  72. package/util/RelayError.js.flow +1 -0
  73. package/util/RelayFeatureFlags.js.flow +28 -0
  74. package/util/RelayRuntimeTypes.js.flow +6 -3
  75. package/util/getPaginationVariables.js.flow +2 -0
  76. package/util/handlePotentialSnapshotErrors.js.flow +23 -11
  77. package/util/registerEnvironmentWithDevTools.js.flow +4 -2
  78. package/util/withProvidedVariables.js.flow +1 -0
  79. package/util/withStartAndDuration.js.flow +3 -0
  80. package/relay-runtime-experimental.js +0 -4
  81. package/relay-runtime-experimental.min.js +0 -9
  82. package/relay-runtime.js +0 -4
  83. 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
 
@@ -751,7 +788,7 @@ class RelayReader {
751
788
  // `getResolverValue`) and converted into an error object.
752
789
  const evaluate = (): EvaluationResult<mixed> => {
753
790
  if (fragment != null) {
754
- const key = {
791
+ const key: SelectorData = {
755
792
  __id: parentRecordID,
756
793
  __fragmentOwner: this._owner,
757
794
  __fragments: {
@@ -760,6 +797,14 @@ class RelayReader {
760
797
  : {},
761
798
  },
762
799
  };
800
+ if (
801
+ this._clientEdgeTraversalPath.length > 0 &&
802
+ this._clientEdgeTraversalPath[
803
+ this._clientEdgeTraversalPath.length - 1
804
+ ] !== null
805
+ ) {
806
+ key[CLIENT_EDGE_TRAVERSAL_PATH] = [...this._clientEdgeTraversalPath];
807
+ }
763
808
  const resolverContext = {getDataForResolverFragment};
764
809
  return withResolverContext(resolverContext, () => {
765
810
  const [resolverResult, resolverError] = getResolverValue(
@@ -824,7 +869,8 @@ class RelayReader {
824
869
  // upwards to mimic the behavior of having traversed into that fragment directly.
825
870
  if (cachedSnapshot != null) {
826
871
  if (cachedSnapshot.missingClientEdges != null) {
827
- for (const missing of cachedSnapshot.missingClientEdges) {
872
+ for (let i = 0; i < cachedSnapshot.missingClientEdges.length; i++) {
873
+ const missing = cachedSnapshot.missingClientEdges[i];
828
874
  this._missingClientEdges.push(missing);
829
875
  }
830
876
  }
@@ -833,27 +879,34 @@ class RelayReader {
833
879
  this._isMissingData ||
834
880
  cachedSnapshot.missingLiveResolverFields.length > 0;
835
881
 
836
- for (const missingResolverField of cachedSnapshot.missingLiveResolverFields) {
882
+ for (
883
+ let i = 0;
884
+ i < cachedSnapshot.missingLiveResolverFields.length;
885
+ i++
886
+ ) {
887
+ const missingResolverField =
888
+ cachedSnapshot.missingLiveResolverFields[i];
837
889
  this._missingLiveResolverFields.push(missingResolverField);
838
890
  }
839
891
  }
840
- if (cachedSnapshot.errorResponseFields != null) {
841
- if (this._errorResponseFields == null) {
842
- this._errorResponseFields = [];
892
+ if (cachedSnapshot.fieldErrors != null) {
893
+ if (this._fieldErrors == null) {
894
+ this._fieldErrors = [];
843
895
  }
844
- for (const error of cachedSnapshot.errorResponseFields) {
896
+ for (let i = 0; i < cachedSnapshot.fieldErrors.length; i++) {
897
+ const error = cachedSnapshot.fieldErrors[i];
845
898
  if (this._selector.node.metadata?.throwOnFieldError === true) {
846
899
  // If this fragment is @throwOnFieldError, any destructive error
847
900
  // encountered inside a resolver's fragment is equivilent to the
848
901
  // resolver field having a field error, and we want that to cause this
849
902
  // fragment to throw. So, we propagate all errors as is.
850
- this._errorResponseFields.push(error);
903
+ this._fieldErrors.push(error);
851
904
  } else {
852
905
  // If this fragment is _not_ @throwOnFieldError, we will simply
853
906
  // accept that any destructive errors encountered in the resolver's
854
907
  // root fragment will cause the resolver to return null, and well
855
908
  // pass the errors along to the logger marked as "handled".
856
- this._errorResponseFields.push(markFieldErrorHasHandled(error));
909
+ this._fieldErrors.push(markFieldErrorHasHandled(error));
857
910
  }
858
911
  }
859
912
  }
@@ -864,18 +917,21 @@ class RelayReader {
864
917
  // the errors can be attached to this read's snapshot. This allows the error
865
918
  // to be logged.
866
919
  if (resolverError) {
867
- const errorEvent = {
920
+ const errorEvent: FieldError = {
868
921
  kind: 'relay_resolver.error',
869
922
  fieldPath,
870
923
  owner: this._fragmentName,
871
924
  error: resolverError,
872
925
  shouldThrow: this._selector.node.metadata?.throwOnFieldError ?? false,
873
926
  handled: false,
927
+ // the uiContext is always undefined here.
928
+ // the loggingContext is provided by hooks - and assigned to uiContext in handlePotentialSnapshotErrors
929
+ uiContext: undefined,
874
930
  };
875
- if (this._errorResponseFields == null) {
876
- this._errorResponseFields = [errorEvent];
931
+ if (this._fieldErrors == null) {
932
+ this._fieldErrors = [errorEvent];
877
933
  } else {
878
- this._errorResponseFields.push(errorEvent);
934
+ this._fieldErrors.push(errorEvent);
879
935
  }
880
936
  }
881
937
 
@@ -896,9 +952,11 @@ class RelayReader {
896
952
  this._missingLiveResolverFields.push(suspenseID);
897
953
  }
898
954
  if (updatedDataIDs != null) {
899
- for (const recordID of updatedDataIDs) {
955
+ // Iterating a Set with for of is okay
956
+ // eslint-disable-next-line relay-internal/no-for-of-loops
957
+ updatedDataIDs.forEach(recordID => {
900
958
  this._updatedDataIDs.add(recordID);
901
- }
959
+ });
902
960
  }
903
961
  }
904
962
 
@@ -1068,8 +1126,8 @@ class RelayReader {
1068
1126
  RelayModernRecord.getDataID(record),
1069
1127
  prevData,
1070
1128
  );
1071
- const prevErrors = this._errorResponseFields;
1072
- this._errorResponseFields = null;
1129
+ const prevErrors = this._fieldErrors;
1130
+ this._fieldErrors = null;
1073
1131
  const edgeValue = this._traverse(
1074
1132
  field.linkedField,
1075
1133
  storeID,
@@ -1091,8 +1149,13 @@ class RelayReader {
1091
1149
  const fieldName = field.alias ?? field.name;
1092
1150
  const storageKey = getStorageKey(field, this._variables);
1093
1151
  const value = RelayModernRecord.getValue(record, storageKey);
1094
- if (value === null) {
1095
- this._maybeAddErrorResponseFields(record, storageKey);
1152
+ if (
1153
+ value === null ||
1154
+ (RelayFeatureFlags.ENABLE_NONCOMPLIANT_ERROR_HANDLING_ON_LISTS &&
1155
+ Array.isArray(value) &&
1156
+ value.length === 0)
1157
+ ) {
1158
+ this._maybeAddFieldErrors(record, storageKey);
1096
1159
  } else if (value === undefined) {
1097
1160
  this._markDataAsMissing(fieldName);
1098
1161
  }
@@ -1111,7 +1174,7 @@ class RelayReader {
1111
1174
  if (linkedID == null) {
1112
1175
  data[fieldName] = linkedID;
1113
1176
  if (linkedID === null) {
1114
- this._maybeAddErrorResponseFields(record, storageKey);
1177
+ this._maybeAddFieldErrors(record, storageKey);
1115
1178
  } else if (linkedID === undefined) {
1116
1179
  this._markDataAsMissing(fieldName);
1117
1180
  }
@@ -1128,8 +1191,8 @@ class RelayReader {
1128
1191
  RelayModernRecord.getDataID(record),
1129
1192
  prevData,
1130
1193
  );
1131
- const prevErrors = this._errorResponseFields;
1132
- this._errorResponseFields = null;
1194
+ const prevErrors = this._fieldErrors;
1195
+ this._fieldErrors = null;
1133
1196
  // $FlowFixMe[incompatible-variance]
1134
1197
  const value = this._traverse(field, linkedID, prevData);
1135
1198
 
@@ -1139,7 +1202,7 @@ class RelayReader {
1139
1202
  }
1140
1203
 
1141
1204
  /**
1142
- * Adds a set of field errors to `this._errorResponseFields`, ensuring the
1205
+ * Adds a set of field errors to `this._fieldErrors`, ensuring the
1143
1206
  * `fieldPath` property of existing field errors are prefixed with the given
1144
1207
  * `fieldNameOrIndex`.
1145
1208
  *
@@ -1158,8 +1221,8 @@ class RelayReader {
1158
1221
  * To achieve this, named field readers must do the following to correctly
1159
1222
  * track error filePaths:
1160
1223
  *
1161
- * 1. Stash the value of `this._errorResponseFields` in a local variable
1162
- * 2. Set `this._errorResponseFields` to `null`
1224
+ * 1. Stash the value of `this._fieldErrors` in a local variable
1225
+ * 2. Set `this._fieldErrors` to `null`
1163
1226
  * 3. Traverse into the field
1164
1227
  * 4. Call this method with the stashed errors and the field's name
1165
1228
  *
@@ -1171,12 +1234,12 @@ class RelayReader {
1171
1234
  * field error paths.
1172
1235
  */
1173
1236
  _prependPreviousErrors(
1174
- prevErrors: ?Array<ErrorResponseField>,
1237
+ prevErrors: ?Array<FieldError>,
1175
1238
  fieldNameOrIndex: string | number,
1176
1239
  ): void {
1177
- if (this._errorResponseFields != null) {
1178
- for (let i = 0; i < this._errorResponseFields.length; i++) {
1179
- const event = this._errorResponseFields[i];
1240
+ if (this._fieldErrors != null) {
1241
+ for (let i = 0; i < this._fieldErrors.length; i++) {
1242
+ const event = this._fieldErrors[i];
1180
1243
  if (
1181
1244
  event.owner === this._fragmentName &&
1182
1245
  (event.kind === 'missing_expected_data.throw' ||
@@ -1188,13 +1251,13 @@ class RelayReader {
1188
1251
  }
1189
1252
  }
1190
1253
  if (prevErrors != null) {
1191
- for (let i = this._errorResponseFields.length - 1; i >= 0; i--) {
1192
- prevErrors.push(this._errorResponseFields[i]);
1254
+ for (let i = this._fieldErrors.length - 1; i >= 0; i--) {
1255
+ prevErrors.push(this._fieldErrors[i]);
1193
1256
  }
1194
- this._errorResponseFields = prevErrors;
1257
+ this._fieldErrors = prevErrors;
1195
1258
  }
1196
1259
  } else {
1197
- this._errorResponseFields = prevErrors;
1260
+ this._fieldErrors = prevErrors;
1198
1261
  }
1199
1262
  }
1200
1263
 
@@ -1215,7 +1278,7 @@ class RelayReader {
1215
1278
  if (externalRef === undefined) {
1216
1279
  this._markDataAsMissing(fieldName);
1217
1280
  } else if (externalRef === null) {
1218
- this._maybeAddErrorResponseFields(record, storageKey);
1281
+ this._maybeAddFieldErrors(record, storageKey);
1219
1282
  }
1220
1283
  return data[fieldName];
1221
1284
  }
@@ -1243,8 +1306,13 @@ class RelayReader {
1243
1306
  ): ?mixed {
1244
1307
  const storageKey = getStorageKey(field, this._variables);
1245
1308
  const linkedIDs = RelayModernRecord.getLinkedRecordIDs(record, storageKey);
1246
- if (linkedIDs === null) {
1247
- this._maybeAddErrorResponseFields(record, storageKey);
1309
+ if (
1310
+ linkedIDs === null ||
1311
+ (RelayFeatureFlags.ENABLE_NONCOMPLIANT_ERROR_HANDLING_ON_LISTS &&
1312
+ Array.isArray(linkedIDs) &&
1313
+ linkedIDs.length === 0)
1314
+ ) {
1315
+ this._maybeAddFieldErrors(record, storageKey);
1248
1316
  }
1249
1317
  return this._readLinkedIds(field, linkedIDs, record, data);
1250
1318
  }
@@ -1274,8 +1342,8 @@ class RelayReader {
1274
1342
  RelayModernRecord.getDataID(record),
1275
1343
  prevData,
1276
1344
  );
1277
- const prevErrors = this._errorResponseFields;
1278
- this._errorResponseFields = null;
1345
+ const prevErrors = this._fieldErrors;
1346
+ this._fieldErrors = null;
1279
1347
  const linkedArray = prevData || [];
1280
1348
  linkedIDs.forEach((linkedID, nextIndex) => {
1281
1349
  if (linkedID == null) {
@@ -1295,8 +1363,8 @@ class RelayReader {
1295
1363
  RelayModernRecord.getDataID(record),
1296
1364
  prevItem,
1297
1365
  );
1298
- const prevErrors = this._errorResponseFields;
1299
- this._errorResponseFields = null;
1366
+ const prevErrors = this._fieldErrors;
1367
+ this._fieldErrors = null;
1300
1368
  // $FlowFixMe[cannot-write]
1301
1369
  // $FlowFixMe[incompatible-variance]
1302
1370
  linkedArray[nextIndex] = this._traverse(field, linkedID, prevItem);
@@ -1319,10 +1387,15 @@ class RelayReader {
1319
1387
  // Determine the component module from the store: if the field is missing
1320
1388
  // it means we don't know what component to render the match with.
1321
1389
  const componentKey = getModuleComponentKey(moduleImport.documentName);
1390
+ const relayStoreComponent = RelayModernRecord.getValue(
1391
+ record,
1392
+ componentKey,
1393
+ );
1322
1394
  // componentModuleProvider is used by Client 3D for read time resolvers.
1323
1395
  const component =
1324
- moduleImport.componentModuleProvider ??
1325
- RelayModernRecord.getValue(record, componentKey);
1396
+ relayStoreComponent !== undefined
1397
+ ? relayStoreComponent
1398
+ : moduleImport.componentModuleProvider;
1326
1399
  if (component == null) {
1327
1400
  if (component === undefined) {
1328
1401
  this._markDataAsMissing('<module-import>');
@@ -1366,8 +1439,8 @@ class RelayReader {
1366
1439
  record: Record,
1367
1440
  data: SelectorData,
1368
1441
  ) {
1369
- const prevErrors = this._errorResponseFields;
1370
- this._errorResponseFields = null;
1442
+ const prevErrors = this._fieldErrors;
1443
+ this._fieldErrors = null;
1371
1444
  let fieldValue = this._readInlineFragment(
1372
1445
  aliasedInlineFragment.fragment,
1373
1446
  record,
@@ -1597,9 +1670,7 @@ class RelayReader {
1597
1670
  }
1598
1671
  }
1599
1672
 
1600
- function markFieldErrorHasHandled(
1601
- event: ErrorResponseField,
1602
- ): ErrorResponseField {
1673
+ function markFieldErrorHasHandled(event: FieldError): FieldError {
1603
1674
  switch (event.kind) {
1604
1675
  case 'missing_expected_data.throw':
1605
1676
  case 'missing_required_field.throw':