react-relay 11.0.2 → 13.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.md +47 -0
  2. package/ReactRelayContainerUtils.js.flow +1 -1
  3. package/ReactRelayContext.js +1 -1
  4. package/ReactRelayContext.js.flow +3 -4
  5. package/ReactRelayFragmentContainer.js.flow +25 -25
  6. package/ReactRelayFragmentMockRenderer.js.flow +2 -2
  7. package/ReactRelayLocalQueryRenderer.js.flow +7 -8
  8. package/ReactRelayPaginationContainer.js.flow +112 -59
  9. package/ReactRelayQueryFetcher.js.flow +10 -11
  10. package/ReactRelayQueryRenderer.js.flow +116 -82
  11. package/ReactRelayQueryRendererContext.js.flow +1 -1
  12. package/ReactRelayRefetchContainer.js.flow +42 -39
  13. package/ReactRelayTestMocker.js.flow +17 -15
  14. package/ReactRelayTypes.js.flow +11 -11
  15. package/RelayContext.js.flow +4 -4
  16. package/__flowtests__/ReactRelayFragmentContainer-flowtest.js.flow +2 -3
  17. package/__flowtests__/ReactRelayPaginationContainer-flowtest.js.flow +12 -8
  18. package/__flowtests__/ReactRelayRefetchContainer-flowtest.js.flow +11 -7
  19. package/__flowtests__/RelayModern-flowtest.js.flow +79 -47
  20. package/__flowtests__/RelayModernFlowtest_badref.graphql.js.flow +6 -5
  21. package/__flowtests__/RelayModernFlowtest_notref.graphql.js.flow +6 -5
  22. package/__flowtests__/RelayModernFlowtest_user.graphql.js.flow +5 -4
  23. package/__flowtests__/RelayModernFlowtest_users.graphql.js.flow +5 -4
  24. package/__flowtests__/__generated__/ReactRelayFragmentContainerFlowtest_viewer.graphql.js.flow +72 -0
  25. package/__flowtests__/__generated__/ReactRelayFragmentContainerFlowtest_viewer2.graphql.js.flow +72 -0
  26. package/__flowtests__/__generated__/ReactRelayPaginationContainerFlowtestQuery.graphql.js.flow +227 -0
  27. package/__flowtests__/__generated__/ReactRelayPaginationContainerFlowtest_viewer.graphql.js.flow +164 -0
  28. package/__flowtests__/__generated__/ReactRelayRefetchContainerFlowtestQuery.graphql.js.flow +227 -0
  29. package/__flowtests__/__generated__/ReactRelayRefetchContainerFlowtest_viewer.graphql.js.flow +164 -0
  30. package/__flowtests__/__generated__/RelayModernFlowtest_badref.graphql.js.flow +66 -0
  31. package/__flowtests__/__generated__/RelayModernFlowtest_notref.graphql.js.flow +66 -0
  32. package/__flowtests__/__generated__/RelayModernFlowtest_user.graphql.js.flow +59 -0
  33. package/__flowtests__/__generated__/RelayModernFlowtest_users.graphql.js.flow +61 -0
  34. package/assertFragmentMap.js.flow +3 -3
  35. package/buildReactRelayContainer.js.flow +12 -11
  36. package/getRootVariablesForFragments.js.flow +3 -5
  37. package/hooks.js +1 -1
  38. package/hooks.js.flow +6 -7
  39. package/index.js +1 -1
  40. package/index.js.flow +7 -8
  41. package/isRelayEnvironment.js.flow +1 -1
  42. package/jest-react/enqueueTask.js.flow +56 -0
  43. package/jest-react/index.js.flow +12 -0
  44. package/jest-react/internalAct.js.flow +138 -0
  45. package/legacy.js +1 -1
  46. package/legacy.js.flow +1 -1
  47. package/lib/ReactRelayContainerUtils.js +1 -1
  48. package/lib/ReactRelayContext.js +1 -1
  49. package/lib/ReactRelayFragmentContainer.js +22 -16
  50. package/lib/ReactRelayFragmentMockRenderer.js +3 -3
  51. package/lib/ReactRelayLocalQueryRenderer.js +8 -9
  52. package/lib/ReactRelayPaginationContainer.js +97 -39
  53. package/lib/ReactRelayQueryFetcher.js +3 -3
  54. package/lib/ReactRelayQueryRenderer.js +87 -54
  55. package/lib/ReactRelayQueryRendererContext.js +1 -1
  56. package/lib/ReactRelayRefetchContainer.js +39 -26
  57. package/lib/ReactRelayTestMocker.js +8 -9
  58. package/lib/ReactRelayTypes.js +1 -1
  59. package/lib/RelayContext.js +4 -3
  60. package/lib/assertFragmentMap.js +3 -2
  61. package/lib/buildReactRelayContainer.js +8 -8
  62. package/lib/getRootVariablesForFragments.js +2 -3
  63. package/lib/hooks.js +6 -6
  64. package/lib/index.js +8 -8
  65. package/lib/isRelayEnvironment.js +1 -1
  66. package/lib/jest-react/enqueueTask.js +53 -0
  67. package/lib/jest-react/index.js +13 -0
  68. package/lib/jest-react/internalAct.js +115 -0
  69. package/lib/legacy.js +1 -1
  70. package/lib/multi-actor/ActorChange.js +30 -0
  71. package/lib/multi-actor/index.js +11 -0
  72. package/lib/multi-actor/useRelayActorEnvironment.js +29 -0
  73. package/lib/readContext.js +1 -1
  74. package/lib/relay-hooks/EntryPointContainer.react.js +4 -4
  75. package/lib/relay-hooks/EntryPointTypes.flow.js +1 -1
  76. package/lib/relay-hooks/FragmentResource.js +342 -89
  77. package/lib/relay-hooks/InternalLogger.js +1 -1
  78. package/lib/relay-hooks/LRUCache.js +1 -1
  79. package/lib/relay-hooks/LazyLoadEntryPointContainer_DEPRECATED.react.js +5 -5
  80. package/lib/relay-hooks/MatchContainer.js +2 -2
  81. package/lib/relay-hooks/ProfilerContext.js +1 -1
  82. package/lib/relay-hooks/QueryResource.js +172 -29
  83. package/lib/relay-hooks/RelayEnvironmentProvider.js +6 -4
  84. package/lib/relay-hooks/SuspenseResource.js +130 -0
  85. package/lib/relay-hooks/loadEntryPoint.js +1 -1
  86. package/lib/relay-hooks/loadQuery.js +42 -20
  87. package/lib/relay-hooks/preloadQuery_DEPRECATED.js +25 -16
  88. package/lib/relay-hooks/prepareEntryPoint_DEPRECATED.js +1 -1
  89. package/lib/relay-hooks/useBlockingPaginationFragment.js +5 -6
  90. package/lib/relay-hooks/useEntryPointLoader.js +3 -3
  91. package/lib/relay-hooks/useFetchTrackingRef.js +3 -2
  92. package/lib/relay-hooks/useFragment.js +7 -7
  93. package/lib/relay-hooks/useFragmentNode.js +5 -5
  94. package/lib/relay-hooks/useIsMountedRef.js +1 -1
  95. package/lib/relay-hooks/useIsOperationNodeActive.js +3 -3
  96. package/lib/relay-hooks/useIsParentQueryActive.js +1 -1
  97. package/lib/relay-hooks/useLazyLoadQuery.js +4 -4
  98. package/lib/relay-hooks/useLazyLoadQueryNode.js +11 -5
  99. package/lib/relay-hooks/useLoadMoreFunction.js +9 -13
  100. package/lib/relay-hooks/useMemoOperationDescriptor.js +3 -3
  101. package/lib/relay-hooks/useMemoVariables.js +3 -3
  102. package/lib/relay-hooks/useMutation.js +18 -7
  103. package/lib/relay-hooks/usePaginationFragment.js +3 -4
  104. package/lib/relay-hooks/usePreloadedQuery.js +6 -6
  105. package/lib/relay-hooks/useQueryLoader.js +31 -11
  106. package/lib/relay-hooks/useRefetchableFragment.js +1 -1
  107. package/lib/relay-hooks/useRefetchableFragmentNode.js +14 -18
  108. package/lib/relay-hooks/useRelayEnvironment.js +3 -3
  109. package/lib/relay-hooks/useStaticFragmentNodeWarning.js +3 -3
  110. package/lib/relay-hooks/useSubscribeToInvalidationState.js +3 -2
  111. package/lib/relay-hooks/useSubscription.js +11 -8
  112. package/multi-actor/ActorChange.js.flow +58 -0
  113. package/multi-actor/index.js.flow +14 -0
  114. package/multi-actor/useRelayActorEnvironment.js.flow +49 -0
  115. package/package.json +3 -3
  116. package/react-relay-hooks.js +2 -2
  117. package/react-relay-hooks.min.js +2 -2
  118. package/react-relay-legacy.js +2 -2
  119. package/react-relay-legacy.min.js +2 -2
  120. package/react-relay.js +2 -2
  121. package/react-relay.min.js +2 -2
  122. package/readContext.js.flow +1 -1
  123. package/relay-hooks/EntryPointContainer.react.js.flow +9 -16
  124. package/relay-hooks/EntryPointTypes.flow.js.flow +25 -26
  125. package/relay-hooks/FragmentResource.js.flow +359 -93
  126. package/relay-hooks/InternalLogger.js.flow +1 -1
  127. package/relay-hooks/LRUCache.js.flow +1 -1
  128. package/relay-hooks/LazyLoadEntryPointContainer_DEPRECATED.react.js.flow +33 -47
  129. package/relay-hooks/MatchContainer.js.flow +4 -3
  130. package/relay-hooks/ProfilerContext.js.flow +1 -1
  131. package/relay-hooks/QueryResource.js.flow +217 -26
  132. package/relay-hooks/RelayEnvironmentProvider.js.flow +15 -5
  133. package/relay-hooks/SuspenseResource.js.flow +115 -0
  134. package/relay-hooks/__flowtests__/EntryPointTypes/EntryPointElementConfig-flowtest.js.flow +5 -4
  135. package/relay-hooks/__flowtests__/EntryPointTypes/NestedEntrypoints-flowtest.js.flow +2 -2
  136. package/relay-hooks/__flowtests__/__generated__/useFragmentFlowtest_user.graphql.js.flow +59 -0
  137. package/relay-hooks/__flowtests__/__generated__/useFragmentFlowtest_users.graphql.js.flow +61 -0
  138. package/relay-hooks/__flowtests__/useBlockingPaginationFragment-flowtest.js.flow +11 -10
  139. package/relay-hooks/__flowtests__/useFragment-flowtest.js.flow +55 -32
  140. package/relay-hooks/__flowtests__/usePaginationFragment-flowtest.js.flow +11 -10
  141. package/relay-hooks/__flowtests__/useRefetchableFragment-flowtest.js.flow +11 -10
  142. package/relay-hooks/__flowtests__/utils.js.flow +21 -32
  143. package/relay-hooks/loadEntryPoint.js.flow +7 -13
  144. package/relay-hooks/loadQuery.js.flow +50 -32
  145. package/relay-hooks/preloadQuery_DEPRECATED.js.flow +31 -22
  146. package/relay-hooks/prepareEntryPoint_DEPRECATED.js.flow +7 -13
  147. package/relay-hooks/useBlockingPaginationFragment.js.flow +14 -12
  148. package/relay-hooks/useEntryPointLoader.js.flow +8 -11
  149. package/relay-hooks/useFetchTrackingRef.js.flow +3 -3
  150. package/relay-hooks/useFragment.js.flow +31 -62
  151. package/relay-hooks/useFragmentNode.js.flow +6 -8
  152. package/relay-hooks/useIsMountedRef.js.flow +1 -1
  153. package/relay-hooks/useIsOperationNodeActive.js.flow +4 -6
  154. package/relay-hooks/useIsParentQueryActive.js.flow +4 -5
  155. package/relay-hooks/useLazyLoadQuery.js.flow +14 -16
  156. package/relay-hooks/useLazyLoadQueryNode.js.flow +20 -14
  157. package/relay-hooks/useLoadMoreFunction.js.flow +21 -30
  158. package/relay-hooks/useMemoOperationDescriptor.js.flow +6 -8
  159. package/relay-hooks/useMemoVariables.js.flow +7 -7
  160. package/relay-hooks/useMutation.js.flow +27 -27
  161. package/relay-hooks/usePaginationFragment.js.flow +39 -45
  162. package/relay-hooks/usePreloadedQuery.js.flow +14 -20
  163. package/relay-hooks/useQueryLoader.js.flow +42 -23
  164. package/relay-hooks/useRefetchableFragment.js.flow +8 -9
  165. package/relay-hooks/useRefetchableFragmentNode.js.flow +25 -33
  166. package/relay-hooks/useRelayEnvironment.js.flow +3 -5
  167. package/relay-hooks/useStaticFragmentNodeWarning.js.flow +3 -4
  168. package/relay-hooks/useSubscribeToInvalidationState.js.flow +4 -7
  169. package/relay-hooks/useSubscription.js.flow +21 -11
  170. package/lib/relay-hooks/getPaginationMetadata.js +0 -41
  171. package/lib/relay-hooks/getPaginationVariables.js +0 -67
  172. package/lib/relay-hooks/getRefetchMetadata.js +0 -36
  173. package/lib/relay-hooks/getValueAtPath.js +0 -51
  174. package/relay-hooks/getPaginationMetadata.js.flow +0 -74
  175. package/relay-hooks/getPaginationVariables.js.flow +0 -110
  176. package/relay-hooks/getRefetchMetadata.js.flow +0 -80
  177. package/relay-hooks/getValueAtPath.js.flow +0 -46
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
3
  *
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
@@ -13,36 +13,28 @@
13
13
 
