relay-runtime 11.0.0 → 13.0.0-rc.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 (169) hide show
  1. package/handlers/RelayDefaultHandlerProvider.js.flow +2 -2
  2. package/handlers/connection/ConnectionHandler.js.flow +8 -10
  3. package/handlers/connection/MutationHandlers.js.flow +31 -7
  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 +8 -8
  8. package/lib/handlers/connection/MutationHandlers.js +61 -5
  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 +12 -5
  25. package/lib/network/ConvertToExecuteFunction.js +2 -1
  26. package/lib/network/RelayNetwork.js +3 -2
  27. package/lib/network/RelayQueryResponseCache.js +21 -4
  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 +141 -60
  35. package/lib/store/{RelayModernQueryExecutor.js → OperationExecutor.js} +532 -195
  36. package/lib/store/RelayConcreteVariables.js +24 -4
  37. package/lib/store/RelayModernEnvironment.js +175 -234
  38. package/lib/store/RelayModernFragmentSpecResolver.js +52 -26
  39. package/lib/store/RelayModernOperationDescriptor.js +2 -1
  40. package/lib/store/RelayModernRecord.js +47 -12
  41. package/lib/store/RelayModernSelector.js +14 -8
  42. package/lib/store/RelayModernStore.js +58 -29
  43. package/lib/store/RelayOperationTracker.js +34 -24
  44. package/lib/store/RelayPublishQueue.js +41 -13
  45. package/lib/store/RelayReader.js +287 -46
  46. package/lib/store/RelayRecordSource.js +87 -3
  47. package/lib/store/RelayReferenceMarker.js +55 -31
  48. package/lib/store/RelayResponseNormalizer.js +250 -108
  49. package/lib/store/RelayStoreReactFlightUtils.js +8 -12
  50. package/lib/store/RelayStoreSubscriptions.js +14 -9
  51. package/lib/store/RelayStoreUtils.js +11 -5
  52. package/lib/store/ResolverCache.js +213 -0
  53. package/lib/store/ResolverFragments.js +61 -0
  54. package/lib/store/cloneRelayHandleSourceField.js +5 -4
  55. package/lib/store/cloneRelayScalarHandleSourceField.js +5 -4
  56. package/lib/store/createRelayContext.js +4 -2
  57. package/lib/store/defaultGetDataID.js +3 -1
  58. package/lib/store/readInlineData.js +6 -2
  59. package/lib/subscription/requestSubscription.js +35 -9
  60. package/lib/util/RelayConcreteNode.js +4 -0
  61. package/lib/util/RelayFeatureFlags.js +11 -4
  62. package/lib/util/RelayProfiler.js +17 -187
  63. package/lib/util/RelayReplaySubject.js +22 -7
  64. package/lib/util/RelayRuntimeTypes.js +0 -6
  65. package/lib/util/StringInterner.js +71 -0
  66. package/lib/util/deepFreeze.js +1 -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 +28 -16
  95. package/network/ConvertToExecuteFunction.js.flow +2 -2
  96. package/network/RelayNetwork.js.flow +4 -5
  97. package/network/RelayNetworkTypes.js.flow +17 -8
  98. package/network/RelayObservable.js.flow +1 -1
  99. package/network/RelayQueryResponseCache.js.flow +34 -20
  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 +162 -67
  111. package/store/{RelayModernQueryExecutor.js.flow → OperationExecutor.js.flow} +616 -283
  112. package/store/RelayConcreteVariables.js.flow +27 -5
  113. package/store/RelayModernEnvironment.js.flow +176 -235
  114. package/store/RelayModernFragmentSpecResolver.js.flow +55 -31
  115. package/store/RelayModernOperationDescriptor.js.flow +12 -7
  116. package/store/RelayModernRecord.js.flow +67 -11
  117. package/store/RelayModernSelector.js.flow +24 -14
  118. package/store/RelayModernStore.js.flow +72 -36
  119. package/store/RelayOperationTracker.js.flow +59 -43
  120. package/store/RelayOptimisticRecordSource.js.flow +2 -2
  121. package/store/RelayPublishQueue.js.flow +79 -34
  122. package/store/RelayReader.js.flow +351 -72
  123. package/store/RelayRecordSource.js.flow +72 -6
  124. package/store/RelayReferenceMarker.js.flow +60 -33
  125. package/store/RelayResponseNormalizer.js.flow +288 -102
  126. package/store/RelayStoreReactFlightUtils.js.flow +9 -13
  127. package/store/RelayStoreSubscriptions.js.flow +19 -11
  128. package/store/RelayStoreTypes.js.flow +210 -44
  129. package/store/RelayStoreUtils.js.flow +25 -11
  130. package/store/ResolverCache.js.flow +249 -0
  131. package/store/ResolverFragments.js.flow +121 -0
  132. package/store/StoreInspector.js.flow +2 -2
  133. package/store/TypeID.js.flow +1 -1
  134. package/store/ViewerPattern.js.flow +2 -2
  135. package/store/cloneRelayHandleSourceField.js.flow +5 -6
  136. package/store/cloneRelayScalarHandleSourceField.js.flow +5 -6
  137. package/store/createFragmentSpecResolver.js.flow +3 -4
  138. package/store/createRelayContext.js.flow +3 -3
  139. package/store/defaultGetDataID.js.flow +3 -1
  140. package/store/normalizeRelayPayload.js.flow +6 -7
  141. package/store/readInlineData.js.flow +7 -8
  142. package/subscription/requestSubscription.js.flow +54 -27
  143. package/util/NormalizationNode.js.flow +16 -3
  144. package/util/ReaderNode.js.flow +38 -2
  145. package/util/RelayConcreteNode.js.flow +4 -0
  146. package/util/RelayFeatureFlags.js.flow +24 -8
  147. package/util/RelayProfiler.js.flow +22 -194
  148. package/util/RelayReplaySubject.js.flow +9 -9
  149. package/util/RelayRuntimeTypes.js.flow +73 -4
  150. package/util/StringInterner.js.flow +69 -0
  151. package/util/createPayloadFor3DField.js.flow +3 -3
  152. package/util/deepFreeze.js.flow +2 -1
  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 +2 -1
  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
  }
