relay-runtime 10.0.1 → 10.1.3
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 +152 -20
- package/index.js +1 -1
- package/index.js.flow +17 -1
- package/lib/handlers/RelayDefaultHandlerProvider.js +9 -0
- package/lib/handlers/connection/MutationHandlers.js +185 -21
- 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 +39 -137
- 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 +196 -33
- package/lib/store/RelayRecordSourceMapImpl.js +3 -5
- package/lib/store/RelayReferenceMarker.js +87 -5
- package/lib/store/RelayResponseNormalizer.js +115 -19
- 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 +2 -6
- 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 +7 -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 +4 -1
- 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 +33 -107
- package/store/RelayPublishQueue.js.flow +1 -1
- package/store/RelayReader.js.flow +180 -15
- package/store/RelayReferenceMarker.js.flow +72 -5
- package/store/RelayResponseNormalizer.js.flow +130 -19
- 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/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 +12 -1
- package/util/getFragmentIdentifier.js.flow +33 -9
- package/util/getOperation.js.flow +40 -0
- package/util/getRequestIdentifier.js.flow +1 -1
- package/util/isEmptyObject.js.flow +25 -0
- package/util/recycleNodesInto.js.flow +11 -0
- 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,
|
|
@@ -582,13 +687,17 @@ class RelayResponseNormalizer {
|
|
|
582
687
|
} else if (__DEV__) {
|
|
583
688
|
this._validateRecordType(nextRecord, field, item);
|
|
584
689
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
+
}
|
|
592
701
|
}
|
|
593
702
|
// $FlowFixMe[incompatible-variance]
|
|
594
703
|
this._traverseSelections(field, nextRecord, item);
|
|
@@ -629,13 +738,14 @@ class RelayResponseNormalizer {
|
|
|
629
738
|
storageKey: string,
|
|
630
739
|
fieldValue: mixed,
|
|
631
740
|
): void {
|
|
741
|
+
// NOTE: Only call this function in DEV
|
|
632
742
|
if (__DEV__) {
|
|
633
743
|
const dataID = RelayModernRecord.getDataID(record);
|
|
634
744
|
var previousValue = RelayModernRecord.getValue(record, storageKey);
|
|
635
745
|
warning(
|
|
636
746
|
storageKey === TYPENAME_KEY ||
|
|
637
747
|
previousValue === undefined ||
|
|
638
|
-
previousValue
|
|
748
|
+
areEqual(previousValue, fieldValue),
|
|
639
749
|
'RelayResponseNormalizer: Invalid record. The record contains two ' +
|
|
640
750
|
'instances of the same id: `%s` with conflicting field, %s and its values: %s and %s. ' +
|
|
641
751
|
'If two fields are different but share ' +
|
|
@@ -657,6 +767,7 @@ class RelayResponseNormalizer {
|
|
|
657
767
|
nextID: DataID,
|
|
658
768
|
storageKey: string,
|
|
659
769
|
): void {
|
|
770
|
+
// NOTE: Only call this function in DEV
|
|
660
771
|
if (__DEV__) {
|
|
661
772
|
warning(
|
|
662
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;
|