relay-runtime 10.0.0 → 10.1.2
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/handlers/RelayDefaultHandlerProvider.js.flow +6 -0
- package/handlers/connection/MutationHandlers.js.flow +121 -3
- package/index.js +1 -1
- package/index.js.flow +16 -1
- package/lib/handlers/RelayDefaultHandlerProvider.js +9 -0
- package/lib/handlers/connection/MutationHandlers.js +147 -14
- package/lib/index.js +7 -0
- package/lib/mutations/RelayDeclarativeMutationConfig.js +5 -7
- package/lib/mutations/commitMutation.js +1 -4
- package/lib/mutations/validateMutation.js +28 -12
- package/lib/network/RelayQueryResponseCache.js +3 -7
- package/lib/query/GraphQLTag.js +2 -1
- package/lib/query/fetchQuery.js +2 -3
- package/lib/query/fetchQueryInternal.js +2 -3
- package/lib/store/DataChecker.js +85 -10
- package/lib/store/RelayConcreteVariables.js +2 -6
- package/lib/store/RelayModernEnvironment.js +81 -72
- package/lib/store/RelayModernFragmentSpecResolver.js +14 -7
- package/lib/store/RelayModernOperationDescriptor.js +6 -5
- package/lib/store/RelayModernQueryExecutor.js +46 -33
- package/lib/store/RelayModernRecord.js +3 -7
- package/lib/store/RelayModernStore.js +45 -143
- package/lib/store/RelayOperationTracker.js +7 -9
- package/lib/store/RelayOptimisticRecordSource.js +2 -6
- package/lib/store/RelayPublishQueue.js +1 -1
- package/lib/store/RelayReader.js +200 -49
- package/lib/store/RelayRecordSourceMapImpl.js +3 -5
- package/lib/store/RelayReferenceMarker.js +87 -5
- package/lib/store/RelayResponseNormalizer.js +123 -54
- package/lib/store/RelayStoreReactFlightUtils.js +47 -0
- package/lib/store/RelayStoreSubscriptions.js +162 -0
- package/lib/store/RelayStoreSubscriptionsUsingMapByID.js +258 -0
- package/lib/store/StoreInspector.js +3 -9
- package/lib/store/createRelayContext.js +5 -0
- package/lib/store/defaultRequiredFieldLogger.js +18 -0
- package/lib/store/normalizeRelayPayload.js +2 -6
- package/lib/subscription/requestSubscription.js +2 -3
- package/lib/util/NormalizationNode.js +1 -5
- package/lib/util/RelayConcreteNode.js +2 -0
- package/lib/util/RelayFeatureFlags.js +6 -2
- package/lib/util/createPayloadFor3DField.js +2 -7
- package/lib/util/getFragmentIdentifier.js +12 -3
- package/lib/util/getOperation.js +33 -0
- package/lib/util/isEmptyObject.js +25 -0
- package/lib/util/recycleNodesInto.js +6 -9
- package/lib/util/reportMissingRequiredFields.js +48 -0
- package/mutations/commitMutation.js.flow +1 -2
- package/mutations/validateMutation.js.flow +34 -5
- package/network/RelayNetworkTypes.js.flow +22 -0
- package/package.json +2 -2
- package/query/GraphQLTag.js.flow +3 -1
- package/query/fetchQuery.js.flow +2 -2
- package/query/fetchQueryInternal.js.flow +0 -5
- package/relay-runtime.js +2 -2
- package/relay-runtime.min.js +2 -2
- package/store/DataChecker.js.flow +68 -2
- package/store/RelayModernEnvironment.js.flow +107 -87
- package/store/RelayModernFragmentSpecResolver.js.flow +13 -1
- package/store/RelayModernOperationDescriptor.js.flow +5 -1
- package/store/RelayModernQueryExecutor.js.flow +47 -23
- package/store/RelayModernStore.js.flow +40 -114
- package/store/RelayPublishQueue.js.flow +1 -1
- package/store/RelayReader.js.flow +184 -27
- package/store/RelayReferenceMarker.js.flow +72 -5
- package/store/RelayResponseNormalizer.js.flow +140 -50
- package/store/RelayStoreReactFlightUtils.js.flow +64 -0
- package/store/RelayStoreSubscriptions.js.flow +168 -0
- package/store/RelayStoreSubscriptionsUsingMapByID.js.flow +259 -0
- package/store/RelayStoreTypes.js.flow +130 -37
- package/store/StoreInspector.js.flow +1 -3
- package/store/createRelayContext.js.flow +3 -0
- package/store/defaultRequiredFieldLogger.js.flow +23 -0
- package/subscription/requestSubscription.js.flow +5 -2
- package/util/NormalizationNode.js.flow +17 -2
- package/util/ReaderNode.js.flow +20 -1
- package/util/RelayConcreteNode.js.flow +6 -0
- package/util/RelayFeatureFlags.js.flow +10 -1
- package/util/getFragmentIdentifier.js.flow +33 -9
- package/util/getOperation.js.flow +40 -0
- package/util/isEmptyObject.js.flow +25 -0
- package/util/recycleNodesInto.js.flow +13 -8
- package/util/reportMissingRequiredFields.js.flow +51 -0
|
@@ -16,6 +16,7 @@ const RelayFeatureFlags = require('../util/RelayFeatureFlags');
|
|
|
16
16
|
const RelayModernRecord = require('./RelayModernRecord');
|
|
17
17
|
const RelayProfiler = require('../util/RelayProfiler');
|
|
18
18
|
|
|
19
|
+
const areEqual = require('areEqual');
|
|
19
20
|
const invariant = require('invariant');
|
|
20
21
|
const warning = require('warning');
|
|
21
22
|
|
|
@@ -23,6 +24,7 @@ const {
|
|
|
23
24
|
CONDITION,
|
|
24
25
|
CLIENT_EXTENSION,
|
|
25
26
|
DEFER,
|
|
27
|
+
FLIGHT_FIELD,
|
|
26
28
|
INLINE_FRAGMENT,
|
|
27
29
|
LINKED_FIELD,
|
|
28
30
|
LINKED_HANDLE,
|
|
@@ -34,6 +36,12 @@ const {
|
|
|
34
36
|
} = require('../util/RelayConcreteNode');
|
|
35
37
|
const {generateClientID, isClientID} = require('./ClientID');
|
|
36
38
|
const {createNormalizationSelector} = require('./RelayModernSelector');
|
|
39
|
+
const {
|
|
40
|
+
refineToReactFlightPayloadData,
|
|
41
|
+
REACT_FLIGHT_QUERIES_STORAGE_KEY,
|
|
42
|
+
REACT_FLIGHT_TREE_STORAGE_KEY,
|
|
43
|
+
REACT_FLIGHT_TYPE_NAME,
|
|
44
|
+
} = require('./RelayStoreReactFlightUtils');
|
|
37
45
|
const {
|
|
38
46
|
getArgumentValues,
|
|
39
47
|
getHandleStorageKey,
|
|
@@ -42,12 +50,14 @@ const {
|
|
|
42
50
|
getStorageKey,
|
|
43
51
|
TYPENAME_KEY,
|
|
44
52
|
ROOT_ID,
|
|
53
|
+
ROOT_TYPE,
|
|
45
54
|
} = require('./RelayStoreUtils');
|
|
46
55
|
const {generateTypeID, TYPE_SCHEMA_TYPE} = require('./TypeID');
|
|
47
56
|
|
|
48
57
|
import type {PayloadData} from '../network/RelayNetworkTypes';
|
|
49
58
|
import type {
|
|
50
59
|
NormalizationDefer,
|
|
60
|
+
NormalizationFlightField,
|
|
51
61
|
NormalizationLinkedField,
|
|
52
62
|
NormalizationModuleImport,
|
|
53
63
|
NormalizationNode,
|
|
@@ -61,6 +71,8 @@ import type {
|
|
|
61
71
|
ModuleImportPayload,
|
|
62
72
|
MutableRecordSource,
|
|
63
73
|
NormalizationSelector,
|
|
74
|
+
ReactFlightReachableQuery,
|
|
75
|
+
ReactFlightPayloadDeserializer,
|
|
64
76
|
Record,
|
|
65
77
|
RelayResponsePayload,
|
|
66
78
|
} from './RelayStoreTypes';
|
|
@@ -74,6 +86,7 @@ export type NormalizationOptions = {|
|
|
|
74
86
|
+getDataID: GetDataID,
|
|
75
87
|
+treatMissingFieldsAsNull: boolean,
|
|
76
88
|
+path?: $ReadOnlyArray<string>,
|
|
89
|
+
+reactFlightPayloadDeserializer?: ?ReactFlightPayloadDeserializer,
|
|
77
90
|
|};
|
|
78
91
|
|
|
79
92
|
/**
|
|
@@ -111,6 +124,7 @@ class RelayResponseNormalizer {
|
|
|
111
124
|
_path: Array<string>;
|
|
112
125
|
_recordSource: MutableRecordSource;
|
|
113
126
|
_variables: Variables;
|
|
127
|
+
_reactFlightPayloadDeserializer: ?ReactFlightPayloadDeserializer;
|
|
114
128
|
|
|
115
129
|
constructor(
|
|
116
130
|
recordSource: MutableRecordSource,
|
|
@@ -127,6 +141,8 @@ class RelayResponseNormalizer {
|
|
|
127
141
|
this._path = options.path ? [...options.path] : [];
|
|
128
142
|
this._recordSource = recordSource;
|
|
129
143
|
this._variables = variables;
|
|
144
|
+
this._reactFlightPayloadDeserializer =
|
|
145
|
+
options.reactFlightPayloadDeserializer;
|
|
130
146
|
}
|
|
131
147
|
|
|
132
148
|
normalizeResponse(
|
|
@@ -277,6 +293,13 @@ class RelayResponseNormalizer {
|
|
|
277
293
|
this._traverseSelections(selection, record, data);
|
|
278
294
|
this._isClientExtension = isClientExtension;
|
|
279
295
|
break;
|
|
296
|
+
case FLIGHT_FIELD:
|
|
297
|
+
if (RelayFeatureFlags.ENABLE_REACT_FLIGHT_COMPONENT_FIELD) {
|
|
298
|
+
this._normalizeFlightField(node, selection, record, data);
|
|
299
|
+
} else {
|
|
300
|
+
throw new Error('Flight fields are not yet supported.');
|
|
301
|
+
}
|
|
302
|
+
break;
|
|
280
303
|
default:
|
|
281
304
|
(selection: empty);
|
|
282
305
|
invariant(
|
|
@@ -441,23 +464,27 @@ class RelayResponseNormalizer {
|
|
|
441
464
|
return;
|
|
442
465
|
}
|
|
443
466
|
}
|
|
444
|
-
if (
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
467
|
+
if (__DEV__) {
|
|
468
|
+
if (selection.kind === SCALAR_FIELD) {
|
|
469
|
+
this._validateConflictingFieldsWithIdenticalId(
|
|
470
|
+
record,
|
|
471
|
+
storageKey,
|
|
472
|
+
fieldValue,
|
|
473
|
+
);
|
|
474
|
+
}
|
|
450
475
|
}
|
|
451
476
|
RelayModernRecord.setValue(record, storageKey, null);
|
|
452
477
|
return;
|
|
453
478
|
}
|
|
454
479
|
|
|
455
480
|
if (selection.kind === SCALAR_FIELD) {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
481
|
+
if (__DEV__) {
|
|
482
|
+
this._validateConflictingFieldsWithIdenticalId(
|
|
483
|
+
record,
|
|
484
|
+
storageKey,
|
|
485
|
+
fieldValue,
|
|
486
|
+
);
|
|
487
|
+
}
|
|
461
488
|
RelayModernRecord.setValue(record, storageKey, fieldValue);
|
|
462
489
|
} else if (selection.kind === LINKED_FIELD) {
|
|
463
490
|
this._path.push(responseKey);
|
|
@@ -477,6 +504,84 @@ class RelayResponseNormalizer {
|
|
|
477
504
|
}
|
|
478
505
|
}
|
|
479
506
|
|
|
507
|
+
_normalizeFlightField(
|
|
508
|
+
parent: NormalizationNode,
|
|
509
|
+
selection: NormalizationFlightField,
|
|
510
|
+
record: Record,
|
|
511
|
+
data: PayloadData,
|
|
512
|
+
) {
|
|
513
|
+
const responseKey = selection.alias || selection.name;
|
|
514
|
+
const storageKey = getStorageKey(selection, this._variables);
|
|
515
|
+
const fieldValue = data[responseKey];
|
|
516
|
+
|
|
517
|
+
if (fieldValue == null) {
|
|
518
|
+
RelayModernRecord.setValue(record, storageKey, null);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const reactFlightPayload = refineToReactFlightPayloadData(fieldValue);
|
|
523
|
+
|
|
524
|
+
invariant(
|
|
525
|
+
reactFlightPayload != null,
|
|
526
|
+
'RelayResponseNormalizer(): Expected React Flight payload data ' +
|
|
527
|
+
'to be an object with `tree` and `queries` properties, got `%s`.',
|
|
528
|
+
fieldValue,
|
|
529
|
+
);
|
|
530
|
+
invariant(
|
|
531
|
+
typeof this._reactFlightPayloadDeserializer === 'function',
|
|
532
|
+
'RelayResponseNormalizer: Expected reactFlightPayloadDeserializer to ' +
|
|
533
|
+
'be a function, got `%s`.',
|
|
534
|
+
this._reactFlightPayloadDeserializer,
|
|
535
|
+
);
|
|
536
|
+
|
|
537
|
+
// We store the deserialized reactFlightClientResponse in a separate
|
|
538
|
+
// record and link it to the parent record. This is so we can GC the Flight
|
|
539
|
+
// tree later even if the parent record is still reachable.
|
|
540
|
+
const reactFlightClientResponse = this._reactFlightPayloadDeserializer(
|
|
541
|
+
reactFlightPayload.tree,
|
|
542
|
+
);
|
|
543
|
+
const reactFlightID = generateClientID(
|
|
544
|
+
RelayModernRecord.getDataID(record),
|
|
545
|
+
getStorageKey(selection, this._variables),
|
|
546
|
+
);
|
|
547
|
+
let reactFlightClientResponseRecord = this._recordSource.get(reactFlightID);
|
|
548
|
+
if (reactFlightClientResponseRecord == null) {
|
|
549
|
+
reactFlightClientResponseRecord = RelayModernRecord.create(
|
|
550
|
+
reactFlightID,
|
|
551
|
+
REACT_FLIGHT_TYPE_NAME,
|
|
552
|
+
);
|
|
553
|
+
this._recordSource.set(reactFlightID, reactFlightClientResponseRecord);
|
|
554
|
+
}
|
|
555
|
+
RelayModernRecord.setValue(
|
|
556
|
+
reactFlightClientResponseRecord,
|
|
557
|
+
REACT_FLIGHT_TREE_STORAGE_KEY,
|
|
558
|
+
reactFlightClientResponse,
|
|
559
|
+
);
|
|
560
|
+
const reachableQueries: Array<ReactFlightReachableQuery> = [];
|
|
561
|
+
for (const query of reactFlightPayload.queries) {
|
|
562
|
+
if (query.response.data != null) {
|
|
563
|
+
this._moduleImportPayloads.push({
|
|
564
|
+
data: query.response.data,
|
|
565
|
+
dataID: ROOT_ID,
|
|
566
|
+
operationReference: query.module,
|
|
567
|
+
path: [],
|
|
568
|
+
typeName: ROOT_TYPE,
|
|
569
|
+
variables: query.variables,
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
reachableQueries.push({
|
|
573
|
+
module: query.module,
|
|
574
|
+
variables: query.variables,
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
RelayModernRecord.setValue(
|
|
578
|
+
reactFlightClientResponseRecord,
|
|
579
|
+
REACT_FLIGHT_QUERIES_STORAGE_KEY,
|
|
580
|
+
reachableQueries,
|
|
581
|
+
);
|
|
582
|
+
RelayModernRecord.setLinkedRecordID(record, storageKey, reactFlightID);
|
|
583
|
+
}
|
|
584
|
+
|
|
480
585
|
_normalizeLink(
|
|
481
586
|
field: NormalizationLinkedField,
|
|
482
587
|
record: Record,
|
|
@@ -490,15 +595,9 @@ class RelayResponseNormalizer {
|
|
|
490
595
|
);
|
|
491
596
|
const nextID =
|
|
492
597
|
this._getDataId(
|
|
493
|
-
|
|
494
|
-
* native_fb,oss) This comment suppresses an error found when Flow
|
|
495
|
-
* v0.98 was deployed. To see the error delete this comment and run
|
|
496
|
-
* Flow. */
|
|
598
|
+
// $FlowFixMe[incompatible-variance]
|
|
497
599
|
fieldValue,
|
|
498
|
-
|
|
499
|
-
* native_fb,oss) This comment suppresses an error found when Flow
|
|
500
|
-
* v0.98 was deployed. To see the error delete this comment and run
|
|
501
|
-
* Flow. */
|
|
600
|
+
// $FlowFixMe[incompatible-variance]
|
|
502
601
|
field.concreteType ?? this._getRecordType(fieldValue),
|
|
503
602
|
) ||
|
|
504
603
|
// Reuse previously generated client IDs
|
|
@@ -520,18 +619,14 @@ class RelayResponseNormalizer {
|
|
|
520
619
|
RelayModernRecord.setLinkedRecordID(record, storageKey, nextID);
|
|
521
620
|
let nextRecord = this._recordSource.get(nextID);
|
|
522
621
|
if (!nextRecord) {
|
|
523
|
-
|
|
524
|
-
* native_fb,oss) This comment suppresses an error found when Flow v0.98
|
|
525
|
-
* was deployed. To see the error delete this comment and run Flow. */
|
|
622
|
+
// $FlowFixMe[incompatible-variance]
|
|
526
623
|
const typeName = field.concreteType || this._getRecordType(fieldValue);
|
|
527
624
|
nextRecord = RelayModernRecord.create(nextID, typeName);
|
|
528
625
|
this._recordSource.set(nextID, nextRecord);
|
|
529
626
|
} else if (__DEV__) {
|
|
530
627
|
this._validateRecordType(nextRecord, field, fieldValue);
|
|
531
628
|
}
|
|
532
|
-
|
|
533
|
-
* fb,oss) This comment suppresses an error found when Flow v0.98 was
|
|
534
|
-
* deployed. To see the error delete this comment and run Flow. */
|
|
629
|
+
// $FlowFixMe[incompatible-variance]
|
|
535
630
|
this._traverseSelections(field, nextRecord, fieldValue);
|
|
536
631
|
}
|
|
537
632
|
|
|
@@ -564,15 +659,9 @@ class RelayResponseNormalizer {
|
|
|
564
659
|
);
|
|
565
660
|
const nextID =
|
|
566
661
|
this._getDataId(
|
|
567
|
-
|
|
568
|
-
* native_fb,oss) This comment suppresses an error found when Flow
|
|
569
|
-
* v0.98 was deployed. To see the error delete this comment and run
|
|
570
|
-
* Flow. */
|
|
662
|
+
// $FlowFixMe[incompatible-variance]
|
|
571
663
|
item,
|
|
572
|
-
|
|
573
|
-
* native_fb,oss) This comment suppresses an error found when Flow
|
|
574
|
-
* v0.98 was deployed. To see the error delete this comment and run
|
|
575
|
-
* Flow. */
|
|
664
|
+
// $FlowFixMe[incompatible-variance]
|
|
576
665
|
field.concreteType ?? this._getRecordType(item),
|
|
577
666
|
) ||
|
|
578
667
|
(prevIDs && prevIDs[nextIndex]) || // Reuse previously generated client IDs:
|
|
@@ -591,27 +680,26 @@ class RelayResponseNormalizer {
|
|
|
591
680
|
nextIDs.push(nextID);
|
|
592
681
|
let nextRecord = this._recordSource.get(nextID);
|
|
593
682
|
if (!nextRecord) {
|
|
594
|
-
|
|
595
|
-
* native_fb,oss) This comment suppresses an error found when Flow
|
|
596
|
-
* v0.98 was deployed. To see the error delete this comment and run
|
|
597
|
-
* Flow. */
|
|
683
|
+
// $FlowFixMe[incompatible-variance]
|
|
598
684
|
const typeName = field.concreteType || this._getRecordType(item);
|
|
599
685
|
nextRecord = RelayModernRecord.create(nextID, typeName);
|
|
600
686
|
this._recordSource.set(nextID, nextRecord);
|
|
601
687
|
} else if (__DEV__) {
|
|
602
688
|
this._validateRecordType(nextRecord, field, item);
|
|
603
689
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
690
|
+
// NOTE: the check to strip __DEV__ code only works for simple
|
|
691
|
+
// `if (__DEV__)`
|
|
692
|
+
if (__DEV__) {
|
|
693
|
+
if (prevIDs) {
|
|
694
|
+
this._validateConflictingLinkedFieldsWithIdenticalId(
|
|
695
|
+
record,
|
|
696
|
+
prevIDs[nextIndex],
|
|
697
|
+
nextID,
|
|
698
|
+
storageKey,
|
|
699
|
+
);
|
|
700
|
+
}
|
|
611
701
|
}
|
|
612
|
-
|
|
613
|
-
* native_fb,oss) This comment suppresses an error found when Flow v0.98
|
|
614
|
-
* was deployed. To see the error delete this comment and run Flow. */
|
|
702
|
+
// $FlowFixMe[incompatible-variance]
|
|
615
703
|
this._traverseSelections(field, nextRecord, item);
|
|
616
704
|
this._path.pop();
|
|
617
705
|
});
|
|
@@ -650,16 +738,17 @@ class RelayResponseNormalizer {
|
|
|
650
738
|
storageKey: string,
|
|
651
739
|
fieldValue: mixed,
|
|
652
740
|
): void {
|
|
741
|
+
// NOTE: Only call this function in DEV
|
|
653
742
|
if (__DEV__) {
|
|
654
743
|
const dataID = RelayModernRecord.getDataID(record);
|
|
655
744
|
var previousValue = RelayModernRecord.getValue(record, storageKey);
|
|
656
745
|
warning(
|
|
657
746
|
storageKey === TYPENAME_KEY ||
|
|
658
747
|
previousValue === undefined ||
|
|
659
|
-
previousValue
|
|
748
|
+
areEqual(previousValue, fieldValue),
|
|
660
749
|
'RelayResponseNormalizer: Invalid record. The record contains two ' +
|
|
661
|
-
'instances of the same id: `%s` with conflicting field, %s and its values: %s and %s.' +
|
|
662
|
-
'If two fields are different but share' +
|
|
750
|
+
'instances of the same id: `%s` with conflicting field, %s and its values: %s and %s. ' +
|
|
751
|
+
'If two fields are different but share ' +
|
|
663
752
|
'the same id, one field will overwrite the other.',
|
|
664
753
|
dataID,
|
|
665
754
|
storageKey,
|
|
@@ -678,6 +767,7 @@ class RelayResponseNormalizer {
|
|
|
678
767
|
nextID: DataID,
|
|
679
768
|
storageKey: string,
|
|
680
769
|
): void {
|
|
770
|
+
// NOTE: Only call this function in DEV
|
|
681
771
|
if (__DEV__) {
|
|
682
772
|
warning(
|
|
683
773
|
prevID === undefined || prevID === nextID,
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its 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
|
|
8
|
+
* @format
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// flowlint ambiguous-object-type:error
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const invariant = require('invariant');
|
|
16
|
+
|
|
17
|
+
const {getType} = require('./RelayModernRecord');
|
|
18
|
+
|
|
19
|
+
import type {ReactFlightPayloadData} from '../network/RelayNetworkTypes';
|
|
20
|
+
import type {ReactFlightClientResponse, Record} from './RelayStoreTypes';
|
|
21
|
+
|
|
22
|
+
const REACT_FLIGHT_QUERIES_STORAGE_KEY = 'queries';
|
|
23
|
+
const REACT_FLIGHT_TREE_STORAGE_KEY = 'tree';
|
|
24
|
+
const REACT_FLIGHT_TYPE_NAME = 'ReactFlightComponent';
|
|
25
|
+
|
|
26
|
+
function refineToReactFlightPayloadData(
|
|
27
|
+
payload: mixed,
|
|
28
|
+
): ?ReactFlightPayloadData {
|
|
29
|
+
if (
|
|
30
|
+
payload == null ||
|
|
31
|
+
typeof payload !== 'object' ||
|
|
32
|
+
!Array.isArray(payload.tree) ||
|
|
33
|
+
!Array.isArray(payload.queries)
|
|
34
|
+
) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return (payload: $FlowFixMe);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getReactFlightClientResponse(
|
|
41
|
+
record: Record,
|
|
42
|
+
): ?ReactFlightClientResponse {
|
|
43
|
+
invariant(
|
|
44
|
+
getType(record) === REACT_FLIGHT_TYPE_NAME,
|
|
45
|
+
'getReactFlightClientResponse(): Expected a ReactFlightComponentRecord, ' +
|
|
46
|
+
'got %s.',
|
|
47
|
+
record,
|
|
48
|
+
);
|
|
49
|
+
const response: ?ReactFlightClientResponse = (record[
|
|
50
|
+
REACT_FLIGHT_TREE_STORAGE_KEY
|
|
51
|
+
]: $FlowFixMe);
|
|
52
|
+
if (response != null) {
|
|
53
|
+
return response;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = {
|
|
59
|
+
REACT_FLIGHT_QUERIES_STORAGE_KEY,
|
|
60
|
+
REACT_FLIGHT_TREE_STORAGE_KEY,
|
|
61
|
+
REACT_FLIGHT_TYPE_NAME,
|
|
62
|
+
getReactFlightClientResponse,
|
|
63
|
+
refineToReactFlightPayloadData,
|
|
64
|
+
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its 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
|
+
*/
|
|
10
|
+
|
|
11
|
+
// flowlint ambiguous-object-type:error
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const RelayReader = require('./RelayReader');
|
|
16
|
+
|
|
17
|
+
const deepFreeze = require('../util/deepFreeze');
|
|
18
|
+
const hasOverlappingIDs = require('./hasOverlappingIDs');
|
|
19
|
+
const isEmptyObject = require('../util/isEmptyObject');
|
|
20
|
+
const recycleNodesInto = require('../util/recycleNodesInto');
|
|
21
|
+
|
|
22
|
+
import type {Disposable} from '../util/RelayRuntimeTypes';
|
|
23
|
+
import type {
|
|
24
|
+
RecordSource,
|
|
25
|
+
RequestDescriptor,
|
|
26
|
+
Snapshot,
|
|
27
|
+
StoreSubscriptions,
|
|
28
|
+
UpdatedRecords,
|
|
29
|
+
} from './RelayStoreTypes';
|
|
30
|
+
|
|
31
|
+
type Subscription = {|
|
|
32
|
+
callback: (snapshot: Snapshot) => void,
|
|
33
|
+
snapshot: Snapshot,
|
|
34
|
+
stale: boolean,
|
|
35
|
+
backup: ?Snapshot,
|
|
36
|
+
|};
|
|
37
|
+
|
|
38
|
+
class RelayStoreSubscriptions implements StoreSubscriptions {
|
|
39
|
+
_subscriptions: Set<Subscription>;
|
|
40
|
+
|
|
41
|
+
constructor() {
|
|
42
|
+
this._subscriptions = new Set();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
subscribe(
|
|
46
|
+
snapshot: Snapshot,
|
|
47
|
+
callback: (snapshot: Snapshot) => void,
|
|
48
|
+
): Disposable {
|
|
49
|
+
const subscription = {backup: null, callback, snapshot, stale: false};
|
|
50
|
+
const dispose = () => {
|
|
51
|
+
this._subscriptions.delete(subscription);
|
|
52
|
+
};
|
|
53
|
+
this._subscriptions.add(subscription);
|
|
54
|
+
return {dispose};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
snapshotSubscriptions(source: RecordSource) {
|
|
58
|
+
this._subscriptions.forEach(subscription => {
|
|
59
|
+
// Backup occurs after writing a new "final" payload(s) and before (re)applying
|
|
60
|
+
// optimistic changes. Each subscription's `snapshot` represents what was *last
|
|
61
|
+
// published to the subscriber*, which notably may include previous optimistic
|
|
62
|
+
// updates. Therefore a subscription can be in any of the following states:
|
|
63
|
+
// - stale=true: This subscription was restored to a different value than
|
|
64
|
+
// `snapshot`. That means this subscription has changes relative to its base,
|
|
65
|
+
// but its base has changed (we just applied a final payload): recompute
|
|
66
|
+
// a backup so that we can later restore to the state the subscription
|
|
67
|
+
// should be in.
|
|
68
|
+
// - stale=false: This subscription was restored to the same value than
|
|
69
|
+
// `snapshot`. That means this subscription does *not* have changes relative
|
|
70
|
+
// to its base, so the current `snapshot` is valid to use as a backup.
|
|
71
|
+
if (!subscription.stale) {
|
|
72
|
+
subscription.backup = subscription.snapshot;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const snapshot = subscription.snapshot;
|
|
76
|
+
const backup = RelayReader.read(source, snapshot.selector);
|
|
77
|
+
const nextData = recycleNodesInto(snapshot.data, backup.data);
|
|
78
|
+
(backup: $FlowFixMe).data = nextData; // backup owns the snapshot and can safely mutate
|
|
79
|
+
subscription.backup = backup;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
restoreSubscriptions() {
|
|
84
|
+
this._subscriptions.forEach(subscription => {
|
|
85
|
+
const backup = subscription.backup;
|
|
86
|
+
subscription.backup = null;
|
|
87
|
+
if (backup) {
|
|
88
|
+
if (backup.data !== subscription.snapshot.data) {
|
|
89
|
+
subscription.stale = true;
|
|
90
|
+
}
|
|
91
|
+
subscription.snapshot = {
|
|
92
|
+
data: subscription.snapshot.data,
|
|
93
|
+
isMissingData: backup.isMissingData,
|
|
94
|
+
seenRecords: backup.seenRecords,
|
|
95
|
+
selector: backup.selector,
|
|
96
|
+
missingRequiredFields: backup.missingRequiredFields,
|
|
97
|
+
};
|
|
98
|
+
} else {
|
|
99
|
+
subscription.stale = true;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
updateSubscriptions(
|
|
105
|
+
source: RecordSource,
|
|
106
|
+
updatedRecordIDs: UpdatedRecords,
|
|
107
|
+
updatedOwners: Array<RequestDescriptor>,
|
|
108
|
+
) {
|
|
109
|
+
const hasUpdatedRecords = !isEmptyObject(updatedRecordIDs);
|
|
110
|
+
this._subscriptions.forEach(subscription => {
|
|
111
|
+
const owner = this._updateSubscription(
|
|
112
|
+
source,
|
|
113
|
+
subscription,
|
|
114
|
+
updatedRecordIDs,
|
|
115
|
+
hasUpdatedRecords,
|
|
116
|
+
);
|
|
117
|
+
if (owner != null) {
|
|
118
|
+
updatedOwners.push(owner);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Notifies the callback for the subscription if the data for the associated
|
|
125
|
+
* snapshot has changed.
|
|
126
|
+
* Additionally, updates the subscription snapshot with the latest snapshot,
|
|
127
|
+
* and marks it as not stale.
|
|
128
|
+
* Returns the owner (RequestDescriptor) if the subscription was affected by the
|
|
129
|
+
* latest update, or null if it was not affected.
|
|
130
|
+
*/
|
|
131
|
+
_updateSubscription(
|
|
132
|
+
source: RecordSource,
|
|
133
|
+
subscription: Subscription,
|
|
134
|
+
updatedRecordIDs: UpdatedRecords,
|
|
135
|
+
hasUpdatedRecords: boolean,
|
|
136
|
+
): ?RequestDescriptor {
|
|
137
|
+
const {backup, callback, snapshot, stale} = subscription;
|
|
138
|
+
const hasOverlappingUpdates =
|
|
139
|
+
hasUpdatedRecords &&
|
|
140
|
+
hasOverlappingIDs(snapshot.seenRecords, updatedRecordIDs);
|
|
141
|
+
if (!stale && !hasOverlappingUpdates) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
let nextSnapshot: Snapshot =
|
|
145
|
+
hasOverlappingUpdates || !backup
|
|
146
|
+
? RelayReader.read(source, snapshot.selector)
|
|
147
|
+
: backup;
|
|
148
|
+
const nextData = recycleNodesInto(snapshot.data, nextSnapshot.data);
|
|
149
|
+
nextSnapshot = ({
|
|
150
|
+
data: nextData,
|
|
151
|
+
isMissingData: nextSnapshot.isMissingData,
|
|
152
|
+
seenRecords: nextSnapshot.seenRecords,
|
|
153
|
+
selector: nextSnapshot.selector,
|
|
154
|
+
missingRequiredFields: nextSnapshot.missingRequiredFields,
|
|
155
|
+
}: Snapshot);
|
|
156
|
+
if (__DEV__) {
|
|
157
|
+
deepFreeze(nextSnapshot);
|
|
158
|
+
}
|
|
159
|
+
subscription.snapshot = nextSnapshot;
|
|
160
|
+
subscription.stale = false;
|
|
161
|
+
if (nextSnapshot.data !== snapshot.data) {
|
|
162
|
+
callback(nextSnapshot);
|
|
163
|
+
return snapshot.selector.owner;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
module.exports = RelayStoreSubscriptions;
|