relay-runtime 20.1.0 → 21.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/experimental.js +1 -1
  2. package/experimental.js.flow +8 -8
  3. package/handlers/connection/ConnectionHandler.js.flow +5 -5
  4. package/handlers/connection/ConnectionInterface.js.flow +1 -1
  5. package/index.js +1 -1
  6. package/index.js.flow +125 -62
  7. package/lib/experimental.js +3 -3
  8. package/lib/index.js +105 -57
  9. package/lib/multi-actor-environment/ActorIdentifier.js +2 -2
  10. package/lib/multi-actor-environment/MultiActorEnvironment.js +3 -1
  11. package/lib/mutations/commitMutation.js +8 -8
  12. package/lib/mutations/validateMutation.js +4 -4
  13. package/lib/query/GraphQLTag.js +3 -3
  14. package/lib/query/fetchQuery.js +15 -3
  15. package/lib/store/DataChecker.js +38 -4
  16. package/lib/store/NormalizationEngine.js +373 -0
  17. package/lib/store/OperationExecutor.js +172 -113
  18. package/lib/store/RelayConcreteVariables.js +1 -1
  19. package/lib/store/RelayErrorTrie.js +2 -2
  20. package/lib/store/RelayExperimentalGraphResponseTransform.js +8 -8
  21. package/lib/store/RelayModernEnvironment.js +26 -19
  22. package/lib/store/RelayModernRecord.js +18 -8
  23. package/lib/store/RelayModernSelector.js +9 -9
  24. package/lib/store/RelayModernStore.js +152 -43
  25. package/lib/store/RelayPublishQueue.js +1 -1
  26. package/lib/store/RelayReader.js +76 -38
  27. package/lib/store/RelayRecordSource.js +6 -0
  28. package/lib/store/RelayReferenceMarker.js +2 -1
  29. package/lib/store/RelayResponseNormalizer.js +88 -55
  30. package/lib/store/RelayStoreSubscriptions.js +34 -10
  31. package/lib/store/RelayStoreUtils.js +8 -1
  32. package/lib/store/ResolverFragments.js +2 -2
  33. package/lib/store/live-resolvers/LiveResolverCache.js +25 -9
  34. package/lib/store/observeFragmentExperimental.js +17 -1
  35. package/lib/store/observeQueryExperimental.js +2 -2
  36. package/lib/subscription/requestSubscription.js +3 -3
  37. package/lib/util/RelayError.js +3 -0
  38. package/lib/util/RelayFeatureFlags.js +6 -2
  39. package/lib/util/RelayReplaySubject.js +4 -4
  40. package/lib/util/handlePotentialSnapshotErrors.js +2 -2
  41. package/lib/util/stableCopy.js +2 -2
  42. package/llm-docs/api-reference/entrypoint-apis/entrypoint-container.mdx +38 -0
  43. package/llm-docs/api-reference/entrypoint-apis/load-entrypoint.mdx +77 -0
  44. package/llm-docs/api-reference/entrypoint-apis/use-entrypoint-loader.mdx +99 -0
  45. package/llm-docs/api-reference/graphql/graphql-directives.mdx +378 -0
  46. package/llm-docs/api-reference/hooks/_use-lazy-load-query-extra.mdx +16 -0
  47. package/llm-docs/api-reference/hooks/load-query.mdx +84 -0
  48. package/llm-docs/api-reference/hooks/relay-environment-provider.mdx +78 -0
  49. package/llm-docs/api-reference/hooks/use-client-query.mdx +65 -0
  50. package/llm-docs/api-reference/hooks/use-fragment.mdx +69 -0
  51. package/llm-docs/api-reference/hooks/use-lazy-load-query.mdx +62 -0
  52. package/llm-docs/api-reference/hooks/use-mutation.mdx +94 -0
  53. package/llm-docs/api-reference/hooks/use-pagination-fragment.mdx +166 -0
  54. package/llm-docs/api-reference/hooks/use-prefetchable-forward-pagination-fragment.mdx +134 -0
  55. package/llm-docs/api-reference/hooks/use-preloaded-query.mdx +84 -0
  56. package/llm-docs/api-reference/hooks/use-query-loader.mdx +95 -0
  57. package/llm-docs/api-reference/hooks/use-refetchable-fragment.mdx +122 -0
  58. package/llm-docs/api-reference/hooks/use-relay-environment.mdx +37 -0
  59. package/llm-docs/api-reference/hooks/use-subscription.mdx +66 -0
  60. package/llm-docs/api-reference/relay-resolvers/docblock-format.mdx +321 -0
  61. package/llm-docs/api-reference/relay-resolvers/runtime-functions.mdx +94 -0
  62. package/llm-docs/api-reference/relay-runtime/commit-mutation.mdx +65 -0
  63. package/llm-docs/api-reference/relay-runtime/fetch-query.mdx +113 -0
  64. package/llm-docs/api-reference/relay-runtime/field-logger.mdx +170 -0
  65. package/llm-docs/api-reference/relay-runtime/observe-fragment.mdx +92 -0
  66. package/llm-docs/api-reference/relay-runtime/relay-environment.mdx +53 -0
  67. package/llm-docs/api-reference/relay-runtime/request-subscription.mdx +54 -0
  68. package/llm-docs/api-reference/relay-runtime/runtime-configuration.mdx +52 -0
  69. package/llm-docs/api-reference/relay-runtime/store.mdx +734 -0
  70. package/llm-docs/api-reference/relay-runtime/wait-for-fragment-data.mdx +89 -0
  71. package/llm-docs/api-reference/types/CacheConfig.mdx +8 -0
  72. package/llm-docs/api-reference/types/Disposable.mdx +4 -0
  73. package/llm-docs/api-reference/types/GraphQLSubscriptionConfig.mdx +17 -0
  74. package/llm-docs/api-reference/types/MutationConfig.mdx +31 -0
  75. package/llm-docs/api-reference/types/SelectorStoreUpdater.mdx +6 -0
  76. package/llm-docs/api-reference/types/UploadableMap.mdx +3 -0
  77. package/llm-docs/community/learning-resources.mdx +64 -0
  78. package/llm-docs/debugging/declarative-mutation-directives.mdx +34 -0
  79. package/llm-docs/debugging/disallowed-id-types-error.mdx +43 -0
  80. package/llm-docs/debugging/inconsistent-typename-error.mdx +47 -0
  81. package/llm-docs/debugging/relay-devtools.mdx +73 -0
  82. package/llm-docs/debugging/why-null.mdx +116 -0
  83. package/llm-docs/editor-support.mdx +55 -0
  84. package/llm-docs/error-reference/unknown-field.mdx +36 -0
  85. package/llm-docs/getting-started/babel-plugin.mdx +31 -0
  86. package/llm-docs/getting-started/compiler-config.mdx +25 -0
  87. package/llm-docs/getting-started/compiler.mdx +82 -0
  88. package/llm-docs/getting-started/lint-rules.mdx +87 -0
  89. package/llm-docs/getting-started/production.mdx +30 -0
  90. package/llm-docs/getting-started/quick-start.mdx +213 -0
  91. package/llm-docs/glossary/glossary.mdx +1040 -0
  92. package/llm-docs/guided-tour/list-data/advanced-pagination.mdx +157 -0
  93. package/llm-docs/guided-tour/list-data/connections.mdx +81 -0
  94. package/llm-docs/guided-tour/list-data/pagination.mdx +193 -0
  95. package/llm-docs/guided-tour/list-data/rendering-connections.mdx +112 -0
  96. package/llm-docs/guided-tour/list-data/streaming-pagination.mdx +87 -0
  97. package/llm-docs/guided-tour/managing-data-outside-react/retaining-queries.mdx +51 -0
  98. package/llm-docs/guided-tour/refetching/refetching-queries-with-different-data.mdx +337 -0
  99. package/llm-docs/guided-tour/refetching/refreshing-queries.mdx +350 -0
  100. package/llm-docs/guided-tour/rendering/environment.mdx +59 -0
  101. package/llm-docs/guided-tour/rendering/error-states.mdx +295 -0
  102. package/llm-docs/guided-tour/rendering/fragments.mdx +354 -0
  103. package/llm-docs/guided-tour/rendering/loading-states.mdx +245 -0
  104. package/llm-docs/guided-tour/rendering/queries.mdx +261 -0
  105. package/llm-docs/guided-tour/rendering/variables.mdx +233 -0
  106. package/llm-docs/guided-tour/reusing-cached-data/fetch-policies.mdx +56 -0
  107. package/llm-docs/guided-tour/reusing-cached-data/filling-in-missing-data.mdx +102 -0
  108. package/llm-docs/guided-tour/reusing-cached-data/introduction.mdx +22 -0
  109. package/llm-docs/guided-tour/reusing-cached-data/presence-of-data.mdx +93 -0
  110. package/llm-docs/guided-tour/reusing-cached-data/rendering-partially-cached-data.mdx +175 -0
  111. package/llm-docs/guided-tour/reusing-cached-data/staleness-of-data.mdx +116 -0
  112. package/llm-docs/guided-tour/updating-data/client-only-data.mdx +115 -0
  113. package/llm-docs/guided-tour/updating-data/graphql-mutations.mdx +334 -0
  114. package/llm-docs/guided-tour/updating-data/graphql-subscriptions.mdx +279 -0
  115. package/llm-docs/guided-tour/updating-data/imperatively-modifying-linked-fields.mdx +511 -0
  116. package/llm-docs/guided-tour/updating-data/imperatively-modifying-store-data-legacy.mdx +142 -0
  117. package/llm-docs/guided-tour/updating-data/imperatively-modifying-store-data.mdx +275 -0
  118. package/llm-docs/guided-tour/updating-data/introduction.mdx +25 -0
  119. package/llm-docs/guided-tour/updating-data/local-data-updates.mdx +71 -0
  120. package/llm-docs/guided-tour/updating-data/typesafe-updaters-faq.mdx +83 -0
  121. package/llm-docs/guided-tour/updating-data/updating-connections.mdx +592 -0
  122. package/llm-docs/guides/alias-directive.mdx +160 -0
  123. package/llm-docs/guides/catch-directive.mdx +167 -0
  124. package/llm-docs/guides/client-schema-extensions.mdx +208 -0
  125. package/llm-docs/guides/codemods.mdx +66 -0
  126. package/llm-docs/guides/data-driven-dependencies/client-3d.mdx +255 -0
  127. package/llm-docs/guides/data-driven-dependencies/configuration.mdx +127 -0
  128. package/llm-docs/guides/data-driven-dependencies/introduction.mdx +39 -0
  129. package/llm-docs/guides/data-driven-dependencies/server-3d.mdx +664 -0
  130. package/llm-docs/guides/document-comparison.mdx +106 -0
  131. package/llm-docs/guides/graphql-server-specification.mdx +453 -0
  132. package/llm-docs/guides/network-layer.mdx +69 -0
  133. package/llm-docs/guides/persisted-queries.mdx +328 -0
  134. package/llm-docs/guides/relay-resolvers/context.mdx +99 -0
  135. package/llm-docs/guides/relay-resolvers/defining-fields.mdx +151 -0
  136. package/llm-docs/guides/relay-resolvers/defining-types.mdx +164 -0
  137. package/llm-docs/guides/relay-resolvers/deprecated.mdx +27 -0
  138. package/llm-docs/guides/relay-resolvers/derived-fields.mdx +127 -0
  139. package/llm-docs/guides/relay-resolvers/descriptions.mdx +44 -0
  140. package/llm-docs/guides/relay-resolvers/enabling.mdx +41 -0
  141. package/llm-docs/guides/relay-resolvers/errors.mdx +64 -0
  142. package/llm-docs/guides/relay-resolvers/field-arguments.mdx +63 -0
  143. package/llm-docs/guides/relay-resolvers/introduction.mdx +62 -0
  144. package/llm-docs/guides/relay-resolvers/limitations.mdx +30 -0
  145. package/llm-docs/guides/relay-resolvers/live-fields.mdx +164 -0
  146. package/llm-docs/guides/relay-resolvers/return-types.mdx +161 -0
  147. package/llm-docs/guides/relay-resolvers/suspense.mdx +41 -0
  148. package/llm-docs/guides/required-directive.mdx +240 -0
  149. package/llm-docs/guides/semantic-nullability.mdx +93 -0
  150. package/llm-docs/guides/testing-relay-components.mdx +642 -0
  151. package/llm-docs/guides/testing-relay-with-preloaded-queries.mdx +160 -0
  152. package/llm-docs/guides/throw-on-field-error-directive.mdx +58 -0
  153. package/llm-docs/guides/type-emission.mdx +414 -0
  154. package/llm-docs/home.mdx +32 -0
  155. package/llm-docs/principles-and-architecture/architecture-overview.mdx +24 -0
  156. package/llm-docs/principles-and-architecture/compiler-architecture.mdx +106 -0
  157. package/llm-docs/principles-and-architecture/runtime-architecture.mdx +249 -0
  158. package/llm-docs/principles-and-architecture/thinking-in-graphql.mdx +309 -0
  159. package/llm-docs/principles-and-architecture/thinking-in-relay.mdx +104 -0
  160. package/llm-docs/principles-and-architecture/videos.mdx +50 -0
  161. package/llm-docs/tutorial/arrays-lists.mdx +126 -0
  162. package/llm-docs/tutorial/fragments-1.mdx +487 -0
  163. package/llm-docs/tutorial/graphql.mdx +172 -0
  164. package/llm-docs/tutorial/interfaces-polymorphism.mdx +161 -0
  165. package/llm-docs/tutorial/intro.mdx +58 -0
  166. package/llm-docs/tutorial/mutations-updates.mdx +624 -0
  167. package/llm-docs/tutorial/organizing-mutations-queries-and-subscriptions.mdx +13 -0
  168. package/llm-docs/tutorial/queries-1.mdx +267 -0
  169. package/llm-docs/tutorial/queries-2.mdx +389 -0
  170. package/llm-docs/tutorial/refetchable-fragments.mdx +352 -0
  171. package/multi-actor-environment/ActorIdentifier.js.flow +2 -2
  172. package/multi-actor-environment/ActorSpecificEnvironment.js.flow +7 -7
  173. package/multi-actor-environment/ActorUtils.js.flow +1 -1
  174. package/multi-actor-environment/MultiActorEnvironment.js.flow +12 -8
  175. package/multi-actor-environment/MultiActorEnvironmentTypes.js.flow +3 -3
  176. package/mutations/RelayDeclarativeMutationConfig.js.flow +9 -9
  177. package/mutations/RelayRecordProxy.js.flow +8 -11
  178. package/mutations/RelayRecordSourceMutator.js.flow +4 -4
  179. package/mutations/RelayRecordSourceProxy.js.flow +4 -4
  180. package/mutations/RelayRecordSourceSelectorProxy.js.flow +6 -6
  181. package/mutations/applyOptimisticMutation.js.flow +2 -2
  182. package/mutations/commitMutation.js.flow +20 -16
  183. package/mutations/createUpdatableProxy.js.flow +19 -19
  184. package/mutations/readUpdatableFragment.js.flow +3 -3
  185. package/mutations/readUpdatableQuery.js.flow +3 -3
  186. package/mutations/validateMutation.js.flow +7 -7
  187. package/network/RelayNetworkTypes.js.flow +4 -4
  188. package/network/RelayObservable.js.flow +16 -14
  189. package/network/RelayQueryResponseCache.js.flow +3 -3
  190. package/network/wrapNetworkWithLogObserver.js.flow +1 -1
  191. package/package.json +2 -1
  192. package/query/GraphQLTag.js.flow +22 -10
  193. package/query/fetchQuery.js.flow +23 -10
  194. package/query/fetchQuery_DEPRECATED.js.flow +1 -1
  195. package/store/DataChecker.js.flow +43 -9
  196. package/store/NormalizationEngine.js.flow +779 -0
  197. package/store/OperationExecutor.js.flow +173 -70
  198. package/store/RelayConcreteVariables.js.flow +5 -5
  199. package/store/RelayErrorTrie.js.flow +12 -12
  200. package/store/RelayExperimentalGraphResponseHandler.js.flow +3 -3
  201. package/store/RelayExperimentalGraphResponseTransform.js.flow +10 -10
  202. package/store/RelayModernEnvironment.js.flow +41 -26
  203. package/store/RelayModernFragmentSpecResolver.js.flow +1 -1
  204. package/store/RelayModernOperationDescriptor.js.flow +1 -1
  205. package/store/RelayModernRecord.js.flow +46 -22
  206. package/store/RelayModernSelector.js.flow +21 -21
  207. package/store/RelayModernStore.js.flow +219 -58
  208. package/store/RelayOperationTracker.js.flow +2 -2
  209. package/store/RelayOptimisticRecordSource.js.flow +2 -2
  210. package/store/RelayPublishQueue.js.flow +21 -12
  211. package/store/RelayReader.js.flow +130 -58
  212. package/store/RelayRecordSource.js.flow +10 -0
  213. package/store/RelayRecordState.js.flow +1 -1
  214. package/store/RelayReferenceMarker.js.flow +5 -4
  215. package/store/RelayResponseNormalizer.js.flow +130 -54
  216. package/store/RelayStoreSubscriptions.js.flow +52 -8
  217. package/store/RelayStoreTypes.js.flow +153 -64
  218. package/store/RelayStoreUtils.js.flow +15 -7
  219. package/store/ResolverCache.js.flow +2 -2
  220. package/store/ResolverFragments.js.flow +12 -12
  221. package/store/StoreInspector.js.flow +6 -7
  222. package/store/cloneRelayHandleSourceField.js.flow +1 -1
  223. package/store/cloneRelayScalarHandleSourceField.js.flow +1 -1
  224. package/store/createRelayContext.js.flow +1 -1
  225. package/store/createRelayLoggingContext.js.flow +4 -4
  226. package/store/defaultGetDataID.js.flow +2 -2
  227. package/store/isRelayModernEnvironment.js.flow +4 -2
  228. package/store/live-resolvers/LiveResolverCache.js.flow +55 -20
  229. package/store/live-resolvers/LiveResolverSuspenseSentinel.js.flow +3 -3
  230. package/store/live-resolvers/getOutputTypeRecordIDs.js.flow +1 -1
  231. package/store/live-resolvers/isLiveStateValue.js.flow +2 -2
  232. package/store/live-resolvers/resolverDataInjector.js.flow +8 -5
  233. package/store/observeFragmentExperimental.js.flow +49 -20
  234. package/store/observeQueryExperimental.js.flow +5 -5
  235. package/store/readInlineData.js.flow +4 -4
  236. package/store/waitForFragmentExperimental.js.flow +3 -3
  237. package/subscription/requestSubscription.js.flow +7 -7
  238. package/util/NormalizationNode.js.flow +34 -32
  239. package/util/ReaderNode.js.flow +32 -30
  240. package/util/RelayConcreteNode.js.flow +5 -5
  241. package/util/RelayError.js.flow +4 -1
  242. package/util/RelayFeatureFlags.js.flow +21 -1
  243. package/util/RelayProfiler.js.flow +1 -1
  244. package/util/RelayReplaySubject.js.flow +3 -3
  245. package/util/RelayRuntimeTypes.js.flow +11 -11
  246. package/util/createPayloadFor3DField.js.flow +9 -5
  247. package/util/deepFreeze.js.flow +2 -2
  248. package/util/getFragmentIdentifier.js.flow +1 -1
  249. package/util/getPaginationMetadata.js.flow +1 -1
  250. package/util/getPaginationVariables.js.flow +1 -1
  251. package/util/getPendingOperationsForFragment.js.flow +2 -2
  252. package/util/getRefetchMetadata.js.flow +6 -5
  253. package/util/getValueAtPath.js.flow +3 -3
  254. package/util/handlePotentialSnapshotErrors.js.flow +5 -5
  255. package/util/isEmptyObject.js.flow +1 -1
  256. package/util/isPromise.js.flow +2 -2
  257. package/util/isScalarAndEqual.js.flow +1 -1
  258. package/util/recycleNodesInto.js.flow +2 -2
  259. package/util/registerEnvironmentWithDevTools.js.flow +1 -1
  260. package/util/shallowFreeze.js.flow +1 -1
  261. package/util/stableCopy.js.flow +5 -5
  262. package/util/withProvidedVariables.js.flow +14 -10
