relay-runtime 11.0.2 → 13.0.0-rc.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.
Files changed (219) hide show
  1. package/README.md +67 -0
  2. package/handlers/RelayDefaultHandlerProvider.js.flow +3 -3
  3. package/handlers/connection/ConnectionHandler.js.flow +9 -18
  4. package/handlers/connection/ConnectionInterface.js.flow +1 -1
  5. package/handlers/connection/MutationHandlers.js.flow +8 -12
  6. package/index.js +1 -1
  7. package/index.js.flow +57 -35
  8. package/lib/handlers/RelayDefaultHandlerProvider.js +1 -1
  9. package/lib/handlers/connection/ConnectionHandler.js +13 -19
  10. package/lib/handlers/connection/ConnectionInterface.js +1 -1
  11. package/lib/handlers/connection/MutationHandlers.js +4 -7
  12. package/lib/index.js +59 -44
  13. package/lib/multi-actor-environment/ActorIdentifier.js +12 -2
  14. package/lib/multi-actor-environment/ActorSpecificEnvironment.js +64 -20
  15. package/lib/multi-actor-environment/ActorUtils.js +27 -0
  16. package/lib/multi-actor-environment/MultiActorEnvironment.js +324 -61
  17. package/lib/multi-actor-environment/MultiActorEnvironmentTypes.js +1 -1
  18. package/lib/multi-actor-environment/index.js +6 -2
  19. package/lib/mutations/RelayDeclarativeMutationConfig.js +5 -2
  20. package/lib/mutations/RelayRecordProxy.js +4 -3
  21. package/lib/mutations/RelayRecordSourceMutator.js +4 -3
  22. package/lib/mutations/RelayRecordSourceProxy.js +13 -5
  23. package/lib/mutations/RelayRecordSourceSelectorProxy.js +19 -6
  24. package/lib/mutations/applyOptimisticMutation.js +7 -7
  25. package/lib/mutations/commitLocalUpdate.js +1 -1
  26. package/lib/mutations/commitMutation.js +15 -11
  27. package/lib/mutations/readUpdatableQuery_EXPERIMENTAL.js +242 -0
  28. package/lib/mutations/validateMutation.js +11 -6
  29. package/lib/network/ConvertToExecuteFunction.js +3 -2
  30. package/lib/network/RelayNetwork.js +4 -3
  31. package/lib/network/RelayNetworkTypes.js +1 -1
  32. package/lib/network/RelayObservable.js +1 -1
  33. package/lib/network/RelayQueryResponseCache.js +22 -6
  34. package/lib/network/wrapNetworkWithLogObserver.js +79 -0
  35. package/lib/query/GraphQLTag.js +3 -2
  36. package/lib/query/PreloadableQueryRegistry.js +1 -1
  37. package/lib/query/fetchQuery.js +7 -6
  38. package/lib/query/fetchQueryInternal.js +1 -1
  39. package/lib/query/fetchQuery_DEPRECATED.js +3 -2
  40. package/lib/store/ClientID.js +8 -2
  41. package/lib/store/DataChecker.js +124 -55
  42. package/lib/store/OperationExecutor.js +489 -215
  43. package/lib/store/RelayConcreteVariables.js +27 -9
  44. package/lib/store/RelayExperimentalGraphResponseHandler.js +153 -0
  45. package/lib/store/RelayExperimentalGraphResponseTransform.js +391 -0
  46. package/lib/store/RelayModernEnvironment.js +100 -120
  47. package/lib/store/RelayModernFragmentSpecResolver.js +53 -27
  48. package/lib/store/RelayModernOperationDescriptor.js +3 -2
  49. package/lib/store/RelayModernRecord.js +48 -13
  50. package/lib/store/RelayModernSelector.js +15 -9
  51. package/lib/store/RelayModernStore.js +56 -23
  52. package/lib/store/RelayOperationTracker.js +34 -24
  53. package/lib/store/RelayOptimisticRecordSource.js +1 -1
  54. package/lib/store/RelayPublishQueue.js +35 -11
  55. package/lib/store/RelayReader.js +257 -72
  56. package/lib/store/RelayRecordSource.js +88 -4
  57. package/lib/store/RelayRecordState.js +1 -1
  58. package/lib/store/RelayReferenceMarker.js +34 -22
  59. package/lib/store/RelayResponseNormalizer.js +172 -96
  60. package/lib/store/RelayStoreReactFlightUtils.js +5 -11
  61. package/lib/store/RelayStoreSubscriptions.js +15 -10
  62. package/lib/store/RelayStoreTypes.js +1 -1
  63. package/lib/store/RelayStoreUtils.js +13 -8
  64. package/lib/store/ResolverCache.js +213 -0
  65. package/lib/store/ResolverFragments.js +10 -6
  66. package/lib/store/StoreInspector.js +1 -1
  67. package/lib/store/TypeID.js +1 -1
  68. package/lib/store/ViewerPattern.js +1 -1
  69. package/lib/store/cloneRelayHandleSourceField.js +6 -5
  70. package/lib/store/cloneRelayScalarHandleSourceField.js +6 -5
  71. package/lib/store/createFragmentSpecResolver.js +1 -1
  72. package/lib/store/createRelayContext.js +5 -3
  73. package/lib/store/defaultGetDataID.js +1 -1
  74. package/lib/store/defaultRequiredFieldLogger.js +1 -1
  75. package/lib/store/hasOverlappingIDs.js +1 -1
  76. package/lib/store/isRelayModernEnvironment.js +1 -1
  77. package/lib/store/normalizeRelayPayload.js +1 -1
  78. package/lib/store/readInlineData.js +7 -3
  79. package/lib/subscription/requestSubscription.js +32 -34
  80. package/lib/util/JSResourceTypes.flow.js +1 -1
  81. package/lib/util/NormalizationNode.js +1 -1
  82. package/lib/util/ReaderNode.js +1 -1
  83. package/lib/util/RelayConcreteNode.js +3 -1
  84. package/lib/util/RelayDefaultHandleKey.js +1 -1
  85. package/lib/util/RelayError.js +1 -1
  86. package/lib/util/RelayFeatureFlags.js +10 -7
  87. package/lib/util/RelayProfiler.js +1 -1
  88. package/lib/util/RelayReplaySubject.js +22 -7
  89. package/lib/util/RelayRuntimeTypes.js +1 -7
  90. package/lib/util/StringInterner.js +71 -0
  91. package/lib/util/createPayloadFor3DField.js +1 -1
  92. package/lib/util/deepFreeze.js +1 -1
  93. package/lib/util/generateID.js +1 -1
  94. package/lib/util/getAllRootVariables.js +29 -0
  95. package/lib/util/getFragmentIdentifier.js +16 -8
  96. package/lib/util/getOperation.js +3 -2
  97. package/lib/util/getPaginationMetadata.js +41 -0
  98. package/lib/util/getPaginationVariables.js +66 -0
  99. package/lib/util/getPendingOperationsForFragment.js +55 -0
  100. package/lib/util/getRefetchMetadata.js +36 -0
  101. package/lib/util/getRelayHandleKey.js +3 -3
  102. package/lib/util/getRequestIdentifier.js +3 -3
  103. package/lib/util/getValueAtPath.js +51 -0
  104. package/lib/util/isEmptyObject.js +2 -2
  105. package/lib/util/isPromise.js +1 -1
  106. package/lib/util/isScalarAndEqual.js +1 -1
  107. package/lib/util/recycleNodesInto.js +1 -1
  108. package/lib/util/registerEnvironmentWithDevTools.js +26 -0
  109. package/lib/util/reportMissingRequiredFields.js +1 -1
  110. package/lib/util/resolveImmediate.js +1 -1
  111. package/lib/util/stableCopy.js +1 -1
  112. package/lib/util/withDuration.js +31 -0
  113. package/multi-actor-environment/ActorIdentifier.js.flow +18 -2
  114. package/multi-actor-environment/ActorSpecificEnvironment.js.flow +94 -58
  115. package/multi-actor-environment/ActorUtils.js.flow +33 -0
  116. package/multi-actor-environment/MultiActorEnvironment.js.flow +366 -93
  117. package/multi-actor-environment/MultiActorEnvironmentTypes.js.flow +88 -23
  118. package/multi-actor-environment/index.js.flow +3 -1
  119. package/mutations/RelayDeclarativeMutationConfig.js.flow +33 -27
  120. package/mutations/RelayRecordProxy.js.flow +5 -6
  121. package/mutations/RelayRecordSourceMutator.js.flow +5 -7
  122. package/mutations/RelayRecordSourceProxy.js.flow +20 -11
  123. package/mutations/RelayRecordSourceSelectorProxy.js.flow +23 -8
  124. package/mutations/applyOptimisticMutation.js.flow +14 -15
  125. package/mutations/commitLocalUpdate.js.flow +2 -2
  126. package/mutations/commitMutation.js.flow +36 -47
  127. package/mutations/readUpdatableQuery_EXPERIMENTAL.js.flow +318 -0
  128. package/mutations/validateMutation.js.flow +27 -17
  129. package/network/ConvertToExecuteFunction.js.flow +3 -3
  130. package/network/RelayNetwork.js.flow +5 -6
  131. package/network/RelayNetworkTypes.js.flow +1 -1
  132. package/network/RelayObservable.js.flow +2 -2
  133. package/network/RelayQueryResponseCache.js.flow +35 -22
  134. package/network/wrapNetworkWithLogObserver.js.flow +99 -0
  135. package/package.json +2 -2
  136. package/query/GraphQLTag.js.flow +11 -11
  137. package/query/PreloadableQueryRegistry.js.flow +5 -3
  138. package/query/fetchQuery.js.flow +19 -19
  139. package/query/fetchQueryInternal.js.flow +7 -10
  140. package/query/fetchQuery_DEPRECATED.js.flow +7 -7
  141. package/relay-runtime.js +2 -2
  142. package/relay-runtime.min.js +2 -2
  143. package/store/ClientID.js.flow +15 -4
  144. package/store/DataChecker.js.flow +142 -60
  145. package/store/OperationExecutor.js.flow +575 -320
  146. package/store/RelayConcreteVariables.js.flow +28 -9
  147. package/store/RelayExperimentalGraphResponseHandler.js.flow +121 -0
  148. package/store/RelayExperimentalGraphResponseTransform.js.flow +470 -0
  149. package/store/RelayModernEnvironment.js.flow +91 -115
  150. package/store/RelayModernFragmentSpecResolver.js.flow +56 -32
  151. package/store/RelayModernOperationDescriptor.js.flow +13 -8
  152. package/store/RelayModernRecord.js.flow +68 -12
  153. package/store/RelayModernSelector.js.flow +25 -15
  154. package/store/RelayModernStore.js.flow +67 -32
  155. package/store/RelayOperationTracker.js.flow +60 -44
  156. package/store/RelayOptimisticRecordSource.js.flow +3 -3
  157. package/store/RelayPublishQueue.js.flow +74 -32
  158. package/store/RelayReader.js.flow +319 -100
  159. package/store/RelayRecordSource.js.flow +73 -7
  160. package/store/RelayRecordState.js.flow +1 -1
  161. package/store/RelayReferenceMarker.js.flow +41 -27
  162. package/store/RelayResponseNormalizer.js.flow +204 -86
  163. package/store/RelayStoreReactFlightUtils.js.flow +5 -12
  164. package/store/RelayStoreSubscriptions.js.flow +20 -12
  165. package/store/RelayStoreTypes.js.flow +200 -41
  166. package/store/RelayStoreUtils.js.flow +25 -12
  167. package/store/ResolverCache.js.flow +249 -0
  168. package/store/ResolverFragments.js.flow +16 -20
  169. package/store/StoreInspector.js.flow +3 -3
  170. package/store/TypeID.js.flow +2 -2
  171. package/store/ViewerPattern.js.flow +3 -3
  172. package/store/cloneRelayHandleSourceField.js.flow +6 -7
  173. package/store/cloneRelayScalarHandleSourceField.js.flow +6 -7
  174. package/store/createFragmentSpecResolver.js.flow +4 -5
  175. package/store/createRelayContext.js.flow +4 -4
  176. package/store/defaultGetDataID.js.flow +1 -1
  177. package/store/defaultRequiredFieldLogger.js.flow +1 -1
  178. package/store/hasOverlappingIDs.js.flow +1 -1
  179. package/store/isRelayModernEnvironment.js.flow +1 -1
  180. package/store/normalizeRelayPayload.js.flow +7 -8
  181. package/store/readInlineData.js.flow +8 -9
  182. package/subscription/requestSubscription.js.flow +55 -51
  183. package/util/JSResourceTypes.flow.js.flow +1 -1
  184. package/util/NormalizationNode.js.flow +11 -4
  185. package/util/ReaderNode.js.flow +25 -2
  186. package/util/RelayConcreteNode.js.flow +5 -1
  187. package/util/RelayDefaultHandleKey.js.flow +1 -1
  188. package/util/RelayError.js.flow +1 -1
  189. package/util/RelayFeatureFlags.js.flow +23 -15
  190. package/util/RelayProfiler.js.flow +1 -1
  191. package/util/RelayReplaySubject.js.flow +10 -10
  192. package/util/RelayRuntimeTypes.js.flow +70 -3
  193. package/util/StringInterner.js.flow +69 -0
  194. package/util/createPayloadFor3DField.js.flow +4 -4
  195. package/util/deepFreeze.js.flow +1 -1
  196. package/util/generateID.js.flow +1 -1
  197. package/util/getAllRootVariables.js.flow +36 -0
  198. package/util/getFragmentIdentifier.js.flow +28 -16
  199. package/util/getOperation.js.flow +3 -3
  200. package/util/getPaginationMetadata.js.flow +69 -0
  201. package/util/getPaginationVariables.js.flow +108 -0
  202. package/util/getPendingOperationsForFragment.js.flow +62 -0
  203. package/util/getRefetchMetadata.js.flow +76 -0
  204. package/util/getRelayHandleKey.js.flow +2 -3
  205. package/util/getRequestIdentifier.js.flow +4 -4
  206. package/util/getValueAtPath.js.flow +46 -0
  207. package/util/isEmptyObject.js.flow +2 -1
  208. package/util/isPromise.js.flow +1 -1
  209. package/util/isScalarAndEqual.js.flow +1 -1
  210. package/util/recycleNodesInto.js.flow +1 -1
  211. package/util/registerEnvironmentWithDevTools.js.flow +33 -0
  212. package/util/reportMissingRequiredFields.js.flow +1 -1
  213. package/util/resolveImmediate.js.flow +2 -2
  214. package/util/stableCopy.js.flow +1 -1
  215. package/util/withDuration.js.flow +32 -0
  216. package/lib/store/RelayRecordSourceMapImpl.js +0 -107
  217. package/lib/store/RelayStoreSubscriptionsUsingMapByID.js +0 -318
  218. package/store/RelayRecordSourceMapImpl.js.flow +0 -91
  219. package/store/RelayStoreSubscriptionsUsingMapByID.js.flow +0 -283
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
3
  *
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
@@ -13,37 +13,22 @@
13
13
 
