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,592 @@
1
+ ---
2
+ id: updating-connections
3
+ title: Updating Connections
4
+ slug: /guided-tour/list-data/updating-connections/
5
+ description: Relay guide to updating connections
6
+ keywords:
7
+ - pagination
8
+ - usePaginationFragment
9
+ - updating
10
+ - connection
11
+ ---
12
+
13
+ import DocsRating from '@site/src/core/DocsRating';
14
+ import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';
15
+
16
+ Usually when you're rendering a connection, you'll also want to be able to add or remove items to/from the connection in response to user actions.
17
+
18
+ Relay holds a local in-memory store of normalized GraphQL data, where records are stored by their IDs. When creating mutations, subscriptions, or local data updates with Relay, you must provide an [`updater`](../../updating-data/graphql-mutations/#updater-functions) function, inside which you can access and read records, as well as write and make updates to them. When records are updated, any components affected by the updated data will be notified and re-rendered.
19
+
20
+
21
+ ## Connection Records
22
+
23
+ In Relay, connection fields that are marked with the `@connection` directive are stored as special records in the store, and they hold and accumulate *all* of the items that have been fetched for the connection so far. In order to add or remove items from a connection, we need to access the connection record using the connection `key`, which was provided when declaring a `@connection`; specifically, this allows us to access a connection inside an [`updater`](../../updating-data/graphql-mutations/#updater-functions) function using the `ConnectionHandler` APIs.
24
+
25
+ For example, given the following fragment that declares a `@connection`, we can access the connection record inside an `updater` function in a few different ways:
26
+
27
+ ```js
28
+ const {graphql} = require('react-relay');
29
+
30
+ const storyFragment = graphql`
31
+ fragment StoryComponent_story on Story {
32
+ comments @connection(key: "StoryComponent_story_comments_connection") {
33
+ nodes {
34
+ body {
35
+ text
36
+ }
37
+ }
38
+ }
39
+ }
40
+ `;
41
+ ```
42
+
43
+ ### Accessing connections using `__id`
44
+
45
+ We can query for a connection's `__id` field, and then use that `__id` to access the record in the store:
46
+
47
+ ```js
48
+ const fragmentData = useFragment(
49
+ graphql`
50
+ fragment StoryComponent_story on Story {
51
+ comments @connection(key: "StoryComponent_story_comments_connection") {
52
+ # Query for the __id field
53
+ __id
54
+
55
+ # ...
56
+ }
57
+ }
58
+ `,
59
+ props.story,
60
+ );
61
+
62
+ // Get the connection record id
63
+ const connectionID = fragmentData?.comments?.__id;
64
+ ```
65
+
66
+ Then use it to access the record in the store:
67
+
68
+ ```js
69
+ function updater(store: RecordSourceSelectorProxy) {
70
+ // connectionID is passed as input to the mutation/subscription
71
+ const connection = store.get(connectionID);
72
+
73
+ // ...
74
+ }
75
+ ```
76
+
77
+ :::note
78
+ The `__id` field is **NOT** something that your GraphQL API needs to expose. Instead, it's an identifier that Relay automatically adds to identify the connection record.
79
+ :::
80
+
81
+ ### Accessing connections using `ConnectionHandler.getConnectionID`
82
+
83
+ If we have access to the ID of the parent record that holds the connection, we can access the connection record by using the `ConnectionHandler.getConnectionID` API:
84
+
85
+ ```js
86
+ const {ConnectionHandler} = require('relay-runtime');
87
+
88
+ function updater(store: RecordSourceSelectorProxy) {
89
+ // Get the connection ID
90
+ const connectionID = ConnectionHandler.getConnectionID(
91
+ storyID, // passed as input to the mutation/subscription
92
+ 'StoryComponent_story_comments_connection',
93
+ );
94
+
95
+ // Get the connection record
96
+ const connectionRecord = store.get(connectionID);
97
+
98
+ // ...
99
+ }
100
+ ```
101
+
102
+ ### Accessing connections using `ConnectionHandler.getConnection`
103
+
104
+ If we have access to the parent record that holds the connection, we can access the connection record via the parent, by using the `ConnectionHandler.getConnection` API:
105
+
106
+ ```js
107
+ const {ConnectionHandler} = require('relay-runtime');
108
+
109
+ function updater(store: RecordSourceSelectorProxy) {
110
+ // Get parent story record
111
+ // storyID is passed as input to the mutation/subscription
112
+ const storyRecord = store.get(storyID);
113
+
114
+ // Get the connection record from the parent
115
+ const connectionRecord = ConnectionHandler.getConnection(
116
+ storyRecord,
117
+ 'StoryComponent_story_comments_connection',
118
+ );
119
+
120
+ // ...
121
+ }
122
+ ```
123
+
124
+ ## Adding edges
125
+
126
+ There are a couple of alternatives for adding edges to a connection:
127
+
128
+ ### Using declarative directives
129
+
130
+ Usually, mutation or subscription payloads will expose the new edges that were added on the server as a field with a single edge or list of edges. If your mutation or subscription exposes an edge or edges field that you can query for in the response, then you can use the `@appendEdge` and `@prependEdge` declarative mutation directives on that field in order to add the newly created edges to the specified connections (note that these directives also work on queries).
131
+
132
+ Alternatively, mutation or subscription payloads might expose the new nodes that were added on the server as a field with a single node or list of nodes. If your mutation or subscription exposes a node or nodes field that you can query for in the response, then you can use the `@appendNode` and `@prependNode` declarative mutation directives on that field in order to add the newly created nodes, wrapped inside edges, to the specified connections (note that these directives also work on queries).
133
+
134
+ These directives accept a `connections` parameter, which needs to be a GraphQL variable containing an array of connection IDs. Connection IDs can be obtained either by using the [`__id` field on connections](#accessing-connections-using-__id) or using the [`ConnectionHandler.getConnectionID`](#accessing-connections-using-connectionhandlergetconnectionid) API.
135
+
136
+
137
+ #### `@appendEdge` / `@prependEdge`
138
+
139
+ These directives work on a field with a single edge or list of edges. `@prependEdge` will add the selected edges to the beginning of each connection defined in the `connections` array, whereas `@appendEdge` will add the selected edges to the end of each connection in the array.
140
+
141
+ **Arguments:**
142
+ - `connections`: An array of connection IDs. Connection IDs can be obtained either by using the [`__id` field on connections](#accessing-connections-using-__id) or using the [`ConnectionHandler.getConnectionID`](#accessing-connections-using-connectionhandlergetconnectionid) API.
143
+
144
+
145
+ **Example:**
146
+
147
+ ```js
148
+ // Get the connection ID using the `__id` field
149
+ const connectionID = fragmentData?.comments?.__id;
150
+
151
+ // Or get it using `ConnectionHandler.getConnectionID()`
152
+ const connectionID = ConnectionHandler.getConnectionID(
153
+ '<story-id>',
154
+ 'StoryComponent_story_comments_connection',
155
+ );
156
+
157
+ // ...
158
+
159
+ // Mutation
160
+ commitMutation<AppendCommentMutation>(environment, {
161
+ mutation: graphql`
162
+ mutation AppendCommentMutation(
163
+ # Define a GraphQL variable for the connections array
164
+ $connections: [ID!]!
165
+ $input: CommentCreateInput
166
+ ) {
167
+ commentCreate(input: $input) {
168
+ # Use @appendEdge or @prependEdge on the edge field
169
+ feedbackCommentEdge @appendEdge(connections: $connections) {
170
+ cursor
171
+ node {
172
+ id
173
+ }
174
+ }
175
+ }
176
+ }
177
+ `,
178
+ variables: {
179
+ input,
180
+ // Pass the `connections` array
181
+ connections: [connectionID],
182
+ },
183
+ });
184
+ ```
185
+
186
+
187
+ #### `@appendNode` / `@prependNode`
188
+
189
+ These directives work on a field with a single node or list of nodes, and will create edges with the specified `edgeTypeName`. `@prependNode` will add edges containing the selected nodes to the beginning of each connection defined in the `connections` array, whereas `@appendNode` will add edges containing the selected nodes to the end of each connection in the array.
190
+
191
+ **Arguments:**
192
+ - `connections`: An array of connection IDs. Connection IDs can be obtained either by using the [`__id` field on connections](#accessing-connections-using-__id) or using the [`ConnectionHandler.getConnectionID`](#accessing-connections-using-connectionhandlergetconnectionid) API.
193
+ - `edgeTypeName`: The type name of the edge that contains the node, corresponding to the edge type argument in `ConnectionHandler.createEdge`.
194
+
195
+ **Example:**
196
+ ```js
197
+ // Get the connection ID using the `__id` field
198
+ const connectionID = fragmentData?.comments?.__id;
199
+
200
+ // Or get it using `ConnectionHandler.getConnectionID()`
201
+ const connectionID = ConnectionHandler.getConnectionID(
202
+ '<story-id>',
203
+ 'StoryComponent_story_comments_connection',
204
+ );
205
+
206
+ // ...
207
+
208
+ // Mutation
209
+ commitMutation<AppendCommentMutation>(environment, {
210
+ mutation: graphql`
211
+ mutation AppendCommentMutation(
212
+ # Define a GraphQL variable for the connections array
213
+ $connections: [ID!]!
214
+ $input: CommentCreateInput
215
+ ) {
216
+ commentCreate(input: $input) {
217
+ # Use @appendNode or @prependNode on the node field
218
+ feedbackCommentNode @appendNode(connections: $connections, edgeTypeName: "CommentsEdge") {
219
+ id
220
+ }
221
+ }
222
+ }
223
+ `,
224
+ variables: {
225
+ input,
226
+ // Pass the `connections` array
227
+ connections: [connectionID],
228
+ },
229
+ });
230
+ ```
231
+
232
+
233
+ #### Order of execution
234
+
235
+ For all of these directives, they will be executed in the following order within the mutation or subscription, as per the [order of execution of updates](../../updating-data/graphql-mutations/#order-of-execution-of-updater-functions):
236
+
237
+ * When the mutation is initiated, after the optimistic response is handled, and after the optimistic updater function is executed, the `@prependEdge`, `@appendEdge`, `@prependNode`, and `@appendNode` directives will be applied to the optimistic response.
238
+ * If the mutation succeeds, after the data from the network response is merged with the existing values in the store, and after the updater function is executed, the `@prependEdge`, `@appendEdge`, `@prependNode`, and `@appendNode` directives will be applied to the data in the network response.
239
+ * If the mutation failed, the updates from processing the `@prependEdge`, `@appendEdge`, `@prependNode`, and `@appendNode` directives will be rolled back.
240
+
241
+
242
+ ### Manually adding edges
243
+
244
+ The directives described [above](#using-declarative-directives) largely remove the need to manually add and remove items from a connection, however, they do not provide as much control as you can get with manually writing an updater, and may not fulfill every use case.
245
+
246
+ In order to write an updater to modify the connection, we need to make sure we have access to the [connection record](#connection-record). Once we have the connection record, we also need a record for the new edge that we want to add to the connection. Usually, mutation or subscription payloads will contain the new edge that was added; if not, you can also construct a new edge from scratch.
247
+
248
+ For example, in the following mutation we can query for the newly created edge in the mutation response:
249
+
250
+ ```js
251
+ const {graphql} = require('react-relay');
252
+
253
+ const createCommentMutation = graphql`
254
+ mutation CreateCommentMutation($input: CommentCreateData!) {
255
+ comment_create(input: $input) {
256
+ comment_edge {
257
+ cursor
258
+ node {
259
+ body {
260
+ text
261
+ }
262
+ }
263
+ }
264
+ }
265
+ }
266
+ `;
267
+ ```
268
+
269
+ * Note that we also query for the `cursor` for the new edge; this isn't strictly necessary, but it is information that will be required if we need to perform pagination based on that `cursor`.
270
+
271
+
272
+ Inside an [`updater`](../../updating-data/graphql-mutations/#updater-functions), we can access the edge inside the mutation response using Relay store APIs:
273
+
274
+ ```js
275
+ const {ConnectionHandler} = require('relay-runtime');
276
+
277
+ function updater(store: RecordSourceSelectorProxy) {
278
+ const storyRecord = store.get(storyID);
279
+ const connectionRecord = ConnectionHandler.getConnection(
280
+ storyRecord,
281
+ 'StoryComponent_story_comments_connection',
282
+ );
283
+
284
+ // Get the payload returned from the server
285
+ const payload = store.getRootField('comment_create');
286
+
287
+ // Get the edge inside the payload
288
+ const serverEdge = payload.getLinkedRecord('comment_edge');
289
+
290
+ // Build edge for adding to the connection
291
+ const newEdge = ConnectionHandler.buildConnectionEdge(
292
+ store,
293
+ connectionRecord,
294
+ serverEdge,
295
+ );
296
+
297
+ // ...
298
+ }
299
+ ```
300
+
301
+ * The mutation payload is available as a root field on that store, which can be read using the `store.getRootField` API. In our case, we're reading `comment_create`, which is the root field in the response.
302
+ * Note that we need to construct the new edge from the edge received from the server using `ConnectionHandler.buildConnectionEdge` before we can add it to the connection.
303
+
304
+
305
+ If you need to create a new edge from scratch, you can use `ConnectionHandler.createEdge`:
306
+
307
+ ```js
308
+ const {ConnectionHandler} = require('relay-runtime');
309
+
310
+ function updater(store: RecordSourceSelectorProxy) {
311
+ const storyRecord = store.get(storyID);
312
+ const connectionRecord = ConnectionHandler.getConnection(
313
+ storyRecord,
314
+ 'StoryComponent_story_comments_connection',
315
+ );
316
+
317
+ // Create a new local Comment record
318
+ const id = `client:new_comment:${randomID()}`;
319
+ const newCommentRecord = store.create(id, 'Comment');
320
+
321
+ // Create new edge
322
+ const newEdge = ConnectionHandler.createEdge(
323
+ store,
324
+ connectionRecord,
325
+ newCommentRecord,
326
+ 'CommentEdge', /* GraphQl Type for edge */
327
+ );
328
+
329
+ // ...
330
+ }
331
+ ```
332
+
333
+
334
+ Once we have a new edge record, we can add it to the the connection using `ConnectionHandler.insertEdgeAfter` or `ConnectionHandler.insertEdgeBefore`:
335
+
336
+ ```js
337
+ const {ConnectionHandler} = require('relay-runtime');
338
+
339
+ function updater(store: RecordSourceSelectorProxy) {
340
+ const storyRecord = store.get(storyID);
341
+ const connectionRecord = ConnectionHandler.getConnection(
342
+ storyRecord,
343
+ 'StoryComponent_story_comments_connection',
344
+ );
345
+
346
+ const newEdge = (...);
347
+
348
+ // Add edge to the end of the connection
349
+ ConnectionHandler.insertEdgeAfter(
350
+ connectionRecord,
351
+ newEdge,
352
+ );
353
+
354
+ // Add edge to the beginning of the connection
355
+ ConnectionHandler.insertEdgeBefore(
356
+ connectionRecord,
357
+ newEdge,
358
+ );
359
+ }
360
+ ```
361
+
362
+ * Note that these APIs will *mutate* the connection in place
363
+
364
+ :::note
365
+ Check out our complete [Relay Store APIs](../../../api-reference/store/).
366
+ :::
367
+
368
+ ## Removing edges
369
+
370
+ ### Using the declarative deletion directive
371
+
372
+ Similarly to the [directives to add edges](#using-declarative-directives), we can use the `@deleteEdge` directive to delete edges from connections. If your mutation or subscription exposes a field with the ID or IDs of the nodes that were deleted that you can query for in the response, then you can use the `@deleteEdge` directive on that field to delete the respective edges from the connection (note that this directive also works on queries).
373
+
374
+ #### `@deleteEdge`
375
+
376
+ Works on GraphQL fields that return an `ID` or `[ID]`. Will delete the edges with nodes that match the `id` from each connection defined in the `connections` array.
377
+
378
+ **Arguments:**
379
+ - `connections`: An array of connection IDs. Connection IDs can be obtained either by using the [`__id` field on connections](#accessing-connections-using-__id) or using the [`ConnectionHandler.getConnectionID`](#accessing-connections-using-connectionhandlergetconnectionid) API.
380
+
381
+
382
+ **Example:**
383
+
384
+ ```js
385
+ // Get the connection ID using the `__id` field
386
+ const connectionID = fragmentData?.comments?.__id;
387
+
388
+ // Or get it using `ConnectionHandler.getConnectionID()`
389
+ const connectionID = ConnectionHandler.getConnectionID(
390
+ '<story-id>',
391
+ 'StoryComponent_story_comments_connection',
392
+ );
393
+
394
+ // ...
395
+
396
+ // Mutation
397
+ commitMutation<DeleteCommentsMutation>(environment, {
398
+ mutation: graphql`
399
+ mutation DeleteCommentsMutation(
400
+ # Define a GraphQL variable for the connections array
401
+ $connections: [ID!]!
402
+ $input: CommentsDeleteInput
403
+ ) {
404
+ commentsDelete(input: $input) {
405
+ deletedCommentIds @deleteEdge(connections: $connections)
406
+ }
407
+ }
408
+ `,
409
+ variables: {
410
+ input,
411
+ // Pass the `connections` array
412
+ connections: [connectionID],
413
+ },
414
+ });
415
+ ```
416
+
417
+ ### Manually removing edges
418
+
419
+ `ConnectionHandler` provides a similar API to remove an edge from a connection, via `ConnectionHandler.deleteNode`:
420
+
421
+ ```js
422
+ const {ConnectionHandler} = require('RelayModern');
423
+
424
+ function updater(store: RecordSourceSelectorProxy) {
425
+ const storyRecord = store.get(storyID);
426
+ const connectionRecord = ConnectionHandler.getConnection(
427
+ storyRecord,
428
+ 'StoryComponent_story_comments_connection',
429
+ );
430
+
431
+ // Remove edge from the connection, given the ID of the node
432
+ ConnectionHandler.deleteNode(
433
+ connectionRecord,
434
+ commentIDToDelete,
435
+ );
436
+ }
437
+ ```
438
+
439
+ * In this case `ConnectionHandler.deleteNode` will remove an edge given a *`node` ID*. This means it will look up which edge in the connection contains a node with the provided ID, and remove that edge.
440
+ * Note that this API will *mutate* the connection in place.
441
+
442
+
443
+ :::note
444
+ Remember: when performing any of the operations described here to mutate a connection, any fragment or query components that are rendering the affected connection will be notified and re-render with the latest version of the connection.
445
+ :::
446
+
447
+
448
+ ## Connection identity with filters
449
+
450
+ In our previous examples, our connections didn't take any arguments as filters. If you declared a connection that takes arguments as filters, the values used for the filters will be part of the connection identifier. In other words, *each of the values passed in as connection filters will be used to identify the connection in the Relay store.*
451
+
452
+ :::note
453
+ Note that this excludes pagination arguments, i.e. it excludes `first`, `last`, `before`, and `after`.
454
+ :::
455
+
456
+
457
+ For example, let's say the `comments` field took the following arguments, which we pass in as GraphQL [variables](../../rendering/variables/):
458
+
459
+ ```js
460
+ const {graphql} = require('RelayModern');
461
+
462
+ const storyFragment = graphql`
463
+ fragment StoryComponent_story on Story {
464
+ comments(
465
+ order_by: $orderBy,
466
+ filter_mode: $filterMode,
467
+ language: $language,
468
+ ) @connection(key: "StoryComponent_story_comments_connection") {
469
+ edges {
470
+ nodes {
471
+ body {
472
+ text
473
+ }
474
+ }
475
+ }
476
+ }
477
+ }
478
+ `;
479
+ ```
480
+
481
+ In the example above, this means that whatever values we used for `$orderBy`, `$filterMode` and `$language` when we queried for the `comments` field will be part of the connection identifier, and we'll need to use those values when accessing the connection record from the Relay store.
482
+
483
+ In order to do so, we need to pass a third argument to `ConnectionHandler.getConnection`, with concrete filter values to identify the connection:
484
+
485
+ ```js
486
+ const {ConnectionHandler} = require('RelayModern');
487
+
488
+ function updater(store: RecordSourceSelectorProxy) {
489
+ const storyRecord = store.get(storyID);
490
+
491
+ // Get the connection instance for the connection with comments sorted
492
+ // by the date they were added
493
+ const connectionRecordSortedByDate = ConnectionHandler.getConnection(
494
+ storyRecord,
495
+ 'StoryComponent_story_comments_connection',
496
+ {order_by: '*DATE_ADDED*', filter_mode: null, language: null}
497
+ );
498
+
499
+ // Get the connection instance for the connection that only contains
500
+ // comments made by friends
501
+ const connectionRecordFriendsOnly = ConnectionHandler.getConnection(
502
+ storyRecord,
503
+ 'StoryComponent_story_comments_connection',
504
+ {order_by: null, filter_mode: '*FRIENDS_ONLY*', language: null}
505
+ );
506
+ }
507
+ ```
508
+
509
+ This implies that by default, *each combination of values used for filters will produce a different record for the connection.*
510
+
511
+ When making updates to a connection, you will need to make sure to update all of the relevant records affected by a change. For example, if we were to add a new comment to our example connection, we'd need to make sure *not* to add the comment to the `FRIENDS_ONLY` connection, if the new comment wasn't made by a friend of the user:
512
+
513
+ ```js
514
+ const {ConnectionHandler} = require('relay-runtime');
515
+
516
+ function updater(store: RecordSourceSelectorProxy) {
517
+ const storyRecord = store.get(storyID);
518
+
519
+ // Get the connection instance for the connection with comments sorted
520
+ // by the date they were added
521
+ const connectionRecordSortedByDate = ConnectionHandler.getConnection(
522
+ storyRecord,
523
+ 'StoryComponent_story_comments_connection',
524
+ {order_by: '*DATE_ADDED*', filter_mode: null, language: null}
525
+ );
526
+
527
+ // Get the connection instance for the connection that only contains
528
+ // comments made by friends
529
+ const connectionRecordFriendsOnly = ConnectionHandler.getConnection(
530
+ storyRecord,
531
+ 'StoryComponent_story_comments_connection',
532
+ {order_by: null, filter_mode: '*FRIENDS_ONLY*', language: null}
533
+ );
534
+
535
+ const newComment = (...);
536
+ const newEdge = (...);
537
+
538
+ ConnectionHandler.insertEdgeAfter(
539
+ connectionRecordSortedByDate,
540
+ newEdge,
541
+ );
542
+
543
+ if (isMadeByFriend(storyRecord, newComment) {
544
+ // Only add new comment to friends-only connection if the comment
545
+ // was made by a friend
546
+ ConnectionHandler.insertEdgeAfter(
547
+ connectionRecordFriendsOnly,
548
+ newEdge,
549
+ );
550
+ }
551
+ }
552
+ ```
553
+
554
+
555
+
556
+ ### Managing connections with many filters
557
+
558
+ As you can see, just adding a few filters to a connection can make the complexity and number of connection records that need to be managed explode. In order to more easily manage this, Relay allows you to specify exactly *which* filters should be used as connection identifiers.
559
+
560
+ By default, *all* non-pagination filters will be used as part of the connection identifier. However, when declaring a `@connection`, you can specify the exact set of filters to use for connection identity:
561
+
562
+ ```js
563
+ const {graphql} = require('relay-runtime');
564
+
565
+ const storyFragment = graphql`
566
+ fragment StoryComponent_story on Story {
567
+ comments(
568
+ order_by: $orderBy
569
+ filter_mode: $filterMode
570
+ language: $language
571
+ )
572
+ @connection(
573
+ key: "StoryComponent_story_comments_connection"
574
+ filters: ["order_by", "filter_mode"]
575
+ ) {
576
+ edges {
577
+ nodes {
578
+ body {
579
+ text
580
+ }
581
+ }
582
+ }
583
+ }
584
+ }
585
+ `;
586
+ ```
587
+
588
+ * By specifying `filters` when declaring the `@connection`, we're indicating to Relay the exact set of filter values that should be used as part of connection identity. In this case, we're excluding `language`, which means that only values for `order_by` and `filter_mode` will affect connection identity and thus produce new connection records.
589
+ * Conceptually, this means that we're specifying which arguments affect the output of the connection from the server, or in other words, which arguments are *actually* *filters*. If one of the connection arguments doesn't actually change the set of items that are returned from the server, or their ordering, then it isn't really a filter on the connection, and we don't need to identify the connection differently when that value changes. In our example, changing the `language` of the comments we request doesn't change the set of comments that are returned by the connection, so it is safe to exclude it from `filters`.
590
+ * This can also be useful if we know that any of the connection arguments will never change in our app, in which case it would also be safe to exclude from `filters`.
591
+
592
+ <DocsRating />