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
@@ -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,
@@ -46,6 +47,7 @@ function mark(
46
47
  references: DataIDSet,
47
48
  operationLoader: ?OperationLoader,
48
49
  shouldProcessClientComponents: ?boolean,
50
+ useExecTimeResolvers: ?boolean,
49
51
  ): void {
50
52
  const {dataID, node, variables} = selector;
51
53
  const marker = new RelayReferenceMarker(
@@ -54,6 +56,7 @@ function mark(
54
56
  references,
55
57
  operationLoader,
56
58
  shouldProcessClientComponents,
59
+ useExecTimeResolvers,
57
60
  );
58
61
  marker.mark(node, dataID);
59
62
  }
@@ -67,6 +70,7 @@ class RelayReferenceMarker {
67
70
  _recordSource: RecordSource;
68
71
  _references: DataIDSet;
69
72
  _variables: Variables;
73
+ _useExecTimeResolvers: boolean;
70
74
  _shouldProcessClientComponents: ?boolean;
71
75
 
72
76
  constructor(
@@ -75,9 +79,11 @@ class RelayReferenceMarker {
75
79
  references: DataIDSet,
76
80
  operationLoader: ?OperationLoader,
77
81
  shouldProcessClientComponents: ?boolean,
82
+ useExecTimeResolvers: ?boolean,
78
83
  ) {
79
84
  this._operationLoader = operationLoader ?? null;
80
85
  this._operationName = null;
86
+ this._useExecTimeResolvers = useExecTimeResolvers ?? false;
81
87
  this._recordSource = recordSource;
82
88
  this._references = references;
83
89
  this._variables = variables;
@@ -216,8 +222,6 @@ class RelayReferenceMarker {
216
222
  this._traverseSelections(selection.fragment.selections, record);
217
223
  break;
218
224
  case 'RelayResolver':
219
- this._traverseResolverField(selection, record);
220
- break;
221
225
  case 'RelayLiveResolver':
222
226
  this._traverseResolverField(selection, record);
223
227
  break;
@@ -239,6 +243,10 @@ class RelayReferenceMarker {
239
243
  field: NormalizationClientEdgeToClientObject,
240
244
  record: Record,
241
245
  ): void {
246
+ if (this._useExecTimeResolvers) {
247
+ this._traverseLink(field.linkedField, record);
248
+ return;
249
+ }
242
250
  const dataID = this._traverseResolverField(field.backingField, record);
243
251
  if (dataID == null) {
244
252
  return;
@@ -291,7 +299,10 @@ class RelayReferenceMarker {
291
299
  field: NormalizationResolverField | NormalizationLiveResolverField,
292
300
  record: Record,
293
301
  ): ?DataID {
294
- const storageKey = getStorageKey(field, this._variables);
302
+ if (this._useExecTimeResolvers) {
303
+ return;
304
+ }
305
+ const storageKey = getReadTimeResolverStorageKey(field, this._variables);
295
306
  const dataID = RelayModernRecord.getLinkedRecordID(record, storageKey);
296
307
 
297
308
  // If the resolver value has been created, we should retain it.
@@ -306,7 +317,6 @@ class RelayReferenceMarker {
306
317
  // Mark the contents of the resolver's data dependencies.
307
318
  this._traverseSelections([fragment], record);
308
319
  }
309
-
310
320
  return dataID;
311
321
  }
312
322
 
@@ -29,7 +29,9 @@ import type {RelayErrorTrie} from './RelayErrorTrie';
29
29
  import type {
30
30
  FollowupPayload,
31
31
  HandleFieldPayload,
32
+ IdCollisionTypenameLogEvent,
32
33
  IncrementalDataPlaceholder,
34
+ LogFunction,
33
35
  MutableRecordSource,
34
36
  NormalizationSelector,
35
37
  Record,
@@ -72,6 +74,7 @@ export type GetDataID = (
72
74
  export type NormalizationOptions = {
73
75
  +getDataID: GetDataID,
74
76
  +treatMissingFieldsAsNull: boolean,
77
+ +log: ?LogFunction,
75
78
  +path?: $ReadOnlyArray<string>,
76
79
  +shouldProcessClientComponents?: ?boolean,
77
80
  +actorIdentifier?: ?ActorIdentifier,
@@ -87,12 +90,14 @@ function normalize(
87
90
  response: PayloadData,
88
91
  options: NormalizationOptions,
89
92
  errors?: Array<PayloadError>,
93
+ useExecTimeResolvers?: boolean,
90
94
  ): RelayResponsePayload {
91
95
  const {dataID, node, variables} = selector;
92
96
  const normalizer = new RelayResponseNormalizer(
93
97
  recordSource,
94
98
  variables,
95
99
  options,
100
+ useExecTimeResolvers ?? false,
96
101
  );
97
102
  return normalizer.normalizeResponse(node, dataID, response, errors);
98
103
  }
@@ -114,13 +119,16 @@ class RelayResponseNormalizer {
114
119
  _path: Array<string>;
115
120
  _recordSource: MutableRecordSource;
116
121
  _variables: Variables;
122
+ _useExecTimeResolvers: boolean;
117
123
  _shouldProcessClientComponents: ?boolean;
118
124
  _errorTrie: RelayErrorTrie | null;
125
+ _log: ?LogFunction;
119
126
 
120
127
  constructor(
121
128
  recordSource: MutableRecordSource,
122
129
  variables: Variables,
123
130
  options: NormalizationOptions,
131
+ useExecTimeResolvers: boolean,
124
132
  ) {
125
133
  this._actorIdentifier = options.actorIdentifier;
126
134
  this._getDataId = options.getDataID;
@@ -129,11 +137,13 @@ class RelayResponseNormalizer {
129
137
  this._incrementalPlaceholders = [];
130
138
  this._isClientExtension = false;
131
139
  this._isUnmatchedAbstractType = false;
140
+ this._useExecTimeResolvers = useExecTimeResolvers;
132
141
  this._followupPayloads = [];
133
142
  this._path = options.path ? [...options.path] : [];
134
143
  this._recordSource = recordSource;
135
144
  this._variables = variables;
136
145
  this._shouldProcessClientComponents = options.shouldProcessClientComponents;
146
+ this._log = options.log;
137
147
  }
138
148
 
139
149
  normalizeResponse(
@@ -319,13 +329,15 @@ class RelayResponseNormalizer {
319
329
  this._normalizeActorChange(selection, record, data);
320
330
  break;
321
331
  case 'RelayResolver':
322
- this._normalizeResolver(selection, record, data);
323
- break;
324
332
  case 'RelayLiveResolver':
325
- this._normalizeResolver(selection, record, data);
333
+ if (!this._useExecTimeResolvers) {
334
+ this._normalizeResolver(selection, record, data);
335
+ }
326
336
  break;
327
337
  case 'ClientEdgeToClientObject':
328
- this._normalizeResolver(selection.backingField, record, data);
338
+ if (!this._useExecTimeResolvers) {
339
+ this._normalizeResolver(selection.backingField, record, data);
340
+ }
329
341
  break;
330
342
  default:
331
343
  (selection: empty);
@@ -472,12 +484,11 @@ class RelayResponseNormalizer {
472
484
  const responseKey = selection.alias || selection.name;
473
485
  const storageKey = getStorageKey(selection, this._variables);
474
486
  const fieldValue = data[responseKey];
475
- if (
476
- fieldValue == null ||
477
- (RelayFeatureFlags.ENABLE_NONCOMPLIANT_ERROR_HANDLING_ON_LISTS &&
478
- Array.isArray(fieldValue) &&
479
- fieldValue.length === 0)
480
- ) {
487
+ const isNoncompliantlyNullish =
488
+ RelayFeatureFlags.ENABLE_NONCOMPLIANT_ERROR_HANDLING_ON_LISTS &&
489
+ Array.isArray(fieldValue) &&
490
+ fieldValue.length === 0;
491
+ if (fieldValue == null || isNoncompliantlyNullish) {
481
492
  if (fieldValue === undefined) {
482
493
  // Fields may be missing in the response in two main cases:
483
494
  // - Inside a client extension: the server will not generally return
@@ -511,20 +522,27 @@ class RelayResponseNormalizer {
511
522
  return;
512
523
  }
513
524
  }
514
- if (__DEV__) {
515
- if (selection.kind === 'ScalarField') {
516
- this._validateConflictingFieldsWithIdenticalId(
517
- record,
518
- storageKey,
519
- // When using `treatMissingFieldsAsNull` the conflicting validation raises a false positive
520
- // because the value is set using `null` but validated using `fieldValue` which at this point
521
- // will be `undefined`.
522
- // Setting this to `null` matches the value that we actually set to the `fieldValue`.
523
- null,
524
- );
525
+ if (selection.kind === 'ScalarField') {
526
+ this._validateConflictingFieldsWithIdenticalId(
527
+ record,
528
+ storageKey,
529
+ // When using `treatMissingFieldsAsNull` the conflicting validation raises a false positive
530
+ // because the value is set using `null` but validated using `fieldValue` which at this point
531
+ // will be `undefined`.
532
+ // Setting this to `null` matches the value that we actually set to the `fieldValue`.
533
+ null,
534
+ );
535
+ }
536
+ if (isNoncompliantlyNullish) {
537
+ // We need to preserve the fact that we received an empty list
538
+ if (selection.kind === 'LinkedField') {
539
+ RelayModernRecord.setLinkedRecordIDs(record, storageKey, []);
540
+ } else {
541
+ RelayModernRecord.setValue(record, storageKey, []);
525
542
  }
543
+ } else {
544
+ RelayModernRecord.setValue(record, storageKey, null);
526
545
  }
527
- RelayModernRecord.setValue(record, storageKey, null);
528
546
  const errorTrie = this._errorTrie;
529
547
  if (errorTrie != null) {
530
548
  const errors = getErrorsByKey(errorTrie, responseKey);
@@ -536,13 +554,11 @@ class RelayResponseNormalizer {
536
554
  }
537
555
 
538
556
  if (selection.kind === 'ScalarField') {
539
- if (__DEV__) {
540
- this._validateConflictingFieldsWithIdenticalId(
541
- record,
542
- storageKey,
543
- fieldValue,
544
- );
545
- }
557
+ this._validateConflictingFieldsWithIdenticalId(
558
+ record,
559
+ storageKey,
560
+ fieldValue,
561
+ );
546
562
  RelayModernRecord.setValue(record, storageKey, fieldValue);
547
563
  } else if (selection.kind === 'LinkedField') {
548
564
  this._path.push(responseKey);
@@ -686,13 +702,11 @@ class RelayResponseNormalizer {
686
702
  'RelayResponseNormalizer: Expected id on field `%s` to be a string.',
687
703
  storageKey,
688
704
  );
689
- if (__DEV__) {
690
- this._validateConflictingLinkedFieldsWithIdenticalId(
691
- RelayModernRecord.getLinkedRecordID(record, storageKey),
692
- nextID,
693
- storageKey,
694
- );
695
- }
705
+ this._validateConflictingLinkedFieldsWithIdenticalId(
706
+ RelayModernRecord.getLinkedRecordID(record, storageKey),
707
+ nextID,
708
+ storageKey,
709
+ );
696
710
  RelayModernRecord.setLinkedRecordID(record, storageKey, nextID);
697
711
  let nextRecord = this._recordSource.get(nextID);
698
712
  if (!nextRecord) {
@@ -700,7 +714,7 @@ class RelayResponseNormalizer {
700
714
  const typeName = field.concreteType || this._getRecordType(fieldValue);
701
715
  nextRecord = RelayModernRecord.create(nextID, typeName);
702
716
  this._recordSource.set(nextID, nextRecord);
703
- } else if (__DEV__) {
717
+ } else {
704
718
  this._validateRecordType(nextRecord, field, fieldValue);
705
719
  }
706
720
  // $FlowFixMe[incompatible-variance]
@@ -766,19 +780,15 @@ class RelayResponseNormalizer {
766
780
  const typeName = field.concreteType || this._getRecordType(item);
767
781
  nextRecord = RelayModernRecord.create(nextID, typeName);
768
782
  this._recordSource.set(nextID, nextRecord);
769
- } else if (__DEV__) {
783
+ } else {
770
784
  this._validateRecordType(nextRecord, field, item);
771
785
  }
772
- // NOTE: the check to strip __DEV__ code only works for simple
773
- // `if (__DEV__)`
774
- if (__DEV__) {
775
- if (prevIDs) {
776
- this._validateConflictingLinkedFieldsWithIdenticalId(
777
- prevIDs[nextIndex],
778
- nextID,
779
- storageKey,
780
- );
781
- }
786
+ if (prevIDs) {
787
+ this._validateConflictingLinkedFieldsWithIdenticalId(
788
+ prevIDs[nextIndex],
789
+ nextID,
790
+ storageKey,
791
+ );
782
792
  }
783
793
  // $FlowFixMe[incompatible-variance]
784
794
  this._traverseSelections(field, nextRecord, item);
@@ -796,20 +806,42 @@ class RelayResponseNormalizer {
796
806
  field: NormalizationLinkedField,
797
807
  payload: Object,
798
808
  ): void {
799
- const typeName = field.concreteType ?? this._getRecordType(payload);
800
- const dataID = RelayModernRecord.getDataID(record);
801
- warning(
802
- (isClientID(dataID) && dataID !== ROOT_ID) ||
803
- RelayModernRecord.getType(record) === typeName,
804
- 'RelayResponseNormalizer: Invalid record `%s`. Expected %s to be ' +
805
- 'consistent, but the record was assigned conflicting types `%s` ' +
806
- 'and `%s`. The GraphQL server likely violated the globally unique ' +
807
- 'id requirement by returning the same id for different objects.',
808
- dataID,
809
- TYPENAME_KEY,
810
- RelayModernRecord.getType(record),
811
- typeName,
812
- );
809
+ if (RelayFeatureFlags.ENABLE_STORE_ID_COLLISION_LOGGING) {
810
+ const typeName = field.concreteType ?? this._getRecordType(payload);
811
+ const dataID = RelayModernRecord.getDataID(record);
812
+ const expected =
813
+ (isClientID(dataID) && dataID !== ROOT_ID) ||
814
+ RelayModernRecord.getType(record) === typeName;
815
+ if (!expected) {
816
+ const logEvent: IdCollisionTypenameLogEvent = {
817
+ name: 'idCollision.typename',
818
+ previous_typename: RelayModernRecord.getType(record),
819
+ new_typename: typeName,
820
+ };
821
+ if (this._log != null) {
822
+ this._log(logEvent);
823
+ }
824
+ }
825
+ }
826
+ // NOTE: Only emit a warning in DEV
827
+ if (__DEV__) {
828
+ const typeName = field.concreteType ?? this._getRecordType(payload);
829
+ const dataID = RelayModernRecord.getDataID(record);
830
+ const expected =
831
+ (isClientID(dataID) && dataID !== ROOT_ID) ||
832
+ RelayModernRecord.getType(record) === typeName;
833
+ warning(
834
+ expected,
835
+ 'RelayResponseNormalizer: Invalid record `%s`. Expected %s to be ' +
836
+ 'consistent, but the record was assigned conflicting types `%s` ' +
837
+ 'and `%s`. The GraphQL server likely violated the globally unique ' +
838
+ 'id requirement by returning the same id for different objects.',
839
+ dataID,
840
+ TYPENAME_KEY,
841
+ RelayModernRecord.getType(record),
842
+ typeName,
843
+ );
844
+ }
813
845
  }
814
846
 
815
847
  /**
@@ -820,14 +852,16 @@ class RelayResponseNormalizer {
820
852
  storageKey: string,
821
853
  fieldValue: mixed,
822
854
  ): void {
823
- // NOTE: Only call this function in DEV
855
+ // NOTE: Only emit a warning in DEV
824
856
  if (__DEV__) {
857
+ const previousValue = RelayModernRecord.getValue(record, storageKey);
825
858
  const dataID = RelayModernRecord.getDataID(record);
826
- var previousValue = RelayModernRecord.getValue(record, storageKey);
827
- warning(
859
+ const expected =
828
860
  storageKey === TYPENAME_KEY ||
829
- previousValue === undefined ||
830
- areEqual(previousValue, fieldValue),
861
+ previousValue === undefined ||
862
+ areEqual(previousValue, fieldValue);
863
+ warning(
864
+ expected,
831
865
  'RelayResponseNormalizer: Invalid record. The record contains two ' +
832
866
  'instances of the same id: `%s` with conflicting field, %s and its values: %s and %s. ' +
833
867
  'If two fields are different but share ' +
@@ -848,10 +882,11 @@ class RelayResponseNormalizer {
848
882
  nextID: DataID,
849
883
  storageKey: string,
850
884
  ): void {
851
- // NOTE: Only call this function in DEV
885
+ // NOTE: Only emit a warning in DEV
852
886
  if (__DEV__) {
887
+ const expected = prevID === undefined || prevID === nextID;
853
888
  warning(
854
- prevID === undefined || prevID === nextID,
889
+ expected,
855
890
  'RelayResponseNormalizer: Invalid record. The record contains ' +
856
891
  'references to the conflicting field, %s and its id values: %s and %s. ' +
857
892
  'We need to make sure that the record the field points ' +
@@ -115,7 +115,7 @@ class RelayStoreSubscriptions implements StoreSubscriptions {
115
115
  missingLiveResolverFields: backup.missingLiveResolverFields,
116
116
  seenRecords: backup.seenRecords,
117
117
  selector: backup.selector,
118
- errorResponseFields: backup.errorResponseFields,
118
+ fieldErrors: backup.fieldErrors,
119
119
  };
120
120
  } else {
121
121
  // This subscription was created during the optimisitic state. We should
@@ -185,7 +185,7 @@ class RelayStoreSubscriptions implements StoreSubscriptions {
185
185
  missingLiveResolverFields: nextSnapshot.missingLiveResolverFields,
186
186
  seenRecords: nextSnapshot.seenRecords,
187
187
  selector: nextSnapshot.selector,
188
- errorResponseFields: nextSnapshot.errorResponseFields,
188
+ fieldErrors: nextSnapshot.fieldErrors,
189
189
  }: Snapshot);
190
190
  if (__DEV__) {
191
191
  deepFreeze(nextSnapshot);
@@ -116,7 +116,7 @@ export type NormalizationSelector = {
116
116
  +variables: Variables,
117
117
  };
118
118
 
119
- export type ErrorResponseField =
119
+ export type FieldError =
120
120
  | RelayFieldPayloadErrorEvent
121
121
  | MissingExpectedDataLogEvent
122
122
  | MissingExpectedDataThrowEvent
@@ -124,7 +124,7 @@ export type ErrorResponseField =
124
124
  | MissingRequiredFieldLogEvent
125
125
  | MissingRequiredFieldThrowEvent;
126
126
 
127
- export type ErrorResponseFields = Array<ErrorResponseField>;
127
+ export type FieldErrors = Array<FieldError>;
128
128
 
129
129
  export type ClientEdgeTraversalInfo = {
130
130
  +readerClientEdge: ReaderClientEdgeToServerObject,
@@ -149,7 +149,7 @@ export type Snapshot = {
149
149
  +missingClientEdges: null | $ReadOnlyArray<MissingClientEdgeRequestInfo>,
150
150
  +seenRecords: DataIDSet,
151
151
  +selector: SingularReaderSelector,
152
- +errorResponseFields: ?ErrorResponseFields,
152
+ +fieldErrors: ?FieldErrors,
153
153
  };
154
154
 
155
155
  /**
@@ -684,6 +684,11 @@ export type ExecuteCompleteLogEvent = {
684
684
  +executeId: number,
685
685
  };
686
686
 
687
+ export type ExecuteUnsubscribeLogEvent = {
688
+ +name: 'execute.unsubscribe',
689
+ +executeId: number,
690
+ };
691
+
687
692
  export type ExecuteNormalizeStart = {
688
693
  +name: 'execute.normalize.start',
689
694
  +operation: OperationDescriptor,
@@ -781,12 +786,23 @@ export type UseFragmentSubscriptionMissedUpdates = {
781
786
  +hasDataChanges: boolean,
782
787
  };
783
788
 
789
+ /**
790
+ * This event is logged when two strong objects share the same id,
791
+ * but have different types, resulting in an collision in the store.
792
+ */
793
+ export type IdCollisionTypenameLogEvent = {
794
+ +name: 'idCollision.typename',
795
+ +previous_typename: string,
796
+ +new_typename: string,
797
+ };
798
+
784
799
  export type LogEvent =
785
800
  | SuspenseFragmentLogEvent
786
801
  | SuspenseQueryLogEvent
787
802
  | QueryResourceFetchLogEvent
788
803
  | QueryResourceRetainLogEvent
789
804
  | FragmentResourceMissingDataLogEvent
805
+ | IdCollisionTypenameLogEvent
790
806
  | PendingOperationFoundLogEvent
791
807
  | NetworkInfoLogEvent
792
808
  | NetworkStartLogEvent
@@ -800,6 +816,7 @@ export type LogEvent =
800
816
  | ExecuteAsyncModuleLogEvent
801
817
  | ExecuteErrorLogEvent
802
818
  | ExecuteCompleteLogEvent
819
+ | ExecuteUnsubscribeLogEvent
803
820
  | ExecuteNormalizeStart
804
821
  | ExecuteNormalizeEnd
805
822
  | StoreDataCheckerStartEvent
@@ -1155,6 +1172,7 @@ export type NormalizeResponseFunction = (
1155
1172
  selector: NormalizationSelector,
1156
1173
  typeName: string,
1157
1174
  options: NormalizationOptions,
1175
+ useExecTimeResolvers: boolean,
1158
1176
  ) => RelayResponsePayload;
1159
1177
 
1160
1178
  /**
@@ -1270,6 +1288,8 @@ export type MissingExpectedDataLogEvent = {
1270
1288
  +kind: 'missing_expected_data.log',
1271
1289
  +owner: string,
1272
1290
  fieldPath: string, // Purposefully mutable to allow lazy construction in RelayReader
1291
+ // To populate this, you should pass the value to a ReactRelayLoggingContext
1292
+ +uiContext: mixed | void,
1273
1293
  };
1274
1294
 
1275
1295
  /**
@@ -1297,6 +1317,8 @@ export type MissingExpectedDataThrowEvent = {
1297
1317
  +owner: string,
1298
1318
  fieldPath: string, // Purposefully mutable to allow lazy construction in RelayReader
1299
1319
  +handled: boolean,
1320
+ // To populate this, you should pass the value to a ReactRelayLoggingContext
1321
+ +uiContext: mixed | void,
1300
1322
  };
1301
1323
 
1302
1324
  /**
@@ -1307,6 +1329,8 @@ export type MissingRequiredFieldLogEvent = {
1307
1329
  +kind: 'missing_required_field.log',
1308
1330
  +owner: string,
1309
1331
  fieldPath: string, // Purposefully mutable to allow lazy construction in RelayReader
1332
+ // To populate this, you should pass the value to a ReactRelayLoggingContext
1333
+ +uiContext: mixed | void,
1310
1334
  };
1311
1335
 
1312
1336
  /**
@@ -1325,6 +1349,8 @@ export type MissingRequiredFieldThrowEvent = {
1325
1349
  +owner: string,
1326
1350
  fieldPath: string, // Purposefully mutable to allow lazy construction in RelayReader
1327
1351
  +handled: boolean,
1352
+ // To populate this, you should pass the value to a ReactRelayLoggingContext
1353
+ +uiContext: mixed | void,
1328
1354
  };
1329
1355
 
1330
1356
  /**
@@ -1346,6 +1372,8 @@ export type RelayResolverErrorEvent = {
1346
1372
  +error: Error,
1347
1373
  +shouldThrow: boolean,
1348
1374
  +handled: boolean,
1375
+ // To populate this, you should pass the value to a ReactRelayLoggingContext
1376
+ +uiContext: mixed | void,
1349
1377
  };
1350
1378
 
1351
1379
  /**
@@ -1372,6 +1400,8 @@ export type RelayFieldPayloadErrorEvent = {
1372
1400
  +error: TRelayFieldError,
1373
1401
  +shouldThrow: boolean,
1374
1402
  +handled: boolean,
1403
+ // To populate this, you should pass the value to a ReactRelayLoggingContext
1404
+ +uiContext: mixed | void,
1375
1405
  };
1376
1406
 
1377
1407
  /**
@@ -1481,7 +1511,7 @@ export type ConcreteClientEdgeResolverReturnType<T = any> = {
1481
1511
  * returns a callback which should be called when the value _may_ have changed.
1482
1512
  *
1483
1513
  * While over-notification (subscription notifications when the read value has
1484
- * not actually changed) is suported, for performance reasons, it is recommended
1514
+ * not actually changed) is supported, for performance reasons, it is recommended
1485
1515
  * that the provider of the LiveState value confirms that the value has indeed
1486
1516
  * change before notifying Relay of the change.
1487
1517
  */
@@ -15,6 +15,8 @@ import type {
15
15
  NormalizationArgument,
16
16
  NormalizationField,
17
17
  NormalizationHandle,
18
+ NormalizationLiveResolverField,
19
+ NormalizationResolverField,
18
20
  } from '../util/NormalizationNode';
19
21
  import type {
20
22
  ReaderActorChange,
@@ -28,6 +30,7 @@ import type {Variables} from '../util/RelayRuntimeTypes';
28
30
 
29
31
  const getRelayHandleKey = require('../util/getRelayHandleKey');
30
32
  const RelayConcreteNode = require('../util/RelayConcreteNode');
33
+ const RelayFeatureFlags = require('../util/RelayFeatureFlags');
31
34
  const {stableCopy} = require('../util/stableCopy');
32
35
  const invariant = require('invariant');
33
36
 
@@ -42,6 +45,8 @@ const ERRORS_KEY: '__errors' = '__errors';
42
45
  const MODULE_COMPONENT_KEY_PREFIX = '__module_component_';
43
46
  const MODULE_OPERATION_KEY_PREFIX = '__module_operation_';
44
47
 
48
+ const RELAY_READ_TIME_RESOLVER_KEY_PREFIX = '$r:';
49
+
45
50
  function getArgumentValue(
46
51
  arg: NormalizationArgument | ReaderArgument,
47
52
  variables: Variables,
@@ -163,6 +168,28 @@ function getStorageKey(
163
168
  : name;
164
169
  }
165
170
 
171
+ /**
172
+ * This is a special case of getStorageKey that should be used when dealing with
173
+ * read time resolver fields. A resolver may be used at both exec time and at read
174
+ * time within the same project. However, the value of the read time resolver is
175
+ * wrapped while the value of the exec time resolver is a standard Relay object. To
176
+ * disambiguate in the case that both types may exist on the same record, the read
177
+ * time resolver storage keys are prefixed.
178
+ */
179
+ function getReadTimeResolverStorageKey(
180
+ field:
181
+ | ReaderRelayResolver
182
+ | ReaderRelayLiveResolver
183
+ | NormalizationResolverField
184
+ | NormalizationLiveResolverField,
185
+ variables: Variables,
186
+ ): string {
187
+ const storageKey = getStorageKey(field, variables);
188
+ return RelayFeatureFlags.ENABLE_READ_TIME_RESOLVER_STORAGE_KEY_PREFIX
189
+ ? '$r:' + storageKey // Using inlined string to test the performance impact
190
+ : storageKey;
191
+ }
192
+
166
193
  /**
167
194
  * Given a field the method returns an array of arguments.
168
195
  * For Relay resolver fields, we store arguments on the field and fragment
@@ -271,12 +298,14 @@ const RelayStoreUtils = {
271
298
  RELAY_RESOLVER_SNAPSHOT_KEY: '__resolverSnapshot',
272
299
  RELAY_RESOLVER_ERROR_KEY: '__resolverError',
273
300
  RELAY_RESOLVER_OUTPUT_TYPE_RECORD_IDS: '__resolverOutputTypeRecordIDs',
301
+ RELAY_READ_TIME_RESOLVER_KEY_PREFIX,
274
302
 
275
303
  formatStorageKey,
276
304
  getArgumentValue,
277
305
  getArgumentValues,
278
306
  getHandleStorageKey,
279
307
  getStorageKey,
308
+ getReadTimeResolverStorageKey,
280
309
  getStableStorageKey,
281
310
  getModuleComponentKey,
282
311
  getModuleOperationKey,
@@ -18,7 +18,7 @@ import type {
18
18
  import type {DataID, Variables} from '../util/RelayRuntimeTypes';
19
19
  import type {
20
20
  DataIDSet,
21
- ErrorResponseFields,
21
+ FieldErrors,
22
22
  SingularReaderSelector,
23
23
  Snapshot,
24
24
  } from './RelayStoreTypes';
@@ -35,7 +35,7 @@ export type EvaluationResult<T> = {
35
35
  export type ResolverFragmentResult = {
36
36
  data: mixed,
37
37
  isMissingData: boolean,
38
- errorResponseFields: ?ErrorResponseFields,
38
+ fieldErrors: ?FieldErrors,
39
39
  };
40
40
 
41
41
  export type GetDataForResolverFragmentFn =
@@ -112,12 +112,14 @@ function readFragment(
112
112
  fragmentSelector.kind === 'SingularReaderSelector',
113
113
  `Expected a singular reader selector for the fragment of the resolver ${fragmentNode.name}, but it was plural.`,
114
114
  );
115
- const {data, isMissingData, errorResponseFields} =
116
- context.getDataForResolverFragment(fragmentSelector, fragmentKey);
115
+ const {data, isMissingData, fieldErrors} = context.getDataForResolverFragment(
116
+ fragmentSelector,
117
+ fragmentKey,
118
+ );
117
119
 
118
120
  if (
119
121
  isMissingData ||
120
- (errorResponseFields != null && errorResponseFields.some(eventShouldThrow))
122
+ (fieldErrors != null && fieldErrors.some(eventShouldThrow))
121
123
  ) {
122
124
  throw RESOLVER_FRAGMENT_ERRORED_SENTINEL;
123
125
  }
@@ -32,11 +32,14 @@ if (__DEV__) {
32
32
  }
33
33
  formattersInstalled = true;
34
34
  // $FlowFixMe[incompatible-use] D61394600
35
+ // $FlowFixMe[cannot-resolve-name]
35
36
  if (window.devtoolsFormatters == null) {
36
37
  // $FlowFixMe[incompatible-use] D61394600
38
+ // $FlowFixMe[cannot-resolve-name]
37
39
  window.devtoolsFormatters = [];
38
40
  }
39
41
  // $FlowFixMe[incompatible-use] D61394600
42
+ // $FlowFixMe[cannot-resolve-name]
40
43
  if (!Array.isArray(window.devtoolsFormatters)) {
41
44
  return;
42
45
  }
@@ -47,6 +50,7 @@ if (__DEV__) {
47
50
  'section.',
48
51
  );
49
52
  // $FlowFixMe[incompatible-use] D61394600
53
+ // $FlowFixMe[cannot-resolve-name]
50
54
  window.devtoolsFormatters.push(...createFormatters());
51
55
  };
52
56
 
@@ -137,6 +141,7 @@ if (__DEV__) {
137
141
  ): ?{[string]: mixed} => {
138
142
  const record = source.get(dataID);
139
143
  if (record == null) {
144
+ // $FlowFixMe[incompatible-return]
140
145
  return record;
141
146
  }
142
147
  return new Proxy(