relay-runtime 11.0.0-rc.0 → 12.0.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 (128) hide show
  1. package/handlers/connection/ConnectionHandler.js.flow +7 -0
  2. package/handlers/connection/MutationHandlers.js.flow +28 -0
  3. package/index.js +1 -1
  4. package/index.js.flow +20 -3
  5. package/lib/handlers/RelayDefaultHandlerProvider.js +1 -1
  6. package/lib/handlers/connection/ConnectionHandler.js +12 -6
  7. package/lib/handlers/connection/MutationHandlers.js +67 -8
  8. package/lib/index.js +15 -0
  9. package/lib/multi-actor-environment/ActorIdentifier.js +33 -0
  10. package/lib/multi-actor-environment/ActorSpecificEnvironment.js +148 -0
  11. package/lib/multi-actor-environment/ActorUtils.js +27 -0
  12. package/lib/multi-actor-environment/MultiActorEnvironment.js +406 -0
  13. package/lib/multi-actor-environment/MultiActorEnvironmentTypes.js +11 -0
  14. package/lib/multi-actor-environment/index.js +21 -0
  15. package/lib/mutations/RelayRecordProxy.js +1 -1
  16. package/lib/mutations/RelayRecordSourceMutator.js +1 -1
  17. package/lib/mutations/RelayRecordSourceProxy.js +1 -1
  18. package/lib/mutations/RelayRecordSourceSelectorProxy.js +7 -2
  19. package/lib/mutations/applyOptimisticMutation.js +1 -1
  20. package/lib/mutations/commitMutation.js +5 -2
  21. package/lib/mutations/validateMutation.js +39 -17
  22. package/lib/network/RelayNetwork.js +1 -1
  23. package/lib/network/RelayObservable.js +3 -1
  24. package/lib/network/RelayQueryResponseCache.js +20 -3
  25. package/lib/network/wrapNetworkWithLogObserver.js +78 -0
  26. package/lib/query/GraphQLTag.js +1 -1
  27. package/lib/query/fetchQuery.js +1 -1
  28. package/lib/query/fetchQueryInternal.js +1 -1
  29. package/lib/store/DataChecker.js +132 -50
  30. package/lib/store/{RelayModernQueryExecutor.js → OperationExecutor.js} +524 -187
  31. package/lib/store/RelayConcreteVariables.js +29 -4
  32. package/lib/store/RelayModernEnvironment.js +137 -220
  33. package/lib/store/RelayModernFragmentSpecResolver.js +49 -23
  34. package/lib/store/RelayModernRecord.js +36 -2
  35. package/lib/store/RelayModernSelector.js +1 -1
  36. package/lib/store/RelayModernStore.js +53 -22
  37. package/lib/store/RelayOperationTracker.js +34 -24
  38. package/lib/store/RelayPublishQueue.js +30 -8
  39. package/lib/store/RelayReader.js +177 -29
  40. package/lib/store/RelayRecordSource.js +87 -3
  41. package/lib/store/RelayReferenceMarker.js +53 -28
  42. package/lib/store/RelayResponseNormalizer.js +247 -108
  43. package/lib/store/RelayStoreReactFlightUtils.js +7 -11
  44. package/lib/store/RelayStoreSubscriptions.js +8 -5
  45. package/lib/store/RelayStoreUtils.js +10 -4
  46. package/lib/store/ResolverCache.js +213 -0
  47. package/lib/store/ResolverFragments.js +57 -0
  48. package/lib/store/cloneRelayHandleSourceField.js +1 -1
  49. package/lib/store/cloneRelayScalarHandleSourceField.js +1 -1
  50. package/lib/store/createRelayContext.js +2 -2
  51. package/lib/store/defaultGetDataID.js +3 -1
  52. package/lib/store/readInlineData.js +1 -1
  53. package/lib/subscription/requestSubscription.js +32 -6
  54. package/lib/util/RelayConcreteNode.js +3 -0
  55. package/lib/util/RelayFeatureFlags.js +5 -4
  56. package/lib/util/RelayProfiler.js +17 -187
  57. package/lib/util/RelayReplaySubject.js +22 -7
  58. package/lib/util/deepFreeze.js +1 -0
  59. package/lib/util/getPaginationMetadata.js +41 -0
  60. package/lib/util/getPaginationVariables.js +67 -0
  61. package/lib/util/getPendingOperationsForFragment.js +55 -0
  62. package/lib/util/getRefetchMetadata.js +36 -0
  63. package/lib/util/getRelayHandleKey.js +1 -1
  64. package/lib/util/getRequestIdentifier.js +1 -1
  65. package/lib/util/getValueAtPath.js +51 -0
  66. package/lib/util/isEmptyObject.js +1 -1
  67. package/lib/util/registerEnvironmentWithDevTools.js +26 -0
  68. package/lib/util/withDuration.js +31 -0
  69. package/multi-actor-environment/ActorIdentifier.js.flow +43 -0
  70. package/multi-actor-environment/ActorSpecificEnvironment.js.flow +217 -0
  71. package/multi-actor-environment/ActorUtils.js.flow +33 -0
  72. package/multi-actor-environment/MultiActorEnvironment.js.flow +485 -0
  73. package/multi-actor-environment/MultiActorEnvironmentTypes.js.flow +245 -0
  74. package/multi-actor-environment/index.js.flow +27 -0
  75. package/mutations/RelayRecordSourceSelectorProxy.js.flow +7 -2
  76. package/mutations/commitMutation.js.flow +3 -1
  77. package/mutations/validateMutation.js.flow +42 -16
  78. package/network/RelayNetworkTypes.js.flow +17 -8
  79. package/network/RelayObservable.js.flow +2 -0
  80. package/network/RelayQueryResponseCache.js.flow +31 -17
  81. package/network/wrapNetworkWithLogObserver.js.flow +99 -0
  82. package/package.json +3 -2
  83. package/relay-runtime.js +2 -2
  84. package/relay-runtime.min.js +2 -2
  85. package/store/ClientID.js.flow +5 -1
  86. package/store/DataChecker.js.flow +148 -44
  87. package/store/{RelayModernQueryExecutor.js.flow → OperationExecutor.js.flow} +578 -237
  88. package/store/RelayConcreteVariables.js.flow +31 -1
  89. package/store/RelayModernEnvironment.js.flow +132 -220
  90. package/store/RelayModernFragmentSpecResolver.js.flow +40 -14
  91. package/store/RelayModernOperationDescriptor.js.flow +9 -3
  92. package/store/RelayModernRecord.js.flow +49 -0
  93. package/store/RelayModernStore.js.flow +57 -17
  94. package/store/RelayOperationTracker.js.flow +56 -34
  95. package/store/RelayPublishQueue.js.flow +37 -11
  96. package/store/RelayReader.js.flow +186 -27
  97. package/store/RelayRecordSource.js.flow +72 -6
  98. package/store/RelayReferenceMarker.js.flow +51 -21
  99. package/store/RelayResponseNormalizer.js.flow +251 -67
  100. package/store/RelayStoreReactFlightUtils.js.flow +6 -9
  101. package/store/RelayStoreSubscriptions.js.flow +10 -3
  102. package/store/RelayStoreTypes.js.flow +144 -21
  103. package/store/RelayStoreUtils.js.flow +19 -4
  104. package/store/ResolverCache.js.flow +247 -0
  105. package/store/ResolverFragments.js.flow +128 -0
  106. package/store/createRelayContext.js.flow +1 -1
  107. package/store/defaultGetDataID.js.flow +3 -1
  108. package/subscription/requestSubscription.js.flow +43 -8
  109. package/util/NormalizationNode.js.flow +16 -3
  110. package/util/ReaderNode.js.flow +29 -2
  111. package/util/RelayConcreteNode.js.flow +3 -0
  112. package/util/RelayFeatureFlags.js.flow +10 -6
  113. package/util/RelayProfiler.js.flow +22 -194
  114. package/util/RelayReplaySubject.js.flow +7 -6
  115. package/util/RelayRuntimeTypes.js.flow +4 -2
  116. package/util/deepFreeze.js.flow +2 -1
  117. package/util/getPaginationMetadata.js.flow +74 -0
  118. package/util/getPaginationVariables.js.flow +112 -0
  119. package/util/getPendingOperationsForFragment.js.flow +62 -0
  120. package/util/getRefetchMetadata.js.flow +80 -0
  121. package/util/getValueAtPath.js.flow +46 -0
  122. package/util/isEmptyObject.js.flow +2 -1
  123. package/util/registerEnvironmentWithDevTools.js.flow +33 -0
  124. package/util/withDuration.js.flow +32 -0
  125. package/lib/store/RelayRecordSourceMapImpl.js +0 -107
  126. package/lib/store/RelayStoreSubscriptionsUsingMapByID.js +0 -318
  127. package/store/RelayRecordSourceMapImpl.js.flow +0 -91
  128. package/store/RelayStoreSubscriptionsUsingMapByID.js.flow +0 -283
