relay-runtime 11.0.2 → 13.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/README.md +67 -0
  2. package/handlers/RelayDefaultHandlerProvider.js.flow +3 -3
  3. package/handlers/connection/ConnectionHandler.js.flow +9 -18
  4. package/handlers/connection/ConnectionInterface.js.flow +1 -1
  5. package/handlers/connection/MutationHandlers.js.flow +8 -12
  6. package/index.js +1 -1
  7. package/index.js.flow +57 -35
  8. package/lib/handlers/RelayDefaultHandlerProvider.js +1 -1
  9. package/lib/handlers/connection/ConnectionHandler.js +13 -19
  10. package/lib/handlers/connection/ConnectionInterface.js +1 -1
  11. package/lib/handlers/connection/MutationHandlers.js +4 -7
  12. package/lib/index.js +59 -44
  13. package/lib/multi-actor-environment/ActorIdentifier.js +12 -2
  14. package/lib/multi-actor-environment/ActorSpecificEnvironment.js +64 -20
  15. package/lib/multi-actor-environment/ActorUtils.js +27 -0
  16. package/lib/multi-actor-environment/MultiActorEnvironment.js +324 -61
  17. package/lib/multi-actor-environment/MultiActorEnvironmentTypes.js +1 -1
  18. package/lib/multi-actor-environment/index.js +6 -2
  19. package/lib/mutations/RelayDeclarativeMutationConfig.js +5 -2
  20. package/lib/mutations/RelayRecordProxy.js +4 -3
  21. package/lib/mutations/RelayRecordSourceMutator.js +4 -3
  22. package/lib/mutations/RelayRecordSourceProxy.js +13 -5
  23. package/lib/mutations/RelayRecordSourceSelectorProxy.js +19 -6
  24. package/lib/mutations/applyOptimisticMutation.js +7 -7
  25. package/lib/mutations/commitLocalUpdate.js +1 -1
  26. package/lib/mutations/commitMutation.js +15 -11
  27. package/lib/mutations/readUpdatableQuery_EXPERIMENTAL.js +242 -0
  28. package/lib/mutations/validateMutation.js +11 -6
  29. package/lib/network/ConvertToExecuteFunction.js +3 -2
  30. package/lib/network/RelayNetwork.js +4 -3
  31. package/lib/network/RelayNetworkTypes.js +1 -1
  32. package/lib/network/RelayObservable.js +1 -1
  33. package/lib/network/RelayQueryResponseCache.js +22 -6
  34. package/lib/network/wrapNetworkWithLogObserver.js +79 -0
  35. package/lib/query/GraphQLTag.js +3 -2
  36. package/lib/query/PreloadableQueryRegistry.js +1 -1
  37. package/lib/query/fetchQuery.js +7 -6
  38. package/lib/query/fetchQueryInternal.js +1 -1
  39. package/lib/query/fetchQuery_DEPRECATED.js +3 -2
  40. package/lib/store/ClientID.js +8 -2
  41. package/lib/store/DataChecker.js +124 -55
  42. package/lib/store/OperationExecutor.js +489 -215
  43. package/lib/store/RelayConcreteVariables.js +27 -9
  44. package/lib/store/RelayExperimentalGraphResponseHandler.js +153 -0
  45. package/lib/store/RelayExperimentalGraphResponseTransform.js +391 -0
  46. package/lib/store/RelayModernEnvironment.js +100 -120
  47. package/lib/store/RelayModernFragmentSpecResolver.js +53 -27
  48. package/lib/store/RelayModernOperationDescriptor.js +3 -2
  49. package/lib/store/RelayModernRecord.js +48 -13
  50. package/lib/store/RelayModernSelector.js +15 -9
  51. package/lib/store/RelayModernStore.js +56 -23
  52. package/lib/store/RelayOperationTracker.js +34 -24
  53. package/lib/store/RelayOptimisticRecordSource.js +1 -1
  54. package/lib/store/RelayPublishQueue.js +35 -11
  55. package/lib/store/RelayReader.js +257 -72
  56. package/lib/store/RelayRecordSource.js +88 -4
  57. package/lib/store/RelayRecordState.js +1 -1
  58. package/lib/store/RelayReferenceMarker.js +34 -22
  59. package/lib/store/RelayResponseNormalizer.js +172 -96
  60. package/lib/store/RelayStoreReactFlightUtils.js +5 -11
  61. package/lib/store/RelayStoreSubscriptions.js +15 -10
  62. package/lib/store/RelayStoreTypes.js +1 -1
  63. package/lib/store/RelayStoreUtils.js +13 -8
  64. package/lib/store/ResolverCache.js +213 -0
  65. package/lib/store/ResolverFragments.js +10 -6
  66. package/lib/store/StoreInspector.js +1 -1
  67. package/lib/store/TypeID.js +1 -1
  68. package/lib/store/ViewerPattern.js +1 -1
  69. package/lib/store/cloneRelayHandleSourceField.js +6 -5
  70. package/lib/store/cloneRelayScalarHandleSourceField.js +6 -5
  71. package/lib/store/createFragmentSpecResolver.js +1 -1
  72. package/lib/store/createRelayContext.js +5 -3
  73. package/lib/store/defaultGetDataID.js +1 -1
  74. package/lib/store/defaultRequiredFieldLogger.js +1 -1
  75. package/lib/store/hasOverlappingIDs.js +1 -1
  76. package/lib/store/isRelayModernEnvironment.js +1 -1
  77. package/lib/store/normalizeRelayPayload.js +1 -1
  78. package/lib/store/readInlineData.js +7 -3
  79. package/lib/subscription/requestSubscription.js +32 -34
  80. package/lib/util/JSResourceTypes.flow.js +1 -1
  81. package/lib/util/NormalizationNode.js +1 -1
  82. package/lib/util/ReaderNode.js +1 -1
  83. package/lib/util/RelayConcreteNode.js +3 -1
  84. package/lib/util/RelayDefaultHandleKey.js +1 -1
  85. package/lib/util/RelayError.js +1 -1
  86. package/lib/util/RelayFeatureFlags.js +10 -7
  87. package/lib/util/RelayProfiler.js +1 -1
  88. package/lib/util/RelayReplaySubject.js +22 -7
  89. package/lib/util/RelayRuntimeTypes.js +1 -7
  90. package/lib/util/StringInterner.js +71 -0
  91. package/lib/util/createPayloadFor3DField.js +1 -1
  92. package/lib/util/deepFreeze.js +1 -1
  93. package/lib/util/generateID.js +1 -1
  94. package/lib/util/getAllRootVariables.js +29 -0
  95. package/lib/util/getFragmentIdentifier.js +16 -8
  96. package/lib/util/getOperation.js +3 -2
  97. package/lib/util/getPaginationMetadata.js +41 -0
  98. package/lib/util/getPaginationVariables.js +66 -0
  99. package/lib/util/getPendingOperationsForFragment.js +55 -0
  100. package/lib/util/getRefetchMetadata.js +36 -0
  101. package/lib/util/getRelayHandleKey.js +3 -3
  102. package/lib/util/getRequestIdentifier.js +3 -3
  103. package/lib/util/getValueAtPath.js +51 -0
  104. package/lib/util/isEmptyObject.js +2 -2
  105. package/lib/util/isPromise.js +1 -1
  106. package/lib/util/isScalarAndEqual.js +1 -1
  107. package/lib/util/recycleNodesInto.js +1 -1
  108. package/lib/util/registerEnvironmentWithDevTools.js +26 -0
  109. package/lib/util/reportMissingRequiredFields.js +1 -1
  110. package/lib/util/resolveImmediate.js +1 -1
  111. package/lib/util/stableCopy.js +1 -1
  112. package/lib/util/withDuration.js +31 -0
  113. package/multi-actor-environment/ActorIdentifier.js.flow +18 -2
  114. package/multi-actor-environment/ActorSpecificEnvironment.js.flow +94 -58
  115. package/multi-actor-environment/ActorUtils.js.flow +33 -0
  116. package/multi-actor-environment/MultiActorEnvironment.js.flow +366 -93
  117. package/multi-actor-environment/MultiActorEnvironmentTypes.js.flow +88 -23
  118. package/multi-actor-environment/index.js.flow +3 -1
  119. package/mutations/RelayDeclarativeMutationConfig.js.flow +33 -27
  120. package/mutations/RelayRecordProxy.js.flow +5 -6
  121. package/mutations/RelayRecordSourceMutator.js.flow +5 -7
  122. package/mutations/RelayRecordSourceProxy.js.flow +20 -11
  123. package/mutations/RelayRecordSourceSelectorProxy.js.flow +23 -8
  124. package/mutations/applyOptimisticMutation.js.flow +14 -15
  125. package/mutations/commitLocalUpdate.js.flow +2 -2
  126. package/mutations/commitMutation.js.flow +36 -47
  127. package/mutations/readUpdatableQuery_EXPERIMENTAL.js.flow +318 -0
  128. package/mutations/validateMutation.js.flow +27 -17
  129. package/network/ConvertToExecuteFunction.js.flow +3 -3
  130. package/network/RelayNetwork.js.flow +5 -6
  131. package/network/RelayNetworkTypes.js.flow +1 -1
  132. package/network/RelayObservable.js.flow +2 -2
  133. package/network/RelayQueryResponseCache.js.flow +35 -22
  134. package/network/wrapNetworkWithLogObserver.js.flow +99 -0
  135. package/package.json +2 -2
  136. package/query/GraphQLTag.js.flow +11 -11
  137. package/query/PreloadableQueryRegistry.js.flow +5 -3
  138. package/query/fetchQuery.js.flow +19 -19
  139. package/query/fetchQueryInternal.js.flow +7 -10
  140. package/query/fetchQuery_DEPRECATED.js.flow +7 -7
  141. package/relay-runtime.js +2 -2
  142. package/relay-runtime.min.js +2 -2
  143. package/store/ClientID.js.flow +15 -4
  144. package/store/DataChecker.js.flow +142 -60
  145. package/store/OperationExecutor.js.flow +575 -320
  146. package/store/RelayConcreteVariables.js.flow +28 -9
  147. package/store/RelayExperimentalGraphResponseHandler.js.flow +121 -0
  148. package/store/RelayExperimentalGraphResponseTransform.js.flow +470 -0
  149. package/store/RelayModernEnvironment.js.flow +91 -115
  150. package/store/RelayModernFragmentSpecResolver.js.flow +56 -32
  151. package/store/RelayModernOperationDescriptor.js.flow +13 -8
  152. package/store/RelayModernRecord.js.flow +68 -12
  153. package/store/RelayModernSelector.js.flow +25 -15
  154. package/store/RelayModernStore.js.flow +67 -32
  155. package/store/RelayOperationTracker.js.flow +60 -44
  156. package/store/RelayOptimisticRecordSource.js.flow +3 -3
  157. package/store/RelayPublishQueue.js.flow +74 -32
  158. package/store/RelayReader.js.flow +319 -100
  159. package/store/RelayRecordSource.js.flow +73 -7
  160. package/store/RelayRecordState.js.flow +1 -1
  161. package/store/RelayReferenceMarker.js.flow +41 -27
  162. package/store/RelayResponseNormalizer.js.flow +204 -86
  163. package/store/RelayStoreReactFlightUtils.js.flow +5 -12
  164. package/store/RelayStoreSubscriptions.js.flow +20 -12
  165. package/store/RelayStoreTypes.js.flow +200 -41
  166. package/store/RelayStoreUtils.js.flow +25 -12
  167. package/store/ResolverCache.js.flow +249 -0
  168. package/store/ResolverFragments.js.flow +16 -20
  169. package/store/StoreInspector.js.flow +3 -3
  170. package/store/TypeID.js.flow +2 -2
  171. package/store/ViewerPattern.js.flow +3 -3
  172. package/store/cloneRelayHandleSourceField.js.flow +6 -7
  173. package/store/cloneRelayScalarHandleSourceField.js.flow +6 -7
  174. package/store/createFragmentSpecResolver.js.flow +4 -5
  175. package/store/createRelayContext.js.flow +4 -4
  176. package/store/defaultGetDataID.js.flow +1 -1
  177. package/store/defaultRequiredFieldLogger.js.flow +1 -1
  178. package/store/hasOverlappingIDs.js.flow +1 -1
  179. package/store/isRelayModernEnvironment.js.flow +1 -1
  180. package/store/normalizeRelayPayload.js.flow +7 -8
  181. package/store/readInlineData.js.flow +8 -9
  182. package/subscription/requestSubscription.js.flow +55 -51
  183. package/util/JSResourceTypes.flow.js.flow +1 -1
  184. package/util/NormalizationNode.js.flow +11 -4
  185. package/util/ReaderNode.js.flow +25 -2
  186. package/util/RelayConcreteNode.js.flow +5 -1
  187. package/util/RelayDefaultHandleKey.js.flow +1 -1
  188. package/util/RelayError.js.flow +1 -1
  189. package/util/RelayFeatureFlags.js.flow +23 -15
  190. package/util/RelayProfiler.js.flow +1 -1
  191. package/util/RelayReplaySubject.js.flow +10 -10
  192. package/util/RelayRuntimeTypes.js.flow +70 -3
  193. package/util/StringInterner.js.flow +69 -0
  194. package/util/createPayloadFor3DField.js.flow +4 -4
  195. package/util/deepFreeze.js.flow +1 -1
  196. package/util/generateID.js.flow +1 -1
  197. package/util/getAllRootVariables.js.flow +36 -0
  198. package/util/getFragmentIdentifier.js.flow +28 -16
  199. package/util/getOperation.js.flow +3 -3
  200. package/util/getPaginationMetadata.js.flow +69 -0
  201. package/util/getPaginationVariables.js.flow +108 -0
  202. package/util/getPendingOperationsForFragment.js.flow +62 -0
  203. package/util/getRefetchMetadata.js.flow +76 -0
  204. package/util/getRelayHandleKey.js.flow +2 -3
  205. package/util/getRequestIdentifier.js.flow +4 -4
  206. package/util/getValueAtPath.js.flow +46 -0
  207. package/util/isEmptyObject.js.flow +2 -1
  208. package/util/isPromise.js.flow +1 -1
  209. package/util/isScalarAndEqual.js.flow +1 -1
  210. package/util/recycleNodesInto.js.flow +1 -1
  211. package/util/registerEnvironmentWithDevTools.js.flow +33 -0
  212. package/util/reportMissingRequiredFields.js.flow +1 -1
  213. package/util/resolveImmediate.js.flow +2 -2
  214. package/util/stableCopy.js.flow +1 -1
  215. package/util/withDuration.js.flow +32 -0
  216. package/lib/store/RelayRecordSourceMapImpl.js +0 -107
  217. package/lib/store/RelayStoreSubscriptionsUsingMapByID.js +0 -318
  218. package/store/RelayRecordSourceMapImpl.js.flow +0 -91
  219. package/store/RelayStoreSubscriptionsUsingMapByID.js.flow +0 -283
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
3
  *
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
@@ -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,57 +54,43 @@ const {
27
54
  INLINE_FRAGMENT,
28
55
  LINKED_FIELD,
29
56
  MODULE_IMPORT,
30
- REQUIRED_FIELD,
31
57
  RELAY_RESOLVER,
58
+ REQUIRED_FIELD,
32
59
  SCALAR_FIELD,
33
60
  STREAM,
34
61
  } = require('../util/RelayConcreteNode');