14
14
  'use strict';
15
15
 
16
- const RelayError = require('../util/RelayError');
17
- const RelayFeatureFlags = require('../util/RelayFeatureFlags');
18
- const RelayModernRecord = require('./RelayModernRecord');
19
- const RelayObservable = require('../network/RelayObservable');
20
- const RelayRecordSource = require('./RelayRecordSource');
21
- const RelayResponseNormalizer = require('./RelayResponseNormalizer');
22
-
23
- const getOperation = require('../util/getOperation');
24
- const invariant = require('invariant');
25
- const stableCopy = require('../util/stableCopy');
26
- const warning = require('warning');
27
-
28
- const {generateClientID, generateUniqueClientID} = require('./ClientID');
29
- const {
30
- createNormalizationSelector,
31
- createReaderSelector,
32
- } = require('./RelayModernSelector');
33
- const {ROOT_TYPE, TYPENAME_KEY, getStorageKey} = require('./RelayStoreUtils');
34
-
16
+ import type {ActorIdentifier} from '../multi-actor-environment/ActorIdentifier';
35
17
  import type {
36
18
  GraphQLResponse,
37
- GraphQLSingularResponse,
38
19
  GraphQLResponseWithData,
20
+ GraphQLSingularResponse,
21
+ ReactFlightServerTree,
39
22
  } from '../network/RelayNetworkTypes';
40
23
  import type {Sink, Subscription} from '../network/RelayObservable';