@@ -14,31 +14,42 @@
14
14
  'use strict';
15
15
 
16
16
  const RelayError = require('../util/RelayError');
17
+ const RelayFeatureFlags = require('../util/RelayFeatureFlags');
17
18
  const RelayModernRecord = require('./RelayModernRecord');
18
19
  const RelayObservable = require('../network/RelayObservable');
19
20
  const RelayRecordSource = require('./RelayRecordSource');
20
21
  const RelayResponseNormalizer = require('./RelayResponseNormalizer');
21
22
 
23
+ const generateID = require('../util/generateID');
22
24
  const getOperation = require('../util/getOperation');
23
25
  const invariant = require('invariant');
24
26
  const stableCopy = require('../util/stableCopy');
25
27
  const warning = require('warning');
26
-
27
- const {generateClientID} = require('./ClientID');
28
- const {createNormalizationSelector} = require('./RelayModernSelector');
28
+ const withDuration = require('../util/withDuration');
29
+
30
+ const {generateClientID, generateUniqueClientID} = require('./ClientID');
31
+ const {getLocalVariables} = require('./RelayConcreteVariables');
32
+ const {
33
+ createNormalizationSelector,
34
+ createReaderSelector,
35
+ } = require('./RelayModernSelector');
29
36
  const {ROOT_TYPE, TYPENAME_KEY, getStorageKey} = require('./RelayStoreUtils');
30
37
 
38
+ import type {ActorIdentifier} from '../multi-actor-environment/ActorIdentifier';
31
39
  import type {
32
40
  GraphQLResponse,
33
41
  GraphQLSingularResponse,
34
42
  GraphQLResponseWithData,
43
+ ReactFlightServerTree,
35
44
  } from '../network/RelayNetworkTypes';
36
45
  import type {Sink, Subscription} from '../network/RelayObservable';