62
+ const RelayFeatureFlags = require('../util/RelayFeatureFlags');
63
+ const ClientID = require('./ClientID');
64
+ const RelayModernRecord = require('./RelayModernRecord');
35
65
  const {getReactFlightClientResponse} = require('./RelayStoreReactFlightUtils');
36
66
  const {
37
- FRAGMENTS_KEY,
67
+ CLIENT_EDGE_TRAVERSAL_PATH,
38
68
  FRAGMENT_OWNER_KEY,
39
69
  FRAGMENT_PROP_NAME_KEY,
70
+ FRAGMENTS_KEY,
40
71
  ID_KEY,
41
72
  IS_WITHIN_UNMATCHED_TYPE_REFINEMENT,
42
73
  MODULE_COMPONENT_KEY,
43
74
  ROOT_ID,
44
75
  getArgumentValues,
45
- getStorageKey,
46
76
  getModuleComponentKey,
77
+ getStorageKey,
47
78
  } = require('./RelayStoreUtils');
79
+ const {NoopResolverCache} = require('./ResolverCache');
48
80
  const {withResolverContext} = require('./ResolverFragments');
49
81
  const {generateTypeID} = require('./TypeID');
50
-
51
- import type {
52
- ReaderFlightField,
53
- ReaderFragment,
54
- ReaderFragmentSpread,
55
- ReaderInlineDataFragmentSpread,
56
- ReaderLinkedField,
57
- ReaderModuleImport,
58
- ReaderNode,
59
- ReaderRelayResolver,
60
- ReaderRequiredField,
61
- ReaderScalarField,
62
- ReaderSelection,
63
- } from '../util/ReaderNode';
64
- import type {DataID, Variables} from '../util/RelayRuntimeTypes';
65
- import type {
66
- Record,
67
- RecordSource,
68
- RequestDescriptor,
69
- SelectorData,
70
- SingularReaderSelector,
71
- Snapshot,
72
- MissingRequiredFields,
73
- DataIDSet,
74
- } from './RelayStoreTypes';
82
+ const invariant = require('invariant');
75
83
 
