relay-runtime 20.1.1 → 21.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. package/experimental.d.ts +34 -0
  2. package/experimental.js +1 -1
  3. package/experimental.js.flow +11 -11
  4. package/handlers/RelayDefaultHandlerProvider.d.ts +12 -0
  5. package/handlers/connection/ConnectionHandler.d.ts +51 -0
  6. package/handlers/connection/ConnectionHandler.js.flow +5 -5
  7. package/handlers/connection/ConnectionInterface.d.ts +40 -0
  8. package/handlers/connection/ConnectionInterface.js.flow +1 -1
  9. package/handlers/connection/MutationHandlers.d.ts +17 -0
  10. package/index.d.ts +274 -0
  11. package/index.js +1 -1
  12. package/index.js.flow +125 -62
  13. package/lib/experimental.js +3 -3
  14. package/lib/index.js +105 -57
  15. package/lib/multi-actor-environment/ActorIdentifier.js +2 -2
  16. package/lib/multi-actor-environment/MultiActorEnvironment.js +3 -1
  17. package/lib/mutations/commitMutation.js +8 -8
  18. package/lib/mutations/validateMutation.js +4 -4
  19. package/lib/query/GraphQLTag.js +3 -3
  20. package/lib/query/fetchQuery.js +15 -3
  21. package/lib/store/DataChecker.js +38 -4
  22. package/lib/store/NormalizationEngine.js +373 -0
  23. package/lib/store/OperationExecutor.js +172 -113
  24. package/lib/store/RelayConcreteVariables.js +1 -1
  25. package/lib/store/RelayErrorTrie.js +2 -2
  26. package/lib/store/RelayExperimentalGraphResponseTransform.js +8 -8
  27. package/lib/store/RelayModernEnvironment.js +26 -19
  28. package/lib/store/RelayModernRecord.js +18 -8
  29. package/lib/store/RelayModernSelector.js +9 -9
  30. package/lib/store/RelayModernStore.js +152 -43
  31. package/lib/store/RelayPublishQueue.js +1 -1
  32. package/lib/store/RelayReader.js +76 -38
  33. package/lib/store/RelayRecordSource.js +6 -0
  34. package/lib/store/RelayReferenceMarker.js +2 -1
  35. package/lib/store/RelayResponseNormalizer.js +88 -55
  36. package/lib/store/RelayStoreSubscriptions.js +34 -10
  37. package/lib/store/RelayStoreUtils.js +8 -1
  38. package/lib/store/ResolverFragments.js +2 -2
  39. package/lib/store/live-resolvers/LiveResolverCache.js +25 -9
  40. package/lib/store/observeFragmentExperimental.js +17 -1
  41. package/lib/store/observeQueryExperimental.js +2 -2
  42. package/lib/subscription/requestSubscription.js +3 -3
  43. package/lib/util/RelayError.js +3 -0
  44. package/lib/util/RelayFeatureFlags.js +6 -2
  45. package/lib/util/RelayReplaySubject.js +4 -4
  46. package/lib/util/handlePotentialSnapshotErrors.js +2 -2
  47. package/lib/util/stableCopy.js +2 -2
  48. package/llm-docs/api-reference/entrypoint-apis/entrypoint-container.mdx +38 -0
  49. package/llm-docs/api-reference/entrypoint-apis/load-entrypoint.mdx +77 -0
  50. package/llm-docs/api-reference/entrypoint-apis/use-entrypoint-loader.mdx +99 -0
  51. package/llm-docs/api-reference/graphql/graphql-directives.mdx +378 -0
  52. package/llm-docs/api-reference/hooks/_use-lazy-load-query-extra.mdx +16 -0
  53. package/llm-docs/api-reference/hooks/load-query.mdx +84 -0
  54. package/llm-docs/api-reference/hooks/relay-environment-provider.mdx +78 -0
  55. package/llm-docs/api-reference/hooks/use-client-query.mdx +65 -0
  56. package/llm-docs/api-reference/hooks/use-fragment.mdx +69 -0
  57. package/llm-docs/api-reference/hooks/use-lazy-load-query.mdx +62 -0
  58. package/llm-docs/api-reference/hooks/use-mutation.mdx +94 -0
  59. package/llm-docs/api-reference/hooks/use-pagination-fragment.mdx +166 -0
  60. package/llm-docs/api-reference/hooks/use-prefetchable-forward-pagination-fragment.mdx +134 -0
  61. package/llm-docs/api-reference/hooks/use-preloaded-query.mdx +84 -0
  62. package/llm-docs/api-reference/hooks/use-query-loader.mdx +95 -0
  63. package/llm-docs/api-reference/hooks/use-refetchable-fragment.mdx +122 -0
  64. package/llm-docs/api-reference/hooks/use-relay-environment.mdx +37 -0
  65. package/llm-docs/api-reference/hooks/use-subscription.mdx +66 -0
  66. package/llm-docs/api-reference/relay-resolvers/docblock-format.mdx +321 -0
  67. package/llm-docs/api-reference/relay-resolvers/runtime-functions.mdx +94 -0
  68. package/llm-docs/api-reference/relay-runtime/commit-mutation.mdx +65 -0
  69. package/llm-docs/api-reference/relay-runtime/fetch-query.mdx +118 -0
  70. package/llm-docs/api-reference/relay-runtime/field-logger.mdx +170 -0
  71. package/llm-docs/api-reference/relay-runtime/observe-fragment.mdx +92 -0
  72. package/llm-docs/api-reference/relay-runtime/relay-environment.mdx +53 -0
  73. package/llm-docs/api-reference/relay-runtime/request-subscription.mdx +54 -0
  74. package/llm-docs/api-reference/relay-runtime/runtime-configuration.mdx +52 -0
  75. package/llm-docs/api-reference/relay-runtime/store.mdx +734 -0
  76. package/llm-docs/api-reference/relay-runtime/wait-for-fragment-data.mdx +89 -0
  77. package/llm-docs/api-reference/types/CacheConfig.mdx +8 -0
  78. package/llm-docs/api-reference/types/Disposable.mdx +4 -0
  79. package/llm-docs/api-reference/types/GraphQLSubscriptionConfig.mdx +17 -0
  80. package/llm-docs/api-reference/types/MutationConfig.mdx +31 -0
  81. package/llm-docs/api-reference/types/SelectorStoreUpdater.mdx +6 -0
  82. package/llm-docs/api-reference/types/UploadableMap.mdx +3 -0
  83. package/llm-docs/community/learning-resources.mdx +64 -0
  84. package/llm-docs/debugging/declarative-mutation-directives.mdx +34 -0
  85. package/llm-docs/debugging/disallowed-id-types-error.mdx +43 -0
  86. package/llm-docs/debugging/inconsistent-typename-error.mdx +47 -0
  87. package/llm-docs/debugging/relay-devtools.mdx +73 -0
  88. package/llm-docs/debugging/why-null.mdx +116 -0
  89. package/llm-docs/editor-support.mdx +55 -0
  90. package/llm-docs/error-reference/unknown-field.mdx +36 -0
  91. package/llm-docs/getting-started/babel-plugin.mdx +31 -0
  92. package/llm-docs/getting-started/compiler-config.mdx +25 -0
  93. package/llm-docs/getting-started/compiler.mdx +98 -0
  94. package/llm-docs/getting-started/lint-rules.mdx +87 -0
  95. package/llm-docs/getting-started/production.mdx +30 -0
  96. package/llm-docs/getting-started/quick-start.mdx +216 -0
  97. package/llm-docs/glossary/glossary.mdx +1040 -0
  98. package/llm-docs/guided-tour/list-data/advanced-pagination.mdx +157 -0
  99. package/llm-docs/guided-tour/list-data/connections.mdx +81 -0
  100. package/llm-docs/guided-tour/list-data/pagination.mdx +193 -0
  101. package/llm-docs/guided-tour/list-data/rendering-connections.mdx +112 -0
  102. package/llm-docs/guided-tour/list-data/streaming-pagination.mdx +87 -0
  103. package/llm-docs/guided-tour/managing-data-outside-react/retaining-queries.mdx +51 -0
  104. package/llm-docs/guided-tour/refetching/refetching-queries-with-different-data.mdx +337 -0
  105. package/llm-docs/guided-tour/refetching/refreshing-queries.mdx +350 -0
  106. package/llm-docs/guided-tour/rendering/environment.mdx +59 -0
  107. package/llm-docs/guided-tour/rendering/error-states.mdx +295 -0
  108. package/llm-docs/guided-tour/rendering/fragments.mdx +354 -0
  109. package/llm-docs/guided-tour/rendering/loading-states.mdx +245 -0
  110. package/llm-docs/guided-tour/rendering/queries.mdx +261 -0
  111. package/llm-docs/guided-tour/rendering/variables.mdx +233 -0
  112. package/llm-docs/guided-tour/reusing-cached-data/fetch-policies.mdx +56 -0
  113. package/llm-docs/guided-tour/reusing-cached-data/filling-in-missing-data.mdx +102 -0
  114. package/llm-docs/guided-tour/reusing-cached-data/introduction.mdx +22 -0
  115. package/llm-docs/guided-tour/reusing-cached-data/presence-of-data.mdx +93 -0
  116. package/llm-docs/guided-tour/reusing-cached-data/rendering-partially-cached-data.mdx +175 -0
  117. package/llm-docs/guided-tour/reusing-cached-data/staleness-of-data.mdx +116 -0
  118. package/llm-docs/guided-tour/updating-data/client-only-data.mdx +115 -0
  119. package/llm-docs/guided-tour/updating-data/graphql-mutations.mdx +334 -0
  120. package/llm-docs/guided-tour/updating-data/graphql-subscriptions.mdx +279 -0
  121. package/llm-docs/guided-tour/updating-data/imperatively-modifying-linked-fields.mdx +511 -0
  122. package/llm-docs/guided-tour/updating-data/imperatively-modifying-store-data-legacy.mdx +142 -0
  123. package/llm-docs/guided-tour/updating-data/imperatively-modifying-store-data.mdx +275 -0
  124. package/llm-docs/guided-tour/updating-data/introduction.mdx +25 -0
  125. package/llm-docs/guided-tour/updating-data/local-data-updates.mdx +71 -0
  126. package/llm-docs/guided-tour/updating-data/typesafe-updaters-faq.mdx +83 -0
  127. package/llm-docs/guided-tour/updating-data/updating-connections.mdx +592 -0
  128. package/llm-docs/guides/alias-directive.mdx +160 -0
  129. package/llm-docs/guides/catch-directive.mdx +167 -0
  130. package/llm-docs/guides/client-schema-extensions.mdx +208 -0
  131. package/llm-docs/guides/codemods.mdx +79 -0
  132. package/llm-docs/guides/data-driven-dependencies/client-3d.mdx +255 -0
  133. package/llm-docs/guides/data-driven-dependencies/configuration.mdx +127 -0
  134. package/llm-docs/guides/data-driven-dependencies/introduction.mdx +39 -0
  135. package/llm-docs/guides/data-driven-dependencies/server-3d.mdx +664 -0
  136. package/llm-docs/guides/document-comparison.mdx +106 -0
  137. package/llm-docs/guides/graphql-server-specification.mdx +453 -0
  138. package/llm-docs/guides/network-layer.mdx +69 -0
  139. package/llm-docs/guides/persisted-queries.mdx +328 -0
  140. package/llm-docs/guides/relay-resolvers/context.mdx +99 -0
  141. package/llm-docs/guides/relay-resolvers/defining-fields.mdx +151 -0
  142. package/llm-docs/guides/relay-resolvers/defining-types.mdx +164 -0
  143. package/llm-docs/guides/relay-resolvers/deprecated.mdx +27 -0
  144. package/llm-docs/guides/relay-resolvers/derived-fields.mdx +127 -0
  145. package/llm-docs/guides/relay-resolvers/descriptions.mdx +44 -0
  146. package/llm-docs/guides/relay-resolvers/enabling.mdx +41 -0
  147. package/llm-docs/guides/relay-resolvers/errors.mdx +64 -0
  148. package/llm-docs/guides/relay-resolvers/field-arguments.mdx +63 -0
  149. package/llm-docs/guides/relay-resolvers/introduction.mdx +62 -0
  150. package/llm-docs/guides/relay-resolvers/limitations.mdx +30 -0
  151. package/llm-docs/guides/relay-resolvers/live-fields.mdx +164 -0
  152. package/llm-docs/guides/relay-resolvers/return-types.mdx +161 -0
  153. package/llm-docs/guides/relay-resolvers/suspense.mdx +41 -0
  154. package/llm-docs/guides/required-directive.mdx +240 -0
  155. package/llm-docs/guides/semantic-nullability.mdx +93 -0
  156. package/llm-docs/guides/testing-relay-components.mdx +642 -0
  157. package/llm-docs/guides/testing-relay-with-preloaded-queries.mdx +160 -0
  158. package/llm-docs/guides/throw-on-field-error-directive.mdx +58 -0
  159. package/llm-docs/guides/type-emission.mdx +414 -0
  160. package/llm-docs/home.mdx +32 -0
  161. package/llm-docs/principles-and-architecture/architecture-overview.mdx +24 -0
  162. package/llm-docs/principles-and-architecture/compiler-architecture.mdx +106 -0
  163. package/llm-docs/principles-and-architecture/runtime-architecture.mdx +249 -0
  164. package/llm-docs/principles-and-architecture/thinking-in-graphql.mdx +309 -0
  165. package/llm-docs/principles-and-architecture/thinking-in-relay.mdx +104 -0
  166. package/llm-docs/principles-and-architecture/videos.mdx +50 -0
  167. package/llm-docs/tutorial/arrays-lists.mdx +126 -0
  168. package/llm-docs/tutorial/fragments-1.mdx +487 -0
  169. package/llm-docs/tutorial/graphql.mdx +172 -0
  170. package/llm-docs/tutorial/interfaces-polymorphism.mdx +161 -0
  171. package/llm-docs/tutorial/intro.mdx +58 -0
  172. package/llm-docs/tutorial/mutations-updates.mdx +624 -0
  173. package/llm-docs/tutorial/organizing-mutations-queries-and-subscriptions.mdx +13 -0
  174. package/llm-docs/tutorial/queries-1.mdx +267 -0
  175. package/llm-docs/tutorial/queries-2.mdx +389 -0
  176. package/llm-docs/tutorial/refetchable-fragments.mdx +352 -0
  177. package/multi-actor-environment/ActorIdentifier.d.ts +17 -0
  178. package/multi-actor-environment/ActorIdentifier.js.flow +2 -2
  179. package/multi-actor-environment/ActorSpecificEnvironment.js.flow +15 -15
  180. package/multi-actor-environment/ActorUtils.js.flow +1 -1
  181. package/multi-actor-environment/MultiActorEnvironment.d.ts +123 -0
  182. package/multi-actor-environment/MultiActorEnvironment.js.flow +32 -24
  183. package/multi-actor-environment/MultiActorEnvironmentTypes.d.ts +225 -0
  184. package/multi-actor-environment/MultiActorEnvironmentTypes.js.flow +6 -6
  185. package/multi-actor-environment/index.d.ts +14 -0
  186. package/multi-actor-environment.d.ts +8 -0
  187. package/mutations/RelayDeclarativeMutationConfig.d.ts +70 -0
  188. package/mutations/RelayDeclarativeMutationConfig.js.flow +9 -9
  189. package/mutations/RelayRecordProxy.js.flow +8 -11
  190. package/mutations/RelayRecordSourceMutator.js.flow +4 -4
  191. package/mutations/RelayRecordSourceProxy.js.flow +4 -4
  192. package/mutations/RelayRecordSourceSelectorProxy.js.flow +6 -6
  193. package/mutations/applyOptimisticMutation.d.ts +25 -0
  194. package/mutations/applyOptimisticMutation.js.flow +2 -2
  195. package/mutations/commitLocalUpdate.d.ts +10 -0
  196. package/mutations/commitMutation.d.ts +48 -0
  197. package/mutations/commitMutation.js.flow +21 -17
  198. package/mutations/createUpdatableProxy.js.flow +19 -19
  199. package/mutations/readUpdatableFragment.js.flow +3 -3
  200. package/mutations/readUpdatableQuery.js.flow +3 -3
  201. package/mutations/validateMutation.js.flow +7 -7
  202. package/network/RelayNetwork.d.ts +12 -0
  203. package/network/RelayNetworkTypes.d.ts +145 -0
  204. package/network/RelayNetworkTypes.js.flow +18 -18
  205. package/network/RelayObservable.d.ts +197 -0
  206. package/network/RelayObservable.js.flow +32 -30
  207. package/network/RelayQueryResponseCache.d.ts +16 -0
  208. package/network/RelayQueryResponseCache.js.flow +3 -3
  209. package/network/wrapNetworkWithLogObserver.js.flow +1 -1
  210. package/package.json +2 -1
  211. package/query/GraphQLTag.d.ts +45 -0
  212. package/query/GraphQLTag.js.flow +22 -10
  213. package/query/fetchQuery.d.ts +21 -0
  214. package/query/fetchQuery.js.flow +23 -10
  215. package/query/fetchQueryInternal.d.ts +26 -0
  216. package/query/fetchQueryInternal.js.flow +4 -4
  217. package/query/fetchQuery_DEPRECATED.d.ts +17 -0
  218. package/query/fetchQuery_DEPRECATED.js.flow +1 -1
  219. package/store/ClientID.d.ts +14 -0
  220. package/store/DataChecker.js.flow +51 -15
  221. package/store/NormalizationEngine.js.flow +782 -0
  222. package/store/OperationExecutor.d.ts +51 -0
  223. package/store/OperationExecutor.js.flow +204 -98
  224. package/store/RelayConcreteVariables.js.flow +5 -5
  225. package/store/RelayErrorTrie.js.flow +12 -12
  226. package/store/RelayExperimentalGraphResponseHandler.js.flow +3 -3
  227. package/store/RelayExperimentalGraphResponseTransform.js.flow +10 -10
  228. package/store/RelayModernEnvironment.d.ts +97 -0
  229. package/store/RelayModernEnvironment.js.flow +58 -43
  230. package/store/RelayModernFragmentSpecResolver.js.flow +1 -1
  231. package/store/RelayModernOperationDescriptor.d.ts +28 -0
  232. package/store/RelayModernOperationDescriptor.js.flow +1 -1
  233. package/store/RelayModernRecord.d.ts +92 -0
  234. package/store/RelayModernRecord.js.flow +44 -20
  235. package/store/RelayModernSelector.d.ts +123 -0
  236. package/store/RelayModernSelector.js.flow +21 -21
  237. package/store/RelayModernStore.d.ts +57 -0
  238. package/store/RelayModernStore.js.flow +219 -58
  239. package/store/RelayOperationTracker.d.ts +29 -0
  240. package/store/RelayOperationTracker.js.flow +2 -2
  241. package/store/RelayOptimisticRecordSource.js.flow +2 -2
  242. package/store/RelayPublishQueue.js.flow +29 -20
  243. package/store/RelayReader.js.flow +129 -57
  244. package/store/RelayRecordSource.d.ts +26 -0
  245. package/store/RelayRecordSource.js.flow +10 -0
  246. package/store/RelayRecordState.d.ts +28 -0
  247. package/store/RelayRecordState.js.flow +1 -1
  248. package/store/RelayReferenceMarker.js.flow +5 -4
  249. package/store/RelayResponseNormalizer.d.ts +28 -0
  250. package/store/RelayResponseNormalizer.js.flow +130 -62
  251. package/store/RelayStoreSubscriptions.js.flow +52 -8
  252. package/store/RelayStoreTypes.d.ts +1327 -0
  253. package/store/RelayStoreTypes.js.flow +371 -278
  254. package/store/RelayStoreUtils.d.ts +86 -0
  255. package/store/RelayStoreUtils.js.flow +16 -8
  256. package/store/ResolverCache.js.flow +2 -2
  257. package/store/ResolverFragments.d.ts +43 -0
  258. package/store/ResolverFragments.js.flow +22 -14
  259. package/store/StoreInspector.js.flow +7 -8
  260. package/store/ViewerPattern.d.ts +11 -0
  261. package/store/cloneRelayHandleSourceField.js.flow +1 -1
  262. package/store/cloneRelayScalarHandleSourceField.js.flow +1 -1
  263. package/store/createFragmentSpecResolver.d.ts +16 -0
  264. package/store/createRelayContext.js.flow +1 -1
  265. package/store/createRelayLoggingContext.js.flow +4 -4
  266. package/store/defaultGetDataID.js.flow +2 -2
  267. package/store/isRelayModernEnvironment.d.ts +8 -0
  268. package/store/isRelayModernEnvironment.js.flow +4 -2
  269. package/store/live-resolvers/LiveResolverCache.js.flow +55 -20
  270. package/store/live-resolvers/LiveResolverSuspenseSentinel.js.flow +3 -3
  271. package/store/live-resolvers/getOutputTypeRecordIDs.js.flow +1 -1
  272. package/store/live-resolvers/isLiveStateValue.js.flow +2 -2
  273. package/store/live-resolvers/resolverDataInjector.d.ts +27 -0
  274. package/store/live-resolvers/resolverDataInjector.js.flow +8 -5
  275. package/store/observeFragmentExperimental.d.ts +46 -0
  276. package/store/observeFragmentExperimental.js.flow +50 -21
  277. package/store/observeQueryExperimental.d.ts +30 -0
  278. package/store/observeQueryExperimental.js.flow +5 -5
  279. package/store/readInlineData.d.ts +19 -0
  280. package/store/readInlineData.js.flow +5 -5
  281. package/store/waitForFragmentExperimental.d.ts +49 -0
  282. package/store/waitForFragmentExperimental.js.flow +3 -3
  283. package/subscription/requestSubscription.d.ts +27 -0
  284. package/subscription/requestSubscription.js.flow +10 -10
  285. package/util/JSResourceTypes.flow.js.flow +4 -4
  286. package/util/NormalizationNode.d.ts +235 -0
  287. package/util/NormalizationNode.js.flow +127 -123
  288. package/util/ReaderNode.d.ts +264 -0
  289. package/util/ReaderNode.js.flow +156 -151
  290. package/util/RelayConcreteNode.d.ts +120 -0
  291. package/util/RelayConcreteNode.js.flow +32 -32
  292. package/util/RelayError.d.ts +13 -0
  293. package/util/RelayError.js.flow +4 -1
  294. package/util/RelayFeatureFlags.d.ts +40 -0
  295. package/util/RelayFeatureFlags.js.flow +21 -1
  296. package/util/RelayProfiler.d.ts +121 -0
  297. package/util/RelayProfiler.js.flow +1 -1
  298. package/util/RelayReplaySubject.d.ts +25 -0
  299. package/util/RelayReplaySubject.js.flow +3 -3
  300. package/util/RelayRuntimeTypes.d.ts +59 -0
  301. package/util/RelayRuntimeTypes.js.flow +36 -33
  302. package/util/createPayloadFor3DField.d.ts +17 -0
  303. package/util/createPayloadFor3DField.js.flow +9 -5
  304. package/util/deepFreeze.d.ts +8 -0
  305. package/util/deepFreeze.js.flow +2 -2
  306. package/util/getFragmentIdentifier.d.ts +10 -0
  307. package/util/getFragmentIdentifier.js.flow +1 -1
  308. package/util/getPaginationMetadata.d.ts +20 -0
  309. package/util/getPaginationMetadata.js.flow +1 -1
  310. package/util/getPaginationVariables.d.ts +20 -0
  311. package/util/getPaginationVariables.js.flow +1 -1
  312. package/util/getPendingOperationsForFragment.d.ts +18 -0
  313. package/util/getPendingOperationsForFragment.js.flow +2 -2
  314. package/util/getRefetchMetadata.d.ts +19 -0
  315. package/util/getRefetchMetadata.js.flow +6 -5
  316. package/util/getRelayHandleKey.d.ts +8 -0
  317. package/util/getRequestIdentifier.d.ts +17 -0
  318. package/util/getValueAtPath.d.ts +8 -0
  319. package/util/getValueAtPath.js.flow +3 -3
  320. package/util/handlePotentialSnapshotErrors.d.ts +14 -0
  321. package/util/handlePotentialSnapshotErrors.js.flow +5 -5
  322. package/util/isEmptyObject.js.flow +1 -1
  323. package/util/isPromise.d.ts +8 -0
  324. package/util/isPromise.js.flow +2 -2
  325. package/util/isScalarAndEqual.d.ts +8 -0
  326. package/util/isScalarAndEqual.js.flow +1 -1
  327. package/util/recycleNodesInto.d.ts +8 -0
  328. package/util/recycleNodesInto.js.flow +2 -2
  329. package/util/registerEnvironmentWithDevTools.js.flow +1 -1
  330. package/util/shallowFreeze.js.flow +1 -1
  331. package/util/stableCopy.d.ts +8 -0
  332. package/util/stableCopy.js.flow +5 -5
  333. package/util/withProvidedVariables.d.ts +19 -0
  334. package/util/withProvidedVariables.js.flow +14 -10
