relay-runtime 11.0.0 → 13.0.0-rc.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 (169) hide show
  1. package/handlers/RelayDefaultHandlerProvider.js.flow +2 -2
  2. package/handlers/connection/ConnectionHandler.js.flow +8 -10
  3. package/handlers/connection/MutationHandlers.js.flow +31 -7
  4. package/index.js +1 -1
  5. package/index.js.flow +60 -36
  6. package/lib/handlers/RelayDefaultHandlerProvider.js +1 -1
  7. package/lib/handlers/connection/ConnectionHandler.js +8 -8
  8. package/lib/handlers/connection/MutationHandlers.js +61 -5
  9. package/lib/index.js +58 -43
  10. package/lib/multi-actor-environment/ActorIdentifier.js +33 -0
  11. package/lib/multi-actor-environment/ActorSpecificEnvironment.js +152 -0
  12. package/lib/multi-actor-environment/ActorUtils.js +27 -0
  13. package/lib/multi-actor-environment/MultiActorEnvironment.js +419 -0
  14. package/lib/multi-actor-environment/MultiActorEnvironmentTypes.js +11 -0
  15. package/lib/multi-actor-environment/index.js +21 -0
  16. package/lib/mutations/RelayDeclarativeMutationConfig.js +4 -1
  17. package/lib/mutations/RelayRecordProxy.js +3 -2
  18. package/lib/mutations/RelayRecordSourceMutator.js +3 -2
  19. package/lib/mutations/RelayRecordSourceProxy.js +12 -4
  20. package/lib/mutations/RelayRecordSourceSelectorProxy.js +18 -5
  21. package/lib/mutations/applyOptimisticMutation.js +6 -6
  22. package/lib/mutations/commitMutation.js +14 -10
  23. package/lib/mutations/readUpdatableQuery_EXPERIMENTAL.js +238 -0
  24. package/lib/mutations/validateMutation.js +12 -5
  25. package/lib/network/ConvertToExecuteFunction.js +2 -1
  26. package/lib/network/RelayNetwork.js +3 -2
  27. package/lib/network/RelayQueryResponseCache.js +21 -4
  28. package/lib/network/wrapNetworkWithLogObserver.js +79 -0
  29. package/lib/query/GraphQLTag.js +3 -2
  30. package/lib/query/fetchQuery.js +6 -5
  31. package/lib/query/fetchQueryInternal.js +1 -1
  32. package/lib/query/fetchQuery_DEPRECATED.js +2 -1
  33. package/lib/store/ClientID.js +7 -1
  34. package/lib/store/DataChecker.js +141 -60
  35. package/lib/store/{RelayModernQueryExecutor.js → OperationExecutor.js} +532 -195
  36. package/lib/store/RelayConcreteVariables.js +24 -4
  37. package/lib/store/RelayModernEnvironment.js +175 -234
  38. package/lib/store/RelayModernFragmentSpecResolver.js +52 -26
  39. package/lib/store/RelayModernOperationDescriptor.js +2 -1
  40. package/lib/store/RelayModernRecord.js +47 -12
  41. package/lib/store/RelayModernSelector.js +14 -8
  42. package/lib/store/RelayModernStore.js +58 -29
  43. package/lib/store/RelayOperationTracker.js +34 -24
  44. package/lib/store/RelayPublishQueue.js +41 -13
  45. package/lib/store/RelayReader.js +287 -46
  46. package/lib/store/RelayRecordSource.js +87 -3
  47. package/lib/store/RelayReferenceMarker.js +55 -31
  48. package/lib/store/RelayResponseNormalizer.js +250 -108
  49. package/lib/store/RelayStoreReactFlightUtils.js +8 -12
  50. package/lib/store/RelayStoreSubscriptions.js +14 -9
  51. package/lib/store/RelayStoreUtils.js +11 -5
  52. package/lib/store/ResolverCache.js +213 -0
  53. package/lib/store/ResolverFragments.js +61 -0
  54. package/lib/store/cloneRelayHandleSourceField.js +5 -4
  55. package/lib/store/cloneRelayScalarHandleSourceField.js +5 -4
  56. package/lib/store/createRelayContext.js +4 -2
  57. package/lib/store/defaultGetDataID.js +3 -1
  58. package/lib/store/readInlineData.js +6 -2
  59. package/lib/subscription/requestSubscription.js +35 -9
  60. package/lib/util/RelayConcreteNode.js +4 -0
  61. package/lib/util/RelayFeatureFlags.js +11 -4
  62. package/lib/util/RelayProfiler.js +17 -187
  63. package/lib/util/RelayReplaySubject.js +22 -7
  64. package/lib/util/RelayRuntimeTypes.js +0 -6
  65. package/lib/util/StringInterner.js +71 -0
  66. package/lib/util/deepFreeze.js +1 -0
  67. package/lib/util/getFragmentIdentifier.js +15 -7
  68. package/lib/util/getOperation.js +2 -1
  69. package/lib/util/getPaginationMetadata.js +41 -0
  70. package/lib/util/getPaginationVariables.js +66 -0
  71. package/lib/util/getPendingOperationsForFragment.js +55 -0
  72. package/lib/util/getRefetchMetadata.js +36 -0
  73. package/lib/util/getRelayHandleKey.js +2 -2
  74. package/lib/util/getRequestIdentifier.js +2 -2
  75. package/lib/util/getValueAtPath.js +51 -0
  76. package/lib/util/isEmptyObject.js +1 -1
  77. package/lib/util/registerEnvironmentWithDevTools.js +26 -0
  78. package/lib/util/withDuration.js +31 -0
  79. package/multi-actor-environment/ActorIdentifier.js.flow +43 -0
  80. package/multi-actor-environment/ActorSpecificEnvironment.js.flow +225 -0
  81. package/multi-actor-environment/ActorUtils.js.flow +33 -0
  82. package/multi-actor-environment/MultiActorEnvironment.js.flow +506 -0
  83. package/multi-actor-environment/MultiActorEnvironmentTypes.js.flow +261 -0
  84. package/multi-actor-environment/index.js.flow +26 -0
  85. package/mutations/RelayDeclarativeMutationConfig.js.flow +32 -26
  86. package/mutations/RelayRecordProxy.js.flow +4 -5
  87. package/mutations/RelayRecordSourceMutator.js.flow +4 -6
  88. package/mutations/RelayRecordSourceProxy.js.flow +19 -10
  89. package/mutations/RelayRecordSourceSelectorProxy.js.flow +22 -7
  90. package/mutations/applyOptimisticMutation.js.flow +13 -14
  91. package/mutations/commitLocalUpdate.js.flow +1 -1
  92. package/mutations/commitMutation.js.flow +35 -46
  93. package/mutations/readUpdatableQuery_EXPERIMENTAL.js.flow +309 -0
  94. package/mutations/validateMutation.js.flow +28 -16
  95. package/network/ConvertToExecuteFunction.js.flow +2 -2
  96. package/network/RelayNetwork.js.flow +4 -5
  97. package/network/RelayNetworkTypes.js.flow +17 -8
  98. package/network/RelayObservable.js.flow +1 -1
  99. package/network/RelayQueryResponseCache.js.flow +34 -20
  100. package/network/wrapNetworkWithLogObserver.js.flow +100 -0
  101. package/package.json +3 -2
  102. package/query/GraphQLTag.js.flow +9 -9
  103. package/query/PreloadableQueryRegistry.js.flow +2 -1
  104. package/query/fetchQuery.js.flow +11 -13
  105. package/query/fetchQueryInternal.js.flow +6 -9
  106. package/query/fetchQuery_DEPRECATED.js.flow +6 -6
  107. package/relay-runtime.js +2 -2
  108. package/relay-runtime.min.js +2 -2
  109. package/store/ClientID.js.flow +14 -3
  110. package/store/DataChecker.js.flow +162 -67
  111. package/store/{RelayModernQueryExecutor.js.flow → OperationExecutor.js.flow} +616 -283
  112. package/store/RelayConcreteVariables.js.flow +27 -5
  113. package/store/RelayModernEnvironment.js.flow +176 -235
  114. package/store/RelayModernFragmentSpecResolver.js.flow +55 -31
  115. package/store/RelayModernOperationDescriptor.js.flow +12 -7
  116. package/store/RelayModernRecord.js.flow +67 -11
  117. package/store/RelayModernSelector.js.flow +24 -14
  118. package/store/RelayModernStore.js.flow +72 -36
  119. package/store/RelayOperationTracker.js.flow +59 -43
  120. package/store/RelayOptimisticRecordSource.js.flow +2 -2
  121. package/store/RelayPublishQueue.js.flow +79 -34
  122. package/store/RelayReader.js.flow +351 -72
  123. package/store/RelayRecordSource.js.flow +72 -6
  124. package/store/RelayReferenceMarker.js.flow +60 -33
  125. package/store/RelayResponseNormalizer.js.flow +288 -102
  126. package/store/RelayStoreReactFlightUtils.js.flow +9 -13
  127. package/store/RelayStoreSubscriptions.js.flow +19 -11
  128. package/store/RelayStoreTypes.js.flow +210 -44
  129. package/store/RelayStoreUtils.js.flow +25 -11
  130. package/store/ResolverCache.js.flow +249 -0
  131. package/store/ResolverFragments.js.flow +121 -0
  132. package/store/StoreInspector.js.flow +2 -2
  133. package/store/TypeID.js.flow +1 -1
  134. package/store/ViewerPattern.js.flow +2 -2
  135. package/store/cloneRelayHandleSourceField.js.flow +5 -6
  136. package/store/cloneRelayScalarHandleSourceField.js.flow +5 -6
  137. package/store/createFragmentSpecResolver.js.flow +3 -4
  138. package/store/createRelayContext.js.flow +3 -3
  139. package/store/defaultGetDataID.js.flow +3 -1
  140. package/store/normalizeRelayPayload.js.flow +6 -7
  141. package/store/readInlineData.js.flow +7 -8
  142. package/subscription/requestSubscription.js.flow +54 -27
  143. package/util/NormalizationNode.js.flow +16 -3
  144. package/util/ReaderNode.js.flow +38 -2
  145. package/util/RelayConcreteNode.js.flow +4 -0
  146. package/util/RelayFeatureFlags.js.flow +24 -8
  147. package/util/RelayProfiler.js.flow +22 -194
  148. package/util/RelayReplaySubject.js.flow +9 -9
  149. package/util/RelayRuntimeTypes.js.flow +73 -4
  150. package/util/StringInterner.js.flow +69 -0
  151. package/util/createPayloadFor3DField.js.flow +3 -3
  152. package/util/deepFreeze.js.flow +2 -1
  153. package/util/getFragmentIdentifier.js.flow +27 -15
  154. package/util/getOperation.js.flow +2 -2
  155. package/util/getPaginationMetadata.js.flow +72 -0
  156. package/util/getPaginationVariables.js.flow +108 -0
  157. package/util/getPendingOperationsForFragment.js.flow +62 -0
  158. package/util/getRefetchMetadata.js.flow +79 -0
  159. package/util/getRelayHandleKey.js.flow +1 -2
  160. package/util/getRequestIdentifier.js.flow +3 -3
  161. package/util/getValueAtPath.js.flow +46 -0
  162. package/util/isEmptyObject.js.flow +2 -1
  163. package/util/registerEnvironmentWithDevTools.js.flow +33 -0
  164. package/util/resolveImmediate.js.flow +1 -1
  165. package/util/withDuration.js.flow +32 -0
  166. package/lib/store/RelayRecordSourceMapImpl.js +0 -107
  167. package/lib/store/RelayStoreSubscriptionsUsingMapByID.js +0 -318
  168. package/store/RelayRecordSourceMapImpl.js.flow +0 -91
  169. package/store/RelayStoreSubscriptionsUsingMapByID.js.flow +0 -283