@@ -0,0 +1,164 @@
1
+ ---
2
+ id: live-fields
3
+ title: "Live Fields"
4
+ slug: /guides/relay-resolvers/live-fields/
5
+ description: Modeling data that changes over time in Relay Resolvers
6
+ ---
7
+ import {FbInternalOnly, fbContent} from 'docusaurus-plugin-internaldocs-fb/internal';
8
+ import Tabs from '@theme/Tabs';
9
+ import TabItem from '@theme/TabItem';
10
+
11
+ One critical difference between client state and server state is that as client state changes over time, those changes will need to be reflected in your UI. To address this, Relay Resolvers support the ability to be marked as `@live`. Live resolvers are expected to return a `LiveState` shaped object which includes methods which allow Relay to both `read()` the current value and also to `subscribe()` to changes to the value.
12
+
13
+ As this value changes over time, Relay will automatically recompute any [derived fields](./derived-fields.mdx) that depend on this field (including transitive dependencies if the changes cascade), and also efficiently trigger the update of any components/subscribers which have read fields that updated as a result of this change.
14
+
15
+ ## @live
16
+
17
+ <Tabs
18
+ groupId="resolver"
19
+ defaultValue="Docblock"
20
+ values={fbContent({
21
+ internal: [
22
+ {label: 'Docblock', value: 'Docblock'},
23
+ {label: 'Flow', value: 'Flow'},
24
+ ],
25
+ external: [
26
+ {label: 'Docblock', value: 'Docblock'},
27
+ ]
28
+ })}>
29
+ <TabItem value="Docblock">
30
+
31
+ To mark a resolver as live, add the `@live` docblock tag to the resolver definition. For example:
32
+
33
+ ```tsx
34
+ import type { LiveState } from 'relay-runtime';
35
+
36
+ /**
37
+ * @relayField Query.counter: Int
38
+ * @live
39
+ */
40
+ export function counter(): LiveState<number> {
41
+ return {
42
+ read: () => store.getState().counter,
43
+ subscribe: (callback) => {
44
+ return store.subscribe(callback);
45
+ },
46
+ };
47
+ }
48
+ ```
49
+
50
+ </TabItem>
51
+
52
+ <TabItem value="Flow">
53
+ <FbInternalOnly>
54
+
55
+ live is determined by the usage of `LiveState` in the Flow return type
56
+
57
+ ```tsx
58
+ import type { LiveState } from 'relay-runtime';
59
+
60
+ /**
61
+ * @relayField
62
+ */
63
+ export function counter(): LiveState<number> {
64
+ return {
65
+ read: () => store.getState().counter,
66
+ subscribe: (callback) => {
67
+ return store.subscribe(callback);
68
+ },
69
+ };
70
+ }
71
+ ```
72
+
73
+ </FbInternalOnly>
74
+ </TabItem>
75
+ </Tabs>
76
+
77
+ :::note
78
+ Both field resolvers and strong model resolvers, which map an ID to a model, may be annotated as `@live`.
79
+ :::
80
+
81
+ ## The LiveState Type
82
+
83
+ The return type of a Live Resolver is known as a `LiveState`. It is conceptually similar to an observable or a signal, if you are familiar with those concepts. Unlike an observable, when a `LiveState` notifies its subscriber of an update, it does not include the new value. Instead, the subscriber (Relay) is expected to call `read()` to get the new value.
84
+
85
+ While over-notification (subscription notifications when the read value has not actually changed) is supported, for performance reasons, it is recommended that the provider of the LiveState value confirms that the value has indeed changed before notifying Relay of the change.
86
+
87
+ The type of a LiveState is defined as follows:
88
+
89
+ ```ts
90
+ export type LiveState<T> = {
91
+ /**
92
+ * Returns the current value of the live state.
93
+ */
94
+ read(): T,
95
+ /**
96
+ * Subscribes to changes in the live state. The state provider should
97
+ * call the callback when the value of the live state changes.
98
+ */
99
+ subscribe(cb: () => void): () => void,
100
+ };
101
+ ```
102
+
103
+ ## Creating a LiveState Object
104
+
105
+ In most cases, you will want to define a helper function that reads your reactive data store and returns a `LiveState` object. For example, you for a Redux store you might write a wrapper that exposes a `LiveState` for a given selector:
106
+
107
+ ```ts
108
+ type Selector<T> = (state: State) => T;
109
+
110
+ function selectorAsLiveState<T>(selector: Selector<T>): LiveState<T> {
111
+ let currentValue = selector(store.getState());
112
+ return {
113
+ read: () => currentValue,
114
+ subscribe: (cb) => {
115
+ return store.subscribe(() => {
116
+ const newValue = selector(store.getState());
117
+ if (newValue === currentValue) {
118
+ return;
119
+ }
120
+ currentValue = newValue;
121
+ cb();
122
+ });
123
+ return unsubscribe;
124
+ },
125
+ };
126
+ }
127
+ ```
128
+
129
+ A Live Resolver that uses this helper might look like this:
130
+
131
+ ```tsx
132
+ /**
133
+ * @relayField Query.counter: Int
134
+ * @live
135
+ */
136
+ export function counter(): LiveState<number> {
137
+ return selectorAsLiveState(getCounter);
138
+ }
139
+
140
+ function getCounter(state) {
141
+ return state.counter;
142
+ }
143
+ ```
144
+
145
+ ## Batching
146
+
147
+ When state changes in your data layer, it's possible that one change could result in notifying many `@live` resolver subscriptions about updates. By default each of these updates will require Relay to do work to determine which components need to be updated. This can lead to significant duplicate work being performed.
148
+
149
+ When possible, it is recommended that you batch updates to `@live` resolvers. This can be done by wrapping your state updates in a `batchLiveStateUpdates()` call on your `RelayStore` instance.
150
+
151
+ A typical use with a Redux store might look like this:
152
+
153
+ ```ts
154
+ const store = createStore(reducer);
155
+ const originalDispatch = store.dispatch;
156
+
157
+ function wrapped(action) {
158
+ relayStore.batchLiveStateUpdates(() => {
159
+ originalDispatch(action);
160
+ })
161
+ }
162
+
163
+ store.dispatch = wrapped;
164
+ ```
@@ -0,0 +1,161 @@
1
+ ---
2
+ id: return-types
3
+ title: "Return Types"
4
+ slug: /guides/relay-resolvers/return-types/
5
+ description: Showing the different types of return values for Relay Resolvers
6
+ keywords:
7
+ - resolvers
8
+ - derived
9
+ - selectors
10
+ - reactive
11
+ ---
12
+
13
+ Relay Resolvers support a number of different return types, each of which has different semantics. This page will walk through the different types of supported return values and how they are used.
14
+
15
+ ## Scalar Types
16
+
17
+ The simplest type for a resolver to return is a built-in GraphQL scalar value. Scalar values are values that can be represented as a primitive value in GraphQL, such as a string, number, or boolean. To return a scalar simply define your resolver as returning the scalar type and then return the corresponding JavaScript value from your resolver function.
18
+
19
+ ```tsx
20
+ /**
21
+ * @relayField Post.isValid: Boolean
22
+ */
23
+ export function isValid(post: PostModel): boolean {
24
+ return post.content !== "" && post.author != null;
25
+ }
26
+ ```
27
+
28
+ ## List Types
29
+
30
+ Resolvers may also return a list of values. To do so, define your resolver as returning a list of the corresponding type and return an array from your resolver function.
31
+
32
+ ```tsx
33
+ /**
34
+ * @relayField User.favoriteColors: [String]
35
+ */
36
+ export function favoriteColors(user: UserModel): string[] {
37
+ return user.favoriteColors;
38
+ }
39
+ ```
40
+
41
+ This pattern can be used for the other types, with the exception of server types, which don't yet support lists.
42
+
43
+ ## Client-defined GraphQL Types
44
+
45
+ Resolvers can also model edges to other GraphQL types in your Resolver schema. If the type was defined as a "strong" type, the resolver function must return an object `{ id: DataID }` where `DataID` is the ID of the object. Relay will take care of invoking the type's model resolver function.
46
+
47
+ ```tsx
48
+ import {DataID} from 'relay-runtime';
49
+ /**
50
+ * @relayField Post.author: User
51
+ */
52
+ export function author(post: PostModel): { id: DataID } {
53
+ return { id: post.authorId };
54
+ }
55
+ ```
56
+
57
+ If the type was defined as `@weak`, the resolver function must return an object matching the type's model type.
58
+
59
+ ```tsx
60
+ /**
61
+ * @relayField User.profilePicture: ProfilePicture
62
+ */
63
+ export function profilePicture(user: UserModel): ProfilePicture {
64
+ return {
65
+ url: user.profilePicture.url,
66
+ width: user.profilePicture.width,
67
+ height: user.profilePicture.width,
68
+ }
69
+ }
70
+ ```
71
+
72
+ :::tip
73
+ Relay will emit type assertions in its generated code to help catch errors where a resolver implementation does not match whats declared in its docblock.
74
+ :::
75
+
76
+ ## Server Types
77
+
78
+ Relay Resolvers also support modeling edges to types defined on your server schema that implement the [`Node` specification](https://graphql.org/learn/global-object-identification/#node-root-field). Since objects which implement Node each have a globally unique ID, resolvers modeling edges to these server types simply need to return that unique ID.
79
+
80
+ At compile-time Relay derives a GraphQL query for each selections on this field and will lazily fetch that data on render.
81
+
82
+ ```tsx
83
+ import {DataID} from 'relay-runtime';
84
+ /**
85
+ * @relayField Post.author: User
86
+ */
87
+ export function author(post: PostModel): DataID {
88
+ return post.authorId;
89
+ }
90
+ ```
91
+
92
+ :::warning
93
+ Edges to server types that are only known the client force Relay to fetch data lazily which will force an additional cascading network roundtrip. This is generally not optimal and should be avoided where possible.
94
+ :::
95
+
96
+ To highlight this point, at compile time, Relay requires that selection that reads client to server edge field annotate the field with the `@waterfall` directive. This is intended to remind the author and reviewer that a tradeoff is being made here and to carefully consider the implications.
97
+
98
+ ```tsx
99
+ function Post() {
100
+ const data = useLazyLoadQuery(graphql`
101
+ query PostQuery {
102
+ post {
103
+ author @waterfall {
104
+ name
105
+ }
106
+ }
107
+ }`, {});
108
+ return <p>{data.post.author.name}</p>;
109
+ }
110
+ ```
111
+
112
+ ## Abstract Types
113
+
114
+ Resolvers may return some permutations of "abstract" types (GraphQL unions and interfaces). To use this feature simply use the abstract type's name in the docblock field description and include the typename in the object returned from your resolver. For "strong" types, that will look like: `{id: DataID, __typename: string}`. For "weak" types that will look like: `{__relay_model_instance: T, __typename: string}`.
115
+
116
+ ```tsx
117
+ import {DataID} from 'relay-runtime';
118
+
119
+ type AnimalTypenames = "Cat" | "Dog";
120
+ /**
121
+ * @relayField User.pet: Animal
122
+ */
123
+ export function pet(user: User): {id: DataID, __typename: AnimalTypenames } {
124
+ return {id: "5", __typename: "Dog" }
125
+ }
126
+ ```
127
+
128
+ :::tip
129
+ Relay will generate type assertions to ensure your resolver function returns the expected type. However, not all combinations are supported. For example, Relay does not yet support the following permutations of abstract types: Unions including weak types, abstract types which mix strong and weak types, and abstract types which include server-backed types.
130
+ :::
131
+
132
+ While abstract types themselves cannot be defined using Resolver syntax today, you may define interfaces and unions, as well as their members, using [Client Schema Extensions](../client-schema-extensions.mdx). For example:
133
+
134
+ ```graphql title="client-schema.graphql"
135
+ interface Animal {
136
+ legs: Int
137
+ }
138
+
139
+ extend type Cat implements Animal {
140
+ __do_not_use: String # Placeholder because GraphQL does not allow empty field sets.
141
+ }
142
+ ```
143
+
144
+ ## JavaScript Values
145
+
146
+ There are rare cases where you want to return an arbitrary JavaScript value from your Resolver schema, one which cannot not have a corresponding GraphQL type. As an escape hatch Relay supports a custom return type `RelayResolverValue` that allows you to return any JavaScript value from your resolver. **JavaScript values returned from resolvers should be immutable.**
147
+
148
+ Consumers of this field will see a TypeScript/Flow type that is derived from your resolver function's return type.
149
+
150
+ ```tsx
151
+ /**
152
+ * @relayField Post.publishDate: RelayResolverValue
153
+ */
154
+ export function metadata(post: PostModel): Date {
155
+ return post.publishDate;
156
+ }
157
+ ```
158
+
159
+ :::warning
160
+ Use of `RelayResolverValue` should be considered an "escape hatch" and may be deprecated in future versions of Relay. In most cases a preferable pattern is to define a custom scalar in your [client schema extensions](../client-schema-extensions.mdx) and add a type definition for that custom scalar in your Relay config.
161
+ :::
@@ -0,0 +1,41 @@
1
+ ---
2
+ id: suspense
3
+ title: "Suspense"
4
+ slug: /guides/relay-resolvers/suspense/
5
+ description: Handling loading states for live data
6
+ ---
7
+
8
+ With [Live Resolvers](./live-fields.mdx), it's possible that the data you are exposing in the graph may not be synchronously available. For example, if you are fetching data from a remote API, it may take some time for the data to be fetched. Relay Resolvers provide a mechanism for handling this loading state.
9
+
10
+ If a Live Resolver returns the "suspense sentinel" value, all consumers of that field will suspend until that field updates with a non-suspense value.
11
+
12
+ ## Suspense Sentinel
13
+
14
+ If a Live Resolver is in a loading state, it may return a special sentinel value to indicate that the data is not yet available.
15
+
16
+ ```ts
17
+ import {suspenseSentinel} from 'relay-runtime';
18
+
19
+ /**
20
+ * @relayField Query.myIp: String
21
+ * @live
22
+ */
23
+ export function myIp(): LiveState<string> {
24
+ return {
25
+ read: () => {
26
+ const state = store.getState();
27
+ const ipLoadObject = state.ip;
28
+ if (ipLoadObject.status === "LOADING") {
29
+ return suspenseSentinel();
30
+ }
31
+ return state.ip;
32
+ },
33
+ subscribe: (cb) => {
34
+ return store.subscribe(cb);
35
+ },
36
+ };
37
+ }
38
+ ```
39
+
40
+ :::note
41
+ If a query or fragment will suspend if it reads any resolver field that is in a suspended state, even if it reads that resolver field indirectly via another resolvers `@rootFragment`.
@@ -0,0 +1,240 @@
1
+ ---
2
+ id: required-directive
3
+ title: "@required Directive"
4
+ slug: /guides/required-directive/
5
+ description: Relay guide to @required
6
+ keywords:
7
+ - required
8
+ - directive
9
+ - optional
10
+ - nullthrows
11
+ ---
12
+
13
+ import DocsRating from '@site/src/core/DocsRating';
14
+
15
+ The `@required` directive can be added to fields in your Relay queries to declare how null values should be handled at runtime. You can think of it as saying "if this field is ever null, its parent field is invalid and should be null".
16
+
17
+ When you have a GraphQL schema where many fields are nullable, a considerable amount of product code is needed to handle each field's potential "nullness" before the underlying data can be used. With `@required`, Relay can handle some types of null checks before it returns data to your component, which means that **any field you annotate with** **`@required`** **will become non-nullable in the generated types for your response**.
18
+
19
+ If a `@required` field is null at runtime, Relay will "bubble" that nullness up to the field's parent. For example, given this query:
20
+
21
+ ```graphql
22
+ query MyQuery {
23
+ viewer {
24
+ name @required(action: LOG)
25
+ age
26
+ }
27
+ }
28
+ ```
29
+
30
+ If `name` is null, Relay would return `{ viewer: null }`. You can think of `@required` in this instance as saying "`viewer` is useless without a `name`".
31
+
32
+ ## Action
33
+
34
+ The `@required` directive has a required `action` argument which has four possible values:
35
+
36
+ ### `NONE` (expected)
37
+
38
+ This field is expected to be null sometimes. The field's nullability will "bubble up" to make the parent field null if this field is missing, allowing the component to check the parent field instead of this specific field.
39
+
40
+ ### `LOG` (recoverable)
41
+
42
+ This value is not expected to ever be null, but the component **can still render** if it is. If a field with `action: LOG` is null, the [Relay field logger](../api-reference/relay-runtime/field-logger.mdx) will receive a `missing_required_field.log` event. The field's nullability will "bubble up" to make the parent field null.
43
+
44
+ ### `THROW` (unrecoverable)
45
+
46
+ This value should not be null, and the component **cannot render without it**. If a field with `action: THROW` is null at runtime, the component which reads that field **will throw during render**. The error message includes both the owner and field path. Only use this option if your component is contained within an [error boundary](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary).
47
+
48
+ **Note**: If you have opted into the optional `disallow_required_action_throw_on_semantically_nullable_fields` [compiler feature flag](../../docs/getting-started/compiler-config.mdx), this can only be used on fields that are non-nullable (`!` in the schema) or are marked with `@semanticNonNull` (an optional [semantic nullability](./semantic-nullability.mdx) feature that indicates fields should never be null in normal operation).
49
+
50
+ ### `DANGEROUSLY_THROW_ON_SEMANTICALLY_NULLABLE_FIELD` (unrecoverable, bypass validation)
51
+
52
+ :::warning
53
+ This option is included mostly as a migration path for existing codebases that used `@required(action: THROW)` on nullable fields. It is strongly discouraged to add new usages of this option.
54
+ :::
55
+
56
+ This action behaves identically to `THROW` but can be used on nullable fields. This is only needed when the optional [compiler feature flag](https://relay.dev/docs/getting-started/compiler-config/#FeatureFlags) `disallow_required_action_throw_on_semantically_nullable_fields` is enabled, which prevents using THROW on semantically nullable fields.
57
+
58
+
59
+ ## Locality
60
+
61
+ A field's `@required` status is **local to the fragment where it is specified**. This allows you to add/remove the directive without having to think about anything outside the scope of your component.
62
+
63
+ This choice reflects the fact that some components may be able to recover better from missing data than others. For example, a `<RestaurantInfo />` component could probably render something sensible even if the restaurant's address is missing, but a `<RestaurantLocationMap />` component might not.
64
+
65
+ However, all usages of the `@required` directive on the same field in a single fragment must be consistent with their usage. This situation mostly occurs when selecting fields in inline fragments. For example, the following fragment would fail to compile:
66
+
67
+ ```graphql
68
+ fragment UserInfo on User {
69
+ job {
70
+ ... on Actor {
71
+ certifications
72
+ }
73
+ ... on Lawyer {
74
+ certifications @required(action: LOG)
75
+ }
76
+ }
77
+ }
78
+ ```
79
+
80
+ The Relay compiler will give you an error like `All references to a field must have matching @required declarations.`. To fix this, either set the `@required` directive on each of the fields selected in the inline fragment or remove the directive entirely.
81
+
82
+ ## Chaining
83
+
84
+ `@required` directives can be chained to make a deeply nested field accessible after just one null check:
85
+
86
+ ```javascript
87
+ const user = useFragment(graphql`
88
+ fragment MyUser on User {
89
+ name @required(action: LOG)
90
+ profile_picture @required(action: LOG) {
91
+ url @required(action: LOG)
92
+ }
93
+ }`, key);
94
+ if(user == null) {
95
+ return null;
96
+ }
97
+ return <img src={user.profile_picture.url} alt={user.name} />
98
+ ```
99
+
100
+ **Note**: If you use `@required` on a top level field of a fragment, the object returned from `useFragment` itself may become nullable. The generated types will reflect this.
101
+
102
+ When chaining `@required` directives, the Relay compiler will help you from unintentionally creating a chain with a more severe action than intended. Consider the following fragment
103
+
104
+ ```graphql
105
+ fragment MyUser on User {
106
+ profile_picture @required(action: THROW) {
107
+ url @required(action: LOG)
108
+ }
109
+ }
110
+ ```
111
+
112
+ In this example we want the component to THROW if the `profile_picture` field is null but we only want to LOG an error if the `url` field is null. But recall, Relay will "bubble" nullness up to the parent field, if the `url` field is null it will then cause the `profile_picture` field to become null as well. And once that happens, the component will THROW. If you implement a pattern like this, the Relay compiler will give you an error
113
+
114
+ ```
115
+ A @required field may not have an `action` less severe than that of its @required parent. This @required directive should probably have `action: LOG` so that it can match its parent
116
+ ```
117
+
118
+ To fix this, either change the `profile_picture` to use `action: LOG` or change the `url` field to use `action: THROW`.
119
+
120
+ ## Caveats with Connections
121
+
122
+ There are currently some limitations in using the `@required` and `@connection` directives together. When you use the `@connection` directive, Relay automatically inserts some additional fields into the connection, and those fields won't be generated with the `@required` directive. This can result in inconsistencies if you use the `@required` directive on fields in a Connection type. Consider the following example:
123
+
124
+ ```graphql
125
+ fragment FriendsList on User @refetchable(queryName: "FriendsListQuery") {
126
+ friends(after: $cursor, first: $count) @connection(key: "FriendsList_friends") {
127
+ edges {
128
+ node @required(action: LOG) {
129
+ job @required(action: LOG) {
130
+ title @required(action: LOG)
131
+ }
132
+ }
133
+ }
134
+ }
135
+ }
136
+ ```
137
+
138
+ Any usages of `@required` on the `node` field or any of its direct child fields will cause the Relay compiler to give you an error saying `All references to a field must have matching @required declarations.`. In order to bypass this you'll need to remove the `@required` directives on those fields.
139
+
140
+ In the above example, we'd need to remove the `@required` directives on both the `node` and `job` fields, but the usage on the `title` field would not create an error.
141
+
142
+ ```graphql
143
+ fragment FriendsList on User @refetchable(queryName: "FriendsListQuery") {
144
+ friends(after: $cursor, first: $count) @connection(key: "FriendsList_friends") {
145
+ edges {
146
+ node {
147
+ job {
148
+ title @required(action: LOG)
149
+ }
150
+ }
151
+ }
152
+ }
153
+ }
154
+ ```
155
+
156
+ ## FAQ
157
+
158
+ ### Why did @required make a non-nullable field/root nullable?
159
+
160
+ When using the `LOG` or `NONE` actions, Relay will "bubble" a missing field up to its parent field or fragment root. This means that adding `@required(action: LOG)` (for example) to a child of a non-nullable fragment root will cause the type of the fragment root to become nullable.
161
+
162
+ ### What happens if you use `@required` in a plural field
163
+
164
+ If a `@required(action: LOG)` field is missing in a plural field, the _item_ in the list will be returned as null. It will _not_ cause the entire array to become null.. If you have any question about how it will behave, you can inspect the generated Flow types.
165
+
166
+ ### Why are @required fields in an inline fragment still nullable?
167
+
168
+ Imagine a fragment like this:
169
+
170
+ ```graphql
171
+ fragment MyFrag on Actor {
172
+ ... on User {
173
+ name @required(action: THROW)
174
+ }
175
+ }
176
+ ```
177
+
178
+ It's possible that your `Actor` will not be a `User` and therefore not include a `name`. To represent that in types, we generate a Flow type that looks like this: `{name?: string}`.
179
+
180
+ If you encounter this issue, you can add a `__typename` like this:
181
+
182
+ ```graphql
183
+ fragment MyFrag on Actor {
184
+ __typename
185
+ ... on User {
186
+ name @required(action: THROW)
187
+ }
188
+ }
189
+ ```
190
+
191
+ In this situation Relay will generate a union type like: `{__typename: 'User', name: string} | {__typename: '%ignore this%}`. Now you can check the `__typename` field to narrow your object's type down to one that has a non-nullable `name`.
192
+
193
+ <FbInternalOnly>
194
+
195
+ Example diff showing the adoption of this strategy: D24370183
196
+
197
+ </FbInternalOnly>
198
+
199
+ ### Why not implement this at the schema/server level?
200
+
201
+ The "requiredness" of a field is actually a product decision and not a schema question. Therefore we need to implement the handling of it at the product level. Individual components need to be able to decide for themselves how to handle a missing value.
202
+
203
+ For example, if a notification is trying to show the price for a Marketplace listing, it could probably just omit the price and still render. If payment flow for that same listing is missing the price, it should probably blow up.
204
+
205
+ Another issue is that changes to the server schema are much more difficult to ship since they affect all existing clients across all platforms.
206
+
207
+ Basically every value returned by Relay is nullable. This is intentional since we want to be able to handle field-level errors whenever possible. If we lean into KillsParentOnException we would end up wanting to make basically every field use it and our apps would be becomes more brittle since errors which used to be small, become large.
208
+
209
+ <FbInternalOnly>
210
+
211
+ _Extracted from [this comment thread](https://fb.workplace.com/groups/cometeng/permalink/937671436726844/?comment_id=937681186725869)._
212
+ _Further discussion in [this comment thread](https://fb.workplace.com/groups/cometeng/permalink/937671436726844/?comment_id=938335873327067)._
213
+
214
+ </FbInternalOnly>
215
+
216
+ ### Can `(action: NONE)` be the default?
217
+
218
+ On one hand action: NONE makes the most sense as a default (omitted action == no action). However, we are aware that whichever value we choose as the default will be considered the default action for engineers to choose since it's the path of least resistance.
219
+
220
+ We actually believe that in most cases LOG is the most ideal choice. It gives the component a chance to gracefully recover while also giving us signal that a part of our app is rendering in a sub-optimal way.
221
+
222
+ We debated making LOG the default action for that reason, but I think that's confusing as well.
223
+
224
+ So, for now we are planning to not offer a default argument. After all, it's still much less to write out than the equivalent manual null checks. Once we see how people use it we will consider what value (if any) should be the default.
225
+
226
+ <FbInternalOnly>
227
+
228
+ ### Does @required change anything about the logger project field?
229
+
230
+ When using recoverableViolation or unrecoverableViolation, the second argument is the FBLogger project name ([defined on Comet here](https://fburl.com/diffusion/rn99dl4s)):
231
+
232
+ ```javascript
233
+ recoverableViolation('My error string', 'my_logger_project');
234
+ ```
235
+
236
+ When you switch to using `@required`, any `THROW` or `LOG` actions will log to the `relay-required` logger project instead ([see here in logview](https://fburl.com/logview/l40t7cjv)).
237
+
238
+ For most teams, this shouldn't be an issue; care has been taken to ensure tasks still get routed to the correct owner of the file that is using `@required`. However, if your team has any queries that utilize the logger project field, you may want to consider the implications.
239
+
240
+ </FbInternalOnly>