relay-runtime 18.2.0 → 20.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 (83) hide show
  1. package/experimental.js +1 -1
  2. package/experimental.js.flow +8 -6
  3. package/index.js +1 -1
  4. package/index.js.flow +3 -0
  5. package/lib/experimental.js +5 -2
  6. package/lib/index.js +3 -0
  7. package/lib/multi-actor-environment/ActorSpecificEnvironment.js +1 -1
  8. package/lib/mutations/RelayRecordSourceProxy.js +2 -1
  9. package/lib/mutations/createUpdatableProxy.js +1 -1
  10. package/lib/mutations/validateMutation.js +2 -2
  11. package/lib/network/RelayObservable.js +1 -3
  12. package/lib/network/wrapNetworkWithLogObserver.js +2 -2
  13. package/lib/query/fetchQuery.js +1 -1
  14. package/lib/store/DataChecker.js +12 -8
  15. package/lib/store/OperationExecutor.js +93 -43
  16. package/lib/store/RelayModernEnvironment.js +13 -4
  17. package/lib/store/RelayModernFragmentSpecResolver.js +4 -4
  18. package/lib/store/RelayModernStore.js +49 -24
  19. package/lib/store/RelayPublishQueue.js +11 -15
  20. package/lib/store/RelayReader.js +134 -151
  21. package/lib/store/RelayReferenceMarker.js +14 -7
  22. package/lib/store/RelayResponseNormalizer.js +57 -31
  23. package/lib/store/RelayStoreSubscriptions.js +2 -2
  24. package/lib/store/RelayStoreUtils.js +8 -0
  25. package/lib/store/ResolverFragments.js +2 -2
  26. package/lib/store/createRelayLoggingContext.js +17 -0
  27. package/lib/store/generateTypenamePrefixedDataID.js +9 -0
  28. package/lib/store/live-resolvers/LiveResolverCache.js +4 -2
  29. package/lib/store/live-resolvers/resolverDataInjector.js +4 -4
  30. package/lib/store/normalizeResponse.js +2 -2
  31. package/lib/store/observeFragmentExperimental.js +60 -13
  32. package/lib/store/observeQueryExperimental.js +21 -0
  33. package/lib/util/RelayFeatureFlags.js +7 -1
  34. package/lib/util/handlePotentialSnapshotErrors.js +11 -8
  35. package/multi-actor-environment/ActorSpecificEnvironment.js.flow +1 -0
  36. package/mutations/RelayRecordSourceProxy.js.flow +4 -0
  37. package/mutations/createUpdatableProxy.js.flow +1 -1
  38. package/mutations/validateMutation.js.flow +3 -3
  39. package/network/RelayNetworkTypes.js.flow +3 -0
  40. package/network/RelayObservable.js.flow +1 -5
  41. package/network/wrapNetworkWithLogObserver.js.flow +19 -1
  42. package/package.json +1 -1
  43. package/query/fetchQuery.js.flow +1 -1
  44. package/store/DataChecker.js.flow +16 -4
  45. package/store/OperationExecutor.js.flow +101 -15
  46. package/store/RelayExperimentalGraphResponseTransform.js.flow +4 -4
  47. package/store/RelayModernEnvironment.js.flow +22 -6
  48. package/store/RelayModernFragmentSpecResolver.js.flow +6 -6
  49. package/store/RelayModernSelector.js.flow +2 -0
  50. package/store/RelayModernStore.js.flow +86 -27
  51. package/store/RelayPublishQueue.js.flow +32 -21
  52. package/store/RelayReader.js.flow +168 -97
  53. package/store/RelayReferenceMarker.js.flow +15 -5
  54. package/store/RelayResponseNormalizer.js.flow +104 -69
  55. package/store/RelayStoreSubscriptions.js.flow +2 -2
  56. package/store/RelayStoreTypes.js.flow +34 -4
  57. package/store/RelayStoreUtils.js.flow +29 -0
  58. package/store/ResolverCache.js.flow +2 -2
  59. package/store/ResolverFragments.js.flow +5 -3
  60. package/store/StoreInspector.js.flow +5 -0
  61. package/store/createRelayContext.js.flow +3 -2
  62. package/store/createRelayLoggingContext.js.flow +46 -0
  63. package/store/generateTypenamePrefixedDataID.js.flow +25 -0
  64. package/store/live-resolvers/LiveResolverCache.js.flow +7 -2
  65. package/store/live-resolvers/resolverDataInjector.js.flow +10 -6
  66. package/store/normalizeResponse.js.flow +2 -0
  67. package/store/observeFragmentExperimental.js.flow +82 -28
  68. package/store/observeQueryExperimental.js.flow +61 -0
  69. package/store/waitForFragmentExperimental.js.flow +4 -3
  70. package/util/NormalizationNode.js.flow +2 -1
  71. package/util/RelayConcreteNode.js.flow +2 -0
  72. package/util/RelayError.js.flow +1 -0
  73. package/util/RelayFeatureFlags.js.flow +28 -0
  74. package/util/RelayRuntimeTypes.js.flow +6 -3
  75. package/util/getPaginationVariables.js.flow +2 -0
  76. package/util/handlePotentialSnapshotErrors.js.flow +23 -11
  77. package/util/registerEnvironmentWithDevTools.js.flow +4 -2
  78. package/util/withProvidedVariables.js.flow +1 -0
  79. package/util/withStartAndDuration.js.flow +3 -0
  80. package/relay-runtime-experimental.js +0 -4
  81. package/relay-runtime-experimental.min.js +0 -9
  82. package/relay-runtime.js +0 -4
  83. package/relay-runtime.min.js +0 -9
