relay-runtime 11.0.1 → 13.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/handlers/RelayDefaultHandlerProvider.js.flow +2 -2
  2. package/handlers/connection/ConnectionHandler.js.flow +8 -17
  3. package/handlers/connection/MutationHandlers.js.flow +7 -11
  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 +13 -19
  8. package/lib/handlers/connection/MutationHandlers.js +4 -7
  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 +10 -5
  25. package/lib/network/ConvertToExecuteFunction.js +2 -1
  26. package/lib/network/RelayNetwork.js +3 -2
  27. package/lib/network/RelayQueryResponseCache.js +21 -5
  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 +123 -54
  35. package/lib/store/{RelayModernQueryExecutor.js → OperationExecutor.js} +518 -200
  36. package/lib/store/RelayConcreteVariables.js +26 -8
  37. package/lib/store/RelayExperimentalGraphResponseHandler.js +153 -0
  38. package/lib/store/RelayExperimentalGraphResponseTransform.js +391 -0
  39. package/lib/store/RelayModernEnvironment.js +175 -240
  40. package/lib/store/RelayModernFragmentSpecResolver.js +52 -26
  41. package/lib/store/RelayModernOperationDescriptor.js +2 -1
  42. package/lib/store/RelayModernRecord.js +47 -12
  43. package/lib/store/RelayModernSelector.js +14 -8
  44. package/lib/store/RelayModernStore.js +56 -28
  45. package/lib/store/RelayOperationTracker.js +34 -24
  46. package/lib/store/RelayPublishQueue.js +41 -13
  47. package/lib/store/RelayReader.js +288 -48
  48. package/lib/store/RelayRecordSource.js +87 -3
  49. package/lib/store/RelayReferenceMarker.js +34 -22
  50. package/lib/store/RelayResponseNormalizer.js +211 -110
  51. package/lib/store/RelayStoreReactFlightUtils.js +4 -10
  52. package/lib/store/RelayStoreSubscriptions.js +14 -9
  53. package/lib/store/RelayStoreUtils.js +12 -7
  54. package/lib/store/ResolverCache.js +213 -0
  55. package/lib/store/ResolverFragments.js +61 -0
  56. package/lib/store/cloneRelayHandleSourceField.js +5 -4
  57. package/lib/store/cloneRelayScalarHandleSourceField.js +5 -4
  58. package/lib/store/createRelayContext.js +4 -2
  59. package/lib/store/readInlineData.js +6 -2
  60. package/lib/subscription/requestSubscription.js +34 -25
  61. package/lib/util/RelayConcreteNode.js +3 -0
  62. package/lib/util/RelayFeatureFlags.js +10 -4
  63. package/lib/util/RelayProfiler.js +17 -187
  64. package/lib/util/RelayReplaySubject.js +22 -7
  65. package/lib/util/RelayRuntimeTypes.js +0 -6
  66. package/lib/util/StringInterner.js +71 -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 +26 -16
  95. package/network/ConvertToExecuteFunction.js.flow +2 -2
  96. package/network/RelayNetwork.js.flow +4 -5
  97. package/network/RelayNetworkTypes.js.flow +5 -4
  98. package/network/RelayObservable.js.flow +1 -1
  99. package/network/RelayQueryResponseCache.js.flow +34 -21
  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 +141 -59
  111. package/store/{RelayModernQueryExecutor.js.flow → OperationExecutor.js.flow} +605 -303
  112. package/store/RelayConcreteVariables.js.flow +27 -8
  113. package/store/RelayExperimentalGraphResponseHandler.js.flow +124 -0
  114. package/store/RelayExperimentalGraphResponseTransform.js.flow +475 -0
  115. package/store/RelayModernEnvironment.js.flow +173 -240
  116. package/store/RelayModernFragmentSpecResolver.js.flow +55 -31
  117. package/store/RelayModernOperationDescriptor.js.flow +12 -7
  118. package/store/RelayModernRecord.js.flow +67 -11
  119. package/store/RelayModernSelector.js.flow +24 -14
  120. package/store/RelayModernStore.js.flow +66 -36
  121. package/store/RelayOperationTracker.js.flow +59 -43
  122. package/store/RelayOptimisticRecordSource.js.flow +2 -2
  123. package/store/RelayPublishQueue.js.flow +79 -34
  124. package/store/RelayReader.js.flow +351 -73
  125. package/store/RelayRecordSource.js.flow +72 -6
  126. package/store/RelayReferenceMarker.js.flow +40 -26
  127. package/store/RelayResponseNormalizer.js.flow +258 -99
  128. package/store/RelayStoreReactFlightUtils.js.flow +4 -11
  129. package/store/RelayStoreSubscriptions.js.flow +19 -11
  130. package/store/RelayStoreTypes.js.flow +209 -43
  131. package/store/RelayStoreUtils.js.flow +24 -11
  132. package/store/ResolverCache.js.flow +249 -0
  133. package/store/ResolverFragments.js.flow +121 -0
  134. package/store/StoreInspector.js.flow +2 -2
  135. package/store/TypeID.js.flow +1 -1
  136. package/store/ViewerPattern.js.flow +2 -2
  137. package/store/cloneRelayHandleSourceField.js.flow +5 -6
  138. package/store/cloneRelayScalarHandleSourceField.js.flow +5 -6
  139. package/store/createFragmentSpecResolver.js.flow +3 -4
  140. package/store/createRelayContext.js.flow +3 -3
  141. package/store/normalizeRelayPayload.js.flow +6 -7
  142. package/store/readInlineData.js.flow +7 -8
  143. package/subscription/requestSubscription.js.flow +53 -41
  144. package/util/NormalizationNode.js.flow +10 -3
  145. package/util/ReaderNode.js.flow +38 -2
  146. package/util/RelayConcreteNode.js.flow +5 -0
  147. package/util/RelayFeatureFlags.js.flow +24 -10
  148. package/util/RelayProfiler.js.flow +22 -194
  149. package/util/RelayReplaySubject.js.flow +9 -9
  150. package/util/RelayRuntimeTypes.js.flow +72 -3
  151. package/util/StringInterner.js.flow +69 -0
  152. package/util/createPayloadFor3DField.js.flow +3 -3
  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 +1 -0
  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,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