14
14
  'use strict';
15
15
 
16
- const ProfilerContext = require('./ProfilerContext');
17
- const React = require('react');
16
+ import type {
17
+ EntryPoint,
18
+ EntryPointComponent,
19
+ EnvironmentProviderOptions,
20
+ IEnvironmentProvider,
21
+ } from './EntryPointTypes.flow';
18
22
 
19
23
  const preloadQuery_DEPRECATED = require('./preloadQuery_DEPRECATED');
24
+ const ProfilerContext = require('./ProfilerContext');
20
25
  const useRelayEnvironment = require('./useRelayEnvironment');
21
-
26
+ const React = require('react');
22
27
  const {useContext, useEffect, useMemo} = require('react');
23
28
  const {stableCopy} = require('relay-runtime');
24
29
 
25
30
  type PreloadedEntryPoint<TEntryPointComponent> = $ReadOnly<{|
26
- entryPoints: $PropertyType<
27
- React.ElementConfig<TEntryPointComponent>,
28
- 'entryPoints',
29
- >,
30
- extraProps: $PropertyType<
31
- React.ElementConfig<TEntryPointComponent>,
32
- 'extraProps',
33
- >,
31
+ entryPoints: React.ElementConfig<TEntryPointComponent>['entryPoints'],
32
+ extraProps: React.ElementConfig<TEntryPointComponent>['extraProps'],
34
33
  getComponent: () => TEntryPointComponent,
35
- queries: $PropertyType<React.ElementConfig<TEntryPointComponent>, 'queries'>,
34
+ queries: React.ElementConfig<TEntryPointComponent>['queries'],
36
35
  rootModuleID: string,
37
36
  |}>;
