relay-runtime 11.0.2 → 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 (100) hide show
  1. package/index.js +1 -1
  2. package/index.js.flow +16 -1
  3. package/lib/index.js +15 -0
  4. package/lib/multi-actor-environment/ActorIdentifier.js +11 -1
  5. package/lib/multi-actor-environment/ActorSpecificEnvironment.js +59 -19
  6. package/lib/multi-actor-environment/ActorUtils.js +27 -0
  7. package/lib/multi-actor-environment/MultiActorEnvironment.js +305 -55
  8. package/lib/multi-actor-environment/index.js +5 -1
  9. package/lib/mutations/RelayRecordSourceSelectorProxy.js +6 -1
  10. package/lib/mutations/commitMutation.js +4 -1
  11. package/lib/mutations/validateMutation.js +6 -1
  12. package/lib/network/RelayObservable.js +3 -1
  13. package/lib/network/RelayQueryResponseCache.js +19 -3
  14. package/lib/network/wrapNetworkWithLogObserver.js +78 -0
  15. package/lib/store/DataChecker.js +110 -40
  16. package/lib/store/OperationExecutor.js +478 -204
  17. package/lib/store/RelayConcreteVariables.js +21 -0
  18. package/lib/store/RelayModernEnvironment.js +41 -85
  19. package/lib/store/RelayModernFragmentSpecResolver.js +48 -22
  20. package/lib/store/RelayModernRecord.js +35 -1
  21. package/lib/store/RelayModernStore.js +48 -14
  22. package/lib/store/RelayOperationTracker.js +33 -23
  23. package/lib/store/RelayPublishQueue.js +23 -5
  24. package/lib/store/RelayReader.js +138 -44
  25. package/lib/store/RelayRecordSource.js +87 -3
  26. package/lib/store/RelayReferenceMarker.js +28 -15
  27. package/lib/store/RelayResponseNormalizer.js +164 -91
  28. package/lib/store/RelayStoreReactFlightUtils.js +1 -7
  29. package/lib/store/RelayStoreSubscriptions.js +8 -5
  30. package/lib/store/RelayStoreUtils.js +7 -2
  31. package/lib/store/ResolverCache.js +213 -0
  32. package/lib/store/ResolverFragments.js +1 -1
  33. package/lib/store/createRelayContext.js +1 -1
  34. package/lib/subscription/requestSubscription.js +27 -29
  35. package/lib/util/RelayConcreteNode.js +1 -0
  36. package/lib/util/RelayFeatureFlags.js +3 -5
  37. package/lib/util/RelayReplaySubject.js +21 -6
  38. package/lib/util/getPaginationMetadata.js +41 -0
  39. package/lib/util/getPaginationVariables.js +67 -0
  40. package/lib/util/getPendingOperationsForFragment.js +55 -0
  41. package/lib/util/getRefetchMetadata.js +36 -0
  42. package/lib/util/getValueAtPath.js +51 -0
  43. package/lib/util/isEmptyObject.js +1 -1
  44. package/lib/util/registerEnvironmentWithDevTools.js +26 -0
  45. package/lib/util/withDuration.js +31 -0
  46. package/multi-actor-environment/ActorIdentifier.js.flow +17 -1
  47. package/multi-actor-environment/ActorSpecificEnvironment.js.flow +72 -44
  48. package/multi-actor-environment/ActorUtils.js.flow +33 -0
  49. package/multi-actor-environment/MultiActorEnvironment.js.flow +332 -80
  50. package/multi-actor-environment/MultiActorEnvironmentTypes.js.flow +61 -12
  51. package/multi-actor-environment/index.js.flow +3 -0
  52. package/mutations/RelayRecordSourceSelectorProxy.js.flow +7 -2
  53. package/mutations/commitMutation.js.flow +2 -0
  54. package/mutations/validateMutation.js.flow +8 -0
  55. package/network/RelayObservable.js.flow +2 -0
  56. package/network/RelayQueryResponseCache.js.flow +31 -18
  57. package/network/wrapNetworkWithLogObserver.js.flow +99 -0
  58. package/package.json +1 -1
  59. package/relay-runtime.js +2 -2
  60. package/relay-runtime.min.js +2 -2
  61. package/store/ClientID.js.flow +5 -1
  62. package/store/DataChecker.js.flow +126 -35
  63. package/store/OperationExecutor.js.flow +528 -265
  64. package/store/RelayConcreteVariables.js.flow +26 -1
  65. package/store/RelayModernEnvironment.js.flow +41 -94
  66. package/store/RelayModernFragmentSpecResolver.js.flow +40 -14
  67. package/store/RelayModernOperationDescriptor.js.flow +9 -3
  68. package/store/RelayModernRecord.js.flow +49 -0
  69. package/store/RelayModernStore.js.flow +50 -12
  70. package/store/RelayOperationTracker.js.flow +56 -34
  71. package/store/RelayPublishQueue.js.flow +31 -8
  72. package/store/RelayReader.js.flow +148 -42
  73. package/store/RelayRecordSource.js.flow +72 -6
  74. package/store/RelayReferenceMarker.js.flow +29 -12
  75. package/store/RelayResponseNormalizer.js.flow +164 -48
  76. package/store/RelayStoreReactFlightUtils.js.flow +1 -7
  77. package/store/RelayStoreSubscriptions.js.flow +10 -3
  78. package/store/RelayStoreTypes.js.flow +128 -12
  79. package/store/RelayStoreUtils.js.flow +17 -3
  80. package/store/ResolverCache.js.flow +247 -0
  81. package/store/ResolverFragments.js.flow +6 -3
  82. package/store/createRelayContext.js.flow +1 -1
  83. package/subscription/requestSubscription.js.flow +41 -29
  84. package/util/NormalizationNode.js.flow +10 -3
  85. package/util/ReaderNode.js.flow +15 -1
  86. package/util/RelayConcreteNode.js.flow +1 -0
  87. package/util/RelayFeatureFlags.js.flow +8 -10
  88. package/util/RelayReplaySubject.js.flow +7 -6
  89. package/util/getPaginationMetadata.js.flow +74 -0
  90. package/util/getPaginationVariables.js.flow +112 -0
  91. package/util/getPendingOperationsForFragment.js.flow +62 -0
  92. package/util/getRefetchMetadata.js.flow +80 -0
  93. package/util/getValueAtPath.js.flow +46 -0
  94. package/util/isEmptyObject.js.flow +1 -0
  95. package/util/registerEnvironmentWithDevTools.js.flow +33 -0
  96. package/util/withDuration.js.flow +32 -0
  97. package/lib/store/RelayRecordSourceMapImpl.js +0 -107
  98. package/lib/store/RelayStoreSubscriptionsUsingMapByID.js +0 -318
  99. package/store/RelayRecordSourceMapImpl.js.flow +0 -91
  100. package/store/RelayStoreSubscriptionsUsingMapByID.js.flow +0 -283