@@ -262,7 +323,9 @@ class RelayReader {
262
323
  }
263
324
  break;
264
325
  case CONDITION:
265
- const conditionValue = this._getVariableValue(selection.condition);
326
+ const conditionValue = Boolean(
327
+ this._getVariableValue(selection.condition),
328
+ );
266
329
  if (conditionValue === selection.passingValue) {
267
330
  const hasExpectedData = this._traverseSelections(
268
331
  selection.selections,
@@ -289,15 +352,15 @@ class RelayReader {
289
352
  return false;
290
353
  }
291
354
  }
292
- } else if (RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT) {
355
+ } else {
293
356
  // Similar to the logic in read(): data is only expected to be present
294
357
  // if the record is known to conform to the interface. If we don't know
295
358
  // whether the type conforms or not, that constitutes missing data.
296
359
 
297
360
  // store flags to reset after reading
298
361
  const parentIsMissingData = this._isMissingData;
299
- const parentIsWithinUnmatchedTypeRefinement = this
300
- ._isWithinUnmatchedTypeRefinement;
362
+ const parentIsWithinUnmatchedTypeRefinement =
363
+ this._isWithinUnmatchedTypeRefinement;
301
364
 
302
365
  const typeName = RelayModernRecord.getType(record);
303
366
  const typeID = generateTypeID(typeName);
@@ -310,22 +373,26 @@ class RelayReader {
310
373
  parentIsWithinUnmatchedTypeRefinement ||
311
374
  implementsInterface === false;
312
375
  this._traverseSelections(selection.selections, record, data);
313
- this._isWithinUnmatchedTypeRefinement = parentIsWithinUnmatchedTypeRefinement;
376
+ this._isWithinUnmatchedTypeRefinement =
377
+ parentIsWithinUnmatchedTypeRefinement;
314
378
 
315
379
  if (implementsInterface === false) {
316
380
  // Type known to not implement the interface, no data expected
317
381
  this._isMissingData = parentIsMissingData;
318
382
  } else if (implementsInterface == null) {
319
383
  // Don't know if the type implements the interface or not
320
- this._isMissingData = true;
384
+ this._markDataAsMissing();
321
385
  }
322
- } else {
323
- // legacy behavior for abstract refinements: always read even
324
- // if the type doesn't conform and don't reset isMissingData
325
- this._traverseSelections(selection.selections, record, data);
326
386
  }
327
387
  break;
328
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
+ }
329
396
  case FRAGMENT_SPREAD:
330
397
  this._createFragmentPointer(selection, record, data);
331
398
  break;
@@ -333,17 +400,33 @@ class RelayReader {
333
400
  this._readModuleImport(selection, record, data);
334
401
  break;
335
402
  case INLINE_DATA_FRAGMENT_SPREAD:
336
- this._createInlineDataFragmentPointer(selection, record, data);
403
+ this._createInlineDataOrResolverFragmentPointer(
404
+ selection,
405
+ record,
406
+ data,
407
+ );
337
408
  break;
338
409
  case DEFER:
339
410
  case CLIENT_EXTENSION: {
340
411
  const isMissingData = this._isMissingData;
412
+ const alreadyMissingClientEdges = this._missingClientEdges.length;
413
+ if (RelayFeatureFlags.ENABLE_CLIENT_EDGES) {
414
+ this._clientEdgeTraversalPath.push(null);
415
+ }
341
416
  const hasExpectedData = this._traverseSelections(
342
417
  selection.selections,
343
418
  record,
344
419
  data,
345
420
  );
346
- 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
+ }
347
430
  if (!hasExpectedData) {
348
431
  return false;
349
432
  }
@@ -367,6 +450,16 @@ class RelayReader {
367
450
  throw new Error('Flight fields are not yet supported.');
368
451
  }
369
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;
370
463
  default:
371
464
  (selection: empty);
372
465
  invariant(
@@ -393,6 +486,12 @@ class RelayReader {
393
486
  } else {
394
487
  return this._readLink(selection.field, record, data);
395
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;
396
495
  default:
397
496
  (selection.field.kind: empty);
398
497
  invariant(
@@ -403,6 +502,145 @@ class RelayReader {
403
502
  }
404
503
  }
405
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
+
406
644
  _readFlightField(
407
645
  field: ReaderFlightField,
408
646
  record: Record,
@@ -410,14 +648,12 @@ class RelayReader {
410
648
  ): ?mixed {
411
649
  const applicationName = field.alias ?? field.name;
412
650
  const storageKey = getStorageKey(field, this._variables);
413
- const reactFlightClientResponseRecordID = RelayModernRecord.getLinkedRecordID(
414
- record,
415
- storageKey,
416
- );
651
+ const reactFlightClientResponseRecordID =
652
+ RelayModernRecord.getLinkedRecordID(record, storageKey);
417
653
  if (reactFlightClientResponseRecordID == null) {
418
654
  data[applicationName] = reactFlightClientResponseRecordID;
419
655
  if (reactFlightClientResponseRecordID === undefined) {
420
- this._isMissingData = true;
656
+ this._markDataAsMissing();
421
657
  }
422
658
  return reactFlightClientResponseRecordID;
423
659
  }
@@ -428,7 +664,7 @@ class RelayReader {
428
664
  if (reactFlightClientResponseRecord == null) {
429
665
  data[applicationName] = reactFlightClientResponseRecord;
430
666
  if (reactFlightClientResponseRecord === undefined) {
431
- this._isMissingData = true;
667
+ this._markDataAsMissing();
432
668
  }
433
669
  return reactFlightClientResponseRecord;
434
670
  }
@@ -448,7 +684,7 @@ class RelayReader {
448
684
  const storageKey = getStorageKey(field, this._variables);
449
685
  const value = RelayModernRecord.getValue(record, storageKey);
450
686
  if (value === undefined) {
451
- this._isMissingData = true;
687
+ this._markDataAsMissing();
452
688
  }
453
689
  data[applicationName] = value;
454
690
  return value;
@@ -465,7 +701,7 @@ class RelayReader {
465
701
  if (linkedID == null) {
466
702
  data[applicationName] = linkedID;
467
703
  if (linkedID === undefined) {
468
- this._isMissingData = true;
704
+ this._markDataAsMissing();
469
705
  }
470
706
  return linkedID;
471
707
  }
@@ -485,6 +721,42 @@ class RelayReader {
485
721
  return value;
486
722
  }
487
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
+
488
760
  _readPluralLink(
489
761
  field: ReaderLinkedField,
490
762
  record: Record,
@@ -497,7 +769,7 @@ class RelayReader {
497
769
  if (linkedIDs == null) {
498
770
  data[applicationName] = linkedIDs;
499
771
  if (linkedIDs === undefined) {
500
- this._isMissingData = true;
772
+ this._markDataAsMissing();
501
773
  }
502
774
  return linkedIDs;
503
775
  }
@@ -515,7 +787,7 @@ class RelayReader {
515
787
  linkedIDs.forEach((linkedID, nextIndex) => {
516
788
  if (linkedID == null) {
517
789
  if (linkedID === undefined) {
518
- this._isMissingData = true;
790
+ this._markDataAsMissing();
519
791
  }
520
792
  // $FlowFixMe[cannot-write]
521
793
  linkedArray[nextIndex] = linkedID;
@@ -553,7 +825,7 @@ class RelayReader {
553
825
  const component = RelayModernRecord.getValue(record, componentKey);
554
826
  if (component == null) {
555
827
  if (component === undefined) {
556
- this._isMissingData = true;
828
+ this._markDataAsMissing();
557
829
  }
558
830
  return;
559
831
  }
@@ -567,7 +839,7 @@ class RelayReader {
567
839
  {
568
840
  kind: 'FragmentSpread',
569
841
  name: moduleImport.fragmentName,
570
- args: null,
842
+ args: moduleImport.args,
571
843
  },
572
844
  record,
573
845
  data,
@@ -598,16 +870,23 @@ class RelayReader {
598
870
  ? getArgumentValues(fragmentSpread.args, this._variables)
599
871
  : {};
600
872
  data[FRAGMENT_OWNER_KEY] = this._owner;
601
-
602
- if (RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT) {
603
- data[
604
- IS_WITHIN_UNMATCHED_TYPE_REFINEMENT
605
- ] = 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
+ }
606
885
  }
607
886
  }
608
887
 
609
- _createInlineDataFragmentPointer(
610
- inlineDataFragmentSpread: ReaderInlineDataFragmentSpread,
888
+ _createInlineDataOrResolverFragmentPointer(
889
+ fragmentSpreadOrFragment: ReaderInlineDataFragmentSpread | ReaderFragment,
611
890
  record: Record,
612
891
  data: SelectorData,
613
892
  ): void {
@@ -625,12 +904,12 @@ class RelayReader {
625
904
  }
626
905
  const inlineData = {};
627
906
  this._traverseSelections(
628
- inlineDataFragmentSpread.selections,
907
+ fragmentSpreadOrFragment.selections,
629
908
  record,
630
909
  inlineData,
631
910
  );
632
911
  // $FlowFixMe[cannot-write] - writing into read-only field
633
- fragmentPointers[inlineDataFragmentSpread.name] = inlineData;
912
+ fragmentPointers[fragmentSpreadOrFragment.name] = inlineData;
634
913
  }
635
914
  }
636
915