relay-runtime 18.1.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 (92) hide show
  1. package/experimental.js +1 -1
  2. package/experimental.js.flow +22 -9
  3. package/handlers/connection/ConnectionHandler.js.flow +6 -1
  4. package/index.js +1 -1
  5. package/index.js.flow +4 -0
  6. package/lib/experimental.js +5 -2
  7. package/lib/handlers/connection/ConnectionHandler.js +1 -1
  8. package/lib/index.js +3 -0
  9. package/lib/multi-actor-environment/ActorSpecificEnvironment.js +1 -1
  10. package/lib/mutations/RelayRecordProxy.js +14 -3
  11. package/lib/mutations/RelayRecordSourceMutator.js +17 -0
  12. package/lib/mutations/RelayRecordSourceProxy.js +2 -1
  13. package/lib/mutations/createUpdatableProxy.js +1 -1
  14. package/lib/mutations/validateMutation.js +2 -2
  15. package/lib/network/RelayObservable.js +1 -3
  16. package/lib/network/wrapNetworkWithLogObserver.js +2 -2
  17. package/lib/query/fetchQuery.js +1 -1
  18. package/lib/store/DataChecker.js +4 -5
  19. package/lib/store/OperationExecutor.js +11 -0
  20. package/lib/store/RelayModernEnvironment.js +13 -4
  21. package/lib/store/RelayModernFragmentSpecResolver.js +4 -4
  22. package/lib/store/RelayModernStore.js +43 -21
  23. package/lib/store/RelayPublishQueue.js +11 -15
  24. package/lib/store/RelayReader.js +244 -183
  25. package/lib/store/RelayReferenceMarker.js +3 -4
  26. package/lib/store/RelayResponseNormalizer.js +48 -26
  27. package/lib/store/RelayStoreSubscriptions.js +2 -2
  28. package/lib/store/RelayStoreUtils.js +8 -0
  29. package/lib/store/ResolverCache.js +1 -165
  30. package/lib/store/ResolverFragments.js +2 -2
  31. package/lib/store/createRelayLoggingContext.js +17 -0
  32. package/lib/store/generateTypenamePrefixedDataID.js +9 -0
  33. package/lib/store/live-resolvers/LiveResolverCache.js +5 -10
  34. package/lib/store/live-resolvers/resolverDataInjector.js +4 -4
  35. package/lib/store/observeFragmentExperimental.js +60 -13
  36. package/lib/store/observeQueryExperimental.js +21 -0
  37. package/lib/util/RelayFeatureFlags.js +7 -2
  38. package/lib/util/handlePotentialSnapshotErrors.js +12 -9
  39. package/multi-actor-environment/ActorSpecificEnvironment.js.flow +1 -0
  40. package/mutations/RelayRecordProxy.js.flow +30 -3
  41. package/mutations/RelayRecordSourceMutator.js.flow +27 -0
  42. package/mutations/RelayRecordSourceProxy.js.flow +4 -0
  43. package/mutations/createUpdatableProxy.js.flow +1 -1
  44. package/mutations/validateMutation.js.flow +3 -3
  45. package/network/RelayNetworkTypes.js.flow +3 -0
  46. package/network/RelayObservable.js.flow +1 -5
  47. package/network/wrapNetworkWithLogObserver.js.flow +19 -1
  48. package/package.json +1 -1
  49. package/query/fetchQuery.js.flow +1 -1
  50. package/store/DataChecker.js.flow +5 -2
  51. package/store/OperationExecutor.js.flow +12 -1
  52. package/store/RelayExperimentalGraphResponseTransform.js.flow +4 -4
  53. package/store/RelayModernEnvironment.js.flow +22 -6
  54. package/store/RelayModernFragmentSpecResolver.js.flow +6 -6
  55. package/store/RelayModernRecord.js.flow +1 -1
  56. package/store/RelayModernSelector.js.flow +2 -0
  57. package/store/RelayModernStore.js.flow +74 -27
  58. package/store/RelayOptimisticRecordSource.js.flow +2 -0
  59. package/store/RelayPublishQueue.js.flow +32 -21
  60. package/store/RelayReader.js.flow +400 -145
  61. package/store/RelayRecordState.js.flow +1 -1
  62. package/store/RelayReferenceMarker.js.flow +3 -4
  63. package/store/RelayResponseNormalizer.js.flow +94 -62
  64. package/store/RelayStoreSubscriptions.js.flow +2 -2
  65. package/store/RelayStoreTypes.js.flow +45 -15
  66. package/store/RelayStoreUtils.js.flow +30 -1
  67. package/store/ResolverCache.js.flow +2 -271
  68. package/store/ResolverFragments.js.flow +5 -3
  69. package/store/StoreInspector.js.flow +5 -0
  70. package/store/createRelayContext.js.flow +3 -2
  71. package/store/createRelayLoggingContext.js.flow +46 -0
  72. package/store/generateTypenamePrefixedDataID.js.flow +25 -0
  73. package/store/live-resolvers/LiveResolverCache.js.flow +5 -10
  74. package/store/live-resolvers/resolverDataInjector.js.flow +10 -6
  75. package/store/observeFragmentExperimental.js.flow +82 -28
  76. package/store/observeQueryExperimental.js.flow +61 -0
  77. package/store/waitForFragmentExperimental.js.flow +4 -3
  78. package/util/NormalizationNode.js.flow +10 -1
  79. package/util/ReaderNode.js.flow +9 -3
  80. package/util/RelayConcreteNode.js.flow +3 -1
  81. package/util/RelayError.js.flow +1 -0
  82. package/util/RelayFeatureFlags.js.flow +31 -7
  83. package/util/RelayRuntimeTypes.js.flow +17 -3
  84. package/util/getPaginationVariables.js.flow +2 -0
  85. package/util/handlePotentialSnapshotErrors.js.flow +24 -12
  86. package/util/registerEnvironmentWithDevTools.js.flow +4 -2
  87. package/util/withProvidedVariables.js.flow +1 -0
  88. package/util/withStartAndDuration.js.flow +3 -0
  89. package/relay-runtime-experimental.js +0 -4
  90. package/relay-runtime-experimental.min.js +0 -9
  91. package/relay-runtime.js +0 -4
  92. package/relay-runtime.min.js +0 -9
