relay-runtime 8.0.0 → 10.0.1
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 +41 -0
- package/handlers/connection/ConnectionHandler.js.flow +549 -0
- package/handlers/connection/ConnectionInterface.js.flow +92 -0
- package/handlers/connection/MutationHandlers.js.flow +88 -0
- package/index.js +1 -1
- package/index.js.flow +320 -0
- package/lib/handlers/RelayDefaultHandlerProvider.js +13 -2
- package/lib/handlers/connection/{RelayConnectionHandler.js → ConnectionHandler.js} +33 -35
- package/lib/handlers/connection/{RelayConnectionInterface.js → ConnectionInterface.js} +2 -2
- package/lib/handlers/connection/MutationHandlers.js +86 -0
- package/lib/index.js +15 -19
- package/lib/mutations/RelayDeclarativeMutationConfig.js +29 -52
- package/lib/mutations/RelayRecordProxy.js +1 -3
- package/lib/mutations/RelayRecordSourceMutator.js +2 -9
- package/lib/mutations/RelayRecordSourceProxy.js +2 -4
- package/lib/mutations/RelayRecordSourceSelectorProxy.js +1 -13
- package/lib/mutations/commitMutation.js +13 -3
- package/lib/mutations/validateMutation.js +16 -9
- package/lib/network/RelayObservable.js +9 -9
- package/lib/network/RelayQueryResponseCache.js +8 -6
- package/lib/query/PreloadableQueryRegistry.js +70 -0
- package/lib/query/fetchQueryInternal.js +31 -23
- package/lib/store/DataChecker.js +122 -110
- package/lib/store/RelayConcreteVariables.js +6 -2
- package/lib/store/RelayModernEnvironment.js +121 -67
- package/lib/store/RelayModernFragmentSpecResolver.js +12 -16
- package/lib/store/RelayModernQueryExecutor.js +389 -314
- package/lib/store/RelayModernRecord.js +14 -9
- package/lib/store/RelayModernSelector.js +7 -3
- package/lib/store/RelayModernStore.js +289 -484
- package/lib/store/RelayOperationTracker.js +35 -78
- package/lib/store/RelayOptimisticRecordSource.js +7 -5
- package/lib/store/RelayPublishQueue.js +6 -33
- package/lib/store/RelayReader.js +113 -45
- package/lib/store/RelayRecordSource.js +2 -9
- package/lib/store/RelayRecordSourceMapImpl.js +13 -18
- package/lib/store/RelayReferenceMarker.js +40 -60
- package/lib/store/RelayResponseNormalizer.js +158 -193
- package/lib/store/RelayStoreUtils.js +1 -0
- package/lib/store/StoreInspector.js +8 -8
- package/lib/store/TypeID.js +28 -0
- package/lib/store/cloneRelayScalarHandleSourceField.js +44 -0
- package/lib/store/normalizeRelayPayload.js +6 -2
- package/lib/store/readInlineData.js +1 -1
- package/lib/subscription/requestSubscription.js +5 -3
- package/lib/util/RelayConcreteNode.js +9 -6
- package/lib/util/RelayError.js +39 -9
- package/lib/util/RelayFeatureFlags.js +2 -5
- package/lib/util/RelayReplaySubject.js +3 -3
- package/lib/util/createPayloadFor3DField.js +7 -2
- package/lib/util/getRequestIdentifier.js +2 -2
- package/lib/util/recycleNodesInto.js +2 -6
- package/mutations/RelayDeclarativeMutationConfig.js.flow +380 -0
- package/mutations/RelayRecordProxy.js.flow +165 -0
- package/mutations/RelayRecordSourceMutator.js.flow +238 -0
- package/mutations/RelayRecordSourceProxy.js.flow +164 -0
- package/mutations/RelayRecordSourceSelectorProxy.js.flow +119 -0
- package/mutations/applyOptimisticMutation.js.flow +76 -0
- package/mutations/commitLocalUpdate.js.flow +24 -0
- package/mutations/commitMutation.js.flow +182 -0
- package/mutations/validateMutation.js.flow +213 -0
- package/network/ConvertToExecuteFunction.js.flow +49 -0
- package/network/RelayNetwork.js.flow +84 -0
- package/network/RelayNetworkTypes.js.flow +123 -0
- package/network/RelayObservable.js.flow +634 -0
- package/network/RelayQueryResponseCache.js.flow +111 -0
- package/package.json +1 -1
- package/query/GraphQLTag.js.flow +166 -0
- package/query/PreloadableQueryRegistry.js.flow +65 -0
- package/query/fetchQuery.js.flow +47 -0
- package/query/fetchQueryInternal.js.flow +348 -0
- package/relay-runtime.js +2 -2
- package/relay-runtime.min.js +2 -2
- package/store/ClientID.js.flow +43 -0
- package/store/DataChecker.js.flow +502 -0
- package/store/RelayConcreteVariables.js.flow +96 -0
- package/store/RelayModernEnvironment.js.flow +551 -0
- package/store/RelayModernFragmentSpecResolver.js.flow +426 -0
- package/store/RelayModernOperationDescriptor.js.flow +88 -0
- package/store/RelayModernQueryExecutor.js.flow +1321 -0
- package/store/RelayModernRecord.js.flow +403 -0
- package/store/RelayModernSelector.js.flow +455 -0
- package/store/RelayModernStore.js.flow +842 -0
- package/store/RelayOperationTracker.js.flow +164 -0
- package/store/RelayOptimisticRecordSource.js.flow +119 -0
- package/store/RelayPublishQueue.js.flow +401 -0
- package/store/RelayReader.js.flow +473 -0
- package/store/RelayRecordSource.js.flow +29 -0
- package/store/RelayRecordSourceMapImpl.js.flow +87 -0
- package/store/RelayRecordState.js.flow +37 -0
- package/store/RelayReferenceMarker.js.flow +257 -0
- package/store/RelayResponseNormalizer.js.flow +680 -0
- package/store/RelayStoreTypes.js.flow +899 -0
- package/store/RelayStoreUtils.js.flow +219 -0
- package/store/StoreInspector.js.flow +171 -0
- package/store/TypeID.js.flow +28 -0
- package/store/ViewerPattern.js.flow +26 -0
- package/store/cloneRelayHandleSourceField.js.flow +66 -0
- package/store/cloneRelayScalarHandleSourceField.js.flow +62 -0
- package/store/createFragmentSpecResolver.js.flow +55 -0
- package/store/createRelayContext.js.flow +44 -0
- package/store/defaultGetDataID.js.flow +27 -0
- package/store/hasOverlappingIDs.js.flow +34 -0
- package/store/isRelayModernEnvironment.js.flow +27 -0
- package/store/normalizeRelayPayload.js.flow +51 -0
- package/store/readInlineData.js.flow +75 -0
- package/subscription/requestSubscription.js.flow +100 -0
- package/util/JSResourceTypes.flow.js.flow +20 -0
- package/util/NormalizationNode.js.flow +198 -0
- package/util/ReaderNode.js.flow +208 -0
- package/util/RelayConcreteNode.js.flow +93 -0
- package/util/RelayDefaultHandleKey.js.flow +17 -0
- package/util/RelayError.js.flow +62 -0
- package/util/RelayFeatureFlags.js.flow +30 -0
- package/util/RelayProfiler.js.flow +284 -0
- package/util/RelayReplaySubject.js.flow +135 -0
- package/util/RelayRuntimeTypes.js.flow +72 -0
- package/util/createPayloadFor3DField.js.flow +43 -0
- package/util/deepFreeze.js.flow +36 -0
- package/util/generateID.js.flow +21 -0
- package/util/getFragmentIdentifier.js.flow +52 -0
- package/util/getRelayHandleKey.js.flow +41 -0
- package/util/getRequestIdentifier.js.flow +42 -0
- package/util/isPromise.js.flow +21 -0
- package/util/isScalarAndEqual.js.flow +26 -0
- package/util/recycleNodesInto.js.flow +76 -0
- package/util/resolveImmediate.js.flow +30 -0
- package/util/stableCopy.js.flow +35 -0
- package/lib/handlers/RelayDefaultMissingFieldHandlers.js +0 -26
- package/lib/handlers/getRelayDefaultMissingFieldHandlers.js +0 -36
- package/lib/query/RelayModernGraphQLTag.js +0 -104
- package/lib/store/RelayConnection.js +0 -37
- package/lib/store/RelayConnectionResolver.js +0 -178
- package/lib/store/RelayRecordSourceObjectImpl.js +0 -79
- package/lib/util/getFragmentSpecIdentifier.js +0 -27
|
@@ -0,0 +1,164 @@
|
|
|
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 invariant = require('invariant');
|
|
16
|
+
|
|
17
|
+
import type {RequestDescriptor} from './RelayStoreTypes';
|
|
18
|
+
|
|
19
|
+
class RelayOperationTracker {
|
|
20
|
+
_ownersToPendingOperations: Map<RequestDescriptor, Set<RequestDescriptor>>;
|
|
21
|
+
_pendingOperationsToOwners: Map<RequestDescriptor, Set<RequestDescriptor>>;
|
|
22
|
+
_ownersToPromise: Map<
|
|
23
|
+
RequestDescriptor,
|
|
24
|
+
{|promise: Promise<void>, resolve: () => void|},
|
|
25
|
+
>;
|
|
26
|
+
|
|
27
|
+
constructor() {
|
|
28
|
+
this._ownersToPendingOperations = new Map();
|
|
29
|
+
this._pendingOperationsToOwners = new Map();
|
|
30
|
+
this._ownersToPromise = new Map();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Update the map of current processing operations with the set of
|
|
35
|
+
* affected owners and notify subscribers
|
|
36
|
+
*/
|
|
37
|
+
update(
|
|
38
|
+
pendingOperation: RequestDescriptor,
|
|
39
|
+
affectedOwners: Set<RequestDescriptor>,
|
|
40
|
+
): void {
|
|
41
|
+
if (affectedOwners.size === 0) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const newlyAffectedOwners = new Set();
|
|
45
|
+
for (const owner of affectedOwners) {
|
|
46
|
+
const pendingOperationsAffectingOwner = this._ownersToPendingOperations.get(
|
|
47
|
+
owner,
|
|
48
|
+
);
|
|
49
|
+
if (pendingOperationsAffectingOwner != null) {
|
|
50
|
+
// In this case the `owner` already affected by some operations
|
|
51
|
+
// We just need to detect, is it the same operation that we already
|
|
52
|
+
// have in the list, or it's a new operation
|
|
53
|
+
if (!pendingOperationsAffectingOwner.has(pendingOperation)) {
|
|
54
|
+
pendingOperationsAffectingOwner.add(pendingOperation);
|
|
55
|
+
newlyAffectedOwners.add(owner);
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
// This is a new `owner` that is affected by the operation
|
|
59
|
+
this._ownersToPendingOperations.set(owner, new Set([pendingOperation]));
|
|
60
|
+
newlyAffectedOwners.add(owner);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// No new owners were affected by this operation, we may stop here
|
|
65
|
+
if (newlyAffectedOwners.size === 0) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// But, if some owners were affected we need to add them to
|
|
70
|
+
// the `_pendingOperationsToOwners` set
|
|
71
|
+
const ownersAffectedByOperation =
|
|
72
|
+
this._pendingOperationsToOwners.get(pendingOperation) || new Set();
|
|
73
|
+
|
|
74
|
+
for (const owner of newlyAffectedOwners) {
|
|
75
|
+
this._resolveOwnerResolvers(owner);
|
|
76
|
+
ownersAffectedByOperation.add(owner);
|
|
77
|
+
}
|
|
78
|
+
this._pendingOperationsToOwners.set(
|
|
79
|
+
pendingOperation,
|
|
80
|
+
ownersAffectedByOperation,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Once pending operation is completed we need to remove it
|
|
86
|
+
* from all tracking maps
|
|
87
|
+
*/
|
|
88
|
+
complete(pendingOperation: RequestDescriptor): void {
|
|
89
|
+
const affectedOwners = this._pendingOperationsToOwners.get(
|
|
90
|
+
pendingOperation,
|
|
91
|
+
);
|
|
92
|
+
if (affectedOwners == null) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// These were the owners affected only by `pendingOperation`
|
|
96
|
+
const completedOwners = new Set();
|
|
97
|
+
|
|
98
|
+
// These were the owners affected by `pendingOperation`
|
|
99
|
+
// and some other operations
|
|
100
|
+
const updatedOwners = new Set();
|
|
101
|
+
for (const owner of affectedOwners) {
|
|
102
|
+
const pendingOperationsAffectingOwner = this._ownersToPendingOperations.get(
|
|
103
|
+
owner,
|
|
104
|
+
);
|
|
105
|
+
if (!pendingOperationsAffectingOwner) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
pendingOperationsAffectingOwner.delete(pendingOperation);
|
|
109
|
+
if (pendingOperationsAffectingOwner.size > 0) {
|
|
110
|
+
updatedOwners.add(owner);
|
|
111
|
+
} else {
|
|
112
|
+
completedOwners.add(owner);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Complete subscriptions for all owners, affected by `pendingOperation`
|
|
117
|
+
for (const owner of completedOwners) {
|
|
118
|
+
this._resolveOwnerResolvers(owner);
|
|
119
|
+
this._ownersToPendingOperations.delete(owner);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Update all owner that were updated by `pendingOperation` but still
|
|
123
|
+
// are affected by other operations
|
|
124
|
+
for (const owner of updatedOwners) {
|
|
125
|
+
this._resolveOwnerResolvers(owner);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Finally, remove pending operation
|
|
129
|
+
this._pendingOperationsToOwners.delete(pendingOperation);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
_resolveOwnerResolvers(owner: RequestDescriptor): void {
|
|
133
|
+
const promiseEntry = this._ownersToPromise.get(owner);
|
|
134
|
+
if (promiseEntry != null) {
|
|
135
|
+
promiseEntry.resolve();
|
|
136
|
+
}
|
|
137
|
+
this._ownersToPromise.delete(owner);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
getPromiseForPendingOperationsAffectingOwner(
|
|
141
|
+
owner: RequestDescriptor,
|
|
142
|
+
): Promise<void> | null {
|
|
143
|
+
if (!this._ownersToPendingOperations.has(owner)) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
const cachedPromiseEntry = this._ownersToPromise.get(owner);
|
|
147
|
+
if (cachedPromiseEntry != null) {
|
|
148
|
+
return cachedPromiseEntry.promise;
|
|
149
|
+
}
|
|
150
|
+
let resolve;
|
|
151
|
+
const promise = new Promise(r => {
|
|
152
|
+
resolve = r;
|
|
153
|
+
});
|
|
154
|
+
invariant(
|
|
155
|
+
resolve != null,
|
|
156
|
+
'RelayOperationTracker: Expected resolver to be defined. If you' +
|
|
157
|
+
'are seeing this, it is likely a bug in Relay.',
|
|
158
|
+
);
|
|
159
|
+
this._ownersToPromise.set(owner, {promise, resolve});
|
|
160
|
+
return promise;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = RelayOperationTracker;
|
|
@@ -0,0 +1,119 @@
|
|
|
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 RelayRecordSource = require('./RelayRecordSource');
|
|
16
|
+
|
|
17
|
+
import type {DataID} from '../util/RelayRuntimeTypes';
|
|
18
|
+
import type {RecordState} from './RelayRecordState';
|
|
19
|
+
import type {
|
|
20
|
+
MutableRecordSource,
|
|
21
|
+
Record,
|
|
22
|
+
RecordSource,
|
|
23
|
+
} from './RelayStoreTypes';
|
|
24
|
+
|
|
25
|
+
const UNPUBLISH_RECORD_SENTINEL = Object.freeze({
|
|
26
|
+
__UNPUBLISH_RECORD_SENTINEL: true,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* An implementation of MutableRecordSource that represents a base RecordSource
|
|
31
|
+
* with optimistic updates stacked on top: records with optimistic updates
|
|
32
|
+
* shadow the base version of the record rather than updating/replacing them.
|
|
33
|
+
*/
|
|
34
|
+
class RelayOptimisticRecordSource implements MutableRecordSource {
|
|
35
|
+
_base: RecordSource;
|
|
36
|
+
_sink: MutableRecordSource;
|
|
37
|
+
|
|
38
|
+
constructor(base: RecordSource): void {
|
|
39
|
+
this._base = base;
|
|
40
|
+
this._sink = RelayRecordSource.create();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
has(dataID: DataID): boolean {
|
|
44
|
+
if (this._sink.has(dataID)) {
|
|
45
|
+
const sinkRecord = this._sink.get(dataID);
|
|
46
|
+
return sinkRecord !== UNPUBLISH_RECORD_SENTINEL;
|
|
47
|
+
} else {
|
|
48
|
+
return this._base.has(dataID);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get(dataID: DataID): ?Record {
|
|
53
|
+
if (this._sink.has(dataID)) {
|
|
54
|
+
const sinkRecord = this._sink.get(dataID);
|
|
55
|
+
if (sinkRecord === UNPUBLISH_RECORD_SENTINEL) {
|
|
56
|
+
return undefined;
|
|
57
|
+
} else {
|
|
58
|
+
return sinkRecord;
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
return this._base.get(dataID);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getStatus(dataID: DataID): RecordState {
|
|
66
|
+
const record = this.get(dataID);
|
|
67
|
+
if (record === undefined) {
|
|
68
|
+
return 'UNKNOWN';
|
|
69
|
+
} else if (record === null) {
|
|
70
|
+
return 'NONEXISTENT';
|
|
71
|
+
} else {
|
|
72
|
+
return 'EXISTENT';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
clear(): void {
|
|
77
|
+
this._base = RelayRecordSource.create();
|
|
78
|
+
this._sink.clear();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
delete(dataID: DataID): void {
|
|
82
|
+
this._sink.delete(dataID);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
remove(dataID: DataID): void {
|
|
86
|
+
this._sink.set(dataID, UNPUBLISH_RECORD_SENTINEL);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
set(dataID: DataID, record: Record): void {
|
|
90
|
+
this._sink.set(dataID, record);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getRecordIDs(): Array<DataID> {
|
|
94
|
+
return Object.keys(this.toJSON());
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
size(): number {
|
|
98
|
+
return Object.keys(this.toJSON()).length;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
toJSON() {
|
|
102
|
+
const merged = {...this._base.toJSON()};
|
|
103
|
+
this._sink.getRecordIDs().forEach(dataID => {
|
|
104
|
+
const record = this.get(dataID);
|
|
105
|
+
if (record === undefined) {
|
|
106
|
+
delete merged[dataID];
|
|
107
|
+
} else {
|
|
108
|
+
merged[dataID] = record;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
return merged;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function create(base: RecordSource): MutableRecordSource {
|
|
116
|
+
return new RelayOptimisticRecordSource(base);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = {create};
|
|
@@ -0,0 +1,401 @@
|
|
|
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 ErrorUtils = require('ErrorUtils');
|
|
16
|
+
const RelayReader = require('./RelayReader');
|
|
17
|
+
const RelayRecordSource = require('./RelayRecordSource');
|
|
18
|
+
const RelayRecordSourceMutator = require('../mutations/RelayRecordSourceMutator');
|
|
19
|
+
const RelayRecordSourceProxy = require('../mutations/RelayRecordSourceProxy');
|
|
20
|
+
const RelayRecordSourceSelectorProxy = require('../mutations/RelayRecordSourceSelectorProxy');
|
|
21
|
+
|
|
22
|
+
const invariant = require('invariant');
|
|
23
|
+
const warning = require('warning');
|
|
24
|
+
|
|
25
|
+
import type {HandlerProvider} from '../handlers/RelayDefaultHandlerProvider';
|
|
26
|
+
import type {Disposable} from '../util/RelayRuntimeTypes';
|
|
27
|
+
import type {GetDataID} from './RelayResponseNormalizer';
|
|
28
|
+
import type {
|
|
29
|
+
OperationDescriptor,
|
|
30
|
+
OptimisticUpdate,
|
|
31
|
+
PublishQueue,
|
|
32
|
+
RecordSource,
|
|
33
|
+
RelayResponsePayload,
|
|
34
|
+
RequestDescriptor,
|
|
35
|
+
SelectorData,
|
|
36
|
+
SelectorStoreUpdater,
|
|
37
|
+
SingularReaderSelector,
|
|
38
|
+
Store,
|
|
39
|
+
StoreUpdater,
|
|
40
|
+
} from './RelayStoreTypes';
|
|
41
|
+
|
|
42
|
+
type PendingCommit = PendingRelayPayload | PendingRecordSource | PendingUpdater;
|
|
43
|
+
type PendingRelayPayload = {|
|
|
44
|
+
+kind: 'payload',
|
|
45
|
+
+operation: OperationDescriptor,
|
|
46
|
+
+payload: RelayResponsePayload,
|
|
47
|
+
+updater: ?SelectorStoreUpdater,
|
|
48
|
+
|};
|
|
49
|
+
type PendingRecordSource = {|
|
|
50
|
+
+kind: 'source',
|
|
51
|
+
+source: RecordSource,
|
|
52
|
+
|};
|
|
53
|
+
type PendingUpdater = {|
|
|
54
|
+
+kind: 'updater',
|
|
55
|
+
+updater: StoreUpdater,
|
|
56
|
+
|};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Coordinates the concurrent modification of a `Store` due to optimistic and
|
|
60
|
+
* non-revertable client updates and server payloads:
|
|
61
|
+
* - Applies optimistic updates.
|
|
62
|
+
* - Reverts optimistic updates, rebasing any subsequent updates.
|
|
63
|
+
* - Commits client updates (typically for client schema extensions).
|
|
64
|
+
* - Commits server updates:
|
|
65
|
+
* - Normalizes query/mutation/subscription responses.
|
|
66
|
+
* - Executes handlers for "handle" fields.
|
|
67
|
+
* - Reverts and reapplies pending optimistic updates.
|
|
68
|
+
*/
|
|
69
|
+
class RelayPublishQueue implements PublishQueue {
|
|
70
|
+
_store: Store;
|
|
71
|
+
_handlerProvider: ?HandlerProvider;
|
|
72
|
+
_getDataID: GetDataID;
|
|
73
|
+
|
|
74
|
+
_hasStoreSnapshot: boolean;
|
|
75
|
+
// True if the next `run()` should apply the backup and rerun all optimistic
|
|
76
|
+
// updates performing a rebase.
|
|
77
|
+
_pendingBackupRebase: boolean;
|
|
78
|
+
// Payloads to apply or Sources to publish to the store with the next `run()`.
|
|
79
|
+
_pendingData: Set<PendingCommit>;
|
|
80
|
+
// Optimistic updaters to add with the next `run()`.
|
|
81
|
+
_pendingOptimisticUpdates: Set<OptimisticUpdate>;
|
|
82
|
+
// Optimistic updaters that are already added and might be rerun in order to
|
|
83
|
+
// rebase them.
|
|
84
|
+
_appliedOptimisticUpdates: Set<OptimisticUpdate>;
|
|
85
|
+
// Garbage collection hold, should rerun gc on dispose
|
|
86
|
+
_gcHold: ?Disposable;
|
|
87
|
+
_isRunning: ?boolean;
|
|
88
|
+
|
|
89
|
+
constructor(
|
|
90
|
+
store: Store,
|
|
91
|
+
handlerProvider?: ?HandlerProvider,
|
|
92
|
+
getDataID: GetDataID,
|
|
93
|
+
) {
|
|
94
|
+
this._hasStoreSnapshot = false;
|
|
95
|
+
this._handlerProvider = handlerProvider || null;
|
|
96
|
+
this._pendingBackupRebase = false;
|
|
97
|
+
this._pendingData = new Set();
|
|
98
|
+
this._pendingOptimisticUpdates = new Set();
|
|
99
|
+
this._store = store;
|
|
100
|
+
this._appliedOptimisticUpdates = new Set();
|
|
101
|
+
this._gcHold = null;
|
|
102
|
+
this._getDataID = getDataID;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Schedule applying an optimistic updates on the next `run()`.
|
|
107
|
+
*/
|
|
108
|
+
applyUpdate(updater: OptimisticUpdate): void {
|
|
109
|
+
invariant(
|
|
110
|
+
!this._appliedOptimisticUpdates.has(updater) &&
|
|
111
|
+
!this._pendingOptimisticUpdates.has(updater),
|
|
112
|
+
'RelayPublishQueue: Cannot apply the same update function more than ' +
|
|
113
|
+
'once concurrently.',
|
|
114
|
+
);
|
|
115
|
+
this._pendingOptimisticUpdates.add(updater);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Schedule reverting an optimistic updates on the next `run()`.
|
|
120
|
+
*/
|
|
121
|
+
revertUpdate(updater: OptimisticUpdate): void {
|
|
122
|
+
if (this._pendingOptimisticUpdates.has(updater)) {
|
|
123
|
+
// Reverted before it was applied
|
|
124
|
+
this._pendingOptimisticUpdates.delete(updater);
|
|
125
|
+
} else if (this._appliedOptimisticUpdates.has(updater)) {
|
|
126
|
+
this._pendingBackupRebase = true;
|
|
127
|
+
this._appliedOptimisticUpdates.delete(updater);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Schedule a revert of all optimistic updates on the next `run()`.
|
|
133
|
+
*/
|
|
134
|
+
revertAll(): void {
|
|
135
|
+
this._pendingBackupRebase = true;
|
|
136
|
+
this._pendingOptimisticUpdates.clear();
|
|
137
|
+
this._appliedOptimisticUpdates.clear();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Schedule applying a payload to the store on the next `run()`.
|
|
142
|
+
*/
|
|
143
|
+
commitPayload(
|
|
144
|
+
operation: OperationDescriptor,
|
|
145
|
+
payload: RelayResponsePayload,
|
|
146
|
+
updater?: ?SelectorStoreUpdater,
|
|
147
|
+
): void {
|
|
148
|
+
this._pendingBackupRebase = true;
|
|
149
|
+
this._pendingData.add({
|
|
150
|
+
kind: 'payload',
|
|
151
|
+
operation,
|
|
152
|
+
payload,
|
|
153
|
+
updater,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Schedule an updater to mutate the store on the next `run()` typically to
|
|
159
|
+
* update client schema fields.
|
|
160
|
+
*/
|
|
161
|
+
commitUpdate(updater: StoreUpdater): void {
|
|
162
|
+
this._pendingBackupRebase = true;
|
|
163
|
+
this._pendingData.add({
|
|
164
|
+
kind: 'updater',
|
|
165
|
+
updater,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Schedule a publish to the store from the provided source on the next
|
|
171
|
+
* `run()`. As an example, to update the store with substituted fields that
|
|
172
|
+
* are missing in the store.
|
|
173
|
+
*/
|
|
174
|
+
commitSource(source: RecordSource): void {
|
|
175
|
+
this._pendingBackupRebase = true;
|
|
176
|
+
this._pendingData.add({kind: 'source', source});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Execute all queued up operations from the other public methods.
|
|
181
|
+
*/
|
|
182
|
+
run(
|
|
183
|
+
sourceOperation?: OperationDescriptor,
|
|
184
|
+
): $ReadOnlyArray<RequestDescriptor> {
|
|
185
|
+
if (__DEV__) {
|
|
186
|
+
warning(
|
|
187
|
+
this._isRunning !== true,
|
|
188
|
+
'A store update was detected within another store update. Please ' +
|
|
189
|
+
'make sure new store updates aren’t being executed within an ' +
|
|
190
|
+
'updater function for a different update.',
|
|
191
|
+
);
|
|
192
|
+
this._isRunning = true;
|
|
193
|
+
}
|
|
194
|
+
if (this._pendingBackupRebase) {
|
|
195
|
+
if (this._hasStoreSnapshot) {
|
|
196
|
+
this._store.restore();
|
|
197
|
+
this._hasStoreSnapshot = false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const invalidatedStore = this._commitData();
|
|
201
|
+
if (
|
|
202
|
+
this._pendingOptimisticUpdates.size ||
|
|
203
|
+
(this._pendingBackupRebase && this._appliedOptimisticUpdates.size)
|
|
204
|
+
) {
|
|
205
|
+
if (!this._hasStoreSnapshot) {
|
|
206
|
+
this._store.snapshot();
|
|
207
|
+
this._hasStoreSnapshot = true;
|
|
208
|
+
}
|
|
209
|
+
this._applyUpdates();
|
|
210
|
+
}
|
|
211
|
+
this._pendingBackupRebase = false;
|
|
212
|
+
if (this._appliedOptimisticUpdates.size > 0) {
|
|
213
|
+
if (!this._gcHold) {
|
|
214
|
+
this._gcHold = this._store.holdGC();
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
if (this._gcHold) {
|
|
218
|
+
this._gcHold.dispose();
|
|
219
|
+
this._gcHold = null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (__DEV__) {
|
|
223
|
+
this._isRunning = false;
|
|
224
|
+
}
|
|
225
|
+
return this._store.notify(sourceOperation, invalidatedStore);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* _publishSourceFromPayload will return a boolean indicating if the
|
|
230
|
+
* publish caused the store to be globally invalidated.
|
|
231
|
+
*/
|
|
232
|
+
_publishSourceFromPayload(pendingPayload: PendingRelayPayload): boolean {
|
|
233
|
+
const {payload, operation, updater} = pendingPayload;
|
|
234
|
+
const {source, fieldPayloads} = payload;
|
|
235
|
+
const mutator = new RelayRecordSourceMutator(
|
|
236
|
+
this._store.getSource(),
|
|
237
|
+
source,
|
|
238
|
+
);
|
|
239
|
+
const recordSourceProxy = new RelayRecordSourceProxy(
|
|
240
|
+
mutator,
|
|
241
|
+
this._getDataID,
|
|
242
|
+
);
|
|
243
|
+
if (fieldPayloads && fieldPayloads.length) {
|
|
244
|
+
fieldPayloads.forEach(fieldPayload => {
|
|
245
|
+
const handler =
|
|
246
|
+
this._handlerProvider && this._handlerProvider(fieldPayload.handle);
|
|
247
|
+
invariant(
|
|
248
|
+
handler,
|
|
249
|
+
'RelayModernEnvironment: Expected a handler to be provided for ' +
|
|
250
|
+
'handle `%s`.',
|
|
251
|
+
fieldPayload.handle,
|
|
252
|
+
);
|
|
253
|
+
handler.update(recordSourceProxy, fieldPayload);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
if (updater) {
|
|
257
|
+
const selector = operation.fragment;
|
|
258
|
+
invariant(
|
|
259
|
+
selector != null,
|
|
260
|
+
'RelayModernEnvironment: Expected a selector to be provided with updater function.',
|
|
261
|
+
);
|
|
262
|
+
const recordSourceSelectorProxy = new RelayRecordSourceSelectorProxy(
|
|
263
|
+
mutator,
|
|
264
|
+
recordSourceProxy,
|
|
265
|
+
selector,
|
|
266
|
+
);
|
|
267
|
+
const selectorData = lookupSelector(source, selector);
|
|
268
|
+
updater(recordSourceSelectorProxy, selectorData);
|
|
269
|
+
}
|
|
270
|
+
const idsMarkedForInvalidation = recordSourceProxy.getIDsMarkedForInvalidation();
|
|
271
|
+
this._store.publish(source, idsMarkedForInvalidation);
|
|
272
|
+
return recordSourceProxy.isStoreMarkedForInvalidation();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* _commitData will return a boolean indicating if any of
|
|
277
|
+
* the pending commits caused the store to be globally invalidated.
|
|
278
|
+
*/
|
|
279
|
+
_commitData(): boolean {
|
|
280
|
+
if (!this._pendingData.size) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
let invalidatedStore = false;
|
|
284
|
+
this._pendingData.forEach(data => {
|
|
285
|
+
if (data.kind === 'payload') {
|
|
286
|
+
const payloadInvalidatedStore = this._publishSourceFromPayload(data);
|
|
287
|
+
invalidatedStore = invalidatedStore || payloadInvalidatedStore;
|
|
288
|
+
} else if (data.kind === 'source') {
|
|
289
|
+
const source = data.source;
|
|
290
|
+
this._store.publish(source);
|
|
291
|
+
} else {
|
|
292
|
+
const updater = data.updater;
|
|
293
|
+
const sink = RelayRecordSource.create();
|
|
294
|
+
const mutator = new RelayRecordSourceMutator(
|
|
295
|
+
this._store.getSource(),
|
|
296
|
+
sink,
|
|
297
|
+
);
|
|
298
|
+
const recordSourceProxy = new RelayRecordSourceProxy(
|
|
299
|
+
mutator,
|
|
300
|
+
this._getDataID,
|
|
301
|
+
);
|
|
302
|
+
ErrorUtils.applyWithGuard(
|
|
303
|
+
updater,
|
|
304
|
+
null,
|
|
305
|
+
[recordSourceProxy],
|
|
306
|
+
null,
|
|
307
|
+
'RelayPublishQueue:commitData',
|
|
308
|
+
);
|
|
309
|
+
invalidatedStore =
|
|
310
|
+
invalidatedStore || recordSourceProxy.isStoreMarkedForInvalidation();
|
|
311
|
+
const idsMarkedForInvalidation = recordSourceProxy.getIDsMarkedForInvalidation();
|
|
312
|
+
|
|
313
|
+
this._store.publish(sink, idsMarkedForInvalidation);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
this._pendingData.clear();
|
|
317
|
+
return invalidatedStore;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Note that unlike _commitData, _applyUpdates will NOT return a boolean
|
|
322
|
+
* indicating if the store was globally invalidated, since invalidating the
|
|
323
|
+
* store during an optimistic update is a no-op.
|
|
324
|
+
*/
|
|
325
|
+
_applyUpdates(): void {
|
|
326
|
+
const sink = RelayRecordSource.create();
|
|
327
|
+
const mutator = new RelayRecordSourceMutator(this._store.getSource(), sink);
|
|
328
|
+
const recordSourceProxy = new RelayRecordSourceProxy(
|
|
329
|
+
mutator,
|
|
330
|
+
this._getDataID,
|
|
331
|
+
this._handlerProvider,
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
const processUpdate = optimisticUpdate => {
|
|
335
|
+
if (optimisticUpdate.storeUpdater) {
|
|
336
|
+
const {storeUpdater} = optimisticUpdate;
|
|
337
|
+
ErrorUtils.applyWithGuard(
|
|
338
|
+
storeUpdater,
|
|
339
|
+
null,
|
|
340
|
+
[recordSourceProxy],
|
|
341
|
+
null,
|
|
342
|
+
'RelayPublishQueue:applyUpdates',
|
|
343
|
+
);
|
|
344
|
+
} else {
|
|
345
|
+
const {operation, payload, updater} = optimisticUpdate;
|
|
346
|
+
const {source, fieldPayloads} = payload;
|
|
347
|
+
const recordSourceSelectorProxy = new RelayRecordSourceSelectorProxy(
|
|
348
|
+
mutator,
|
|
349
|
+
recordSourceProxy,
|
|
350
|
+
operation.fragment,
|
|
351
|
+
);
|
|
352
|
+
let selectorData;
|
|
353
|
+
if (source) {
|
|
354
|
+
recordSourceProxy.publishSource(source, fieldPayloads);
|
|
355
|
+
selectorData = lookupSelector(source, operation.fragment);
|
|
356
|
+
}
|
|
357
|
+
if (updater) {
|
|
358
|
+
ErrorUtils.applyWithGuard(
|
|
359
|
+
updater,
|
|
360
|
+
null,
|
|
361
|
+
[recordSourceSelectorProxy, selectorData],
|
|
362
|
+
null,
|
|
363
|
+
'RelayPublishQueue:applyUpdates',
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
// rerun all updaters in case we are running a rebase
|
|
370
|
+
if (this._pendingBackupRebase && this._appliedOptimisticUpdates.size) {
|
|
371
|
+
this._appliedOptimisticUpdates.forEach(processUpdate);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// apply any new updaters
|
|
375
|
+
if (this._pendingOptimisticUpdates.size) {
|
|
376
|
+
this._pendingOptimisticUpdates.forEach(optimisticUpdate => {
|
|
377
|
+
processUpdate(optimisticUpdate);
|
|
378
|
+
this._appliedOptimisticUpdates.add(optimisticUpdate);
|
|
379
|
+
});
|
|
380
|
+
this._pendingOptimisticUpdates.clear();
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
this._store.publish(sink);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function lookupSelector(
|
|
388
|
+
source: RecordSource,
|
|
389
|
+
selector: SingularReaderSelector,
|
|
390
|
+
): ?SelectorData {
|
|
391
|
+
const selectorData = RelayReader.read(source, selector).data;
|
|
392
|
+
if (__DEV__) {
|
|
393
|
+
const deepFreeze = require('../util/deepFreeze');
|
|
394
|
+
if (selectorData) {
|
|
395
|
+
deepFreeze(selectorData);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return selectorData;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
module.exports = RelayPublishQueue;
|