37
46
  import type {
38
47
  DeferPlaceholder,
48
+ FollowupPayload,
39
49
  RequestDescriptor,
40
50
  HandleFieldPayload,
41
51
  IncrementalDataPlaceholder,
52
+ LogFunction,
42
53
  ModuleImportPayload,
43
54
  NormalizationSelector,
44
55
  OperationDescriptor,
@@ -49,6 +60,7 @@ import type {
49
60
  PublishQueue,
50
61
  ReactFlightPayloadDeserializer,
51
62
  ReactFlightServerErrorHandler,
63
+ ReactFlightClientResponse,
52
64
  Record,
53
65
  RelayResponsePayload,
54
66
  SelectorStoreUpdater,
@@ -67,22 +79,25 @@ import type {GetDataID} from './RelayResponseNormalizer';
67
79
  import type {NormalizationOptions} from './RelayResponseNormalizer';
68
80
 
69
81
  export type ExecuteConfig = {|
82
+ +actorIdentifier: ActorIdentifier,
70
83
  +getDataID: GetDataID,
71
- +treatMissingFieldsAsNull: boolean,
84
+ +getPublishQueue: (actorIdentifier: ActorIdentifier) => PublishQueue,
85
+ +getStore: (actorIdentifier: ActorIdentifier) => Store,
86
+ +isClientPayload?: boolean,
72
87
  +operation: OperationDescriptor,
73
88
  +operationExecutions: Map<string, ActiveState>,
74
89
  +operationLoader: ?OperationLoader,
75
- +operationTracker?: ?OperationTracker,
90
+ +operationTracker: OperationTracker,
76
91
  +optimisticConfig: ?OptimisticResponseConfig,
77
- +publishQueue: PublishQueue,
78
92
  +reactFlightPayloadDeserializer?: ?ReactFlightPayloadDeserializer,
79
93
  +reactFlightServerErrorHandler?: ?ReactFlightServerErrorHandler,
80
94
  +scheduler?: ?TaskScheduler,
95
+ +shouldProcessClientComponents?: ?boolean,
81
96
  +sink: Sink<GraphQLResponse>,
82
97
  +source: RelayObservable<GraphQLResponse>,
83
- +store: Store,
98
+ +treatMissingFieldsAsNull: boolean,
84
99
  +updater?: ?SelectorStoreUpdater,
85
- +isClientPayload?: boolean,
100
+ +log: LogFunction,
86
101
  |};
87
102
 
88
103
  export type ActiveState = 'active' | 'inactive';
@@ -120,21 +135,25 @@ function execute(config: ExecuteConfig): Executor {
120
135
  * dependencies, etc.
121
136
  */
122
137
  class Executor {
138
+ _actorIdentifier: ActorIdentifier;
123
139
  _getDataID: GetDataID;
124
140
  _treatMissingFieldsAsNull: boolean;
125
141
  _incrementalPayloadsPending: boolean;
126
142
  _incrementalResults: Map<Label, Map<PathKey, IncrementalResults>>;
143
+ _log: LogFunction;
144
+ _executeId: number;
127
145
  _nextSubscriptionId: number;
128
146
  _operation: OperationDescriptor;
129
147
  _operationExecutions: Map<string, ActiveState>;
130
148
  _operationLoader: ?OperationLoader;
131
- _operationTracker: ?OperationTracker;
149
+ _operationTracker: OperationTracker;
132
150
  _operationUpdateEpochs: Map<string, number>;
133
151
  _optimisticUpdates: null | Array<OptimisticUpdate>;
134
152
  _pendingModulePayloadsCount: number;
135
- _publishQueue: PublishQueue;
153
+ +_getPublishQueue: (actorIdentifier: ActorIdentifier) => PublishQueue;
136
154
  _reactFlightPayloadDeserializer: ?ReactFlightPayloadDeserializer;
137
155
  _reactFlightServerErrorHandler: ?ReactFlightServerErrorHandler;
156
+ _shouldProcessClientComponents: ?boolean;
138
157
  _scheduler: ?TaskScheduler;
139
158
  _sink: Sink<GraphQLResponse>;
140
159
  _source: Map<
@@ -142,34 +161,44 @@ class Executor {
142
161
  {|+record: Record, +fieldPayloads: Array<HandleFieldPayload>|},
143
162
  >;
144
163
  _state: 'started' | 'loading_incremental' | 'loading_final' | 'completed';
145
- _store: Store;
164
+ +_getStore: (actorIdentifier: ActorIdentifier) => Store;
146
165
  _subscriptions: Map<number, Subscription>;
147
166
  _updater: ?SelectorStoreUpdater;
148
- _retainDisposable: ?Disposable;
167
+ _asyncStoreUpdateDisposable: ?Disposable;
168
+ _completeFns: Array<() => void>;
169
+ +_retainDisposables: Map<ActorIdentifier, Disposable>;
149
170
  +_isClientPayload: boolean;
171
+ +_isSubscriptionOperation: boolean;
172
+ +_seenActors: Set<ActorIdentifier>;
150
173
 
151
174
  constructor({
175
+ actorIdentifier,
176
+ getDataID,
177
+ getPublishQueue,
178
+ getStore,
179
+ isClientPayload,
152
180
  operation,
153
181
  operationExecutions,
154
182
  operationLoader,
183
+ operationTracker,
155
184
  optimisticConfig,
156
- publishQueue,
185
+ reactFlightPayloadDeserializer,
186
+ reactFlightServerErrorHandler,
157
187
  scheduler,
188
+ shouldProcessClientComponents,
158
189
  sink,
159
190
  source,
160
- store,
161
- updater,
162
- operationTracker,
163
191
  treatMissingFieldsAsNull,
164
- getDataID,
165
- isClientPayload,
166
- reactFlightPayloadDeserializer,
167
- reactFlightServerErrorHandler,
192
+ updater,
193
+ log,
168
194
  }: ExecuteConfig): void {
195
+ this._actorIdentifier = actorIdentifier;
169
196
  this._getDataID = getDataID;
170
197
  this._treatMissingFieldsAsNull = treatMissingFieldsAsNull;
171
198
  this._incrementalPayloadsPending = false;
172
199
  this._incrementalResults = new Map();
200
+ this._log = log;
201
+ this._executeId = generateID();
173
202
  this._nextSubscriptionId = 0;
174
203
  this._operation = operation;
175
204
  this._operationExecutions = operationExecutions;
@@ -178,17 +207,23 @@ class Executor {
178
207
  this._operationUpdateEpochs = new Map();
179
208
  this._optimisticUpdates = null;
180
209
  this._pendingModulePayloadsCount = 0;
181
- this._publishQueue = publishQueue;
210
+ this._getPublishQueue = getPublishQueue;
182
211
  this._scheduler = scheduler;
183
212
  this._sink = sink;
184
213
  this._source = new Map();
185
214
  this._state = 'started';
186
- this._store = store;
215
+ this._getStore = getStore;
187
216
  this._subscriptions = new Map();
188
217
  this._updater = updater;
189
218
  this._isClientPayload = isClientPayload === true;
190
219
  this._reactFlightPayloadDeserializer = reactFlightPayloadDeserializer;
191
220
  this._reactFlightServerErrorHandler = reactFlightServerErrorHandler;
221
+ this._isSubscriptionOperation =
222
+ this._operation.request.node.params.operationKind === 'subscription';
223
+ this._shouldProcessClientComponents = shouldProcessClientComponents;
224
+ this._retainDisposables = new Map();
225
+ this._seenActors = new Set();
226
+ this._completeFns = [];
192
227
 
193
228
  const id = this._nextSubscriptionId++;
194
229
  source.subscribe({
@@ -201,7 +236,16 @@ class Executor {
201
236
  sink.error(error);
202
237
  }
203
238
  },
204
- start: subscription => this._start(id, subscription),
239
+ start: subscription => {
240
+ this._start(id, subscription);
241
+ this._log({
242
+ name: 'execute.start',
243
+ executeId: this._executeId,
244
+ params: this._operation.request.node.params,
245
+ variables: this._operation.request.variables,
246
+ cacheConfig: this._operation.request.cacheConfig ?? {},
247
+ });
248
+ },
205
249
  });
206
250
 
207
251
  if (optimisticConfig != null) {
@@ -231,18 +275,41 @@ class Executor {
231
275
  if (optimisticUpdates !== null) {
232
276
  this._optimisticUpdates = null;
233
277
  optimisticUpdates.forEach(update =>
234
- this._publishQueue.revertUpdate(update),
278
+ this._getPublishQueueAndSaveActor().revertUpdate(update),
235
279
  );
236
- this._publishQueue.run();
280
+ // OK: run revert on cancel
281
+ this._runPublishQueue();
237
282
  }
238
283
  this._incrementalResults.clear();
239
- this._completeOperationTracker();
240
- if (this._retainDisposable) {
241
- this._retainDisposable.dispose();
242
- this._retainDisposable = null;
284
+ if (this._asyncStoreUpdateDisposable != null) {
285
+ this._asyncStoreUpdateDisposable.dispose();
286
+ this._asyncStoreUpdateDisposable = null;
243
287
  }
288
+ this._completeFns = [];
289
+ this._completeOperationTracker();
290
+ this._disposeRetainedData();
244
291
  }
245
292
 
293
+ _deserializeReactFlightPayloadWithLogging = (
294
+ tree: ReactFlightServerTree,
295
+ ): ReactFlightClientResponse => {
296
+ const reactFlightPayloadDeserializer = this._reactFlightPayloadDeserializer;
297
+ invariant(
298
+ typeof reactFlightPayloadDeserializer === 'function',
299
+ 'OperationExecutor: Expected reactFlightPayloadDeserializer to be available when calling _deserializeReactFlightPayloadWithLogging.',
300
+ );
301
+ const [duration, result] = withDuration(() => {
302
+ return reactFlightPayloadDeserializer(tree);
303
+ });
304
+ this._log({
305
+ name: 'execute.flight.payload_deserialize',
306
+ executeId: this._executeId,
307
+ operationName: this._operation.request.node.params.name,
308
+ duration,
309
+ });
310
+ return result;
311
+ };
312
+
246
313
  _updateActiveState(): void {
247
314
  let activeState;
248
315
  switch (this._state) {
@@ -265,7 +332,7 @@ class Executor {
265
332
  }
266
333
  default:
267
334
  (this._state: empty);
268
- invariant(false, 'RelayModernQueryExecutor: invalid executor state.');
335
+ invariant(false, 'OperationExecutor: invalid executor state.');
269
336
  }
270
337
  this._operationExecutions.set(
271
338
  this._operation.request.identifier,
@@ -302,12 +369,21 @@ class Executor {
302
369
  if (this._subscriptions.size === 0) {
303
370
  this.cancel();
304
371
  this._sink.complete();
372
+ this._log({
373
+ name: 'execute.complete',
374
+ executeId: this._executeId,
375
+ });
305
376
  }
306
377
  }
307
378
 
308
379
  _error(error: Error): void {
309
380
  this.cancel();
310
381
  this._sink.error(error);
382
+ this._log({
383
+ name: 'execute.error',
384
+ executeId: this._executeId,
385
+ error,
386
+ });
311
387
  }
312
388
 
313
389
  _start(id: number, subscription: Subscription): void {
@@ -318,8 +394,16 @@ class Executor {
318
394
  // Handle a raw GraphQL response.
319
395
  _next(_id: number, response: GraphQLResponse): void {
320
396
  this._schedule(() => {
321
- this._handleNext(response);
322
- this._maybeCompleteSubscriptionOperationTracking();
397
+ const [duration] = withDuration(() => {
398
+ this._handleNext(response);
399
+ this._maybeCompleteSubscriptionOperationTracking();
400
+ });
401
+ this._log({
402
+ name: 'execute.next',
403
+ executeId: this._executeId,
404
+ response,
405
+ duration,
406
+ });
323
407
  });
324
408
  }
325
409
 
@@ -383,7 +467,10 @@ class Executor {
383
467
  responsePart => responsePart.extensions?.isOptimistic === true,
384
468
  )
385
469
  ) {
386
- invariant(false, 'Optimistic responses cannot be batched.');
470
+ invariant(
471
+ false,
472
+ 'OperationExecutor: Optimistic responses cannot be batched.',
473
+ );
387
474
  }
388
475
  return false;
389
476
  }
@@ -392,7 +479,7 @@ class Executor {
392
479
  if (isOptimistic && this._state !== 'started') {
393
480
  invariant(
394
481
  false,
395
- 'RelayModernQueryExecutor: optimistic payload received after server payload.',
482
+ 'OperationExecutor: optimistic payload received after server payload.',
396
483
  );
397
484
  }
398
485
  if (isOptimistic) {
@@ -411,6 +498,7 @@ class Executor {
411
498
  if (this._state === 'completed') {
412
499
  return;
413
500
  }
501
+ this._seenActors.clear();
414
502
 
415
503
  const responses = Array.isArray(response) ? response : [response];
416
504
  const responsesWithData = this._handleErrorResponse(responses);
@@ -438,6 +526,7 @@ class Executor {
438
526
  nonIncrementalResponses,
439
527
  incrementalResponses,
440
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,30 +535,66 @@ 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
565
  const payloadFollowups = this._processIncrementalResponses(
464
566
  incrementalResponses,
465
567
  );
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);
568
+
471
569
  this._processPayloadFollowups(payloadFollowups);
472
570
  }
571
+ if (this._isSubscriptionOperation) {
572
+ // We attach the id to allow the `requestSubscription` to read from the store using
573
+ // the current id in its `onNext` callback
574
+ if (responsesWithData[0].extensions == null) {
575
+ // $FlowFixMe[cannot-write]
576
+ responsesWithData[0].extensions = {
577
+ __relay_subscription_root_id: this._operation.fragment.dataID,
578
+ };
579
+ } else {
580
+ responsesWithData[0].extensions.__relay_subscription_root_id = 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
 
@@ -480,7 +605,7 @@ class Executor {
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) {
@@ -493,10 +618,15 @@ class Executor {
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,95 @@ 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
665
  optimisticUpdates: Array<OptimisticUpdate>,
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 = this._processOptimisticModuleImport(
680
+ operation,
681
+ followupPayload,
682
+ );
683
+ optimisticUpdates.push(...moduleImportOptimisticUpdates);
684
+ }
685
+ break;
686
+ case 'ActorPayload':
687
+ warning(
688
+ false,
689
+ 'OperationExecutor: Unexpected optimistic ActorPayload. These updates are not supported.',
690
+ );
691
+ break;
692
+ default:
693
+ (followupPayload: empty);
694
+ invariant(
695
+ false,
696
+ 'OperationExecutor: Unexpected followup kind `%s`. when processing optimistic updates.',
697
+ followupPayload.kind,
698
+ );
556
699
  }
557
700
  }
558
701
  }
559
702
  }
560
703
 
561
- _normalizeModuleImport(
562
- moduleImportPayload: ModuleImportPayload,
563
- operation: NormalizationSelectableNode,
704
+ /**
705
+ * Normalize Data for @module payload, and actor-specific payload
706
+ */
707
+ _normalizeFollowupPayload(
708
+ followupPayload: FollowupPayload,
709
+ normalizationNode: NormalizationSelectableNode,
564
710
  ) {
711
+ let variables;
712
+ if (
713
+ normalizationNode.kind === 'SplitOperation' &&
714
+ followupPayload.kind === 'ModuleImportPayload'
715
+ ) {
716
+ variables = getLocalVariables(
717
+ followupPayload.variables,
718
+ normalizationNode.argumentDefinitions,
719
+ followupPayload.args,
720
+ );
721
+ } else {
722
+ variables = followupPayload.variables;
723
+ }
565
724
  const selector = createNormalizationSelector(
566
- operation,
567
- moduleImportPayload.dataID,
568
- moduleImportPayload.variables,
725
+ normalizationNode,
726
+ followupPayload.dataID,
727
+ variables,
569
728
  );
570
729
  return normalizeResponse(
571
- {data: moduleImportPayload.data},
730
+ {data: followupPayload.data},
572
731
  selector,
573
- moduleImportPayload.typeName,
732
+ followupPayload.typeName,
574
733
  {
734
+ actorIdentifier: this._actorIdentifier,
575
735
  getDataID: this._getDataID,
576
- path: moduleImportPayload.path,
577
- reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
736
+ path: followupPayload.path,
737
+ reactFlightPayloadDeserializer:
738
+ this._reactFlightPayloadDeserializer != null
739
+ ? this._deserializeReactFlightPayloadWithLogging
740
+ : null,
578
741
  reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
579
742
  treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
743
+ shouldProcessClientComponents: this._shouldProcessClientComponents,
580
744
  },
581
745
  );
582
746
  }
@@ -587,7 +751,7 @@ class Executor {
587
751
  ): $ReadOnlyArray<OptimisticUpdate> {
588
752
  const operation = getOperation(normalizationRootNode);
589
753
  const optimisticUpdates = [];
590
- const modulePayload = this._normalizeModuleImport(
754
+ const modulePayload = this._normalizeFollowupPayload(
591
755
  moduleImportPayload,
592
756
  operation,
593
757
  );
@@ -602,10 +766,9 @@ class Executor {
602
766
  }
603
767
 
604
768
  _processAsyncOptimisticModuleImport(
605
- operationLoader: OperationLoader,
606
769
  moduleImportPayload: ModuleImportPayload,
607
770
  ): void {
608
- operationLoader
771
+ this._expectOperationLoader()
609
772
  .load(moduleImportPayload.operationReference)
610
773
  .then(operation => {
611
774
  if (operation == null || this._state !== 'started') {
@@ -616,27 +779,30 @@ class Executor {
616
779
  moduleImportPayload,
617
780
  );
618
781
  moduleImportOptimisticUpdates.forEach(update =>
619
- this._publishQueue.applyUpdate(update),
782
+ this._getPublishQueueAndSaveActor().applyUpdate(update),
620
783
  );
621
784
  if (this._optimisticUpdates == null) {
622
785
  warning(
623
786
  false,
624
- 'RelayModernQueryExecutor: Unexpected ModuleImport optimistic ' +
787
+ 'OperationExecutor: Unexpected ModuleImport optimistic ' +
625
788
  'update in operation %s.' +
626
789
  this._operation.request.node.params.name,
627
790
  );
628
791
  } else {
629
792
  this._optimisticUpdates.push(...moduleImportOptimisticUpdates);
630
- this._publishQueue.run();
793
+ // OK: always have to run() after an module import resolves async
794
+ this._runPublishQueue();
631
795
  }
632
796
  });
633
797
  }
634
798
 
635
- _processResponses(responses: $ReadOnlyArray<GraphQLResponseWithData>) {
799
+ _processResponses(
800
+ responses: $ReadOnlyArray<GraphQLResponseWithData>,
801
+ ): $ReadOnlyArray<RelayResponsePayload> {
636
802
  if (this._optimisticUpdates !== null) {
637
- this._optimisticUpdates.forEach(update =>
638
- this._publishQueue.revertUpdate(update),
639
- );
803
+ this._optimisticUpdates.forEach(update => {
804
+ this._getPublishQueueAndSaveActor().revertUpdate(update);
805
+ });
640
806
  this._optimisticUpdates = null;
641
807
  }
642
808
 
@@ -649,18 +815,24 @@ class Executor {
649
815
  this._operation.root,
650
816
  ROOT_TYPE,
651
817
  {
818
+ actorIdentifier: this._actorIdentifier,
652
819
  getDataID: this._getDataID,
653
820
  path: [],
654
- reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
821
+ reactFlightPayloadDeserializer:
822
+ this._reactFlightPayloadDeserializer != null
823
+ ? this._deserializeReactFlightPayloadWithLogging
824
+ : null,
655
825
  reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
656
826
  treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
827
+ shouldProcessClientComponents: this._shouldProcessClientComponents,
657
828
  },
658
829
  );
659
- this._publishQueue.commitPayload(
830
+ this._getPublishQueueAndSaveActor().commitPayload(
660
831
  this._operation,
661
832
  relayPayload,
662
833
  this._updater,
663
834
  );
835
+
664
836
  return relayPayload;
665
837
  });
666
838
  }
@@ -676,30 +848,30 @@ class Executor {
676
848
  return;
677
849
  }
678
850
  payloads.forEach(payload => {
679
- const {incrementalPlaceholders, moduleImportPayloads, isFinal} = payload;
851
+ const {incrementalPlaceholders, followupPayloads, isFinal} = payload;
680
852
  this._state = isFinal ? 'loading_final' : 'loading_incremental';
681
853
  this._updateActiveState();
682
854
  if (isFinal) {
683
855
  this._incrementalPayloadsPending = false;
684
856
  }
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
- );
857
+ if (followupPayloads && followupPayloads.length !== 0) {
858
+ followupPayloads.forEach(followupPayload => {
859
+ const prevActorIdentifier = this._actorIdentifier;
860
+ this._actorIdentifier =
861
+ followupPayload.actorIdentifier ?? this._actorIdentifier;
862
+ this._processFollowupPayload(followupPayload);
863
+ this._actorIdentifier = prevActorIdentifier;
697
864
  });
698
865
  }
866
+
699
867
  if (incrementalPlaceholders && incrementalPlaceholders.length !== 0) {
700
868
  this._incrementalPayloadsPending = this._state !== 'loading_final';
701
869
  incrementalPlaceholders.forEach(incrementalPlaceholder => {
870
+ const prevActorIdentifier = this._actorIdentifier;
871
+ this._actorIdentifier =
872
+ incrementalPlaceholder.actorIdentifier ?? this._actorIdentifier;
702
873
  this._processIncrementalPlaceholder(payload, incrementalPlaceholder);
874
+ this._actorIdentifier = prevActorIdentifier;
703
875
  });
704
876
 
705
877
  if (this._isClientPayload || this._state === 'loading_final') {
@@ -731,8 +903,6 @@ class Executor {
731
903
  }
732
904
  });
733
905
  if (relayPayloads.length > 0) {
734
- const updatedOwners = this._publishQueue.run();
735
- this._updateOperationTracker(updatedOwners);
736
906
  this._processPayloadFollowups(relayPayloads);
737
907
  }
738
908
  }
@@ -741,9 +911,7 @@ class Executor {
741
911
  }
742
912
 
743
913
  _maybeCompleteSubscriptionOperationTracking() {
744
- const isSubscriptionOperation =
745
- this._operation.request.node.params.operationKind === 'subscription';
746
- if (!isSubscriptionOperation) {
914
+ if (!this._isSubscriptionOperation) {
747
915
  return;
748
916
  }
749
917
  if (
@@ -761,73 +929,154 @@ class Executor {
761
929
  * defer, stream, etc); these are handled by calling
762
930
  * `_processPayloadFollowups()`.
763
931
  */
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
- );
932
+ _processFollowupPayload(followupPayload: FollowupPayload): void {
933
+ switch (followupPayload.kind) {
934
+ case 'ModuleImportPayload':
935
+ const operationLoader = this._expectOperationLoader();
936
+ const node = operationLoader.get(followupPayload.operationReference);
937
+ if (node != null) {
938
+ // If the operation module is available synchronously, normalize the
939
+ // data synchronously.
940
+ this._processFollowupPayloadWithNormalizationNode(
941
+ followupPayload,
942
+ getOperation(node),
943
+ );
944
+ } else {
945
+ // Otherwise load the operation module and schedule a task to normalize
946
+ // the data when the module is available.
947
+ const id = this._nextSubscriptionId++;
948
+ this._pendingModulePayloadsCount++;
949
+
950
+ const decrementPendingCount = () => {
951
+ this._pendingModulePayloadsCount--;
952
+ this._maybeCompleteSubscriptionOperationTracking();
953
+ };
954
+
955
+ // Observable.from(operationLoader.load()) wouldn't catch synchronous
956
+ // errors thrown by the load function, which is user-defined. Guard
957
+ // against that with Observable.from(new Promise(<work>)).
958
+ const networkObservable = RelayObservable.from(
959
+ new Promise((resolve, reject) => {
960
+ operationLoader
961
+ .load(followupPayload.operationReference)
962
+ .then(resolve, reject);
963
+ }),
964
+ );
965
+ RelayObservable.create(sink => {
966
+ let cancellationToken;
967
+ const subscription = networkObservable.subscribe({
968
+ next: (loadedNode: ?NormalizationRootNode) => {
969
+ if (loadedNode != null) {
970
+ const publishModuleImportPayload = () => {
971
+ try {
972
+ const operation = getOperation(loadedNode);
973
+ const batchAsyncModuleUpdatesFN =
974
+ RelayFeatureFlags.BATCH_ASYNC_MODULE_UPDATES_FN;
975
+ const shouldScheduleAsyncStoreUpdate =
976
+ batchAsyncModuleUpdatesFN != null &&
977
+ this._pendingModulePayloadsCount > 1;
978
+ const [duration] = withDuration(() => {
979
+ this._handleFollowupPayload(followupPayload, operation);
980
+ // OK: always have to run after an async module import resolves
981
+ if (shouldScheduleAsyncStoreUpdate) {
982
+ this._scheduleAsyncStoreUpdate(
983
+ // $FlowFixMe[incompatible-call] `shouldScheduleAsyncStoreUpdate` check should cover `null` case
984
+ batchAsyncModuleUpdatesFN,
985
+ sink.complete,
986
+ );
987
+ } else {
988
+ const updatedOwners = this._runPublishQueue();
989
+ this._updateOperationTracker(updatedOwners);
990
+ }
991
+ });
992
+ this._log({
993
+ name: 'execute.async.module',
994
+ executeId: this._executeId,
995
+ operationName: operation.name,
996
+ duration,
997
+ });
998
+ if (!shouldScheduleAsyncStoreUpdate) {
999
+ sink.complete();
1000
+ }
1001
+ } catch (error) {
1002
+ sink.error(error);
1003
+ }
1004
+ };
1005
+ const scheduler = this._scheduler;
1006
+ if (scheduler == null) {
1007
+ publishModuleImportPayload();
1008
+ } else {
1009
+ cancellationToken = scheduler.schedule(
1010
+ publishModuleImportPayload,
1011
+ );
1012
+ }
1013
+ } else {
1014
+ sink.complete();
1015
+ }
1016
+ },
1017
+ error: sink.error,
803
1018
  });
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
- });
1019
+ return () => {
1020
+ subscription.unsubscribe();
1021
+ if (this._scheduler != null && cancellationToken != null) {
1022
+ this._scheduler.cancel(cancellationToken);
1023
+ }
1024
+ };
1025
+ }).subscribe({
1026
+ complete: () => {
1027
+ this._complete(id);
1028
+ decrementPendingCount();
1029
+ },
1030
+ error: error => {
1031
+ this._error(error);
1032
+ decrementPendingCount();
1033
+ },
1034
+ start: subscription => this._start(id, subscription),
1035
+ });
1036
+ }
1037
+ break;
1038
+ case 'ActorPayload':
1039
+ this._processFollowupPayloadWithNormalizationNode(
1040
+ followupPayload,
1041
+ followupPayload.node,
1042
+ );
1043
+ break;
1044
+ default:
1045
+ (followupPayload: empty);
1046
+ invariant(
1047
+ false,
1048
+ 'OperationExecutor: Unexpected followup kind `%s`.',
1049
+ followupPayload.kind,
1050
+ );
817
1051
  }
818
1052
  }
819
1053
 
820
- _handleModuleImportPayload(
821
- moduleImportPayload: ModuleImportPayload,
822
- operation: NormalizationSplitOperation | NormalizationOperation,
1054
+ _processFollowupPayloadWithNormalizationNode(
1055
+ followupPayload: FollowupPayload,
1056
+ normalizationNode:
1057
+ | NormalizationSplitOperation
1058
+ | NormalizationOperation
1059
+ | NormalizationLinkedField,
1060
+ ) {
1061
+ this._handleFollowupPayload(followupPayload, normalizationNode);
1062
+ this._maybeCompleteSubscriptionOperationTracking();
1063
+ }
1064
+
1065
+ _handleFollowupPayload(
1066
+ followupPayload: FollowupPayload,
1067
+ normalizationNode:
1068
+ | NormalizationSplitOperation
1069
+ | NormalizationOperation
1070
+ | NormalizationLinkedField,
823
1071
  ): void {
824
- const relayPayload = this._normalizeModuleImport(
825
- moduleImportPayload,
826
- operation,
1072
+ const relayPayload = this._normalizeFollowupPayload(
1073
+ followupPayload,
1074
+ normalizationNode,
1075
+ );
1076
+ this._getPublishQueueAndSaveActor().commitPayload(
1077
+ this._operation,
1078
+ relayPayload,
827
1079
  );
828
- this._publishQueue.commitPayload(this._operation, relayPayload);
829
- const updatedOwners = this._publishQueue.run();
830
- this._updateOperationTracker(updatedOwners);
831
1080
  this._processPayloadFollowups([relayPayload]);
832
1081
  }
833
1082
 
@@ -873,7 +1122,7 @@ class Executor {
873
1122
  (placeholder: empty);
874
1123
  invariant(
875
1124
  false,
876
- 'Unsupported incremental placeholder kind `%s`.',
1125
+ 'OperationExecutor: Unsupported incremental placeholder kind `%s`.',
877
1126
  placeholder.kind,
878
1127
  );
879
1128
  }
@@ -897,7 +1146,7 @@ class Executor {
897
1146
  // exist.
898
1147
  invariant(
899
1148
  parentRecord != null,
900
- 'RelayModernEnvironment: Expected record `%s` to exist.',
1149
+ 'OperationExecutor: Expected record `%s` to exist.',
901
1150
  parentID,
902
1151
  );
903
1152
  let nextParentRecord;
@@ -926,14 +1175,13 @@ class Executor {
926
1175
  record: nextParentRecord,
927
1176
  fieldPayloads: nextParentPayloads,
928
1177
  });
1178
+
929
1179
  // If there were any queued responses, process them now that placeholders
930
1180
  // are in place
931
1181
  if (pendingResponses != null) {
932
1182
  const payloadFollowups = this._processIncrementalResponses(
933
1183
  pendingResponses,
934
1184
  );
935
- const updatedOwners = this._publishQueue.run();
936
- this._updateOperationTracker(updatedOwners);
937
1185
  this._processPayloadFollowups(payloadFollowups);
938
1186
  }
939
1187
  }
@@ -969,7 +1217,7 @@ class Executor {
969
1217
  const placeholder = resultForPath.placeholder;
970
1218
  invariant(
971
1219
  placeholder.kind === 'defer',
972
- 'RelayModernEnvironment: Expected data for path `%s` for label `%s` ' +
1220
+ 'OperationExecutor: Expected data for path `%s` for label `%s` ' +
973
1221
  'to be data for @defer, was `@%s`.',
974
1222
  pathKey,
975
1223
  label,
@@ -999,7 +1247,7 @@ class Executor {
999
1247
  const placeholder = resultForPath.placeholder;
1000
1248
  invariant(
1001
1249
  placeholder.kind === 'stream',
1002
- 'RelayModernEnvironment: Expected data for path `%s` for label `%s` ' +
1250
+ 'OperationExecutor: Expected data for path `%s` for label `%s` ' +
1003
1251
  'to be data for @stream, was `@%s`.',
1004
1252
  pathKey,
1005
1253
  label,
@@ -1020,26 +1268,37 @@ class Executor {
1020
1268
  response: GraphQLResponseWithData,
1021
1269
  ): RelayResponsePayload {
1022
1270
  const {dataID: parentID} = placeholder.selector;
1271
+ const prevActorIdentifier = this._actorIdentifier;
1272
+ this._actorIdentifier =
1273
+ placeholder.actorIdentifier ?? this._actorIdentifier;
1023
1274
  const relayPayload = normalizeResponse(
1024
1275
  response,
1025
1276
  placeholder.selector,
1026
1277
  placeholder.typeName,
1027
1278
  {
1279
+ actorIdentifier: this._actorIdentifier,
1028
1280
  getDataID: this._getDataID,
1029
1281
  path: placeholder.path,
1030
- reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
1282
+ reactFlightPayloadDeserializer:
1283
+ this._reactFlightPayloadDeserializer != null
1284
+ ? this._deserializeReactFlightPayloadWithLogging
1285
+ : null,
1031
1286
  reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
1032
1287
  treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
1288
+ shouldProcessClientComponents: this._shouldProcessClientComponents,
1033
1289
  },
1034
1290
  );
1035
- this._publishQueue.commitPayload(this._operation, relayPayload);
1291
+ this._getPublishQueueAndSaveActor().commitPayload(
1292
+ this._operation,
1293
+ relayPayload,
1294
+ );
1036
1295
 
1037
1296
  // Load the version of the parent record from which this incremental data
1038
1297
  // was derived
1039
1298
  const parentEntry = this._source.get(parentID);
1040
1299
  invariant(
1041
1300
  parentEntry != null,
1042
- 'RelayModernEnvironment: Expected the parent record `%s` for @defer ' +
1301
+ 'OperationExecutor: Expected the parent record `%s` for @defer ' +
1043
1302
  'data to exist.',
1044
1303
  parentID,
1045
1304
  );
@@ -1049,15 +1308,17 @@ class Executor {
1049
1308
  errors: null,
1050
1309
  fieldPayloads,
1051
1310
  incrementalPlaceholders: null,
1052
- moduleImportPayloads: null,
1311
+ followupPayloads: null,
1053
1312
  source: RelayRecordSource.create(),
1054
1313
  isFinal: response.extensions?.is_final === true,
1055
1314
  };
1056
- this._publishQueue.commitPayload(
1315
+ this._getPublishQueueAndSaveActor().commitPayload(
1057
1316
  this._operation,
1058
1317
  handleFieldsRelayPayload,
1059
1318
  );
1060
1319
  }
1320
+
1321
+ this._actorIdentifier = prevActorIdentifier;
1061
1322
  return relayPayload;
1062
1323
  }
1063
1324
 
@@ -1070,12 +1331,14 @@ class Executor {
1070
1331
  placeholder: StreamPlaceholder,
1071
1332
  response: GraphQLResponseWithData,
1072
1333
  ): RelayResponsePayload {
1073
- const {parentID, node, variables} = placeholder;
1334
+ const {parentID, node, variables, actorIdentifier} = placeholder;
1335
+ const prevActorIdentifier = this._actorIdentifier;
1336
+ this._actorIdentifier = actorIdentifier ?? this._actorIdentifier;
1074
1337
  // Find the LinkedField where @stream was applied
1075
1338
  const field = node.selections[0];
1076
1339
  invariant(
1077
1340
  field != null && field.kind === 'LinkedField' && field.plural === true,
1078
- 'RelayModernEnvironment: Expected @stream to be used on a plural field.',
1341
+ 'OperationExecutor: Expected @stream to be used on a plural field.',
1079
1342
  );
1080
1343
  const {
1081
1344
  fieldPayloads,
@@ -1095,34 +1358,38 @@ class Executor {
1095
1358
  // Publish the new item and update the parent record to set
1096
1359
  // field[index] = item *if* the parent record hasn't been concurrently
1097
1360
  // 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
- });
1361
+ this._getPublishQueueAndSaveActor().commitPayload(
1362
+ this._operation,
1363
+ relayPayload,
1364
+ store => {
1365
+ const currentParentRecord = store.get(parentID);
1366
+ if (currentParentRecord == null) {
1367
+ // parent has since been deleted, stream data is stale
1368
+ return;
1369
+ }
1370
+ const currentItems = currentParentRecord.getLinkedRecords(storageKey);
1371
+ if (currentItems == null) {
1372
+ // field has since been deleted, stream data is stale
1373
+ return;
1374
+ }
1375
+ if (
1376
+ currentItems.length !== prevIDs.length ||
1377
+ currentItems.some(
1378
+ (currentItem, index) =>
1379
+ prevIDs[index] !== (currentItem && currentItem.getDataID()),
1380
+ )
1381
+ ) {
1382
+ // field has been modified by something other than this query,
1383
+ // stream data is stale
1384
+ return;
1385
+ }
1386
+ // parent.field has not been concurrently modified:
1387
+ // update `parent.field[index] = item`
1388
+ const nextItems = [...currentItems];
1389
+ nextItems[itemIndex] = store.get(itemID);
1390
+ currentParentRecord.setLinkedRecords(nextItems, storageKey);
1391
+ },
1392
+ );
1126
1393
 
1127
1394
  // Now that the parent record has been updated to include the new item,
1128
1395
  // also update any handle fields that are derived from the parent record.
@@ -1131,15 +1398,17 @@ class Executor {
1131
1398
  errors: null,
1132
1399
  fieldPayloads,
1133
1400
  incrementalPlaceholders: null,
1134
- moduleImportPayloads: null,
1401
+ followupPayloads: null,
1135
1402
  source: RelayRecordSource.create(),
1136
1403
  isFinal: false,
1137
1404
  };
1138
- this._publishQueue.commitPayload(
1405
+ this._getPublishQueueAndSaveActor().commitPayload(
1139
1406
  this._operation,
1140
1407
  handleFieldsRelayPayload,
1141
1408
  );
1142
1409
  }
1410
+
1411
+ this._actorIdentifier = prevActorIdentifier;
1143
1412
  return relayPayload;
1144
1413
  }
1145
1414
 
@@ -1161,7 +1430,7 @@ class Executor {
1161
1430
  const {data} = response;
1162
1431
  invariant(
1163
1432
  typeof data === 'object',
1164
- 'RelayModernEnvironment: Expected the GraphQL @stream payload `data` ' +
1433
+ 'OperationExecutor: Expected the GraphQL @stream payload `data` ' +
1165
1434
  'value to be an object.',
1166
1435
  );
1167
1436
  const responseKey = field.alias ?? field.name;
@@ -1172,7 +1441,7 @@ class Executor {
1172
1441
  const parentEntry = this._source.get(parentID);
1173
1442
  invariant(
1174
1443
  parentEntry != null,
1175
- 'RelayModernEnvironment: Expected the parent record `%s` for @stream ' +
1444
+ 'OperationExecutor: Expected the parent record `%s` for @stream ' +
1176
1445
  'data to exist.',
1177
1446
  parentID,
1178
1447
  );
@@ -1187,7 +1456,7 @@ class Executor {
1187
1456
  );
1188
1457
  invariant(
1189
1458
  prevIDs != null,
1190
- 'RelayModernEnvironment: Expected record `%s` to have fetched field ' +
1459
+ 'OperationExecutor: Expected record `%s` to have fetched field ' +
1191
1460
  '`%s` with @stream.',
1192
1461
  parentID,
1193
1462
  field.name,
@@ -1198,7 +1467,7 @@ class Executor {
1198
1467
  const itemIndex = parseInt(finalPathEntry, 10);
1199
1468
  invariant(
1200
1469
  itemIndex === finalPathEntry && itemIndex >= 0,
1201
- 'RelayModernEnvironment: Expected path for @stream to end in a ' +
1470
+ 'OperationExecutor: Expected path for @stream to end in a ' +
1202
1471
  'positive integer index, got `%s`',
1203
1472
  finalPathEntry,
1204
1473
  );
@@ -1206,7 +1475,7 @@ class Executor {
1206
1475
  const typeName = field.concreteType ?? data[TYPENAME_KEY];
1207
1476
  invariant(
1208
1477
  typeof typeName === 'string',
1209
- 'RelayModernEnvironment: Expected @stream field `%s` to have a ' +
1478
+ 'OperationExecutor: Expected @stream field `%s` to have a ' +
1210
1479
  '__typename.',
1211
1480
  field.name,
1212
1481
  );
@@ -1221,7 +1490,7 @@ class Executor {
1221
1490
  generateClientID(parentID, storageKey, itemIndex);
1222
1491
  invariant(
1223
1492
  typeof itemID === 'string',
1224
- 'RelayModernEnvironment: Expected id of elements of field `%s` to ' +
1493
+ 'OperationExecutor: Expected id of elements of field `%s` to ' +
1225
1494
  'be strings.',
1226
1495
  storageKey,
1227
1496
  );
@@ -1241,11 +1510,16 @@ class Executor {
1241
1510
  fieldPayloads,
1242
1511
  });
1243
1512
  const relayPayload = normalizeResponse(response, selector, typeName, {
1513
+ actorIdentifier: this._actorIdentifier,
1244
1514
  getDataID: this._getDataID,
1245
1515
  path: [...normalizationPath, responseKey, String(itemIndex)],
1246
- reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
1516
+ reactFlightPayloadDeserializer:
1517
+ this._reactFlightPayloadDeserializer != null
1518
+ ? this._deserializeReactFlightPayloadWithLogging
1519
+ : null,
1247
1520
  reactFlightServerErrorHandler: this._reactFlightServerErrorHandler,
1248
1521
  treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
1522
+ shouldProcessClientComponents: this._shouldProcessClientComponents,
1249
1523
  });
1250
1524
  return {
1251
1525
  fieldPayloads,
@@ -1257,14 +1531,29 @@ class Executor {
1257
1531
  };
1258
1532
  }
1259
1533
 
1534
+ _scheduleAsyncStoreUpdate(
1535
+ scheduleFn: (() => void) => Disposable,
1536
+ completeFn: () => void,
1537
+ ): void {
1538
+ this._completeFns.push(completeFn);
1539
+ if (this._asyncStoreUpdateDisposable != null) {
1540
+ return;
1541
+ }
1542
+ this._asyncStoreUpdateDisposable = scheduleFn(() => {
1543
+ this._asyncStoreUpdateDisposable = null;
1544
+ const updatedOwners = this._runPublishQueue();
1545
+ this._updateOperationTracker(updatedOwners);
1546
+ for (const complete of this._completeFns) {
1547
+ complete();
1548
+ }
1549
+ this._completeFns = [];
1550
+ });
1551
+ }
1552
+
1260
1553
  _updateOperationTracker(
1261
1554
  updatedOwners: ?$ReadOnlyArray<RequestDescriptor>,
1262
1555
  ): void {
1263
- if (
1264
- this._operationTracker != null &&
1265
- updatedOwners != null &&
1266
- updatedOwners.length > 0
1267
- ) {
1556
+ if (updatedOwners != null && updatedOwners.length > 0) {
1268
1557
  this._operationTracker.update(
1269
1558
  this._operation.request,
1270
1559
  new Set(updatedOwners),
@@ -1273,10 +1562,60 @@ class Executor {
1273
1562
  }
1274
1563
 
1275
1564
  _completeOperationTracker() {
1276
- if (this._operationTracker != null) {
1277
- this._operationTracker.complete(this._operation.request);
1565
+ this._operationTracker.complete(this._operation.request);
1566
+ }
1567
+
1568
+ _getPublishQueueAndSaveActor(): PublishQueue {
1569
+ this._seenActors.add(this._actorIdentifier);
1570
+ return this._getPublishQueue(this._actorIdentifier);
1571
+ }
1572
+
1573
+ _getActorsToVisit(): $ReadOnlySet<ActorIdentifier> {
1574
+ if (this._seenActors.size === 0) {
1575
+ return new Set([this._actorIdentifier]);
1576
+ } else {
1577
+ return this._seenActors;
1278
1578
  }
1279
1579
  }
1580
+
1581
+ _runPublishQueue(
1582
+ operation?: OperationDescriptor,
1583
+ ): $ReadOnlyArray<RequestDescriptor> {
1584
+ const updatedOwners = new Set();
1585
+ for (const actorIdentifier of this._getActorsToVisit()) {
1586
+ const owners = this._getPublishQueue(actorIdentifier).run(operation);
1587
+ owners.forEach(owner => updatedOwners.add(owner));
1588
+ }
1589
+ return Array.from(updatedOwners);
1590
+ }
1591
+
1592
+ _retainData() {
1593
+ for (const actorIdentifier of this._getActorsToVisit()) {
1594
+ if (!this._retainDisposables.has(actorIdentifier)) {
1595
+ this._retainDisposables.set(
1596
+ actorIdentifier,
1597
+ this._getStore(actorIdentifier).retain(this._operation),
1598
+ );
1599
+ }
1600
+ }
1601
+ }
1602
+
1603
+ _disposeRetainedData() {
1604
+ for (const disposable of this._retainDisposables.values()) {
1605
+ disposable.dispose();
1606
+ }
1607
+ this._retainDisposables.clear();
1608
+ }
1609
+
1610
+ _expectOperationLoader(): OperationLoader {
1611
+ const operationLoader = this._operationLoader;
1612
+ invariant(
1613
+ operationLoader,
1614
+ 'OperationExecutor: Expected an operationLoader to be ' +
1615
+ 'configured when using `@match`.',
1616
+ );
1617
+ return operationLoader;
1618
+ }
1280
1619
  }
1281
1620
 
1282
1621
  function partitionGraphQLResponses(
@@ -1293,7 +1632,7 @@ function partitionGraphQLResponses(
1293
1632
  if (label == null || path == null) {
1294
1633
  invariant(
1295
1634
  false,
1296
- 'RelayModernQueryExecutor: invalid incremental payload, expected ' +
1635
+ 'OperationExecutor: invalid incremental payload, expected ' +
1297
1636
  '`path` and `label` to either both be null/undefined, or ' +
1298
1637
  '`path` to be an `Array<string | number>` and `label` to be a ' +
1299
1638
  '`string`.',
@@ -1345,11 +1684,13 @@ function validateOptimisticResponsePayload(
1345
1684
  if (incrementalPlaceholders != null && incrementalPlaceholders.length !== 0) {
1346
1685
  invariant(
1347
1686
  false,
1348
- 'RelayModernQueryExecutor: optimistic responses cannot be returned ' +
1687
+ 'OperationExecutor: optimistic responses cannot be returned ' +
1349
1688
  'for operations that use incremental data delivery (@defer, ' +
1350
1689
  '@stream, and @stream_connection).',
1351
1690
  );
1352
1691
  }
1353
1692
  }
1354
1693
 
1355
- module.exports = {execute};
1694
+ module.exports = {
1695
+ execute,
1696
+ };