@@ -107,6 +107,7 @@ class RelayModernStore implements Store {
107
107
  fetchTime: ?number,
108
108
  },
109
109
  >;
110
+ _shouldRetainWithinTTL_EXPERIMENTAL: boolean;
110
111
  _shouldScheduleGC: boolean;
111
112
  _storeSubscriptions: StoreSubscriptions;
112
113
  _updatedRecordIDs: DataIDSet;
@@ -127,6 +128,9 @@ class RelayModernStore implements Store {
127
128
  shouldProcessClientComponents?: ?boolean,
128
129
  resolverContext?: ResolverContext,
129
130
 
131
+ // Experimental
132
+ shouldRetainWithinTTL_EXPERIMENTAL?: boolean,
133
+
130
134
  // These additional config options are only used if the experimental
131
135
  // @outputType resolver feature is used
132
136
  treatMissingFieldsAsNull?: ?boolean,
@@ -147,6 +151,8 @@ class RelayModernStore implements Store {
147
151
  this._gcHoldCounter = 0;
148
152
  this._gcReleaseBufferSize =
149
153
  options?.gcReleaseBufferSize ?? DEFAULT_RELEASE_BUFFER_SIZE;
154
+ this._shouldRetainWithinTTL_EXPERIMENTAL =
155
+ options?.shouldRetainWithinTTL_EXPERIMENTAL ?? false;
150
156
  this._gcRun = null;
151
157
  this._gcScheduler = options?.gcScheduler ?? resolveImmediate;
152
158
  this._getDataID = options?.getDataID ?? defaultGetDataID;
@@ -165,14 +171,15 @@ class RelayModernStore implements Store {
165
171
  () => this._getMutableRecordSource(),
166
172
  this,
167
173
  );
174
+ this._resolverContext = options?.resolverContext;
168
175
  this._storeSubscriptions = new RelayStoreSubscriptions(
169
176
  options?.log,
170
177
  this._resolverCache,
178
+ this._resolverContext,
171
179
  );
172
180
  this._updatedRecordIDs = new Set();
173
181
  this._shouldProcessClientComponents =
174
182
  options?.shouldProcessClientComponents ?? false;
175
- this._resolverContext = options?.resolverContext;
176
183
 
177
184
  this._treatMissingFieldsAsNull = options?.treatMissingFieldsAsNull ?? false;
178
185
  this._actorIdentifier = options?.actorIdentifier;
@@ -235,6 +242,11 @@ class RelayModernStore implements Store {
235
242
  const selector = operation.root;
236
243
  const source = this._getMutableRecordSource();
237
244
  const globalInvalidationEpoch = this._globalInvalidationEpoch;
245
+ const useExecTimeResolvers =
246
+ operation.request.node.operation.use_exec_time_resolvers ??
247
+ operation.request.node.operation.exec_time_resolvers_enabled_provider?.get() ===
248
+ true ??
249
+ false;
238
250
 
239
251
  const rootEntry = this._roots.get(operation.request.identifier);
240
252
  const operationLastWrittenAt = rootEntry != null ? rootEntry.epoch : null;
@@ -279,6 +291,7 @@ class RelayModernStore implements Store {
279
291
  this._getDataID,
280
292
  this._shouldProcessClientComponents,
281
293
  this.__log,
294
+ useExecTimeResolvers,
282
295
  );
283
296
 
284
297
  return getAvailabilityStatus(
@@ -315,7 +328,9 @@ class RelayModernStore implements Store {
315
328
  rootEntry.fetchTime <= Date.now() - _queryCacheExpirationTime;
316
329
 
317
330
  if (rootEntryIsStale) {
318
- this._roots.delete(id);
331
+ if (!this._shouldRetainWithinTTL_EXPERIMENTAL) {
332
+ this._roots.delete(id);
333
+ }
319
334
  this.scheduleGC();
320
335
  } else {
321
336
  this._releaseBuffer.push(id);
@@ -325,8 +340,10 @@ class RelayModernStore implements Store {
325
340
  // buffer have a refCount of 0.
326
341
  if (this._releaseBuffer.length > this._gcReleaseBufferSize) {
327
342
  const _id = this._releaseBuffer.shift();
328
- // $FlowFixMe[incompatible-call]
329
- this._roots.delete(_id);
343
+ if (!this._shouldRetainWithinTTL_EXPERIMENTAL) {
344
+ // $FlowFixMe[incompatible-call]
345
+ this._roots.delete(_id);
346
+ }
330
347
  this.scheduleGC();
331
348
  }
332
349
  }
@@ -688,6 +705,14 @@ class RelayModernStore implements Store {
688
705
  };
689
706
 
690
707
  *_collect(): Generator<void, void, void> {
708
+ if (
709
+ this._shouldRetainWithinTTL_EXPERIMENTAL &&
710
+ this._queryCacheExpirationTime == null
711
+ ) {
712
+ // Null expiration time indicates infinite TTL, so we don't need to
713
+ // run GC.
714
+ return;
715
+ }
691
716
  /* eslint-disable no-labels */
692
717
  const log = this.__log;
693
718
  top: while (true) {
@@ -699,15 +724,43 @@ class RelayModernStore implements Store {
699
724
  const startEpoch = this._currentWriteEpoch;
700
725
  const references = new Set<DataID>();
701
726
 
702
- // Mark all records that are traversable from a root
703
- for (const {operation} of this._roots.values()) {
727
+ for (const [
728
+ dataID,
729
+ {operation, refCount, fetchTime},
730
+ ] of this._roots.entries()) {
731
+ if (this._shouldRetainWithinTTL_EXPERIMENTAL) {
732
+ // Do not mark records that should be garbage collected
733
+ const {_queryCacheExpirationTime} = this;
734
+ invariant(
735
+ _queryCacheExpirationTime != null,
736
+ 'Query cache expiration time should be non-null if executing GC',
737
+ );
738
+ const recordHasExpired =
739
+ fetchTime == null ||
740
+ fetchTime <= Date.now() - _queryCacheExpirationTime;
741
+ const recordShouldBeCollected =
742
+ recordHasExpired &&
743
+ refCount === 0 &&
744
+ !this._releaseBuffer.includes(dataID);
745
+ if (recordShouldBeCollected) {
746
+ continue;
747
+ }
748
+ }
749
+
750
+ // Mark all records that are traversable from a root that is still valid
704
751
  const selector = operation.root;
752
+ const useExecTimeResolvers =
753
+ operation.request.node.operation.use_exec_time_resolvers ??
754
+ operation.request.node.operation.exec_time_resolvers_enabled_provider?.get() ===
755
+ true ??
756
+ false;
705
757
  RelayReferenceMarker.mark(
706
758
  this._recordSource,
707
759
  selector,
708
760
  references,
709
761
  this._operationLoader,
710
762
  this._shouldProcessClientComponents,
763
+ useExecTimeResolvers,
711
764
  );
712
765
  // Yield for other work after each operation
713
766
  yield;
@@ -723,31 +776,36 @@ class RelayModernStore implements Store {
723
776
  }
724
777
  }
725
778
 
726
- // Sweep records without references
727
- if (references.size === 0) {
728
- // Short-circuit if *nothing* is referenced
729
- this._recordSource.clear();
730
- } else {
731
- // Evict any unreferenced nodes
732
- const storeIDs = this._recordSource.getRecordIDs();
733
- for (let ii = 0; ii < storeIDs.length; ii++) {
734
- const dataID = storeIDs[ii];
735
- if (!references.has(dataID)) {
736
- const record = this._recordSource.get(dataID);
737
- if (record != null) {
738
- const maybeResolverSubscription = RelayModernRecord.getValue(
739
- record,
740
- RELAY_RESOLVER_LIVE_STATE_SUBSCRIPTION_KEY,
741
- );
742
- if (maybeResolverSubscription != null) {
743
- // $FlowFixMe - this value if it is not null, it is a function
744
- maybeResolverSubscription();
745
- }
779
+ // NOTE: It may be tempting to use `this._recordSource.clear()`
780
+ // when no references are found, but that would prevent calling
781
+ // maybeResolverSubscription() on any records that have an active
782
+ // resolver subscription. This would result in a memory leak.
783
+
784
+ // Evict any unreferenced nodes
785
+ const storeIDs = this._recordSource.getRecordIDs();
786
+ for (let ii = 0; ii < storeIDs.length; ii++) {
787
+ const dataID = storeIDs[ii];
788
+ if (!references.has(dataID)) {
789
+ const record = this._recordSource.get(dataID);
790
+ if (record != null) {
791
+ const maybeResolverSubscription = RelayModernRecord.getValue(
792
+ record,
793
+ RELAY_RESOLVER_LIVE_STATE_SUBSCRIPTION_KEY,
794
+ );
795
+ if (maybeResolverSubscription != null) {
796
+ // $FlowFixMe - this value if it is not null, it is a function
797
+ maybeResolverSubscription();
746
798
  }
747
- this._recordSource.remove(dataID);
799
+ }
800
+ this._recordSource.remove(dataID);
801
+ if (this._shouldRetainWithinTTL_EXPERIMENTAL) {
802
+ // Note: A record that was never retained will not be in the roots map
803
+ // but the following line should not throw
804
+ this._roots.delete(dataID);
748
805
  }
749
806
  }
750
807
  }
808
+
751
809
  if (log != null) {
752
810
  log({
753
811
  name: 'store.gc.end',
@@ -765,6 +823,7 @@ class RelayModernStore implements Store {
765
823
  return {
766
824
  path,
767
825
  getDataID: this._getDataID,
826
+ log: this.__log,
768
827
  treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
769
828
  shouldProcessClientComponents: this._shouldProcessClientComponents,
770
829
  actorIdentifier: this._actorIdentifier,
@@ -15,6 +15,7 @@ import type {HandlerProvider} from '../handlers/RelayDefaultHandlerProvider';
15
15
  import type {Disposable} from '../util/RelayRuntimeTypes';
16
16
  import type {GetDataID} from './RelayResponseNormalizer';
17
17
  import type {
18
+ LogFunction,
18
19
  MissingFieldHandler,
19
20
  MutationParameters,
20
21
  OperationDescriptor,
@@ -33,6 +34,7 @@ import type {
33
34
  const RelayRecordSourceMutator = require('../mutations/RelayRecordSourceMutator');
34
35
  const RelayRecordSourceProxy = require('../mutations/RelayRecordSourceProxy');
35
36
  const RelayRecordSourceSelectorProxy = require('../mutations/RelayRecordSourceSelectorProxy');
37
+ const RelayFeatureFlags = require('../util/RelayFeatureFlags');
36
38
  const RelayReader = require('./RelayReader');
37
39
  const RelayRecordSource = require('./RelayRecordSource');
38
40
  const invariant = require('invariant');
@@ -60,8 +62,10 @@ type PendingUpdater = {
60
62
  const _global: typeof global | $FlowFixMe =
61
63
  typeof global !== 'undefined'
62
64
  ? global
63
- : typeof window !== 'undefined'
64
- ? window
65
+ : // $FlowFixMe[cannot-resolve-name]
66
+ typeof window !== 'undefined'
67
+ ? // $FlowFixMe[cannot-resolve-name]
68
+ window
65
69
  : undefined;
66
70
 
67
71
  const applyWithGuard =
@@ -84,6 +88,7 @@ class RelayPublishQueue implements PublishQueue {
84
88
  _handlerProvider: ?HandlerProvider;
85
89
  _missingFieldHandlers: $ReadOnlyArray<MissingFieldHandler>;
86
90
  _getDataID: GetDataID;
91
+ _log: ?LogFunction;
87
92
 
88
93
  _hasStoreSnapshot: boolean;
89
94
  // True if the next `run()` should apply the backup and rerun all optimistic
@@ -112,6 +117,7 @@ class RelayPublishQueue implements PublishQueue {
112
117
  handlerProvider?: ?HandlerProvider,
113
118
  getDataID: GetDataID,
114
119
  missingFieldHandlers: $ReadOnlyArray<MissingFieldHandler>,
120
+ log: LogFunction,
115
121
  ) {
116
122
  this._hasStoreSnapshot = false;
117
123
  this._handlerProvider = handlerProvider || null;
@@ -123,6 +129,7 @@ class RelayPublishQueue implements PublishQueue {
123
129
  this._gcHold = null;
124
130
  this._getDataID = getDataID;
125
131
  this._missingFieldHandlers = missingFieldHandlers;
132
+ this._log = log;
126
133
  }
127
134
 
128
135
  /**
@@ -219,24 +226,27 @@ class RelayPublishQueue implements PublishQueue {
219
226
  this._pendingOptimisticUpdates.size === 0 &&
220
227
  !runWillClearGcHold;
221
228
 
222
- if (__DEV__) {
223
- warning(
224
- !runIsANoop,
225
- 'RelayPublishQueue.run was called, but the call would have been a noop.',
226
- );
227
- warning(
228
- this._isRunning !== true,
229
- 'A store update was detected within another store update. Please ' +
230
- "make sure new store updates aren't being executed within an " +
231
- 'updater function for a different update.',
232
- );
233
- this._isRunning = true;
234
- }
229
+ warning(
230
+ !runIsANoop,
231
+ 'RelayPublishQueue.run was called, but the call would have been a noop.',
232
+ );
233
+ RelayFeatureFlags.DISALLOW_NESTED_UPDATES
234
+ ? invariant(
235
+ this._isRunning !== true,
236
+ 'A store update was detected within another store update. Please ' +
237
+ "make sure new store updates aren't being executed within an " +
238
+ 'updater function for a different update.',
239
+ )
240
+ : warning(
241
+ this._isRunning !== true,
242
+ 'A store update was detected within another store update. Please ' +
243
+ "make sure new store updates aren't being executed within an " +
244
+ 'updater function for a different update.',
245
+ );
246
+ this._isRunning = true;
235
247
 
236
248
  if (runIsANoop) {
237
- if (__DEV__) {
238
- this._isRunning = false;
239
- }
249
+ this._isRunning = false;
240
250
  return [];
241
251
  }
242
252
 
@@ -268,9 +278,7 @@ class RelayPublishQueue implements PublishQueue {
268
278
  this._gcHold = null;
269
279
  }
270
280
  }
271
- if (__DEV__) {
272
- this._isRunning = false;
273
- }
281
+ this._isRunning = false;
274
282
  return this._store.notify(sourceOperation, invalidatedStore);
275
283
  }
276
284
 
@@ -292,6 +300,7 @@ class RelayPublishQueue implements PublishQueue {
292
300
  this._getDataID,
293
301
  this._handlerProvider,
294
302
  this._missingFieldHandlers,
303
+ this._log,
295
304
  );
296
305
  if (fieldPayloads && fieldPayloads.length) {
297
306
  fieldPayloads.forEach(fieldPayload => {
@@ -355,6 +364,7 @@ class RelayPublishQueue implements PublishQueue {
355
364
  this._getDataID,
356
365
  this._handlerProvider,
357
366
  this._missingFieldHandlers,
367
+ this._log,
358
368
  );
359
369
  applyWithGuard(
360
370
  updater,
@@ -388,6 +398,7 @@ class RelayPublishQueue implements PublishQueue {
388
398
  this._getDataID,
389
399
  this._handlerProvider,
390
400
  this._missingFieldHandlers,
401
+ this._log,
391
402
  );
392
403
 
393
404
  // $FlowFixMe[unclear-type] see explanation above.