relay-runtime 20.1.0 → 21.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/experimental.js +1 -1
  2. package/experimental.js.flow +8 -8
  3. package/handlers/connection/ConnectionHandler.js.flow +5 -5
  4. package/handlers/connection/ConnectionInterface.js.flow +1 -1
  5. package/index.js +1 -1
  6. package/index.js.flow +125 -62
  7. package/lib/experimental.js +3 -3
  8. package/lib/index.js +105 -57
  9. package/lib/multi-actor-environment/ActorIdentifier.js +2 -2
  10. package/lib/multi-actor-environment/MultiActorEnvironment.js +3 -1
  11. package/lib/mutations/commitMutation.js +8 -8
  12. package/lib/mutations/validateMutation.js +4 -4
  13. package/lib/query/GraphQLTag.js +3 -3
  14. package/lib/query/fetchQuery.js +15 -3
  15. package/lib/store/DataChecker.js +38 -4
  16. package/lib/store/NormalizationEngine.js +373 -0
  17. package/lib/store/OperationExecutor.js +172 -113
  18. package/lib/store/RelayConcreteVariables.js +1 -1
  19. package/lib/store/RelayErrorTrie.js +2 -2
  20. package/lib/store/RelayExperimentalGraphResponseTransform.js +8 -8
  21. package/lib/store/RelayModernEnvironment.js +26 -19
  22. package/lib/store/RelayModernRecord.js +18 -8
  23. package/lib/store/RelayModernSelector.js +9 -9
  24. package/lib/store/RelayModernStore.js +152 -43
  25. package/lib/store/RelayPublishQueue.js +1 -1
  26. package/lib/store/RelayReader.js +76 -38
  27. package/lib/store/RelayRecordSource.js +6 -0
  28. package/lib/store/RelayReferenceMarker.js +2 -1
  29. package/lib/store/RelayResponseNormalizer.js +88 -55
  30. package/lib/store/RelayStoreSubscriptions.js +34 -10
  31. package/lib/store/RelayStoreUtils.js +8 -1
  32. package/lib/store/ResolverFragments.js +2 -2
  33. package/lib/store/live-resolvers/LiveResolverCache.js +25 -9
  34. package/lib/store/observeFragmentExperimental.js +17 -1
  35. package/lib/store/observeQueryExperimental.js +2 -2
  36. package/lib/subscription/requestSubscription.js +3 -3
  37. package/lib/util/RelayError.js +3 -0
  38. package/lib/util/RelayFeatureFlags.js +6 -2
  39. package/lib/util/RelayReplaySubject.js +4 -4
  40. package/lib/util/handlePotentialSnapshotErrors.js +2 -2
  41. package/lib/util/stableCopy.js +2 -2
  42. package/llm-docs/api-reference/entrypoint-apis/entrypoint-container.mdx +38 -0
  43. package/llm-docs/api-reference/entrypoint-apis/load-entrypoint.mdx +77 -0
  44. package/llm-docs/api-reference/entrypoint-apis/use-entrypoint-loader.mdx +99 -0
  45. package/llm-docs/api-reference/graphql/graphql-directives.mdx +378 -0
  46. package/llm-docs/api-reference/hooks/_use-lazy-load-query-extra.mdx +16 -0
  47. package/llm-docs/api-reference/hooks/load-query.mdx +84 -0
  48. package/llm-docs/api-reference/hooks/relay-environment-provider.mdx +78 -0
  49. package/llm-docs/api-reference/hooks/use-client-query.mdx +65 -0
  50. package/llm-docs/api-reference/hooks/use-fragment.mdx +69 -0
  51. package/llm-docs/api-reference/hooks/use-lazy-load-query.mdx +62 -0
  52. package/llm-docs/api-reference/hooks/use-mutation.mdx +94 -0
  53. package/llm-docs/api-reference/hooks/use-pagination-fragment.mdx +166 -0
  54. package/llm-docs/api-reference/hooks/use-prefetchable-forward-pagination-fragment.mdx +134 -0
  55. package/llm-docs/api-reference/hooks/use-preloaded-query.mdx +84 -0
  56. package/llm-docs/api-reference/hooks/use-query-loader.mdx +95 -0
  57. package/llm-docs/api-reference/hooks/use-refetchable-fragment.mdx +122 -0
  58. package/llm-docs/api-reference/hooks/use-relay-environment.mdx +37 -0
  59. package/llm-docs/api-reference/hooks/use-subscription.mdx +66 -0
  60. package/llm-docs/api-reference/relay-resolvers/docblock-format.mdx +321 -0
  61. package/llm-docs/api-reference/relay-resolvers/runtime-functions.mdx +94 -0
  62. package/llm-docs/api-reference/relay-runtime/commit-mutation.mdx +65 -0
  63. package/llm-docs/api-reference/relay-runtime/fetch-query.mdx +113 -0
  64. package/llm-docs/api-reference/relay-runtime/field-logger.mdx +170 -0
  65. package/llm-docs/api-reference/relay-runtime/observe-fragment.mdx +92 -0
  66. package/llm-docs/api-reference/relay-runtime/relay-environment.mdx +53 -0
  67. package/llm-docs/api-reference/relay-runtime/request-subscription.mdx +54 -0
  68. package/llm-docs/api-reference/relay-runtime/runtime-configuration.mdx +52 -0
  69. package/llm-docs/api-reference/relay-runtime/store.mdx +734 -0
  70. package/llm-docs/api-reference/relay-runtime/wait-for-fragment-data.mdx +89 -0
  71. package/llm-docs/api-reference/types/CacheConfig.mdx +8 -0
  72. package/llm-docs/api-reference/types/Disposable.mdx +4 -0
  73. package/llm-docs/api-reference/types/GraphQLSubscriptionConfig.mdx +17 -0
  74. package/llm-docs/api-reference/types/MutationConfig.mdx +31 -0
  75. package/llm-docs/api-reference/types/SelectorStoreUpdater.mdx +6 -0
  76. package/llm-docs/api-reference/types/UploadableMap.mdx +3 -0
  77. package/llm-docs/community/learning-resources.mdx +64 -0
  78. package/llm-docs/debugging/declarative-mutation-directives.mdx +34 -0
  79. package/llm-docs/debugging/disallowed-id-types-error.mdx +43 -0
  80. package/llm-docs/debugging/inconsistent-typename-error.mdx +47 -0
  81. package/llm-docs/debugging/relay-devtools.mdx +73 -0
  82. package/llm-docs/debugging/why-null.mdx +116 -0
  83. package/llm-docs/editor-support.mdx +55 -0
  84. package/llm-docs/error-reference/unknown-field.mdx +36 -0
  85. package/llm-docs/getting-started/babel-plugin.mdx +31 -0
  86. package/llm-docs/getting-started/compiler-config.mdx +25 -0
  87. package/llm-docs/getting-started/compiler.mdx +82 -0
  88. package/llm-docs/getting-started/lint-rules.mdx +87 -0
  89. package/llm-docs/getting-started/production.mdx +30 -0
  90. package/llm-docs/getting-started/quick-start.mdx +213 -0
  91. package/llm-docs/glossary/glossary.mdx +1040 -0
  92. package/llm-docs/guided-tour/list-data/advanced-pagination.mdx +157 -0
  93. package/llm-docs/guided-tour/list-data/connections.mdx +81 -0
  94. package/llm-docs/guided-tour/list-data/pagination.mdx +193 -0
  95. package/llm-docs/guided-tour/list-data/rendering-connections.mdx +112 -0
  96. package/llm-docs/guided-tour/list-data/streaming-pagination.mdx +87 -0
  97. package/llm-docs/guided-tour/managing-data-outside-react/retaining-queries.mdx +51 -0
  98. package/llm-docs/guided-tour/refetching/refetching-queries-with-different-data.mdx +337 -0
  99. package/llm-docs/guided-tour/refetching/refreshing-queries.mdx +350 -0
  100. package/llm-docs/guided-tour/rendering/environment.mdx +59 -0
  101. package/llm-docs/guided-tour/rendering/error-states.mdx +295 -0
  102. package/llm-docs/guided-tour/rendering/fragments.mdx +354 -0
  103. package/llm-docs/guided-tour/rendering/loading-states.mdx +245 -0
  104. package/llm-docs/guided-tour/rendering/queries.mdx +261 -0
  105. package/llm-docs/guided-tour/rendering/variables.mdx +233 -0
  106. package/llm-docs/guided-tour/reusing-cached-data/fetch-policies.mdx +56 -0
  107. package/llm-docs/guided-tour/reusing-cached-data/filling-in-missing-data.mdx +102 -0
  108. package/llm-docs/guided-tour/reusing-cached-data/introduction.mdx +22 -0
  109. package/llm-docs/guided-tour/reusing-cached-data/presence-of-data.mdx +93 -0
  110. package/llm-docs/guided-tour/reusing-cached-data/rendering-partially-cached-data.mdx +175 -0
  111. package/llm-docs/guided-tour/reusing-cached-data/staleness-of-data.mdx +116 -0
  112. package/llm-docs/guided-tour/updating-data/client-only-data.mdx +115 -0
  113. package/llm-docs/guided-tour/updating-data/graphql-mutations.mdx +334 -0
  114. package/llm-docs/guided-tour/updating-data/graphql-subscriptions.mdx +279 -0
  115. package/llm-docs/guided-tour/updating-data/imperatively-modifying-linked-fields.mdx +511 -0
  116. package/llm-docs/guided-tour/updating-data/imperatively-modifying-store-data-legacy.mdx +142 -0
  117. package/llm-docs/guided-tour/updating-data/imperatively-modifying-store-data.mdx +275 -0
  118. package/llm-docs/guided-tour/updating-data/introduction.mdx +25 -0
  119. package/llm-docs/guided-tour/updating-data/local-data-updates.mdx +71 -0
  120. package/llm-docs/guided-tour/updating-data/typesafe-updaters-faq.mdx +83 -0
  121. package/llm-docs/guided-tour/updating-data/updating-connections.mdx +592 -0
  122. package/llm-docs/guides/alias-directive.mdx +160 -0
  123. package/llm-docs/guides/catch-directive.mdx +167 -0
  124. package/llm-docs/guides/client-schema-extensions.mdx +208 -0
  125. package/llm-docs/guides/codemods.mdx +66 -0
  126. package/llm-docs/guides/data-driven-dependencies/client-3d.mdx +255 -0
  127. package/llm-docs/guides/data-driven-dependencies/configuration.mdx +127 -0
  128. package/llm-docs/guides/data-driven-dependencies/introduction.mdx +39 -0
  129. package/llm-docs/guides/data-driven-dependencies/server-3d.mdx +664 -0
  130. package/llm-docs/guides/document-comparison.mdx +106 -0
  131. package/llm-docs/guides/graphql-server-specification.mdx +453 -0
  132. package/llm-docs/guides/network-layer.mdx +69 -0
  133. package/llm-docs/guides/persisted-queries.mdx +328 -0
  134. package/llm-docs/guides/relay-resolvers/context.mdx +99 -0
  135. package/llm-docs/guides/relay-resolvers/defining-fields.mdx +151 -0
  136. package/llm-docs/guides/relay-resolvers/defining-types.mdx +164 -0
  137. package/llm-docs/guides/relay-resolvers/deprecated.mdx +27 -0
  138. package/llm-docs/guides/relay-resolvers/derived-fields.mdx +127 -0
  139. package/llm-docs/guides/relay-resolvers/descriptions.mdx +44 -0
  140. package/llm-docs/guides/relay-resolvers/enabling.mdx +41 -0
  141. package/llm-docs/guides/relay-resolvers/errors.mdx +64 -0
  142. package/llm-docs/guides/relay-resolvers/field-arguments.mdx +63 -0
  143. package/llm-docs/guides/relay-resolvers/introduction.mdx +62 -0
  144. package/llm-docs/guides/relay-resolvers/limitations.mdx +30 -0
  145. package/llm-docs/guides/relay-resolvers/live-fields.mdx +164 -0
  146. package/llm-docs/guides/relay-resolvers/return-types.mdx +161 -0
  147. package/llm-docs/guides/relay-resolvers/suspense.mdx +41 -0
  148. package/llm-docs/guides/required-directive.mdx +240 -0
  149. package/llm-docs/guides/semantic-nullability.mdx +93 -0
  150. package/llm-docs/guides/testing-relay-components.mdx +642 -0
  151. package/llm-docs/guides/testing-relay-with-preloaded-queries.mdx +160 -0
  152. package/llm-docs/guides/throw-on-field-error-directive.mdx +58 -0
  153. package/llm-docs/guides/type-emission.mdx +414 -0
  154. package/llm-docs/home.mdx +32 -0
  155. package/llm-docs/principles-and-architecture/architecture-overview.mdx +24 -0
  156. package/llm-docs/principles-and-architecture/compiler-architecture.mdx +106 -0
  157. package/llm-docs/principles-and-architecture/runtime-architecture.mdx +249 -0
  158. package/llm-docs/principles-and-architecture/thinking-in-graphql.mdx +309 -0
  159. package/llm-docs/principles-and-architecture/thinking-in-relay.mdx +104 -0
  160. package/llm-docs/principles-and-architecture/videos.mdx +50 -0
  161. package/llm-docs/tutorial/arrays-lists.mdx +126 -0
  162. package/llm-docs/tutorial/fragments-1.mdx +487 -0
  163. package/llm-docs/tutorial/graphql.mdx +172 -0
  164. package/llm-docs/tutorial/interfaces-polymorphism.mdx +161 -0
  165. package/llm-docs/tutorial/intro.mdx +58 -0
  166. package/llm-docs/tutorial/mutations-updates.mdx +624 -0
  167. package/llm-docs/tutorial/organizing-mutations-queries-and-subscriptions.mdx +13 -0
  168. package/llm-docs/tutorial/queries-1.mdx +267 -0
  169. package/llm-docs/tutorial/queries-2.mdx +389 -0
  170. package/llm-docs/tutorial/refetchable-fragments.mdx +352 -0
  171. package/multi-actor-environment/ActorIdentifier.js.flow +2 -2
  172. package/multi-actor-environment/ActorSpecificEnvironment.js.flow +7 -7
  173. package/multi-actor-environment/ActorUtils.js.flow +1 -1
  174. package/multi-actor-environment/MultiActorEnvironment.js.flow +12 -8
  175. package/multi-actor-environment/MultiActorEnvironmentTypes.js.flow +3 -3
  176. package/mutations/RelayDeclarativeMutationConfig.js.flow +9 -9
  177. package/mutations/RelayRecordProxy.js.flow +8 -11
  178. package/mutations/RelayRecordSourceMutator.js.flow +4 -4
  179. package/mutations/RelayRecordSourceProxy.js.flow +4 -4
  180. package/mutations/RelayRecordSourceSelectorProxy.js.flow +6 -6
  181. package/mutations/applyOptimisticMutation.js.flow +2 -2
  182. package/mutations/commitMutation.js.flow +20 -16
  183. package/mutations/createUpdatableProxy.js.flow +19 -19
  184. package/mutations/readUpdatableFragment.js.flow +3 -3
  185. package/mutations/readUpdatableQuery.js.flow +3 -3
  186. package/mutations/validateMutation.js.flow +7 -7
  187. package/network/RelayNetworkTypes.js.flow +4 -4
  188. package/network/RelayObservable.js.flow +16 -14
  189. package/network/RelayQueryResponseCache.js.flow +3 -3
  190. package/network/wrapNetworkWithLogObserver.js.flow +1 -1
  191. package/package.json +2 -1
  192. package/query/GraphQLTag.js.flow +22 -10
  193. package/query/fetchQuery.js.flow +23 -10
  194. package/query/fetchQuery_DEPRECATED.js.flow +1 -1
  195. package/store/DataChecker.js.flow +43 -9
  196. package/store/NormalizationEngine.js.flow +779 -0
  197. package/store/OperationExecutor.js.flow +173 -70
  198. package/store/RelayConcreteVariables.js.flow +5 -5
  199. package/store/RelayErrorTrie.js.flow +12 -12
  200. package/store/RelayExperimentalGraphResponseHandler.js.flow +3 -3
  201. package/store/RelayExperimentalGraphResponseTransform.js.flow +10 -10
  202. package/store/RelayModernEnvironment.js.flow +41 -26
  203. package/store/RelayModernFragmentSpecResolver.js.flow +1 -1
  204. package/store/RelayModernOperationDescriptor.js.flow +1 -1
  205. package/store/RelayModernRecord.js.flow +46 -22
  206. package/store/RelayModernSelector.js.flow +21 -21
  207. package/store/RelayModernStore.js.flow +219 -58
  208. package/store/RelayOperationTracker.js.flow +2 -2
  209. package/store/RelayOptimisticRecordSource.js.flow +2 -2
  210. package/store/RelayPublishQueue.js.flow +21 -12
  211. package/store/RelayReader.js.flow +130 -58
  212. package/store/RelayRecordSource.js.flow +10 -0
  213. package/store/RelayRecordState.js.flow +1 -1
  214. package/store/RelayReferenceMarker.js.flow +5 -4
  215. package/store/RelayResponseNormalizer.js.flow +130 -54
  216. package/store/RelayStoreSubscriptions.js.flow +52 -8
  217. package/store/RelayStoreTypes.js.flow +153 -64
  218. package/store/RelayStoreUtils.js.flow +15 -7
  219. package/store/ResolverCache.js.flow +2 -2
  220. package/store/ResolverFragments.js.flow +12 -12
  221. package/store/StoreInspector.js.flow +6 -7
  222. package/store/cloneRelayHandleSourceField.js.flow +1 -1
  223. package/store/cloneRelayScalarHandleSourceField.js.flow +1 -1
  224. package/store/createRelayContext.js.flow +1 -1
  225. package/store/createRelayLoggingContext.js.flow +4 -4
  226. package/store/defaultGetDataID.js.flow +2 -2
  227. package/store/isRelayModernEnvironment.js.flow +4 -2
  228. package/store/live-resolvers/LiveResolverCache.js.flow +55 -20
  229. package/store/live-resolvers/LiveResolverSuspenseSentinel.js.flow +3 -3
  230. package/store/live-resolvers/getOutputTypeRecordIDs.js.flow +1 -1
  231. package/store/live-resolvers/isLiveStateValue.js.flow +2 -2
  232. package/store/live-resolvers/resolverDataInjector.js.flow +8 -5
  233. package/store/observeFragmentExperimental.js.flow +49 -20
  234. package/store/observeQueryExperimental.js.flow +5 -5
  235. package/store/readInlineData.js.flow +4 -4
  236. package/store/waitForFragmentExperimental.js.flow +3 -3
  237. package/subscription/requestSubscription.js.flow +7 -7
  238. package/util/NormalizationNode.js.flow +34 -32
  239. package/util/ReaderNode.js.flow +32 -30
  240. package/util/RelayConcreteNode.js.flow +5 -5
  241. package/util/RelayError.js.flow +4 -1
  242. package/util/RelayFeatureFlags.js.flow +21 -1
  243. package/util/RelayProfiler.js.flow +1 -1
  244. package/util/RelayReplaySubject.js.flow +3 -3
  245. package/util/RelayRuntimeTypes.js.flow +11 -11
  246. package/util/createPayloadFor3DField.js.flow +9 -5
  247. package/util/deepFreeze.js.flow +2 -2
  248. package/util/getFragmentIdentifier.js.flow +1 -1
  249. package/util/getPaginationMetadata.js.flow +1 -1
  250. package/util/getPaginationVariables.js.flow +1 -1
  251. package/util/getPendingOperationsForFragment.js.flow +2 -2
  252. package/util/getRefetchMetadata.js.flow +6 -5
  253. package/util/getValueAtPath.js.flow +3 -3
  254. package/util/handlePotentialSnapshotErrors.js.flow +5 -5
  255. package/util/isEmptyObject.js.flow +1 -1
  256. package/util/isPromise.js.flow +2 -2
  257. package/util/isScalarAndEqual.js.flow +1 -1
  258. package/util/recycleNodesInto.js.flow +2 -2
  259. package/util/registerEnvironmentWithDevTools.js.flow +1 -1
  260. package/util/shallowFreeze.js.flow +1 -1
  261. package/util/stableCopy.js.flow +5 -5
  262. package/util/withProvidedVariables.js.flow +14 -10
