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.
- package/experimental.js +1 -1
- package/experimental.js.flow +8 -6
- package/index.js +1 -1
- package/index.js.flow +3 -0
- package/lib/experimental.js +5 -2
- package/lib/index.js +3 -0
- package/lib/multi-actor-environment/ActorSpecificEnvironment.js +1 -1
- package/lib/mutations/RelayRecordSourceProxy.js +2 -1
- package/lib/mutations/createUpdatableProxy.js +1 -1
- package/lib/mutations/validateMutation.js +2 -2
- package/lib/network/RelayObservable.js +1 -3
- package/lib/network/wrapNetworkWithLogObserver.js +2 -2
- package/lib/query/fetchQuery.js +1 -1
- package/lib/store/DataChecker.js +4 -5
- package/lib/store/OperationExecutor.js +11 -0
- package/lib/store/RelayModernEnvironment.js +13 -4
- package/lib/store/RelayModernFragmentSpecResolver.js +4 -4
- package/lib/store/RelayModernStore.js +43 -21
- package/lib/store/RelayPublishQueue.js +11 -15
- package/lib/store/RelayReader.js +131 -151
- package/lib/store/RelayReferenceMarker.js +3 -4
- package/lib/store/RelayResponseNormalizer.js +47 -26
- package/lib/store/RelayStoreSubscriptions.js +2 -2
- package/lib/store/RelayStoreUtils.js +8 -0
- package/lib/store/ResolverFragments.js +2 -2
- package/lib/store/createRelayLoggingContext.js +17 -0
- package/lib/store/generateTypenamePrefixedDataID.js +9 -0
- package/lib/store/live-resolvers/LiveResolverCache.js +2 -1
- package/lib/store/live-resolvers/resolverDataInjector.js +4 -4
- package/lib/store/observeFragmentExperimental.js +60 -13
- package/lib/store/observeQueryExperimental.js +21 -0
- package/lib/util/RelayFeatureFlags.js +6 -1
- package/lib/util/handlePotentialSnapshotErrors.js +11 -8
- package/multi-actor-environment/ActorSpecificEnvironment.js.flow +1 -0
- package/mutations/RelayRecordSourceProxy.js.flow +4 -0
- package/mutations/createUpdatableProxy.js.flow +1 -1
- package/mutations/validateMutation.js.flow +3 -3
- package/network/RelayNetworkTypes.js.flow +3 -0
- package/network/RelayObservable.js.flow +1 -5
- package/network/wrapNetworkWithLogObserver.js.flow +19 -1
- package/package.json +1 -1
- package/query/fetchQuery.js.flow +1 -1
- package/store/DataChecker.js.flow +5 -2
- package/store/OperationExecutor.js.flow +12 -1
- package/store/RelayExperimentalGraphResponseTransform.js.flow +4 -4
- package/store/RelayModernEnvironment.js.flow +22 -6
- package/store/RelayModernFragmentSpecResolver.js.flow +6 -6
- package/store/RelayModernSelector.js.flow +2 -0
- package/store/RelayModernStore.js.flow +74 -27
- package/store/RelayPublishQueue.js.flow +32 -21
- package/store/RelayReader.js.flow +159 -96
- package/store/RelayReferenceMarker.js.flow +3 -4
- package/store/RelayResponseNormalizer.js.flow +93 -67
- package/store/RelayStoreSubscriptions.js.flow +2 -2
- package/store/RelayStoreTypes.js.flow +33 -4
- package/store/RelayStoreUtils.js.flow +29 -0
- package/store/ResolverCache.js.flow +2 -2
- package/store/ResolverFragments.js.flow +5 -3
- package/store/StoreInspector.js.flow +5 -0
- package/store/createRelayContext.js.flow +3 -2
- package/store/createRelayLoggingContext.js.flow +46 -0
- package/store/generateTypenamePrefixedDataID.js.flow +25 -0
- package/store/live-resolvers/LiveResolverCache.js.flow +2 -1
- package/store/live-resolvers/resolverDataInjector.js.flow +10 -6
- package/store/observeFragmentExperimental.js.flow +82 -28
- package/store/observeQueryExperimental.js.flow +61 -0
- package/store/waitForFragmentExperimental.js.flow +4 -3
- package/util/NormalizationNode.js.flow +2 -1
- package/util/RelayConcreteNode.js.flow +2 -0
- package/util/RelayError.js.flow +1 -0
- package/util/RelayFeatureFlags.js.flow +18 -0
- package/util/RelayRuntimeTypes.js.flow +6 -3
- package/util/getPaginationVariables.js.flow +2 -0
- package/util/handlePotentialSnapshotErrors.js.flow +23 -11
- package/util/registerEnvironmentWithDevTools.js.flow +4 -2
- package/util/withProvidedVariables.js.flow +1 -0
- package/util/withStartAndDuration.js.flow +3 -0
- package/relay-runtime-experimental.js +0 -4
- package/relay-runtime-experimental.min.js +0 -9
- package/relay-runtime.js +0 -4
- package/relay-runtime.min.js +0 -9
|
@@ -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,
|
|
@@ -116,6 +119,7 @@ class RelayResponseNormalizer {
|
|
|
116
119
|
_variables: Variables;
|
|
117
120
|
_shouldProcessClientComponents: ?boolean;
|
|
118
121
|
_errorTrie: RelayErrorTrie | null;
|
|
122
|
+
_log: ?LogFunction;
|
|
119
123
|
|
|
120
124
|
constructor(
|
|
121
125
|
recordSource: MutableRecordSource,
|
|
@@ -134,6 +138,7 @@ class RelayResponseNormalizer {
|
|
|
134
138
|
this._recordSource = recordSource;
|
|
135
139
|
this._variables = variables;
|
|
136
140
|
this._shouldProcessClientComponents = options.shouldProcessClientComponents;
|
|
141
|
+
this._log = options.log;
|
|
137
142
|
}
|
|
138
143
|
|
|
139
144
|
normalizeResponse(
|
|
@@ -319,8 +324,6 @@ class RelayResponseNormalizer {
|
|
|
319
324
|
this._normalizeActorChange(selection, record, data);
|
|
320
325
|
break;
|
|
321
326
|
case 'RelayResolver':
|
|
322
|
-
this._normalizeResolver(selection, record, data);
|
|
323
|
-
break;
|
|
324
327
|
case 'RelayLiveResolver':
|
|
325
328
|
this._normalizeResolver(selection, record, data);
|
|
326
329
|
break;
|
|
@@ -472,12 +475,11 @@ class RelayResponseNormalizer {
|
|
|
472
475
|
const responseKey = selection.alias || selection.name;
|
|
473
476
|
const storageKey = getStorageKey(selection, this._variables);
|
|
474
477
|
const fieldValue = data[responseKey];
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
(
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
) {
|
|
478
|
+
const isNoncompliantlyNullish =
|
|
479
|
+
RelayFeatureFlags.ENABLE_NONCOMPLIANT_ERROR_HANDLING_ON_LISTS &&
|
|
480
|
+
Array.isArray(fieldValue) &&
|
|
481
|
+
fieldValue.length === 0;
|
|
482
|
+
if (fieldValue == null || isNoncompliantlyNullish) {
|
|
481
483
|
if (fieldValue === undefined) {
|
|
482
484
|
// Fields may be missing in the response in two main cases:
|
|
483
485
|
// - Inside a client extension: the server will not generally return
|
|
@@ -511,20 +513,27 @@ class RelayResponseNormalizer {
|
|
|
511
513
|
return;
|
|
512
514
|
}
|
|
513
515
|
}
|
|
514
|
-
if (
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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, []);
|
|
525
533
|
}
|
|
534
|
+
} else {
|
|
535
|
+
RelayModernRecord.setValue(record, storageKey, null);
|
|
526
536
|
}
|
|
527
|
-
RelayModernRecord.setValue(record, storageKey, null);
|
|
528
537
|
const errorTrie = this._errorTrie;
|
|
529
538
|
if (errorTrie != null) {
|
|
530
539
|
const errors = getErrorsByKey(errorTrie, responseKey);
|
|
@@ -536,13 +545,11 @@ class RelayResponseNormalizer {
|
|
|
536
545
|
}
|
|
537
546
|
|
|
538
547
|
if (selection.kind === 'ScalarField') {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
);
|
|
545
|
-
}
|
|
548
|
+
this._validateConflictingFieldsWithIdenticalId(
|
|
549
|
+
record,
|
|
550
|
+
storageKey,
|
|
551
|
+
fieldValue,
|
|
552
|
+
);
|
|
546
553
|
RelayModernRecord.setValue(record, storageKey, fieldValue);
|
|
547
554
|
} else if (selection.kind === 'LinkedField') {
|
|
548
555
|
this._path.push(responseKey);
|
|
@@ -686,13 +693,11 @@ class RelayResponseNormalizer {
|
|
|
686
693
|
'RelayResponseNormalizer: Expected id on field `%s` to be a string.',
|
|
687
694
|
storageKey,
|
|
688
695
|
);
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
);
|
|
695
|
-
}
|
|
696
|
+
this._validateConflictingLinkedFieldsWithIdenticalId(
|
|
697
|
+
RelayModernRecord.getLinkedRecordID(record, storageKey),
|
|
698
|
+
nextID,
|
|
699
|
+
storageKey,
|
|
700
|
+
);
|
|
696
701
|
RelayModernRecord.setLinkedRecordID(record, storageKey, nextID);
|
|
697
702
|
let nextRecord = this._recordSource.get(nextID);
|
|
698
703
|
if (!nextRecord) {
|
|
@@ -700,7 +705,7 @@ class RelayResponseNormalizer {
|
|
|
700
705
|
const typeName = field.concreteType || this._getRecordType(fieldValue);
|
|
701
706
|
nextRecord = RelayModernRecord.create(nextID, typeName);
|
|
702
707
|
this._recordSource.set(nextID, nextRecord);
|
|
703
|
-
} else
|
|
708
|
+
} else {
|
|
704
709
|
this._validateRecordType(nextRecord, field, fieldValue);
|
|
705
710
|
}
|
|
706
711
|
// $FlowFixMe[incompatible-variance]
|
|
@@ -766,19 +771,15 @@ class RelayResponseNormalizer {
|
|
|
766
771
|
const typeName = field.concreteType || this._getRecordType(item);
|
|
767
772
|
nextRecord = RelayModernRecord.create(nextID, typeName);
|
|
768
773
|
this._recordSource.set(nextID, nextRecord);
|
|
769
|
-
} else
|
|
774
|
+
} else {
|
|
770
775
|
this._validateRecordType(nextRecord, field, item);
|
|
771
776
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
nextID,
|
|
779
|
-
storageKey,
|
|
780
|
-
);
|
|
781
|
-
}
|
|
777
|
+
if (prevIDs) {
|
|
778
|
+
this._validateConflictingLinkedFieldsWithIdenticalId(
|
|
779
|
+
prevIDs[nextIndex],
|
|
780
|
+
nextID,
|
|
781
|
+
storageKey,
|
|
782
|
+
);
|
|
782
783
|
}
|
|
783
784
|
// $FlowFixMe[incompatible-variance]
|
|
784
785
|
this._traverseSelections(field, nextRecord, item);
|
|
@@ -796,20 +797,42 @@ class RelayResponseNormalizer {
|
|
|
796
797
|
field: NormalizationLinkedField,
|
|
797
798
|
payload: Object,
|
|
798
799
|
): void {
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
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
|
+
}
|
|
813
836
|
}
|
|
814
837
|
|
|
815
838
|
/**
|
|
@@ -820,14 +843,16 @@ class RelayResponseNormalizer {
|
|
|
820
843
|
storageKey: string,
|
|
821
844
|
fieldValue: mixed,
|
|
822
845
|
): void {
|
|
823
|
-
// NOTE: Only
|
|
846
|
+
// NOTE: Only emit a warning in DEV
|
|
824
847
|
if (__DEV__) {
|
|
848
|
+
const previousValue = RelayModernRecord.getValue(record, storageKey);
|
|
825
849
|
const dataID = RelayModernRecord.getDataID(record);
|
|
826
|
-
|
|
827
|
-
warning(
|
|
850
|
+
const expected =
|
|
828
851
|
storageKey === TYPENAME_KEY ||
|
|
829
|
-
|
|
830
|
-
|
|
852
|
+
previousValue === undefined ||
|
|
853
|
+
areEqual(previousValue, fieldValue);
|
|
854
|
+
warning(
|
|
855
|
+
expected,
|
|
831
856
|
'RelayResponseNormalizer: Invalid record. The record contains two ' +
|
|
832
857
|
'instances of the same id: `%s` with conflicting field, %s and its values: %s and %s. ' +
|
|
833
858
|
'If two fields are different but share ' +
|
|
@@ -848,10 +873,11 @@ class RelayResponseNormalizer {
|
|
|
848
873
|
nextID: DataID,
|
|
849
874
|
storageKey: string,
|
|
850
875
|
): void {
|
|
851
|
-
// NOTE: Only
|
|
876
|
+
// NOTE: Only emit a warning in DEV
|
|
852
877
|
if (__DEV__) {
|
|
878
|
+
const expected = prevID === undefined || prevID === nextID;
|
|
853
879
|
warning(
|
|
854
|
-
|
|
880
|
+
expected,
|
|
855
881
|
'RelayResponseNormalizer: Invalid record. The record contains ' +
|
|
856
882
|
'references to the conflicting field, %s and its id values: %s and %s. ' +
|
|
857
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
+
|
|
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
|
|
@@ -1270,6 +1287,8 @@ export type MissingExpectedDataLogEvent = {
|
|
|
1270
1287
|
+kind: 'missing_expected_data.log',
|
|
1271
1288
|
+owner: string,
|
|
1272
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,
|
|
1273
1292
|
};
|
|
1274
1293
|
|
|
1275
1294
|
/**
|
|
@@ -1297,6 +1316,8 @@ export type MissingExpectedDataThrowEvent = {
|
|
|
1297
1316
|
+owner: string,
|
|
1298
1317
|
fieldPath: string, // Purposefully mutable to allow lazy construction in RelayReader
|
|
1299
1318
|
+handled: boolean,
|
|
1319
|
+
// To populate this, you should pass the value to a ReactRelayLoggingContext
|
|
1320
|
+
+uiContext: mixed | void,
|
|
1300
1321
|
};
|
|
1301
1322
|
|
|
1302
1323
|
/**
|
|
@@ -1307,6 +1328,8 @@ export type MissingRequiredFieldLogEvent = {
|
|
|
1307
1328
|
+kind: 'missing_required_field.log',
|
|
1308
1329
|
+owner: string,
|
|
1309
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,
|
|
1310
1333
|
};
|
|
1311
1334
|
|
|
1312
1335
|
/**
|
|
@@ -1325,6 +1348,8 @@ export type MissingRequiredFieldThrowEvent = {
|
|
|
1325
1348
|
+owner: string,
|
|
1326
1349
|
fieldPath: string, // Purposefully mutable to allow lazy construction in RelayReader
|
|
1327
1350
|
+handled: boolean,
|
|
1351
|
+
// To populate this, you should pass the value to a ReactRelayLoggingContext
|
|
1352
|
+
+uiContext: mixed | void,
|
|
1328
1353
|
};
|
|
1329
1354
|
|
|
1330
1355
|
/**
|
|
@@ -1346,6 +1371,8 @@ export type RelayResolverErrorEvent = {
|
|
|
1346
1371
|
+error: Error,
|
|
1347
1372
|
+shouldThrow: boolean,
|
|
1348
1373
|
+handled: boolean,
|
|
1374
|
+
// To populate this, you should pass the value to a ReactRelayLoggingContext
|
|
1375
|
+
+uiContext: mixed | void,
|
|
1349
1376
|
};
|
|
1350
1377
|
|
|
1351
1378
|
/**
|
|
@@ -1372,6 +1399,8 @@ export type RelayFieldPayloadErrorEvent = {
|
|
|
1372
1399
|
+error: TRelayFieldError,
|
|
1373
1400
|
+shouldThrow: boolean,
|
|
1374
1401
|
+handled: boolean,
|
|
1402
|
+
// To populate this, you should pass the value to a ReactRelayLoggingContext
|
|
1403
|
+
+uiContext: mixed | void,
|
|
1375
1404
|
};
|
|
1376
1405
|
|
|
1377
1406
|
/**
|
|
@@ -1481,7 +1510,7 @@ export type ConcreteClientEdgeResolverReturnType<T = any> = {
|
|
|
1481
1510
|
* returns a callback which should be called when the value _may_ have changed.
|
|
1482
1511
|
*
|
|
1483
1512
|
* While over-notification (subscription notifications when the read value has
|
|
1484
|
-
* not actually changed) is
|
|
1513
|
+
* not actually changed) is supported, for performance reasons, it is recommended
|
|
1485
1514
|
* that the provider of the LiveState value confirms that the value has indeed
|
|
1486
1515
|
* change before notifying Relay of the change.
|
|
1487
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,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
|
-
|
|
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
|
-
|
|
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,
|
|
116
|
-
|
|
115
|
+
const {data, isMissingData, fieldErrors} = context.getDataForResolverFragment(
|
|
116
|
+
fragmentSelector,
|
|
117
|
+
fragmentKey,
|
|
118
|
+
);
|
|
117
119
|
|
|
118
120
|
if (
|
|
119
121
|
isMissingData ||
|
|
120
|
-
(
|
|
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(
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
'use strict';
|
|
13
13
|
|
|
14
14
|
import type {RelayContext} from './RelayStoreTypes.js';
|
|
15
|
+
import type {Context} from 'react';
|
|
15
16
|
import typeof {createContext} from 'react';
|
|
16
17
|
|
|
17
18
|
const invariant = require('invariant');
|
|
@@ -24,10 +25,10 @@ type React = $ReadOnly<{
|
|
|
24
25
|
...
|
|
25
26
|
}>;
|
|
26
27
|
|
|
27
|
-
let relayContext: ?
|
|
28
|
+
let relayContext: ?Context<RelayContext | null>;
|
|
28
29
|
let firstReact: ?React;
|
|
29
30
|
|
|
30
|
-
function createRelayContext(react: React):
|
|
31
|
+
function createRelayContext(react: React): Context<RelayContext | null> {
|
|
31
32
|
if (!relayContext) {
|
|
32
33
|
relayContext = react.createContext(null);
|
|
33
34
|
if (__DEV__) {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @flow strict-local
|
|
8
|
+
* @format
|
|
9
|
+
* @oncall relay
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
import type {Context} from 'react';
|
|
15
|
+
import typeof {createContext} from 'react';
|
|
16
|
+
|
|
17
|
+
const invariant = require('invariant');
|
|
18
|
+
|
|
19
|
+
// Ideally, we'd just import the type of the react module, but this causes Flow
|
|
20
|
+
// problems.
|
|
21
|
+
type React = $ReadOnly<{
|
|
22
|
+
createContext: createContext<mixed | null>,
|
|
23
|
+
version: string,
|
|
24
|
+
...
|
|
25
|
+
}>;
|
|
26
|
+
|
|
27
|
+
let relayLoggingContext: ?Context<mixed | null>;
|
|
28
|
+
let firstReact: ?React;
|
|
29
|
+
|
|
30
|
+
function createRelayLoggingContext(react: React): Context<mixed | null> {
|
|
31
|
+
if (!relayLoggingContext) {
|
|
32
|
+
relayLoggingContext = react.createContext(null);
|
|
33
|
+
if (__DEV__) {
|
|
34
|
+
relayLoggingContext.displayName = 'RelayLoggingContext';
|
|
35
|
+
}
|
|
36
|
+
firstReact = react;
|
|
37
|
+
}
|
|
38
|
+
invariant(
|
|
39
|
+
react === firstReact,
|
|
40
|
+
'[createRelayLoggingContext]: You are passing a different instance of React',
|
|
41
|
+
react.version,
|
|
42
|
+
);
|
|
43
|
+
return relayLoggingContext;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = createRelayLoggingContext;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @flow strict-local
|
|
8
|
+
* @format
|
|
9
|
+
* @oncall relay
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
import type {DataID} from 'relay-runtime/util/RelayRuntimeTypes';
|
|
15
|
+
|
|
16
|
+
const TYPENAME_PREFIX = '__type:';
|
|
17
|
+
|
|
18
|
+
function generateTypenamePrefixedDataID(
|
|
19
|
+
typeName: string,
|
|
20
|
+
dataID: DataID,
|
|
21
|
+
): DataID {
|
|
22
|
+
return `${TYPENAME_PREFIX}${typeName}:${dataID}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = {generateTypenamePrefixedDataID};
|
|
@@ -48,6 +48,7 @@ const {
|
|
|
48
48
|
RELAY_RESOLVER_OUTPUT_TYPE_RECORD_IDS,
|
|
49
49
|
RELAY_RESOLVER_SNAPSHOT_KEY,
|
|
50
50
|
RELAY_RESOLVER_VALUE_KEY,
|
|
51
|
+
getReadTimeResolverStorageKey,
|
|
51
52
|
getStorageKey,
|
|
52
53
|
} = require('../RelayStoreUtils');
|
|
53
54
|
const getOutputTypeRecordIDs = require('./getOutputTypeRecordIDs');
|
|
@@ -129,7 +130,7 @@ class LiveResolverCache implements ResolverCache {
|
|
|
129
130
|
// resolvers on this parent record.
|
|
130
131
|
const record = expectRecord(recordSource, recordID);
|
|
131
132
|
|
|
132
|
-
const storageKey =
|
|
133
|
+
const storageKey = getReadTimeResolverStorageKey(field, variables);
|
|
133
134
|
let linkedID = RelayModernRecord.getLinkedRecordID(record, storageKey);
|
|
134
135
|
let linkedRecord = linkedID == null ? null : recordSource.get(linkedID);
|
|
135
136
|
|