41
24
  import type {
42
25
  DeferPlaceholder,
43
- RequestDescriptor,
26
+ FollowupPayload,
44
27
  HandleFieldPayload,
45
28
  IncrementalDataPlaceholder,
29
+ LogFunction,
46
30
  ModuleImportPayload,
31
+ MutationParameters,
47
32
  NormalizationSelector,
48
33
  OperationDescriptor,
49
34
  OperationLoader,
@@ -51,10 +36,12 @@ import type {
51
36
  OptimisticResponseConfig,
52
37
  OptimisticUpdate,
53
38
  PublishQueue,
39
+ ReactFlightClientResponse,
54
40
  ReactFlightPayloadDeserializer,
55
41
  ReactFlightServerErrorHandler,
56
42
  Record,
57
43
  RelayResponsePayload,
44
+ RequestDescriptor,
58
45
  SelectorStoreUpdater,
59
46
  Store,
60
47
  StreamPlaceholder,
@@ -66,28 +53,50 @@ import type {
66
53
  NormalizationSelectableNode,
67
54
  NormalizationSplitOperation,
68
55
  } from '../util/NormalizationNode';
69
- import type {DataID, Variables, Disposable} from '../util/RelayRuntimeTypes';
56
+ import type {DataID, Disposable, Variables} from '../util/RelayRuntimeTypes';
70
57
  import type {GetDataID} from './RelayResponseNormalizer';
71
58
  import type {NormalizationOptions} from './RelayResponseNormalizer';
72
59
 
73
- export type ExecuteConfig = {|
60
+ const RelayObservable = require('../network/RelayObservable');
61
+ const generateID = require('../util/generateID');
62
+ const getOperation = require('../util/getOperation');
63
+ const RelayError = require('../util/RelayError');
64
+ const RelayFeatureFlags = require('../util/RelayFeatureFlags');
65
+ const stableCopy = require('../util/stableCopy');
66
+ const withDuration = require('../util/withDuration');
67
+ const {generateClientID, generateUniqueClientID} = require('./ClientID');
68
+ const {getLocalVariables} = require('./RelayConcreteVariables');
69
+ const RelayModernRecord = require('./RelayModernRecord');
70
+ const {
71
+ createNormalizationSelector,
72
+ createReaderSelector,
73
+ } = require('./RelayModernSelector');
74
+ const RelayRecordSource = require('./RelayRecordSource');
75
+ const RelayResponseNormalizer = require('./RelayResponseNormalizer');
76
+ const {ROOT_TYPE, TYPENAME_KEY, getStorageKey} = require('./RelayStoreUtils');
77
+ const invariant = require('invariant');
78
+ const warning = require('warning');
79
+
80
+ export type ExecuteConfig<TMutation: MutationParameters> = {|
81
+ +actorIdentifier: ActorIdentifier,
74
82
  +getDataID: GetDataID,
75
- +treatMissingFieldsAsNull: boolean,
83
+ +getPublishQueue: (actorIdentifier: ActorIdentifier) => PublishQueue,
84
+ +getStore: (actorIdentifier: ActorIdentifier) => Store,
85
+ +isClientPayload?: boolean,
76
86
  +operation: OperationDescriptor,
77
87
  +operationExecutions: Map<string, ActiveState>,
78
88
  +operationLoader: ?OperationLoader,
79
89
  +operationTracker: OperationTracker,
80
- +optimisticConfig: ?OptimisticResponseConfig,
81
- +publishQueue: PublishQueue,
90
+ +optimisticConfig: ?OptimisticResponseConfig<TMutation>,
82
91
  +reactFlightPayloadDeserializer?: ?ReactFlightPayloadDeserializer,
83
92
  +reactFlightServerErrorHandler?: ?ReactFlightServerErrorHandler,
84
93
  +scheduler?: ?TaskScheduler,
94
+ +shouldProcessClientComponents?: ?boolean,
85
95
  +sink: Sink<GraphQLResponse>,
86
96
  +source: RelayObservable<GraphQLResponse>,
87
- +store: Store,
88
- +updater?: ?SelectorStoreUpdater,
89
- +isClientPayload?: boolean,
90
- +shouldProcessClientComponents?: ?boolean,
97
+ +treatMissingFieldsAsNull: boolean,
98
+ +updater?: ?SelectorStoreUpdater<TMutation['response']>,
99
+ +log: LogFunction,
91
100
  |};
92
101
 
93
102
  export type ActiveState = 'active' | 'inactive';
@@ -115,7 +124,9 @@ type IncrementalGraphQLResponse = {|
115
124
  response: GraphQLResponseWithData,
116
125
  |};
117
126
 
118
- function execute(config: ExecuteConfig): Executor {
127
+ function execute<TMutation: MutationParameters>(
128
+ config: ExecuteConfig<TMutation>,
129
+ ): Executor<TMutation> {
119
130
  return new Executor(config);
120
131
  }
121
132
 
@@ -124,20 +135,23 @@ function execute(config: ExecuteConfig): Executor {
124
135
  * including optimistic payloads, standard payloads, resolution of match
125
136
  * dependencies, etc.
126
137
  */
127
- class Executor {
138
+ class Executor<TMutation: MutationParameters> {
139
+ _actorIdentifier: ActorIdentifier;
128
140
  _getDataID: GetDataID;
129
141
  _treatMissingFieldsAsNull: boolean;
130
142
  _incrementalPayloadsPending: boolean;
131
143
  _incrementalResults: Map<Label, Map<PathKey, IncrementalResults>>;
144
+ _log: LogFunction;
145
+ _executeId: number;
132
146
  _nextSubscriptionId: number;
133
147
  _operation: OperationDescriptor;
134
148
  _operationExecutions: Map<string, ActiveState>;
135
149
  _operationLoader: ?OperationLoader;
136
150
  _operationTracker: OperationTracker;
137
151
  _operationUpdateEpochs: Map<string, number>;
138
- _optimisticUpdates: null | Array<OptimisticUpdate>;
152
+ _optimisticUpdates: null | Array<OptimisticUpdate<TMutation>>;
139
153
  _pendingModulePayloadsCount: number;
140
- _publishQueue: PublishQueue;
154
+ +_getPublishQueue: (actorIdentifier: ActorIdentifier) => PublishQueue;
141
155
  _reactFlightPayloadDeserializer: ?ReactFlightPayloadDeserializer;
142
156
  _reactFlightServerErrorHandler: ?ReactFlightServerErrorHandler;
143
157
  _shouldProcessClientComponents: ?boolean;
@@ -148,36 +162,44 @@ class Executor {
148
162
  {|+record: Record, +fieldPayloads: Array<HandleFieldPayload>|},
149
163
  >;
150
164
  _state: 'started' | 'loading_incremental' | 'loading_final' | 'completed';
151
- _store: Store;
165
+ +_getStore: (actorIdentifier: ActorIdentifier) => Store;
152
166
  _subscriptions: Map<number, Subscription>;
153
- _updater: ?SelectorStoreUpdater;
154
- _retainDisposable: ?Disposable;
167
+ _updater: ?SelectorStoreUpdater<TMutation['response']>;
168
+ _asyncStoreUpdateDisposable: ?Disposable;
169
+ _completeFns: Array<() => void>;
170
+ +_retainDisposables: Map<ActorIdentifier, Disposable>;
155
171
  +_isClientPayload: boolean;
156
172
  +_isSubscriptionOperation: boolean;
173
+ +_seenActors: Set<ActorIdentifier>;
157
174
 
158
175
  constructor({
176
+ actorIdentifier,
177
+ getDataID,
178
+ getPublishQueue,
179
+ getStore,
180
+ isClientPayload,
159
181
  operation,
160
182
  operationExecutions,
161
183
  operationLoader,
184
+ operationTracker,
162
185
  optimisticConfig,
163
- publishQueue,
186
+ reactFlightPayloadDeserializer,
187
+ reactFlightServerErrorHandler,
164
188
  scheduler,
189
+ shouldProcessClientComponents,
165
190
  sink,
166
191
  source,
167
- store,
168
- updater,
169
- operationTracker,
170
192
  treatMissingFieldsAsNull,
171
- getDataID,
172
- isClientPayload,
173
- reactFlightPayloadDeserializer,
174
- reactFlightServerErrorHandler,
175
- shouldProcessClientComponents,
176
- }: ExecuteConfig): void {
193
+ updater,
194
+ log,
195
+ }: ExecuteConfig<TMutation>): void {
196
+ this._actorIdentifier = actorIdentifier;
177
197
  this._getDataID = getDataID;
178
198
  this._treatMissingFieldsAsNull = treatMissingFieldsAsNull;
179
199
  this._incrementalPayloadsPending = false;
180
200
  this._incrementalResults = new Map();
201
+ this._log = log;
202
+ this._executeId = generateID();
181
203
  this._nextSubscriptionId = 0;
182
204
  this._operation = operation;
183
205
  this._operationExecutions = operationExecutions;
@@ -186,12 +208,12 @@ class Executor {
186
208
  this._operationUpdateEpochs = new Map();
187
209
  this._optimisticUpdates = null;
188
210
  this._pendingModulePayloadsCount = 0;
189
- this._publishQueue = publishQueue;
211
+ this._getPublishQueue = getPublishQueue;
190
212
  this._scheduler = scheduler;
191
213
  this._sink = sink;
192
214
  this._source = new Map();
193
215
  this._state = 'started';
194
- this._store = store;
216
+ this._getStore = getStore;
195
217
  this._subscriptions = new Map();
196
218
  this._updater = updater;
197
219
  this._isClientPayload = isClientPayload === true;
@@ -200,6 +222,9 @@ class Executor {
200
222
  this._isSubscriptionOperation =
201
223
  this._operation.request.node.params.operationKind === 'subscription';
202
224
  this._shouldProcessClientComponents = shouldProcessClientComponents;
225
+ this._retainDisposables = new Map();
226
+ this._seenActors = new Set();
227
+ this._completeFns = [];
203
228
 
204
229
  const id = this._nextSubscriptionId++;
205
230
  source.subscribe({
@@ -212,7 +237,16 @@ class Executor {
212
237
  sink.error(error);
213
238
  }
214
239
  },
215
- start: subscription => this._start(id, subscription),
240
+ start: subscription => {
241
+ this._start(id, subscription);
242
+ this._log({
243
+ name: 'execute.start',
244
+ executeId: this._executeId,
245
+ params: this._operation.request.node.params,
246
+ variables: this._operation.request.variables,
247
+ cacheConfig: this._operation.request.cacheConfig ?? {},
248
+ });
249
+ },
216
250
  });
217
251
 
218
252
  if (optimisticConfig != null) {
@@ -242,19 +276,41 @@ class Executor {
242
276
  if (optimisticUpdates !== null) {
243
277
  this._optimisticUpdates = null;
244
278
  optimisticUpdates.forEach(update =>
245
- this._publishQueue.revertUpdate(update),
279
+ this._getPublishQueueAndSaveActor().revertUpdate(update),
246
280
  );
247
281
  // OK: run revert on cancel
248
- this._publishQueue.run();
282
+ this._runPublishQueue();
249
283
  }
250
284
  this._incrementalResults.clear();
251
- this._completeOperationTracker();
252
- if (this._retainDisposable) {
253
- this._retainDisposable.dispose();
254
- this._retainDisposable = null;
285
+ if (this._asyncStoreUpdateDisposable != null) {
286
+ this._asyncStoreUpdateDisposable.dispose();
287
+ this._asyncStoreUpdateDisposable = null;
255
288
  }
289
+ this._completeFns = [];
290
+ this._completeOperationTracker();
291
+ this._disposeRetainedData();
256
292
  }
257
293
 
294
+ _deserializeReactFlightPayloadWithLogging = (
295
+ tree: ReactFlightServerTree,
296
+ ): ReactFlightClientResponse => {
297
+ const reactFlightPayloadDeserializer = this._reactFlightPayloadDeserializer;
298
+ invariant(
299
+ typeof reactFlightPayloadDeserializer === 'function',
300
+ 'OperationExecutor: Expected reactFlightPayloadDeserializer to be available when calling _deserializeReactFlightPayloadWithLogging.',
301
+ );
302
+ const [duration, result] = withDuration(() => {
303
+ return reactFlightPayloadDeserializer(tree);
304
+ });
305
+ this._log({
306
+ name: 'execute.flight.payload_deserialize',
307
+ executeId: this._executeId,
308
+ operationName: this._operation.request.node.params.name,
309
+ duration,
310
+ });
311
+ return result;
312
+ };
313
+
258
314
  _updateActiveState(): void {
259
315
  let activeState;
260
316
  switch (this._state) {
@@ -314,12 +370,21 @@ class Executor {
314
370
  if (this._subscriptions.size === 0) {
315
371
  this.cancel();
316
372
  this._sink.complete();
373
+ this._log({
374
+ name: 'execute.complete',
375
+ executeId: this._executeId,
376
+ });
317
377
  }
318
378
  }
319
379
 
320
380
  _error(error: Error): void {
321
381
  this.cancel();
322
382
  this._sink.error(error);
383
+ this._log({
384
+ name: 'execute.error',
385
+ executeId: this._executeId,
386
+ error,
387
+ });
323
388
  }
324
389
 
325
390
  _start(id: number, subscription: Subscription): void {
@@ -330,8 +395,16 @@ class Executor {
330
395
  // Handle a raw GraphQL response.
331
396
  _next(_id: number, response: GraphQLResponse): void {
332
397
  this._schedule(() => {
333
- this._handleNext(response);
334
- this._maybeCompleteSubscriptionOperationTracking();
398
+ const [duration] = withDuration(() => {
399
+ this._handleNext(response);
400
+ this._maybeCompleteSubscriptionOperationTracking();
401
+ });
402
+ this._log({
403
+ name: 'execute.next',
404
+ executeId: this._executeId,
405
+ response,
406
+ duration,
407
+ });
335
408
  });
336
409
  }
337
410
 
@@ -375,7 +448,8 @@ class Executor {
375
448
  error.stack;
376
449
  throw error;
377
450
  } else {
378
- const responseWithData: GraphQLResponseWithData = (response: $FlowFixMe);
451
+ const responseWithData: GraphQLResponseWithData =
452
+ (response: $FlowFixMe);
379
453
  results.push(responseWithData);
380
454
  }
381
455
  });
@@ -395,7 +469,10 @@ class Executor {
395
469
  responsePart => responsePart.extensions?.isOptimistic === true,
396
470
  )
397
471
  ) {
398
- invariant(false, 'Optimistic responses cannot be batched.');
472
+ invariant(
473
+ false,
474
+ 'OperationExecutor: Optimistic responses cannot be batched.',
475
+ );
399
476
  }
400
477
  return false;
401
478
  }
@@ -423,6 +500,7 @@ class Executor {
423
500
  if (this._state === 'completed') {
424
501
  return;
425
502
  }
503
+ this._seenActors.clear();
426
504
 
427
505
  const responses = Array.isArray(response) ? response : [response];
428
506
  const responsesWithData = this._handleErrorResponse(responses);
@@ -446,10 +524,8 @@ class Executor {
446
524
  return;
447
525
  }
448
526
 
449
- const [
450
- nonIncrementalResponses,
451
- incrementalResponses,
452
- ] = partitionGraphQLResponses(responsesWithData);
527
+ const [nonIncrementalResponses, incrementalResponses] =
528
+ partitionGraphQLResponses(responsesWithData);
453
529
  const hasNonIncrementalResponses = nonIncrementalResponses.length > 0;
454
530
 
455
531
  // In theory this doesn't preserve the ordering of the batch.
@@ -460,39 +536,38 @@ class Executor {
460
536
  // with the initial payload followed by some early-to-resolve incremental
461
537
  // payloads (although, can that even happen?)
462
538
  if (hasNonIncrementalResponses) {
463
- const payloadFollowups = this._processResponses(nonIncrementalResponses);
464
-
465
- if (!RelayFeatureFlags.ENABLE_BATCHED_STORE_UPDATES) {
466
- const updatedOwners = this._publishQueue.run(this._operation);
467
- this._updateOperationTracker(updatedOwners);
539
+ // For subscriptions, to avoid every new payload from overwriting existing
540
+ // data from previous payloads, assign a unique rootID for every new
541
+ // non-incremental payload.
542
+ if (this._isSubscriptionOperation) {
543
+ const nextID = generateUniqueClientID();
544
+ this._operation = {
545
+ request: this._operation.request,
546
+ fragment: createReaderSelector(
547
+ this._operation.fragment.node,
548
+ nextID,
549
+ this._operation.fragment.variables,
550
+ this._operation.fragment.owner,
551
+ ),
552
+ root: createNormalizationSelector(
553
+ this._operation.root.node,
554
+ nextID,
555
+ this._operation.root.variables,
556
+ ),
557
+ };
468
558
  }
469
559
 
560
+ const payloadFollowups = this._processResponses(nonIncrementalResponses);
470
561
  this._processPayloadFollowups(payloadFollowups);
471
-
472
- if (!RelayFeatureFlags.ENABLE_BATCHED_STORE_UPDATES) {
473
- if (this._incrementalPayloadsPending && !this._retainDisposable) {
474
- this._retainDisposable = this._store.retain(this._operation);
475
- }
476
- }
477
562
  }
478
563
 
479
564
  if (incrementalResponses.length > 0) {
480
- const payloadFollowups = this._processIncrementalResponses(
481
- incrementalResponses,
482
- );
483
- if (!RelayFeatureFlags.ENABLE_BATCHED_STORE_UPDATES) {
484
- // For the incremental case, we're only handling follow-up responses
485
- // for already initiated operation (and we're not passing it to
486
- // the run(...) call)
487
- const updatedOwners = this._publishQueue.run();
488
- this._updateOperationTracker(updatedOwners);
489
- }
565
+ const payloadFollowups =
566
+ this._processIncrementalResponses(incrementalResponses);
567
+
490
568
  this._processPayloadFollowups(payloadFollowups);
491
569
  }
492
- if (
493
- this._isSubscriptionOperation &&
494
- RelayFeatureFlags.ENABLE_UNIQUE_SUBSCRIPTION_ROOT
495
- ) {
570
+ if (this._isSubscriptionOperation) {
496
571
  // We attach the id to allow the `requestSubscription` to read from the store using
497
572
  // the current id in its `onNext` callback
498
573
  if (responsesWithData[0].extensions == null) {
@@ -501,51 +576,55 @@ class Executor {
501
576
  __relay_subscription_root_id: this._operation.fragment.dataID,
502
577
  };
503
578
  } else {
504
- responsesWithData[0].extensions.__relay_subscription_root_id = this._operation.fragment.dataID;
579
+ responsesWithData[0].extensions.__relay_subscription_root_id =
580
+ this._operation.fragment.dataID;
505
581
  }
506
582
  }
507
583
 
508
- if (RelayFeatureFlags.ENABLE_BATCHED_STORE_UPDATES) {
509
- // OK: run once after each new payload
510
- // If we have non-incremental responses, we passing `this._operation` to
511
- // the publish queue here, which will later be passed to the store (via
512
- // notify) to indicate that this operation caused the store to update
513
- const updatedOwners = this._publishQueue.run(
514
- hasNonIncrementalResponses ? this._operation : undefined,
515
- );
516
- if (hasNonIncrementalResponses) {
517
- if (this._incrementalPayloadsPending && !this._retainDisposable) {
518
- this._retainDisposable = this._store.retain(this._operation);
519
- }
584
+ // OK: run once after each new payload
585
+ // If we have non-incremental responses, we passing `this._operation` to
586
+ // the publish queue here, which will later be passed to the store (via
587
+ // notify) to indicate that this operation caused the store to update
588
+ const updatedOwners = this._runPublishQueue(
589
+ hasNonIncrementalResponses ? this._operation : undefined,
590
+ );
591
+
592
+ if (hasNonIncrementalResponses) {
593
+ if (this._incrementalPayloadsPending) {
594
+ this._retainData();
520
595
  }
521
- this._updateOperationTracker(updatedOwners);
522
596
  }
597
+ this._updateOperationTracker(updatedOwners);
523
598
  this._sink.next(response);
524
599
  }
525
600
 
526
601
  _processOptimisticResponse(
527
602
  response: ?GraphQLResponseWithData,
528
- updater: ?SelectorStoreUpdater,
603
+ updater: ?SelectorStoreUpdater<TMutation['response']>,
529
604
  treatMissingFieldsAsNull: boolean,
530
605
  ): void {
531
606
  invariant(
532
607
  this._optimisticUpdates === null,
533
- 'environment.execute: only support one optimistic response per ' +
608
+ 'OperationExecutor: environment.execute: only support one optimistic response per ' +
534
609
  'execute.',
535
610
  );
536
611
  if (response == null && updater == null) {
537
612
  return;
538
613
  }
539
- const optimisticUpdates: Array<OptimisticUpdate> = [];
614
+ const optimisticUpdates: Array<OptimisticUpdate<TMutation>> = [];
540
615
  if (response) {
541
616
  const payload = normalizeResponse(
542
617
  response,
543
618
  this._operation.root,
544
619
  ROOT_TYPE,
545
620
  {
621
+ actorIdentifier: this._actorIdentifier,
546
622
  getDataID: this._getDataID,
547
623
  path: [],
548
- reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
624
+ reactFlightPayloadDeserializer:
625
+ this._reactFlightPayloadDeserializer != null
626
+ ? this._deserializeReactFlightPayloadWithLogging
627
+ : null,
549
628
  reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
550
629
  shouldProcessClientComponents: this._shouldProcessClientComponents,
551
630
  treatMissingFieldsAsNull,
@@ -565,7 +644,7 @@ class Executor {
565
644
  errors: null,
566
645
  fieldPayloads: null,
567
646
  incrementalPlaceholders: null,
568
- moduleImportPayloads: null,
647
+ followupPayloads: null,
569
648
  source: RelayRecordSource.create(),
570
649
  isFinal: false,
571
650
  },
@@ -573,61 +652,90 @@ class Executor {
573
652
  });
574
653
  }
575
654
  this._optimisticUpdates = optimisticUpdates;
576
- optimisticUpdates.forEach(update => this._publishQueue.applyUpdate(update));
655
+ optimisticUpdates.forEach(update =>
656
+ this._getPublishQueueAndSaveActor().applyUpdate(update),
657
+ );
577
658
  // OK: only called on construction and when receiving an optimistic payload from network,
578
659
  // which doesn't fall-through to the regular next() handling
579
- this._publishQueue.run();
660
+ this._runPublishQueue();
580
661
  }
581
662
 
582
663
  _processOptimisticFollowups(
583
664
  payload: RelayResponsePayload,
584
- optimisticUpdates: Array<OptimisticUpdate>,
665
+ optimisticUpdates: Array<OptimisticUpdate<TMutation>>,
585
666
  ): void {
586
- if (payload.moduleImportPayloads && payload.moduleImportPayloads.length) {
587
- const moduleImportPayloads = payload.moduleImportPayloads;
588
- const operationLoader = this._operationLoader;
589
- invariant(
590
- operationLoader,
591
- 'RelayModernEnvironment: Expected an operationLoader to be ' +
592
- 'configured when using `@match`.',
593
- );
594
- for (const moduleImportPayload of moduleImportPayloads) {
595
- const operation = operationLoader.get(
596
- moduleImportPayload.operationReference,
597
- );
598
- if (operation == null) {
599
- this._processAsyncOptimisticModuleImport(
600
- operationLoader,
601
- moduleImportPayload,
602
- );
603
- } else {
604
- const moduleImportOptimisticUpdates = this._processOptimisticModuleImport(
605
- operation,
606
- moduleImportPayload,
607
- );
608
- optimisticUpdates.push(...moduleImportOptimisticUpdates);
667
+ if (payload.followupPayloads && payload.followupPayloads.length) {
668
+ const followupPayloads = payload.followupPayloads;
669
+ for (const followupPayload of followupPayloads) {
670
+ switch (followupPayload.kind) {
671
+ case 'ModuleImportPayload':
672
+ const operationLoader = this._expectOperationLoader();
673
+ const operation = operationLoader.get(
674
+ followupPayload.operationReference,
675
+ );
676
+ if (operation == null) {
677
+ this._processAsyncOptimisticModuleImport(followupPayload);
678
+ } else {
679
+ const moduleImportOptimisticUpdates =
680
+ this._processOptimisticModuleImport(operation, followupPayload);
681
+ optimisticUpdates.push(...moduleImportOptimisticUpdates);
682
+ }
683
+ break;
684
+ case 'ActorPayload':
685
+ warning(
686
+ false,
687
+ 'OperationExecutor: Unexpected optimistic ActorPayload. These updates are not supported.',
688
+ );
689
+ break;
690
+ default:
691
+ (followupPayload: empty);
692
+ invariant(
693
+ false,
694
+ 'OperationExecutor: Unexpected followup kind `%s`. when processing optimistic updates.',
695
+ followupPayload.kind,
696
+ );
609
697
  }
610
698
  }
611
699
  }
612
700
  }
613
701
 
614
- _normalizeModuleImport(
615
- moduleImportPayload: ModuleImportPayload,
616
- operation: NormalizationSelectableNode,
702
+ /**
703
+ * Normalize Data for @module payload, and actor-specific payload
704
+ */
705
+ _normalizeFollowupPayload(
706
+ followupPayload: FollowupPayload,
707
+ normalizationNode: NormalizationSelectableNode,
617
708
  ) {
709
+ let variables;
710
+ if (
711
+ normalizationNode.kind === 'SplitOperation' &&
712
+ followupPayload.kind === 'ModuleImportPayload'
713
+ ) {
714
+ variables = getLocalVariables(
715
+ followupPayload.variables,
716
+ normalizationNode.argumentDefinitions,
717
+ followupPayload.args,
718
+ );
719
+ } else {
720
+ variables = followupPayload.variables;
721
+ }
618
722
  const selector = createNormalizationSelector(
619
- operation,
620
- moduleImportPayload.dataID,
621
- moduleImportPayload.variables,
723
+ normalizationNode,
724
+ followupPayload.dataID,
725
+ variables,
622
726
  );
623
727
  return normalizeResponse(
624
- {data: moduleImportPayload.data},
728
+ {data: followupPayload.data},
625
729
  selector,
626
- moduleImportPayload.typeName,
730
+ followupPayload.typeName,
627
731
  {
732
+ actorIdentifier: this._actorIdentifier,
628
733
  getDataID: this._getDataID,
629
- path: moduleImportPayload.path,
630
- reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
734
+ path: followupPayload.path,
735
+ reactFlightPayloadDeserializer:
736
+ this._reactFlightPayloadDeserializer != null
737
+ ? this._deserializeReactFlightPayloadWithLogging
738
+ : null,
631
739
  reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
632
740
  treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
633
741
  shouldProcessClientComponents: this._shouldProcessClientComponents,
@@ -638,10 +746,10 @@ class Executor {
638
746
  _processOptimisticModuleImport(
639
747
  normalizationRootNode: NormalizationRootNode,
640
748
  moduleImportPayload: ModuleImportPayload,
641
- ): $ReadOnlyArray<OptimisticUpdate> {
749
+ ): $ReadOnlyArray<OptimisticUpdate<TMutation>> {
642
750
  const operation = getOperation(normalizationRootNode);
643
751
  const optimisticUpdates = [];
644
- const modulePayload = this._normalizeModuleImport(
752
+ const modulePayload = this._normalizeFollowupPayload(
645
753
  moduleImportPayload,
646
754
  operation,
647
755
  );
@@ -656,21 +764,18 @@ class Executor {
656
764
  }
657
765
 
658
766
  _processAsyncOptimisticModuleImport(
659
- operationLoader: OperationLoader,
660
767
  moduleImportPayload: ModuleImportPayload,
661
768
  ): void {
662
- operationLoader
769
+ this._expectOperationLoader()
663
770
  .load(moduleImportPayload.operationReference)
664
771
  .then(operation => {
665
772
  if (operation == null || this._state !== 'started') {
666
773
  return;
667
774
  }
668
- const moduleImportOptimisticUpdates = this._processOptimisticModuleImport(
669
- operation,
670
- moduleImportPayload,
671
- );
775
+ const moduleImportOptimisticUpdates =
776
+ this._processOptimisticModuleImport(operation, moduleImportPayload);
672
777
  moduleImportOptimisticUpdates.forEach(update =>
673
- this._publishQueue.applyUpdate(update),
778
+ this._getPublishQueueAndSaveActor().applyUpdate(update),
674
779
  );
675
780
  if (this._optimisticUpdates == null) {
676
781
  warning(
@@ -682,16 +787,18 @@ class Executor {
682
787
  } else {
683
788
  this._optimisticUpdates.push(...moduleImportOptimisticUpdates);
684
789
  // OK: always have to run() after an module import resolves async
685
- this._publishQueue.run();
790
+ this._runPublishQueue();
686
791
  }
687
792
  });
688
793
  }
689
794
 
690
- _processResponses(responses: $ReadOnlyArray<GraphQLResponseWithData>) {
795
+ _processResponses(
796
+ responses: $ReadOnlyArray<GraphQLResponseWithData>,
797
+ ): $ReadOnlyArray<RelayResponsePayload> {
691
798
  if (this._optimisticUpdates !== null) {
692
- this._optimisticUpdates.forEach(update =>
693
- this._publishQueue.revertUpdate(update),
694
- );
799
+ this._optimisticUpdates.forEach(update => {
800
+ this._getPublishQueueAndSaveActor().revertUpdate(update);
801
+ });
695
802
  this._optimisticUpdates = null;
696
803
  }
697
804
 
@@ -704,19 +811,24 @@ class Executor {
704
811
  this._operation.root,
705
812
  ROOT_TYPE,
706
813
  {
814
+ actorIdentifier: this._actorIdentifier,
707
815
  getDataID: this._getDataID,
708
816
  path: [],
709
- reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
817
+ reactFlightPayloadDeserializer:
818
+ this._reactFlightPayloadDeserializer != null
819
+ ? this._deserializeReactFlightPayloadWithLogging
820
+ : null,
710
821
  reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
711
822
  treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
712
823
  shouldProcessClientComponents: this._shouldProcessClientComponents,
713
824
  },
714
825
  );
715
- this._publishQueue.commitPayload(
826
+ this._getPublishQueueAndSaveActor().commitPayload(
716
827
  this._operation,
717
828
  relayPayload,
718
829
  this._updater,
719
830
  );
831
+
720
832
  return relayPayload;
721
833
  });
722
834
  }
@@ -732,30 +844,30 @@ class Executor {
732
844
  return;
733
845
  }
734
846
  payloads.forEach(payload => {
735
- const {incrementalPlaceholders, moduleImportPayloads, isFinal} = payload;
847
+ const {incrementalPlaceholders, followupPayloads, isFinal} = payload;
736
848
  this._state = isFinal ? 'loading_final' : 'loading_incremental';
737
849
  this._updateActiveState();
738
850
  if (isFinal) {
739
851
  this._incrementalPayloadsPending = false;
740
852
  }
741
- if (moduleImportPayloads && moduleImportPayloads.length !== 0) {
742
- const operationLoader = this._operationLoader;
743
- invariant(
744
- operationLoader,
745
- 'RelayModernEnvironment: Expected an operationLoader to be ' +
746
- 'configured when using `@match`.',
747
- );
748
- moduleImportPayloads.forEach(moduleImportPayload => {
749
- this._processModuleImportPayload(
750
- moduleImportPayload,
751
- operationLoader,
752
- );
853
+ if (followupPayloads && followupPayloads.length !== 0) {
854
+ followupPayloads.forEach(followupPayload => {
855
+ const prevActorIdentifier = this._actorIdentifier;
856
+ this._actorIdentifier =
857
+ followupPayload.actorIdentifier ?? this._actorIdentifier;
858
+ this._processFollowupPayload(followupPayload);
859
+ this._actorIdentifier = prevActorIdentifier;
753
860
  });
754
861
  }
862
+
755
863
  if (incrementalPlaceholders && incrementalPlaceholders.length !== 0) {
756
864
  this._incrementalPayloadsPending = this._state !== 'loading_final';
757
865
  incrementalPlaceholders.forEach(incrementalPlaceholder => {
866
+ const prevActorIdentifier = this._actorIdentifier;
867
+ this._actorIdentifier =
868
+ incrementalPlaceholder.actorIdentifier ?? this._actorIdentifier;
758
869
  this._processIncrementalPlaceholder(payload, incrementalPlaceholder);
870
+ this._actorIdentifier = prevActorIdentifier;
759
871
  });
760
872
 
761
873
  if (this._isClientPayload || this._state === 'loading_final') {
@@ -787,10 +899,6 @@ class Executor {
787
899
  }
788
900
  });
789
901
  if (relayPayloads.length > 0) {
790
- if (!RelayFeatureFlags.ENABLE_BATCHED_STORE_UPDATES) {
791
- const updatedOwners = this._publishQueue.run();
792
- this._updateOperationTracker(updatedOwners);
793
- }
794
902
  this._processPayloadFollowups(relayPayloads);
795
903
  }
796
904
  }
@@ -808,23 +916,6 @@ class Executor {
808
916
  ) {
809
917
  this._completeOperationTracker();
810
918
  }
811
- if (RelayFeatureFlags.ENABLE_UNIQUE_SUBSCRIPTION_ROOT) {
812
- const nextID = generateUniqueClientID();
813
- this._operation = {
814
- request: this._operation.request,
815
- fragment: createReaderSelector(
816
- this._operation.fragment.node,
817
- nextID,
818
- this._operation.fragment.variables,
819
- this._operation.fragment.owner,
820
- ),
821
- root: createNormalizationSelector(
822
- this._operation.root.node,
823
- nextID,
824
- this._operation.root.variables,
825
- ),
826
- };
827
- }
828
919
  }
829
920
 
830
921
  /**
@@ -834,78 +925,154 @@ class Executor {
834
925
  * defer, stream, etc); these are handled by calling
835
926
  * `_processPayloadFollowups()`.
836
927
  */
837
- _processModuleImportPayload(
838
- moduleImportPayload: ModuleImportPayload,
839
- operationLoader: OperationLoader,
840
- ): void {
841
- const node = operationLoader.get(moduleImportPayload.operationReference);
842
- if (node != null) {
843
- const operation = getOperation(node);
844
- // If the operation module is available synchronously, normalize the
845
- // data synchronously.
846
- this._handleModuleImportPayload(moduleImportPayload, operation);
847
- this._maybeCompleteSubscriptionOperationTracking();
848
- } else {
849
- // Otherwise load the operation module and schedule a task to normalize
850
- // the data when the module is available.
851
- const id = this._nextSubscriptionId++;
852
- this._pendingModulePayloadsCount++;
853
-
854
- const decrementPendingCount = () => {
855
- this._pendingModulePayloadsCount--;
856
- this._maybeCompleteSubscriptionOperationTracking();
857
- };
858
-
859
- // Observable.from(operationLoader.load()) wouldn't catch synchronous
860
- // errors thrown by the load function, which is user-defined. Guard
861
- // against that with Observable.from(new Promise(<work>)).
862
- RelayObservable.from(
863
- new Promise((resolve, reject) => {
864
- operationLoader
865
- .load(moduleImportPayload.operationReference)
866
- .then(resolve, reject);
867
- }),
868
- )
869
- .map((operation: ?NormalizationRootNode) => {
870
- if (operation != null) {
871
- this._schedule(() => {
872
- this._handleModuleImportPayload(
873
- moduleImportPayload,
874
- getOperation(operation),
875
- );
876
- // OK: always have to run after an async module import resolves
877
- const updatedOwners = this._publishQueue.run();
878
- this._updateOperationTracker(updatedOwners);
928
+ _processFollowupPayload(followupPayload: FollowupPayload): void {
929
+ switch (followupPayload.kind) {
930
+ case 'ModuleImportPayload':
931
+ const operationLoader = this._expectOperationLoader();
932
+ const node = operationLoader.get(followupPayload.operationReference);
933
+ if (node != null) {
934
+ // If the operation module is available synchronously, normalize the
935
+ // data synchronously.
936
+ this._processFollowupPayloadWithNormalizationNode(
937
+ followupPayload,
938
+ getOperation(node),
939
+ );
940
+ } else {
941
+ // Otherwise load the operation module and schedule a task to normalize
942
+ // the data when the module is available.
943
+ const id = this._nextSubscriptionId++;
944
+ this._pendingModulePayloadsCount++;
945
+
946
+ const decrementPendingCount = () => {
947
+ this._pendingModulePayloadsCount--;
948
+ this._maybeCompleteSubscriptionOperationTracking();
949
+ };
950
+
951
+ // Observable.from(operationLoader.load()) wouldn't catch synchronous
952
+ // errors thrown by the load function, which is user-defined. Guard
953
+ // against that with Observable.from(new Promise(<work>)).
954
+ const networkObservable = RelayObservable.from(
955
+ new Promise((resolve, reject) => {
956
+ operationLoader
957
+ .load(followupPayload.operationReference)
958
+ .then(resolve, reject);
959
+ }),
960
+ );
961
+ RelayObservable.create(sink => {
962
+ let cancellationToken;
963
+ const subscription = networkObservable.subscribe({
964
+ next: (loadedNode: ?NormalizationRootNode) => {
965
+ if (loadedNode != null) {
966
+ const publishModuleImportPayload = () => {
967
+ try {
968
+ const operation = getOperation(loadedNode);
969
+ const batchAsyncModuleUpdatesFN =
970
+ RelayFeatureFlags.BATCH_ASYNC_MODULE_UPDATES_FN;
971
+ const shouldScheduleAsyncStoreUpdate =
972
+ batchAsyncModuleUpdatesFN != null &&
973
+ this._pendingModulePayloadsCount > 1;
974
+ const [duration] = withDuration(() => {
975
+ this._handleFollowupPayload(followupPayload, operation);
976
+ // OK: always have to run after an async module import resolves
977
+ if (shouldScheduleAsyncStoreUpdate) {
978
+ this._scheduleAsyncStoreUpdate(
979
+ // $FlowFixMe[incompatible-call] `shouldScheduleAsyncStoreUpdate` check should cover `null` case
980
+ batchAsyncModuleUpdatesFN,
981
+ sink.complete,
982
+ );
983
+ } else {
984
+ const updatedOwners = this._runPublishQueue();
985
+ this._updateOperationTracker(updatedOwners);
986
+ }
987
+ });
988
+ this._log({
989
+ name: 'execute.async.module',
990
+ executeId: this._executeId,
991
+ operationName: operation.name,
992
+ duration,
993
+ });
994
+ if (!shouldScheduleAsyncStoreUpdate) {
995
+ sink.complete();
996
+ }
997
+ } catch (error) {
998
+ sink.error(error);
999
+ }
1000
+ };
1001
+ const scheduler = this._scheduler;
1002
+ if (scheduler == null) {
1003
+ publishModuleImportPayload();
1004
+ } else {
1005
+ cancellationToken = scheduler.schedule(
1006
+ publishModuleImportPayload,
1007
+ );
1008
+ }
1009
+ } else {
1010
+ sink.complete();
1011
+ }
1012
+ },
1013
+ error: sink.error,
879
1014
  });
880
- }
881
- })
882
- .subscribe({
883
- complete: () => {
884
- this._complete(id);
885
- decrementPendingCount();
886
- },
887
- error: error => {
888
- this._error(error);
889
- decrementPendingCount();
890
- },
891
- start: subscription => this._start(id, subscription),
892
- });
1015
+ return () => {
1016
+ subscription.unsubscribe();
1017
+ if (this._scheduler != null && cancellationToken != null) {
1018
+ this._scheduler.cancel(cancellationToken);
1019
+ }
1020
+ };
1021
+ }).subscribe({
1022
+ complete: () => {
1023
+ this._complete(id);
1024
+ decrementPendingCount();
1025
+ },
1026
+ error: error => {
1027
+ this._error(error);
1028
+ decrementPendingCount();
1029
+ },
1030
+ start: subscription => this._start(id, subscription),
1031
+ });
1032
+ }
1033
+ break;
1034
+ case 'ActorPayload':
1035
+ this._processFollowupPayloadWithNormalizationNode(
1036
+ followupPayload,
1037
+ followupPayload.node,
1038
+ );
1039
+ break;
1040
+ default:
1041
+ (followupPayload: empty);
1042
+ invariant(
1043
+ false,
1044
+ 'OperationExecutor: Unexpected followup kind `%s`.',
1045
+ followupPayload.kind,
1046
+ );
893
1047
  }
894
1048
  }
895
1049
 
896
- _handleModuleImportPayload(
897
- moduleImportPayload: ModuleImportPayload,
898
- operation: NormalizationSplitOperation | NormalizationOperation,
1050
+ _processFollowupPayloadWithNormalizationNode(
1051
+ followupPayload: FollowupPayload,
1052
+ normalizationNode:
1053
+ | NormalizationSplitOperation
1054
+ | NormalizationOperation
1055
+ | NormalizationLinkedField,
1056
+ ) {
1057
+ this._handleFollowupPayload(followupPayload, normalizationNode);
1058
+ this._maybeCompleteSubscriptionOperationTracking();
1059
+ }
1060
+
1061
+ _handleFollowupPayload(
1062
+ followupPayload: FollowupPayload,
1063
+ normalizationNode:
1064
+ | NormalizationSplitOperation
1065
+ | NormalizationOperation
1066
+ | NormalizationLinkedField,
899
1067
  ): void {
900
- const relayPayload = this._normalizeModuleImport(
901
- moduleImportPayload,
902
- operation,
1068
+ const relayPayload = this._normalizeFollowupPayload(
1069
+ followupPayload,
1070
+ normalizationNode,
1071
+ );
1072
+ this._getPublishQueueAndSaveActor().commitPayload(
1073
+ this._operation,
1074
+ relayPayload,
903
1075
  );
904
- this._publishQueue.commitPayload(this._operation, relayPayload);
905
- if (!RelayFeatureFlags.ENABLE_BATCHED_STORE_UPDATES) {
906
- const updatedOwners = this._publishQueue.run();
907
- this._updateOperationTracker(updatedOwners);
908
- }
909
1076
  this._processPayloadFollowups([relayPayload]);
910
1077
  }
911
1078
 
@@ -951,7 +1118,7 @@ class Executor {
951
1118
  (placeholder: empty);
952
1119
  invariant(
953
1120
  false,
954
- 'Unsupported incremental placeholder kind `%s`.',
1121
+ 'OperationExecutor: Unsupported incremental placeholder kind `%s`.',
955
1122
  placeholder.kind,
956
1123
  );
957
1124
  }
@@ -975,7 +1142,7 @@ class Executor {
975
1142
  // exist.
976
1143
  invariant(
977
1144
  parentRecord != null,
978
- 'RelayModernEnvironment: Expected record `%s` to exist.',
1145
+ 'OperationExecutor: Expected record `%s` to exist.',
979
1146
  parentID,
980
1147
  );
981
1148
  let nextParentRecord;
@@ -1004,16 +1171,12 @@ class Executor {
1004
1171
  record: nextParentRecord,
1005
1172
  fieldPayloads: nextParentPayloads,
1006
1173
  });
1174
+
1007
1175
  // If there were any queued responses, process them now that placeholders
1008
1176
  // are in place
1009
1177
  if (pendingResponses != null) {
1010
- const payloadFollowups = this._processIncrementalResponses(
1011
- pendingResponses,
1012
- );
1013
- if (!RelayFeatureFlags.ENABLE_BATCHED_STORE_UPDATES) {
1014
- const updatedOwners = this._publishQueue.run();
1015
- this._updateOperationTracker(updatedOwners);
1016
- }
1178
+ const payloadFollowups =
1179
+ this._processIncrementalResponses(pendingResponses);
1017
1180
  this._processPayloadFollowups(payloadFollowups);
1018
1181
  }
1019
1182
  }
@@ -1049,7 +1212,7 @@ class Executor {
1049
1212
  const placeholder = resultForPath.placeholder;
1050
1213
  invariant(
1051
1214
  placeholder.kind === 'defer',
1052
- 'RelayModernEnvironment: Expected data for path `%s` for label `%s` ' +
1215
+ 'OperationExecutor: Expected data for path `%s` for label `%s` ' +
1053
1216
  'to be data for @defer, was `@%s`.',
1054
1217
  pathKey,
1055
1218
  label,
@@ -1063,10 +1226,7 @@ class Executor {
1063
1226
  // but Relay records paths relative to the parent of the stream node:
1064
1227
  // therefore we strip the last two elements just to lookup the path
1065
1228
  // (the item index is used later to insert the element in the list)
1066
- const pathKey = path
1067
- .slice(0, -2)
1068
- .map(String)
1069
- .join('.');
1229
+ const pathKey = path.slice(0, -2).map(String).join('.');
1070
1230
  let resultForPath = resultForLabel.get(pathKey);
1071
1231
  if (resultForPath == null) {
1072
1232
  resultForPath = {kind: 'response', responses: [incrementalResponse]};
@@ -1079,7 +1239,7 @@ class Executor {
1079
1239
  const placeholder = resultForPath.placeholder;
1080
1240
  invariant(
1081
1241
  placeholder.kind === 'stream',
1082
- 'RelayModernEnvironment: Expected data for path `%s` for label `%s` ' +
1242
+ 'OperationExecutor: Expected data for path `%s` for label `%s` ' +
1083
1243
  'to be data for @stream, was `@%s`.',
1084
1244
  pathKey,
1085
1245
  label,
@@ -1100,27 +1260,37 @@ class Executor {
1100
1260
  response: GraphQLResponseWithData,
1101
1261
  ): RelayResponsePayload {
1102
1262
  const {dataID: parentID} = placeholder.selector;
1263
+ const prevActorIdentifier = this._actorIdentifier;
1264
+ this._actorIdentifier =
1265
+ placeholder.actorIdentifier ?? this._actorIdentifier;
1103
1266
  const relayPayload = normalizeResponse(
1104
1267
  response,
1105
1268
  placeholder.selector,
1106
1269
  placeholder.typeName,
1107
1270
  {
1271
+ actorIdentifier: this._actorIdentifier,
1108
1272
  getDataID: this._getDataID,
1109
1273
  path: placeholder.path,
1110
- reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
1274
+ reactFlightPayloadDeserializer:
1275
+ this._reactFlightPayloadDeserializer != null
1276
+ ? this._deserializeReactFlightPayloadWithLogging
1277
+ : null,
1111
1278
  reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
1112
1279
  treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
1113
1280
  shouldProcessClientComponents: this._shouldProcessClientComponents,
1114
1281
  },
1115
1282
  );
1116
- this._publishQueue.commitPayload(this._operation, relayPayload);
1283
+ this._getPublishQueueAndSaveActor().commitPayload(
1284
+ this._operation,
1285
+ relayPayload,
1286
+ );
1117
1287
 
1118
1288
  // Load the version of the parent record from which this incremental data
1119
1289
  // was derived
1120
1290
  const parentEntry = this._source.get(parentID);
1121
1291
  invariant(
1122
1292
  parentEntry != null,
1123
- 'RelayModernEnvironment: Expected the parent record `%s` for @defer ' +
1293
+ 'OperationExecutor: Expected the parent record `%s` for @defer ' +
1124
1294
  'data to exist.',
1125
1295
  parentID,
1126
1296
  );
@@ -1130,15 +1300,17 @@ class Executor {
1130
1300
  errors: null,
1131
1301
  fieldPayloads,
1132
1302
  incrementalPlaceholders: null,
1133
- moduleImportPayloads: null,
1303
+ followupPayloads: null,
1134
1304
  source: RelayRecordSource.create(),
1135
1305
  isFinal: response.extensions?.is_final === true,
1136
1306
  };
1137
- this._publishQueue.commitPayload(
1307
+ this._getPublishQueueAndSaveActor().commitPayload(
1138
1308
  this._operation,
1139
1309
  handleFieldsRelayPayload,
1140
1310
  );
1141
1311
  }
1312
+
1313
+ this._actorIdentifier = prevActorIdentifier;
1142
1314
  return relayPayload;
1143
1315
  }
1144
1316
 
@@ -1151,12 +1323,14 @@ class Executor {
1151
1323
  placeholder: StreamPlaceholder,
1152
1324
  response: GraphQLResponseWithData,
1153
1325
  ): RelayResponsePayload {
1154
- const {parentID, node, variables} = placeholder;
1326
+ const {parentID, node, variables, actorIdentifier} = placeholder;
1327
+ const prevActorIdentifier = this._actorIdentifier;
1328
+ this._actorIdentifier = actorIdentifier ?? this._actorIdentifier;
1155
1329
  // Find the LinkedField where @stream was applied
1156
1330
  const field = node.selections[0];
1157
1331
  invariant(
1158
1332
  field != null && field.kind === 'LinkedField' && field.plural === true,
1159
- 'RelayModernEnvironment: Expected @stream to be used on a plural field.',
1333
+ 'OperationExecutor: Expected @stream to be used on a plural field.',
1160
1334
  );
1161
1335
  const {
1162
1336
  fieldPayloads,
@@ -1176,34 +1350,38 @@ class Executor {
1176
1350
  // Publish the new item and update the parent record to set
1177
1351
  // field[index] = item *if* the parent record hasn't been concurrently
1178
1352
  // modified.
1179
- this._publishQueue.commitPayload(this._operation, relayPayload, store => {
1180
- const currentParentRecord = store.get(parentID);
1181
- if (currentParentRecord == null) {
1182
- // parent has since been deleted, stream data is stale
1183
- return;
1184
- }
1185
- const currentItems = currentParentRecord.getLinkedRecords(storageKey);
1186
- if (currentItems == null) {
1187
- // field has since been deleted, stream data is stale
1188
- return;
1189
- }
1190
- if (
1191
- currentItems.length !== prevIDs.length ||
1192
- currentItems.some(
1193
- (currentItem, index) =>
1194
- prevIDs[index] !== (currentItem && currentItem.getDataID()),
1195
- )
1196
- ) {
1197
- // field has been modified by something other than this query,
1198
- // stream data is stale
1199
- return;
1200
- }
1201
- // parent.field has not been concurrently modified:
1202
- // update `parent.field[index] = item`
1203
- const nextItems = [...currentItems];
1204
- nextItems[itemIndex] = store.get(itemID);
1205
- currentParentRecord.setLinkedRecords(nextItems, storageKey);
1206
- });
1353
+ this._getPublishQueueAndSaveActor().commitPayload(
1354
+ this._operation,
1355
+ relayPayload,
1356
+ store => {
1357
+ const currentParentRecord = store.get(parentID);
1358
+ if (currentParentRecord == null) {
1359
+ // parent has since been deleted, stream data is stale
1360
+ return;
1361
+ }
1362
+ const currentItems = currentParentRecord.getLinkedRecords(storageKey);
1363
+ if (currentItems == null) {
1364
+ // field has since been deleted, stream data is stale
1365
+ return;
1366
+ }
1367
+ if (
1368
+ currentItems.length !== prevIDs.length ||
1369
+ currentItems.some(
1370
+ (currentItem, index) =>
1371
+ prevIDs[index] !== (currentItem && currentItem.getDataID()),
1372
+ )
1373
+ ) {
1374
+ // field has been modified by something other than this query,
1375
+ // stream data is stale
1376
+ return;
1377
+ }
1378
+ // parent.field has not been concurrently modified:
1379
+ // update `parent.field[index] = item`
1380
+ const nextItems = [...currentItems];
1381
+ nextItems[itemIndex] = store.get(itemID);
1382
+ currentParentRecord.setLinkedRecords(nextItems, storageKey);
1383
+ },
1384
+ );
1207
1385
 
1208
1386
  // Now that the parent record has been updated to include the new item,
1209
1387
  // also update any handle fields that are derived from the parent record.
@@ -1212,15 +1390,17 @@ class Executor {
1212
1390
  errors: null,
1213
1391
  fieldPayloads,
1214
1392
  incrementalPlaceholders: null,
1215
- moduleImportPayloads: null,
1393
+ followupPayloads: null,
1216
1394
  source: RelayRecordSource.create(),
1217
1395
  isFinal: false,
1218
1396
  };
1219
- this._publishQueue.commitPayload(
1397
+ this._getPublishQueueAndSaveActor().commitPayload(
1220
1398
  this._operation,
1221
1399
  handleFieldsRelayPayload,
1222
1400
  );
1223
1401
  }
1402
+
1403
+ this._actorIdentifier = prevActorIdentifier;
1224
1404
  return relayPayload;
1225
1405
  }
1226
1406
 
@@ -1242,7 +1422,7 @@ class Executor {
1242
1422
  const {data} = response;
1243
1423
  invariant(
1244
1424
  typeof data === 'object',
1245
- 'RelayModernEnvironment: Expected the GraphQL @stream payload `data` ' +
1425
+ 'OperationExecutor: Expected the GraphQL @stream payload `data` ' +
1246
1426
  'value to be an object.',
1247
1427
  );
1248
1428
  const responseKey = field.alias ?? field.name;
@@ -1253,7 +1433,7 @@ class Executor {
1253
1433
  const parentEntry = this._source.get(parentID);
1254
1434
  invariant(
1255
1435
  parentEntry != null,
1256
- 'RelayModernEnvironment: Expected the parent record `%s` for @stream ' +
1436
+ 'OperationExecutor: Expected the parent record `%s` for @stream ' +
1257
1437
  'data to exist.',
1258
1438
  parentID,
1259
1439
  );
@@ -1268,7 +1448,7 @@ class Executor {
1268
1448
  );
1269
1449
  invariant(
1270
1450
  prevIDs != null,
1271
- 'RelayModernEnvironment: Expected record `%s` to have fetched field ' +
1451
+ 'OperationExecutor: Expected record `%s` to have fetched field ' +
1272
1452
  '`%s` with @stream.',
1273
1453
  parentID,
1274
1454
  field.name,
@@ -1279,7 +1459,7 @@ class Executor {
1279
1459
  const itemIndex = parseInt(finalPathEntry, 10);
1280
1460
  invariant(
1281
1461
  itemIndex === finalPathEntry && itemIndex >= 0,
1282
- 'RelayModernEnvironment: Expected path for @stream to end in a ' +
1462
+ 'OperationExecutor: Expected path for @stream to end in a ' +
1283
1463
  'positive integer index, got `%s`',
1284
1464
  finalPathEntry,
1285
1465
  );
@@ -1287,7 +1467,7 @@ class Executor {
1287
1467
  const typeName = field.concreteType ?? data[TYPENAME_KEY];
1288
1468
  invariant(
1289
1469
  typeof typeName === 'string',
1290
- 'RelayModernEnvironment: Expected @stream field `%s` to have a ' +
1470
+ 'OperationExecutor: Expected @stream field `%s` to have a ' +
1291
1471
  '__typename.',
1292
1472
  field.name,
1293
1473
  );
@@ -1302,7 +1482,7 @@ class Executor {
1302
1482
  generateClientID(parentID, storageKey, itemIndex);
1303
1483
  invariant(
1304
1484
  typeof itemID === 'string',
1305
- 'RelayModernEnvironment: Expected id of elements of field `%s` to ' +
1485
+ 'OperationExecutor: Expected id of elements of field `%s` to ' +
1306
1486
  'be strings.',
1307
1487
  storageKey,
1308
1488
  );
@@ -1322,9 +1502,13 @@ class Executor {
1322
1502
  fieldPayloads,
1323
1503
  });
1324
1504
  const relayPayload = normalizeResponse(response, selector, typeName, {
1505
+ actorIdentifier: this._actorIdentifier,
1325
1506
  getDataID: this._getDataID,
1326
1507
  path: [...normalizationPath, responseKey, String(itemIndex)],
1327
- reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
1508
+ reactFlightPayloadDeserializer:
1509
+ this._reactFlightPayloadDeserializer != null
1510
+ ? this._deserializeReactFlightPayloadWithLogging
1511
+ : null,
1328
1512
  reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
1329
1513
  treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
1330
1514
  shouldProcessClientComponents: this._shouldProcessClientComponents,
@@ -1339,6 +1523,25 @@ class Executor {
1339
1523
  };
1340
1524
  }
1341
1525
 
1526
+ _scheduleAsyncStoreUpdate(
1527
+ scheduleFn: (() => void) => Disposable,
1528
+ completeFn: () => void,
1529
+ ): void {
1530
+ this._completeFns.push(completeFn);
1531
+ if (this._asyncStoreUpdateDisposable != null) {
1532
+ return;
1533
+ }
1534
+ this._asyncStoreUpdateDisposable = scheduleFn(() => {
1535
+ this._asyncStoreUpdateDisposable = null;
1536
+ const updatedOwners = this._runPublishQueue();
1537
+ this._updateOperationTracker(updatedOwners);
1538
+ for (const complete of this._completeFns) {
1539
+ complete();
1540
+ }
1541
+ this._completeFns = [];
1542
+ });
1543
+ }
1544
+
1342
1545
  _updateOperationTracker(
1343
1546
  updatedOwners: ?$ReadOnlyArray<RequestDescriptor>,
1344
1547
  ): void {
@@ -1353,6 +1556,58 @@ class Executor {
1353
1556
  _completeOperationTracker() {
1354
1557
  this._operationTracker.complete(this._operation.request);
1355
1558
  }
1559
+
1560
+ _getPublishQueueAndSaveActor(): PublishQueue {
1561
+ this._seenActors.add(this._actorIdentifier);
1562
+ return this._getPublishQueue(this._actorIdentifier);
1563
+ }
1564
+
1565
+ _getActorsToVisit(): $ReadOnlySet<ActorIdentifier> {
1566
+ if (this._seenActors.size === 0) {
1567
+ return new Set([this._actorIdentifier]);
1568
+ } else {
1569
+ return this._seenActors;
1570
+ }
1571
+ }
1572
+
1573
+ _runPublishQueue(
1574
+ operation?: OperationDescriptor,
1575
+ ): $ReadOnlyArray<RequestDescriptor> {
1576
+ const updatedOwners = new Set();
1577
+ for (const actorIdentifier of this._getActorsToVisit()) {
1578
+ const owners = this._getPublishQueue(actorIdentifier).run(operation);
1579
+ owners.forEach(owner => updatedOwners.add(owner));
1580
+ }
1581
+ return Array.from(updatedOwners);
1582
+ }
1583
+
1584
+ _retainData() {
1585
+ for (const actorIdentifier of this._getActorsToVisit()) {
1586
+ if (!this._retainDisposables.has(actorIdentifier)) {
1587
+ this._retainDisposables.set(
1588
+ actorIdentifier,
1589
+ this._getStore(actorIdentifier).retain(this._operation),
1590
+ );
1591
+ }
1592
+ }
1593
+ }
1594
+
1595
+ _disposeRetainedData() {
1596
+ for (const disposable of this._retainDisposables.values()) {
1597
+ disposable.dispose();
1598
+ }
1599
+ this._retainDisposables.clear();
1600
+ }
1601
+
1602
+ _expectOperationLoader(): OperationLoader {
1603
+ const operationLoader = this._operationLoader;
1604
+ invariant(
1605
+ operationLoader,
1606
+ 'OperationExecutor: Expected an operationLoader to be ' +
1607
+ 'configured when using `@match`.',
1608
+ );
1609
+ return operationLoader;
1610
+ }
1356
1611
  }
1357
1612
 
1358
1613
  function partitionGraphQLResponses(