@@ -37,6 +37,7 @@ import type {
37
37
  DataIDSet,
38
38
  FieldError,
39
39
  FieldErrors,
40
+ LogFunction,
40
41
  MissingClientEdgeRequestInfo,
41
42
  Record,
42
43
  RecordSource,
@@ -57,6 +58,7 @@ const RelayConcreteVariables = require('./RelayConcreteVariables');
57
58
  const RelayModernRecord = require('./RelayModernRecord');
58
59
  const {
59
60
  CLIENT_EDGE_TRAVERSAL_PATH,
61
+ FIELD_GRANULAR_NOTIFICATIONS_KEY,
60
62
  FRAGMENT_OWNER_KEY,
61
63
  FRAGMENT_PROP_NAME_KEY,
62
64
  FRAGMENTS_KEY,
@@ -64,6 +66,7 @@ const {
64
66
  MODULE_COMPONENT_KEY,
65
67
  ROOT_ID,
66
68
  getArgumentValues,
69
+ getFieldNotificationKey,
67
70
  getModuleComponentKey,
68
71
  getStorageKey,
69
72
  } = require('./RelayStoreUtils');
@@ -78,12 +81,14 @@ const invariant = require('invariant');
78
81
  function read(
79
82
  recordSource: RecordSource,
80
83
  selector: SingularReaderSelector,
84
+ log: ?LogFunction,
81
85
  resolverCache?: ResolverCache,
82
86
  resolverContext?: ResolverContext,
83
87
  ): Snapshot {
84
88
  const reader = new RelayReader(
85
89
  recordSource,
86
90
  selector,
91
+ log,
87
92
  resolverCache ?? new NoopResolverCache(),
88
93
  resolverContext,
89
94
  );
@@ -100,6 +105,7 @@ class RelayReader {
100
105
  _missingLiveResolverFields: Array<DataID>;
101
106
  _isWithinUnmatchedTypeRefinement: boolean;
102
107
  _fieldErrors: ?FieldErrors;
108
+ _fieldGranularNotificationDataID: ?DataID;
103
109
  _owner: RequestDescriptor;
104
110
  // Exec time resolvers are run before reaching the Relay store so the store already contains
105
111
  // the normalized data; the same as if the data were sent from the server. However, since a
@@ -115,10 +121,15 @@ class RelayReader {
115
121
  _resolverCache: ResolverCache;
116
122
  _fragmentName: string;
117
123
  _resolverContext: ?ResolverContext;
124
+ // The log function currently is only used to log fragment spread accesses for unused
125
+ // fragments detection, so it is not passed in from all callsites. If we decide to
126
+ // extend the logging, this needs to be updated.
127
+ _log: ?LogFunction;
118
128
 
119
129
  constructor(
120
130
  recordSource: RecordSource,
121
131
  selector: SingularReaderSelector,
132
+ log: ?LogFunction,
122
133
  resolverCache: ResolverCache,
123
134
  resolverContext: ?ResolverContext,
124
135
  ) {
@@ -130,6 +141,7 @@ class RelayReader {
130
141
  this._isMissingData = false;
131
142
  this._isWithinUnmatchedTypeRefinement = false;
132
143
  this._fieldErrors = null;
144
+ this._fieldGranularNotificationDataID = null;
133
145
  this._owner = selector.owner;
134
146
  this._useExecTimeResolvers =
135
147
  this._owner.node.operation.use_exec_time_resolvers ??
@@ -144,6 +156,7 @@ class RelayReader {
144
156
  this._fragmentName = selector.node.name;
145
157
  this._updatedDataIDs = new Set();
146
158
  this._resolverContext = resolverContext;
159
+ this._log = log;
147
160
  }
148
161
 
149
162
  read(): Snapshot {
@@ -200,8 +213,17 @@ class RelayReader {
200
213
  this._resolverCache.notifyUpdatedSubscribers(this._updatedDataIDs);
201
214
  this._updatedDataIDs.clear();
202
215
  }
216
+
217
+ if (RelayFeatureFlags.ENABLE_READER_FRAGMENTS_LOGGING) {
218
+ this._log?.({
219
+ name: 'reader.read',
220
+ selector: this._selector,
221
+ });
222
+ }
223
+
203
224
  return {
204
225
  data,
226
+ fieldErrors: this._fieldErrors,
205
227
  isMissingData: this._isMissingData && isDataExpectedToBePresent,
206
228
  missingClientEdges: this._missingClientEdges.length
207
229
  ? this._missingClientEdges
@@ -209,7 +231,6 @@ class RelayReader {
209
231
  missingLiveResolverFields: this._missingLiveResolverFields,
210
232
  seenRecords: this._seenRecords,
211
233
  selector: this._selector,
212
- fieldErrors: this._fieldErrors,
213
234
  };
214
235
  }
215
236
 
@@ -227,12 +248,12 @@ class RelayReader {
227
248
  for (let i = 0; i < errors.length; i++) {
228
249
  const error = errors[i];
229
250
  this._fieldErrors.push({
251
+ error,
252
+ fieldPath: (error.path ?? []).join('.'),
253
+ handled: false,
230
254
  kind: 'relay_field_payload.error',
231
255
  owner,
232
- fieldPath: (error.path ?? []).join('.'),
233
- error,
234
256
  shouldThrow: this._selector.node.metadata?.throwOnFieldError ?? false,
235
- handled: false,
236
257
  // the uiContext is always undefined here.
237
258
  // the loggingContext is provided by hooks - and assigned to uiContext in handlePotentialSnapshotErrors
238
259
  uiContext: undefined,
@@ -252,20 +273,20 @@ class RelayReader {
252
273
  const owner = this._fragmentName;
253
274
 
254
275
  this._fieldErrors.push(
255
- this._selector.node.metadata?.throwOnFieldError ?? false
276
+ (this._selector.node.metadata?.throwOnFieldError ?? false)
256
277
  ? {
257
- kind: 'missing_expected_data.throw',
258
- owner,
259
278
  fieldPath: fieldName,
260
279
  handled: false,
280
+ kind: 'missing_expected_data.throw',
281
+ owner,
261
282
  // the uiContext is always undefined here.
262
283
  // the loggingContext is provided by hooks - and assigned to uiContext in handlePotentialSnapshotErrors
263
284
  uiContext: undefined,
264
285
  }
265
286
  : {
287
+ fieldPath: fieldName,
266
288
  kind: 'missing_expected_data.log',
267
289
  owner,
268
- fieldPath: fieldName,
269
290
  // the uiContext is always undefined here.
270
291
  // the loggingContext is provided by hooks - and assigned to uiContext in handlePotentialSnapshotErrors
271
292
  uiContext: undefined,
@@ -282,8 +303,8 @@ class RelayReader {
282
303
  // data off of a client extension field.
283
304
  if (top !== null) {
284
305
  this._missingClientEdges.push({
285
- request: top.readerClientEdge.operation,
286
306
  clientEdgeDestinationID: top.clientEdgeDestinationID,
307
+ request: top.readerClientEdge.operation,
287
308
  });
288
309
  }
289
310
  }
@@ -295,12 +316,22 @@ class RelayReader {
295
316
  prevData: ?SelectorData,
296
317
  ): ?SelectorData {
297
318
  const record = this._recordSource.get(dataID);
298
- this._seenRecords.add(dataID);
319
+ const prevFieldGranularDataID = this._fieldGranularNotificationDataID;
320
+ if (
321
+ record != null &&
322
+ RelayModernRecord.getValue(record, FIELD_GRANULAR_NOTIFICATIONS_KEY)
323
+ ) {
324
+ this._fieldGranularNotificationDataID = dataID;
325
+ } else {
326
+ this._seenRecords.add(dataID);
327
+ this._fieldGranularNotificationDataID = null;
328
+ }
299
329
  if (record == null) {
300
330
  if (record === undefined) {
301
331
  this._markDataAsMissing('<record>');
302
332
  }
303
- // $FlowFixMe[incompatible-return]
333
+ this._fieldGranularNotificationDataID = prevFieldGranularDataID;
334
+ // $FlowFixMe[incompatible-type]
304
335
  return record;
305
336
  }
306
337
  const data = prevData || {};
@@ -309,10 +340,11 @@ class RelayReader {
309
340
  record,
310
341
  data,
311
342
  );
343
+ this._fieldGranularNotificationDataID = prevFieldGranularDataID;
312
344
  return hadRequiredData ? data : null;
313
345
  }
314
346
 
315
- _getVariableValue(name: string): mixed {
347
+ _getVariableValue(name: string): unknown {
316
348
  invariant(
317
349
  this._variables.hasOwnProperty(name),
318
350
  'RelayReader(): Undefined variable `%s`.',
@@ -342,10 +374,10 @@ class RelayReader {
342
374
  switch (selection.action) {
343
375
  case 'THROW':
344
376
  this._fieldErrors.push({
345
- kind: 'missing_required_field.throw',
346
377
  fieldPath: fieldName,
347
- owner,
348
378
  handled: false,
379
+ kind: 'missing_required_field.throw',
380
+ owner,
349
381
  // the uiContext is always undefined here.
350
382
  // the loggingContext is provided by hooks - and assigned to uiContext in handlePotentialSnapshotErrors
351
383
  uiContext: undefined,
@@ -353,8 +385,8 @@ class RelayReader {
353
385
  return;
354
386
  case 'LOG':
355
387
  this._fieldErrors.push({
356
- kind: 'missing_required_field.log',
357
388
  fieldPath: fieldName,
389
+ kind: 'missing_required_field.log',
358
390
  owner,
359
391
  // the uiContext is always undefined here.
360
392
  // the loggingContext is provided by hooks - and assigned to uiContext in handlePotentialSnapshotErrors
@@ -362,13 +394,13 @@ class RelayReader {
362
394
  });
363
395
  return;
364
396
  default:
365
- (selection.action: empty);
397
+ selection.action as empty;
366
398
  }
367
399
  }
368
400
 
369
401
  _handleRequiredFieldValue(
370
402
  selection: ReaderRequiredField,
371
- value: mixed,
403
+ value: unknown,
372
404
  ): boolean /*should continue to siblings*/ {
373
405
  if (value == null) {
374
406
  this._maybeReportUnexpectedNull(selection);
@@ -402,8 +434,8 @@ class RelayReader {
402
434
  _value: T,
403
435
  to: CatchFieldTo,
404
436
  previousResponseFields: ?FieldErrors,
405
- ): ?T | Result<T, mixed> {
406
- let value: T | null | Result<T, mixed> = _value;
437
+ ): ?T | Result<T, unknown> {
438
+ let value: T | null | Result<T, unknown> = _value;
407
439
  switch (to) {
408
440
  case 'RESULT':
409
441
  value = this._asResult(_value);
@@ -414,7 +446,7 @@ class RelayReader {
414
446
  }
415
447
  break;
416
448
  default:
417
- (to: empty);
449
+ to as empty;
418
450
  }
419
451
 
420
452
  const childrenFieldErrors = this._fieldErrors;
@@ -446,7 +478,7 @@ class RelayReader {
446
478
  * **Note**: This method does _not_ mark errors as handled. It is the caller's
447
479
  * responsibility to ensure that errors are marked as handled.
448
480
  */
449
- _asResult<T>(value: T): Result<T, mixed> {
481
+ _asResult<T>(value: T): Result<T, unknown> {
450
482
  if (this._fieldErrors == null || this._fieldErrors.length === 0) {
451
483
  return {ok: true, value};
452
484
  }
@@ -477,7 +509,7 @@ class RelayReader {
477
509
  // For backwards compatibility, we don't surface log level missing required fields
478
510
  return null;
479
511
  default:
480
- (error.kind: empty);
512
+ error.kind as empty;
481
513
  invariant(
482
514
  false,
483
515
  'Unexpected error fieldError kind: %s',
@@ -487,11 +519,11 @@ class RelayReader {
487
519
  })
488
520
  .filter(Boolean);
489
521
 
490
- return {ok: false, errors};
522
+ return {errors, ok: false};
491
523
  }
492
524
 
493
525
  _traverseSelections(
494
- selections: $ReadOnlyArray<ReaderSelection>,
526
+ selections: ReadonlyArray<ReaderSelection>,
495
527
  record: Record,
496
528
  data: SelectorData,
497
529
  ): boolean /* had all expected data */ {
@@ -656,7 +688,7 @@ class RelayReader {
656
688
  }
657
689
  break;
658
690
  default:
659
- (selection: empty);
691
+ selection as empty;
660
692
  invariant(
661
693
  false,
662
694
  'RelayReader(): Unexpected ast kind `%s`.',
@@ -671,7 +703,7 @@ class RelayReader {
671
703
  selection: ReaderRequiredField | ReaderCatchField,
672
704
  record: Record,
673
705
  data: SelectorData,
674
- ): ?mixed {
706
+ ): ?unknown {
675
707
  switch (selection.field.kind) {
676
708
  case 'ScalarField':
677
709
  return this._readScalar(selection.field, record, data);
@@ -709,7 +741,7 @@ class RelayReader {
709
741
  case 'AliasedInlineFragmentSpread':
710
742
  return this._readAliasedInlineFragment(selection.field, record, data);
711
743
  default:
712
- (selection.field.kind: empty);
744
+ selection.field.kind as empty;
713
745
  invariant(
714
746
  false,
715
747
  'RelayReader(): Unexpected ast kind `%s`.',
@@ -722,7 +754,7 @@ class RelayReader {
722
754
  field: ReaderRelayResolver | ReaderRelayLiveResolver,
723
755
  record: Record,
724
756
  data: SelectorData,
725
- ): mixed {
757
+ ): unknown {
726
758
  const parentRecordID = RelayModernRecord.getDataID(record);
727
759
  const prevErrors = this._fieldErrors;
728
760
  this._fieldErrors = null;
@@ -737,7 +769,7 @@ class RelayReader {
737
769
  _readResolverFieldImpl(
738
770
  field: ReaderRelayResolver | ReaderRelayLiveResolver,
739
771
  parentRecordID: DataID,
740
- ): mixed {
772
+ ): unknown {
741
773
  const {fragment} = field;
742
774
 
743
775
  // Found when reading the resolver fragment, which can happen either when
@@ -762,21 +794,26 @@ class RelayReader {
762
794
  // already been set and will still be used in this case.
763
795
  return {
764
796
  data: snapshot.data,
765
- isMissingData: snapshot.isMissingData,
766
797
  fieldErrors: snapshot.fieldErrors,
798
+ isMissingData: snapshot.isMissingData,
767
799
  };
768
800
  }
769
801
 
770
802
  snapshot = read(
771
803
  this._recordSource,
772
804
  singularReaderSelector,
805
+ // This reads data for a rootFragment in read time resolvers. There is no fragment
806
+ // spread created for it so the existing fragment spread logger can't be used to log
807
+ // unused rootFragments. Thus we skip passing the logger here for now.
808
+ null,
773
809
  this._resolverCache,
810
+ undefined,
774
811
  );
775
812
 
776
813
  return {
777
814
  data: snapshot.data,
778
- isMissingData: snapshot.isMissingData,
779
815
  fieldErrors: snapshot.fieldErrors,
816
+ isMissingData: snapshot.isMissingData,
780
817
  };
781
818
  };
782
819
 
@@ -786,16 +823,16 @@ class RelayReader {
786
823
  // * `snapshot` The snapshot returned when reading the resolver's root fragment (if it has one)
787
824
  // * `error` If the resolver throws, its error is caught (inside
788
825
  // `getResolverValue`) and converted into an error object.
789
- const evaluate = (): EvaluationResult<mixed> => {
826
+ const evaluate = (): EvaluationResult<unknown> => {
790
827
  if (fragment != null) {
791
828
  const key: SelectorData = {
792
- __id: parentRecordID,
793
829
  __fragmentOwner: this._owner,
794
830
  __fragments: {
795
831
  [fragment.name]: fragment.args
796
832
  ? getArgumentValues(fragment.args, this._variables)
797
833
  : {},
798
834
  },
835
+ __id: parentRecordID,
799
836
  };
800
837
  if (
801
838
  this._clientEdgeTraversalPath.length > 0 &&
@@ -806,6 +843,7 @@ class RelayReader {
806
843
  key[CLIENT_EDGE_TRAVERSAL_PATH] = [...this._clientEdgeTraversalPath];
807
844
  }
808
845
  const resolverContext = {getDataForResolverFragment};
846
+ // $FlowFixMe[incompatible-type]
809
847
  return withResolverContext(resolverContext, () => {
810
848
  const [resolverResult, resolverError] = getResolverValue(
811
849
  field,
@@ -813,7 +851,7 @@ class RelayReader {
813
851
  key,
814
852
  this._resolverContext,
815
853
  );
816
- return {resolverResult, snapshot, error: resolverError};
854
+ return {error: resolverError, resolverResult, snapshot};
817
855
  });
818
856
  } else {
819
857
  const [resolverResult, resolverError] = getResolverValue(
@@ -822,7 +860,7 @@ class RelayReader {
822
860
  null,
823
861
  this._resolverContext,
824
862
  );
825
- return {resolverResult, snapshot: undefined, error: resolverError};
863
+ return {error: resolverError, resolverResult, snapshot: undefined};
826
864
  }
827
865
  };
828
866
 
@@ -918,12 +956,12 @@ class RelayReader {
918
956
  // to be logged.
919
957
  if (resolverError) {
920
958
  const errorEvent: FieldError = {
921
- kind: 'relay_resolver.error',
959
+ error: resolverError,
922
960
  fieldPath,
961
+ handled: false,
962
+ kind: 'relay_resolver.error',
923
963
  owner: this._fragmentName,
924
- error: resolverError,
925
964
  shouldThrow: this._selector.node.metadata?.throwOnFieldError ?? false,
926
- handled: false,
927
965
  // the uiContext is always undefined here.
928
966
  // the loggingContext is provided by hooks - and assigned to uiContext in handlePotentialSnapshotErrors
929
967
  uiContext: undefined,
@@ -964,7 +1002,7 @@ class RelayReader {
964
1002
  field: ReaderClientEdge,
965
1003
  record: Record,
966
1004
  data: SelectorData,
967
- ): ?mixed {
1005
+ ): ?unknown {
968
1006
  const backingField = field.backingField;
969
1007
 
970
1008
  // Because ReaderClientExtension doesn't have `alias` or `name` and so I don't know
@@ -997,7 +1035,7 @@ class RelayReader {
997
1035
  backingField.path,
998
1036
  this._owner.identifier,
999
1037
  );
1000
- let storeIDs: $ReadOnlyArray<DataID>;
1038
+ let storeIDs: ReadonlyArray<DataID>;
1001
1039
  invariant(
1002
1040
  field.kind === 'ClientEdgeToClientObject',
1003
1041
  'Unexpected Client Edge to plural server type `%s`. This should be prevented by the compiler.',
@@ -1084,8 +1122,8 @@ class RelayReader {
1084
1122
  } else {
1085
1123
  storeID = id;
1086
1124
  traversalPathSegment = {
1087
- readerClientEdge: field,
1088
1125
  clientEdgeDestinationID: id,
1126
+ readerClientEdge: field,
1089
1127
  };
1090
1128
  }
1091
1129
 
@@ -1145,9 +1183,17 @@ class RelayReader {
1145
1183
  field: ReaderScalarField | ReaderRelayResolver | ReaderRelayLiveResolver,
1146
1184
  record: Record,
1147
1185
  data: SelectorData,
1148
- ): ?mixed {
1186
+ ): ?unknown {
1149
1187
  const fieldName = field.alias ?? field.name;
1150
1188
  const storageKey = getStorageKey(field, this._variables);
1189
+ if (this._fieldGranularNotificationDataID != null) {
1190
+ this._seenRecords.add(
1191
+ getFieldNotificationKey(
1192
+ this._fieldGranularNotificationDataID,
1193
+ storageKey,
1194
+ ),
1195
+ );
1196
+ }
1151
1197
  const value = RelayModernRecord.getValue(record, storageKey);
1152
1198
  if (
1153
1199
  value === null ||
@@ -1167,9 +1213,17 @@ class RelayReader {
1167
1213
  field: ReaderLinkedField,
1168
1214
  record: Record,
1169
1215
  data: SelectorData,
1170
- ): ?mixed {
1216
+ ): ?unknown {
1171
1217
  const fieldName = field.alias ?? field.name;
1172
1218
  const storageKey = getStorageKey(field, this._variables);
1219
+ if (this._fieldGranularNotificationDataID != null) {
1220
+ this._seenRecords.add(
1221
+ getFieldNotificationKey(
1222
+ this._fieldGranularNotificationDataID,
1223
+ storageKey,
1224
+ ),
1225
+ );
1226
+ }
1173
1227
  const linkedID = RelayModernRecord.getLinkedRecordID(record, storageKey);
1174
1228
  if (linkedID == null) {
1175
1229
  data[fieldName] = linkedID;
@@ -1265,7 +1319,7 @@ class RelayReader {
1265
1319
  field: ReaderActorChange,
1266
1320
  record: Record,
1267
1321
  data: SelectorData,
1268
- ): ?mixed {
1322
+ ): ?unknown {
1269
1323
  const fieldName = field.alias ?? field.name;
1270
1324
  const storageKey = getStorageKey(field, this._variables);
1271
1325
  const externalRef = RelayModernRecord.getActorLinkedRecordID(
@@ -1303,8 +1357,16 @@ class RelayReader {
1303
1357
  field: ReaderLinkedField,
1304
1358
  record: Record,
1305
1359
  data: SelectorData,
1306
- ): ?mixed {
1360
+ ): ?unknown {
1307
1361
  const storageKey = getStorageKey(field, this._variables);
1362
+ if (this._fieldGranularNotificationDataID != null) {
1363
+ this._seenRecords.add(
1364
+ getFieldNotificationKey(
1365
+ this._fieldGranularNotificationDataID,
1366
+ storageKey,
1367
+ ),
1368
+ );
1369
+ }
1308
1370
  const linkedIDs = RelayModernRecord.getLinkedRecordIDs(record, storageKey);
1309
1371
  if (
1310
1372
  linkedIDs === null ||
@@ -1319,10 +1381,10 @@ class RelayReader {
1319
1381
 
1320
1382
  _readLinkedIds(
1321
1383
  field: ReaderLinkedField,
1322
- linkedIDs: ?$ReadOnlyArray<?DataID>,
1384
+ linkedIDs: ?ReadonlyArray<?DataID>,
1323
1385
  record: Record,
1324
1386
  data: SelectorData,
1325
- ): ?mixed {
1387
+ ): ?unknown {
1326
1388
  const fieldName = field.alias ?? field.name;
1327
1389
 
1328
1390
  if (linkedIDs == null) {
@@ -1410,9 +1472,9 @@ class RelayReader {
1410
1472
  // - For the matched module, create a reference to the module
1411
1473
  this._createFragmentPointer(
1412
1474
  {
1475
+ args: moduleImport.args,
1413
1476
  kind: 'FragmentSpread',
1414
1477
  name: moduleImport.fragmentName,
1415
- args: moduleImport.args,
1416
1478
  },
1417
1479
  record,
1418
1480
  data,
@@ -1438,7 +1500,7 @@ class RelayReader {
1438
1500
  aliasedInlineFragment: ReaderAliasedInlineFragmentSpread,
1439
1501
  record: Record,
1440
1502
  data: SelectorData,
1441
- ) {
1503
+ ): ?unknown {
1442
1504
  const prevErrors = this._fieldErrors;
1443
1505
  this._fieldErrors = null;
1444
1506
  let fieldValue = this._readInlineFragment(
@@ -1452,6 +1514,7 @@ class RelayReader {
1452
1514
  fieldValue = null;
1453
1515
  }
1454
1516
  data[aliasedInlineFragment.name] = fieldValue;
1517
+ return fieldValue;
1455
1518
  }
1456
1519
 
1457
1520
  // Has three possible return values:
@@ -1569,9 +1632,9 @@ class RelayReader {
1569
1632
  ): void {
1570
1633
  let fragmentPointers = data[FRAGMENTS_KEY];
1571
1634
  if (fragmentPointers == null) {
1572
- fragmentPointers = data[FRAGMENTS_KEY] = ({}: {
1635
+ fragmentPointers = data[FRAGMENTS_KEY] = {} as {
1573
1636
  [string]: Arguments,
1574
- });
1637
+ };
1575
1638
  }
1576
1639
  invariant(
1577
1640
  typeof fragmentPointers === 'object' && fragmentPointers != null,
@@ -1582,12 +1645,13 @@ class RelayReader {
1582
1645
  if (data[ID_KEY] == null) {
1583
1646
  data[ID_KEY] = RelayModernRecord.getDataID(record);
1584
1647
  }
1585
- // $FlowFixMe[cannot-write] - writing into read-only field
1586
- fragmentPointers[fragmentSpread.name] = getArgumentValues(
1648
+ const args = getArgumentValues(
1587
1649
  fragmentSpread.args,
1588
1650
  this._variables,
1589
1651
  this._isWithinUnmatchedTypeRefinement,
1590
1652
  );
1653
+ // $FlowFixMe[cannot-write] - writing into read-only field
1654
+ fragmentPointers[fragmentSpread.name] = args;
1591
1655
  data[FRAGMENT_OWNER_KEY] = this._owner;
1592
1656
 
1593
1657
  if (
@@ -1598,6 +1662,14 @@ class RelayReader {
1598
1662
  ) {
1599
1663
  data[CLIENT_EDGE_TRAVERSAL_PATH] = [...this._clientEdgeTraversalPath];
1600
1664
  }
1665
+
1666
+ if (RelayFeatureFlags.ENABLE_READER_FRAGMENTS_LOGGING) {
1667
+ this._log?.({
1668
+ name: 'reader.fragmentSpread',
1669
+ fragmentName: fragmentSpread.name,
1670
+ data,
1671
+ });
1672
+ }
1601
1673
  }
1602
1674
 
1603
1675
  _createInlineDataOrResolverFragmentPointer(
@@ -1607,7 +1679,7 @@ class RelayReader {
1607
1679
  ): void {
1608
1680
  let fragmentPointers = data[FRAGMENTS_KEY];
1609
1681
  if (fragmentPointers == null) {
1610
- fragmentPointers = data[FRAGMENTS_KEY] = ({}: {[string]: {...}});
1682
+ fragmentPointers = data[FRAGMENTS_KEY] = {} as {[string]: {...}};
1611
1683
  }
1612
1684
  invariant(
1613
1685
  typeof fragmentPointers === 'object' && fragmentPointers != null,
@@ -1665,7 +1737,7 @@ class RelayReader {
1665
1737
  // still in flight.
1666
1738
  this._markDataAsMissing('<abstract-type-hint>');
1667
1739
  }
1668
- // $FlowFixMe Casting record value
1740
+ // $FlowFixMe[incompatible-type] Casting record value
1669
1741
  return implementsInterface;
1670
1742
  }
1671
1743
  }
@@ -1696,9 +1768,9 @@ function markFieldErrorHasHandled(event: FieldError): FieldError {
1696
1768
  function getResolverValue(
1697
1769
  field: ReaderRelayResolver | ReaderRelayLiveResolver,
1698
1770
  variables: Variables,
1699
- fragmentKey: mixed,
1771
+ fragmentKey: unknown,
1700
1772
  resolverContext: ?ResolverContext,
1701
- ): [mixed, ?Error] {
1773
+ ): [unknown, ?Error] {
1702
1774
  // Support for languages that work (best) with ES6 modules, such as TypeScript.
1703
1775
  const resolverFunction =
1704
1776
  typeof field.resolverModule === 'function'
@@ -1734,7 +1806,7 @@ function getResolverValue(
1734
1806
  }
1735
1807
 
1736
1808
  function extractIdFromResponse(
1737
- individualResponse: mixed,
1809
+ individualResponse: unknown,
1738
1810
  path: string,
1739
1811
  owner: string,
1740
1812
  ): string {
@@ -19,8 +19,10 @@ import type {
19
19
  RecordSourceJSON,
20
20
  } from './RelayStoreTypes';
21
21
 
22
+ const RelayFeatureFlags = require('../util/RelayFeatureFlags');
22
23
  const RelayModernRecord = require('./RelayModernRecord');
23
24
  const RelayRecordState = require('./RelayRecordState');
25
+ const {RELAY_RESOLVER_RECORD_TYPENAME} = require('./RelayStoreUtils');
24
26
 
25
27
  const {EXISTENT, NONEXISTENT, UNKNOWN} = RelayRecordState;
26
28
 
@@ -88,6 +90,14 @@ class RelayRecordSource implements MutableRecordSource {
88
90
  toJSON(): RecordSourceJSON {
89
91
  const obj: {...RecordSourceJSON} = {};
90
92
  for (const [key, record] of this._records) {
93
+ // Filter out any Relay Resolver records
94
+ if (
95
+ RelayFeatureFlags.FILTER_OUT_RELAY_RESOLVER_RECORDS &&
96
+ record != null &&
97
+ RelayModernRecord.getType(record) === RELAY_RESOLVER_RECORD_TYPENAME
98
+ ) {
99
+ continue;
100
+ }
91
101
  obj[key] = RelayModernRecord.toJSON<null | void>(record);
92
102
  }
93
103
  return obj;
@@ -31,6 +31,6 @@ const RelayRecordState = {
31
31
  UNKNOWN: 'UNKNOWN',
32
32
  } as const;
33
33
 
34
- export type RecordState = $Keys<typeof RelayRecordState>;
34
+ export type RecordState = keyof typeof RelayRecordState;
35
35
 
36
36
  module.exports = RelayRecordState;
@@ -106,7 +106,7 @@ class RelayReferenceMarker {
106
106
  this._traverseSelections(node.selections, record);
107
107
  }
108
108
 
109
- _getVariableValue(name: string): mixed {
109
+ _getVariableValue(name: string): unknown {
110
110
  invariant(
111
111
  this._variables.hasOwnProperty(name),
112
112
  'RelayReferenceMarker(): Undefined variable `%s`.',
@@ -116,7 +116,7 @@ class RelayReferenceMarker {
116
116
  }
117
117
 
118
118
  _traverseSelections(
119
- selections: $ReadOnlyArray<NormalizationSelection>,
119
+ selections: ReadonlyArray<NormalizationSelection>,
120
120
  record: Record,
121
121
  ): void {
122
122
  selections.forEach(selection => {
@@ -229,7 +229,7 @@ class RelayReferenceMarker {
229
229
  this._traverseClientEdgeToClientObject(selection, record);
230
230
  break;
231
231
  default:
232
- (selection: empty);
232
+ selection as empty;
233
233
  invariant(
234
234
  false,
235
235
  'RelayReferenceMarker: Unknown AST node `%s`.',
@@ -255,16 +255,17 @@ class RelayReferenceMarker {
255
255
  if (resolverRecord == null) {
256
256
  return;
257
257
  }
258
+ const {linkedField} = field;
258
259
  if (field.backingField.isOutputType) {
259
260
  // Mark all @outputType record IDs
260
261
  const outputTypeRecordIDs = getOutputTypeRecordIDs(resolverRecord);
261
262
  if (outputTypeRecordIDs != null) {
262
263
  for (const dataID of outputTypeRecordIDs) {
263
264
  this._references.add(dataID);
265
+ this._traverse(linkedField, dataID);
264
266
  }
265
267
  }
266
268
  } else {
267
- const {linkedField} = field;
268
269
  const concreteType = linkedField.concreteType;
269
270
  if (concreteType == null) {
270
271
  // TODO: Handle retaining abstract client edges to client types.