@@ -13,33 +13,22 @@
13
13
 
14
14
  'use strict';
15
15
 
16
- const RelayError = require('../util/RelayError');
17
- const RelayModernRecord = require('./RelayModernRecord');
18
- const RelayObservable = require('../network/RelayObservable');
19
- const RelayRecordSource = require('./RelayRecordSource');
20
- const RelayResponseNormalizer = require('./RelayResponseNormalizer');
21
-
22
- const getOperation = require('../util/getOperation');
23
- const invariant = require('invariant');
24
- const stableCopy = require('../util/stableCopy');
25
- const warning = require('warning');
26
-
27
- const {generateClientID} = require('./ClientID');
28
- const {createNormalizationSelector} = require('./RelayModernSelector');
29
- const {ROOT_TYPE, TYPENAME_KEY, getStorageKey} = require('./RelayStoreUtils');
30
-
16
+ import type {ActorIdentifier} from '../multi-actor-environment/ActorIdentifier';
31
17
  import type {
32
18
  GraphQLResponse,
33
- GraphQLSingularResponse,
34
19
  GraphQLResponseWithData,
20
+ GraphQLSingularResponse,
21
+ ReactFlightServerTree,
35
22
  } from '../network/RelayNetworkTypes';
36
23
  import type {Sink, Subscription} from '../network/RelayObservable';
