relay-runtime 11.0.1 → 13.0.0-rc.1

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 (169) hide show
  1. package/handlers/RelayDefaultHandlerProvider.js.flow +2 -2
  2. package/handlers/connection/ConnectionHandler.js.flow +8 -17
  3. package/handlers/connection/MutationHandlers.js.flow +7 -11
  4. package/index.js +1 -1
  5. package/index.js.flow +60 -36
  6. package/lib/handlers/RelayDefaultHandlerProvider.js +1 -1
  7. package/lib/handlers/connection/ConnectionHandler.js +13 -19
  8. package/lib/handlers/connection/MutationHandlers.js +4 -7
  9. package/lib/index.js +58 -43
  10. package/lib/multi-actor-environment/ActorIdentifier.js +33 -0
  11. package/lib/multi-actor-environment/ActorSpecificEnvironment.js +152 -0
  12. package/lib/multi-actor-environment/ActorUtils.js +27 -0
  13. package/lib/multi-actor-environment/MultiActorEnvironment.js +419 -0
  14. package/lib/multi-actor-environment/MultiActorEnvironmentTypes.js +11 -0
  15. package/lib/multi-actor-environment/index.js +21 -0
  16. package/lib/mutations/RelayDeclarativeMutationConfig.js +4 -1
  17. package/lib/mutations/RelayRecordProxy.js +3 -2
  18. package/lib/mutations/RelayRecordSourceMutator.js +3 -2
  19. package/lib/mutations/RelayRecordSourceProxy.js +12 -4
  20. package/lib/mutations/RelayRecordSourceSelectorProxy.js +18 -5
  21. package/lib/mutations/applyOptimisticMutation.js +6 -6
  22. package/lib/mutations/commitMutation.js +14 -10
  23. package/lib/mutations/readUpdatableQuery_EXPERIMENTAL.js +238 -0
  24. package/lib/mutations/validateMutation.js +10 -5
  25. package/lib/network/ConvertToExecuteFunction.js +2 -1
  26. package/lib/network/RelayNetwork.js +3 -2
  27. package/lib/network/RelayQueryResponseCache.js +21 -5
  28. package/lib/network/wrapNetworkWithLogObserver.js +79 -0
  29. package/lib/query/GraphQLTag.js +3 -2
  30. package/lib/query/fetchQuery.js +6 -5
  31. package/lib/query/fetchQueryInternal.js +1 -1
  32. package/lib/query/fetchQuery_DEPRECATED.js +2 -1
  33. package/lib/store/ClientID.js +7 -1
  34. package/lib/store/DataChecker.js +123 -54
  35. package/lib/store/{RelayModernQueryExecutor.js → OperationExecutor.js} +518 -200
  36. package/lib/store/RelayConcreteVariables.js +26 -8
  37. package/lib/store/RelayExperimentalGraphResponseHandler.js +153 -0
  38. package/lib/store/RelayExperimentalGraphResponseTransform.js +391 -0
  39. package/lib/store/RelayModernEnvironment.js +175 -240
  40. package/lib/store/RelayModernFragmentSpecResolver.js +52 -26
  41. package/lib/store/RelayModernOperationDescriptor.js +2 -1
  42. package/lib/store/RelayModernRecord.js +47 -12
  43. package/lib/store/RelayModernSelector.js +14 -8
  44. package/lib/store/RelayModernStore.js +56 -28
  45. package/lib/store/RelayOperationTracker.js +34 -24
  46. package/lib/store/RelayPublishQueue.js +41 -13
  47. package/lib/store/RelayReader.js +288 -48
  48. package/lib/store/RelayRecordSource.js +87 -3
  49. package/lib/store/RelayReferenceMarker.js +34 -22
  50. package/lib/store/RelayResponseNormalizer.js +211 -110
  51. package/lib/store/RelayStoreReactFlightUtils.js +4 -10
  52. package/lib/store/RelayStoreSubscriptions.js +14 -9
  53. package/lib/store/RelayStoreUtils.js +12 -7
  54. package/lib/store/ResolverCache.js +213 -0
  55. package/lib/store/ResolverFragments.js +61 -0
  56. package/lib/store/cloneRelayHandleSourceField.js +5 -4
  57. package/lib/store/cloneRelayScalarHandleSourceField.js +5 -4
  58. package/lib/store/createRelayContext.js +4 -2
  59. package/lib/store/readInlineData.js +6 -2
  60. package/lib/subscription/requestSubscription.js +34 -25
  61. package/lib/util/RelayConcreteNode.js +3 -0
  62. package/lib/util/RelayFeatureFlags.js +10 -4
  63. package/lib/util/RelayProfiler.js +17 -187
  64. package/lib/util/RelayReplaySubject.js +22 -7
  65. package/lib/util/RelayRuntimeTypes.js +0 -6
  66. package/lib/util/StringInterner.js +71 -0
  67. package/lib/util/getFragmentIdentifier.js +15 -7
  68. package/lib/util/getOperation.js +2 -1
  69. package/lib/util/getPaginationMetadata.js +41 -0
  70. package/lib/util/getPaginationVariables.js +66 -0
  71. package/lib/util/getPendingOperationsForFragment.js +55 -0
  72. package/lib/util/getRefetchMetadata.js +36 -0
  73. package/lib/util/getRelayHandleKey.js +2 -2
  74. package/lib/util/getRequestIdentifier.js +2 -2
  75. package/lib/util/getValueAtPath.js +51 -0
  76. package/lib/util/isEmptyObject.js +1 -1
  77. package/lib/util/registerEnvironmentWithDevTools.js +26 -0
  78. package/lib/util/withDuration.js +31 -0
  79. package/multi-actor-environment/ActorIdentifier.js.flow +43 -0
  80. package/multi-actor-environment/ActorSpecificEnvironment.js.flow +225 -0
  81. package/multi-actor-environment/ActorUtils.js.flow +33 -0
  82. package/multi-actor-environment/MultiActorEnvironment.js.flow +506 -0
  83. package/multi-actor-environment/MultiActorEnvironmentTypes.js.flow +261 -0
  84. package/multi-actor-environment/index.js.flow +26 -0
  85. package/mutations/RelayDeclarativeMutationConfig.js.flow +32 -26
  86. package/mutations/RelayRecordProxy.js.flow +4 -5
  87. package/mutations/RelayRecordSourceMutator.js.flow +4 -6
  88. package/mutations/RelayRecordSourceProxy.js.flow +19 -10
  89. package/mutations/RelayRecordSourceSelectorProxy.js.flow +22 -7
  90. package/mutations/applyOptimisticMutation.js.flow +13 -14
  91. package/mutations/commitLocalUpdate.js.flow +1 -1
  92. package/mutations/commitMutation.js.flow +35 -46
  93. package/mutations/readUpdatableQuery_EXPERIMENTAL.js.flow +309 -0
  94. package/mutations/validateMutation.js.flow +26 -16
  95. package/network/ConvertToExecuteFunction.js.flow +2 -2
  96. package/network/RelayNetwork.js.flow +4 -5
  97. package/network/RelayNetworkTypes.js.flow +5 -4
  98. package/network/RelayObservable.js.flow +1 -1
  99. package/network/RelayQueryResponseCache.js.flow +34 -21
  100. package/network/wrapNetworkWithLogObserver.js.flow +100 -0
  101. package/package.json +3 -2
  102. package/query/GraphQLTag.js.flow +9 -9
  103. package/query/PreloadableQueryRegistry.js.flow +2 -1
  104. package/query/fetchQuery.js.flow +11 -13
  105. package/query/fetchQueryInternal.js.flow +6 -9
  106. package/query/fetchQuery_DEPRECATED.js.flow +6 -6
  107. package/relay-runtime.js +2 -2
  108. package/relay-runtime.min.js +2 -2
  109. package/store/ClientID.js.flow +14 -3
  110. package/store/DataChecker.js.flow +141 -59
  111. package/store/{RelayModernQueryExecutor.js.flow → OperationExecutor.js.flow} +605 -303
  112. package/store/RelayConcreteVariables.js.flow +27 -8
  113. package/store/RelayExperimentalGraphResponseHandler.js.flow +124 -0
  114. package/store/RelayExperimentalGraphResponseTransform.js.flow +475 -0
  115. package/store/RelayModernEnvironment.js.flow +173 -240
  116. package/store/RelayModernFragmentSpecResolver.js.flow +55 -31
  117. package/store/RelayModernOperationDescriptor.js.flow +12 -7
  118. package/store/RelayModernRecord.js.flow +67 -11
  119. package/store/RelayModernSelector.js.flow +24 -14
  120. package/store/RelayModernStore.js.flow +66 -36
  121. package/store/RelayOperationTracker.js.flow +59 -43
  122. package/store/RelayOptimisticRecordSource.js.flow +2 -2
  123. package/store/RelayPublishQueue.js.flow +79 -34
  124. package/store/RelayReader.js.flow +351 -73
  125. package/store/RelayRecordSource.js.flow +72 -6
  126. package/store/RelayReferenceMarker.js.flow +40 -26
  127. package/store/RelayResponseNormalizer.js.flow +258 -99
  128. package/store/RelayStoreReactFlightUtils.js.flow +4 -11
  129. package/store/RelayStoreSubscriptions.js.flow +19 -11
  130. package/store/RelayStoreTypes.js.flow +209 -43
  131. package/store/RelayStoreUtils.js.flow +24 -11
  132. package/store/ResolverCache.js.flow +249 -0
  133. package/store/ResolverFragments.js.flow +121 -0
  134. package/store/StoreInspector.js.flow +2 -2
  135. package/store/TypeID.js.flow +1 -1
  136. package/store/ViewerPattern.js.flow +2 -2
  137. package/store/cloneRelayHandleSourceField.js.flow +5 -6
  138. package/store/cloneRelayScalarHandleSourceField.js.flow +5 -6
  139. package/store/createFragmentSpecResolver.js.flow +3 -4
  140. package/store/createRelayContext.js.flow +3 -3
  141. package/store/normalizeRelayPayload.js.flow +6 -7
  142. package/store/readInlineData.js.flow +7 -8
  143. package/subscription/requestSubscription.js.flow +53 -41
  144. package/util/NormalizationNode.js.flow +10 -3
  145. package/util/ReaderNode.js.flow +38 -2
  146. package/util/RelayConcreteNode.js.flow +5 -0
  147. package/util/RelayFeatureFlags.js.flow +24 -10
  148. package/util/RelayProfiler.js.flow +22 -194
  149. package/util/RelayReplaySubject.js.flow +9 -9
  150. package/util/RelayRuntimeTypes.js.flow +72 -3
  151. package/util/StringInterner.js.flow +69 -0
  152. package/util/createPayloadFor3DField.js.flow +3 -3
  153. package/util/getFragmentIdentifier.js.flow +27 -15
  154. package/util/getOperation.js.flow +2 -2
  155. package/util/getPaginationMetadata.js.flow +72 -0
  156. package/util/getPaginationVariables.js.flow +108 -0
  157. package/util/getPendingOperationsForFragment.js.flow +62 -0
  158. package/util/getRefetchMetadata.js.flow +79 -0
  159. package/util/getRelayHandleKey.js.flow +1 -2
  160. package/util/getRequestIdentifier.js.flow +3 -3
  161. package/util/getValueAtPath.js.flow +46 -0
  162. package/util/isEmptyObject.js.flow +1 -0
  163. package/util/registerEnvironmentWithDevTools.js.flow +33 -0
  164. package/util/resolveImmediate.js.flow +1 -1
  165. package/util/withDuration.js.flow +32 -0
  166. package/lib/store/RelayRecordSourceMapImpl.js +0 -107
  167. package/lib/store/RelayStoreSubscriptionsUsingMapByID.js +0 -318
  168. package/store/RelayRecordSourceMapImpl.js.flow +0 -91
  169. package/store/RelayStoreSubscriptionsUsingMapByID.js.flow +0 -283