- +operationTracker?: ?OperationTracker,
80
- +optimisticConfig: ?OptimisticResponseConfig,
81
- +publishQueue: PublishQueue,
89
+ +operationTracker: OperationTracker,
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
- _operationTracker: ?OperationTracker;
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,18 +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
- this._publishQueue.run();
281
+ // OK: run revert on cancel
282
+ this._runPublishQueue();
248
283
  }
249
284
  this._incrementalResults.clear();
250
- this._completeOperationTracker();
251
- if (this._retainDisposable) {
252
- this._retainDisposable.dispose();
253
- this._retainDisposable = null;
285
+ if (this._asyncStoreUpdateDisposable != null) {
286
+ this._asyncStoreUpdateDisposable.dispose();
287
+ this._asyncStoreUpdateDisposable = null;
254
288
  }
289
+ this._completeFns = [];
290
+ this._completeOperationTracker();
291
+ this._disposeRetainedData();
255
292
  }
256
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
+
257
314
  _updateActiveState(): void {
258
315
  let activeState;
259
316
  switch (this._state) {
@@ -276,7 +333,7 @@ class Executor {
276
333
  }
277
334
  default:
278
335
  (this._state: empty);
279
- invariant(false, 'RelayModernQueryExecutor: invalid executor state.');
336
+ invariant(false, 'OperationExecutor: invalid executor state.');
280
337
  }
281
338
  this._operationExecutions.set(
282
339
  this._operation.request.identifier,
@@ -313,12 +370,21 @@ class Executor {
313
370
  if (this._subscriptions.size === 0) {
314
371
  this.cancel();
315
372
  this._sink.complete();
373
+ this._log({
374
+ name: 'execute.complete',
375
+ executeId: this._executeId,
376
+ });
316
377
  }
317
378
  }
318
379
 
319
380
  _error(error: Error): void {
320
381
  this.cancel();
321
382
  this._sink.error(error);
383
+ this._log({
384
+ name: 'execute.error',
385
+ executeId: this._executeId,
386
+ error,
387
+ });
322
388
  }
323
389
 
324
390
  _start(id: number, subscription: Subscription): void {
@@ -329,8 +395,16 @@ class Executor {
329
395
  // Handle a raw GraphQL response.
330
396
  _next(_id: number, response: GraphQLResponse): void {
331
397
  this._schedule(() => {
332
- this._handleNext(response);
333
- 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
+ });
334
408
  });
335
409
  }
336
410
 
@@ -374,7 +448,8 @@ class Executor {
374
448
  error.stack;
375
449
  throw error;
376
450
  } else {
377
- const responseWithData: GraphQLResponseWithData = (response: $FlowFixMe);
451
+ const responseWithData: GraphQLResponseWithData =
452
+ (response: $FlowFixMe);
378
453
  results.push(responseWithData);
379
454
  }
380
455
  });
@@ -394,7 +469,10 @@ class Executor {
394
469
  responsePart => responsePart.extensions?.isOptimistic === true,
395
470
  )
396
471
  ) {
397
- invariant(false, 'Optimistic responses cannot be batched.');
472
+ invariant(
473
+ false,
474
+ 'OperationExecutor: Optimistic responses cannot be batched.',
475
+ );
398
476
  }
399
477
  return false;
400
478
  }
