relay-runtime 10.0.1 → 10.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/handlers/RelayDefaultHandlerProvider.js.flow +6 -0
  2. package/handlers/connection/MutationHandlers.js.flow +114 -3
  3. package/index.js +1 -1
  4. package/index.js.flow +16 -1
  5. package/lib/handlers/RelayDefaultHandlerProvider.js +9 -0
  6. package/lib/handlers/connection/MutationHandlers.js +138 -12
  7. package/lib/index.js +7 -0
  8. package/lib/mutations/RelayDeclarativeMutationConfig.js +2 -2
  9. package/lib/mutations/commitMutation.js +1 -4
  10. package/lib/mutations/validateMutation.js +27 -7
  11. package/lib/network/RelayQueryResponseCache.js +2 -2
  12. package/lib/query/GraphQLTag.js +2 -1
  13. package/lib/query/fetchQuery.js +2 -3
  14. package/lib/query/fetchQueryInternal.js +2 -3
  15. package/lib/store/DataChecker.js +82 -5
  16. package/lib/store/RelayModernEnvironment.js +18 -6
  17. package/lib/store/RelayModernFragmentSpecResolver.js +10 -1
  18. package/lib/store/RelayModernOperationDescriptor.js +6 -5
  19. package/lib/store/RelayModernQueryExecutor.js +44 -23
  20. package/lib/store/RelayModernStore.js +25 -14
  21. package/lib/store/RelayOperationTracker.js +2 -2
  22. package/lib/store/RelayPublishQueue.js +1 -1
  23. package/lib/store/RelayReader.js +196 -33
  24. package/lib/store/RelayRecordSourceMapImpl.js +2 -2
  25. package/lib/store/RelayReferenceMarker.js +89 -5
  26. package/lib/store/RelayResponseNormalizer.js +119 -19
  27. package/lib/store/RelayStoreReactFlightUtils.js +47 -0
  28. package/lib/store/defaultRequiredFieldLogger.js +18 -0
  29. package/lib/store/normalizeRelayPayload.js +1 -1
  30. package/lib/subscription/requestSubscription.js +2 -3
  31. package/lib/util/NormalizationNode.js +1 -5
  32. package/lib/util/RelayConcreteNode.js +2 -0
  33. package/lib/util/RelayFeatureFlags.js +5 -2
  34. package/lib/util/getFragmentIdentifier.js +12 -3
  35. package/lib/util/getOperation.js +33 -0
  36. package/lib/util/isEmptyObject.js +25 -0
  37. package/lib/util/recycleNodesInto.js +4 -1
  38. package/lib/util/reportMissingRequiredFields.js +48 -0
  39. package/mutations/commitMutation.js.flow +1 -2
  40. package/mutations/validateMutation.js.flow +34 -5
  41. package/network/RelayNetworkTypes.js.flow +22 -0
  42. package/package.json +2 -2
  43. package/query/GraphQLTag.js.flow +3 -1
  44. package/query/fetchQuery.js.flow +2 -2
  45. package/query/fetchQueryInternal.js.flow +0 -5
  46. package/relay-runtime.js +2 -2
  47. package/relay-runtime.min.js +2 -2
  48. package/store/DataChecker.js.flow +68 -2
  49. package/store/RelayModernEnvironment.js.flow +29 -9
  50. package/store/RelayModernFragmentSpecResolver.js.flow +13 -1
  51. package/store/RelayModernOperationDescriptor.js.flow +5 -1
  52. package/store/RelayModernQueryExecutor.js.flow +47 -23
  53. package/store/RelayModernStore.js.flow +31 -15
  54. package/store/RelayPublishQueue.js.flow +1 -1
  55. package/store/RelayReader.js.flow +180 -15
  56. package/store/RelayReferenceMarker.js.flow +72 -5
  57. package/store/RelayResponseNormalizer.js.flow +130 -19
  58. package/store/RelayStoreReactFlightUtils.js.flow +64 -0
  59. package/store/RelayStoreTypes.js.flow +90 -31
  60. package/store/defaultRequiredFieldLogger.js.flow +23 -0
  61. package/subscription/requestSubscription.js.flow +5 -2
  62. package/util/NormalizationNode.js.flow +17 -2
  63. package/util/ReaderNode.js.flow +20 -1
  64. package/util/RelayConcreteNode.js.flow +6 -0
  65. package/util/RelayFeatureFlags.js.flow +8 -1
  66. package/util/getFragmentIdentifier.js.flow +33 -9
  67. package/util/getOperation.js.flow +40 -0
  68. package/util/isEmptyObject.js.flow +25 -0
  69. package/util/recycleNodesInto.js.flow +11 -0
  70. package/util/reportMissingRequiredFields.js.flow +51 -0