@@ -12,12 +12,39 @@
12
12
 
13
13
  'use strict';
14
14
 
15
- const RelayFeatureFlags = require('../util/RelayFeatureFlags');
16
- const RelayModernRecord = require('./RelayModernRecord');
17
-
18
- const invariant = require('invariant');
15
+ import type {
16
+ ReaderActorChange,
17
+ ReaderClientEdge,
18
+ ReaderFlightField,
19
+ ReaderFragment,
20
+ ReaderFragmentSpread,
21
+ ReaderInlineDataFragmentSpread,
22
+ ReaderLinkedField,
23
+ ReaderModuleImport,
24
+ ReaderNode,
25
+ ReaderRelayResolver,
26
+ ReaderRequiredField,
27
+ ReaderScalarField,
28
+ ReaderSelection,
29
+ } from '../util/ReaderNode';
30
+ import type {DataID, Variables} from '../util/RelayRuntimeTypes';
31
+ import type {
32
+ ClientEdgeTraversalInfo,
33
+ DataIDSet,
34
+ MissingClientEdgeRequestInfo,
35
+ MissingRequiredFields,
36
+ Record,
37
+ RecordSource,
38
+ RequestDescriptor,
39
+ SelectorData,
40
+ SingularReaderSelector,
41
+ Snapshot,
42
+ } from './RelayStoreTypes';
43
+ import type {ResolverCache} from './ResolverCache';
19
44
 