37
24
  import type {
38
25
  DeferPlaceholder,
39
- RequestDescriptor,
26
+ FollowupPayload,
40
27
  HandleFieldPayload,
41
28
  IncrementalDataPlaceholder,
29
+ LogFunction,
42
30
  ModuleImportPayload,
31
+ MutationParameters,
43
32
  NormalizationSelector,
44
33
  OperationDescriptor,
45
34
  OperationLoader,
@@ -47,10 +36,12 @@ import type {
47
36
  OptimisticResponseConfig,
48
37
  OptimisticUpdate,
49
38
  PublishQueue,
39
+ ReactFlightClientResponse,
50
40
  ReactFlightPayloadDeserializer,
51
41
  ReactFlightServerErrorHandler,
52
42
  Record,
53
43
  RelayResponsePayload,
44
+ RequestDescriptor,
54
45
  SelectorStoreUpdater,
55
46
  Store,
56
47
  StreamPlaceholder,
@@ -62,27 +53,50 @@ import type {
62
53
  NormalizationSelectableNode,
63
54
  NormalizationSplitOperation,
64
55
  } from '../util/NormalizationNode';
65
- import type {DataID, Variables, Disposable} from '../util/RelayRuntimeTypes';
56
+ import type {DataID, Disposable, Variables} from '../util/RelayRuntimeTypes';
66
57
  import type {GetDataID} from './RelayResponseNormalizer';
67
58
  import type {NormalizationOptions} from './RelayResponseNormalizer';
68
59
 
69
- 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,
70
82
  +getDataID: GetDataID,
71
- +treatMissingFieldsAsNull: boolean,
83
+ +getPublishQueue: (actorIdentifier: ActorIdentifier) => PublishQueue,
84
+ +getStore: (actorIdentifier: ActorIdentifier) => Store,
85
+ +isClientPayload?: boolean,
72
86
  +operation: OperationDescriptor,
73
87
  +operationExecutions: Map<string, ActiveState>,
74
88
  +operationLoader: ?OperationLoader,
75
- +operationTracker?: ?OperationTracker,
76
- +optimisticConfig: ?OptimisticResponseConfig,
77
- +publishQueue: PublishQueue,
89
+ +operationTracker: OperationTracker,
90
+ +optimisticConfig: ?OptimisticResponseConfig<TMutation>,
78
91
  +reactFlightPayloadDeserializer?: ?ReactFlightPayloadDeserializer,
79
92
  +reactFlightServerErrorHandler?: ?ReactFlightServerErrorHandler,
80
93
  +scheduler?: ?TaskScheduler,
94
+ +shouldProcessClientComponents?: ?boolean,
81
95
  +sink: Sink<GraphQLResponse>,
82
96
  +source: RelayObservable<GraphQLResponse>,
83
- +store: Store,
84
- +updater?: ?SelectorStoreUpdater,
85
- +isClientPayload?: boolean,
97
+ +treatMissingFieldsAsNull: boolean,
98
+ +updater?: ?SelectorStoreUpdater<TMutation['response']>,
99
+ +log: LogFunction,
86
100
  |};
87
101
 
88
102
  export type ActiveState = 'active' | 'inactive';
@@ -110,7 +124,9 @@ type IncrementalGraphQLResponse = {|
110
124
  response: GraphQLResponseWithData,
111
125
  |};
112
126
 
113
- function execute(config: ExecuteConfig): Executor {
127
+ function execute<TMutation: MutationParameters>(
128
+ config: ExecuteConfig<TMutation>,
129
+ ): Executor<TMutation> {
114
130
  return new Executor(config);
115
131
  }
116
132
 
@@ -119,22 +135,26 @@ function execute(config: ExecuteConfig): Executor {
119
135
  * including optimistic payloads, standard payloads, resolution of match
120
136
  * dependencies, etc.
121
137
  */
122
- class Executor {
138
+ class Executor<TMutation: MutationParameters> {
139
+ _actorIdentifier: ActorIdentifier;
123
140
  _getDataID: GetDataID;
124
141
  _treatMissingFieldsAsNull: boolean;
125
142
  _incrementalPayloadsPending: boolean;
126
143
  _incrementalResults: Map<Label, Map<PathKey, IncrementalResults>>;
144
+ _log: LogFunction;
145
+ _executeId: number;
127
146
  _nextSubscriptionId: number;
128
147
  _operation: OperationDescriptor;
129
148
  _operationExecutions: Map<string, ActiveState>;
130
149
  _operationLoader: ?OperationLoader;
131
- _operationTracker: ?OperationTracker;
150
+ _operationTracker: OperationTracker;
132
151
  _operationUpdateEpochs: Map<string, number>;
133
- _optimisticUpdates: null | Array<OptimisticUpdate>;
152
+ _optimisticUpdates: null | Array<OptimisticUpdate<TMutation>>;
134
153
  _pendingModulePayloadsCount: number;
135
- _publishQueue: PublishQueue;
154
+ +_getPublishQueue: (actorIdentifier: ActorIdentifier) => PublishQueue;
136
155
  _reactFlightPayloadDeserializer: ?ReactFlightPayloadDeserializer;
137
156
  _reactFlightServerErrorHandler: ?ReactFlightServerErrorHandler;
157
+ _shouldProcessClientComponents: ?boolean;
138
158
  _scheduler: ?TaskScheduler;
139
159
  _sink: Sink<GraphQLResponse>;
140
160
  _source: Map<
@@ -142,34 +162,44 @@ class Executor {
142
162
  {|+record: Record, +fieldPayloads: Array<HandleFieldPayload>|},
143
163
  >;
144
164
  _state: 'started' | 'loading_incremental' | 'loading_final' | 'completed';
145
- _store: Store;
165
+ +_getStore: (actorIdentifier: ActorIdentifier) => Store;
146
166
  _subscriptions: Map<number, Subscription>;
147
- _updater: ?SelectorStoreUpdater;
148
- _retainDisposable: ?Disposable;
167
+ _updater: ?SelectorStoreUpdater<TMutation['response']>;
168
+ _asyncStoreUpdateDisposable: ?Disposable;
169
+ _completeFns: Array<() => void>;
170
+ +_retainDisposables: Map<ActorIdentifier, Disposable>;
149
171
  +_isClientPayload: boolean;
172
+ +_isSubscriptionOperation: boolean;
173
+ +_seenActors: Set<ActorIdentifier>;
150
174
 
151
175
  constructor({
176
+ actorIdentifier,
177
+ getDataID,
178
+ getPublishQueue,
179
+ getStore,
180
+ isClientPayload,
152
181
  operation,
153
182
  operationExecutions,
154
183
  operationLoader,
184
+ operationTracker,
155
185
  optimisticConfig,
156
- publishQueue,
186
+ reactFlightPayloadDeserializer,
187
+ reactFlightServerErrorHandler,
157
188
  scheduler,
189
+ shouldProcessClientComponents,
158
190
  sink,
159
191
  source,
160
- store,
161
- updater,
162
- operationTracker,
163
192
  treatMissingFieldsAsNull,
164
- getDataID,
165
- isClientPayload,
166
- reactFlightPayloadDeserializer,
167
- reactFlightServerErrorHandler,
168
- }: ExecuteConfig): void {
193
+ updater,
194
+ log,
195
+ }: ExecuteConfig<TMutation>): void {
196
+ this._actorIdentifier = actorIdentifier;
169
197
  this._getDataID = getDataID;
170
198
  this._treatMissingFieldsAsNull = treatMissingFieldsAsNull;
171
199
  this._incrementalPayloadsPending = false;
172
200
  this._incrementalResults = new Map();
201
+ this._log = log;
202
+ this._executeId = generateID();
173
203
  this._nextSubscriptionId = 0;
174
204
  this._operation = operation;
175
205
  this._operationExecutions = operationExecutions;
@@ -178,17 +208,23 @@ class Executor {
178
208
  this._operationUpdateEpochs = new Map();
179
209
  this._optimisticUpdates = null;
180
210
  this._pendingModulePayloadsCount = 0;
181
- this._publishQueue = publishQueue;
211
+ this._getPublishQueue = getPublishQueue;
182
212
  this._scheduler = scheduler;
183
213
  this._sink = sink;
184
214
  this._source = new Map();
185
215
  this._state = 'started';
186
- this._store = store;
216
+ this._getStore = getStore;
187
217
  this._subscriptions = new Map();
188
218
  this._updater = updater;
189
219
  this._isClientPayload = isClientPayload === true;
190
220
  this._reactFlightPayloadDeserializer = reactFlightPayloadDeserializer;
191
221
  this._reactFlightServerErrorHandler = reactFlightServerErrorHandler;
222
+ this._isSubscriptionOperation =
223
+ this._operation.request.node.params.operationKind === 'subscription';
224
+ this._shouldProcessClientComponents = shouldProcessClientComponents;
225
+ this._retainDisposables = new Map();
226
+ this._seenActors = new Set();
227
+ this._completeFns = [];
192
228
 
193
229
  const id = this._nextSubscriptionId++;
194
230
  source.subscribe({
@@ -201,7 +237,16 @@ class Executor {
201
237
  sink.error(error);
202
238
  }
203
239
  },
204
- 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
+ },
205
250
  });
206
251
 
207
252
  if (optimisticConfig != null) {
@@ -231,18 +276,41 @@ class Executor {
231
276
  if (optimisticUpdates !== null) {
232
277
  this._optimisticUpdates = null;
233
278
  optimisticUpdates.forEach(update =>
234
- this._publishQueue.revertUpdate(update),
279
+ this._getPublishQueueAndSaveActor().revertUpdate(update),
235
280
  );
236
- this._publishQueue.run();
281
+ // OK: run revert on cancel
282
+ this._runPublishQueue();
237
283
  }
238
284
  this._incrementalResults.clear();
239
- this._completeOperationTracker();
240
- if (this._retainDisposable) {
241
- this._retainDisposable.dispose();
242
- this._retainDisposable = null;
285
+ if (this._asyncStoreUpdateDisposable != null) {
286
+ this._asyncStoreUpdateDisposable.dispose();
287
+ this._asyncStoreUpdateDisposable = null;
243
288
  }
289
+ this._completeFns = [];
290
+ this._completeOperationTracker();
291
+ this._disposeRetainedData();
244
292
  }
245
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
+
246
314
  _updateActiveState(): void {
247
315
  let activeState;
248
316
  switch (this._state) {
@@ -265,7 +333,7 @@ class Executor {
265
333
  }
266
334
  default:
267
335
  (this._state: empty);
268
- invariant(false, 'RelayModernQueryExecutor: invalid executor state.');
336
+ invariant(false, 'OperationExecutor: invalid executor state.');
269
337
  }
270
338
  this._operationExecutions.set(
271
339
  this._operation.request.identifier,
@@ -302,12 +370,21 @@ class Executor {
302
370
  if (this._subscriptions.size === 0) {
303
371
  this.cancel();
304
372
  this._sink.complete();
373
+ this._log({
374
+ name: 'execute.complete',
375
+ executeId: this._executeId,
376
+ });
305
377
  }
306
378
  }
307
379
 
308
380
  _error(error: Error): void {
309
381
  this.cancel();
310
382
  this._sink.error(error);
383
+ this._log({
384
+ name: 'execute.error',
385
+ executeId: this._executeId,
386
+ error,
387
+ });
311
388
  }
312
389
 
313
390
  _start(id: number, subscription: Subscription): void {
@@ -318,8 +395,16 @@ class Executor {
318
395
  // Handle a raw GraphQL response.
319
396
  _next(_id: number, response: GraphQLResponse): void {
320
397
  this._schedule(() => {
321
- this._handleNext(response);
322
- 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
+ });
323
408
  });
324
409
  }
325
410
 
@@ -363,7 +448,8 @@ class Executor {
363
448
  error.stack;
364
449
  throw error;
365
450
  } else {
366
- const responseWithData: GraphQLResponseWithData = (response: $FlowFixMe);
451
+ const responseWithData: GraphQLResponseWithData =
452
+ (response: $FlowFixMe);
367
453
  results.push(responseWithData);
368
454
  }
369
455
  });
@@ -383,7 +469,10 @@ class Executor {
383
469
  responsePart => responsePart.extensions?.isOptimistic === true,
384
470
  )
385
471
  ) {
386
- invariant(false, 'Optimistic responses cannot be batched.');
472
+ invariant(
473
+ false,
474
+ 'OperationExecutor: Optimistic responses cannot be batched.',
475
+ );
387
476
  }
388
477
  return false;
389
478
  }
@@ -392,7 +481,7 @@ class Executor {
392
481
  if (isOptimistic && this._state !== 'started') {
393
482
  invariant(
394
483
  false,
395
- 'RelayModernQueryExecutor: optimistic payload received after server payload.',
484
+ 'OperationExecutor: optimistic payload received after server payload.',
396
485
  );
397
486
  }
398
487
  if (isOptimistic) {
@@ -411,6 +500,7 @@ class Executor {
411
500
  if (this._state === 'completed') {
412
501
  return;
413
502
  }
503
+ this._seenActors.clear();
414
504
 
415
505
  const responses = Array.isArray(response) ? response : [response];
416
506
  const responsesWithData = this._handleErrorResponse(responses);
@@ -434,10 +524,9 @@ class Executor {
434
524
  return;
435
525
  }
436
526
 
437
- const [
438
- nonIncrementalResponses,
439
- incrementalResponses,
440
- ] = partitionGraphQLResponses(responsesWithData);
527
+ const [nonIncrementalResponses, incrementalResponses] =
528
+ partitionGraphQLResponses(responsesWithData);
529
+ const hasNonIncrementalResponses = nonIncrementalResponses.length > 0;
441
530
 
442
531
  // In theory this doesn't preserve the ordering of the batch.
443
532
  // The idea is that a batch is always:
@@ -446,57 +535,98 @@ class Executor {
446
535
  // The non-incremental payload can appear if the server sends a batch
447
536
  // with the initial payload followed by some early-to-resolve incremental
448
537
  // payloads (although, can that even happen?)
449
- if (nonIncrementalResponses.length > 0) {
538
+ if (hasNonIncrementalResponses) {
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
+ };
558
+ }
559
+
450
560
  const payloadFollowups = this._processResponses(nonIncrementalResponses);
451
- // Please note that we're passing `this._operation` to the publish
452
- // queue here, which will later passed to the store (via notify)
453
- // to indicate that this is an operation that caused the store to update
454
- const updatedOwners = this._publishQueue.run(this._operation);
455
- this._updateOperationTracker(updatedOwners);
456
561
  this._processPayloadFollowups(payloadFollowups);
457
- if (this._incrementalPayloadsPending && !this._retainDisposable) {
458
- this._retainDisposable = this._store.retain(this._operation);
459
- }
460
562
  }
461
563
 
462
564
  if (incrementalResponses.length > 0) {
463
- const payloadFollowups = this._processIncrementalResponses(
464
- incrementalResponses,
465
- );
466
- // For the incremental case, we're only handling follow-up responses
467
- // for already initiated operation (and we're not passing it to
468
- // the run(...) call)
469
- const updatedOwners = this._publishQueue.run();
470
- this._updateOperationTracker(updatedOwners);
565
+ const payloadFollowups =
566
+ this._processIncrementalResponses(incrementalResponses);
567
+
471
568
  this._processPayloadFollowups(payloadFollowups);
472
569
  }
570
+ if (this._isSubscriptionOperation) {
571
+ // We attach the id to allow the `requestSubscription` to read from the store using
572
+ // the current id in its `onNext` callback
573
+ if (responsesWithData[0].extensions == null) {
574
+ // $FlowFixMe[cannot-write]
575
+ responsesWithData[0].extensions = {
576
+ __relay_subscription_root_id: this._operation.fragment.dataID,
577
+ };
578
+ } else {
579
+ responsesWithData[0].extensions.__relay_subscription_root_id =
580
+ this._operation.fragment.dataID;
581
+ }
582
+ }
583
+
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();
595
+ }
596
+ }
597
+ this._updateOperationTracker(updatedOwners);
473
598
  this._sink.next(response);
474
599
  }
475
600
 
476
601
  _processOptimisticResponse(
477
602
  response: ?GraphQLResponseWithData,
478
- updater: ?SelectorStoreUpdater,
603
+ updater: ?SelectorStoreUpdater<TMutation['response']>,
479
604
  treatMissingFieldsAsNull: boolean,
480
605
  ): void {
481
606
  invariant(
482
607
  this._optimisticUpdates === null,
483
- 'environment.execute: only support one optimistic response per ' +
608
+ 'OperationExecutor: environment.execute: only support one optimistic response per ' +
484
609
  'execute.',
485
610
  );
486
611
  if (response == null && updater == null) {
487
612
  return;
488
613
  }
489
- const optimisticUpdates: Array<OptimisticUpdate> = [];
614
+ const optimisticUpdates: Array<OptimisticUpdate<TMutation>> = [];
490
615
  if (response) {
491
616
  const payload = normalizeResponse(
492
617
  response,
493
618
  this._operation.root,
494
619
  ROOT_TYPE,
495
620
  {
621
+ actorIdentifier: this._actorIdentifier,
496
622
  getDataID: this._getDataID,
497
623
  path: [],
498
- reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
624
+ reactFlightPayloadDeserializer:
625
+ this._reactFlightPayloadDeserializer != null
626
+ ? this._deserializeReactFlightPayloadWithLogging
627
+ : null,
499
628
  reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
629
+ shouldProcessClientComponents: this._shouldProcessClientComponents,
500
630
  treatMissingFieldsAsNull,
501
631
  },
502
632
  );
@@ -514,7 +644,7 @@ class Executor {
514
644
  errors: null,
515
645
  fieldPayloads: null,
516
646
  incrementalPlaceholders: null,
517
- moduleImportPayloads: null,
647
+ followupPayloads: null,
518
648
  source: RelayRecordSource.create(),
519
649
  isFinal: false,
520
650
  },
@@ -522,61 +652,93 @@ class Executor {
522
652
  });
523
653
  }
524
654
  this._optimisticUpdates = optimisticUpdates;
525
- optimisticUpdates.forEach(update => this._publishQueue.applyUpdate(update));
526
- this._publishQueue.run();
655
+ optimisticUpdates.forEach(update =>
656
+ this._getPublishQueueAndSaveActor().applyUpdate(update),
657
+ );
658
+ // OK: only called on construction and when receiving an optimistic payload from network,
659
+ // which doesn't fall-through to the regular next() handling
660
+ this._runPublishQueue();
527
661
  }
528
662
 
529
663
  _processOptimisticFollowups(
530
664
  payload: RelayResponsePayload,
531
- optimisticUpdates: Array<OptimisticUpdate>,
665
+ optimisticUpdates: Array<OptimisticUpdate<TMutation>>,
532
666
  ): void {
533
- if (payload.moduleImportPayloads && payload.moduleImportPayloads.length) {
534
- const moduleImportPayloads = payload.moduleImportPayloads;
535
- const operationLoader = this._operationLoader;
536
- invariant(
537
- operationLoader,
538
- 'RelayModernEnvironment: Expected an operationLoader to be ' +
539
- 'configured when using `@match`.',
540
- );
541
- for (const moduleImportPayload of moduleImportPayloads) {
542
- const operation = operationLoader.get(
543
- moduleImportPayload.operationReference,
544
- );
545
- if (operation == null) {
546
- this._processAsyncOptimisticModuleImport(
547
- operationLoader,
548
- moduleImportPayload,
549
- );
550
- } else {
551
- const moduleImportOptimisticUpdates = this._processOptimisticModuleImport(
552
- operation,
553
- moduleImportPayload,
554
- );
555
- 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
+ );
556
697
  }
557
698
  }
558
699
  }
559
700
  }
560
701
 
561
- _normalizeModuleImport(
562
- moduleImportPayload: ModuleImportPayload,
563
- operation: NormalizationSelectableNode,
702
+ /**
703
+ * Normalize Data for @module payload, and actor-specific payload
704
+ */
705
+ _normalizeFollowupPayload(
706
+ followupPayload: FollowupPayload,
707
+ normalizationNode: NormalizationSelectableNode,
564
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
+ }
565
722
  const selector = createNormalizationSelector(
566
- operation,
567
- moduleImportPayload.dataID,
568
- moduleImportPayload.variables,
723
+ normalizationNode,
724
+ followupPayload.dataID,
725
+ variables,
569
726
  );
570
727
  return normalizeResponse(
571
- {data: moduleImportPayload.data},
728
+ {data: followupPayload.data},
572
729
  selector,
573
- moduleImportPayload.typeName,
730
+ followupPayload.typeName,
574
731
  {
732
+ actorIdentifier: this._actorIdentifier,
575
733
  getDataID: this._getDataID,
576
- path: moduleImportPayload.path,
577
- reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
734
+ path: followupPayload.path,
735
+ reactFlightPayloadDeserializer:
736
+ this._reactFlightPayloadDeserializer != null
737
+ ? this._deserializeReactFlightPayloadWithLogging
738
+ : null,
578
739
  reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
579
740
  treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
741
+ shouldProcessClientComponents: this._shouldProcessClientComponents,
580
742
  },
581
743
  );
582
744
  }
@@ -584,10 +746,10 @@ class Executor {
584
746
  _processOptimisticModuleImport(
585
747
  normalizationRootNode: NormalizationRootNode,
586
748
  moduleImportPayload: ModuleImportPayload,
587
- ): $ReadOnlyArray<OptimisticUpdate> {
749
+ ): $ReadOnlyArray<OptimisticUpdate<TMutation>> {
588
750
  const operation = getOperation(normalizationRootNode);
589
751
  const optimisticUpdates = [];
590
- const modulePayload = this._normalizeModuleImport(
752
+ const modulePayload = this._normalizeFollowupPayload(
591
753
  moduleImportPayload,
592
754
  operation,
593
755
  );
@@ -602,41 +764,41 @@ class Executor {
602
764
  }
603
765
 
604
766
  _processAsyncOptimisticModuleImport(
605
- operationLoader: OperationLoader,
606
767
  moduleImportPayload: ModuleImportPayload,
607
768
  ): void {
608
- operationLoader
769
+ this._expectOperationLoader()
609
770
  .load(moduleImportPayload.operationReference)
610
771
  .then(operation => {
611
772
  if (operation == null || this._state !== 'started') {
612
773
  return;
613
774
  }
614
- const moduleImportOptimisticUpdates = this._processOptimisticModuleImport(
615
- operation,
616
- moduleImportPayload,
617
- );
775
+ const moduleImportOptimisticUpdates =
776
+ this._processOptimisticModuleImport(operation, moduleImportPayload);
618
777
  moduleImportOptimisticUpdates.forEach(update =>
619
- this._publishQueue.applyUpdate(update),
778
+ this._getPublishQueueAndSaveActor().applyUpdate(update),
620
779
  );
621
780
  if (this._optimisticUpdates == null) {
622
781
  warning(
623
782
  false,
624
- 'RelayModernQueryExecutor: Unexpected ModuleImport optimistic ' +
783
+ 'OperationExecutor: Unexpected ModuleImport optimistic ' +
625
784
  'update in operation %s.' +
626
785
  this._operation.request.node.params.name,
627
786
  );
628
787
  } else {
629
788
  this._optimisticUpdates.push(...moduleImportOptimisticUpdates);
630
- this._publishQueue.run();
789
+ // OK: always have to run() after an module import resolves async
790
+ this._runPublishQueue();
631
791
  }
632
792
  });
633
793
  }
634
794
 
635
- _processResponses(responses: $ReadOnlyArray<GraphQLResponseWithData>) {
795
+ _processResponses(
796
+ responses: $ReadOnlyArray<GraphQLResponseWithData>,
797
+ ): $ReadOnlyArray<RelayResponsePayload> {
636
798
  if (this._optimisticUpdates !== null) {
637
- this._optimisticUpdates.forEach(update =>
638
- this._publishQueue.revertUpdate(update),
639
- );
799
+ this._optimisticUpdates.forEach(update => {
800
+ this._getPublishQueueAndSaveActor().revertUpdate(update);
801
+ });
640
802
  this._optimisticUpdates = null;
641
803
  }
642
804
 
@@ -649,18 +811,24 @@ class Executor {
649
811
  this._operation.root,
650
812
  ROOT_TYPE,
651
813
  {
814
+ actorIdentifier: this._actorIdentifier,
652
815
  getDataID: this._getDataID,
653
816
  path: [],
654
- reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
817
+ reactFlightPayloadDeserializer:
818
+ this._reactFlightPayloadDeserializer != null
819
+ ? this._deserializeReactFlightPayloadWithLogging
820
+ : null,
655
821
  reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
656
822
  treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
823
+ shouldProcessClientComponents: this._shouldProcessClientComponents,
657
824
  },
658
825
  );
659
- this._publishQueue.commitPayload(
826
+ this._getPublishQueueAndSaveActor().commitPayload(
660
827
  this._operation,
661
828
  relayPayload,
662
829
  this._updater,
663
830
  );
831
+
664
832
  return relayPayload;
665
833
  });
666
834
  }
@@ -676,30 +844,30 @@ class Executor {
676
844
  return;
677
845
  }
678
846
  payloads.forEach(payload => {
679
- const {incrementalPlaceholders, moduleImportPayloads, isFinal} = payload;
847
+ const {incrementalPlaceholders, followupPayloads, isFinal} = payload;
680
848
  this._state = isFinal ? 'loading_final' : 'loading_incremental';
681
849
  this._updateActiveState();
682
850
  if (isFinal) {
683
851
  this._incrementalPayloadsPending = false;
684
852
  }
685
- if (moduleImportPayloads && moduleImportPayloads.length !== 0) {
686
- const operationLoader = this._operationLoader;
687
- invariant(
688
- operationLoader,
689
- 'RelayModernEnvironment: Expected an operationLoader to be ' +
690
- 'configured when using `@match`.',
691
- );
692
- moduleImportPayloads.forEach(moduleImportPayload => {
693
- this._processModuleImportPayload(
694
- moduleImportPayload,
695
- operationLoader,
696
- );
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;
697
860
  });
698
861
  }
862
+
699
863
  if (incrementalPlaceholders && incrementalPlaceholders.length !== 0) {
700
864
  this._incrementalPayloadsPending = this._state !== 'loading_final';
701
865
  incrementalPlaceholders.forEach(incrementalPlaceholder => {
866
+ const prevActorIdentifier = this._actorIdentifier;
867
+ this._actorIdentifier =
868
+ incrementalPlaceholder.actorIdentifier ?? this._actorIdentifier;
702
869
  this._processIncrementalPlaceholder(payload, incrementalPlaceholder);
870
+ this._actorIdentifier = prevActorIdentifier;
703
871
  });
704
872
 
705
873
  if (this._isClientPayload || this._state === 'loading_final') {
@@ -731,8 +899,6 @@ class Executor {
731
899
  }
732
900
  });
733
901
  if (relayPayloads.length > 0) {
734
- const updatedOwners = this._publishQueue.run();
735
- this._updateOperationTracker(updatedOwners);
736
902
  this._processPayloadFollowups(relayPayloads);
737
903
  }
738
904
  }
@@ -741,9 +907,7 @@ class Executor {
741
907
  }
742
908
 
743
909
  _maybeCompleteSubscriptionOperationTracking() {
744
- const isSubscriptionOperation =
745
- this._operation.request.node.params.operationKind === 'subscription';
746
- if (!isSubscriptionOperation) {
910
+ if (!this._isSubscriptionOperation) {
747
911
  return;
748
912
  }
749
913
  if (
@@ -761,73 +925,154 @@ class Executor {
761
925
  * defer, stream, etc); these are handled by calling
762
926
  * `_processPayloadFollowups()`.
763
927
  */
764
- _processModuleImportPayload(
765
- moduleImportPayload: ModuleImportPayload,
766
- operationLoader: OperationLoader,
767
- ): void {
768
- const node = operationLoader.get(moduleImportPayload.operationReference);
769
- if (node != null) {
770
- const operation = getOperation(node);
771
- // If the operation module is available synchronously, normalize the
772
- // data synchronously.
773
- this._handleModuleImportPayload(moduleImportPayload, operation);
774
- this._maybeCompleteSubscriptionOperationTracking();
775
- } else {
776
- // Otherwise load the operation module and schedule a task to normalize
777
- // the data when the module is available.
778
- const id = this._nextSubscriptionId++;
779
- this._pendingModulePayloadsCount++;
780
-
781
- const decrementPendingCount = () => {
782
- this._pendingModulePayloadsCount--;
783
- this._maybeCompleteSubscriptionOperationTracking();
784
- };
785
-
786
- // Observable.from(operationLoader.load()) wouldn't catch synchronous
787
- // errors thrown by the load function, which is user-defined. Guard
788
- // against that with Observable.from(new Promise(<work>)).
789
- RelayObservable.from(
790
- new Promise((resolve, reject) => {
791
- operationLoader
792
- .load(moduleImportPayload.operationReference)
793
- .then(resolve, reject);
794
- }),
795
- )
796
- .map((operation: ?NormalizationRootNode) => {
797
- if (operation != null) {
798
- this._schedule(() => {
799
- this._handleModuleImportPayload(
800
- moduleImportPayload,
801
- getOperation(operation),
802
- );
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,
803
1014
  });
804
- }
805
- })
806
- .subscribe({
807
- complete: () => {
808
- this._complete(id);
809
- decrementPendingCount();
810
- },
811
- error: error => {
812
- this._error(error);
813
- decrementPendingCount();
814
- },
815
- start: subscription => this._start(id, subscription),
816
- });
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
+ );
817
1047
  }
818
1048
  }
819
1049
 
820
- _handleModuleImportPayload(
821
- moduleImportPayload: ModuleImportPayload,
822
- 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,
823
1067
  ): void {
824
- const relayPayload = this._normalizeModuleImport(
825
- moduleImportPayload,
826
- operation,
1068
+ const relayPayload = this._normalizeFollowupPayload(
1069
+ followupPayload,
1070
+ normalizationNode,
1071
+ );
1072
+ this._getPublishQueueAndSaveActor().commitPayload(
1073
+ this._operation,
1074
+ relayPayload,
827
1075
  );
828
- this._publishQueue.commitPayload(this._operation, relayPayload);
829
- const updatedOwners = this._publishQueue.run();
830
- this._updateOperationTracker(updatedOwners);
831
1076
  this._processPayloadFollowups([relayPayload]);
832
1077
  }
833
1078
 
@@ -873,7 +1118,7 @@ class Executor {
873
1118
  (placeholder: empty);
874
1119
  invariant(
875
1120
  false,
876
- 'Unsupported incremental placeholder kind `%s`.',
1121
+ 'OperationExecutor: Unsupported incremental placeholder kind `%s`.',
877
1122
  placeholder.kind,
878
1123
  );
879
1124
  }
@@ -897,7 +1142,7 @@ class Executor {
897
1142
  // exist.
898
1143
  invariant(
899
1144
  parentRecord != null,
900
- 'RelayModernEnvironment: Expected record `%s` to exist.',
1145
+ 'OperationExecutor: Expected record `%s` to exist.',
901
1146
  parentID,
902
1147
  );
903
1148
  let nextParentRecord;
@@ -926,14 +1171,12 @@ class Executor {
926
1171
  record: nextParentRecord,
927
1172
  fieldPayloads: nextParentPayloads,
928
1173
  });
1174
+
929
1175
  // If there were any queued responses, process them now that placeholders
930
1176
  // are in place
931
1177
  if (pendingResponses != null) {
932
- const payloadFollowups = this._processIncrementalResponses(
933
- pendingResponses,
934
- );
935
- const updatedOwners = this._publishQueue.run();
936
- this._updateOperationTracker(updatedOwners);
1178
+ const payloadFollowups =
1179
+ this._processIncrementalResponses(pendingResponses);
937
1180
  this._processPayloadFollowups(payloadFollowups);
938
1181
  }
939
1182
  }
@@ -969,7 +1212,7 @@ class Executor {
969
1212
  const placeholder = resultForPath.placeholder;
970
1213
  invariant(
971
1214
  placeholder.kind === 'defer',
972
- 'RelayModernEnvironment: Expected data for path `%s` for label `%s` ' +
1215
+ 'OperationExecutor: Expected data for path `%s` for label `%s` ' +
973
1216
  'to be data for @defer, was `@%s`.',
974
1217
  pathKey,
975
1218
  label,
@@ -983,10 +1226,7 @@ class Executor {
983
1226
  // but Relay records paths relative to the parent of the stream node:
984
1227
  // therefore we strip the last two elements just to lookup the path
985
1228
  // (the item index is used later to insert the element in the list)
986
- const pathKey = path
987
- .slice(0, -2)
988
- .map(String)
989
- .join('.');
1229
+ const pathKey = path.slice(0, -2).map(String).join('.');
990
1230
  let resultForPath = resultForLabel.get(pathKey);
991
1231
  if (resultForPath == null) {
992
1232
  resultForPath = {kind: 'response', responses: [incrementalResponse]};
@@ -999,7 +1239,7 @@ class Executor {
999
1239
  const placeholder = resultForPath.placeholder;
1000
1240
  invariant(
1001
1241
  placeholder.kind === 'stream',
1002
- 'RelayModernEnvironment: Expected data for path `%s` for label `%s` ' +
1242
+ 'OperationExecutor: Expected data for path `%s` for label `%s` ' +
1003
1243
  'to be data for @stream, was `@%s`.',
1004
1244
  pathKey,
1005
1245
  label,
@@ -1020,26 +1260,37 @@ class Executor {
1020
1260
  response: GraphQLResponseWithData,
1021
1261
  ): RelayResponsePayload {
1022
1262
  const {dataID: parentID} = placeholder.selector;
1263
+ const prevActorIdentifier = this._actorIdentifier;
1264
+ this._actorIdentifier =
1265
+ placeholder.actorIdentifier ?? this._actorIdentifier;
1023
1266
  const relayPayload = normalizeResponse(
1024
1267
  response,
1025
1268
  placeholder.selector,
1026
1269
  placeholder.typeName,
1027
1270
  {
1271
+ actorIdentifier: this._actorIdentifier,
1028
1272
  getDataID: this._getDataID,
1029
1273
  path: placeholder.path,
1030
- reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
1274
+ reactFlightPayloadDeserializer:
1275
+ this._reactFlightPayloadDeserializer != null
1276
+ ? this._deserializeReactFlightPayloadWithLogging
1277
+ : null,
1031
1278
  reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
1032
1279
  treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
1280
+ shouldProcessClientComponents: this._shouldProcessClientComponents,
1033
1281
  },
1034
1282
  );
1035
- this._publishQueue.commitPayload(this._operation, relayPayload);
1283
+ this._getPublishQueueAndSaveActor().commitPayload(
1284
+ this._operation,
1285
+ relayPayload,
1286
+ );
1036
1287
 
1037
1288
  // Load the version of the parent record from which this incremental data
1038
1289
  // was derived
1039
1290
  const parentEntry = this._source.get(parentID);
1040
1291
  invariant(
1041
1292
  parentEntry != null,
1042
- 'RelayModernEnvironment: Expected the parent record `%s` for @defer ' +
1293
+ 'OperationExecutor: Expected the parent record `%s` for @defer ' +
1043
1294
  'data to exist.',
1044
1295
  parentID,
1045
1296
  );
@@ -1049,15 +1300,17 @@ class Executor {
1049
1300
  errors: null,
1050
1301
  fieldPayloads,
1051
1302
  incrementalPlaceholders: null,
1052
- moduleImportPayloads: null,
1303
+ followupPayloads: null,
1053
1304
  source: RelayRecordSource.create(),
1054
1305
  isFinal: response.extensions?.is_final === true,
1055
1306
  };
1056
- this._publishQueue.commitPayload(
1307
+ this._getPublishQueueAndSaveActor().commitPayload(
1057
1308
  this._operation,
1058
1309
  handleFieldsRelayPayload,
1059
1310
  );
1060
1311
  }
1312
+
1313
+ this._actorIdentifier = prevActorIdentifier;
1061
1314
  return relayPayload;
1062
1315
  }
1063
1316
 
@@ -1070,12 +1323,14 @@ class Executor {
1070
1323
  placeholder: StreamPlaceholder,
1071
1324
  response: GraphQLResponseWithData,
1072
1325
  ): RelayResponsePayload {
1073
- const {parentID, node, variables} = placeholder;
1326
+ const {parentID, node, variables, actorIdentifier} = placeholder;
1327
+ const prevActorIdentifier = this._actorIdentifier;
1328
+ this._actorIdentifier = actorIdentifier ?? this._actorIdentifier;
1074
1329
  // Find the LinkedField where @stream was applied
1075
1330
  const field = node.selections[0];
1076
1331
  invariant(
1077
1332
  field != null && field.kind === 'LinkedField' && field.plural === true,
1078
- 'RelayModernEnvironment: Expected @stream to be used on a plural field.',
1333
+ 'OperationExecutor: Expected @stream to be used on a plural field.',
1079
1334
  );
1080
1335
  const {
1081
1336
  fieldPayloads,
@@ -1095,34 +1350,38 @@ class Executor {
1095
1350
  // Publish the new item and update the parent record to set
1096
1351
  // field[index] = item *if* the parent record hasn't been concurrently
1097
1352
  // modified.
1098
- this._publishQueue.commitPayload(this._operation, relayPayload, store => {
1099
- const currentParentRecord = store.get(parentID);
1100
- if (currentParentRecord == null) {
1101
- // parent has since been deleted, stream data is stale
1102
- return;
1103
- }
1104
- const currentItems = currentParentRecord.getLinkedRecords(storageKey);
1105
- if (currentItems == null) {
1106
- // field has since been deleted, stream data is stale
1107
- return;
1108
- }
1109
- if (
1110
- currentItems.length !== prevIDs.length ||
1111
- currentItems.some(
1112
- (currentItem, index) =>
1113
- prevIDs[index] !== (currentItem && currentItem.getDataID()),
1114
- )
1115
- ) {
1116
- // field has been modified by something other than this query,
1117
- // stream data is stale
1118
- return;
1119
- }
1120
- // parent.field has not been concurrently modified:
1121
- // update `parent.field[index] = item`
1122
- const nextItems = [...currentItems];
1123
- nextItems[itemIndex] = store.get(itemID);
1124
- currentParentRecord.setLinkedRecords(nextItems, storageKey);
1125
- });
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
+ );
1126
1385
 
1127
1386
  // Now that the parent record has been updated to include the new item,
1128
1387
  // also update any handle fields that are derived from the parent record.
@@ -1131,15 +1390,17 @@ class Executor {
1131
1390
  errors: null,
1132
1391
  fieldPayloads,
1133
1392
  incrementalPlaceholders: null,
1134
- moduleImportPayloads: null,
1393
+ followupPayloads: null,
1135
1394
  source: RelayRecordSource.create(),
1136
1395
  isFinal: false,
1137
1396
  };
1138
- this._publishQueue.commitPayload(
1397
+ this._getPublishQueueAndSaveActor().commitPayload(
1139
1398
  this._operation,
1140
1399
  handleFieldsRelayPayload,
1141
1400
  );
1142
1401
  }
1402
+
1403
+ this._actorIdentifier = prevActorIdentifier;
1143
1404
  return relayPayload;
1144
1405
  }
1145
1406
 
@@ -1161,7 +1422,7 @@ class Executor {
1161
1422
  const {data} = response;
1162
1423
  invariant(
1163
1424
  typeof data === 'object',
1164
- 'RelayModernEnvironment: Expected the GraphQL @stream payload `data` ' +
1425
+ 'OperationExecutor: Expected the GraphQL @stream payload `data` ' +
1165
1426
  'value to be an object.',
1166
1427
  );
1167
1428
  const responseKey = field.alias ?? field.name;
@@ -1172,7 +1433,7 @@ class Executor {
1172
1433
  const parentEntry = this._source.get(parentID);
1173
1434
  invariant(
1174
1435
  parentEntry != null,
1175
- 'RelayModernEnvironment: Expected the parent record `%s` for @stream ' +
1436
+ 'OperationExecutor: Expected the parent record `%s` for @stream ' +
1176
1437
  'data to exist.',
1177
1438
  parentID,
1178
1439
  );
@@ -1187,7 +1448,7 @@ class Executor {
1187
1448
  );
1188
1449
  invariant(
1189
1450
  prevIDs != null,
1190
- 'RelayModernEnvironment: Expected record `%s` to have fetched field ' +
1451
+ 'OperationExecutor: Expected record `%s` to have fetched field ' +
1191
1452
  '`%s` with @stream.',
1192
1453
  parentID,
1193
1454
  field.name,
@@ -1198,7 +1459,7 @@ class Executor {
1198
1459
  const itemIndex = parseInt(finalPathEntry, 10);
1199
1460
  invariant(
1200
1461
  itemIndex === finalPathEntry && itemIndex >= 0,
1201
- 'RelayModernEnvironment: Expected path for @stream to end in a ' +
1462
+ 'OperationExecutor: Expected path for @stream to end in a ' +
1202
1463
  'positive integer index, got `%s`',
1203
1464
  finalPathEntry,
1204
1465
  );
@@ -1206,7 +1467,7 @@ class Executor {
1206
1467
  const typeName = field.concreteType ?? data[TYPENAME_KEY];
1207
1468
  invariant(
1208
1469
  typeof typeName === 'string',
1209
- 'RelayModernEnvironment: Expected @stream field `%s` to have a ' +
1470
+ 'OperationExecutor: Expected @stream field `%s` to have a ' +
1210
1471
  '__typename.',
1211
1472
  field.name,
1212
1473
  );
@@ -1221,7 +1482,7 @@ class Executor {
1221
1482
  generateClientID(parentID, storageKey, itemIndex);
1222
1483
  invariant(
1223
1484
  typeof itemID === 'string',
1224
- 'RelayModernEnvironment: Expected id of elements of field `%s` to ' +
1485
+ 'OperationExecutor: Expected id of elements of field `%s` to ' +
1225
1486
  'be strings.',
1226
1487
  storageKey,
1227
1488
  );
@@ -1241,11 +1502,16 @@ class Executor {
1241
1502
  fieldPayloads,
1242
1503
  });
1243
1504
  const relayPayload = normalizeResponse(response, selector, typeName, {
1505
+ actorIdentifier: this._actorIdentifier,
1244
1506
  getDataID: this._getDataID,
1245
1507
  path: [...normalizationPath, responseKey, String(itemIndex)],
1246
- reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
1508
+ reactFlightPayloadDeserializer:
1509
+ this._reactFlightPayloadDeserializer != null
1510
+ ? this._deserializeReactFlightPayloadWithLogging
1511
+ : null,
1247
1512
  reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
1248
1513
  treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
1514
+ shouldProcessClientComponents: this._shouldProcessClientComponents,
1249
1515
  });
1250
1516
  return {
1251
1517
  fieldPayloads,
@@ -1257,14 +1523,29 @@ class Executor {
1257
1523
  };
1258
1524
  }
1259
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
+
1260
1545
  _updateOperationTracker(
1261
1546
  updatedOwners: ?$ReadOnlyArray<RequestDescriptor>,
1262
1547
  ): void {
1263
- if (
1264
- this._operationTracker != null &&
1265
- updatedOwners != null &&
1266
- updatedOwners.length > 0
1267
- ) {
1548
+ if (updatedOwners != null && updatedOwners.length > 0) {
1268
1549
  this._operationTracker.update(
1269
1550
  this._operation.request,
1270
1551
  new Set(updatedOwners),
@@ -1273,9 +1554,59 @@ class Executor {
1273
1554
  }
1274
1555
 
1275
1556
  _completeOperationTracker() {
1276
- if (this._operationTracker != null) {
1277
- this._operationTracker.complete(this._operation.request);
1557
+ this._operationTracker.complete(this._operation.request);
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));
1278
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;
1279
1610
  }
1280
1611
  }
1281
1612
 
@@ -1293,7 +1624,7 @@ function partitionGraphQLResponses(
1293
1624
  if (label == null || path == null) {
1294
1625
  invariant(
1295
1626
  false,
1296
- 'RelayModernQueryExecutor: invalid incremental payload, expected ' +
1627
+ 'OperationExecutor: invalid incremental payload, expected ' +
1297
1628
  '`path` and `label` to either both be null/undefined, or ' +
1298
1629
  '`path` to be an `Array<string | number>` and `label` to be a ' +
1299
1630
  '`string`.',
@@ -1345,11 +1676,13 @@ function validateOptimisticResponsePayload(
1345
1676
  if (incrementalPlaceholders != null && incrementalPlaceholders.length !== 0) {
1346
1677
  invariant(
1347
1678
  false,
1348
- 'RelayModernQueryExecutor: optimistic responses cannot be returned ' +
1679
+ 'OperationExecutor: optimistic responses cannot be returned ' +
1349
1680
  'for operations that use incremental data delivery (@defer, ' +
1350
1681
  '@stream, and @stream_connection).',
1351
1682
  );
1352
1683
  }
1353
1684
  }
1354
1685
 
1355
- module.exports = {execute};
1686
+ module.exports = {
1687
+ execute,
1688
+ };