relay-runtime 10.0.0 → 10.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/handlers/RelayDefaultHandlerProvider.js.flow +6 -0
  2. package/handlers/connection/MutationHandlers.js.flow +121 -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 +147 -14
  7. package/lib/index.js +7 -0
  8. package/lib/mutations/RelayDeclarativeMutationConfig.js +5 -7
  9. package/lib/mutations/commitMutation.js +1 -4
  10. package/lib/mutations/validateMutation.js +28 -12
  11. package/lib/network/RelayQueryResponseCache.js +3 -7
  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 +85 -10
  16. package/lib/store/RelayConcreteVariables.js +2 -6
  17. package/lib/store/RelayModernEnvironment.js +81 -72
  18. package/lib/store/RelayModernFragmentSpecResolver.js +14 -7
  19. package/lib/store/RelayModernOperationDescriptor.js +6 -5
  20. package/lib/store/RelayModernQueryExecutor.js +46 -33
  21. package/lib/store/RelayModernRecord.js +3 -7
  22. package/lib/store/RelayModernStore.js +45 -143
  23. package/lib/store/RelayOperationTracker.js +7 -9
  24. package/lib/store/RelayOptimisticRecordSource.js +2 -6
  25. package/lib/store/RelayPublishQueue.js +1 -1
  26. package/lib/store/RelayReader.js +200 -49
  27. package/lib/store/RelayRecordSourceMapImpl.js +3 -5
  28. package/lib/store/RelayReferenceMarker.js +87 -5
  29. package/lib/store/RelayResponseNormalizer.js +123 -54
  30. package/lib/store/RelayStoreReactFlightUtils.js +47 -0
  31. package/lib/store/RelayStoreSubscriptions.js +162 -0
  32. package/lib/store/RelayStoreSubscriptionsUsingMapByID.js +258 -0
  33. package/lib/store/StoreInspector.js +3 -9
  34. package/lib/store/createRelayContext.js +5 -0
  35. package/lib/store/defaultRequiredFieldLogger.js +18 -0
  36. package/lib/store/normalizeRelayPayload.js +2 -6
  37. package/lib/subscription/requestSubscription.js +2 -3
  38. package/lib/util/NormalizationNode.js +1 -5
  39. package/lib/util/RelayConcreteNode.js +2 -0
  40. package/lib/util/RelayFeatureFlags.js +6 -2
  41. package/lib/util/createPayloadFor3DField.js +2 -7
  42. package/lib/util/getFragmentIdentifier.js +12 -3
  43. package/lib/util/getOperation.js +33 -0
  44. package/lib/util/isEmptyObject.js +25 -0
  45. package/lib/util/recycleNodesInto.js +6 -9
  46. package/lib/util/reportMissingRequiredFields.js +48 -0
  47. package/mutations/commitMutation.js.flow +1 -2
  48. package/mutations/validateMutation.js.flow +34 -5
  49. package/network/RelayNetworkTypes.js.flow +22 -0
  50. package/package.json +2 -2
  51. package/query/GraphQLTag.js.flow +3 -1
  52. package/query/fetchQuery.js.flow +2 -2
  53. package/query/fetchQueryInternal.js.flow +0 -5
  54. package/relay-runtime.js +2 -2
  55. package/relay-runtime.min.js +2 -2
  56. package/store/DataChecker.js.flow +68 -2
  57. package/store/RelayModernEnvironment.js.flow +107 -87
  58. package/store/RelayModernFragmentSpecResolver.js.flow +13 -1
  59. package/store/RelayModernOperationDescriptor.js.flow +5 -1
  60. package/store/RelayModernQueryExecutor.js.flow +47 -23
  61. package/store/RelayModernStore.js.flow +40 -114
  62. package/store/RelayPublishQueue.js.flow +1 -1
  63. package/store/RelayReader.js.flow +184 -27
  64. package/store/RelayReferenceMarker.js.flow +72 -5
  65. package/store/RelayResponseNormalizer.js.flow +140 -50
  66. package/store/RelayStoreReactFlightUtils.js.flow +64 -0
  67. package/store/RelayStoreSubscriptions.js.flow +168 -0
  68. package/store/RelayStoreSubscriptionsUsingMapByID.js.flow +259 -0
  69. package/store/RelayStoreTypes.js.flow +130 -37
  70. package/store/StoreInspector.js.flow +1 -3
  71. package/store/createRelayContext.js.flow +3 -0
  72. package/store/defaultRequiredFieldLogger.js.flow +23 -0
  73. package/subscription/requestSubscription.js.flow +5 -2
  74. package/util/NormalizationNode.js.flow +17 -2
  75. package/util/ReaderNode.js.flow +20 -1
  76. package/util/RelayConcreteNode.js.flow +6 -0
  77. package/util/RelayFeatureFlags.js.flow +10 -1
  78. package/util/getFragmentIdentifier.js.flow +33 -9
  79. package/util/getOperation.js.flow +40 -0
  80. package/util/isEmptyObject.js.flow +25 -0
  81. package/util/recycleNodesInto.js.flow +13 -8
  82. package/util/reportMissingRequiredFields.js.flow +51 -0