38
37
 
39
- import type {
40
- EntryPoint,
41
- EntryPointComponent,
42
- EnvironmentProviderOptions,
43
- IEnvironmentProvider,
44
- } from './EntryPointTypes.flow';
45
-
46
38
  type EntryPointContainerProps<
47
39
  TEntryPointParams,
48
40
  TPreloadedQueries,
@@ -100,12 +92,8 @@ function prepareEntryPoint<
100
92
  if (queries != null) {
101
93
  const queriesPropNames = Object.keys(queries);
102
94
  queriesPropNames.forEach(queryPropName => {
103
- const {
104
- environmentProviderOptions,
105
- options,
106
- parameters,
107
- variables,
108
- } = queries[queryPropName];
95
+ const {environmentProviderOptions, options, parameters, variables} =
96
+ queries[queryPropName];
109
97
 
110
98
  const environment = environmentProvider.getEnvironment(
111
99
  environmentProviderOptions,
@@ -128,10 +116,8 @@ function prepareEntryPoint<
128
116
  if (entryPointDescription == null) {
129
117
  return;
130
118
  }
131
- const {
132
- entryPoint: nestedEntryPoint,
133
- entryPointParams: nestedParams,
134
- } = entryPointDescription;
119
+ const {entryPoint: nestedEntryPoint, entryPointParams: nestedParams} =
120
+ entryPointDescription;
135
121
  preloadedEntryPoints[entryPointPropName] = prepareEntryPoint(
136
122
  environmentProvider,
137
123
  nestedEntryPoint,
@@ -180,23 +166,23 @@ function LazyLoadEntryPointContainer_DEPRECATED<
180
166
  // *must* be computed first to fetch the component's data-dependencies in
181
167
  // parallel with the component itself (the code).
182
168
  const entryPointParamsHash = stableStringify(entryPointParams);
183
- const {
184
- getComponent,
185
- queries,
186
- entryPoints,
187
- extraProps,
188
- rootModuleID,
189
- } = useMemo(() => {
190
- return prepareEntryPoint(
191
- environmentProvider ?? {
192
- getEnvironment: () => environment,
193
- },
194
- entryPoint,
195
- entryPointParams,
196
- );
197
- // NOTE: stableParams encodes the information from params
198
- // eslint-disable-next-line react-hooks/exhaustive-deps
199
- }, [environment, environmentProvider, getPreloadProps, entryPointParamsHash]);
169
+ const {getComponent, queries, entryPoints, extraProps, rootModuleID} =
170
+ useMemo(() => {
171
+ return prepareEntryPoint(
172
+ environmentProvider ?? {
173
+ getEnvironment: () => environment,
174
+ },
175
+ entryPoint,
176
+ entryPointParams,
177
+ );
178
+ // NOTE: stableParams encodes the information from params
179
+ // eslint-disable-next-line react-hooks/exhaustive-deps
180
+ }, [
181
+ environment,
182
+ environmentProvider,
183
+ getPreloadProps,
184
+ entryPointParamsHash,
185
+ ]);
200
186
  const Component = useMemo(() => {
201
187
  return getComponent();
202
188
  }, [getComponent]);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
3
  *
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
@@ -14,6 +14,7 @@
14
14
  'use strict';
15
15
 
16
16
  const React = require('react');
17
+
17
18
  const {useMemo} = React;
18
19
 
19
20
  /**
@@ -90,7 +91,7 @@ type TypenameOnlyPointer = {|+__typename: string|};
90
91
  export type MatchPointer = {
91
92
  +__fragmentPropName?: ?string,
92
93
  +__module_component?: mixed,
93
- +$fragmentRefs: mixed,
94
+ +$fragmentSpreads: mixed,
94
95
  ...
95
96
  };
96
97
 
@@ -115,7 +116,7 @@ function MatchContainer<TProps: {...}, TFallback: React.Node | null>({
115
116
  'MatchContainer: Expected `match` value to be an object or null/undefined.',
116
117
  );
117
118
  }
118
- // NOTE: the MatchPointer type has a $fragmentRefs field to ensure that only
119
+ // NOTE: the MatchPointer type has a $fragmentSpreads field to ensure that only
119
120
  // an object that contains a FragmentSpread can be passed. If the fragment
120
121
  // spread matches, then the metadata fields below (__id, __fragments, etc.)
121
122
  // will be present. But they can be missing if all the fragment spreads use
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
3
  *
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
3
  *
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
@@ -13,18 +13,7 @@
13
13
 
14
14
  'use strict';
15
15
 
16
- const LRUCache = require('./LRUCache');
17
-
18
- const invariant = require('invariant');
19
-
20
- const {isPromise} = require('relay-runtime');
21
-
22
- const CACHE_CAPACITY = 1000;
23
-
24
- const DEFAULT_FETCH_POLICY = 'store-or-network';
25
-
26
- const DATA_RETENTION_TIMEOUT = 5 * 60 * 1000;
27
-
16
+ import type {Cache} from './LRUCache';
28
17
  import type {
29
18
  Disposable,
30
19
  FetchPolicy,
@@ -32,13 +21,22 @@ import type {
32
21
  IEnvironment,
33
22
  Observable,
34
23
  Observer,
24
+ OperationAvailability,
35
25
  OperationDescriptor,
36
26
  ReaderFragment,
37
27
  RenderPolicy,
38
28
  Snapshot,
39
29
  Subscription,
40
30
  } from 'relay-runtime';
41
- import type {Cache} from './LRUCache';
31
+
32
+ const LRUCache = require('./LRUCache');
33
+ const SuspenseResource = require('./SuspenseResource');
34
+ const invariant = require('invariant');
35
+ const {RelayFeatureFlags, isPromise} = require('relay-runtime');
36
+ const warning = require('warning');
37
+
38
+ const CACHE_CAPACITY = 1000;
39
+ const DEFAULT_FETCH_POLICY = 'store-or-network';
42
40
 
43
41
  export type QueryResource = QueryResourceImpl;
44
42
 
@@ -46,6 +44,12 @@ type QueryResourceCache = Cache<QueryResourceCacheEntry>;
46
44
  type QueryResourceCacheEntry = {|
47
45
  +id: number,
48
46
  +cacheIdentifier: string,
47
+ +operationAvailability: ?OperationAvailability,
48
+ // The number of received payloads for the operation.
49
+ // We want to differentiate the initial graphql response for the operation
50
+ // from the incremental responses, so later we can choose how to handle errors
51
+ // in the incremental payloads.
52
+ processedPayloadsCount: number,
49
53
  getRetainCount(): number,
50
54
  getNetworkSubscription(): ?Subscription,
51
55
  setNetworkSubscription(?Subscription): void,
@@ -53,8 +57,9 @@ type QueryResourceCacheEntry = {|
53
57
  setValue(Error | Promise<void> | QueryResult): void,
54
58
  temporaryRetain(environment: IEnvironment): Disposable,
55
59
  permanentRetain(environment: IEnvironment): Disposable,
60
+ releaseTemporaryRetain(): void,
56
61
  |};
57
- opaque type QueryResult: {
62
+ export opaque type QueryResult: {
58
63
  fragmentNode: ReaderFragment,
59
64
  fragmentRef: mixed,
60
65
  ...
@@ -71,6 +76,10 @@ interface IMap<K, V> {
71
76
  set(key: K, value: V): IMap<K, V>;
72
77
  }
73
78
 
79
+ function operationIsLiveQuery(operation: OperationDescriptor): boolean {
80
+ return operation.request.node.params.metadata.live !== undefined;
81
+ }
82
+
74
83
  function getQueryCacheIdentifier(
75
84
  environment: IEnvironment,
76
85
  operation: OperationDescriptor,
@@ -112,10 +121,113 @@ let nextID = 200000;
112
121
  function createCacheEntry(
113
122
  cacheIdentifier: string,
114
123
  operation: OperationDescriptor,
124
+ operationAvailability: ?OperationAvailability,
125
+ value: Error | Promise<void> | QueryResult,
126
+ networkSubscription: ?Subscription,
127
+ onDispose: QueryResourceCacheEntry => void,
128
+ ): QueryResourceCacheEntry {
129
+ // There should be no behavior difference between createCacheEntry_new and
130
+ // createCacheEntry_old, and it doesn't directly relate to Client Edges.
131
+ // It was just a refactoring that was needed for Client Edges but that
132
+ // is behind the feature flag just in case there is any accidental breakage.
133
+ if (RelayFeatureFlags.REFACTOR_SUSPENSE_RESOURCE) {
134
+ return createCacheEntry_new(
135
+ cacheIdentifier,
136
+ operation,
137
+ operationAvailability,
138
+ value,
139
+ networkSubscription,
140
+ onDispose,
141
+ );
142
+ } else {
143
+ return createCacheEntry_old(
144
+ cacheIdentifier,
145
+ operation,
146
+ operationAvailability,
147
+ value,
148
+ networkSubscription,
149
+ onDispose,
150
+ );
151
+ }
152
+ }
153
+
154
+ function createCacheEntry_new(
155
+ cacheIdentifier: string,
156
+ operation: OperationDescriptor,
157
+ operationAvailability: ?OperationAvailability,
158
+ value: Error | Promise<void> | QueryResult,
159
+ networkSubscription: ?Subscription,
160
+ onDispose: QueryResourceCacheEntry => void,
161
+ ): QueryResourceCacheEntry {
162
+ const isLiveQuery = operationIsLiveQuery(operation);
163
+
164
+ let currentValue: Error | Promise<void> | QueryResult = value;
165
+ let currentNetworkSubscription: ?Subscription = networkSubscription;
166
+
167
+ const suspenseResource = new SuspenseResource(environment => {
168
+ const retention = environment.retain(operation);
169
+ return {
170
+ dispose: () => {
171
+ // Normally if this entry never commits, the request would've ended by the
172
+ // time this timeout expires and the temporary retain is released. However,
173
+ // we need to do this for live queries which remain open indefinitely.
174
+ if (isLiveQuery && currentNetworkSubscription != null) {
175
+ currentNetworkSubscription.unsubscribe();
176
+ }
177
+ retention.dispose();
178
+ onDispose(cacheEntry);
179
+ },
180
+ };
181
+ });
182
+
183
+ const cacheEntry = {
184
+ cacheIdentifier,
185
+ id: nextID++,
186
+ processedPayloadsCount: 0,
187
+ operationAvailability,
188
+ getValue() {
189
+ return currentValue;
190
+ },
191
+ setValue(val: QueryResult | Promise<void> | Error) {
192
+ currentValue = val;
193
+ },
194
+ getRetainCount() {
195
+ return suspenseResource.getRetainCount();
196
+ },
197
+ getNetworkSubscription() {
198
+ return currentNetworkSubscription;
199
+ },
200
+ setNetworkSubscription(subscription: ?Subscription) {
201
+ if (isLiveQuery && currentNetworkSubscription != null) {
202
+ currentNetworkSubscription.unsubscribe();
203
+ }
204
+ currentNetworkSubscription = subscription;
205
+ },
206
+ temporaryRetain(environment: IEnvironment): Disposable {
207
+ return suspenseResource.temporaryRetain(environment);
208
+ },
209
+ permanentRetain(environment: IEnvironment): Disposable {
210
+ return suspenseResource.permanentRetain(environment);
211
+ },
212
+ releaseTemporaryRetain() {
213
+ suspenseResource.releaseTemporaryRetain();
214
+ },
215
+ };
216
+
217
+ return cacheEntry;
218
+ }
219
+
220
+ const DATA_RETENTION_TIMEOUT = 5 * 60 * 1000;
221
+ function createCacheEntry_old(
222
+ cacheIdentifier: string,
223
+ operation: OperationDescriptor,
224
+ operationAvailability: ?OperationAvailability,
115
225
  value: Error | Promise<void> | QueryResult,
116
226
  networkSubscription: ?Subscription,
117
227
  onDispose: QueryResourceCacheEntry => void,
118
228
  ): QueryResourceCacheEntry {
229
+ const isLiveQuery = operationIsLiveQuery(operation);
230
+
119
231
  let currentValue: Error | Promise<void> | QueryResult = value;
120
232
  let retainCount = 0;
121
233
  let retainDisposable: ?Disposable = null;
@@ -147,10 +259,12 @@ function createCacheEntry(
147
259
  const cacheEntry = {
148
260
  cacheIdentifier,
149
261
  id: nextID++,
262
+ processedPayloadsCount: 0,
263
+ operationAvailability,
150
264
  getValue() {
151
265
  return currentValue;
152
266
  },
153
- setValue(val) {
267
+ setValue(val: QueryResult | Promise<void> | Error) {
154
268
  currentValue = val;
155
269
  },
156
270
  getRetainCount() {
@@ -160,7 +274,7 @@ function createCacheEntry(
160
274
  return currentNetworkSubscription;
161
275
  },
162
276
  setNetworkSubscription(subscription: ?Subscription) {
163
- if (currentNetworkSubscription != null) {
277
+ if (isLiveQuery && currentNetworkSubscription != null) {
164
278
  currentNetworkSubscription.unsubscribe();
165
279
  }
166
280
  currentNetworkSubscription = subscription;
@@ -188,7 +302,11 @@ function createCacheEntry(
188
302
  // Normally if this entry never commits, the request would've ended by the
189
303
  // time this timeout expires and the temporary retain is released. However,
190
304
  // we need to do this for live queries which remain open indefinitely.
191
- if (retainCount <= 0 && currentNetworkSubscription != null) {
305
+ if (
306
+ isLiveQuery &&
307
+ retainCount <= 0 &&
308
+ currentNetworkSubscription != null
309
+ ) {
192
310
  currentNetworkSubscription.unsubscribe();
193
311
  }
194
312
  };
@@ -225,12 +343,22 @@ function createCacheEntry(
225
343
  return {
226
344
  dispose: () => {
227
345
  disposable.dispose();
228
- if (retainCount <= 0 && currentNetworkSubscription != null) {
346
+ if (
347
+ isLiveQuery &&
348
+ retainCount <= 0 &&
349
+ currentNetworkSubscription != null
350
+ ) {
229
351
  currentNetworkSubscription.unsubscribe();
230
352
  }
231
353
  },
232
354
  };
233
355
  },
356
+ releaseTemporaryRetain() {
357
+ if (releaseTemporaryRetain != null) {
358
+ releaseTemporaryRetain();
359
+ releaseTemporaryRetain = null;
360
+ }
361
+ },
234
362
  };
235
363
 
236
364
  return cacheEntry;
@@ -295,6 +423,7 @@ class QueryResourceImpl {
295
423
  // it's available
296
424
  let cacheEntry = this._cache.get(cacheIdentifier);
297
425
  let temporaryRetainDisposable: ?Disposable = null;
426
+ const entryWasCached = cacheEntry != null;
298
427
  if (cacheEntry == null) {
299
428
  // 2. If a cached value isn't available, try fetching the operation.
300
429
  // _fetchAndSaveQuery will update the cache with either a Promise or
@@ -332,7 +461,18 @@ class QueryResourceImpl {
332
461
  temporaryRetainDisposable = cacheEntry.temporaryRetain(environment);
333
462
 
334
463
  const cachedValue = cacheEntry.getValue();
335
- if (isPromise(cachedValue) || cachedValue instanceof Error) {
464
+ if (isPromise(cachedValue)) {
465
+ environment.__log({
466
+ name: 'suspense.query',
467
+ fetchPolicy,
468
+ isPromiseCached: entryWasCached,
469
+ operation: operation,
470
+ queryAvailability: cacheEntry.operationAvailability,
471
+ renderPolicy,
472
+ });
473
+ throw cachedValue;
474
+ }
475
+ if (cachedValue instanceof Error) {
336
476
  throw cachedValue;
337
477
  }
338
478
  return cachedValue;
@@ -349,6 +489,7 @@ class QueryResourceImpl {
349
489
  const cacheEntry = this._getOrCreateCacheEntry(
350
490
  cacheIdentifier,
351
491
  operation,
492
+ null,
352
493
  queryResult,
353
494
  null,
354
495
  );
@@ -366,6 +507,13 @@ class QueryResourceImpl {
366
507
  };
367
508
  }
368
509
 
510
+ releaseTemporaryRetain(queryResult: QueryResult) {
511
+ const cacheEntry = this._cache.get(queryResult.cacheIdentifier);
512
+ if (cacheEntry != null) {
513
+ cacheEntry.releaseTemporaryRetain();
514
+ }
515
+ }
516
+
369
517
  TESTS_ONLY__getCacheEntry(
370
518
  operation: OperationDescriptor,
371
519
  maybeFetchPolicy?: ?FetchPolicy,
@@ -384,14 +532,21 @@ class QueryResourceImpl {
384
532
  }
385
533
 
386
534
  _clearCacheEntry = (cacheEntry: QueryResourceCacheEntry): void => {
387
- if (cacheEntry.getRetainCount() <= 0) {
535
+ // The new code does this retainCount <= 0 check within SuspenseResource
536
+ // before calling _clearCacheEntry, whereas with the old code we do it here.
537
+ if (RelayFeatureFlags.REFACTOR_SUSPENSE_RESOURCE) {
388
538
  this._cache.delete(cacheEntry.cacheIdentifier);
539
+ } else {
540
+ if (cacheEntry.getRetainCount() <= 0) {
541
+ this._cache.delete(cacheEntry.cacheIdentifier);
542
+ }
389
543
  }
390
544
  };
391
545
 
392
546
  _getOrCreateCacheEntry(
393
547
  cacheIdentifier: string,
394
548
  operation: OperationDescriptor,
549
+ operationAvailability: ?OperationAvailability,
395
550
  value: Error | Promise<void> | QueryResult,
396
551
  networkSubscription: ?Subscription,
397
552
  ): QueryResourceCacheEntry {
@@ -400,6 +555,7 @@ class QueryResourceImpl {
400
555
  cacheEntry = createCacheEntry(
401
556
  cacheIdentifier,
402
557
  operation,
558
+ operationAvailability,
403
559
  value,
404
560
  networkSubscription,
405
561
  this._clearCacheEntry,
@@ -466,6 +622,7 @@ class QueryResourceImpl {
466
622
  const cacheEntry = createCacheEntry(
467
623
  cacheIdentifier,
468
624
  operation,
625
+ queryAvailability,
469
626
  queryResult,
470
627
  null,
471
628
  this._clearCacheEntry,
@@ -483,32 +640,65 @@ class QueryResourceImpl {
483
640
  if (cacheEntry) {
484
641
  cacheEntry.setNetworkSubscription(networkSubscription);
485
642
  }
486
-
487
643
  const observerStart = observer?.start;
488
- observerStart && observerStart(subscription);
644
+ if (observerStart) {
645
+ const subscriptionWithConditionalCancelation = {
646
+ ...subscription,
647
+ unsubscribe: () => {
648
+ // Only live queries should have their network requests canceled.
649
+ if (operationIsLiveQuery(operation)) {
650
+ subscription.unsubscribe();
651
+ }
652
+ },
653
+ };
654
+ observerStart(subscriptionWithConditionalCancelation);
655
+ }
489
656
  },
490
657
  next: () => {
491
- const snapshot = environment.lookup(operation.fragment);
492
658
  const cacheEntry = this._getOrCreateCacheEntry(
493
659
  cacheIdentifier,
494
660
  operation,
661
+ queryAvailability,
495
662
  queryResult,
496
663
  networkSubscription,
497
664
  );
665
+ cacheEntry.processedPayloadsCount += 1;
498
666
  cacheEntry.setValue(queryResult);
499
667
  resolveNetworkPromise();
500
668
 
501
669
  const observerNext = observer?.next;
502
- observerNext && observerNext(snapshot);
670
+ if (observerNext != null) {
671
+ const snapshot = environment.lookup(operation.fragment);
672
+ observerNext(snapshot);
673
+ }
503
674
  },
504
675
  error: error => {
505
676
  const cacheEntry = this._getOrCreateCacheEntry(
506
677
  cacheIdentifier,
507
678
  operation,
679
+ queryAvailability,
508
680
  error,
509
681
  networkSubscription,
510
682
  );
511
- cacheEntry.setValue(error);
683
+
684
+ // If, this is the first thing we receive for the query,
685
+ // before any other payload handled is error, we will cache and
686
+ // re-throw that error later.
687
+
688
+ // We will ignore errors for any incremental payloads we receive.
689
+ if (cacheEntry.processedPayloadsCount === 0) {
690
+ cacheEntry.setValue(error);
691
+ } else {
692
+ // TODO:T92030819 Remove this warning and actually throw the network error
693
+ // To complete this task we need to have a way of precisely tracking suspendable points
694
+ warning(
695
+ false,
696
+ 'QueryResource: An incremental payload for query `%` returned an error: `%`:`%`.',
697
+ operation.fragment.node.name,
698
+ error.message,
699
+ error.stack,
700
+ );
701
+ }
512
702
  resolveNetworkPromise();
513
703
 
514
704
  networkSubscription = null;
@@ -543,6 +733,7 @@ class QueryResourceImpl {
543
733
  cacheEntry = createCacheEntry(
544
734
  cacheIdentifier,
545
735
  operation,
736
+ queryAvailability,
546
737
  networkPromise,
547
738
  networkSubscription,
548
739
  this._clearCacheEntry,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
3
  *
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
@@ -13,21 +13,31 @@
13
13
 
14
14
  'use strict';
15
15
 
16
+ import type {IEnvironment} from 'relay-runtime';
17
+ import type {
18
+ ActorIdentifier,
19
+ IActorEnvironment,
20
+ } from 'relay-runtime/multi-actor-environment';
21
+
16
22
  const React = require('react');
17
23
  const ReactRelayContext = require('react-relay/ReactRelayContext');
18
24
 
19
- import type {IEnvironment} from 'relay-runtime';
20
-
21
25
  const {useMemo} = React;
22
26
 
23
27
  type Props = $ReadOnly<{|
24
28
  children: React.Node,
25
29
  environment: IEnvironment,
30
+ getEnvironmentForActor?: ?(
31
+ actorIdentifier: ActorIdentifier,
32
+ ) => IActorEnvironment,
26
33
  |}>;
27
34
 
28
35
  function RelayEnvironmentProvider(props: Props): React.Node {
29
- const {children, environment} = props;
30
- const context = useMemo(() => ({environment}), [environment]);
36
+ const {children, environment, getEnvironmentForActor} = props;
37
+ const context = useMemo(
38
+ () => ({environment, getEnvironmentForActor}),
39
+ [environment, getEnvironmentForActor],
40
+ );
31
41
  return (
32
42
  <ReactRelayContext.Provider value={context}>
33
43
  {children}