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.
- 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 +12 -8
- package/lib/store/OperationExecutor.js +93 -43
- package/lib/store/RelayModernEnvironment.js +13 -4
- package/lib/store/RelayModernFragmentSpecResolver.js +4 -4
- package/lib/store/RelayModernStore.js +49 -24
- package/lib/store/RelayPublishQueue.js +11 -15
- package/lib/store/RelayReader.js +134 -151
- package/lib/store/RelayReferenceMarker.js +14 -7
- package/lib/store/RelayResponseNormalizer.js +57 -31
- 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 +4 -2
- package/lib/store/live-resolvers/resolverDataInjector.js +4 -4
- package/lib/store/normalizeResponse.js +2 -2
- package/lib/store/observeFragmentExperimental.js +60 -13
- package/lib/store/observeQueryExperimental.js +21 -0
- package/lib/util/RelayFeatureFlags.js +7 -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 +16 -4
- package/store/OperationExecutor.js.flow +101 -15
- 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 +86 -27
- package/store/RelayPublishQueue.js.flow +32 -21
- package/store/RelayReader.js.flow +168 -97
- package/store/RelayReferenceMarker.js.flow +15 -5
- package/store/RelayResponseNormalizer.js.flow +104 -69
- package/store/RelayStoreSubscriptions.js.flow +2 -2
- package/store/RelayStoreTypes.js.flow +34 -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 +7 -2
- package/store/live-resolvers/resolverDataInjector.js.flow +10 -6
- package/store/normalizeResponse.js.flow +2 -0
- 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 +28 -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
|
@@ -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} =
|
|
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
|
-
|
|
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.
|
|
333
|
+
if (!this._useExecTimeResolvers) {
|
|
334
|
+
this._normalizeResolver(selection, record, data);
|
|
335
|
+
}
|
|
326
336
|
break;
|
|
327
337
|
case 'ClientEdgeToClientObject':
|
|
328
|
-
this.
|
|
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
|
-
|
|
476
|
-
|
|
477
|
-
(
|
|
478
|
-
|
|
479
|
-
|
|
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 (
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
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
|
|
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
|
|
783
|
+
} else {
|
|
770
784
|
this._validateRecordType(nextRecord, field, item);
|
|
771
785
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
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
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
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
|
|
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
|
-
|
|
827
|
-
warning(
|
|
859
|
+
const expected =
|
|
828
860
|
storageKey === TYPENAME_KEY ||
|
|
829
|
-
|
|
830
|
-
|
|
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
|
|
885
|
+
// NOTE: Only emit a warning in DEV
|
|
852
886
|
if (__DEV__) {
|
|
887
|
+
const expected = prevID === undefined || prevID === nextID;
|
|
853
888
|
warning(
|
|
854
|
-
|
|
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
|
-
|
|
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
|
|
@@ -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
|
|
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
|
-
|
|
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(
|