@@ -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 {
@@ -13,18 +13,20 @@
13
13
  'use strict';
14
14
 
15
15
  const DataChecker = require('./DataChecker');
16
+ const RelayFeatureFlags = require('../util/RelayFeatureFlags');
16
17
  const RelayModernRecord = require('./RelayModernRecord');
17
18
  const RelayOptimisticRecordSource = require('./RelayOptimisticRecordSource');
18
19
  const RelayProfiler = require('../util/RelayProfiler');
19
20
  const RelayReader = require('./RelayReader');
20
21
  const RelayReferenceMarker = require('./RelayReferenceMarker');
22
+ const RelayStoreReactFlightUtils = require('./RelayStoreReactFlightUtils');
23
+ const RelayStoreSubscriptions = require('./RelayStoreSubscriptions');
24
+ const RelayStoreSubscriptionsUsingMapByID = require('./RelayStoreSubscriptionsUsingMapByID');
21
25
  const RelayStoreUtils = require('./RelayStoreUtils');
22
26
 
23
27
  const deepFreeze = require('../util/deepFreeze');
24
28
  const defaultGetDataID = require('./defaultGetDataID');
25
- const hasOverlappingIDs = require('./hasOverlappingIDs');
26
29
  const invariant = require('invariant');
27
- const recycleNodesInto = require('../util/recycleNodesInto');
28
30
  const resolveImmediate = require('../util/resolveImmediate');
29
31
 
30
32
  const {ROOT_ID, ROOT_TYPE} = require('./RelayStoreUtils');
@@ -45,6 +47,7 @@ import type {
45
47
  SingularReaderSelector,
46
48
  Snapshot,
47
49
  Store,
50
+ StoreSubscriptions,
48
51
  UpdatedRecords,
49
52
  } from './RelayStoreTypes';
50
53
 
@@ -53,14 +56,6 @@ export opaque type InvalidationState = {|
53
56
  invalidations: Map<DataID, ?number>,
54
57
  |};
55
58
 
56
- type Subscription = {
57
- callback: (snapshot: Snapshot) => void,
58
- snapshot: Snapshot,
59
- stale: boolean,
60
- backup: ?Snapshot,
61
- ...
62
- };
63
-
64
59
  type InvalidationSubscription = {|
65
60
  callback: () => void,
66
61
  invalidationState: InvalidationState,
@@ -90,7 +85,7 @@ class RelayModernStore implements Store {
90
85
  _globalInvalidationEpoch: ?number;
91
86
  _invalidationSubscriptions: Set<InvalidationSubscription>;
92
87
  _invalidatedRecordIDs: Set<DataID>;
93
- _log: ?LogFunction;
88
+ __log: ?LogFunction;
94
89
  _queryCacheExpirationTime: ?number;
95
90
  _operationLoader: ?OperationLoader;
96
91
  _optimisticSource: ?MutableRecordSource;
@@ -106,7 +101,7 @@ class RelayModernStore implements Store {
106
101
  |},
107
102
  >;
108
103
  _shouldScheduleGC: boolean;
109
- _subscriptions: Set<Subscription>;
104
+ _storeSubscriptions: StoreSubscriptions;
110
105
  _updatedRecordIDs: UpdatedRecords;
111
106
 
112
107
  constructor(
@@ -141,7 +136,7 @@ class RelayModernStore implements Store {
141
136
  this._globalInvalidationEpoch = null;
142
137
  this._invalidationSubscriptions = new Set();
143
138
  this._invalidatedRecordIDs = new Set();
144
- this._log = options?.log ?? null;
139
+ this.__log = options?.log ?? null;
145
140
  this._queryCacheExpirationTime = options?.queryCacheExpirationTime;
146
141
  this._operationLoader = options?.operationLoader ?? null;
147
142
  this._optimisticSource = null;
@@ -149,7 +144,10 @@ class RelayModernStore implements Store {
149
144
  this._releaseBuffer = [];
150
145
  this._roots = new Map();
151
146
  this._shouldScheduleGC = false;
152
- this._subscriptions = new Set();
147
+ this._storeSubscriptions =
148
+ RelayFeatureFlags.ENABLE_STORE_SUBSCRIPTIONS_REFACTOR === true
149
+ ? new RelayStoreSubscriptionsUsingMapByID()
150
+ : new RelayStoreSubscriptions();
153
151
  this._updatedRecordIDs = {};
154
152
 
155
153
  initializeRecordSource(this._recordSource);
@@ -232,7 +230,7 @@ class RelayModernStore implements Store {
232
230
 
233
231
  if (rootEntryIsStale) {
234
232
  this._roots.delete(id);
235
- this._scheduleGC();
233
+ this.scheduleGC();
236
234
  } else {
237
235
  this._releaseBuffer.push(id);
238
236
 
@@ -242,7 +240,7 @@ class RelayModernStore implements Store {
242
240
  if (this._releaseBuffer.length > this._gcReleaseBufferSize) {
243
241
  const _id = this._releaseBuffer.shift();
244
242
  this._roots.delete(_id);
245
- this._scheduleGC();
243
+ this.scheduleGC();
246
244
  }
247
245
  }
248
246
  }
@@ -285,7 +283,7 @@ class RelayModernStore implements Store {
285
283
  sourceOperation?: OperationDescriptor,
286
284
  invalidateStore?: boolean,
287
285
  ): $ReadOnlyArray<RequestDescriptor> {
288
- const log = this._log;
286
+ const log = this.__log;
289
287
  if (log != null) {
290
288
  log({
291
289
  name: 'store.notify.start',
@@ -302,12 +300,11 @@ class RelayModernStore implements Store {
302
300
 
303
301
  const source = this.getSource();
304
302
  const updatedOwners = [];
305
- this._subscriptions.forEach(subscription => {
306
- const owner = this._updateSubscription(source, subscription);
307
- if (owner != null) {
308
- updatedOwners.push(owner);
309
- }
310
- });
303
+ this._storeSubscriptions.updateSubscriptions(
304
+ source,
305
+ this._updatedRecordIDs,
306
+ updatedOwners,
307
+ );
311
308
  this._invalidationSubscriptions.forEach(subscription => {
312
309
  this._updateInvalidationSubscription(
313
310
  subscription,
@@ -375,7 +372,7 @@ class RelayModernStore implements Store {
375
372
  );
376
373
  // NOTE: log *after* processing the source so that even if a bad log function
377
374
  // mutates the source, it doesn't affect Relay processing of it.
378
- const log = this._log;
375
+ const log = this.__log;
379
376
  if (log != null) {
380
377
  log({
381
378
  name: 'store.publish',
@@ -389,12 +386,7 @@ class RelayModernStore implements Store {
389
386
  snapshot: Snapshot,
390
387
  callback: (snapshot: Snapshot) => void,
391
388
  ): Disposable {
392
- const subscription = {backup: null, callback, snapshot, stale: false};
393
- const dispose = () => {
394
- this._subscriptions.delete(subscription);
395
- };
396
- this._subscriptions.add(subscription);
397
- return {dispose};
389
+ return this._storeSubscriptions.subscribe(snapshot, callback);
398
390
  }
399
391
 
400
392
  holdGC(): Disposable {
@@ -407,7 +399,7 @@ class RelayModernStore implements Store {
407
399
  if (this._gcHoldCounter > 0) {
408
400
  this._gcHoldCounter--;
409
401
  if (this._gcHoldCounter === 0 && this._shouldScheduleGC) {
410
- this._scheduleGC();
402
+ this.scheduleGC();
411
403
  this._shouldScheduleGC = false;
412
404
  }
413
405
  }
@@ -424,42 +416,6 @@ class RelayModernStore implements Store {
424
416
  return this._updatedRecordIDs;
425
417
  }
426
418
 
427
- // Returns the owner (RequestDescriptor) if the subscription was affected by the
428
- // latest update, or null if it was not affected.
429
- _updateSubscription(
430
- source: RecordSource,
431
- subscription: Subscription,
432
- ): ?RequestDescriptor {
433
- const {backup, callback, snapshot, stale} = subscription;
434
- const hasOverlappingUpdates = hasOverlappingIDs(
435
- snapshot.seenRecords,
436
- this._updatedRecordIDs,
437
- );
438
- if (!stale && !hasOverlappingUpdates) {
439
- return;
440
- }
441
- let nextSnapshot: Snapshot =
442
- hasOverlappingUpdates || !backup
443
- ? RelayReader.read(source, snapshot.selector)
444
- : backup;
445
- const nextData = recycleNodesInto(snapshot.data, nextSnapshot.data);
446
- nextSnapshot = ({
447
- data: nextData,
448
- isMissingData: nextSnapshot.isMissingData,
449
- seenRecords: nextSnapshot.seenRecords,
450
- selector: nextSnapshot.selector,
451
- }: Snapshot);
452
- if (__DEV__) {
453
- deepFreeze(nextSnapshot);
454
- }
455
- subscription.snapshot = nextSnapshot;
456
- subscription.stale = false;
457
- if (nextSnapshot.data !== snapshot.data) {
458
- callback(nextSnapshot);
459
- return snapshot.selector.owner;
460
- }
461
- }
462
-
463
419
  lookupInvalidationState(dataIDs: $ReadOnlyArray<DataID>): InvalidationState {
464
420
  const invalidations = new Map();
465
421
  dataIDs.forEach(dataID => {
@@ -533,35 +489,13 @@ class RelayModernStore implements Store {
533
489
  'RelayModernStore: Unexpected call to snapshot() while a previous ' +
534
490
  'snapshot exists.',
535
491
  );
536
- const log = this._log;
492
+ const log = this.__log;
537
493
  if (log != null) {
538
494
  log({
539
495
  name: 'store.snapshot',
540
496
  });
541
497
  }
542
- this._subscriptions.forEach(subscription => {
543
- // Backup occurs after writing a new "final" payload(s) and before (re)applying
544
- // optimistic changes. Each subscription's `snapshot` represents what was *last
545
- // published to the subscriber*, which notably may include previous optimistic
546
- // updates. Therefore a subscription can be in any of the following states:
547
- // - stale=true: This subscription was restored to a different value than
548
- // `snapshot`. That means this subscription has changes relative to its base,
549
- // but its base has changed (we just applied a final payload): recompute
550
- // a backup so that we can later restore to the state the subscription
551
- // should be in.
552
- // - stale=false: This subscription was restored to the same value than
553
- // `snapshot`. That means this subscription does *not* have changes relative
554
- // to its base, so the current `snapshot` is valid to use as a backup.
555
- if (!subscription.stale) {
556
- subscription.backup = subscription.snapshot;
557
- return;
558
- }
559
- const snapshot = subscription.snapshot;
560
- const backup = RelayReader.read(this.getSource(), snapshot.selector);
561
- const nextData = recycleNodesInto(snapshot.data, backup.data);
562
- (backup: $FlowFixMe).data = nextData; // backup owns the snapshot and can safely mutate
563
- subscription.backup = backup;
564
- });
498
+ this._storeSubscriptions.snapshotSubscriptions(this.getSource());
565
499
  if (this._gcRun) {
566
500
  this._gcRun = null;
567
501
  this._shouldScheduleGC = true;
@@ -577,7 +511,7 @@ class RelayModernStore implements Store {
577
511
  'RelayModernStore: Unexpected call to restore(), expected a snapshot ' +
578
512
  'to exist (make sure to call snapshot()).',
579
513
  );
580
- const log = this._log;
514
+ const log = this.__log;
581
515
  if (log != null) {
582
516
  log({
583
517
  name: 'store.restore',
@@ -585,28 +519,12 @@ class RelayModernStore implements Store {
585
519
  }
586
520
  this._optimisticSource = null;
587
521
  if (this._shouldScheduleGC) {
588
- this._scheduleGC();
522
+ this.scheduleGC();
589
523
  }
590
- this._subscriptions.forEach(subscription => {
591
- const backup = subscription.backup;
592
- subscription.backup = null;
593
- if (backup) {
594
- if (backup.data !== subscription.snapshot.data) {
595
- subscription.stale = true;
596
- }
597
- subscription.snapshot = {
598
- data: subscription.snapshot.data,
599
- isMissingData: backup.isMissingData,
600
- seenRecords: backup.seenRecords,
601
- selector: backup.selector,
602
- };
603
- } else {
604
- subscription.stale = true;
605
- }
606
- });
524
+ this._storeSubscriptions.restoreSubscriptions();
607
525
  }
608
526
 
609
- _scheduleGC() {
527
+ scheduleGC() {
610
528
  if (this._gcHoldCounter > 0) {
611
529
  this._shouldScheduleGC = true;
612
530
  return;
@@ -664,7 +582,7 @@ class RelayModernStore implements Store {
664
582
  }
665
583
  }
666
584
 
667
- const log = this._log;
585
+ const log = this.__log;
668
586
  if (log != null) {
669
587
  log({
670
588
  name: 'store.gc',
@@ -701,7 +619,7 @@ function initializeRecordSource(target: MutableRecordSource) {
701
619
  /**
702
620
  * Updates the target with information from source, also updating a mapping of
703
621
  * which records in the target were changed as a result.
704
- * Additionally, will marc records as invalidated at the current write epoch
622
+ * Additionally, will mark records as invalidated at the current write epoch
705
623
  * given the set of record ids marked as stale in this update.
706
624
  */
707
625
  function updateTargetFromSource(
@@ -770,7 +688,15 @@ function updateTargetFromSource(
770
688
  }
771
689
  }
772
690
  if (sourceRecord && targetRecord) {
773
- const nextRecord = RelayModernRecord.update(targetRecord, sourceRecord);
691
+ // ReactFlightClientResponses are lazy and only materialize when readRoot
692
+ // is called when we read the field, so if the record is a Flight field
693
+ // we always use the new record's data regardless of whether
694
+ // it actually changed. Let React take care of reconciliation instead.
695
+ const nextRecord =
696
+ RelayModernRecord.getType(targetRecord) ===
697
+ RelayStoreReactFlightUtils.REACT_FLIGHT_TYPE_NAME
698
+ ? sourceRecord
699
+ : RelayModernRecord.update(targetRecord, sourceRecord);
774
700
  if (nextRecord !== targetRecord) {
775
701
  // Prevent mutation of a record from outside the store.
776
702
  if (__DEV__) {
@@ -186,7 +186,7 @@ class RelayPublishQueue implements PublishQueue {
186
186
  warning(
187
187
  this._isRunning !== true,
188
188
  'A store update was detected within another store update. Please ' +
189
- 'make sure new store updates arent being executed within an ' +
189
+ "make sure new store updates aren't being executed within an " +
190
190
  'updater function for a different update.',
191
191
  );
192
192
  this._isRunning = true;