relay-runtime 7.0.0 → 9.1.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.
Files changed (157) hide show
  1. package/handlers/RelayDefaultHandlerProvider.js.flow +34 -0
  2. package/handlers/connection/ConnectionHandler.js.flow +549 -0
  3. package/handlers/connection/ConnectionInterface.js.flow +92 -0
  4. package/index.js +1 -1
  5. package/index.js.flow +314 -0
  6. package/lib/handlers/RelayDefaultHandlerProvider.js +3 -2
  7. package/lib/handlers/connection/{RelayConnectionHandler.js → ConnectionHandler.js} +34 -35
  8. package/lib/handlers/connection/{RelayConnectionInterface.js → ConnectionInterface.js} +3 -30
  9. package/lib/index.js +29 -27
  10. package/lib/mutations/RelayDeclarativeMutationConfig.js +30 -52
  11. package/lib/mutations/RelayRecordProxy.js +6 -3
  12. package/lib/mutations/RelayRecordSourceMutator.js +3 -9
  13. package/lib/mutations/RelayRecordSourceProxy.js +21 -24
  14. package/lib/mutations/RelayRecordSourceSelectorProxy.js +18 -14
  15. package/lib/mutations/applyOptimisticMutation.js +2 -1
  16. package/lib/mutations/commitLocalUpdate.js +1 -0
  17. package/lib/mutations/commitMutation.js +26 -8
  18. package/lib/mutations/validateMutation.js +21 -11
  19. package/lib/network/ConvertToExecuteFunction.js +1 -0
  20. package/lib/network/RelayNetwork.js +1 -0
  21. package/lib/network/RelayNetworkTypes.js +1 -0
  22. package/lib/network/RelayObservable.js +10 -9
  23. package/lib/network/RelayQueryResponseCache.js +9 -7
  24. package/lib/query/{RelayModernGraphQLTag.js → GraphQLTag.js} +15 -8
  25. package/lib/query/fetchQuery.js +2 -1
  26. package/lib/query/fetchQueryInternal.js +30 -20
  27. package/lib/store/ClientID.js +1 -0
  28. package/lib/store/DataChecker.js +47 -97
  29. package/lib/store/RelayConcreteVariables.js +7 -2
  30. package/lib/store/RelayModernEnvironment.js +82 -41
  31. package/lib/store/RelayModernFragmentSpecResolver.js +61 -21
  32. package/lib/store/RelayModernOperationDescriptor.js +2 -1
  33. package/lib/store/RelayModernQueryExecutor.js +476 -333
  34. package/lib/store/RelayModernRecord.js +39 -9
  35. package/lib/store/RelayModernSelector.js +2 -1
  36. package/lib/store/RelayModernStore.js +359 -371
  37. package/lib/store/RelayOperationTracker.js +36 -78
  38. package/lib/store/RelayOptimisticRecordSource.js +8 -5
  39. package/lib/store/RelayPublishQueue.js +66 -53
  40. package/lib/store/RelayReader.js +2 -24
  41. package/lib/store/RelayRecordSource.js +3 -9
  42. package/lib/store/RelayRecordSourceMapImpl.js +14 -18
  43. package/lib/store/RelayRecordState.js +1 -0
  44. package/lib/store/RelayReferenceMarker.js +8 -58
  45. package/lib/store/RelayResponseNormalizer.js +15 -144
  46. package/lib/store/RelayStoreTypes.js +1 -0
  47. package/lib/store/RelayStoreUtils.js +34 -10
  48. package/lib/store/StoreInspector.js +11 -5
  49. package/lib/store/ViewerPattern.js +1 -0
  50. package/lib/store/cloneRelayHandleSourceField.js +1 -0
  51. package/lib/store/createFragmentSpecResolver.js +1 -0
  52. package/lib/store/createRelayContext.js +1 -0
  53. package/lib/store/defaultGetDataID.js +1 -0
  54. package/lib/store/hasOverlappingIDs.js +1 -0
  55. package/lib/store/isRelayModernEnvironment.js +1 -0
  56. package/lib/store/normalizeRelayPayload.js +8 -4
  57. package/lib/store/readInlineData.js +2 -1
  58. package/lib/subscription/requestSubscription.js +6 -3
  59. package/lib/util/JSResourceTypes.flow.js +12 -0
  60. package/lib/util/NormalizationNode.js +1 -0
  61. package/lib/util/ReaderNode.js +1 -0
  62. package/lib/util/RelayConcreteNode.js +3 -0
  63. package/lib/util/RelayDefaultHandleKey.js +1 -0
  64. package/lib/util/RelayError.js +2 -1
  65. package/lib/util/RelayFeatureFlags.js +3 -2
  66. package/lib/util/RelayProfiler.js +1 -0
  67. package/lib/util/RelayReplaySubject.js +2 -3
  68. package/lib/util/RelayRuntimeTypes.js +1 -0
  69. package/lib/util/createPayloadFor3DField.js +34 -0
  70. package/lib/util/deepFreeze.js +1 -0
  71. package/lib/util/generateID.js +1 -0
  72. package/lib/util/getFragmentIdentifier.js +1 -0
  73. package/lib/util/getRelayHandleKey.js +1 -0
  74. package/lib/util/getRequestIdentifier.js +1 -0
  75. package/lib/util/isPromise.js +1 -0
  76. package/lib/util/isScalarAndEqual.js +1 -0
  77. package/lib/util/recycleNodesInto.js +1 -0
  78. package/lib/util/resolveImmediate.js +1 -0
  79. package/lib/util/stableCopy.js +1 -0
  80. package/mutations/RelayDeclarativeMutationConfig.js.flow +380 -0
  81. package/mutations/RelayRecordProxy.js.flow +165 -0
  82. package/mutations/RelayRecordSourceMutator.js.flow +238 -0
  83. package/mutations/RelayRecordSourceProxy.js.flow +164 -0
  84. package/mutations/RelayRecordSourceSelectorProxy.js.flow +119 -0
  85. package/mutations/applyOptimisticMutation.js.flow +76 -0
  86. package/mutations/commitLocalUpdate.js.flow +24 -0
  87. package/mutations/commitMutation.js.flow +184 -0
  88. package/mutations/validateMutation.js.flow +211 -0
  89. package/network/ConvertToExecuteFunction.js.flow +49 -0
  90. package/network/RelayNetwork.js.flow +84 -0
  91. package/network/RelayNetworkTypes.js.flow +123 -0
  92. package/network/RelayObservable.js.flow +634 -0
  93. package/network/RelayQueryResponseCache.js.flow +111 -0
  94. package/package.json +1 -1
  95. package/query/GraphQLTag.js.flow +166 -0
  96. package/query/fetchQuery.js.flow +47 -0
  97. package/query/fetchQueryInternal.js.flow +349 -0
  98. package/relay-runtime.js +2 -2
  99. package/relay-runtime.min.js +2 -2
  100. package/store/ClientID.js.flow +43 -0
  101. package/store/DataChecker.js.flow +426 -0
  102. package/store/RelayConcreteVariables.js.flow +96 -0
  103. package/store/RelayModernEnvironment.js.flow +526 -0
  104. package/store/RelayModernFragmentSpecResolver.js.flow +426 -0
  105. package/store/RelayModernOperationDescriptor.js.flow +88 -0
  106. package/store/RelayModernQueryExecutor.js.flow +1327 -0
  107. package/store/RelayModernRecord.js.flow +403 -0
  108. package/store/RelayModernSelector.js.flow +444 -0
  109. package/store/RelayModernStore.js.flow +757 -0
  110. package/store/RelayOperationTracker.js.flow +164 -0
  111. package/store/RelayOptimisticRecordSource.js.flow +119 -0
  112. package/store/RelayPublishQueue.js.flow +401 -0
  113. package/store/RelayReader.js.flow +376 -0
  114. package/store/RelayRecordSource.js.flow +29 -0
  115. package/store/RelayRecordSourceMapImpl.js.flow +87 -0
  116. package/store/RelayRecordState.js.flow +37 -0
  117. package/store/RelayReferenceMarker.js.flow +236 -0
  118. package/store/RelayResponseNormalizer.js.flow +556 -0
  119. package/store/RelayStoreTypes.js.flow +873 -0
  120. package/store/RelayStoreUtils.js.flow +218 -0
  121. package/store/StoreInspector.js.flow +173 -0
  122. package/store/ViewerPattern.js.flow +26 -0
  123. package/store/cloneRelayHandleSourceField.js.flow +66 -0
  124. package/store/createFragmentSpecResolver.js.flow +55 -0
  125. package/store/createRelayContext.js.flow +44 -0
  126. package/store/defaultGetDataID.js.flow +27 -0
  127. package/store/hasOverlappingIDs.js.flow +34 -0
  128. package/store/isRelayModernEnvironment.js.flow +27 -0
  129. package/store/normalizeRelayPayload.js.flow +51 -0
  130. package/store/readInlineData.js.flow +75 -0
  131. package/subscription/requestSubscription.js.flow +100 -0
  132. package/util/JSResourceTypes.flow.js.flow +20 -0
  133. package/util/NormalizationNode.js.flow +191 -0
  134. package/util/ReaderNode.js.flow +208 -0
  135. package/util/RelayConcreteNode.js.flow +80 -0
  136. package/util/RelayDefaultHandleKey.js.flow +17 -0
  137. package/util/RelayError.js.flow +33 -0
  138. package/util/RelayFeatureFlags.js.flow +30 -0
  139. package/util/RelayProfiler.js.flow +284 -0
  140. package/util/RelayReplaySubject.js.flow +134 -0
  141. package/util/RelayRuntimeTypes.js.flow +70 -0
  142. package/util/createPayloadFor3DField.js.flow +43 -0
  143. package/util/deepFreeze.js.flow +36 -0
  144. package/util/generateID.js.flow +21 -0
  145. package/util/getFragmentIdentifier.js.flow +52 -0
  146. package/util/getRelayHandleKey.js.flow +41 -0
  147. package/util/getRequestIdentifier.js.flow +41 -0
  148. package/util/isPromise.js.flow +21 -0
  149. package/util/isScalarAndEqual.js.flow +26 -0
  150. package/util/recycleNodesInto.js.flow +80 -0
  151. package/util/resolveImmediate.js.flow +30 -0
  152. package/util/stableCopy.js.flow +35 -0
  153. package/lib/handlers/RelayDefaultMissingFieldHandlers.js +0 -26
  154. package/lib/store/RelayConnection.js +0 -36
  155. package/lib/store/RelayConnectionResolver.js +0 -177
  156. package/lib/store/RelayRecordSourceObjectImpl.js +0 -78
  157. package/lib/util/getFragmentSpecIdentifier.js +0 -26
@@ -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;