76
84
  function read(
77
85
  recordSource: RecordSource,
78
86
  selector: SingularReaderSelector,
87
+ resolverCache?: ResolverCache,
79
88
  ): Snapshot {
80
- const reader = new RelayReader(recordSource, selector);
89
+ const reader = new RelayReader(
90
+ recordSource,
91
+ selector,
92
+ resolverCache ?? new NoopResolverCache(),
93
+ );
81
94
  return reader.read();
82
95
  }
83
96
 
@@ -85,7 +98,9 @@ function read(
85
98
  * @private
86
99
  */
87
100
  class RelayReader {
101
+ _clientEdgeTraversalPath: Array<ClientEdgeTraversalInfo | null>;
88
102
  _isMissingData: boolean;
103
+ _missingClientEdges: Array<MissingClientEdgeRequestInfo>;
89
104
  _isWithinUnmatchedTypeRefinement: boolean;
90
105
  _missingRequiredFields: ?MissingRequiredFields;
91
106
  _owner: RequestDescriptor;
@@ -93,8 +108,19 @@ class RelayReader {
93
108
  _seenRecords: DataIDSet;
94
109
  _selector: SingularReaderSelector;
95
110
  _variables: Variables;
111
+ _resolverCache: ResolverCache;
96
112
 
97
- 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 = [];
98
124
  this._isMissingData = false;
99
125
  this._isWithinUnmatchedTypeRefinement = false;
100
126
  this._missingRequiredFields = null;
@@ -103,6 +129,7 @@ class RelayReader {
103
129
  this._seenRecords = new Set();
104
130
  this._selector = selector;
105
131
  this._variables = selector.variables;
132
+ this._resolverCache = resolverCache;
106
133
  }
107
134
 
108
135
  read(): Snapshot {
@@ -126,7 +153,18 @@ class RelayReader {
126
153
  // match, then no data is expected to be present.
127
154
  if (isDataExpectedToBePresent && abstractKey == null && record != null) {
128
155
  const recordType = RelayModernRecord.getType(record);
129
- 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
+ ) {
130
168
  isDataExpectedToBePresent = false;
131
169
  }
132
170
  }
@@ -135,12 +173,7 @@ class RelayReader {
135
173
  // then data is only expected to be present if the record type is known to
136
174
  // implement the interface. If we aren't sure whether the record implements
137
175
  // 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
- ) {
176
+ if (isDataExpectedToBePresent && abstractKey != null && record != null) {
144
177
  const recordType = RelayModernRecord.getType(record);
145
178
  const typeID = generateTypeID(recordType);
146
179
  const typeRecord = this._recordSource.get(typeID);
@@ -162,12 +195,36 @@ class RelayReader {
162
195
  return {
163
196
  data,
164
197
  isMissingData: this._isMissingData && isDataExpectedToBePresent,
198
+ missingClientEdges:
199
+ RelayFeatureFlags.ENABLE_CLIENT_EDGES && this._missingClientEdges.length
200
+ ? this._missingClientEdges
201
+ : null,
165
202
  seenRecords: this._seenRecords,
166
203
  selector: this._selector,
167
204
  missingRequiredFields: this._missingRequiredFields,
168
205
  };
169
206
  }
170
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
+
171
228
  _traverse(
172
229
  node: ReaderNode,
173
230
  dataID: DataID,
@@ -177,7 +234,7 @@ class RelayReader {
177
234
  this._seenRecords.add(dataID);
178
235
  if (record == null) {
179
236
  if (record === undefined) {
180
- this._isMissingData = true;
237
+ this._markDataAsMissing();
181
238
  }
182
239
  return record;
183
240
  }
@@ -196,7 +253,6 @@ class RelayReader {
196
253
  'RelayReader(): Undefined variable `%s`.',
197
254
  name,
198
255
  );
199
- // $FlowFixMe[cannot-write]
200
256
  return this._variables[name];
201
257
  }
202
258
 
@@ -237,13 +293,6 @@ class RelayReader {
237
293
  const selection = selections[i];
238
294
  switch (selection.kind) {
239
295
  case REQUIRED_FIELD:
240
- invariant(
241
- RelayFeatureFlags.ENABLE_REQUIRED_DIRECTIVES,
242
- 'RelayReader(): Encountered a `@required` directive at path "%s" in `%s` without the `ENABLE_REQUIRED_DIRECTIVES` feature flag enabled.',
243
- selection.path,
244
- this._selector.node.name,
245
- );
246
-
247
296
  const fieldValue = this._readRequiredField(selection, record, data);
248
297
  if (fieldValue == null) {
249
298
  const {action} = selection;
@@ -267,7 +316,9 @@ class RelayReader {
267
316
  }
268
317
  break;
269
318
  case CONDITION:
270
- const conditionValue = this._getVariableValue(selection.condition);
319
+ const conditionValue = Boolean(
320
+ this._getVariableValue(selection.condition),
321
+ );
271
322
  if (conditionValue === selection.passingValue) {
272
323
  const hasExpectedData = this._traverseSelections(
273
324
  selection.selections,
@@ -294,15 +345,15 @@ class RelayReader {
294
345
  return false;
295
346
  }
296
347
  }
297
- } else if (RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT) {
348
+ } else {
298
349
  // Similar to the logic in read(): data is only expected to be present
299
350
  // if the record is known to conform to the interface. If we don't know
300
351
  // whether the type conforms or not, that constitutes missing data.
301
352
 
302
353
  // store flags to reset after reading
303
354
  const parentIsMissingData = this._isMissingData;
304
- const parentIsWithinUnmatchedTypeRefinement = this
305
- ._isWithinUnmatchedTypeRefinement;
355
+ const parentIsWithinUnmatchedTypeRefinement =
356
+ this._isWithinUnmatchedTypeRefinement;
306
357
 
307
358
  const typeName = RelayModernRecord.getType(record);
308
359
  const typeID = generateTypeID(typeName);
@@ -315,19 +366,16 @@ class RelayReader {
315
366
  parentIsWithinUnmatchedTypeRefinement ||
316
367
  implementsInterface === false;
317
368
  this._traverseSelections(selection.selections, record, data);
318
- this._isWithinUnmatchedTypeRefinement = parentIsWithinUnmatchedTypeRefinement;
369
+ this._isWithinUnmatchedTypeRefinement =
370
+ parentIsWithinUnmatchedTypeRefinement;
319
371
 
320
372
  if (implementsInterface === false) {
321
373
  // Type known to not implement the interface, no data expected
322
374
  this._isMissingData = parentIsMissingData;
323
375
  } else if (implementsInterface == null) {
324
376
  // Don't know if the type implements the interface or not
325
- this._isMissingData = true;
377
+ this._markDataAsMissing();
326
378
  }
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
379
  }
332
380
  break;
333
381
  }
@@ -354,12 +402,24 @@ class RelayReader {
354
402
  case DEFER:
355
403
  case CLIENT_EXTENSION: {
356
404
  const isMissingData = this._isMissingData;
405
+ const alreadyMissingClientEdges = this._missingClientEdges.length;
406
+ if (RelayFeatureFlags.ENABLE_CLIENT_EDGES) {
407
+ this._clientEdgeTraversalPath.push(null);
408
+ }
357
409
  const hasExpectedData = this._traverseSelections(
358
410
  selection.selections,
359
411
  record,
360
412
  data,
361
413
  );
362
- this._isMissingData = isMissingData;
414
+ // The only case where we want to suspend due to missing data off of
415
+ // a client extension is if we reached a client edge that we might be
416
+ // able to fetch:
417
+ this._isMissingData =
418
+ isMissingData ||
419
+ this._missingClientEdges.length > alreadyMissingClientEdges;
420
+ if (RelayFeatureFlags.ENABLE_CLIENT_EDGES) {
421
+ this._clientEdgeTraversalPath.pop();
422
+ }
363
423
  if (!hasExpectedData) {
364
424
  return false;
365
425
  }
@@ -383,6 +443,16 @@ class RelayReader {
383
443
  throw new Error('Flight fields are not yet supported.');
384
444
  }
385
445
  break;
446
+ case ACTOR_CHANGE:
447
+ this._readActorChange(selection, record, data);
448
+ break;
449
+ case CLIENT_EDGE:
450
+ if (RelayFeatureFlags.ENABLE_CLIENT_EDGES) {
451
+ this._readClientEdge(selection, record, data);
452
+ } else {
453
+ throw new Error('Client edges are not yet supported.');
454
+ }
455
+ break;
386
456
  default:
387
457
  (selection: empty);
388
458
  invariant(
@@ -409,6 +479,12 @@ class RelayReader {
409
479
  } else {
410
480
  return this._readLink(selection.field, record, data);
411
481
  }
482
+ case RELAY_RESOLVER:
483
+ if (!RelayFeatureFlags.ENABLE_RELAY_RESOLVERS) {
484
+ throw new Error('Relay Resolver fields are not yet supported.');
485
+ }
486
+ this._readResolverField(selection.field, record, data);
487
+ break;
412
488
  default:
413
489
  (selection.field.kind: empty);
414
490
  invariant(
@@ -420,40 +496,142 @@ class RelayReader {
420
496
  }
421
497
 
422
498
  _readResolverField(
423
- selection: ReaderRelayResolver,
499
+ field: ReaderRelayResolver,
424
500
  record: Record,
425
501
  data: SelectorData,
426
- ): ?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 => {
502
+ ): void {
503
+ const {resolverModule, fragment} = field;
504
+ const storageKey = getStorageKey(field, this._variables);
505
+ const resolverID = ClientID.generateClientID(
506
+ RelayModernRecord.getDataID(record),
507
+ storageKey,
508
+ );
509
+
510
+ // Found when reading the resolver fragment, which can happen either when
511
+ // evaluating the resolver and it calls readFragment, or when checking if the
512
+ // inputs have changed since a previous evaluation:
513
+ let fragmentValue;
514
+ let fragmentReaderSelector;
515
+ const fragmentSeenRecordIDs = new Set();
516
+
517
+ const getDataForResolverFragment = singularReaderSelector => {
518
+ if (fragmentValue != null) {
519
+ // It was already read when checking for input staleness; no need to read it again.
520
+ // Note that the variables like fragmentSeenRecordIDs in the outer closure will have
521
+ // already been set and will still be used in this case.
522
+ return fragmentValue;
523
+ }
524
+ fragmentReaderSelector = singularReaderSelector;
525
+ const existingSeenRecords = this._seenRecords;
526
+ try {
527
+ this._seenRecords = fragmentSeenRecordIDs;
437
528
  const resolverFragmentData = {};
438
529
  this._createInlineDataOrResolverFragmentPointer(
439
530
  singularReaderSelector.node,
440
531
  record,
441
532
  resolverFragmentData,
442
533
  );
443
- const answer = resolverFragmentData[FRAGMENTS_KEY]?.[fragment.name];
534
+ fragmentValue = resolverFragmentData[FRAGMENTS_KEY]?.[fragment.name];
444
535
  invariant(
445
- typeof answer === 'object' && answer !== null,
536
+ typeof fragmentValue === 'object' && fragmentValue !== null,
446
537
  `Expected reader data to contain a __fragments property with a property for the fragment named ${fragment.name}, but it is missing.`,
447
538
  );
448
- return answer;
449
- },
539
+ return fragmentValue;
540
+ } finally {
541
+ this._seenRecords = existingSeenRecords;
542
+ }
450
543
  };
451
- const resolverResult = withResolverContext(resolverContext, () =>
452
- // $FlowFixMe[prop-missing] - resolver module's type signature is a lie
453
- resolverModule(key),
544
+ const resolverContext = {getDataForResolverFragment};
545
+
546
+ const [result, seenRecord] = this._resolverCache.readFromCacheOrEvaluate(
547
+ record,
548
+ field,
549
+ this._variables,
550
+ () => {
551
+ const key = {
552
+ __id: RelayModernRecord.getDataID(record),
553
+ __fragmentOwner: this._owner,
554
+ __fragments: {
555
+ [fragment.name]: {}, // Arguments to this fragment; not yet supported.
556
+ },
557
+ };
558
+ return withResolverContext(resolverContext, () => {
559
+ // $FlowFixMe[prop-missing] - resolver module's type signature is a lie
560
+ const resolverResult = resolverModule(key);
561
+ return {
562
+ resolverResult,
563
+ fragmentValue,
564
+ resolverID,
565
+ seenRecordIDs: fragmentSeenRecordIDs,
566
+ readerSelector: fragmentReaderSelector,
567
+ };
568
+ });
569
+ },
570
+ getDataForResolverFragment,
571
+ );
572
+ if (seenRecord != null) {
573
+ this._seenRecords.add(seenRecord);
574
+ }
575
+
576
+ const applicationName = field.alias ?? field.name;
577
+ data[applicationName] = result;
578
+ }
579
+
580
+ _readClientEdge(
581
+ field: ReaderClientEdge,
582
+ record: Record,
583
+ data: SelectorData,
584
+ ): void {
585
+ const backingField = field.backingField;
586
+
587
+ // Because ReaderClientExtension doesn't have `alias` or `name` and so I don't know
588
+ // how to get its applicationName or storageKey yet:
589
+ invariant(
590
+ backingField.kind !== 'ClientExtension',
591
+ 'Client extension client edges are not yet implemented.',
592
+ );
593
+
594
+ const applicationName = backingField.alias ?? backingField.name;
595
+
596
+ const backingFieldData = {};
597
+ this._traverseSelections([backingField], record, backingFieldData);
598
+ const destinationDataID = backingFieldData[applicationName];
599
+
600
+ if (destinationDataID == null) {
601
+ data[applicationName] = destinationDataID;
602
+ return;
603
+ }
604
+
605
+ invariant(
606
+ typeof destinationDataID === 'string',
607
+ 'Plural client edges not are yet implemented',
608
+ ); // FIXME support plural
609
+
610
+ // Not wrapping the push/pop in a try/finally because if we throw, the
611
+ // Reader object is not usable after that anyway.
612
+ this._clientEdgeTraversalPath.push({
613
+ readerClientEdge: field,
614
+ clientEdgeDestinationID: destinationDataID,
615
+ });
616
+
617
+ const prevData = data[applicationName];
618
+ invariant(
619
+ prevData == null || typeof prevData === 'object',
620
+ 'RelayReader(): Expected data for field `%s` on record `%s` ' +
621
+ 'to be an object, got `%s`.',
622
+ applicationName,
623
+ RelayModernRecord.getDataID(record),
624
+ prevData,
454
625
  );
455
- data[alias ?? name] = resolverResult;
456
- return resolverResult;
626
+ const value = this._traverse(
627
+ field.linkedField,
628
+ destinationDataID,
629
+ // $FlowFixMe[incompatible-variance]
630
+ prevData,
631
+ );
632
+ data[applicationName] = value;
633
+
634
+ this._clientEdgeTraversalPath.pop();
457
635
  }
458
636
 
459
637
  _readFlightField(
@@ -463,14 +641,12 @@ class RelayReader {
463
641
  ): ?mixed {
464
642
  const applicationName = field.alias ?? field.name;
465
643
  const storageKey = getStorageKey(field, this._variables);
466
- const reactFlightClientResponseRecordID = RelayModernRecord.getLinkedRecordID(
467
- record,
468
- storageKey,
469
- );
644
+ const reactFlightClientResponseRecordID =
645
+ RelayModernRecord.getLinkedRecordID(record, storageKey);
470
646
  if (reactFlightClientResponseRecordID == null) {
471
647
  data[applicationName] = reactFlightClientResponseRecordID;
472
648
  if (reactFlightClientResponseRecordID === undefined) {
473
- this._isMissingData = true;
649
+ this._markDataAsMissing();
474
650
  }
475
651
  return reactFlightClientResponseRecordID;
476
652
  }
@@ -481,7 +657,7 @@ class RelayReader {
481
657
  if (reactFlightClientResponseRecord == null) {
482
658
  data[applicationName] = reactFlightClientResponseRecord;
483
659
  if (reactFlightClientResponseRecord === undefined) {
484
- this._isMissingData = true;
660
+ this._markDataAsMissing();
485
661
  }
486
662
  return reactFlightClientResponseRecord;
487
663
  }
@@ -501,7 +677,7 @@ class RelayReader {
501
677
  const storageKey = getStorageKey(field, this._variables);
502
678
  const value = RelayModernRecord.getValue(record, storageKey);
503
679
  if (value === undefined) {
504
- this._isMissingData = true;
680
+ this._markDataAsMissing();
505
681
  }
506
682
  data[applicationName] = value;
507
683
  return value;
@@ -518,7 +694,7 @@ class RelayReader {
518
694
  if (linkedID == null) {
519
695
  data[applicationName] = linkedID;
520
696
  if (linkedID === undefined) {
521
- this._isMissingData = true;
697
+ this._markDataAsMissing();
522
698
  }
523
699
  return linkedID;
524
700
  }
@@ -538,6 +714,42 @@ class RelayReader {
538
714
  return value;
539
715
  }
540
716
 
717
+ _readActorChange(
718
+ field: ReaderActorChange,
719
+ record: Record,
720
+ data: SelectorData,
721
+ ): ?mixed {
722
+ const applicationName = field.alias ?? field.name;
723
+ const storageKey = getStorageKey(field, this._variables);
724
+ const externalRef = RelayModernRecord.getActorLinkedRecordID(
725
+ record,
726
+ storageKey,
727
+ );
728
+
729
+ if (externalRef == null) {
730
+ data[applicationName] = externalRef;
731
+ if (externalRef === undefined) {
732
+ this._markDataAsMissing();
733
+ }
734
+ return data[applicationName];
735
+ }
736
+ const [actorIdentifier, dataID] = externalRef;
737
+
738
+ const fragmentRef = {};
739
+ this._createFragmentPointer(
740
+ field.fragmentSpread,
741
+ {
742
+ __id: dataID,
743
+ },
744
+ fragmentRef,
745
+ );
746
+ data[applicationName] = {
747
+ __fragmentRef: fragmentRef,
748
+ __viewer: actorIdentifier,
749
+ };
750
+ return data[applicationName];
751
+ }
752
+
541
753
  _readPluralLink(
542
754
  field: ReaderLinkedField,
543
755
  record: Record,
@@ -550,7 +762,7 @@ class RelayReader {
550
762
  if (linkedIDs == null) {
551
763
  data[applicationName] = linkedIDs;
552
764
  if (linkedIDs === undefined) {
553
- this._isMissingData = true;
765
+ this._markDataAsMissing();
554
766
  }
555
767
  return linkedIDs;
556
768
  }
@@ -568,7 +780,7 @@ class RelayReader {
568
780
  linkedIDs.forEach((linkedID, nextIndex) => {
569
781
  if (linkedID == null) {
570
782
  if (linkedID === undefined) {
571
- this._isMissingData = true;
783
+ this._markDataAsMissing();
572
784
  }
573
785
  // $FlowFixMe[cannot-write]
574
786
  linkedArray[nextIndex] = linkedID;
@@ -606,7 +818,7 @@ class RelayReader {
606
818
  const component = RelayModernRecord.getValue(record, componentKey);
607
819
  if (component == null) {
608
820
  if (component === undefined) {
609
- this._isMissingData = true;
821
+ this._markDataAsMissing();
610
822
  }
611
823
  return;
612
824
  }
@@ -620,7 +832,7 @@ class RelayReader {
620
832
  {
621
833
  kind: 'FragmentSpread',
622
834
  name: moduleImport.fragmentName,
623
- args: null,
835
+ args: moduleImport.args,
624
836
  },
625
837
  record,
626
838
  data,
@@ -651,11 +863,18 @@ class RelayReader {
651
863
  ? getArgumentValues(fragmentSpread.args, this._variables)
652
864
  : {};
653
865
  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;
866
+ data[IS_WITHIN_UNMATCHED_TYPE_REFINEMENT] =
867
+ this._isWithinUnmatchedTypeRefinement;
868
+
869
+ if (RelayFeatureFlags.ENABLE_CLIENT_EDGES) {
870
+ if (
871
+ this._clientEdgeTraversalPath.length > 0 &&
872
+ this._clientEdgeTraversalPath[
873
+ this._clientEdgeTraversalPath.length - 1
874
+ ] !== null
875
+ ) {
876
+ data[CLIENT_EDGE_TRAVERSAL_PATH] = [...this._clientEdgeTraversalPath];
877
+ }
659
878
  }
660
879
  }
661
880