20
45
  const {
46
+ ACTOR_CHANGE,
47
+ CLIENT_EDGE,
21
48
  CLIENT_EXTENSION,
22
49
  CONDITION,
23
50
  DEFER,
@@ -27,53 +54,43 @@ const {
27
54
  INLINE_FRAGMENT,
28
55
  LINKED_FIELD,
29
56
  MODULE_IMPORT,
57
+ RELAY_RESOLVER,
30
58
  REQUIRED_FIELD,
31
59
  SCALAR_FIELD,
32
60
  STREAM,
33
61
  } = require('../util/RelayConcreteNode');
62
+ const RelayFeatureFlags = require('../util/RelayFeatureFlags');
63
+ const ClientID = require('./ClientID');
64
+ const RelayModernRecord = require('./RelayModernRecord');
34
65
  const {getReactFlightClientResponse} = require('./RelayStoreReactFlightUtils');
35
66
  const {
36
- FRAGMENTS_KEY,
67
+ CLIENT_EDGE_TRAVERSAL_PATH,
37
68
  FRAGMENT_OWNER_KEY,
38
69
  FRAGMENT_PROP_NAME_KEY,
70
+ FRAGMENTS_KEY,
39
71
  ID_KEY,
40
72
  IS_WITHIN_UNMATCHED_TYPE_REFINEMENT,
41
73
  MODULE_COMPONENT_KEY,
42
74
  ROOT_ID,
43
75
  getArgumentValues,
44
- getStorageKey,
45
76
  getModuleComponentKey,
77
+ getStorageKey,
46
78
  } = require('./RelayStoreUtils');
79
+ const {NoopResolverCache} = require('./ResolverCache');
80
+ const {withResolverContext} = require('./ResolverFragments');
47
81
  const {generateTypeID} = require('./TypeID');
48
-
49
- import type {
50
- ReaderFlightField,
51
- ReaderFragmentSpread,
52
- ReaderInlineDataFragmentSpread,
53
- ReaderLinkedField,
54
- ReaderModuleImport,
55
- ReaderNode,
56
- ReaderRequiredField,
57
- ReaderScalarField,
58
- ReaderSelection,
59
- } from '../util/ReaderNode';
60
- import type {DataID, Variables} from '../util/RelayRuntimeTypes';
61
- import type {
62
- Record,
63
- RecordSource,
64
- RequestDescriptor,
65
- SelectorData,
66
- SingularReaderSelector,
67
- Snapshot,
68
- MissingRequiredFields,
69
- DataIDSet,
70
- } from './RelayStoreTypes';
82
+ const invariant = require('invariant');
71
83
 
72
84
  function read(
73
85
  recordSource: RecordSource,
74
86
  selector: SingularReaderSelector,
87
+ resolverCache?: ResolverCache,
75
88
  ): Snapshot {
76
- const reader = new RelayReader(recordSource, selector);
89
+ const reader = new RelayReader(
90
+ recordSource,
91
+ selector,
92
+ resolverCache ?? new NoopResolverCache(),
93
+ );
77
94
  return reader.read();
78
95
  }
79
96
 
@@ -81,7 +98,9 @@ function read(
81
98
  * @private
82
99
  */
83
100
  class RelayReader {
101
+ _clientEdgeTraversalPath: Array<ClientEdgeTraversalInfo | null>;
84
102
  _isMissingData: boolean;
103
+ _missingClientEdges: Array<MissingClientEdgeRequestInfo>;
85
104
  _isWithinUnmatchedTypeRefinement: boolean;
86
105
  _missingRequiredFields: ?MissingRequiredFields;
87
106
  _owner: RequestDescriptor;
@@ -89,8 +108,19 @@ class RelayReader {
89
108
  _seenRecords: DataIDSet;
90
109
  _selector: SingularReaderSelector;
91
110
  _variables: Variables;
111
+ _resolverCache: ResolverCache;
92
112
 
93
- constructor(recordSource: RecordSource, selector: SingularReaderSelector) {
113
+ constructor(
114
+ recordSource: RecordSource,
115
+ selector: SingularReaderSelector,
116
+ resolverCache: ResolverCache,
117
+ ) {
118
+ this._clientEdgeTraversalPath =
119
+ RelayFeatureFlags.ENABLE_CLIENT_EDGES &&
120
+ selector.clientEdgeTraversalPath?.length
121
+ ? [...selector.clientEdgeTraversalPath]
122
+ : [];
123
+ this._missingClientEdges = [];
94
124
  this._isMissingData = false;
95
125
  this._isWithinUnmatchedTypeRefinement = false;
96
126
  this._missingRequiredFields = null;
@@ -99,6 +129,7 @@ class RelayReader {
99
129
  this._seenRecords = new Set();
100
130
  this._selector = selector;
101
131
  this._variables = selector.variables;
132
+ this._resolverCache = resolverCache;
102
133
  }
103
134
 
104
135
  read(): Snapshot {
@@ -122,7 +153,18 @@ class RelayReader {
122
153
  // match, then no data is expected to be present.
123
154
  if (isDataExpectedToBePresent && abstractKey == null && record != null) {
124
155
  const recordType = RelayModernRecord.getType(record);
125
- if (recordType !== node.type && dataID !== ROOT_ID) {
156
+ if (
157
+ recordType !== node.type &&
158
+ // The root record type is a special `__Root` type and may not match the
159
+ // type on the ast, so ignore type mismatches at the root.
160
+ // We currently detect whether we're at the root by checking against ROOT_ID,
161
+ // but this does not work for mutations/subscriptions which generate unique
162
+ // root ids. This is acceptable in practice as we don't read data for mutations/
163
+ // subscriptions in a situation where we would use isMissingData to decide whether
164
+ // to suspend or not.
165
+ // TODO T96653810: Correctly detect reading from root of mutation/subscription
166
+ dataID !== ROOT_ID
167
+ ) {
126
168
  isDataExpectedToBePresent = false;
127
169
  }
128
170
  }
@@ -131,12 +173,7 @@ class RelayReader {
131
173
  // then data is only expected to be present if the record type is known to
132
174
  // implement the interface. If we aren't sure whether the record implements
133
175
  // the interface, that itself constitutes "expected" data being missing.
134
- if (
135
- isDataExpectedToBePresent &&
136
- abstractKey != null &&
137
- record != null &&
138
- RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT
139
- ) {
176
+ if (isDataExpectedToBePresent && abstractKey != null && record != null) {
140
177
  const recordType = RelayModernRecord.getType(record);
141
178
  const typeID = generateTypeID(recordType);
142
179
  const typeRecord = this._recordSource.get(typeID);
@@ -158,12 +195,36 @@ class RelayReader {
158
195
  return {
159
196
  data,
160
197
  isMissingData: this._isMissingData && isDataExpectedToBePresent,
198
+ missingClientEdges:
199
+ RelayFeatureFlags.ENABLE_CLIENT_EDGES && this._missingClientEdges.length
200
+ ? this._missingClientEdges
201
+ : null,
161
202
  seenRecords: this._seenRecords,
162
203
  selector: this._selector,
163
204
  missingRequiredFields: this._missingRequiredFields,
164
205
  };
165
206
  }
166
207
 
208
+ _markDataAsMissing(): void {
209
+ this._isMissingData = true;
210
+ if (
211
+ RelayFeatureFlags.ENABLE_CLIENT_EDGES &&
212
+ this._clientEdgeTraversalPath.length
213
+ ) {
214
+ const top =
215
+ this._clientEdgeTraversalPath[this._clientEdgeTraversalPath.length - 1];
216
+ // Top can be null if we've traversed past a client edge into an ordinary
217
+ // client extension field; we never want to fetch in response to missing
218
+ // data off of a client extension field.
219
+ if (top !== null) {
220
+ this._missingClientEdges.push({
221
+ request: top.readerClientEdge.operation,
222
+ clientEdgeDestinationID: top.clientEdgeDestinationID,
223
+ });
224
+ }
225
+ }
226
+ }
227
+
167
228
  _traverse(
168
229
  node: ReaderNode,
169
230
  dataID: DataID,
@@ -173,7 +234,7 @@ class RelayReader {
173
234
  this._seenRecords.add(dataID);
174
235
  if (record == null) {
175
236
  if (record === undefined) {
176
- this._isMissingData = true;
237
+ this._markDataAsMissing();
177
238
  }
178
239
  return record;
179
240
  }
@@ -192,7 +253,6 @@ class RelayReader {
192
253
  'RelayReader(): Undefined variable `%s`.',
193
254
  name,
194
255
  );
195
- // $FlowFixMe[cannot-write]
196
256
  return this._variables[name];
197
257
  }
198
258
 
@@ -263,7 +323,9 @@ class RelayReader {
263
323
  }
264
324
  break;
265
325
  case CONDITION:
266
- const conditionValue = this._getVariableValue(selection.condition);
326
+ const conditionValue = Boolean(
327
+ this._getVariableValue(selection.condition),
328
+ );
267
329
  if (conditionValue === selection.passingValue) {
268
330
  const hasExpectedData = this._traverseSelections(
269
331
  selection.selections,
@@ -290,15 +352,15 @@ class RelayReader {
290
352
  return false;
291
353
  }
292
354
  }
293
- } else if (RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT) {
355
+ } else {
294
356
  // Similar to the logic in read(): data is only expected to be present
295
357
  // if the record is known to conform to the interface. If we don't know
296
358
  // whether the type conforms or not, that constitutes missing data.
297
359
 
298
360
  // store flags to reset after reading
299
361
  const parentIsMissingData = this._isMissingData;
300
- const parentIsWithinUnmatchedTypeRefinement = this
301
- ._isWithinUnmatchedTypeRefinement;
362
+ const parentIsWithinUnmatchedTypeRefinement =
363
+ this._isWithinUnmatchedTypeRefinement;
302
364
 
303
365
  const typeName = RelayModernRecord.getType(record);
304
366
  const typeID = generateTypeID(typeName);
@@ -311,22 +373,26 @@ class RelayReader {
311
373
  parentIsWithinUnmatchedTypeRefinement ||
312
374
  implementsInterface === false;
313
375
  this._traverseSelections(selection.selections, record, data);
314
- this._isWithinUnmatchedTypeRefinement = parentIsWithinUnmatchedTypeRefinement;
376
+ this._isWithinUnmatchedTypeRefinement =
377
+ parentIsWithinUnmatchedTypeRefinement;
315
378
 
316
379
  if (implementsInterface === false) {
317
380
  // Type known to not implement the interface, no data expected
318
381
  this._isMissingData = parentIsMissingData;
319
382
  } else if (implementsInterface == null) {
320
383
  // Don't know if the type implements the interface or not
321
- this._isMissingData = true;
384
+ this._markDataAsMissing();
322
385
  }
323
- } else {
324
- // legacy behavior for abstract refinements: always read even
325
- // if the type doesn't conform and don't reset isMissingData
326
- this._traverseSelections(selection.selections, record, data);
327
386
  }
328
387
  break;
329
388
  }
389
+ case RELAY_RESOLVER: {
390
+ if (!RelayFeatureFlags.ENABLE_RELAY_RESOLVERS) {
391
+ throw new Error('Relay Resolver fields are not yet supported.');
392
+ }
393
+ this._readResolverField(selection, record, data);
394
+ break;
395
+ }
330
396
  case FRAGMENT_SPREAD:
331
397
  this._createFragmentPointer(selection, record, data);
332
398
  break;
@@ -334,17 +400,33 @@ class RelayReader {
334
400
  this._readModuleImport(selection, record, data);
335
401
  break;
336
402
  case INLINE_DATA_FRAGMENT_SPREAD:
337
- this._createInlineDataFragmentPointer(selection, record, data);
403
+ this._createInlineDataOrResolverFragmentPointer(
404
+ selection,
405
+ record,
406
+ data,
407
+ );
338
408
  break;
339
409
  case DEFER:
340
410
  case CLIENT_EXTENSION: {
341
411
  const isMissingData = this._isMissingData;
412
+ const alreadyMissingClientEdges = this._missingClientEdges.length;
413
+ if (RelayFeatureFlags.ENABLE_CLIENT_EDGES) {
414
+ this._clientEdgeTraversalPath.push(null);
415
+ }
342
416
  const hasExpectedData = this._traverseSelections(
343
417
  selection.selections,
344
418
  record,
345
419
  data,
346
420
  );
347
- this._isMissingData = isMissingData;
421
+ // The only case where we want to suspend due to missing data off of
422
+ // a client extension is if we reached a client edge that we might be
423
+ // able to fetch:
424
+ this._isMissingData =
425
+ isMissingData ||
426
+ this._missingClientEdges.length > alreadyMissingClientEdges;
427
+ if (RelayFeatureFlags.ENABLE_CLIENT_EDGES) {
428
+ this._clientEdgeTraversalPath.pop();
429
+ }
348
430
  if (!hasExpectedData) {
349
431
  return false;
350
432
  }
@@ -368,6 +450,16 @@ class RelayReader {
368
450
  throw new Error('Flight fields are not yet supported.');
369
451
  }
370
452
  break;
453
+ case ACTOR_CHANGE:
454
+ this._readActorChange(selection, record, data);
455
+ break;
456
+ case CLIENT_EDGE:
457
+ if (RelayFeatureFlags.ENABLE_CLIENT_EDGES) {
458
+ this._readClientEdge(selection, record, data);
459
+ } else {
460
+ throw new Error('Client edges are not yet supported.');
461
+ }
462
+ break;
371
463
  default:
372
464
  (selection: empty);
373
465
  invariant(
@@ -394,6 +486,12 @@ class RelayReader {
394
486
  } else {
395
487
  return this._readLink(selection.field, record, data);
396
488
  }
489
+ case RELAY_RESOLVER:
490
+ if (!RelayFeatureFlags.ENABLE_RELAY_RESOLVERS) {
491
+ throw new Error('Relay Resolver fields are not yet supported.');
492
+ }
493
+ this._readResolverField(selection.field, record, data);
494
+ break;
397
495
  default:
398
496
  (selection.field.kind: empty);
399
497
  invariant(
@@ -404,6 +502,145 @@ class RelayReader {
404
502
  }
405
503
  }
406
504
 
505
+ _readResolverField(
506
+ field: ReaderRelayResolver,
507
+ record: Record,
508
+ data: SelectorData,
509
+ ): void {
510
+ const {resolverModule, fragment} = field;
511
+ const storageKey = getStorageKey(field, this._variables);
512
+ const resolverID = ClientID.generateClientID(
513
+ RelayModernRecord.getDataID(record),
514
+ storageKey,
515
+ );
516
+
517
+ // Found when reading the resolver fragment, which can happen either when
518
+ // evaluating the resolver and it calls readFragment, or when checking if the
519
+ // inputs have changed since a previous evaluation:
520
+ let fragmentValue;
521
+ let fragmentReaderSelector;
522
+ const fragmentSeenRecordIDs = new Set();
523
+
524
+ const getDataForResolverFragment = singularReaderSelector => {
525
+ if (fragmentValue != null) {
526
+ // It was already read when checking for input staleness; no need to read it again.
527
+ // Note that the variables like fragmentSeenRecordIDs in the outer closure will have
528
+ // already been set and will still be used in this case.
529
+ return fragmentValue;
530
+ }
531
+ fragmentReaderSelector = singularReaderSelector;
532
+ const existingSeenRecords = this._seenRecords;
533
+ try {
534
+ this._seenRecords = fragmentSeenRecordIDs;
535
+ const resolverFragmentData = {};
536
+ this._createInlineDataOrResolverFragmentPointer(
537
+ singularReaderSelector.node,
538
+ record,
539
+ resolverFragmentData,
540
+ );
541
+ fragmentValue = resolverFragmentData[FRAGMENTS_KEY]?.[fragment.name];
542
+ invariant(
543
+ typeof fragmentValue === 'object' && fragmentValue !== null,
544
+ `Expected reader data to contain a __fragments property with a property for the fragment named ${fragment.name}, but it is missing.`,
545
+ );
546
+ return fragmentValue;
547
+ } finally {
548
+ this._seenRecords = existingSeenRecords;
549
+ }
550
+ };
551
+ const resolverContext = {getDataForResolverFragment};
552
+
553
+ const [result, seenRecord] = this._resolverCache.readFromCacheOrEvaluate(
554
+ record,
555
+ field,
556
+ this._variables,
557
+ () => {
558
+ const key = {
559
+ __id: RelayModernRecord.getDataID(record),
560
+ __fragmentOwner: this._owner,
561
+ __fragments: {
562
+ [fragment.name]: {}, // Arguments to this fragment; not yet supported.
563
+ },
564
+ };
565
+ return withResolverContext(resolverContext, () => {
566
+ // $FlowFixMe[prop-missing] - resolver module's type signature is a lie
567
+ const resolverResult = resolverModule(key);
568
+ return {
569
+ resolverResult,
570
+ fragmentValue,
571
+ resolverID,
572
+ seenRecordIDs: fragmentSeenRecordIDs,
573
+ readerSelector: fragmentReaderSelector,
574
+ };
575
+ });
576
+ },
577
+ getDataForResolverFragment,
578
+ );
579
+ if (seenRecord != null) {
580
+ this._seenRecords.add(seenRecord);
581
+ }
582
+
583
+ const applicationName = field.alias ?? field.name;
584
+ data[applicationName] = result;
585
+ }
586
+
587
+ _readClientEdge(
588
+ field: ReaderClientEdge,
589
+ record: Record,
590
+ data: SelectorData,
591
+ ): void {
592
+ const backingField = field.backingField;
593
+
594
+ // Because ReaderClientExtension doesn't have `alias` or `name` and so I don't know
595
+ // how to get its applicationName or storageKey yet:
596
+ invariant(
597
+ backingField.kind !== 'ClientExtension',
598
+ 'Client extension client edges are not yet implemented.',
599
+ );
600
+
601
+ const applicationName = backingField.alias ?? backingField.name;
602
+
603
+ const backingFieldData = {};
604
+ this._traverseSelections([backingField], record, backingFieldData);
605
+ const destinationDataID = backingFieldData[applicationName];
606
+
607
+ if (destinationDataID == null) {
608
+ data[applicationName] = destinationDataID;
609
+ return;
610
+ }
611
+
612
+ invariant(
613
+ typeof destinationDataID === 'string',
614
+ 'Plural client edges not are yet implemented',
615
+ ); // FIXME support plural
616
+
617
+ // Not wrapping the push/pop in a try/finally because if we throw, the
618
+ // Reader object is not usable after that anyway.
619
+ this._clientEdgeTraversalPath.push({
620
+ readerClientEdge: field,
621
+ clientEdgeDestinationID: destinationDataID,
622
+ });
623
+
624
+ const prevData = data[applicationName];
625
+ invariant(
626
+ prevData == null || typeof prevData === 'object',
627
+ 'RelayReader(): Expected data for field `%s` on record `%s` ' +
628
+ 'to be an object, got `%s`.',
629
+ applicationName,
630
+ RelayModernRecord.getDataID(record),
631
+ prevData,
632
+ );
633
+ const value = this._traverse(
634
+ field.linkedField,
635
+ destinationDataID,
636
+ // $FlowFixMe[incompatible-variance]
637
+ prevData,
638
+ );
639
+ data[applicationName] = value;
640
+
641
+ this._clientEdgeTraversalPath.pop();
642
+ }
643
+
407
644
  _readFlightField(
408
645
  field: ReaderFlightField,
409
646
  record: Record,
@@ -411,14 +648,12 @@ class RelayReader {
411
648
  ): ?mixed {
412
649
  const applicationName = field.alias ?? field.name;
413
650
  const storageKey = getStorageKey(field, this._variables);
414
- const reactFlightClientResponseRecordID = RelayModernRecord.getLinkedRecordID(
415
- record,
416
- storageKey,
417
- );
651
+ const reactFlightClientResponseRecordID =
652
+ RelayModernRecord.getLinkedRecordID(record, storageKey);
418
653
  if (reactFlightClientResponseRecordID == null) {
419
654
  data[applicationName] = reactFlightClientResponseRecordID;
420
655
  if (reactFlightClientResponseRecordID === undefined) {
421
- this._isMissingData = true;
656
+ this._markDataAsMissing();
422
657
  }
423
658
  return reactFlightClientResponseRecordID;
424
659
  }
@@ -429,7 +664,7 @@ class RelayReader {
429
664
  if (reactFlightClientResponseRecord == null) {
430
665
  data[applicationName] = reactFlightClientResponseRecord;
431
666
  if (reactFlightClientResponseRecord === undefined) {
432
- this._isMissingData = true;
667
+ this._markDataAsMissing();
433
668
  }
434
669
  return reactFlightClientResponseRecord;
435
670
  }
@@ -449,7 +684,7 @@ class RelayReader {
449
684
  const storageKey = getStorageKey(field, this._variables);
450
685
  const value = RelayModernRecord.getValue(record, storageKey);
451
686
  if (value === undefined) {
452
- this._isMissingData = true;
687
+ this._markDataAsMissing();
453
688
  }
454
689
  data[applicationName] = value;
455
690
  return value;
@@ -466,7 +701,7 @@ class RelayReader {
466
701
  if (linkedID == null) {
467
702
  data[applicationName] = linkedID;
468
703
  if (linkedID === undefined) {
469
- this._isMissingData = true;
704
+ this._markDataAsMissing();
470
705
  }
471
706
  return linkedID;
472
707
  }
@@ -486,6 +721,42 @@ class RelayReader {
486
721
  return value;
487
722
  }
488
723
 
724
+ _readActorChange(
725
+ field: ReaderActorChange,
726
+ record: Record,
727
+ data: SelectorData,
728
+ ): ?mixed {
729
+ const applicationName = field.alias ?? field.name;
730
+ const storageKey = getStorageKey(field, this._variables);
731
+ const externalRef = RelayModernRecord.getActorLinkedRecordID(
732
+ record,
733
+ storageKey,
734
+ );
735
+
736
+ if (externalRef == null) {
737
+ data[applicationName] = externalRef;
738
+ if (externalRef === undefined) {
739
+ this._markDataAsMissing();
740
+ }
741
+ return data[applicationName];
742
+ }
743
+ const [actorIdentifier, dataID] = externalRef;
744
+
745
+ const fragmentRef = {};
746
+ this._createFragmentPointer(
747
+ field.fragmentSpread,
748
+ {
749
+ __id: dataID,
750
+ },
751
+ fragmentRef,
752
+ );
753
+ data[applicationName] = {
754
+ __fragmentRef: fragmentRef,
755
+ __viewer: actorIdentifier,
756
+ };
757
+ return data[applicationName];
758
+ }
759
+
489
760
  _readPluralLink(
490
761
  field: ReaderLinkedField,
491
762
  record: Record,
@@ -498,7 +769,7 @@ class RelayReader {
498
769
  if (linkedIDs == null) {
499
770
  data[applicationName] = linkedIDs;
500
771
  if (linkedIDs === undefined) {
501
- this._isMissingData = true;
772
+ this._markDataAsMissing();
502
773
  }
503
774
  return linkedIDs;
504
775
  }
@@ -516,7 +787,7 @@ class RelayReader {
516
787
  linkedIDs.forEach((linkedID, nextIndex) => {
517
788
  if (linkedID == null) {
518
789
  if (linkedID === undefined) {
519
- this._isMissingData = true;
790
+ this._markDataAsMissing();
520
791
  }
521
792
  // $FlowFixMe[cannot-write]
522
793
  linkedArray[nextIndex] = linkedID;
@@ -554,7 +825,7 @@ class RelayReader {
554
825
  const component = RelayModernRecord.getValue(record, componentKey);
555
826
  if (component == null) {
556
827
  if (component === undefined) {
557
- this._isMissingData = true;
828
+ this._markDataAsMissing();
558
829
  }
559
830
  return;
560
831
  }
@@ -568,7 +839,7 @@ class RelayReader {
568
839
  {
569
840
  kind: 'FragmentSpread',
570
841
  name: moduleImport.fragmentName,
571
- args: null,
842
+ args: moduleImport.args,
572
843
  },
573
844
  record,
574
845
  data,
@@ -599,16 +870,23 @@ class RelayReader {
599
870
  ? getArgumentValues(fragmentSpread.args, this._variables)
600
871
  : {};
601
872
  data[FRAGMENT_OWNER_KEY] = this._owner;
602
-
603
- if (RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT) {
604
- data[
605
- IS_WITHIN_UNMATCHED_TYPE_REFINEMENT
606
- ] = this._isWithinUnmatchedTypeRefinement;
873
+ data[IS_WITHIN_UNMATCHED_TYPE_REFINEMENT] =
874
+ this._isWithinUnmatchedTypeRefinement;
875
+
876
+ if (RelayFeatureFlags.ENABLE_CLIENT_EDGES) {
877
+ if (
878
+ this._clientEdgeTraversalPath.length > 0 &&
879
+ this._clientEdgeTraversalPath[
880
+ this._clientEdgeTraversalPath.length - 1
881
+ ] !== null
882
+ ) {
883
+ data[CLIENT_EDGE_TRAVERSAL_PATH] = [...this._clientEdgeTraversalPath];
884
+ }
607
885
  }
608
886
  }
609
887
 
610
- _createInlineDataFragmentPointer(
611
- inlineDataFragmentSpread: ReaderInlineDataFragmentSpread,
888
+ _createInlineDataOrResolverFragmentPointer(
889
+ fragmentSpreadOrFragment: ReaderInlineDataFragmentSpread | ReaderFragment,
612
890
  record: Record,
613
891
  data: SelectorData,
614
892
  ): void {
@@ -626,12 +904,12 @@ class RelayReader {
626
904
  }
627
905
  const inlineData = {};
628
906
  this._traverseSelections(
629
- inlineDataFragmentSpread.selections,
907
+ fragmentSpreadOrFragment.selections,
630
908
  record,
631
909
  inlineData,
632
910
  );
633
911
  // $FlowFixMe[cannot-write] - writing into read-only field
634
- fragmentPointers[inlineDataFragmentSpread.name] = inlineData;
912
+ fragmentPointers[fragmentSpreadOrFragment.name] = inlineData;
635
913
  }
636
914
  }
637
915