@@ -29,7 +29,7 @@ const RelayRecordState = {
29
29
  * server.
30
30
  */
31
31
  UNKNOWN: 'UNKNOWN',
32
- };
32
+ } as const;
33
33
 
34
34
  export type RecordState = $Keys<typeof RelayRecordState>;
35
35
 
@@ -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.
@@ -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,
@@ -40,6 +42,7 @@ const {
40
42
  ACTOR_IDENTIFIER_FIELD_NAME,
41
43
  getActorIdentifierFromPayload,
42
44
  } = require('../multi-actor-environment/ActorUtils');
45
+ const RelayFeatureFlags = require('../util/RelayFeatureFlags');
43
46
  const {generateClientID, isClientID} = require('./ClientID');
44
47
  const {getLocalVariables} = require('./RelayConcreteVariables');
45
48
  const {
@@ -71,6 +74,7 @@ export type GetDataID = (
71
74
  export type NormalizationOptions = {
72
75
  +getDataID: GetDataID,
73
76
  +treatMissingFieldsAsNull: boolean,
77
+ +log: ?LogFunction,
74
78
  +path?: $ReadOnlyArray<string>,
75
79
  +shouldProcessClientComponents?: ?boolean,
76
80
  +actorIdentifier?: ?ActorIdentifier,
@@ -115,6 +119,7 @@ class RelayResponseNormalizer {
115
119
  _variables: Variables;
116
120
  _shouldProcessClientComponents: ?boolean;
117
121
  _errorTrie: RelayErrorTrie | null;
122
+ _log: ?LogFunction;
118
123
 
119
124
  constructor(
120
125
  recordSource: MutableRecordSource,
@@ -133,6 +138,7 @@ class RelayResponseNormalizer {
133
138
  this._recordSource = recordSource;
134
139
  this._variables = variables;
135
140
  this._shouldProcessClientComponents = options.shouldProcessClientComponents;
141
+ this._log = options.log;
136
142
  }
137
143
 
138
144
  normalizeResponse(
@@ -318,8 +324,6 @@ class RelayResponseNormalizer {
318
324
  this._normalizeActorChange(selection, record, data);
319
325
  break;
320
326
  case 'RelayResolver':
321
- this._normalizeResolver(selection, record, data);
322
- break;
323
327
  case 'RelayLiveResolver':
324
328
  this._normalizeResolver(selection, record, data);
325
329
  break;
@@ -471,7 +475,11 @@ class RelayResponseNormalizer {
471
475
  const responseKey = selection.alias || selection.name;
472
476
  const storageKey = getStorageKey(selection, this._variables);
473
477
  const fieldValue = data[responseKey];
474
- if (fieldValue == null) {
478
+ const isNoncompliantlyNullish =
479
+ RelayFeatureFlags.ENABLE_NONCOMPLIANT_ERROR_HANDLING_ON_LISTS &&
480
+ Array.isArray(fieldValue) &&
481
+ fieldValue.length === 0;
482
+ if (fieldValue == null || isNoncompliantlyNullish) {
475
483
  if (fieldValue === undefined) {
476
484
  // Fields may be missing in the response in two main cases:
477
485
  // - Inside a client extension: the server will not generally return
@@ -505,20 +513,27 @@ class RelayResponseNormalizer {
505
513
  return;
506
514
  }
507
515
  }
508
- if (__DEV__) {
509
- if (selection.kind === 'ScalarField') {
510
- this._validateConflictingFieldsWithIdenticalId(
511
- record,
512
- storageKey,
513
- // When using `treatMissingFieldsAsNull` the conflicting validation raises a false positive
514
- // because the value is set using `null` but validated using `fieldValue` which at this point
515
- // will be `undefined`.
516
- // Setting this to `null` matches the value that we actually set to the `fieldValue`.
517
- null,
518
- );
516
+ if (selection.kind === 'ScalarField') {
517
+ this._validateConflictingFieldsWithIdenticalId(
518
+ record,
519
+ storageKey,
520
+ // When using `treatMissingFieldsAsNull` the conflicting validation raises a false positive
521
+ // because the value is set using `null` but validated using `fieldValue` which at this point
522
+ // will be `undefined`.
523
+ // Setting this to `null` matches the value that we actually set to the `fieldValue`.
524
+ null,
525
+ );
526
+ }
527
+ if (isNoncompliantlyNullish) {
528
+ // We need to preserve the fact that we received an empty list
529
+ if (selection.kind === 'LinkedField') {
530
+ RelayModernRecord.setLinkedRecordIDs(record, storageKey, []);
531
+ } else {
532
+ RelayModernRecord.setValue(record, storageKey, []);
519
533
  }
534
+ } else {
535
+ RelayModernRecord.setValue(record, storageKey, null);
520
536
  }
521
- RelayModernRecord.setValue(record, storageKey, null);
522
537
  const errorTrie = this._errorTrie;
523
538
  if (errorTrie != null) {
524
539
  const errors = getErrorsByKey(errorTrie, responseKey);
@@ -530,13 +545,11 @@ class RelayResponseNormalizer {
530
545
  }
531
546
 
532
547
  if (selection.kind === 'ScalarField') {
533
- if (__DEV__) {
534
- this._validateConflictingFieldsWithIdenticalId(
535
- record,
536
- storageKey,
537
- fieldValue,
538
- );
539
- }
548
+ this._validateConflictingFieldsWithIdenticalId(
549
+ record,
550
+ storageKey,
551
+ fieldValue,
552
+ );
540
553
  RelayModernRecord.setValue(record, storageKey, fieldValue);
541
554
  } else if (selection.kind === 'LinkedField') {
542
555
  this._path.push(responseKey);
@@ -680,13 +693,11 @@ class RelayResponseNormalizer {
680
693
  'RelayResponseNormalizer: Expected id on field `%s` to be a string.',
681
694
  storageKey,
682
695
  );
683
- if (__DEV__) {
684
- this._validateConflictingLinkedFieldsWithIdenticalId(
685
- RelayModernRecord.getLinkedRecordID(record, storageKey),
686
- nextID,
687
- storageKey,
688
- );
689
- }
696
+ this._validateConflictingLinkedFieldsWithIdenticalId(
697
+ RelayModernRecord.getLinkedRecordID(record, storageKey),
698
+ nextID,
699
+ storageKey,
700
+ );
690
701
  RelayModernRecord.setLinkedRecordID(record, storageKey, nextID);
691
702
  let nextRecord = this._recordSource.get(nextID);
692
703
  if (!nextRecord) {
@@ -694,7 +705,7 @@ class RelayResponseNormalizer {
694
705
  const typeName = field.concreteType || this._getRecordType(fieldValue);
695
706
  nextRecord = RelayModernRecord.create(nextID, typeName);
696
707
  this._recordSource.set(nextID, nextRecord);
697
- } else if (__DEV__) {
708
+ } else {
698
709
  this._validateRecordType(nextRecord, field, fieldValue);
699
710
  }
700
711
  // $FlowFixMe[incompatible-variance]
@@ -760,19 +771,15 @@ class RelayResponseNormalizer {
760
771
  const typeName = field.concreteType || this._getRecordType(item);
761
772
  nextRecord = RelayModernRecord.create(nextID, typeName);
762
773
  this._recordSource.set(nextID, nextRecord);
763
- } else if (__DEV__) {
774
+ } else {
764
775
  this._validateRecordType(nextRecord, field, item);
765
776
  }
766
- // NOTE: the check to strip __DEV__ code only works for simple
767
- // `if (__DEV__)`
768
- if (__DEV__) {
769
- if (prevIDs) {
770
- this._validateConflictingLinkedFieldsWithIdenticalId(
771
- prevIDs[nextIndex],
772
- nextID,
773
- storageKey,
774
- );
775
- }
777
+ if (prevIDs) {
778
+ this._validateConflictingLinkedFieldsWithIdenticalId(
779
+ prevIDs[nextIndex],
780
+ nextID,
781
+ storageKey,
782
+ );
776
783
  }
777
784
  // $FlowFixMe[incompatible-variance]
778
785
  this._traverseSelections(field, nextRecord, item);
@@ -790,20 +797,42 @@ class RelayResponseNormalizer {
790
797
  field: NormalizationLinkedField,
791
798
  payload: Object,
792
799
  ): void {
793
- const typeName = field.concreteType ?? this._getRecordType(payload);
794
- const dataID = RelayModernRecord.getDataID(record);
795
- warning(
796
- (isClientID(dataID) && dataID !== ROOT_ID) ||
797
- RelayModernRecord.getType(record) === typeName,
798
- 'RelayResponseNormalizer: Invalid record `%s`. Expected %s to be ' +
799
- 'consistent, but the record was assigned conflicting types `%s` ' +
800
- 'and `%s`. The GraphQL server likely violated the globally unique ' +
801
- 'id requirement by returning the same id for different objects.',
802
- dataID,
803
- TYPENAME_KEY,
804
- RelayModernRecord.getType(record),
805
- typeName,
806
- );
800
+ if (RelayFeatureFlags.ENABLE_STORE_ID_COLLISION_LOGGING) {
801
+ const typeName = field.concreteType ?? this._getRecordType(payload);
802
+ const dataID = RelayModernRecord.getDataID(record);
803
+ const expected =
804
+ (isClientID(dataID) && dataID !== ROOT_ID) ||
805
+ RelayModernRecord.getType(record) === typeName;
806
+ if (!expected) {
807
+ const logEvent: IdCollisionTypenameLogEvent = {
808
+ name: 'idCollision.typename',
809
+ previous_typename: RelayModernRecord.getType(record),
810
+ new_typename: typeName,
811
+ };
812
+ if (this._log != null) {
813
+ this._log(logEvent);
814
+ }
815
+ }
816
+ }
817
+ // NOTE: Only emit a warning in DEV
818
+ if (__DEV__) {
819
+ const typeName = field.concreteType ?? this._getRecordType(payload);
820
+ const dataID = RelayModernRecord.getDataID(record);
821
+ const expected =
822
+ (isClientID(dataID) && dataID !== ROOT_ID) ||
823
+ RelayModernRecord.getType(record) === typeName;
824
+ warning(
825
+ expected,
826
+ 'RelayResponseNormalizer: Invalid record `%s`. Expected %s to be ' +
827
+ 'consistent, but the record was assigned conflicting types `%s` ' +
828
+ 'and `%s`. The GraphQL server likely violated the globally unique ' +
829
+ 'id requirement by returning the same id for different objects.',
830
+ dataID,
831
+ TYPENAME_KEY,
832
+ RelayModernRecord.getType(record),
833
+ typeName,
834
+ );
835
+ }
807
836
  }
808
837
 
809
838
  /**
@@ -814,14 +843,16 @@ class RelayResponseNormalizer {
814
843
  storageKey: string,
815
844
  fieldValue: mixed,
816
845
  ): void {
817
- // NOTE: Only call this function in DEV
846
+ // NOTE: Only emit a warning in DEV
818
847
  if (__DEV__) {
848
+ const previousValue = RelayModernRecord.getValue(record, storageKey);
819
849
  const dataID = RelayModernRecord.getDataID(record);
820
- var previousValue = RelayModernRecord.getValue(record, storageKey);
821
- warning(
850
+ const expected =
822
851
  storageKey === TYPENAME_KEY ||
823
- previousValue === undefined ||
824
- areEqual(previousValue, fieldValue),
852
+ previousValue === undefined ||
853
+ areEqual(previousValue, fieldValue);
854
+ warning(
855
+ expected,
825
856
  'RelayResponseNormalizer: Invalid record. The record contains two ' +
826
857
  'instances of the same id: `%s` with conflicting field, %s and its values: %s and %s. ' +
827
858
  'If two fields are different but share ' +
@@ -842,10 +873,11 @@ class RelayResponseNormalizer {
842
873
  nextID: DataID,
843
874
  storageKey: string,
844
875
  ): void {
845
- // NOTE: Only call this function in DEV
876
+ // NOTE: Only emit a warning in DEV
846
877
  if (__DEV__) {
878
+ const expected = prevID === undefined || prevID === nextID;
847
879
  warning(
848
- prevID === undefined || prevID === nextID,
880
+ expected,
849
881
  'RelayResponseNormalizer: Invalid record. The record contains ' +
850
882
  'references to the conflicting field, %s and its id values: %s and %s. ' +
851
883
  '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,
@@ -139,22 +139,17 @@ export type MissingClientEdgeRequestInfo = {
139
139
  +clientEdgeDestinationID: DataID,
140
140
  };
141
141
 
142
- export type MissingLiveResolverField = {
143
- +path: string,
144
- +liveStateID: DataID,
145
- };
146
-
147
142
  /**
148
143
  * A representation of a selector and its results at a particular point in time.
149
144
  */
150
145
  export type Snapshot = {
151
146
  +data: ?SelectorData,
152
147
  +isMissingData: boolean,
153
- +missingLiveResolverFields?: $ReadOnlyArray<MissingLiveResolverField>,
148
+ +missingLiveResolverFields?: $ReadOnlyArray<DataID>,
154
149
  +missingClientEdges: null | $ReadOnlyArray<MissingClientEdgeRequestInfo>,
155
150
  +seenRecords: DataIDSet,
156
151
  +selector: SingularReaderSelector,
157
- +errorResponseFields: ?ErrorResponseFields,
152
+ +fieldErrors: ?FieldErrors,
158
153
  };
159
154
 
160
155
  /**
@@ -467,6 +462,7 @@ export interface RecordProxy {
467
462
  ): RecordProxy;
468
463
  getType(): string;
469
464
  getValue(name: string, args?: ?Variables): mixed;
465
+ getErrors(name: string, args?: ?Variables): ?$ReadOnlyArray<TRelayFieldError>;
470
466
  setLinkedRecord(
471
467
  record: RecordProxy,
472
468
  name: string,
@@ -477,7 +473,12 @@ export interface RecordProxy {
477
473
  name: string,
478
474
  args?: ?Variables,
479
475
  ): RecordProxy;
480
- setValue(value: mixed, name: string, args?: ?Variables): RecordProxy;
476
+ setValue(
477
+ value: mixed,
478
+ name: string,
479
+ args?: ?Variables,
480
+ errors?: ?$ReadOnlyArray<TRelayFieldError>,
481
+ ): RecordProxy;
481
482
  invalidateRecord(): void;
482
483
  }
483
484
 
@@ -683,6 +684,11 @@ export type ExecuteCompleteLogEvent = {
683
684
  +executeId: number,
684
685
  };
685
686
 
687
+ export type ExecuteUnsubscribeLogEvent = {
688
+ +name: 'execute.unsubscribe',
689
+ +executeId: number,
690
+ };
691
+
686
692
  export type ExecuteNormalizeStart = {
687
693
  +name: 'execute.normalize.start',
688
694
  +operation: OperationDescriptor,
@@ -780,12 +786,23 @@ export type UseFragmentSubscriptionMissedUpdates = {
780
786
  +hasDataChanges: boolean,
781
787
  };
782
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
+
783
799
  export type LogEvent =
784
800
  | SuspenseFragmentLogEvent
785
801
  | SuspenseQueryLogEvent
786
802
  | QueryResourceFetchLogEvent
787
803
  | QueryResourceRetainLogEvent
788
804
  | FragmentResourceMissingDataLogEvent
805
+ | IdCollisionTypenameLogEvent
789
806
  | PendingOperationFoundLogEvent
790
807
  | NetworkInfoLogEvent
791
808
  | NetworkStartLogEvent
@@ -799,6 +816,7 @@ export type LogEvent =
799
816
  | ExecuteAsyncModuleLogEvent
800
817
  | ExecuteErrorLogEvent
801
818
  | ExecuteCompleteLogEvent
819
+ | ExecuteUnsubscribeLogEvent
802
820
  | ExecuteNormalizeStart
803
821
  | ExecuteNormalizeEnd
804
822
  | StoreDataCheckerStartEvent
@@ -1268,7 +1286,9 @@ export type MissingFieldHandler =
1268
1286
  export type MissingExpectedDataLogEvent = {
1269
1287
  +kind: 'missing_expected_data.log',
1270
1288
  +owner: string,
1271
- +fieldPath: string,
1289
+ fieldPath: string, // Purposefully mutable to allow lazy construction in RelayReader
1290
+ // To populate this, you should pass the value to a ReactRelayLoggingContext
1291
+ +uiContext: mixed | void,
1272
1292
  };
1273
1293
 
1274
1294
  /**
@@ -1294,8 +1314,10 @@ export type MissingExpectedDataLogEvent = {
1294
1314
  export type MissingExpectedDataThrowEvent = {
1295
1315
  +kind: 'missing_expected_data.throw',
1296
1316
  +owner: string,
1297
- +fieldPath: string,
1317
+ fieldPath: string, // Purposefully mutable to allow lazy construction in RelayReader
1298
1318
  +handled: boolean,
1319
+ // To populate this, you should pass the value to a ReactRelayLoggingContext
1320
+ +uiContext: mixed | void,
1299
1321
  };
1300
1322
 
1301
1323
  /**
@@ -1305,7 +1327,9 @@ export type MissingExpectedDataThrowEvent = {
1305
1327
  export type MissingRequiredFieldLogEvent = {
1306
1328
  +kind: 'missing_required_field.log',
1307
1329
  +owner: string,
1308
- +fieldPath: string,
1330
+ fieldPath: string, // Purposefully mutable to allow lazy construction in RelayReader
1331
+ // To populate this, you should pass the value to a ReactRelayLoggingContext
1332
+ +uiContext: mixed | void,
1309
1333
  };
1310
1334
 
1311
1335
  /**
@@ -1322,8 +1346,10 @@ export type MissingRequiredFieldLogEvent = {
1322
1346
  export type MissingRequiredFieldThrowEvent = {
1323
1347
  +kind: 'missing_required_field.throw',
1324
1348
  +owner: string,
1325
- +fieldPath: string,
1349
+ fieldPath: string, // Purposefully mutable to allow lazy construction in RelayReader
1326
1350
  +handled: boolean,
1351
+ // To populate this, you should pass the value to a ReactRelayLoggingContext
1352
+ +uiContext: mixed | void,
1327
1353
  };
1328
1354
 
1329
1355
  /**
@@ -1345,6 +1371,8 @@ export type RelayResolverErrorEvent = {
1345
1371
  +error: Error,
1346
1372
  +shouldThrow: boolean,
1347
1373
  +handled: boolean,
1374
+ // To populate this, you should pass the value to a ReactRelayLoggingContext
1375
+ +uiContext: mixed | void,
1348
1376
  };
1349
1377
 
1350
1378
  /**
@@ -1371,6 +1399,8 @@ export type RelayFieldPayloadErrorEvent = {
1371
1399
  +error: TRelayFieldError,
1372
1400
  +shouldThrow: boolean,
1373
1401
  +handled: boolean,
1402
+ // To populate this, you should pass the value to a ReactRelayLoggingContext
1403
+ +uiContext: mixed | void,
1374
1404
  };
1375
1405
 
1376
1406
  /**
@@ -1480,7 +1510,7 @@ export type ConcreteClientEdgeResolverReturnType<T = any> = {
1480
1510
  * returns a callback which should be called when the value _may_ have changed.
1481
1511
  *
1482
1512
  * While over-notification (subscription notifications when the read value has
1483
- * not actually changed) is suported, for performance reasons, it is recommended
1513
+ * not actually changed) is supported, for performance reasons, it is recommended
1484
1514
  * that the provider of the LiveState value confirms that the value has indeed
1485
1515
  * change before notifying Relay of the change.
1486
1516
  */
@@ -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,15 +298,17 @@ 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,
283
- };
312
+ } as const;
284
313
 
285
314
  module.exports = RelayStoreUtils;