@@ -18,10 +18,12 @@ const RelayFeatureFlags = require('../util/RelayFeatureFlags');
18
18
  const RelayModernRecord = require('./RelayModernRecord');
19
19
  const RelayRecordSourceMutator = require('../mutations/RelayRecordSourceMutator');
20
20
  const RelayRecordSourceProxy = require('../mutations/RelayRecordSourceProxy');
21
+ const RelayStoreReactFlightUtils = require('./RelayStoreReactFlightUtils');
21
22
  const RelayStoreUtils = require('./RelayStoreUtils');
22
23
 
23
24
  const cloneRelayHandleSourceField = require('./cloneRelayHandleSourceField');
24
25
  const cloneRelayScalarHandleSourceField = require('./cloneRelayScalarHandleSourceField');
26
+ const getOperation = require('../util/getOperation');
25
27
  const invariant = require('invariant');
26
28
 
27
29
  const {isClientID} = require('./ClientID');
@@ -30,6 +32,7 @@ const {generateTypeID} = require('./TypeID');
30
32
 
31
33
  import type {
32
34
  NormalizationField,
35
+ NormalizationFlightField,
33
36
  NormalizationLinkedField,
34
37
  NormalizationModuleImport,
35
38
  NormalizationNode,
@@ -43,6 +46,7 @@ import type {
43
46
  MutableRecordSource,
44
47
  NormalizationSelector,
45
48
  OperationLoader,
49
+ ReactFlightReachableQuery,
46
50
  Record,
47
51
  RecordSource,
48
52
  } from './RelayStoreTypes';
@@ -56,6 +60,7 @@ const {
56
60
  CONDITION,
57
61
  CLIENT_EXTENSION,
58
62
  DEFER,
63
+ FLIGHT_FIELD,
59
64
  FRAGMENT_SPREAD,
60
65
  INLINE_FRAGMENT,
61
66
  LINKED_FIELD,
@@ -67,6 +72,7 @@ const {
67
72
  TYPE_DISCRIMINATOR,
68
73
  } = RelayConcreteNode;
69
74
  const {
75
+ ROOT_ID,
70
76
  getModuleOperationKey,
71
77
  getStorageKey,
72
78
  getArgumentValues,
@@ -375,6 +381,7 @@ class DataChecker {
375
381
  case STREAM:
376
382
  this._traverseSelections(selection.selections, dataID);
377
383
  break;
384
+ // $FlowFixMe[incompatible-type]
378
385
  case FRAGMENT_SPREAD:
379
386
  invariant(
380
387
  false,
@@ -409,6 +416,13 @@ class DataChecker {
409
416
  } // else: if it does or doesn't implement, we don't need to check or skip anything else
410
417
  }
411
418
  break;
419
+ case FLIGHT_FIELD:
420
+ if (RelayFeatureFlags.ENABLE_REACT_FLIGHT_COMPONENT_FIELD) {
421
+ this._checkFlightField(selection, dataID);
422
+ } else {
423
+ throw new Error('Flight fields are not yet supported.');
424
+ }
425
+ break;
412
426
  default:
413
427
  (selection: empty);
414
428
  invariant(
@@ -437,8 +451,9 @@ class DataChecker {
437
451
  }
438
452
  return;
439
453
  }
440
- const operation = operationLoader.get(operationReference);
441
- if (operation != null) {
454
+ const normalizationRootNode = operationLoader.get(operationReference);
455
+ if (normalizationRootNode != null) {
456
+ const operation = getOperation(normalizationRootNode);
442
457
  this._traverse(operation, dataID);
443
458
  } else {
444
459
  // If the fragment is not available, we assume that the data cannot have been
@@ -495,6 +510,57 @@ class DataChecker {
495
510
  });
496
511
  }
497
512
  }
513
+
514
+ _checkFlightField(field: NormalizationFlightField, dataID: DataID): void {
515
+ const storageKey = getStorageKey(field, this._variables);
516
+ const linkedID = this._mutator.getLinkedRecordID(dataID, storageKey);
517
+
518
+ if (linkedID == null) {
519
+ if (linkedID === undefined) {
520
+ this._handleMissing();
521
+ return;
522
+ }
523
+ return;
524
+ }
525
+
526
+ const tree = this._mutator.getValue(
527
+ linkedID,
528
+ RelayStoreReactFlightUtils.REACT_FLIGHT_TREE_STORAGE_KEY,
529
+ );
530
+ const reachableQueries = this._mutator.getValue(
531
+ linkedID,
532
+ RelayStoreReactFlightUtils.REACT_FLIGHT_QUERIES_STORAGE_KEY,
533
+ );
534
+
535
+ if (tree == null || !Array.isArray(reachableQueries)) {
536
+ this._handleMissing();
537
+ return;
538
+ }
539
+
540
+ const operationLoader = this._operationLoader;
541
+ invariant(
542
+ operationLoader !== null,
543
+ 'DataChecker: Expected an operationLoader to be configured when using ' +
544
+ 'React Flight.',
545
+ );
546
+ // In Flight, the variables that are in scope for reachable queries aren't
547
+ // the same as what's in scope for the outer query.
548
+ const prevVariables = this._variables;
549
+ // $FlowFixMe[incompatible-cast]
550
+ for (const query of (reachableQueries: Array<ReactFlightReachableQuery>)) {
551
+ this._variables = query.variables;
552
+ const normalizationRootNode = operationLoader.get(query.module);
553
+ if (normalizationRootNode != null) {
554
+ const operation = getOperation(normalizationRootNode);
555
+ this._traverseSelections(operation.selections, ROOT_ID);
556
+ } else {
557
+ // If the fragment is not available, we assume that the data cannot have
558
+ // been processed yet and must therefore be missing.
559
+ this._handleMissing();
560
+ }
561
+ }
562
+ this._variables = prevVariables;
563
+ }
498
564
  }
499
565
 
500
566
  module.exports = {
@@ -22,6 +22,7 @@ const RelayPublishQueue = require('./RelayPublishQueue');
22
22
  const RelayRecordSource = require('./RelayRecordSource');
23
23
 
24
24
  const defaultGetDataID = require('./defaultGetDataID');
25
+ const defaultRequiredFieldLogger = require('./defaultRequiredFieldLogger');
25
26
  const generateID = require('../util/generateID');
26
27
  const invariant = require('invariant');
27
28
 
@@ -48,6 +49,7 @@ import type {
48
49
  IEnvironment,
49
50
  LogFunction,
50
51
  MissingFieldHandler,
52
+ RequiredFieldLogger,
51
53
  OperationAvailability,
52
54
  OperationDescriptor,
53
55
  OperationLoader,
@@ -55,6 +57,7 @@ import type {
55
57
  OptimisticResponseConfig,
56
58
  OptimisticUpdateFunction,
57
59
  PublishQueue,
60
+ ReactFlightPayloadDeserializer,
58
61
  SelectorStoreUpdater,
59
62
  SingularReaderSelector,
60
63
  Snapshot,
@@ -68,6 +71,7 @@ export type EnvironmentConfig = {|
68
71
  +treatMissingFieldsAsNull?: boolean,
69
72
  +log?: ?LogFunction,
70
73
  +operationLoader?: ?OperationLoader,
74
+ +reactFlightPayloadDeserializer?: ?ReactFlightPayloadDeserializer,
71
75
  +network: INetwork,
72
76
  +scheduler?: ?TaskScheduler,
73
77
  +store: Store,
@@ -82,12 +86,14 @@ export type EnvironmentConfig = {|
82
86
  +UNSTABLE_defaultRenderPolicy?: ?RenderPolicy,
83
87
  +options?: mixed,
84
88
  +isServer?: boolean,
89
+ +requiredFieldLogger?: ?RequiredFieldLogger,
85
90
  |};
86
91
 
87
92
  class RelayModernEnvironment implements IEnvironment {
88
93
  __log: LogFunction;
89
94
  +_defaultRenderPolicy: RenderPolicy;
90
95
  _operationLoader: ?OperationLoader;
96
+ _reactFlightPayloadDeserializer: ?ReactFlightPayloadDeserializer;
91
97
  _network: INetwork;
92
98
  _publishQueue: PublishQueue;
93
99
  _scheduler: ?TaskScheduler;
@@ -100,6 +106,7 @@ class RelayModernEnvironment implements IEnvironment {
100
106
  _operationExecutions: Map<string, ActiveState>;
101
107
  +options: mixed;
102
108
  +_isServer: boolean;
109
+ requiredFieldLogger: RequiredFieldLogger;
103
110
 
104
111
  constructor(config: EnvironmentConfig) {
105
112
  this.configName = config.configName;
@@ -108,6 +115,8 @@ class RelayModernEnvironment implements IEnvironment {
108
115
  : RelayDefaultHandlerProvider;
109
116
  this._treatMissingFieldsAsNull = config.treatMissingFieldsAsNull === true;
110
117
  const operationLoader = config.operationLoader;
118
+ const reactFlightPayloadDeserializer =
119
+ config.reactFlightPayloadDeserializer;
111
120
  if (__DEV__) {
112
121
  if (operationLoader != null) {
113
122
  invariant(
@@ -119,8 +128,18 @@ class RelayModernEnvironment implements IEnvironment {
119
128
  operationLoader,
120
129
  );
121
130
  }
131
+ if (reactFlightPayloadDeserializer != null) {
132
+ invariant(
133
+ typeof reactFlightPayloadDeserializer === 'function',
134
+ 'RelayModernEnvironment: Expected `reactFlightPayloadDeserializer` ' +
135
+ ' to be a function, got `%s`.',
136
+ reactFlightPayloadDeserializer,
137
+ );
138
+ }
122
139
  }
123
140
  this.__log = config.log ?? emptyFunction;
141
+ this.requiredFieldLogger =
142
+ config.requiredFieldLogger ?? defaultRequiredFieldLogger;
124
143
  this._defaultRenderPolicy =
125
144
  config.UNSTABLE_defaultRenderPolicy ??
126
145
  RelayFeatureFlags.ENABLE_PARTIAL_RENDERING_DEFAULT === true
@@ -162,6 +181,7 @@ class RelayModernEnvironment implements IEnvironment {
162
181
  this._missingFieldHandlers = config.missingFieldHandlers;
163
182
  this._operationTracker =
164
183
  config.operationTracker ?? new RelayOperationTracker();
184
+ this._reactFlightPayloadDeserializer = reactFlightPayloadDeserializer;
165
185
  }
166
186
 
167
187
  getStore(): Store {
@@ -226,6 +246,7 @@ class RelayModernEnvironment implements IEnvironment {
226
246
  operationLoader: this._operationLoader,
227
247
  optimisticConfig,
228
248
  publishQueue: this._publishQueue,
249
+ reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
229
250
  scheduler: this._scheduler,
230
251
  sink,
231
252
  source,
@@ -263,6 +284,7 @@ class RelayModernEnvironment implements IEnvironment {
263
284
  operationLoader: this._operationLoader,
264
285
  optimisticConfig: null,
265
286
  publishQueue: this._publishQueue,
287
+ reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
266
288
  scheduler: this._scheduler,
267
289
  sink,
268
290
  source: RelayObservable.from({
@@ -339,14 +361,11 @@ class RelayModernEnvironment implements IEnvironment {
339
361
  */
340
362
  execute({
341
363
  operation,
342
- cacheConfig,
343
364
  updater,
344
- }: {
365
+ }: {|
345
366
  operation: OperationDescriptor,
346
- cacheConfig?: ?CacheConfig,
347
367
  updater?: ?SelectorStoreUpdater,
348
- ...
349
- }): RelayObservable<GraphQLResponse> {
368
+ |}): RelayObservable<GraphQLResponse> {
350
369
  const [logObserver, logRequestInfo] = this.__createLogObserver(
351
370
  operation.request.node.params,
352
371
  operation.request.variables,
@@ -356,7 +375,7 @@ class RelayModernEnvironment implements IEnvironment {
356
375
  .execute(
357
376
  operation.request.node.params,
358
377
  operation.request.variables,
359
- cacheConfig || {},
378
+ operation.request.cacheConfig || {},
360
379
  null,
361
380
  logRequestInfo,
362
381
  )
@@ -367,6 +386,7 @@ class RelayModernEnvironment implements IEnvironment {
367
386
  operationLoader: this._operationLoader,
368
387
  optimisticConfig: null,
369
388
  publishQueue: this._publishQueue,
389
+ reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
370
390
  scheduler: this._scheduler,
371
391
  sink,
372
392
  source,
@@ -391,14 +411,12 @@ class RelayModernEnvironment implements IEnvironment {
391
411
  * environment.executeMutation({...}).subscribe({...}).
392
412
  */
393
413
  executeMutation({
394
- cacheConfig,
395
414
  operation,
396
415
  optimisticResponse,
397
416
  optimisticUpdater,
398
417
  updater,
399
418
  uploadables,
400
419
  }: {|
401
- cacheConfig?: ?CacheConfig,
402
420
  operation: OperationDescriptor,
403
421
  optimisticUpdater?: ?SelectorStoreUpdater,
404
422
  optimisticResponse?: ?Object,
@@ -423,7 +441,7 @@ class RelayModernEnvironment implements IEnvironment {
423
441
  operation.request.node.params,
424
442
  operation.request.variables,
425
443
  {
426
- ...cacheConfig,
444
+ ...operation.request.cacheConfig,
427
445
  force: true,
428
446
  },
429
447
  uploadables,
@@ -436,6 +454,7 @@ class RelayModernEnvironment implements IEnvironment {
436
454
  operationLoader: this._operationLoader,
437
455
  optimisticConfig,
438
456
  publishQueue: this._publishQueue,
457
+ reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
439
458
  scheduler: this._scheduler,
440
459
  sink,
441
460
  source,
@@ -473,6 +492,7 @@ class RelayModernEnvironment implements IEnvironment {
473
492
  operationTracker: this._operationTracker,
474
493
  optimisticConfig: null,
475
494
  publishQueue: this._publishQueue,
495
+ reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
476
496
  scheduler: this._scheduler,
477
497
  sink,
478
498
  source,
@@ -17,6 +17,7 @@ const RelayFeatureFlags = require('../util/RelayFeatureFlags');
17
17
  const areEqual = require('areEqual');
18
18
  const invariant = require('invariant');
19
19
  const isScalarAndEqual = require('../util/isScalarAndEqual');
20
+ const reportMissingRequiredFields = require('../util/reportMissingRequiredFields');
20
21
  const warning = require('warning');
21
22
 
22
23
  const {getPromiseForActiveRequest} = require('../query/fetchQueryInternal');
@@ -34,6 +35,7 @@ import type {
34
35
  FragmentMap,
35
36
  FragmentSpecResolver,
36
37
  FragmentSpecResults,
38
+ MissingRequiredFields,
37
39
  PluralReaderSelector,
38
40
  RelayContext,
39
41
  SelectorData,
@@ -216,6 +218,7 @@ class SelectorResolver {
216
218
  _data: ?SelectorData;
217
219
  _environment: IEnvironment;
218
220
  _isMissingData: boolean;
221
+ _missingRequiredFields: ?MissingRequiredFields;
219
222
  _selector: SingularReaderSelector;
220
223
  _subscription: ?Disposable;
221
224
 
@@ -228,6 +231,7 @@ class SelectorResolver {
228
231
  this._callback = callback;
229
232
  this._data = snapshot.data;
230
233
  this._isMissingData = snapshot.isMissingData;
234
+ this._missingRequiredFields = snapshot.missingRequiredFields;
231
235
  this._environment = environment;
232
236
  this._selector = selector;
233
237
  this._subscription = environment.subscribe(snapshot, this._onChange);
@@ -282,6 +286,12 @@ class SelectorResolver {
282
286
  throw promise;
283
287
  }
284
288
  }
289
+ if (this._missingRequiredFields != null) {
290
+ reportMissingRequiredFields(
291
+ this._environment,
292
+ this._missingRequiredFields,
293
+ );
294
+ }
285
295
  return this._data;
286
296
  }
287
297
 
@@ -296,6 +306,7 @@ class SelectorResolver {
296
306
  const snapshot = this._environment.lookup(selector);
297
307
  this._data = snapshot.data;
298
308
  this._isMissingData = snapshot.isMissingData;
309
+ this._missingRequiredFields = snapshot.missingRequiredFields;
299
310
  this._selector = selector;
300
311
  this._subscription = this._environment.subscribe(snapshot, this._onChange);
301
312
  }
@@ -314,7 +325,7 @@ class SelectorResolver {
314
325
  // NOTE: We manually create the request descriptor here instead of
315
326
  // calling createOperationDescriptor() because we want to set a
316
327
  // descriptor with *unaltered* variables as the fragment owner.
317
- // This is a hack that allows us to preserve exisiting (broken)
328
+ // This is a hack that allows us to preserve existing (broken)
318
329
  // behavior of RelayModern containers while using fragment ownership
319
330
  // to propagate variables instead of Context.
320
331
  // For more details, see the summary of D13999308
@@ -331,6 +342,7 @@ class SelectorResolver {
331
342
  _onChange = (snapshot: Snapshot): void => {
332
343
  this._data = snapshot.data;
333
344
  this._isMissingData = snapshot.isMissingData;
345
+ this._missingRequiredFields = snapshot.missingRequiredFields;
334
346
  this._callback();
335
347
  };
336
348
  }
@@ -23,7 +23,7 @@ const {
23
23
  const {ROOT_ID} = require('./RelayStoreUtils');
24
24
 
25
25
  import type {ConcreteRequest} from '../util/RelayConcreteNode';
26
- import type {DataID, Variables} from '../util/RelayRuntimeTypes';
26
+ import type {CacheConfig, DataID, Variables} from '../util/RelayRuntimeTypes';
27
27
  import type {OperationDescriptor, RequestDescriptor} from './RelayStoreTypes';
28
28
 
29
29
  /**
@@ -35,6 +35,7 @@ import type {OperationDescriptor, RequestDescriptor} from './RelayStoreTypes';
35
35
  function createOperationDescriptor(
36
36
  request: ConcreteRequest,
37
37
  variables: Variables,
38
+ cacheConfig?: ?CacheConfig,
38
39
  dataID?: DataID = ROOT_ID,
39
40
  ): OperationDescriptor {
40
41
  const operation = request.operation;
@@ -42,6 +43,7 @@ function createOperationDescriptor(
42
43
  const requestDescriptor = createRequestDescriptor(
43
44
  request,
44
45
  operationVariables,
46
+ cacheConfig,
45
47
  );
46
48
  const operationDescriptor = {
47
49
  fragment: createReaderSelector(
@@ -68,11 +70,13 @@ function createOperationDescriptor(
68
70
  function createRequestDescriptor(
69
71
  request: ConcreteRequest,
70
72
  variables: Variables,
73
+ cacheConfig?: ?CacheConfig,
71
74
  ): RequestDescriptor {
72
75
  const requestDescriptor = {
73
76
  identifier: getRequestIdentifier(request.params, variables),
74
77
  node: request,
75
78
  variables: variables,
79
+ cacheConfig: cacheConfig,
76
80
  };
77
81
  if (__DEV__) {
78
82
  deepFreeze(variables);
@@ -19,6 +19,7 @@ const RelayObservable = require('../network/RelayObservable');
19
19
  const RelayRecordSource = require('./RelayRecordSource');
20
20
  const RelayResponseNormalizer = require('./RelayResponseNormalizer');
21
21
 
22
+ const getOperation = require('../util/getOperation');
22
23
  const invariant = require('invariant');
23
24
  const stableCopy = require('../util/stableCopy');
24
25
  const warning = require('warning');
@@ -46,6 +47,7 @@ import type {
46
47
  OptimisticResponseConfig,
47
48
  OptimisticUpdate,
48
49
  PublishQueue,
50
+ ReactFlightPayloadDeserializer,
49
51
  Record,
50
52
  RelayResponsePayload,
51
53
  SelectorStoreUpdater,
@@ -54,10 +56,12 @@ import type {
54
56
  } from '../store/RelayStoreTypes';
55
57
  import type {
56
58
  NormalizationLinkedField,
57
- NormalizationSplitOperation,
59
+ NormalizationOperation,
60
+ NormalizationRootNode,
58
61
  NormalizationSelectableNode,
62
+ NormalizationSplitOperation,
59
63
  } from '../util/NormalizationNode';
60
- import type {DataID, Variables} from '../util/RelayRuntimeTypes';
64
+ import type {DataID, Variables, Disposable} from '../util/RelayRuntimeTypes';
61
65
  import type {GetDataID} from './RelayResponseNormalizer';
62
66
  import type {NormalizationOptions} from './RelayResponseNormalizer';
63
67
 
@@ -70,6 +74,7 @@ export type ExecuteConfig = {|
70
74
  +operationTracker?: ?OperationTracker,
71
75
  +optimisticConfig: ?OptimisticResponseConfig,
72
76
  +publishQueue: PublishQueue,
77
+ +reactFlightPayloadDeserializer?: ?ReactFlightPayloadDeserializer,
73
78
  +scheduler?: ?TaskScheduler,
74
79
  +sink: Sink<GraphQLResponse>,
75
80
  +source: RelayObservable<GraphQLResponse>,
@@ -126,6 +131,7 @@ class Executor {
126
131
  _optimisticUpdates: null | Array<OptimisticUpdate>;
127
132
  _pendingModulePayloadsCount: number;
128
133
  _publishQueue: PublishQueue;
134
+ _reactFlightPayloadDeserializer: ?ReactFlightPayloadDeserializer;
129
135
  _scheduler: ?TaskScheduler;
130
136
  _sink: Sink<GraphQLResponse>;
131
137
  _source: Map<
@@ -136,6 +142,7 @@ class Executor {
136
142
  _store: Store;
137
143
  _subscriptions: Map<number, Subscription>;
138
144
  _updater: ?SelectorStoreUpdater;
145
+ _retainDisposable: ?Disposable;
139
146
  +_isClientPayload: boolean;
140
147
 
141
148
  constructor({
@@ -153,6 +160,7 @@ class Executor {
153
160
  treatMissingFieldsAsNull,
154
161
  getDataID,
155
162
  isClientPayload,
163
+ reactFlightPayloadDeserializer,
156
164
  }: ExecuteConfig): void {
157
165
  this._getDataID = getDataID;
158
166
  this._treatMissingFieldsAsNull = treatMissingFieldsAsNull;
@@ -175,6 +183,7 @@ class Executor {
175
183
  this._subscriptions = new Map();
176
184
  this._updater = updater;
177
185
  this._isClientPayload = isClientPayload === true;
186
+ this._reactFlightPayloadDeserializer = reactFlightPayloadDeserializer;
178
187
 
179
188
  const id = this._nextSubscriptionId++;
180
189
  source.subscribe({
@@ -223,6 +232,10 @@ class Executor {
223
232
  }
224
233
  this._incrementalResults.clear();
225
234
  this._completeOperationTracker();
235
+ if (this._retainDisposable) {
236
+ this._retainDisposable.dispose();
237
+ this._retainDisposable = null;
238
+ }
226
239
  }
227
240
 
228
241
  _updateActiveState(): void {
@@ -423,19 +436,22 @@ class Executor {
423
436
 
424
437
  // In theory this doesn't preserve the ordering of the batch.
425
438
  // The idea is that a batch is always:
426
- // * at-most one non-incremental payload
427
- // * followed zero or more incremental payloads
439
+ // * at most one non-incremental payload
440
+ // * followed by zero or more incremental payloads
428
441
  // The non-incremental payload can appear if the server sends a batch
429
- // w the initial payload followed by some early-to-resolve incremental
442
+ // with the initial payload followed by some early-to-resolve incremental
430
443
  // payloads (although, can that even happen?)
431
444
  if (nonIncrementalResponses.length > 0) {
432
445
  const payloadFollowups = this._processResponses(nonIncrementalResponses);
433
- // Please note, that we're passing `this._operation` to the publish
446
+ // Please note that we're passing `this._operation` to the publish
434
447
  // queue here, which will later passed to the store (via notify)
435
- // to indicate that this is an operation that cause the store to update
448
+ // to indicate that this is an operation that caused the store to update
436
449
  const updatedOwners = this._publishQueue.run(this._operation);
437
450
  this._updateOperationTracker(updatedOwners);
438
451
  this._processPayloadFollowups(payloadFollowups);
452
+ if (this._incrementalPayloadsPending && !this._retainDisposable) {
453
+ this._retainDisposable = this._store.retain(this._operation);
454
+ }
439
455
  }
440
456
 
441
457
  if (incrementalResponses.length > 0) {
@@ -474,6 +490,7 @@ class Executor {
474
490
  {
475
491
  getDataID: this._getDataID,
476
492
  path: [],
493
+ reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
477
494
  treatMissingFieldsAsNull,
478
495
  },
479
496
  );
@@ -525,11 +542,11 @@ class Executor {
525
542
  moduleImportPayload,
526
543
  );
527
544
  } else {
528
- const moduleImportOptimisitcUpdates = this._processOptimisticModuleImport(
545
+ const moduleImportOptimisticUpdates = this._processOptimisticModuleImport(
529
546
  operation,
530
547
  moduleImportPayload,
531
548
  );
532
- optimisticUpdates.push(...moduleImportOptimisitcUpdates);
549
+ optimisticUpdates.push(...moduleImportOptimisticUpdates);
533
550
  }
534
551
  }
535
552
  }
@@ -551,15 +568,17 @@ class Executor {
551
568
  {
552
569
  getDataID: this._getDataID,
553
570
  path: moduleImportPayload.path,
571
+ reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
554
572
  treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
555
573
  },
556
574
  );
557
575
  }
558
576
 
559
577
  _processOptimisticModuleImport(
560
- operation: NormalizationSplitOperation,
578
+ normalizationRootNode: NormalizationRootNode,
561
579
  moduleImportPayload: ModuleImportPayload,
562
580
  ): $ReadOnlyArray<OptimisticUpdate> {
581
+ const operation = getOperation(normalizationRootNode);
563
582
  const optimisticUpdates = [];
564
583
  const modulePayload = this._normalizeModuleImport(
565
584
  moduleImportPayload,
@@ -585,22 +604,22 @@ class Executor {
585
604
  if (operation == null || this._state !== 'started') {
586
605
  return;
587
606
  }
588
- const moduleImportOptimisitcUpdates = this._processOptimisticModuleImport(
607
+ const moduleImportOptimisticUpdates = this._processOptimisticModuleImport(
589
608
  operation,
590
609
  moduleImportPayload,
591
610
  );
592
- moduleImportOptimisitcUpdates.forEach(update =>
611
+ moduleImportOptimisticUpdates.forEach(update =>
593
612
  this._publishQueue.applyUpdate(update),
594
613
  );
595
614
  if (this._optimisticUpdates == null) {
596
615
  warning(
597
616
  false,
598
- 'RelayModernQueryExecutor: Unexpected ModuleImport optimisitc ' +
617
+ 'RelayModernQueryExecutor: Unexpected ModuleImport optimistic ' +
599
618
  'update in operation %s.' +
600
619
  this._operation.request.node.params.name,
601
620
  );
602
621
  } else {
603
- this._optimisticUpdates.push(...moduleImportOptimisitcUpdates);
622
+ this._optimisticUpdates.push(...moduleImportOptimisticUpdates);
604
623
  this._publishQueue.run();
605
624
  }
606
625
  });
@@ -624,8 +643,9 @@ class Executor {
624
643
  ROOT_TYPE,
625
644
  {
626
645
  getDataID: this._getDataID,
627
- treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
628
646
  path: [],
647
+ reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
648
+ treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
629
649
  },
630
650
  );
631
651
  this._publishQueue.commitPayload(
@@ -737,13 +757,12 @@ class Executor {
737
757
  moduleImportPayload: ModuleImportPayload,
738
758
  operationLoader: OperationLoader,
739
759
  ): void {
740
- const syncOperation = operationLoader.get(
741
- moduleImportPayload.operationReference,
742
- );
743
- if (syncOperation != null) {
760
+ const node = operationLoader.get(moduleImportPayload.operationReference);
761
+ if (node != null) {
762
+ const operation = getOperation(node);
744
763
  // If the operation module is available synchronously, normalize the
745
764
  // data synchronously.
746
- this._handleModuleImportPayload(moduleImportPayload, syncOperation);
765
+ this._handleModuleImportPayload(moduleImportPayload, operation);
747
766
  this._maybeCompleteSubscriptionOperationTracking();
748
767
  } else {
749
768
  // Otherwise load the operation module and schedule a task to normalize
@@ -766,10 +785,13 @@ class Executor {
766
785
  .then(resolve, reject);
767
786
  }),
768
787
  )
769
- .map((operation: ?NormalizationSplitOperation) => {
788
+ .map((operation: ?NormalizationRootNode) => {
770
789
  if (operation != null) {
771
790
  this._schedule(() => {
772
- this._handleModuleImportPayload(moduleImportPayload, operation);
791
+ this._handleModuleImportPayload(
792
+ moduleImportPayload,
793
+ getOperation(operation),
794
+ );
773
795
  });
774
796
  }
775
797
  })
@@ -789,7 +811,7 @@ class Executor {
789
811
 
790
812
  _handleModuleImportPayload(
791
813
  moduleImportPayload: ModuleImportPayload,
792
- operation: NormalizationSplitOperation,
814
+ operation: NormalizationSplitOperation | NormalizationOperation,
793
815
  ): void {
794
816
  const relayPayload = this._normalizeModuleImport(
795
817
  moduleImportPayload,
@@ -997,6 +1019,7 @@ class Executor {
997
1019
  {
998
1020
  getDataID: this._getDataID,
999
1021
  path: placeholder.path,
1022
+ reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
1000
1023
  treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
1001
1024
  },
1002
1025
  );
@@ -1211,6 +1234,7 @@ class Executor {
1211
1234
  const relayPayload = normalizeResponse(response, selector, typeName, {
1212
1235
  getDataID: this._getDataID,
1213
1236
  path: [...normalizationPath, responseKey, String(itemIndex)],
1237
+ reactFlightPayloadDeserializer: this._reactFlightPayloadDeserializer,
1214
1238
  treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
1215
1239
  });
1216
1240
  return {