@@ -0,0 +1,782 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict-local
8
+ * @format
9
+ * @oncall relay
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ import type {GraphQLResponseWithData} from '../network/RelayNetworkTypes';
15
+ import type {
16
+ NormalizationLinkedField,
17
+ NormalizationOperation,
18
+ NormalizationRootNode,
19
+ } from '../util/NormalizationNode';
20
+ import type {Variables} from '../util/RelayRuntimeTypes';
21
+ import type {NormalizationOptions} from './RelayResponseNormalizer';
22
+ import type {
23
+ DeferPlaceholder,
24
+ FollowupPayload,
25
+ HandleFieldPayload,
26
+ IncrementalDataPlaceholder,
27
+ ModuleImportPayload,
28
+ MutableRecordSource,
29
+ NormalizationSelector,
30
+ NormalizeResponseFunction,
31
+ OperationLoader,
32
+ Record,
33
+ RecordSourceProxy,
34
+ RelayResponsePayload,
35
+ StreamPlaceholder,
36
+ } from './RelayStoreTypes';
37
+
38
+ const {stableCopy} = require('../util/stableCopy');
39
+ const {generateClientID} = require('./ClientID');
40
+ const defaultGetDataID = require('./defaultGetDataID');
41
+ const RelayModernRecord = require('./RelayModernRecord');
42
+ const {createNormalizationSelector} = require('./RelayModernSelector');
43
+ const {ROOT_ID, ROOT_TYPE, getStorageKey} = require('./RelayStoreUtils');
44
+
45
+ function err(message: string): Error {
46
+ const e = new Error(message);
47
+ void e.stack;
48
+ return e;
49
+ }
50
+
51
+ function stableStringify(value: unknown): string {
52
+ return JSON.stringify(stableCopy(value)) ?? '';
53
+ }
54
+
55
+ export type NormalizationResult = Readonly<{
56
+ payloads: ReadonlyArray<RelayResponsePayload>,
57
+ // Each pending module resolves to its own NormalizationResult so the caller
58
+ // can recursively drain nested incremental data (extra payloads + further
59
+ // pending modules) produced by an async @module load. Mirrors the recursion
60
+ // that OperationExecutor performs synchronously via `_processPayloadFollowups`.
61
+ pendingModules: ReadonlyArray<Promise<NormalizationResult>>,
62
+ }>;
63
+
64
+ type Config = Readonly<{
65
+ getDataID?: (
66
+ fieldValue: {readonly [string]: unknown},
67
+ typeName: string,
68
+ ) => unknown,
69
+ normalizeResponse: NormalizeResponseFunction,
70
+ operation: NormalizationOperation,
71
+ operationLoader?: ?OperationLoader,
72
+ treatMissingFieldsAsNull?: boolean,
73
+ variables: Variables,
74
+ }>;
75
+
76
+ type ParentEntry = {
77
+ fieldPayloads: Array<HandleFieldPayload>,
78
+ record: Record,
79
+ };
80
+
81
+ /**
82
+ * Per-request normalization engine. Normalizes raw server responses and
83
+ * returns pre-normalized RelayResponsePayload objects (with isPreNormalized:
84
+ * true) that OperationExecutor can commit directly to the store, bypassing
85
+ * the normalization pass it would otherwise perform.
86
+ *
87
+ * Handles initial responses, @defer chunks, @stream items, and @module
88
+ * followups. Tracks per-request state for incremental delivery including
89
+ * placeholder registration, response buffering, and parent record caching.
90
+ *
91
+ * Returns results synchronously via arrays. Async @module loads are returned
92
+ * as Promises — the caller bridges them to the sink.
93
+ */
94
+ class NormalizationEngine {
95
+ _normalizeResponse: NormalizeResponseFunction;
96
+ _operationLoader: ?OperationLoader;
97
+ _options: NormalizationOptions;
98
+ _rootSelector: NormalizationSelector;
99
+ _useExecTimeResolvers: boolean;
100
+
101
+ // Per-request incremental delivery state
102
+ _placeholders: Map<string, IncrementalDataPlaceholder>;
103
+ _bufferedResponses: Map<string, Array<GraphQLResponseWithData>>;
104
+ _parentRecords: Map<string, ParentEntry>;
105
+ _serverComplete: boolean;
106
+
107
+ constructor(config: Config) {
108
+ this._normalizeResponse = config.normalizeResponse;
109
+ this._operationLoader = config.operationLoader ?? null;
110
+ this._rootSelector = {
111
+ dataID: ROOT_ID,
112
+ node: config.operation,
113
+ variables: config.variables,
114
+ };
115
+ this._options = {
116
+ deferDeduplicatedFields: false,
117
+ getDataID: config.getDataID ?? defaultGetDataID,
118
+ log: null,
119
+ path: [],
120
+ treatMissingFieldsAsNull: config.treatMissingFieldsAsNull ?? false,
121
+ };
122
+ this._useExecTimeResolvers =
123
+ config.operation.use_exec_time_resolvers ??
124
+ config.operation.exec_time_resolvers_enabled_provider?.get() === true ??
125
+ false;
126
+ this._placeholders = new Map();
127
+ this._bufferedResponses = new Map();
128
+ this._parentRecords = new Map();
129
+ this._serverComplete = false;
130
+ }
131
+
132
+ /**
133
+ * Process an initial (non-incremental) server response. Normalizes the
134
+ * response, registers any incremental placeholders, processes @module
135
+ * followups, and flushes buffered responses.
136
+ *
137
+ * Returns an array of payloads (the primary result plus any flushed
138
+ * buffered responses) and an array of Promises for async @module loads.
139
+ */
140
+ processResponse(response: GraphQLResponseWithData): NormalizationResult {
141
+ const payload = this._normalizeResponse(
142
+ response,
143
+ this._rootSelector,
144
+ ROOT_TYPE,
145
+ this._options,
146
+ this._useExecTimeResolvers,
147
+ );
148
+
149
+ const extraPayloads: Array<RelayResponsePayload> = [];
150
+ const pendingModules: Array<Promise<NormalizationResult>> = [];
151
+
152
+ // Register incremental placeholders and flush any buffered responses
153
+ if (
154
+ payload.incrementalPlaceholders != null &&
155
+ payload.incrementalPlaceholders.length > 0
156
+ ) {
157
+ this._registerPlaceholders(
158
+ payload.incrementalPlaceholders,
159
+ payload.source,
160
+ payload.fieldPayloads,
161
+ extraPayloads,
162
+ pendingModules,
163
+ );
164
+ }
165
+
166
+ // Process module followups
167
+ if (
168
+ payload.followupPayloads != null &&
169
+ payload.followupPayloads.length > 0
170
+ ) {
171
+ this._processFollowups(
172
+ payload.followupPayloads,
173
+ extraPayloads,
174
+ pendingModules,
175
+ );
176
+ }
177
+
178
+ const primaryPayload: RelayResponsePayload = {
179
+ ...payload,
180
+ followupPayloads: null,
181
+ incrementalPlaceholders: null,
182
+ isFinal: this._computeIsFinal(payload.isFinal),
183
+ isPreNormalized: true,
184
+ };
185
+
186
+ return {
187
+ payloads: [primaryPayload, ...extraPayloads],
188
+ pendingModules,
189
+ };
190
+ }
191
+
192
+ /**
193
+ * Process an incremental response (@defer chunk or @stream item).
194
+ * Matches the response to a registered placeholder by label + path.
195
+ *
196
+ * Returns null if the response was buffered (no placeholder yet).
197
+ * Otherwise returns payloads array and pending module Promises.
198
+ */
199
+ processIncrementalResponse(
200
+ response: GraphQLResponseWithData,
201
+ ): ?NormalizationResult {
202
+ const label: ?string = response.label;
203
+ const path: ?ReadonlyArray<string | number> = response.path;
204
+
205
+ if (label == null || path == null) {
206
+ throw err(
207
+ 'NormalizationEngine: Expected incremental response to have ' +
208
+ '`label` and `path` properties.',
209
+ );
210
+ }
211
+
212
+ const isDefer = label.indexOf('$defer$') !== -1;
213
+ const pathKey = isDefer
214
+ ? path.map(String).join('.')
215
+ : path.slice(0, -2).map(String).join('.');
216
+ const key = makeKey(label, pathKey);
217
+
218
+ const placeholder = this._placeholders.get(key);
219
+
220
+ if (placeholder == null) {
221
+ // Buffer: response arrived before placeholder was registered
222
+ let buffer = this._bufferedResponses.get(key);
223
+ if (buffer == null) {
224
+ buffer = [];
225
+ this._bufferedResponses.set(key, buffer);
226
+ }
227
+ buffer.push(response);
228
+ return null;
229
+ }
230
+
231
+ if (placeholder.kind === 'defer') {
232
+ return this._processDefer(response, path, placeholder);
233
+ } else {
234
+ return this._processStream(response, path, placeholder);
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Mark the server stream as complete for isFinal computation.
240
+ */
241
+ setServerComplete(): void {
242
+ this._serverComplete = true;
243
+ }
244
+
245
+ /**
246
+ * Whether the server is complete AND all incremental data has been received.
247
+ */
248
+ isFinal(): boolean {
249
+ return (
250
+ this._serverComplete &&
251
+ this._bufferedResponses.size === 0 &&
252
+ this._placeholders.size === 0
253
+ );
254
+ }
255
+
256
+ // ---------------------------------------------------------------------------
257
+ // Private: @defer handling
258
+ // ---------------------------------------------------------------------------
259
+
260
+ _processDefer(
261
+ response: GraphQLResponseWithData,
262
+ _path: ReadonlyArray<unknown>,
263
+ placeholder: DeferPlaceholder,
264
+ ): NormalizationResult {
265
+ const payload = this._normalizeResponse(
266
+ response,
267
+ placeholder.selector,
268
+ placeholder.typeName,
269
+ {
270
+ ...this._options,
271
+ deferDeduplicatedFields: true,
272
+ path: placeholder.path,
273
+ },
274
+ this._useExecTimeResolvers,
275
+ );
276
+
277
+ const extraPayloads: Array<RelayResponsePayload> = [];
278
+ const pendingModules: Array<Promise<NormalizationResult>> = [];
279
+
280
+ // Register nested placeholders (recursive @defer)
281
+ if (
282
+ payload.incrementalPlaceholders != null &&
283
+ payload.incrementalPlaceholders.length > 0
284
+ ) {
285
+ this._registerPlaceholders(
286
+ payload.incrementalPlaceholders,
287
+ payload.source,
288
+ payload.fieldPayloads,
289
+ extraPayloads,
290
+ pendingModules,
291
+ );
292
+ }
293
+
294
+ // Process nested followups (recursive @module)
295
+ if (
296
+ payload.followupPayloads != null &&
297
+ payload.followupPayloads.length > 0
298
+ ) {
299
+ this._processFollowups(
300
+ payload.followupPayloads,
301
+ extraPayloads,
302
+ pendingModules,
303
+ );
304
+ }
305
+
306
+ // Replay handle field payloads from parent
307
+ // TODO(skyyao): emit parent fieldPayloads as a separate payload (with empty
308
+ // source) like OperationExecutor._processDeferResponse so handles run
309
+ // against the just-committed updated parent record.
310
+ const parentID = placeholder.selector.dataID;
311
+ const parentEntry = this._parentRecords.get(parentID);
312
+ let fieldPayloads = payload.fieldPayloads;
313
+ if (parentEntry != null && parentEntry.fieldPayloads.length > 0) {
314
+ fieldPayloads = (fieldPayloads ?? []).concat(parentEntry.fieldPayloads);
315
+ }
316
+
317
+ const primaryPayload: RelayResponsePayload = {
318
+ ...payload,
319
+ fieldPayloads,
320
+ followupPayloads: null,
321
+ incrementalPlaceholders: null,
322
+ isFinal: this._computeIsFinal(payload.isFinal),
323
+ isPreNormalized: true,
324
+ };
325
+
326
+ return {
327
+ payloads: [primaryPayload, ...extraPayloads],
328
+ pendingModules,
329
+ };
330
+ }
331
+
332
+ // ---------------------------------------------------------------------------
333
+ // Private: @stream handling
334
+ // ---------------------------------------------------------------------------
335
+
336
+ _processStream(
337
+ response: GraphQLResponseWithData,
338
+ path: ReadonlyArray<unknown>,
339
+ placeholder: StreamPlaceholder,
340
+ ): NormalizationResult {
341
+ const {node, parentID, variables} = placeholder;
342
+
343
+ // Find the LinkedField where @stream was applied
344
+ const field = node.selections[0];
345
+ if (
346
+ field == null ||
347
+ field.kind !== 'LinkedField' ||
348
+ field.plural !== true
349
+ ) {
350
+ throw err(
351
+ 'NormalizationEngine: Expected @stream to be used on a plural field.',
352
+ );
353
+ }
354
+
355
+ const {
356
+ fieldPayloads,
357
+ itemID,
358
+ itemIndex,
359
+ prevIDs,
360
+ relayPayload,
361
+ storageKey,
362
+ } = this._normalizeStreamItem(
363
+ response,
364
+ parentID,
365
+ field,
366
+ variables,
367
+ path,
368
+ placeholder.path,
369
+ );
370
+
371
+ // Build store updater for concurrent modification detection
372
+ const storeUpdater = (store: RecordSourceProxy) => {
373
+ const currentParentRecord = store.get(parentID);
374
+ if (currentParentRecord == null) {
375
+ return;
376
+ }
377
+ const currentItems = currentParentRecord.getLinkedRecords(storageKey);
378
+ if (currentItems == null) {
379
+ return;
380
+ }
381
+ if (
382
+ currentItems.length !== prevIDs.length ||
383
+ currentItems.some(
384
+ (item, i) => prevIDs[i] !== (item && item.getDataID()),
385
+ )
386
+ ) {
387
+ return; // Concurrent modification -- drop stale stream data
388
+ }
389
+ const nextItems = [...currentItems];
390
+ nextItems[itemIndex] = store.get(itemID);
391
+ currentParentRecord.setLinkedRecords(nextItems, storageKey);
392
+ };
393
+
394
+ // Replay handle field payloads from parent
395
+ // TODO(skyyao): emit parent fieldPayloads as a separate payload (with empty
396
+ // source) like OperationExecutor._processStreamResponse so handles run
397
+ // against the just-committed updated parent record.
398
+ let mergedFieldPayloads = relayPayload.fieldPayloads;
399
+ if (fieldPayloads.length > 0) {
400
+ mergedFieldPayloads = (mergedFieldPayloads ?? []).concat(fieldPayloads);
401
+ }
402
+
403
+ return {
404
+ payloads: [
405
+ {
406
+ ...relayPayload,
407
+ fieldPayloads: mergedFieldPayloads,
408
+ followupPayloads: null,
409
+ incrementalPlaceholders: null,
410
+ isFinal: this._computeIsFinal(relayPayload.isFinal),
411
+ isPreNormalized: true,
412
+ storeUpdater,
413
+ },
414
+ ],
415
+ pendingModules: [],
416
+ };
417
+ }
418
+
419
+ _normalizeStreamItem(
420
+ response: GraphQLResponseWithData,
421
+ parentID: string,
422
+ field: NormalizationLinkedField,
423
+ variables: Variables,
424
+ path: ReadonlyArray<unknown>,
425
+ normalizationPath: ReadonlyArray<string>,
426
+ ): {
427
+ fieldPayloads: Array<HandleFieldPayload>,
428
+ itemID: string,
429
+ itemIndex: number,
430
+ prevIDs: Array<?string>,
431
+ relayPayload: RelayResponsePayload,
432
+ storageKey: string,
433
+ } {
434
+ const {data} = response;
435
+ if (typeof data !== 'object') {
436
+ throw err(
437
+ 'NormalizationEngine: Expected the GraphQL @stream payload `data` ' +
438
+ 'value to be an object.',
439
+ );
440
+ }
441
+ const responseKey = field.alias ?? field.name;
442
+ const storageKey = getStorageKey(field, variables);
443
+
444
+ const parentEntry = this._parentRecords.get(parentID);
445
+ if (parentEntry == null) {
446
+ throw err(
447
+ 'NormalizationEngine: Expected the parent record `' +
448
+ parentID +
449
+ '` for @stream data to exist.',
450
+ );
451
+ }
452
+ const {fieldPayloads, record: parentRecord} = parentEntry;
453
+
454
+ const prevIDs = RelayModernRecord.getLinkedRecordIDs(
455
+ parentRecord,
456
+ storageKey,
457
+ );
458
+ if (prevIDs == null) {
459
+ throw err(
460
+ 'NormalizationEngine: Expected record `' +
461
+ parentID +
462
+ '` to have fetched field `' +
463
+ field.name +
464
+ '` with @stream.',
465
+ );
466
+ }
467
+
468
+ const finalPathEntry = path[path.length - 1];
469
+ const itemIndex = parseInt(finalPathEntry, 10);
470
+ if (itemIndex !== finalPathEntry || itemIndex < 0) {
471
+ throw err(
472
+ 'NormalizationEngine: Expected path for @stream to end in a ' +
473
+ 'positive integer index, got `' +
474
+ String(finalPathEntry) +
475
+ '`',
476
+ );
477
+ }
478
+
479
+ const typeName = field.concreteType ?? (data as $FlowFixMe).__typename;
480
+ if (typeof typeName !== 'string') {
481
+ throw err(
482
+ 'NormalizationEngine: Expected @stream field `' +
483
+ field.name +
484
+ '` to have a __typename.',
485
+ );
486
+ }
487
+
488
+ const getDataID = this._options.getDataID;
489
+ const itemID =
490
+ (typeof getDataID === 'function'
491
+ ? getDataID(data as $FlowFixMe, typeName)
492
+ : null) ??
493
+ prevIDs?.[itemIndex] ??
494
+ generateClientID(parentID, storageKey, itemIndex);
495
+ if (typeof itemID !== 'string') {
496
+ throw err(
497
+ 'NormalizationEngine: Expected id of elements of field `' +
498
+ storageKey +
499
+ '` to be strings.',
500
+ );
501
+ }
502
+
503
+ const selector = createNormalizationSelector(field, itemID, variables);
504
+
505
+ const nextParentRecord = RelayModernRecord.clone(parentRecord);
506
+ const nextIDs = [...prevIDs];
507
+ nextIDs[itemIndex] = itemID;
508
+ RelayModernRecord.setLinkedRecordIDs(nextParentRecord, storageKey, nextIDs);
509
+ this._parentRecords.set(parentID, {
510
+ fieldPayloads,
511
+ record: nextParentRecord,
512
+ });
513
+
514
+ const relayPayload = this._normalizeResponse(
515
+ response,
516
+ selector,
517
+ typeName,
518
+ {
519
+ ...this._options,
520
+ path: [...normalizationPath, responseKey, String(itemIndex)],
521
+ },
522
+ this._useExecTimeResolvers,
523
+ );
524
+
525
+ return {
526
+ fieldPayloads,
527
+ itemID,
528
+ itemIndex,
529
+ prevIDs,
530
+ relayPayload,
531
+ storageKey,
532
+ };
533
+ }
534
+
535
+ // ---------------------------------------------------------------------------
536
+ // Private: placeholder registration
537
+ // ---------------------------------------------------------------------------
538
+
539
+ _registerPlaceholders(
540
+ placeholders: ReadonlyArray<IncrementalDataPlaceholder>,
541
+ source: MutableRecordSource,
542
+ fieldPayloads: ?ReadonlyArray<HandleFieldPayload>,
543
+ outPayloads: Array<RelayResponsePayload>,
544
+ outPendingModules: Array<Promise<NormalizationResult>>,
545
+ ): void {
546
+ for (let i = 0; i < placeholders.length; i++) {
547
+ const placeholder = placeholders[i];
548
+ const {label, path} = placeholder;
549
+ const pathKey = path.map(String).join('.');
550
+ const key = makeKey(label, pathKey);
551
+ this._placeholders.set(key, placeholder);
552
+
553
+ // Cache parent record for @stream concurrent modification detection
554
+ // and for @defer handle field replay
555
+ let parentID: string;
556
+ if (placeholder.kind === 'stream') {
557
+ parentID = placeholder.parentID;
558
+ } else {
559
+ parentID = placeholder.selector.dataID;
560
+ }
561
+
562
+ const parentRecord = source.get(parentID);
563
+ if (parentRecord == null) {
564
+ throw err(
565
+ 'NormalizationEngine: Expected record `' + parentID + '` to exist.',
566
+ );
567
+ }
568
+
569
+ const parentPayloads = (fieldPayloads ?? []).filter(
570
+ (fieldPayload: HandleFieldPayload) => {
571
+ const fieldID = generateClientID(
572
+ fieldPayload.dataID,
573
+ fieldPayload.fieldKey,
574
+ );
575
+ return fieldPayload.dataID === parentID || fieldID === parentID;
576
+ },
577
+ );
578
+
579
+ const previousEntry = this._parentRecords.get(parentID);
580
+ if (previousEntry != null) {
581
+ const nextRecord = RelayModernRecord.update(
582
+ previousEntry.record,
583
+ parentRecord,
584
+ );
585
+ const handlePayloads = new Map<string, HandleFieldPayload>();
586
+ // TODO(skyyao): use cheaper structural key (dataID + fieldKey + handleKey)
587
+ // to skip serializing args/handleArgs; apply the same optimization to
588
+ // OperationExecutor.js:1317.
589
+ for (let j = 0; j < previousEntry.fieldPayloads.length; j++) {
590
+ const p = previousEntry.fieldPayloads[j];
591
+ handlePayloads.set(stableStringify(p), p);
592
+ }
593
+ for (let j = 0; j < parentPayloads.length; j++) {
594
+ const p = parentPayloads[j];
595
+ handlePayloads.set(stableStringify(p), p);
596
+ }
597
+ this._parentRecords.set(parentID, {
598
+ fieldPayloads: Array.from(handlePayloads.values()),
599
+ record: nextRecord,
600
+ });
601
+ } else {
602
+ this._parentRecords.set(parentID, {
603
+ fieldPayloads: parentPayloads,
604
+ record: parentRecord,
605
+ });
606
+ }
607
+
608
+ // Flush any buffered responses that arrived before this placeholder
609
+ const buffered = this._bufferedResponses.get(key);
610
+ if (buffered != null) {
611
+ this._bufferedResponses.delete(key);
612
+ for (let j = 0; j < buffered.length; j++) {
613
+ const resp = buffered[j];
614
+ let result: ?NormalizationResult;
615
+ if (placeholder.kind === 'defer') {
616
+ result = this._processDefer(resp, resp.path ?? [], placeholder);
617
+ } else {
618
+ result = this._processStream(resp, resp.path ?? [], placeholder);
619
+ }
620
+ if (result != null) {
621
+ outPayloads.push(...result.payloads);
622
+ outPendingModules.push(...result.pendingModules);
623
+ }
624
+ }
625
+ }
626
+ }
627
+ }
628
+
629
+ // ---------------------------------------------------------------------------
630
+ // Private: @module followup handling
631
+ // ---------------------------------------------------------------------------
632
+
633
+ _processFollowups(
634
+ followups: ReadonlyArray<FollowupPayload>,
635
+ outPayloads: Array<RelayResponsePayload>,
636
+ outPendingModules: Array<Promise<NormalizationResult>>,
637
+ ): void {
638
+ for (let i = 0; i < followups.length; i++) {
639
+ const followup = followups[i];
640
+ if (followup.kind === 'ModuleImportPayload') {
641
+ this._processModuleImport(followup, outPayloads, outPendingModules);
642
+ }
643
+ }
644
+ }
645
+
646
+ _processModuleImport(
647
+ followup: ModuleImportPayload,
648
+ outPayloads: Array<RelayResponsePayload>,
649
+ outPendingModules: Array<Promise<NormalizationResult>>,
650
+ ): void {
651
+ const operationLoader = this._operationLoader;
652
+ if (operationLoader == null) {
653
+ return;
654
+ }
655
+
656
+ // Try sync first
657
+ const node: ?NormalizationRootNode = operationLoader.get(
658
+ followup.operationReference,
659
+ );
660
+ if (node != null) {
661
+ const result = this._normalizeFollowup(followup, node);
662
+ outPayloads.push(...result.payloads);
663
+ outPendingModules.push(...result.pendingModules);
664
+ return;
665
+ }
666
+
667
+ // Async: return a Promise that resolves to the full NormalizationResult,
668
+ // preserving any nested payloads and further pending modules produced by
669
+ // the followup. The caller drains pendingModules recursively (see
670
+ // ReactiveQueryExecutionNode_EXPERIMENTAL.executeWithNetwork) — this
671
+ // mirrors OperationExecutor's recursive `_processPayloadFollowups`.
672
+ const emptyResult: NormalizationResult = {
673
+ payloads: [],
674
+ pendingModules: [],
675
+ };
676
+ outPendingModules.push(
677
+ operationLoader
678
+ .load(followup.operationReference)
679
+ .then((loadedNode: ?NormalizationRootNode) =>
680
+ loadedNode != null
681
+ ? this._normalizeFollowup(followup, loadedNode)
682
+ : emptyResult,
683
+ )
684
+ // TODO(skyyao): surface async @module load errors instead of silently
685
+ // swallowing — OperationExecutor terminates execution via sink.error().
686
+ .catch((_error: unknown) => emptyResult),
687
+ );
688
+ }
689
+
690
+ _normalizeFollowup(
691
+ followup: ModuleImportPayload,
692
+ node: NormalizationRootNode,
693
+ ): NormalizationResult {
694
+ const operationNode =
695
+ node.kind === 'SplitOperation' ? node : node.operation;
696
+ // TODO(skyyao): for SplitOperation followups, bind variables via
697
+ // getLocalVariables(followup.variables, operationNode.argumentDefinitions,
698
+ // followup.args) like OperationExecutor._normalizeFollowupPayload — current
699
+ // pass-through breaks @module argument binding.
700
+ // TODO(skyyao): propagate is_final extension on the synthetic response when
701
+ // in loading_final state, to enable nested defer/3D processing on
702
+ // non-streaming server mode (see OperationExecutor.js:864-870).
703
+ const selector = createNormalizationSelector(
704
+ operationNode,
705
+ followup.dataID,
706
+ followup.variables,
707
+ );
708
+
709
+ const payload = this._normalizeResponse(
710
+ {data: followup.data} as $FlowFixMe,
711
+ selector,
712
+ followup.typeName,
713
+ {
714
+ ...this._options,
715
+ path: followup.path,
716
+ },
717
+ this._useExecTimeResolvers,
718
+ );
719
+
720
+ const extraPayloads: Array<RelayResponsePayload> = [];
721
+ const pendingModules: Array<Promise<NormalizationResult>> = [];
722
+
723
+ // Register nested placeholders
724
+ if (
725
+ payload.incrementalPlaceholders != null &&
726
+ payload.incrementalPlaceholders.length > 0
727
+ ) {
728
+ this._registerPlaceholders(
729
+ payload.incrementalPlaceholders,
730
+ payload.source,
731
+ payload.fieldPayloads,
732
+ extraPayloads,
733
+ pendingModules,
734
+ );
735
+ }
736
+
737
+ // Process nested followups
738
+ if (
739
+ payload.followupPayloads != null &&
740
+ payload.followupPayloads.length > 0
741
+ ) {
742
+ this._processFollowups(
743
+ payload.followupPayloads,
744
+ extraPayloads,
745
+ pendingModules,
746
+ );
747
+ }
748
+
749
+ const primaryPayload: RelayResponsePayload = {
750
+ ...payload,
751
+ followupPayloads: null,
752
+ incrementalPlaceholders: null,
753
+ isFinal: this._computeIsFinal(payload.isFinal),
754
+ isPreNormalized: true,
755
+ };
756
+
757
+ return {
758
+ payloads: [primaryPayload, ...extraPayloads],
759
+ pendingModules,
760
+ };
761
+ }
762
+
763
+ // ---------------------------------------------------------------------------
764
+ // Private: completion tracking
765
+ // ---------------------------------------------------------------------------
766
+
767
+ _computeIsFinal(serverIsFinal: boolean): boolean {
768
+ return (
769
+ (serverIsFinal || this._serverComplete) &&
770
+ this._bufferedResponses.size === 0
771
+ );
772
+ }
773
+ }
774
+
775
+ /**
776
+ * Build a unique key for matching incremental responses to placeholders.
777
+ */
778
+ function makeKey(label: string, pathKey: string): string {
779
+ return `${label}::${pathKey}`;
780
+ }
781
+
782
+ module.exports = NormalizationEngine;