@@ -17,17 +17,21 @@ const invariant = require('invariant');
17
17
  import type {RequestDescriptor} from './RelayStoreTypes';
18
18
 
19
19
  class RelayOperationTracker {
20
- _ownersToPendingOperationsIdentifier: Map<string, Set<string>>;
21
- _pendingOperationsToOwnersIdentifier: Map<string, Set<string>>;
22
- _ownersIdentifierToPromise: Map<
20
+ _ownersToPendingOperations: Map<string, Map<string, RequestDescriptor>>;
21
+ _pendingOperationsToOwners: Map<string, Set<string>>;
22
+ _ownersToPendingPromise: Map<
23
23
  string,
24
- {|promise: Promise<void>, resolve: () => void|},
24
+ {|
25
+ promise: Promise<void>,
26
+ resolve: () => void,
27
+ pendingOperations: $ReadOnlyArray<RequestDescriptor>,
28
+ |},
25
29
  >;
26
30
 
27
31
  constructor() {
28
- this._ownersToPendingOperationsIdentifier = new Map();
29
- this._pendingOperationsToOwnersIdentifier = new Map();
30
- this._ownersIdentifierToPromise = new Map();
32
+ this._ownersToPendingOperations = new Map();
33
+ this._pendingOperationsToOwners = new Map();
34
+ this._ownersToPendingPromise = new Map();
31
35
  }
32
36
 
33
37
  /**
@@ -45,7 +49,7 @@ class RelayOperationTracker {
45
49
  const newlyAffectedOwnersIdentifier = new Set();
46
50
  for (const owner of affectedOwners) {
47
51
  const ownerIdentifier = owner.identifier;
48
- const pendingOperationsAffectingOwner = this._ownersToPendingOperationsIdentifier.get(
52
+ const pendingOperationsAffectingOwner = this._ownersToPendingOperations.get(
49
53
  ownerIdentifier,
50
54
  );
51
55
  if (pendingOperationsAffectingOwner != null) {
@@ -53,14 +57,17 @@ class RelayOperationTracker {
53
57
  // We just need to detect, is it the same operation that we already
54
58
  // have in the list, or it's a new operation
55
59
  if (!pendingOperationsAffectingOwner.has(pendingOperationIdentifier)) {
56
- pendingOperationsAffectingOwner.add(pendingOperationIdentifier);
60
+ pendingOperationsAffectingOwner.set(
61
+ pendingOperationIdentifier,
62
+ pendingOperation,
63
+ );
57
64
  newlyAffectedOwnersIdentifier.add(ownerIdentifier);
58
65
  }
59
66
  } else {
60
67
  // This is a new `ownerIdentifier` that is affected by the operation
61
- this._ownersToPendingOperationsIdentifier.set(
68
+ this._ownersToPendingOperations.set(
62
69
  ownerIdentifier,
63
- new Set([pendingOperationIdentifier]),
70
+ new Map([[pendingOperationIdentifier, pendingOperation]]),
64
71
  );
65
72
  newlyAffectedOwnersIdentifier.add(ownerIdentifier);
66
73
  }
@@ -72,19 +79,18 @@ class RelayOperationTracker {
72
79
  }
73
80
 
74
81
  // But, if some owners were affected we need to add them to
75
- // the `_pendingOperationsToOwnersIdentifier` set
76
- const ownersAffectedByOperationIdentifier =
77
- this._pendingOperationsToOwnersIdentifier.get(
78
- pendingOperationIdentifier,
79
- ) || new Set();
82
+ // the `_pendingOperationsToOwners` set
83
+ const ownersAffectedByPendingOperation =
84
+ this._pendingOperationsToOwners.get(pendingOperationIdentifier) ||
85
+ new Set();
80
86
 
81
87
  for (const ownerIdentifier of newlyAffectedOwnersIdentifier) {
82
88
  this._resolveOwnerResolvers(ownerIdentifier);
83
- ownersAffectedByOperationIdentifier.add(ownerIdentifier);
89
+ ownersAffectedByPendingOperation.add(ownerIdentifier);
84
90
  }
85
- this._pendingOperationsToOwnersIdentifier.set(
91
+ this._pendingOperationsToOwners.set(
86
92
  pendingOperationIdentifier,
87
- ownersAffectedByOperationIdentifier,
93
+ ownersAffectedByPendingOperation,
88
94
  );
89
95
  }
90
96
 
@@ -94,7 +100,7 @@ class RelayOperationTracker {
94
100
  */
95
101
  complete(pendingOperation: RequestDescriptor): void {
96
102
  const pendingOperationIdentifier = pendingOperation.identifier;
97
- const affectedOwnersIdentifier = this._pendingOperationsToOwnersIdentifier.get(
103
+ const affectedOwnersIdentifier = this._pendingOperationsToOwners.get(
98
104
  pendingOperationIdentifier,
99
105
  );
100
106
  if (affectedOwnersIdentifier == null) {
@@ -107,7 +113,7 @@ class RelayOperationTracker {
107
113
  // and some other operations
108
114
  const updatedOwnersIdentifier = new Set();
109
115
  for (const ownerIdentifier of affectedOwnersIdentifier) {
110
- const pendingOperationsAffectingOwner = this._ownersToPendingOperationsIdentifier.get(
116
+ const pendingOperationsAffectingOwner = this._ownersToPendingOperations.get(
111
117
  ownerIdentifier,
112
118
  );
113
119
  if (!pendingOperationsAffectingOwner) {
@@ -124,7 +130,7 @@ class RelayOperationTracker {
124
130
  // Complete subscriptions for all owners, affected by `pendingOperationIdentifier`
125
131
  for (const ownerIdentifier of completedOwnersIdentifier) {
126
132
  this._resolveOwnerResolvers(ownerIdentifier);
127
- this._ownersToPendingOperationsIdentifier.delete(ownerIdentifier);
133
+ this._ownersToPendingOperations.delete(ownerIdentifier);
128
134
  }
129
135
 
130
136
  // Update all ownerIdentifier that were updated by `pendingOperationIdentifier` but still
@@ -134,31 +140,42 @@ class RelayOperationTracker {
134
140
  }
135
141
 
136
142
  // Finally, remove pending operation identifier
137
- this._pendingOperationsToOwnersIdentifier.delete(
138
- pendingOperationIdentifier,
139
- );
143
+ this._pendingOperationsToOwners.delete(pendingOperationIdentifier);
140
144
  }
141
145
 
142
146
  _resolveOwnerResolvers(ownerIdentifier: string): void {
143
- const promiseEntry = this._ownersIdentifierToPromise.get(ownerIdentifier);
147
+ const promiseEntry = this._ownersToPendingPromise.get(ownerIdentifier);
144
148
  if (promiseEntry != null) {
145
149
  promiseEntry.resolve();
146
150
  }
147
- this._ownersIdentifierToPromise.delete(ownerIdentifier);
151
+ this._ownersToPendingPromise.delete(ownerIdentifier);
148
152
  }
149
153
 
150
- getPromiseForPendingOperationsAffectingOwner(
154
+ getPendingOperationsAffectingOwner(
151
155
  owner: RequestDescriptor,
152
- ): Promise<void> | null {
156
+ ): {|
157
+ promise: Promise<void>,
158
+ pendingOperations: $ReadOnlyArray<RequestDescriptor>,
159
+ |} | null {
153
160
  const ownerIdentifier = owner.identifier;
154
- if (!this._ownersToPendingOperationsIdentifier.has(ownerIdentifier)) {
161
+ const pendingOperationsForOwner = this._ownersToPendingOperations.get(
162
+ ownerIdentifier,
163
+ );
164
+ if (
165
+ pendingOperationsForOwner == null ||
166
+ pendingOperationsForOwner.size === 0
167
+ ) {
155
168
  return null;
156
169
  }
157
- const cachedPromiseEntry = this._ownersIdentifierToPromise.get(
170
+
171
+ const cachedPromiseEntry = this._ownersToPendingPromise.get(
158
172
  ownerIdentifier,
159
173
  );
160
174
  if (cachedPromiseEntry != null) {
161
- return cachedPromiseEntry.promise;
175
+ return {
176
+ promise: cachedPromiseEntry.promise,
177
+ pendingOperations: cachedPromiseEntry.pendingOperations,
178
+ };
162
179
  }
163
180
  let resolve;
164
181
  const promise = new Promise(r => {
@@ -169,8 +186,13 @@ class RelayOperationTracker {
169
186
  'RelayOperationTracker: Expected resolver to be defined. If you' +
170
187
  'are seeing this, it is likely a bug in Relay.',
171
188
  );
172
- this._ownersIdentifierToPromise.set(ownerIdentifier, {promise, resolve});
173
- return promise;
189
+ const pendingOperations = Array.from(pendingOperationsForOwner.values());
190
+ this._ownersToPendingPromise.set(ownerIdentifier, {
191
+ promise,
192
+ resolve,
193
+ pendingOperations,
194
+ });
195
+ return {promise, pendingOperations};
174
196
  }
175
197
  }
176
198
 
@@ -55,7 +55,7 @@ type PendingUpdater = {|
55
55
  |};
56
56
 
57
57
  const applyWithGuard =
58
- global.ErrorUtils?.applyWithGuard ??
58
+ global?.ErrorUtils?.applyWithGuard ??
59
59
  ((callback, context, args, onError, name) => callback.apply(context, args));
60
60
 
61
61
  /**
@@ -185,7 +185,20 @@ class RelayPublishQueue implements PublishQueue {
185
185
  run(
186
186
  sourceOperation?: OperationDescriptor,
187
187
  ): $ReadOnlyArray<RequestDescriptor> {
188
+ const runWillClearGcHold =
189
+ this._appliedOptimisticUpdates === 0 && !!this._gcHold;
190
+ const runIsANoop =
191
+ // this._pendingBackupRebase is true if an applied optimistic
192
+ // update has potentially been reverted or if this._pendingData is not empty.
193
+ !this._pendingBackupRebase &&
194
+ this._pendingOptimisticUpdates.size === 0 &&
195
+ !runWillClearGcHold;
196
+
188
197
  if (__DEV__) {
198
+ warning(
199
+ !runIsANoop,
200
+ 'RelayPublishQueue.run was called, but the call would have been a noop.',
201
+ );
189
202
  warning(
190
203
  this._isRunning !== true,
191
204
  'A store update was detected within another store update. Please ' +
@@ -194,6 +207,14 @@ class RelayPublishQueue implements PublishQueue {
194
207
  );
195
208
  this._isRunning = true;
196
209
  }
210
+
211
+ if (runIsANoop) {
212
+ if (__DEV__) {
213
+ this._isRunning = false;
214
+ }
215
+ return [];
216
+ }
217
+
197
218
  if (this._pendingBackupRebase) {
198
219
  if (this._hasStoreSnapshot) {
199
220
  this._store.restore();
@@ -347,17 +368,19 @@ class RelayPublishQueue implements PublishQueue {
347
368
  } else {
348
369
  const {operation, payload, updater} = optimisticUpdate;
349
370
  const {source, fieldPayloads} = payload;
350
- const recordSourceSelectorProxy = new RelayRecordSourceSelectorProxy(
351
- mutator,
352
- recordSourceProxy,
353
- operation.fragment,
354
- );
355
- let selectorData;
356
371
  if (source) {
357
372
  recordSourceProxy.publishSource(source, fieldPayloads);
358
- selectorData = lookupSelector(source, operation.fragment);
359
373
  }
360
374
  if (updater) {
375
+ let selectorData;
376
+ if (source) {
377
+ selectorData = lookupSelector(source, operation.fragment);
378
+ }
379
+ const recordSourceSelectorProxy = new RelayRecordSourceSelectorProxy(
380
+ mutator,
381
+ recordSourceProxy,
382
+ operation.fragment,
383
+ );
361
384
  applyWithGuard(
362
385
  updater,
363
386
  null,
@@ -12,12 +12,14 @@
12
12
 
13
13
  'use strict';
14
14
 
15
+ const ClientID = require('./ClientID');
15
16
  const RelayFeatureFlags = require('../util/RelayFeatureFlags');
16
17
  const RelayModernRecord = require('./RelayModernRecord');
17
18
 
18
19
  const invariant = require('invariant');
19
20
 
20
21
  const {
22
+ ACTOR_CHANGE,
21
23
  CLIENT_EXTENSION,
22
24
  CONDITION,
23
25
  DEFER,
@@ -45,6 +47,7 @@ const {
45
47
  getStorageKey,
46
48
  getModuleComponentKey,
47
49
  } = require('./RelayStoreUtils');
50
+ const {NoopResolverCache} = require('./ResolverCache');
48
51
  const {withResolverContext} = require('./ResolverFragments');
49
52
  const {generateTypeID} = require('./TypeID');
50
53
 
@@ -54,6 +57,7 @@ import type {
54
57
  ReaderFragmentSpread,
55
58
  ReaderInlineDataFragmentSpread,
56
59
  ReaderLinkedField,
60
+ ReaderActorChange,
57
61
  ReaderModuleImport,
58
62
  ReaderNode,
59
63
  ReaderRelayResolver,
@@ -72,12 +76,18 @@ import type {
72
76
  MissingRequiredFields,
73
77
  DataIDSet,
74
78
  } from './RelayStoreTypes';
79
+ import type {ResolverCache} from './ResolverCache';
75
80
 
76
81
  function read(
77
82
  recordSource: RecordSource,
78
83
  selector: SingularReaderSelector,
84
+ resolverCache?: ResolverCache,
79
85
  ): Snapshot {
80
- const reader = new RelayReader(recordSource, selector);
86
+ const reader = new RelayReader(
87
+ recordSource,
88
+ selector,
89
+ resolverCache ?? new NoopResolverCache(),
90
+ );
81
91
  return reader.read();
82
92
  }
83
93
 
@@ -93,8 +103,13 @@ class RelayReader {
93
103
  _seenRecords: DataIDSet;
94
104
  _selector: SingularReaderSelector;
95
105
  _variables: Variables;
106
+ _resolverCache: ResolverCache;
96
107
 
97
- constructor(recordSource: RecordSource, selector: SingularReaderSelector) {
108
+ constructor(
109
+ recordSource: RecordSource,
110
+ selector: SingularReaderSelector,
111
+ resolverCache: ResolverCache,
112
+ ) {
98
113
  this._isMissingData = false;
99
114
  this._isWithinUnmatchedTypeRefinement = false;
100
115
  this._missingRequiredFields = null;
@@ -103,6 +118,7 @@ class RelayReader {
103
118
  this._seenRecords = new Set();
104
119
  this._selector = selector;
105
120
  this._variables = selector.variables;
121
+ this._resolverCache = resolverCache;
106
122
  }
107
123
 
108
124
  read(): Snapshot {
@@ -126,7 +142,18 @@ class RelayReader {
126
142
  // match, then no data is expected to be present.
127
143
  if (isDataExpectedToBePresent && abstractKey == null && record != null) {
128
144
  const recordType = RelayModernRecord.getType(record);
129
- if (recordType !== node.type && dataID !== ROOT_ID) {
145
+ if (
146
+ recordType !== node.type &&
147
+ // The root record type is a special `__Root` type and may not match the
148
+ // type on the ast, so ignore type mismatches at the root.
149
+ // We currently detect whether we're at the root by checking against ROOT_ID,
150
+ // but this does not work for mutations/subscriptions which generate unique
151
+ // root ids. This is acceptable in practice as we don't read data for mutations/
152
+ // subscriptions in a situation where we would use isMissingData to decide whether
153
+ // to suspend or not.
154
+ // TODO T96653810: Correctly detect reading from root of mutation/subscription
155
+ dataID !== ROOT_ID
156
+ ) {
130
157
  isDataExpectedToBePresent = false;
131
158
  }
132
159
  }
@@ -135,12 +162,7 @@ class RelayReader {
135
162
  // then data is only expected to be present if the record type is known to
136
163
  // implement the interface. If we aren't sure whether the record implements
137
164
  // the interface, that itself constitutes "expected" data being missing.
138
- if (
139
- isDataExpectedToBePresent &&
140
- abstractKey != null &&
141
- record != null &&
142
- RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT
143
- ) {
165
+ if (isDataExpectedToBePresent && abstractKey != null && record != null) {
144
166
  const recordType = RelayModernRecord.getType(record);
145
167
  const typeID = generateTypeID(recordType);
146
168
  const typeRecord = this._recordSource.get(typeID);
@@ -267,7 +289,9 @@ class RelayReader {
267
289
  }
268
290
  break;
269
291
  case CONDITION:
270
- const conditionValue = this._getVariableValue(selection.condition);
292
+ const conditionValue = Boolean(
293
+ this._getVariableValue(selection.condition),
294
+ );
271
295
  if (conditionValue === selection.passingValue) {
272
296
  const hasExpectedData = this._traverseSelections(
273
297
  selection.selections,
@@ -294,7 +318,7 @@ class RelayReader {
294
318
  return false;
295
319
  }
296
320
  }
297
- } else if (RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT) {
321
+ } else {
298
322
  // Similar to the logic in read(): data is only expected to be present
299
323
  // if the record is known to conform to the interface. If we don't know
300
324
  // whether the type conforms or not, that constitutes missing data.
@@ -324,10 +348,6 @@ class RelayReader {
324
348
  // Don't know if the type implements the interface or not
325
349
  this._isMissingData = true;
326
350
  }
327
- } else {
328
- // legacy behavior for abstract refinements: always read even
329
- // if the type doesn't conform and don't reset isMissingData
330
- this._traverseSelections(selection.selections, record, data);
331
351
  }
332
352
  break;
333
353
  }
@@ -383,6 +403,9 @@ class RelayReader {
383
403
  throw new Error('Flight fields are not yet supported.');
384
404
  }
385
405
  break;
406
+ case ACTOR_CHANGE:
407
+ this._readActorChange(selection, record, data);
408
+ break;
386
409
  default:
387
410
  (selection: empty);
388
411
  invariant(
@@ -409,6 +432,12 @@ class RelayReader {
409
432
  } else {
410
433
  return this._readLink(selection.field, record, data);
411
434
  }
435
+ case RELAY_RESOLVER:
436
+ if (!RelayFeatureFlags.ENABLE_RELAY_RESOLVERS) {
437
+ throw new Error('Relay Resolver fields are not yet supported.');
438
+ }
439
+ this._readResolverField(selection.field, record, data);
440
+ break;
412
441
  default:
413
442
  (selection.field.kind: empty);
414
443
  invariant(
@@ -420,40 +449,84 @@ class RelayReader {
420
449
  }
421
450
 
422
451
  _readResolverField(
423
- selection: ReaderRelayResolver,
452
+ field: ReaderRelayResolver,
424
453
  record: Record,
425
454
  data: SelectorData,
426
455
  ): ?mixed {
427
- const {name, alias, resolverModule, fragment} = selection;
428
- const key = {
429
- __id: RelayModernRecord.getDataID(record),
430
- __fragmentOwner: this._owner,
431
- __fragments: {
432
- [fragment.name]: {}, // Arguments to this fragment; not yet supported.
433
- },
434
- };
435
- const resolverContext = {
436
- getDataForResolverFragment: singularReaderSelector => {
456
+ const {resolverModule, fragment} = field;
457
+ const storageKey = getStorageKey(field, this._variables);
458
+ const resolverID = ClientID.generateClientID(
459
+ RelayModernRecord.getDataID(record),
460
+ storageKey,
461
+ );
462
+
463
+ // Found when reading the resolver fragment, which can happen either when
464
+ // evaluating the resolver and it calls readFragment, or when checking if the
465
+ // inputs have changed since a previous evaluation:
466
+ let fragmentValue;
467
+ let fragmentReaderSelector;
468
+ const fragmentSeenRecordIDs = new Set();
469
+
470
+ const getDataForResolverFragment = singularReaderSelector => {
471
+ if (fragmentValue != null) {
472
+ // It was already read when checking for input staleness; no need to read it again.
473
+ // Note that the variables like fragmentSeenRecordIDs in the outer closure will have
474
+ // already been set and will still be used in this case.
475
+ return fragmentValue;
476
+ }
477
+ fragmentReaderSelector = singularReaderSelector;
478
+ const existingSeenRecords = this._seenRecords;
479
+ try {
480
+ this._seenRecords = fragmentSeenRecordIDs;
437
481
  const resolverFragmentData = {};
438
482
  this._createInlineDataOrResolverFragmentPointer(
439
483
  singularReaderSelector.node,
440
484
  record,
441
485
  resolverFragmentData,
442
486
  );
443
- const answer = resolverFragmentData[FRAGMENTS_KEY]?.[fragment.name];
487
+ fragmentValue = resolverFragmentData[FRAGMENTS_KEY]?.[fragment.name];
444
488
  invariant(
445
- typeof answer === 'object' && answer !== null,
489
+ typeof fragmentValue === 'object' && fragmentValue !== null,
446
490
  `Expected reader data to contain a __fragments property with a property for the fragment named ${fragment.name}, but it is missing.`,
447
491
  );
448
- return answer;
449
- },
492
+ return fragmentValue;
493
+ } finally {
494
+ this._seenRecords = existingSeenRecords;
495
+ }
450
496
  };
451
- const resolverResult = withResolverContext(resolverContext, () =>
452
- // $FlowFixMe[prop-missing] - resolver module's type signature is a lie
453
- resolverModule(key),
497
+ const resolverContext = {getDataForResolverFragment};
498
+
499
+ const [result, seenRecord] = this._resolverCache.readFromCacheOrEvaluate(
500
+ record,
501
+ field,
502
+ this._variables,
503
+ () => {
504
+ const key = {
505
+ __id: RelayModernRecord.getDataID(record),
506
+ __fragmentOwner: this._owner,
507
+ __fragments: {
508
+ [fragment.name]: {}, // Arguments to this fragment; not yet supported.
509
+ },
510
+ };
511
+ return withResolverContext(resolverContext, () => {
512
+ // $FlowFixMe[prop-missing] - resolver module's type signature is a lie
513
+ const resolverResult = resolverModule(key);
514
+ return {
515
+ resolverResult,
516
+ fragmentValue,
517
+ resolverID,
518
+ seenRecordIDs: fragmentSeenRecordIDs,
519
+ readerSelector: fragmentReaderSelector,
520
+ };
521
+ });
522
+ },
523
+ getDataForResolverFragment,
454
524
  );
455
- data[alias ?? name] = resolverResult;
456
- return resolverResult;
525
+ if (seenRecord != null) {
526
+ this._seenRecords.add(seenRecord);
527
+ }
528
+ data[storageKey] = result;
529
+ return result;
457
530
  }
458
531
 
459
532
  _readFlightField(
@@ -538,6 +611,42 @@ class RelayReader {
538
611
  return value;
539
612
  }
540
613
 
614
+ _readActorChange(
615
+ field: ReaderActorChange,
616
+ record: Record,
617
+ data: SelectorData,
618
+ ): ?mixed {
619
+ const applicationName = field.alias ?? field.name;
620
+ const storageKey = getStorageKey(field, this._variables);
621
+ const externalRef = RelayModernRecord.getActorLinkedRecordID(
622
+ record,
623
+ storageKey,
624
+ );
625
+
626
+ if (externalRef == null) {
627
+ data[applicationName] = externalRef;
628
+ if (externalRef === undefined) {
629
+ this._isMissingData = true;
630
+ }
631
+ return data[applicationName];
632
+ }
633
+ const [actorIdentifier, dataID] = externalRef;
634
+
635
+ const fragmentRef = {};
636
+ this._createFragmentPointer(
637
+ field.fragmentSpread,
638
+ {
639
+ __id: dataID,
640
+ },
641
+ fragmentRef,
642
+ );
643
+ data[applicationName] = {
644
+ __fragmentRef: fragmentRef,
645
+ __viewer: actorIdentifier,
646
+ };
647
+ return data[applicationName];
648
+ }
649
+
541
650
  _readPluralLink(
542
651
  field: ReaderLinkedField,
543
652
  record: Record,
@@ -620,7 +729,7 @@ class RelayReader {
620
729
  {
621
730
  kind: 'FragmentSpread',
622
731
  name: moduleImport.fragmentName,
623
- args: null,
732
+ args: moduleImport.args,
624
733
  },
625
734
  record,
626
735
  data,
@@ -651,12 +760,9 @@ class RelayReader {
651
760
  ? getArgumentValues(fragmentSpread.args, this._variables)
652
761
  : {};
653
762
  data[FRAGMENT_OWNER_KEY] = this._owner;
654
-
655
- if (RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT) {
656
- data[
657
- IS_WITHIN_UNMATCHED_TYPE_REFINEMENT
658
- ] = this._isWithinUnmatchedTypeRefinement;
659
- }
763
+ data[
764
+ IS_WITHIN_UNMATCHED_TYPE_REFINEMENT
765
+ ] = this._isWithinUnmatchedTypeRefinement;
660
766
  }
661
767
 
662
768
  _createInlineDataOrResolverFragmentPointer(
@@ -12,17 +12,83 @@
12
12
 
13
13
  'use strict';
14
14
 
15
- const RelayRecordSourceMapImpl = require('./RelayRecordSourceMapImpl');
15
+ const RelayRecordState = require('./RelayRecordState');
16
16
 
17
- import type {MutableRecordSource, RecordObjectMap} from './RelayStoreTypes';
17
+ import type {DataID} from '../util/RelayRuntimeTypes';
18
+ import type {RecordState} from './RelayRecordState';
19
+ import type {
20
+ MutableRecordSource,
21
+ Record,
22
+ RecordObjectMap,
23
+ } from './RelayStoreTypes';
18
24
 
19
- class RelayRecordSource {
20
- constructor(records?: RecordObjectMap): MutableRecordSource {
21
- return RelayRecordSource.create(records);
25
+ const {EXISTENT, NONEXISTENT, UNKNOWN} = RelayRecordState;
26
+
27
+ /**
28
+ * An implementation of the `MutableRecordSource` interface (defined in
29
+ * `RelayStoreTypes`) that holds all records in memory (JS Map).
30
+ */
31
+ class RelayRecordSource implements MutableRecordSource {
32
+ _records: Map<DataID, ?Record>;
33
+
34
+ constructor(records?: RecordObjectMap) {
35
+ this._records = new Map();
36
+ if (records != null) {
37
+ Object.keys(records).forEach(key => {
38
+ this._records.set(key, records[key]);
39
+ });
40
+ }
22
41
  }
23
42
 
24
43
  static create(records?: RecordObjectMap): MutableRecordSource {
25
- return new RelayRecordSourceMapImpl(records);
44
+ return new RelayRecordSource(records);
45
+ }
46
+
47
+ clear(): void {
48
+ this._records = new Map();
49
+ }
50
+
51
+ delete(dataID: DataID): void {
52
+ this._records.set(dataID, null);
53
+ }
54
+
55
+ get(dataID: DataID): ?Record {
56
+ return this._records.get(dataID);
57
+ }
58
+
59
+ getRecordIDs(): Array<DataID> {
60
+ return Array.from(this._records.keys());
61
+ }
62
+
63
+ getStatus(dataID: DataID): RecordState {
64
+ if (!this._records.has(dataID)) {
65
+ return UNKNOWN;
66
+ }
67
+ return this._records.get(dataID) == null ? NONEXISTENT : EXISTENT;
68
+ }
69
+
70
+ has(dataID: DataID): boolean {
71
+ return this._records.has(dataID);
72
+ }
73
+
74
+ remove(dataID: DataID): void {
75
+ this._records.delete(dataID);
76
+ }
77
+
78
+ set(dataID: DataID, record: Record): void {
79
+ this._records.set(dataID, record);
80
+ }
81
+
82
+ size(): number {
83
+ return this._records.size;
84
+ }
85
+
86
+ toJSON(): {[DataID]: ?Record, ...} {
87
+ const obj = {};
88
+ for (const [key, value] of this._records) {
89
+ obj[key] = value;
90
+ }
91
+ return obj;
26
92
  }
27
93
  }
28
94