@@ -403,7 +481,7 @@ class Executor {
403
481
  if (isOptimistic && this._state !== 'started') {
404
482
  invariant(
405
483
  false,
406
- 'RelayModernQueryExecutor: optimistic payload received after server payload.',
484
+ 'OperationExecutor: optimistic payload received after server payload.',
407
485
  );
408
486
  }
409
487
  if (isOptimistic) {
@@ -422,6 +500,7 @@ class Executor {
422
500
  if (this._state === 'completed') {
423
501
  return;
424
502
  }
503
+ this._seenActors.clear();
425
504
 
426
505
  const responses = Array.isArray(response) ? response : [response];
427
506
  const responsesWithData = this._handleErrorResponse(responses);
@@ -445,10 +524,9 @@ class Executor {
445
524
  return;
446
525
  }
447
526
 
448
- const [
449
- nonIncrementalResponses,
450
- incrementalResponses,
451
- ] = partitionGraphQLResponses(responsesWithData);
527
+ const [nonIncrementalResponses, incrementalResponses] =
528
+ partitionGraphQLResponses(responsesWithData);
529
+ const hasNonIncrementalResponses = nonIncrementalResponses.length > 0;
452
530
 
453
531
  // In theory this doesn't preserve the ordering of the batch.
454
532
  // The idea is that a batch is always:
@@ -457,56 +535,96 @@ class Executor {
457
535
  // The non-incremental payload can appear if the server sends a batch
458
536
  // with the initial payload followed by some early-to-resolve incremental
459
537
  // payloads (although, can that even happen?)
460
- 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
+
461
560
  const payloadFollowups = this._processResponses(nonIncrementalResponses);
462
- // Please note that we're passing `this._operation` to the publish
463
- // queue here, which will later passed to the store (via notify)
464
- // to indicate that this is an operation that caused the store to update
465
- const updatedOwners = this._publishQueue.run(this._operation);
466
- this._updateOperationTracker(updatedOwners);
467
561
  this._processPayloadFollowups(payloadFollowups);
468
- if (this._incrementalPayloadsPending && !this._retainDisposable) {
469
- this._retainDisposable = this._store.retain(this._operation);
470
- }
471
562
  }
472
563
 
473
564
  if (incrementalResponses.length > 0) {
474
- const payloadFollowups = this._processIncrementalResponses(
475
- incrementalResponses,
476
- );
477
- // For the incremental case, we're only handling follow-up responses
478
- // for already initiated operation (and we're not passing it to
479
- // the run(...) call)
480
- const updatedOwners = this._publishQueue.run();
481
- this._updateOperationTracker(updatedOwners);
565
+ const payloadFollowups =
566
+ this._processIncrementalResponses(incrementalResponses);
567
+
482
568
  this._processPayloadFollowups(payloadFollowups);
483
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);
484
598
  this._sink.next(response);
485
599
  }
486
600
 
487
601
  _processOptimisticResponse(
488
602
  response: ?GraphQLResponseWithData,
489
- updater: ?SelectorStoreUpdater,
603
+ updater: ?SelectorStoreUpdater<TMutation['response']>,
490
604
  treatMissingFieldsAsNull: boolean,
491
605
  ): void {
492
606
  invariant(
493
607
  this._optimisticUpdates === null,
494
- 'environment.execute: only support one optimistic response per ' +
608
+ 'OperationExecutor: environment.execute: only support one optimistic response per ' +
495
609
  'execute.',
496
610
  );
497
611
  if (response == null && updater == null) {
498
612
  return;
499
613
  }
500
- const optimisticUpdates: Array<OptimisticUpdate> = [];
614
+ const optimisticUpdates: Array<OptimisticUpdate<TMutation>> = [];
501
615
  if (response) {
502
616
  const payload = normalizeResponse(
503
617
  response,
504
618
  this._operation.root,
505
619
  ROOT_TYPE,
506
620
  {
621
+ actorIdentifier: this._actorIdentifier,
507
622
  getDataID: this._getDataID,
508
623
  path: [],
509
- reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
624
+ reactFlightPayloadDeserializer:
625
+ this._reactFlightPayloadDeserializer != null
626
+ ? this._deserializeReactFlightPayloadWithLogging
627
+ : null,
510
628
  reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
511
629
  shouldProcessClientComponents: this._shouldProcessClientComponents,
512
630
  treatMissingFieldsAsNull,
@@ -526,7 +644,7 @@ class Executor {
526
644
  errors: null,
527
645
  fieldPayloads: null,
528
646
  incrementalPlaceholders: null,
529
- moduleImportPayloads: null,
647
+ followupPayloads: null,
530
648
  source: RelayRecordSource.create(),
531
649
  isFinal: false,
532
650
  },
@@ -534,59 +652,90 @@ class Executor {
534
652
  });
535
653
  }
536
654
  this._optimisticUpdates = optimisticUpdates;
537
- optimisticUpdates.forEach(update => this._publishQueue.applyUpdate(update));
538
- 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();
539
661
  }
540
662
 
541
663
  _processOptimisticFollowups(
542
664
  payload: RelayResponsePayload,
543
- optimisticUpdates: Array<OptimisticUpdate>,
665
+ optimisticUpdates: Array<OptimisticUpdate<TMutation>>,
544
666
  ): void {
545
- if (payload.moduleImportPayloads && payload.moduleImportPayloads.length) {
546
- const moduleImportPayloads = payload.moduleImportPayloads;
547
- const operationLoader = this._operationLoader;
548
- invariant(
549
- operationLoader,
550
- 'RelayModernEnvironment: Expected an operationLoader to be ' +
551
- 'configured when using `@match`.',
552
- );
553
- for (const moduleImportPayload of moduleImportPayloads) {
554
- const operation = operationLoader.get(
555
- moduleImportPayload.operationReference,
556
- );
557
- if (operation == null) {
558
- this._processAsyncOptimisticModuleImport(
559
- operationLoader,
560
- moduleImportPayload,
561
- );
562
- } else {
563
- const moduleImportOptimisticUpdates = this._processOptimisticModuleImport(
564
- operation,
565
- moduleImportPayload,
566
- );
567
- 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
+ );
568
697
  }
569
698
  }
570
699
  }
571
700
  }
572
701
 
573
- _normalizeModuleImport(
574
- moduleImportPayload: ModuleImportPayload,
575
- operation: NormalizationSelectableNode,
702
+ /**
703
+ * Normalize Data for @module payload, and actor-specific payload
704
+ */
705
+ _normalizeFollowupPayload(
706
+ followupPayload: FollowupPayload,
707
+ normalizationNode: NormalizationSelectableNode,
576
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
+ }
577
722
  const selector = createNormalizationSelector(
578
- operation,
579
- moduleImportPayload.dataID,
580
- moduleImportPayload.variables,
723
+ normalizationNode,
724
+ followupPayload.dataID,
725
+ variables,
581
726
  );
582
727
  return normalizeResponse(
583
- {data: moduleImportPayload.data},
728
+ {data: followupPayload.data},
584
729
  selector,
585
- moduleImportPayload.typeName,
730
+ followupPayload.typeName,
586
731
  {
732
+ actorIdentifier: this._actorIdentifier,
587
733
  getDataID: this._getDataID,
588
- path: moduleImportPayload.path,
589
- reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
734
+ path: followupPayload.path,
735
+ reactFlightPayloadDeserializer:
736
+ this._reactFlightPayloadDeserializer != null
737
+ ? this._deserializeReactFlightPayloadWithLogging
738
+ : null,
590
739
  reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
591
740
  treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
592
741
  shouldProcessClientComponents: this._shouldProcessClientComponents,
@@ -597,10 +746,10 @@ class Executor {
597
746
  _processOptimisticModuleImport(
598
747
  normalizationRootNode: NormalizationRootNode,
599
748
  moduleImportPayload: ModuleImportPayload,
600
- ): $ReadOnlyArray<OptimisticUpdate> {
749
+ ): $ReadOnlyArray<OptimisticUpdate<TMutation>> {
601
750
  const operation = getOperation(normalizationRootNode);
602
751
  const optimisticUpdates = [];
603
- const modulePayload = this._normalizeModuleImport(
752
+ const modulePayload = this._normalizeFollowupPayload(
604
753
  moduleImportPayload,
605
754
  operation,
606
755
  );
@@ -615,41 +764,41 @@ class Executor {
615
764
  }
616
765
 
617
766
  _processAsyncOptimisticModuleImport(
618
- operationLoader: OperationLoader,
619
767
  moduleImportPayload: ModuleImportPayload,
620
768
  ): void {
621
- operationLoader
769
+ this._expectOperationLoader()
622
770
  .load(moduleImportPayload.operationReference)
623
771
  .then(operation => {
624
772
  if (operation == null || this._state !== 'started') {
625
773
  return;
626
774
  }
627
- const moduleImportOptimisticUpdates = this._processOptimisticModuleImport(
628
- operation,
629
- moduleImportPayload,
630
- );
775
+ const moduleImportOptimisticUpdates =
776
+ this._processOptimisticModuleImport(operation, moduleImportPayload);
631
777
  moduleImportOptimisticUpdates.forEach(update =>
632
- this._publishQueue.applyUpdate(update),
778
+ this._getPublishQueueAndSaveActor().applyUpdate(update),
633
779
  );
634
780
  if (this._optimisticUpdates == null) {
635
781
  warning(
636
782
  false,
637
- 'RelayModernQueryExecutor: Unexpected ModuleImport optimistic ' +
783
+ 'OperationExecutor: Unexpected ModuleImport optimistic ' +
638
784
  'update in operation %s.' +
639
785
  this._operation.request.node.params.name,
640
786
  );
641
787
  } else {
642
788
  this._optimisticUpdates.push(...moduleImportOptimisticUpdates);
643
- this._publishQueue.run();
789
+ // OK: always have to run() after an module import resolves async
790
+ this._runPublishQueue();
644
791
  }
645
792
  });
646
793
  }
647
794
 
648
- _processResponses(responses: $ReadOnlyArray<GraphQLResponseWithData>) {
795
+ _processResponses(
796
+ responses: $ReadOnlyArray<GraphQLResponseWithData>,
797
+ ): $ReadOnlyArray<RelayResponsePayload> {
649
798
  if (this._optimisticUpdates !== null) {
650
- this._optimisticUpdates.forEach(update =>
651
- this._publishQueue.revertUpdate(update),
652
- );
799
+ this._optimisticUpdates.forEach(update => {
800
+ this._getPublishQueueAndSaveActor().revertUpdate(update);
801
+ });
653
802
  this._optimisticUpdates = null;
654
803
  }
655
804
 
@@ -662,19 +811,24 @@ class Executor {
662
811
  this._operation.root,
663
812
  ROOT_TYPE,
664
813
  {
814
+ actorIdentifier: this._actorIdentifier,
665
815
  getDataID: this._getDataID,
666
816
  path: [],
667
- reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
817
+ reactFlightPayloadDeserializer:
818
+ this._reactFlightPayloadDeserializer != null
819
+ ? this._deserializeReactFlightPayloadWithLogging
820
+ : null,
668
821
  reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
669
822
  treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
670
823
  shouldProcessClientComponents: this._shouldProcessClientComponents,
671
824
  },
672
825
  );
673
- this._publishQueue.commitPayload(
826
+ this._getPublishQueueAndSaveActor().commitPayload(
674
827
  this._operation,
675
828
  relayPayload,
676
829
  this._updater,
677
830
  );
831
+
678
832
  return relayPayload;
679
833
  });
680
834
  }
@@ -690,30 +844,30 @@ class Executor {
690
844
  return;
691
845
  }
692
846
  payloads.forEach(payload => {
693
- const {incrementalPlaceholders, moduleImportPayloads, isFinal} = payload;
847
+ const {incrementalPlaceholders, followupPayloads, isFinal} = payload;
694
848
  this._state = isFinal ? 'loading_final' : 'loading_incremental';
695
849
  this._updateActiveState();
696
850
  if (isFinal) {
697
851
  this._incrementalPayloadsPending = false;
698
852
  }
699
- if (moduleImportPayloads && moduleImportPayloads.length !== 0) {
700
- const operationLoader = this._operationLoader;
701
- invariant(
702
- operationLoader,
703
- 'RelayModernEnvironment: Expected an operationLoader to be ' +
704
- 'configured when using `@match`.',
705
- );
706
- moduleImportPayloads.forEach(moduleImportPayload => {
707
- this._processModuleImportPayload(
708
- moduleImportPayload,
709
- operationLoader,
710
- );
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;
711
860
  });
712
861
  }
862
+
713
863
  if (incrementalPlaceholders && incrementalPlaceholders.length !== 0) {
714
864
  this._incrementalPayloadsPending = this._state !== 'loading_final';
715
865
  incrementalPlaceholders.forEach(incrementalPlaceholder => {
866
+ const prevActorIdentifier = this._actorIdentifier;
867
+ this._actorIdentifier =
868
+ incrementalPlaceholder.actorIdentifier ?? this._actorIdentifier;
716
869
  this._processIncrementalPlaceholder(payload, incrementalPlaceholder);
870
+ this._actorIdentifier = prevActorIdentifier;
717
871
  });
718
872
 
719
873
  if (this._isClientPayload || this._state === 'loading_final') {
@@ -745,8 +899,6 @@ class Executor {
745
899
  }
746
900
  });
747
901
  if (relayPayloads.length > 0) {
748
- const updatedOwners = this._publishQueue.run();
749
- this._updateOperationTracker(updatedOwners);
750
902
  this._processPayloadFollowups(relayPayloads);
751
903
  }
752
904
  }
@@ -764,23 +916,6 @@ class Executor {
764
916
  ) {
765
917
  this._completeOperationTracker();
766
918
  }
767
- if (RelayFeatureFlags.ENABLE_UNIQUE_SUBSCRIPTION_ROOT) {
768
- const nextID = generateUniqueClientID();
769
- this._operation = {
770
- request: this._operation.request,
771
- fragment: createReaderSelector(
772
- this._operation.fragment.node,
773
- nextID,
774
- this._operation.fragment.variables,
775
- this._operation.fragment.owner,
776
- ),
777
- root: createNormalizationSelector(
778
- this._operation.root.node,
779
- nextID,
780
- this._operation.root.variables,
781
- ),
782
- };
783
- }
784
919
  }
785
920
 
786
921
  /**
@@ -790,73 +925,154 @@ class Executor {
790
925
  * defer, stream, etc); these are handled by calling
791
926
  * `_processPayloadFollowups()`.
792
927
  */
793
- _processModuleImportPayload(
794
- moduleImportPayload: ModuleImportPayload,
795
- operationLoader: OperationLoader,
796
- ): void {
797
- const node = operationLoader.get(moduleImportPayload.operationReference);
798
- if (node != null) {
799
- const operation = getOperation(node);
800
- // If the operation module is available synchronously, normalize the
801
- // data synchronously.
802
- this._handleModuleImportPayload(moduleImportPayload, operation);
803
- this._maybeCompleteSubscriptionOperationTracking();
804
- } else {
805
- // Otherwise load the operation module and schedule a task to normalize
806
- // the data when the module is available.
807
- const id = this._nextSubscriptionId++;
808
- this._pendingModulePayloadsCount++;
809
-
810
- const decrementPendingCount = () => {
811
- this._pendingModulePayloadsCount--;
812
- this._maybeCompleteSubscriptionOperationTracking();
813
- };
814
-
815
- // Observable.from(operationLoader.load()) wouldn't catch synchronous
816
- // errors thrown by the load function, which is user-defined. Guard
817
- // against that with Observable.from(new Promise(<work>)).
818
- RelayObservable.from(
819
- new Promise((resolve, reject) => {
820
- operationLoader
821
- .load(moduleImportPayload.operationReference)
822
- .then(resolve, reject);
823
- }),
824
- )
825
- .map((operation: ?NormalizationRootNode) => {
826
- if (operation != null) {
827
- this._schedule(() => {
828
- this._handleModuleImportPayload(
829
- moduleImportPayload,
830
- getOperation(operation),
831
- );
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,
832
1014
  });
833
- }
834
- })
835
- .subscribe({
836
- complete: () => {
837
- this._complete(id);
838
- decrementPendingCount();
839
- },
840
- error: error => {
841
- this._error(error);
842
- decrementPendingCount();
843
- },
844
- start: subscription => this._start(id, subscription),
845
- });
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
+ );
846
1047
  }
847
1048
  }
848
1049
 
849
- _handleModuleImportPayload(
850
- moduleImportPayload: ModuleImportPayload,
851
- 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,
852
1067
  ): void {
853
- const relayPayload = this._normalizeModuleImport(
854
- moduleImportPayload,
855
- operation,
1068
+ const relayPayload = this._normalizeFollowupPayload(
1069
+ followupPayload,
1070
+ normalizationNode,
1071
+ );
1072
+ this._getPublishQueueAndSaveActor().commitPayload(
1073
+ this._operation,
1074
+ relayPayload,
856
1075
  );
857
- this._publishQueue.commitPayload(this._operation, relayPayload);
858
- const updatedOwners = this._publishQueue.run();
859
- this._updateOperationTracker(updatedOwners);
860
1076
  this._processPayloadFollowups([relayPayload]);
861
1077
  }
862
1078
 
@@ -902,7 +1118,7 @@ class Executor {
902
1118
  (placeholder: empty);
903
1119
  invariant(
904
1120
  false,
905
- 'Unsupported incremental placeholder kind `%s`.',
1121
+ 'OperationExecutor: Unsupported incremental placeholder kind `%s`.',
906
1122
  placeholder.kind,
907
1123
  );
908
1124
  }
@@ -926,7 +1142,7 @@ class Executor {
926
1142
  // exist.
927
1143
  invariant(
928
1144
  parentRecord != null,
929
- 'RelayModernEnvironment: Expected record `%s` to exist.',
1145
+ 'OperationExecutor: Expected record `%s` to exist.',
930
1146
  parentID,
931
1147
  );
932
1148
  let nextParentRecord;
@@ -955,14 +1171,12 @@ class Executor {
955
1171
  record: nextParentRecord,
956
1172
  fieldPayloads: nextParentPayloads,
957
1173
  });
1174
+
958
1175
  // If there were any queued responses, process them now that placeholders
959
1176
  // are in place
960
1177
  if (pendingResponses != null) {
961
- const payloadFollowups = this._processIncrementalResponses(
962
- pendingResponses,
963
- );
964
- const updatedOwners = this._publishQueue.run();
965
- this._updateOperationTracker(updatedOwners);
1178
+ const payloadFollowups =
1179
+ this._processIncrementalResponses(pendingResponses);
966
1180
  this._processPayloadFollowups(payloadFollowups);
967
1181
  }
968
1182
  }
@@ -998,7 +1212,7 @@ class Executor {
998
1212
  const placeholder = resultForPath.placeholder;
999
1213
  invariant(
1000
1214
  placeholder.kind === 'defer',
1001
- 'RelayModernEnvironment: Expected data for path `%s` for label `%s` ' +
1215
+ 'OperationExecutor: Expected data for path `%s` for label `%s` ' +
1002
1216
  'to be data for @defer, was `@%s`.',
1003
1217
  pathKey,
1004
1218
  label,
@@ -1012,10 +1226,7 @@ class Executor {
1012
1226
  // but Relay records paths relative to the parent of the stream node:
1013
1227
  // therefore we strip the last two elements just to lookup the path
1014
1228
  // (the item index is used later to insert the element in the list)
1015
- const pathKey = path
1016
- .slice(0, -2)
1017
- .map(String)
1018
- .join('.');
1229
+ const pathKey = path.slice(0, -2).map(String).join('.');
1019
1230
  let resultForPath = resultForLabel.get(pathKey);
1020
1231
  if (resultForPath == null) {
1021
1232
  resultForPath = {kind: 'response', responses: [incrementalResponse]};
@@ -1028,7 +1239,7 @@ class Executor {
1028
1239
  const placeholder = resultForPath.placeholder;
1029
1240
  invariant(
1030
1241
  placeholder.kind === 'stream',
1031
- 'RelayModernEnvironment: Expected data for path `%s` for label `%s` ' +
1242
+ 'OperationExecutor: Expected data for path `%s` for label `%s` ' +
1032
1243
  'to be data for @stream, was `@%s`.',
1033
1244
  pathKey,
1034
1245
  label,
@@ -1049,27 +1260,37 @@ class Executor {
1049
1260
  response: GraphQLResponseWithData,
1050
1261
  ): RelayResponsePayload {
1051
1262
  const {dataID: parentID} = placeholder.selector;
1263
+ const prevActorIdentifier = this._actorIdentifier;
1264
+ this._actorIdentifier =
1265
+ placeholder.actorIdentifier ?? this._actorIdentifier;
1052
1266
  const relayPayload = normalizeResponse(
1053
1267
  response,
1054
1268
  placeholder.selector,
1055
1269
  placeholder.typeName,
1056
1270
  {
1271
+ actorIdentifier: this._actorIdentifier,
1057
1272
  getDataID: this._getDataID,
1058
1273
  path: placeholder.path,
1059
- reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
1274
+ reactFlightPayloadDeserializer:
1275
+ this._reactFlightPayloadDeserializer != null
1276
+ ? this._deserializeReactFlightPayloadWithLogging
1277
+ : null,
1060
1278
  reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
1061
1279
  treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
1062
1280
  shouldProcessClientComponents: this._shouldProcessClientComponents,
1063
1281
  },
1064
1282
  );
1065
- this._publishQueue.commitPayload(this._operation, relayPayload);
1283
+ this._getPublishQueueAndSaveActor().commitPayload(
1284
+ this._operation,
1285
+ relayPayload,
1286
+ );
1066
1287
 
1067
1288
  // Load the version of the parent record from which this incremental data
1068
1289
  // was derived
1069
1290
  const parentEntry = this._source.get(parentID);
1070
1291
  invariant(
1071
1292
  parentEntry != null,
1072
- 'RelayModernEnvironment: Expected the parent record `%s` for @defer ' +
1293
+ 'OperationExecutor: Expected the parent record `%s` for @defer ' +
1073
1294
  'data to exist.',
1074
1295
  parentID,
1075
1296
  );
@@ -1079,15 +1300,17 @@ class Executor {
1079
1300
  errors: null,
1080
1301
  fieldPayloads,
1081
1302
  incrementalPlaceholders: null,
1082
- moduleImportPayloads: null,
1303
+ followupPayloads: null,
1083
1304
  source: RelayRecordSource.create(),
1084
1305
  isFinal: response.extensions?.is_final === true,
1085
1306
  };
1086
- this._publishQueue.commitPayload(
1307
+ this._getPublishQueueAndSaveActor().commitPayload(
1087
1308
  this._operation,
1088
1309
  handleFieldsRelayPayload,
1089
1310
  );
1090
1311
  }
1312
+
1313
+ this._actorIdentifier = prevActorIdentifier;
1091
1314
  return relayPayload;
1092
1315
  }
1093
1316
 
@@ -1100,12 +1323,14 @@ class Executor {
1100
1323
  placeholder: StreamPlaceholder,
1101
1324
  response: GraphQLResponseWithData,
1102
1325
  ): RelayResponsePayload {
1103
- const {parentID, node, variables} = placeholder;
1326
+ const {parentID, node, variables, actorIdentifier} = placeholder;
1327
+ const prevActorIdentifier = this._actorIdentifier;
1328
+ this._actorIdentifier = actorIdentifier ?? this._actorIdentifier;
1104
1329
  // Find the LinkedField where @stream was applied
1105
1330
  const field = node.selections[0];
1106
1331
  invariant(
1107
1332
  field != null && field.kind === 'LinkedField' && field.plural === true,
1108
- 'RelayModernEnvironment: Expected @stream to be used on a plural field.',
1333
+ 'OperationExecutor: Expected @stream to be used on a plural field.',
1109
1334
  );
1110
1335
  const {
1111
1336
  fieldPayloads,
@@ -1125,34 +1350,38 @@ class Executor {
1125
1350
  // Publish the new item and update the parent record to set
1126
1351
  // field[index] = item *if* the parent record hasn't been concurrently
1127
1352
  // modified.
1128
- this._publishQueue.commitPayload(this._operation, relayPayload, store => {
1129
- const currentParentRecord = store.get(parentID);
1130
- if (currentParentRecord == null) {
1131
- // parent has since been deleted, stream data is stale
1132
- return;
1133
- }
1134
- const currentItems = currentParentRecord.getLinkedRecords(storageKey);
1135
- if (currentItems == null) {
1136
- // field has since been deleted, stream data is stale
1137
- return;
1138
- }
1139
- if (
1140
- currentItems.length !== prevIDs.length ||
1141
- currentItems.some(
1142
- (currentItem, index) =>
1143
- prevIDs[index] !== (currentItem && currentItem.getDataID()),
1144
- )
1145
- ) {
1146
- // field has been modified by something other than this query,
1147
- // stream data is stale
1148
- return;
1149
- }
1150
- // parent.field has not been concurrently modified:
1151
- // update `parent.field[index] = item`
1152
- const nextItems = [...currentItems];
1153
- nextItems[itemIndex] = store.get(itemID);
1154
- currentParentRecord.setLinkedRecords(nextItems, storageKey);
1155
- });
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
+ );
1156
1385
 
1157
1386
  // Now that the parent record has been updated to include the new item,
1158
1387
  // also update any handle fields that are derived from the parent record.
@@ -1161,15 +1390,17 @@ class Executor {
1161
1390
  errors: null,
1162
1391
  fieldPayloads,
1163
1392
  incrementalPlaceholders: null,
1164
- moduleImportPayloads: null,
1393
+ followupPayloads: null,
1165
1394
  source: RelayRecordSource.create(),
1166
1395
  isFinal: false,
1167
1396
  };
1168
- this._publishQueue.commitPayload(
1397
+ this._getPublishQueueAndSaveActor().commitPayload(
1169
1398
  this._operation,
1170
1399
  handleFieldsRelayPayload,
1171
1400
  );
1172
1401
  }
1402
+
1403
+ this._actorIdentifier = prevActorIdentifier;
1173
1404
  return relayPayload;
1174
1405
  }
1175
1406
 
@@ -1191,7 +1422,7 @@ class Executor {
1191
1422
  const {data} = response;
1192
1423
  invariant(
1193
1424
  typeof data === 'object',
1194
- 'RelayModernEnvironment: Expected the GraphQL @stream payload `data` ' +
1425
+ 'OperationExecutor: Expected the GraphQL @stream payload `data` ' +
1195
1426
  'value to be an object.',
1196
1427
  );
1197
1428
  const responseKey = field.alias ?? field.name;
@@ -1202,7 +1433,7 @@ class Executor {
1202
1433
  const parentEntry = this._source.get(parentID);
1203
1434
  invariant(
1204
1435
  parentEntry != null,
1205
- 'RelayModernEnvironment: Expected the parent record `%s` for @stream ' +
1436
+ 'OperationExecutor: Expected the parent record `%s` for @stream ' +
1206
1437
  'data to exist.',
1207
1438
  parentID,
1208
1439
  );
@@ -1217,7 +1448,7 @@ class Executor {
1217
1448
  );
1218
1449
  invariant(
1219
1450
  prevIDs != null,
1220
- 'RelayModernEnvironment: Expected record `%s` to have fetched field ' +
1451
+ 'OperationExecutor: Expected record `%s` to have fetched field ' +
1221
1452
  '`%s` with @stream.',
1222
1453
  parentID,
1223
1454
  field.name,
@@ -1228,7 +1459,7 @@ class Executor {
1228
1459
  const itemIndex = parseInt(finalPathEntry, 10);
1229
1460
  invariant(
1230
1461
  itemIndex === finalPathEntry && itemIndex >= 0,
1231
- 'RelayModernEnvironment: Expected path for @stream to end in a ' +
1462
+ 'OperationExecutor: Expected path for @stream to end in a ' +
1232
1463
  'positive integer index, got `%s`',
1233
1464
  finalPathEntry,
1234
1465
  );
@@ -1236,7 +1467,7 @@ class Executor {
1236
1467
  const typeName = field.concreteType ?? data[TYPENAME_KEY];
1237
1468
  invariant(
1238
1469
  typeof typeName === 'string',
1239
- 'RelayModernEnvironment: Expected @stream field `%s` to have a ' +
1470
+ 'OperationExecutor: Expected @stream field `%s` to have a ' +
1240
1471
  '__typename.',
1241
1472
  field.name,
1242
1473
  );
@@ -1251,7 +1482,7 @@ class Executor {
1251
1482
  generateClientID(parentID, storageKey, itemIndex);
1252
1483
  invariant(
1253
1484
  typeof itemID === 'string',
1254
- 'RelayModernEnvironment: Expected id of elements of field `%s` to ' +
1485
+ 'OperationExecutor: Expected id of elements of field `%s` to ' +
1255
1486
  'be strings.',
1256
1487
  storageKey,
1257
1488
  );
@@ -1271,9 +1502,13 @@ class Executor {
1271
1502
  fieldPayloads,
1272
1503
  });
1273
1504
  const relayPayload = normalizeResponse(response, selector, typeName, {
1505
+ actorIdentifier: this._actorIdentifier,
1274
1506
  getDataID: this._getDataID,
1275
1507
  path: [...normalizationPath, responseKey, String(itemIndex)],
1276
- reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
1508
+ reactFlightPayloadDeserializer:
1509
+ this._reactFlightPayloadDeserializer != null
1510
+ ? this._deserializeReactFlightPayloadWithLogging
1511
+ : null,
1277
1512
  reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
1278
1513
  treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
1279
1514
  shouldProcessClientComponents: this._shouldProcessClientComponents,
@@ -1288,14 +1523,29 @@ class Executor {
1288
1523
  };
1289
1524
  }
1290
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
+
1291
1545
  _updateOperationTracker(
1292
1546
  updatedOwners: ?$ReadOnlyArray<RequestDescriptor>,
1293
1547
  ): void {
1294
- if (
1295
- this._operationTracker != null &&
1296
- updatedOwners != null &&
1297
- updatedOwners.length > 0
1298
- ) {
1548
+ if (updatedOwners != null && updatedOwners.length > 0) {
1299
1549
  this._operationTracker.update(
1300
1550
  this._operation.request,
1301
1551
  new Set(updatedOwners),
@@ -1304,10 +1554,60 @@ class Executor {
1304
1554
  }
1305
1555
 
1306
1556
  _completeOperationTracker() {
1307
- if (this._operationTracker != null) {
1308
- 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;
1309
1570
  }
1310
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
+ }
1311
1611
  }
1312
1612
 
1313
1613
  function partitionGraphQLResponses(
@@ -1324,7 +1624,7 @@ function partitionGraphQLResponses(
1324
1624
  if (label == null || path == null) {
1325
1625
  invariant(
1326
1626
  false,
1327
- 'RelayModernQueryExecutor: invalid incremental payload, expected ' +
1627
+ 'OperationExecutor: invalid incremental payload, expected ' +
1328
1628
  '`path` and `label` to either both be null/undefined, or ' +
1329
1629
  '`path` to be an `Array<string | number>` and `label` to be a ' +
1330
1630
  '`string`.',
@@ -1376,11 +1676,13 @@ function validateOptimisticResponsePayload(
1376
1676
  if (incrementalPlaceholders != null && incrementalPlaceholders.length !== 0) {
1377
1677
  invariant(
1378
1678
  false,
1379
- 'RelayModernQueryExecutor: optimistic responses cannot be returned ' +
1679
+ 'OperationExecutor: optimistic responses cannot be returned ' +
1380
1680
  'for operations that use incremental data delivery (@defer, ' +
1381
1681
  '@stream, and @stream_connection).',
1382
1682
  );
1383
1683
  }
1384
1684
  }
1385
1685
 
1386
- module.exports = {execute};
1686
+ module.exports = {
1687
+ execute,
1688
+ };