react-relay 11.0.1 → 13.0.0-rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (150) hide show
  1. package/ReactRelayContext.js +1 -1
  2. package/ReactRelayContext.js.flow +2 -3
  3. package/ReactRelayFragmentContainer.js.flow +24 -24
  4. package/ReactRelayFragmentMockRenderer.js.flow +1 -1
  5. package/ReactRelayLocalQueryRenderer.js.flow +6 -7
  6. package/ReactRelayPaginationContainer.js.flow +111 -58
  7. package/ReactRelayQueryFetcher.js.flow +9 -10
  8. package/ReactRelayQueryRenderer.js.flow +115 -81
  9. package/ReactRelayRefetchContainer.js.flow +41 -38
  10. package/ReactRelayTestMocker.js.flow +16 -14
  11. package/ReactRelayTypes.js.flow +10 -10
  12. package/RelayContext.js.flow +3 -3
  13. package/__flowtests__/ReactRelayFragmentContainer-flowtest.js.flow +1 -2
  14. package/__flowtests__/ReactRelayPaginationContainer-flowtest.js.flow +11 -7
  15. package/__flowtests__/ReactRelayRefetchContainer-flowtest.js.flow +10 -6
  16. package/__flowtests__/RelayModern-flowtest.js.flow +78 -46
  17. package/__flowtests__/RelayModernFlowtest_badref.graphql.js.flow +5 -4
  18. package/__flowtests__/RelayModernFlowtest_notref.graphql.js.flow +5 -4
  19. package/__flowtests__/RelayModernFlowtest_user.graphql.js.flow +4 -3
  20. package/__flowtests__/RelayModernFlowtest_users.graphql.js.flow +4 -3
  21. package/__flowtests__/__generated__/ReactRelayFragmentContainerFlowtest_viewer.graphql.js.flow +72 -0
  22. package/__flowtests__/__generated__/ReactRelayFragmentContainerFlowtest_viewer2.graphql.js.flow +72 -0
  23. package/__flowtests__/__generated__/ReactRelayPaginationContainerFlowtestQuery.graphql.js.flow +227 -0
  24. package/__flowtests__/__generated__/ReactRelayPaginationContainerFlowtest_viewer.graphql.js.flow +164 -0
  25. package/__flowtests__/__generated__/ReactRelayRefetchContainerFlowtestQuery.graphql.js.flow +227 -0
  26. package/__flowtests__/__generated__/ReactRelayRefetchContainerFlowtest_viewer.graphql.js.flow +164 -0
  27. package/__flowtests__/__generated__/RelayModernFlowtest_badref.graphql.js.flow +66 -0
  28. package/__flowtests__/__generated__/RelayModernFlowtest_notref.graphql.js.flow +66 -0
  29. package/__flowtests__/__generated__/RelayModernFlowtest_user.graphql.js.flow +59 -0
  30. package/__flowtests__/__generated__/RelayModernFlowtest_users.graphql.js.flow +61 -0
  31. package/assertFragmentMap.js.flow +2 -2
  32. package/buildReactRelayContainer.js.flow +15 -12
  33. package/getRootVariablesForFragments.js.flow +2 -4
  34. package/hooks.js +1 -1
  35. package/hooks.js.flow +5 -6
  36. package/index.js +1 -1
  37. package/index.js.flow +6 -7
  38. package/jest-react/enqueueTask.js.flow +56 -0
  39. package/jest-react/index.js.flow +12 -0
  40. package/jest-react/internalAct.js.flow +139 -0
  41. package/legacy.js +1 -1
  42. package/lib/ReactRelayFragmentContainer.js +21 -15
  43. package/lib/ReactRelayFragmentMockRenderer.js +2 -2
  44. package/lib/ReactRelayLocalQueryRenderer.js +7 -8
  45. package/lib/ReactRelayPaginationContainer.js +96 -38
  46. package/lib/ReactRelayQueryFetcher.js +3 -3
  47. package/lib/ReactRelayQueryRenderer.js +86 -53
  48. package/lib/ReactRelayRefetchContainer.js +38 -25
  49. package/lib/ReactRelayTestMocker.js +8 -9
  50. package/lib/RelayContext.js +3 -2
  51. package/lib/assertFragmentMap.js +3 -2
  52. package/lib/buildReactRelayContainer.js +14 -11
  53. package/lib/getRootVariablesForFragments.js +1 -2
  54. package/lib/hooks.js +5 -5
  55. package/lib/index.js +7 -7
  56. package/lib/jest-react/enqueueTask.js +53 -0
  57. package/lib/jest-react/index.js +13 -0
  58. package/lib/jest-react/internalAct.js +116 -0
  59. package/lib/multi-actor/ActorChange.js +30 -0
  60. package/lib/multi-actor/index.js +11 -0
  61. package/lib/multi-actor/useRelayActorEnvironment.js +29 -0
  62. package/lib/relay-hooks/EntryPointContainer.react.js +3 -3
  63. package/lib/relay-hooks/FragmentResource.js +347 -92
  64. package/lib/relay-hooks/LRUCache.js +1 -1
  65. package/lib/relay-hooks/LazyLoadEntryPointContainer_DEPRECATED.react.js +4 -4
  66. package/lib/relay-hooks/MatchContainer.js +1 -1
  67. package/lib/relay-hooks/QueryResource.js +172 -29
  68. package/lib/relay-hooks/RelayEnvironmentProvider.js +5 -3
  69. package/lib/relay-hooks/SuspenseResource.js +130 -0
  70. package/lib/relay-hooks/loadQuery.js +42 -20
  71. package/lib/relay-hooks/preloadQuery_DEPRECATED.js +24 -15
  72. package/lib/relay-hooks/useBlockingPaginationFragment.js +4 -5
  73. package/lib/relay-hooks/useEntryPointLoader.js +2 -2
  74. package/lib/relay-hooks/useFetchTrackingRef.js +2 -1
  75. package/lib/relay-hooks/useFragment.js +8 -7
  76. package/lib/relay-hooks/useFragmentNode.js +4 -4
  77. package/lib/relay-hooks/useIsOperationNodeActive.js +3 -3
  78. package/lib/relay-hooks/useLazyLoadQuery.js +3 -3
  79. package/lib/relay-hooks/useLazyLoadQueryNode.js +10 -4
  80. package/lib/relay-hooks/useLoadMoreFunction.js +8 -12
  81. package/lib/relay-hooks/useMemoOperationDescriptor.js +2 -2
  82. package/lib/relay-hooks/useMemoVariables.js +2 -2
  83. package/lib/relay-hooks/useMutation.js +17 -6
  84. package/lib/relay-hooks/usePaginationFragment.js +2 -3
  85. package/lib/relay-hooks/usePreloadedQuery.js +8 -7
  86. package/lib/relay-hooks/useQueryLoader.js +30 -10
  87. package/lib/relay-hooks/useRefetchableFragmentNode.js +13 -17
  88. package/lib/relay-hooks/useRelayEnvironment.js +3 -3
  89. package/lib/relay-hooks/useStaticFragmentNodeWarning.js +2 -2
  90. package/lib/relay-hooks/useSubscribeToInvalidationState.js +2 -1
  91. package/lib/relay-hooks/useSubscription.js +10 -7
  92. package/multi-actor/ActorChange.js.flow +58 -0
  93. package/multi-actor/index.js.flow +14 -0
  94. package/multi-actor/useRelayActorEnvironment.js.flow +49 -0
  95. package/package.json +3 -2
  96. package/react-relay-hooks.js +2 -2
  97. package/react-relay-hooks.min.js +2 -2
  98. package/react-relay-legacy.js +2 -2
  99. package/react-relay-legacy.min.js +2 -2
  100. package/react-relay.js +2 -2
  101. package/react-relay.min.js +2 -2
  102. package/relay-hooks/EntryPointContainer.react.js.flow +8 -15
  103. package/relay-hooks/EntryPointTypes.flow.js.flow +24 -25
  104. package/relay-hooks/FragmentResource.js.flow +368 -94
  105. package/relay-hooks/LazyLoadEntryPointContainer_DEPRECATED.react.js.flow +32 -46
  106. package/relay-hooks/MatchContainer.js.flow +3 -2
  107. package/relay-hooks/QueryResource.js.flow +216 -25
  108. package/relay-hooks/RelayEnvironmentProvider.js.flow +14 -4
  109. package/relay-hooks/SuspenseResource.js.flow +115 -0
  110. package/relay-hooks/__flowtests__/EntryPointTypes/EntryPointElementConfig-flowtest.js.flow +4 -3
  111. package/relay-hooks/__flowtests__/EntryPointTypes/NestedEntrypoints-flowtest.js.flow +1 -1
  112. package/relay-hooks/__flowtests__/useBlockingPaginationFragment-flowtest.js.flow +10 -9
  113. package/relay-hooks/__flowtests__/useFragment-flowtest.js.flow +8 -7
  114. package/relay-hooks/__flowtests__/usePaginationFragment-flowtest.js.flow +10 -9
  115. package/relay-hooks/__flowtests__/useRefetchableFragment-flowtest.js.flow +10 -9
  116. package/relay-hooks/__flowtests__/utils.js.flow +8 -12
  117. package/relay-hooks/loadEntryPoint.js.flow +6 -12
  118. package/relay-hooks/loadQuery.js.flow +49 -31
  119. package/relay-hooks/preloadQuery_DEPRECATED.js.flow +30 -21
  120. package/relay-hooks/prepareEntryPoint_DEPRECATED.js.flow +6 -12
  121. package/relay-hooks/useBlockingPaginationFragment.js.flow +13 -11
  122. package/relay-hooks/useEntryPointLoader.js.flow +7 -10
  123. package/relay-hooks/useFetchTrackingRef.js.flow +2 -2
  124. package/relay-hooks/useFragment.js.flow +26 -46
  125. package/relay-hooks/useFragmentNode.js.flow +5 -7
  126. package/relay-hooks/useIsOperationNodeActive.js.flow +3 -5
  127. package/relay-hooks/useIsParentQueryActive.js.flow +3 -4
  128. package/relay-hooks/useLazyLoadQuery.js.flow +9 -10
  129. package/relay-hooks/useLazyLoadQueryNode.js.flow +19 -13
  130. package/relay-hooks/useLoadMoreFunction.js.flow +20 -29
  131. package/relay-hooks/useMemoOperationDescriptor.js.flow +5 -7
  132. package/relay-hooks/useMemoVariables.js.flow +6 -6
  133. package/relay-hooks/useMutation.js.flow +26 -26
  134. package/relay-hooks/usePaginationFragment.js.flow +38 -44
  135. package/relay-hooks/usePreloadedQuery.js.flow +18 -14
  136. package/relay-hooks/useQueryLoader.js.flow +41 -22
  137. package/relay-hooks/useRefetchableFragment.js.flow +7 -8
  138. package/relay-hooks/useRefetchableFragmentNode.js.flow +24 -32
  139. package/relay-hooks/useRelayEnvironment.js.flow +2 -4
  140. package/relay-hooks/useStaticFragmentNodeWarning.js.flow +2 -3
  141. package/relay-hooks/useSubscribeToInvalidationState.js.flow +3 -6
  142. package/relay-hooks/useSubscription.js.flow +20 -10
  143. package/lib/relay-hooks/getPaginationMetadata.js +0 -41
  144. package/lib/relay-hooks/getPaginationVariables.js +0 -67
  145. package/lib/relay-hooks/getRefetchMetadata.js +0 -36
  146. package/lib/relay-hooks/getValueAtPath.js +0 -51
  147. package/relay-hooks/getPaginationMetadata.js.flow +0 -74
  148. package/relay-hooks/getPaginationVariables.js.flow +0 -110
  149. package/relay-hooks/getRefetchMetadata.js.flow +0 -80
  150. package/relay-hooks/getValueAtPath.js.flow +0 -46
@@ -15,21 +15,32 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
15
15
 
16
16
  var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread2"));
17
17
 
18
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
19
+
18
20
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
19
21
 
20
22
  var LRUCache = require('./LRUCache');
21
23
 
22
- var invariant = require("fbjs/lib/invariant");
24
+ var _require = require('./QueryResource'),
25
+ getQueryResourceForEnvironment = _require.getQueryResourceForEnvironment;
26
+
27
+ var SuspenseResource = require('./SuspenseResource');
23
28
 
24
- var mapObject = require("fbjs/lib/mapObject");
29
+ var invariant = require('invariant');
25
30
 
26
- var _require = require('relay-runtime'),
27
- getPromiseForActiveRequest = _require.__internal.getPromiseForActiveRequest,
28
- getFragmentIdentifier = _require.getFragmentIdentifier,
29
- getSelector = _require.getSelector,
30
- isPromise = _require.isPromise,
31
- recycleNodesInto = _require.recycleNodesInto,
32
- reportMissingRequiredFields = _require.reportMissingRequiredFields;
31
+ var _require2 = require('relay-runtime'),
32
+ RelayFeatureFlags = _require2.RelayFeatureFlags,
33
+ _require2$__internal = _require2.__internal,
34
+ fetchQuery = _require2$__internal.fetchQuery,
35
+ getPromiseForActiveRequest = _require2$__internal.getPromiseForActiveRequest,
36
+ createOperationDescriptor = _require2.createOperationDescriptor,
37
+ getFragmentIdentifier = _require2.getFragmentIdentifier,
38
+ getPendingOperationsForFragment = _require2.getPendingOperationsForFragment,
39
+ getSelector = _require2.getSelector,
40
+ getVariablesFromFragment = _require2.getVariablesFromFragment,
41
+ isPromise = _require2.isPromise,
42
+ recycleNodesInto = _require2.recycleNodesInto,
43
+ reportMissingRequiredFields = _require2.reportMissingRequiredFields;
33
44
 
34
45
  var WEAKMAP_SUPPORTED = typeof WeakMap === 'function';
35
46
  // TODO: Fix to not rely on LRU. If the number of active fragments exceeds this
@@ -49,32 +60,133 @@ function isMissingData(snapshot) {
49
60
  return snapshot.isMissingData;
50
61
  }
51
62
 
52
- function getFragmentResult(cacheKey, snapshot) {
63
+ function hasMissingClientEdges(snapshot) {
64
+ var _snapshot$missingClie, _snapshot$missingClie2;
65
+
66
+ if (Array.isArray(snapshot)) {
67
+ return snapshot.some(function (s) {
68
+ var _s$missingClientEdges, _s$missingClientEdges2;
69
+
70
+ return ((_s$missingClientEdges = (_s$missingClientEdges2 = s.missingClientEdges) === null || _s$missingClientEdges2 === void 0 ? void 0 : _s$missingClientEdges2.length) !== null && _s$missingClientEdges !== void 0 ? _s$missingClientEdges : 0) > 0;
71
+ });
72
+ }
73
+
74
+ return ((_snapshot$missingClie = (_snapshot$missingClie2 = snapshot.missingClientEdges) === null || _snapshot$missingClie2 === void 0 ? void 0 : _snapshot$missingClie2.length) !== null && _snapshot$missingClie !== void 0 ? _snapshot$missingClie : 0) > 0;
75
+ }
76
+
77
+ function singularOrPluralForEach(snapshot, f) {
78
+ if (Array.isArray(snapshot)) {
79
+ snapshot.forEach(f);
80
+ } else {
81
+ f(snapshot);
82
+ }
83
+ }
84
+
85
+ function getFragmentResult(cacheKey, snapshot, storeEpoch) {
53
86
  if (Array.isArray(snapshot)) {
54
87
  return {
55
88
  cacheKey: cacheKey,
56
89
  snapshot: snapshot,
57
90
  data: snapshot.map(function (s) {
58
91
  return s.data;
59
- })
92
+ }),
93
+ isMissingData: isMissingData(snapshot),
94
+ storeEpoch: storeEpoch
60
95
  };
61
96
  }
62
97
 
63
98
  return {
64
99
  cacheKey: cacheKey,
65
100
  snapshot: snapshot,
66
- data: snapshot.data
101
+ data: snapshot.data,
102
+ isMissingData: isMissingData(snapshot),
103
+ storeEpoch: storeEpoch
67
104
  };
68
105
  }
106
+ /**
107
+ * The purpose of this cache is to allow information to be passed from an
108
+ * initial read which suspends through to the commit that follows a subsequent
109
+ * successful read. Specifically, the QueryResource result for the data fetch
110
+ * is passed through so that that query can be retained on commit.
111
+ */
69
112
 
70
- function getPromiseForPendingOperationAffectingOwner(environment, request) {
71
- return environment.getOperationTracker().getPromiseForPendingOperationsAffectingOwner(request);
72
- }
113
+
114
+ var ClientEdgeQueryResultsCache = /*#__PURE__*/function () {
115
+ function ClientEdgeQueryResultsCache(environment) {
116
+ (0, _defineProperty2["default"])(this, "_cache", new Map());
117
+ (0, _defineProperty2["default"])(this, "_retainCounts", new Map());
118
+ this._environment = environment;
119
+ }
120
+
121
+ var _proto = ClientEdgeQueryResultsCache.prototype;
122
+
123
+ _proto.get = function get(fragmentIdentifier) {
124
+ var _this$_cache$get$, _this$_cache$get;
125
+
126
+ return (_this$_cache$get$ = (_this$_cache$get = this._cache.get(fragmentIdentifier)) === null || _this$_cache$get === void 0 ? void 0 : _this$_cache$get[0]) !== null && _this$_cache$get$ !== void 0 ? _this$_cache$get$ : undefined;
127
+ };
128
+
129
+ _proto.recordQueryResults = function recordQueryResults(fragmentIdentifier, value) {
130
+ var _this = this;
131
+
132
+ var existing = this._cache.get(fragmentIdentifier);
133
+
134
+ if (!existing) {
135
+ var suspenseResource = new SuspenseResource(function () {
136
+ return _this._retain(fragmentIdentifier);
137
+ });
138
+
139
+ this._cache.set(fragmentIdentifier, [value, suspenseResource]);
140
+
141
+ suspenseResource.temporaryRetain(this._environment);
142
+ } else {
143
+ var existingResults = existing[0],
144
+ _suspenseResource = existing[1];
145
+ value.forEach(function (queryResult) {
146
+ existingResults.push(queryResult);
147
+ });
148
+
149
+ _suspenseResource.temporaryRetain(this._environment);
150
+ }
151
+ };
152
+
153
+ _proto._retain = function _retain(id) {
154
+ var _this2 = this;
155
+
156
+ var _this$_retainCounts$g;
157
+
158
+ var retainCount = ((_this$_retainCounts$g = this._retainCounts.get(id)) !== null && _this$_retainCounts$g !== void 0 ? _this$_retainCounts$g : 0) + 1;
159
+
160
+ this._retainCounts.set(id, retainCount);
161
+
162
+ return {
163
+ dispose: function dispose() {
164
+ var _this$_retainCounts$g2;
165
+
166
+ var newRetainCount = ((_this$_retainCounts$g2 = _this2._retainCounts.get(id)) !== null && _this$_retainCounts$g2 !== void 0 ? _this$_retainCounts$g2 : 0) - 1;
167
+
168
+ if (newRetainCount > 0) {
169
+ _this2._retainCounts.set(id, newRetainCount);
170
+ } else {
171
+ _this2._retainCounts["delete"](id);
172
+
173
+ _this2._cache["delete"](id);
174
+ }
175
+ }
176
+ };
177
+ };
178
+
179
+ return ClientEdgeQueryResultsCache;
180
+ }();
73
181
 
74
182
  var FragmentResourceImpl = /*#__PURE__*/function () {
75
183
  function FragmentResourceImpl(environment) {
76
184
  this._environment = environment;
77
185
  this._cache = LRUCache.create(CACHE_CAPACITY);
186
+
187
+ if (RelayFeatureFlags.ENABLE_CLIENT_EDGES) {
188
+ this._clientEdgeQueryResultsCache = new ClientEdgeQueryResultsCache(environment);
189
+ }
78
190
  }
79
191
  /**
80
192
  * This function should be called during a Component's render function,
@@ -83,9 +195,9 @@ var FragmentResourceImpl = /*#__PURE__*/function () {
83
195
  */
84
196
 
85
197
 
86
- var _proto = FragmentResourceImpl.prototype;
198
+ var _proto2 = FragmentResourceImpl.prototype;
87
199
 
88
- _proto.read = function read(fragmentNode, fragmentRef, componentDisplayName, fragmentKey) {
200
+ _proto2.read = function read(fragmentNode, fragmentRef, componentDisplayName, fragmentKey) {
89
201
  return this.readWithIdentifier(fragmentNode, fragmentRef, getFragmentIdentifier(fragmentNode, fragmentRef), componentDisplayName, fragmentKey);
90
202
  }
91
203
  /**
@@ -95,8 +207,10 @@ var FragmentResourceImpl = /*#__PURE__*/function () {
95
207
  */
96
208
  ;
97
209
 
98
- _proto.readWithIdentifier = function readWithIdentifier(fragmentNode, fragmentRef, fragmentIdentifier, componentDisplayName, fragmentKey) {
99
- var _fragmentNode$metadat;
210
+ _proto2.readWithIdentifier = function readWithIdentifier(fragmentNode, fragmentRef, fragmentIdentifier, componentDisplayName, fragmentKey) {
211
+ var _this3 = this;
212
+
213
+ var _fragmentNode$metadat, _clientEdgePromises;
100
214
 
101
215
  var environment = this._environment; // If fragmentRef is null or undefined, pass it directly through.
102
216
  // This is a convenience when consuming fragments via a HOC API, when the
@@ -106,11 +220,14 @@ var FragmentResourceImpl = /*#__PURE__*/function () {
106
220
  return {
107
221
  cacheKey: fragmentIdentifier,
108
222
  data: null,
109
- snapshot: null
223
+ isMissingData: false,
224
+ snapshot: null,
225
+ storeEpoch: 0
110
226
  };
111
- } // If fragmentRef is plural, ensure that it is an array.
112
- // If it's empty, return the empty array directly before doing any more work.
227
+ }
113
228
 
229
+ var storeEpoch = environment.getStore().getEpoch(); // If fragmentRef is plural, ensure that it is an array.
230
+ // If it's empty, return the empty array directly before doing any more work.
114
231
 
115
232
  if ((fragmentNode === null || fragmentNode === void 0 ? void 0 : (_fragmentNode$metadat = fragmentNode.metadata) === null || _fragmentNode$metadat === void 0 ? void 0 : _fragmentNode$metadat.plural) === true) {
116
233
  !Array.isArray(fragmentRef) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Relay: Expected fragment pointer%s for fragment `%s` to be ' + 'an array, instead got `%s`. Remove `@relay(plural: true)` ' + 'from fragment `%s` to allow the prop to be an object.', fragmentKey != null ? " for key `".concat(fragmentKey, "`") : '', fragmentNode.name, typeof fragmentRef, fragmentNode.name) : invariant(false) : void 0;
@@ -119,7 +236,9 @@ var FragmentResourceImpl = /*#__PURE__*/function () {
119
236
  return {
120
237
  cacheKey: fragmentIdentifier,
121
238
  data: CONSTANT_READONLY_EMPTY_ARRAY,
122
- snapshot: CONSTANT_READONLY_EMPTY_ARRAY
239
+ isMissingData: false,
240
+ snapshot: CONSTANT_READONLY_EMPTY_ARRAY,
241
+ storeEpoch: storeEpoch
123
242
  };
124
243
  }
125
244
  } // Now we actually attempt to read the fragment:
@@ -129,14 +248,24 @@ var FragmentResourceImpl = /*#__PURE__*/function () {
129
248
  var cachedValue = this._cache.get(fragmentIdentifier);
130
249
 
131
250
  if (cachedValue != null) {
132
- if (isPromise(cachedValue)) {
133
- throw cachedValue;
251
+ if (cachedValue.kind === 'pending' && isPromise(cachedValue.promise)) {
252
+ environment.__log({
253
+ name: 'suspense.fragment',
254
+ data: cachedValue.result.data,
255
+ fragment: fragmentNode,
256
+ isRelayHooks: true,
257
+ isMissingData: cachedValue.result.isMissingData,
258
+ isPromiseCached: true,
259
+ pendingOperations: cachedValue.pendingOperations
260
+ });
261
+
262
+ throw cachedValue.promise;
134
263
  }
135
264
 
136
- if (cachedValue.snapshot) {
137
- this._reportMissingRequiredFieldsInSnapshot(cachedValue.snapshot);
265
+ if (cachedValue.kind === 'done' && cachedValue.result.snapshot) {
266
+ this._reportMissingRequiredFieldsInSnapshot(cachedValue.result.snapshot);
138
267
 
139
- return cachedValue;
268
+ return cachedValue.result;
140
269
  }
141
270
  } // 2. If not, try reading the fragment from the Relay store.
142
271
  // If the snapshot has data, return it and save it in cache
@@ -147,41 +276,120 @@ var FragmentResourceImpl = /*#__PURE__*/function () {
147
276
  var snapshot = fragmentSelector.kind === 'PluralReaderSelector' ? fragmentSelector.selectors.map(function (s) {
148
277
  return environment.lookup(s);
149
278
  }) : environment.lookup(fragmentSelector);
150
- var fragmentOwner = fragmentSelector.kind === 'PluralReaderSelector' ? fragmentSelector.selectors[0].owner : fragmentSelector.owner;
279
+ var fragmentResult = getFragmentResult(fragmentIdentifier, snapshot, storeEpoch);
151
280
 
152
- if (!isMissingData(snapshot)) {
281
+ if (!fragmentResult.isMissingData) {
153
282
  this._reportMissingRequiredFieldsInSnapshot(snapshot);
154
283
 
155
- var fragmentResult = getFragmentResult(fragmentIdentifier, snapshot);
156
-
157
- this._cache.set(fragmentIdentifier, fragmentResult);
284
+ this._cache.set(fragmentIdentifier, {
285
+ kind: 'done',
286
+ result: fragmentResult
287
+ });
158
288
 
159
289
  return fragmentResult;
160
- } // 3. If we don't have data in the store, check if a request is in
161
- // flight for the fragment's parent query, or for another operation
162
- // that may affect the parent's query data, such as a mutation
163
- // or subscription. If a promise exists, cache the promise and use it
164
- // to suspend.
290
+ } // 3. If we don't have data in the store, there's two cases where we should
291
+ // suspend to await the data: First if any client edges were traversed where
292
+ // the destination record was missing data; in that case we initiate a query
293
+ // here to fetch the missing data. Second, there may already be a request
294
+ // in flight for the fragment's parent query, or for another operation that
295
+ // may affect the parent's query data, such as a mutation or subscription.
296
+ // For any of these cases we can get a promise, which we will cache and
297
+ // suspend on.
298
+ // First, initiate a query for any client edges that were missing data:
299
+
300
+
301
+ var clientEdgeRequests = null;
302
+
303
+ if (RelayFeatureFlags.ENABLE_CLIENT_EDGES && hasMissingClientEdges(snapshot)) {
304
+ clientEdgeRequests = [];
305
+ var queryResource = getQueryResourceForEnvironment(this._environment);
306
+ var queryResults = [];
307
+ singularOrPluralForEach(snapshot, function (snap) {
308
+ var _snap$missingClientEd;
309
+
310
+ (_snap$missingClientEd = snap.missingClientEdges) === null || _snap$missingClientEd === void 0 ? void 0 : _snap$missingClientEd.forEach(function (_ref) {
311
+ var _clientEdgeRequests;
312
+
313
+ var request = _ref.request,
314
+ clientEdgeDestinationID = _ref.clientEdgeDestinationID;
315
+
316
+ var _this3$_performClient = _this3._performClientEdgeQuery(queryResource, fragmentNode, fragmentRef, request, clientEdgeDestinationID),
317
+ queryResult = _this3$_performClient.queryResult,
318
+ requestDescriptor = _this3$_performClient.requestDescriptor;
319
+
320
+ queryResults.push(queryResult);
321
+ (_clientEdgeRequests = clientEdgeRequests) === null || _clientEdgeRequests === void 0 ? void 0 : _clientEdgeRequests.push(requestDescriptor);
322
+ });
323
+ }); // Store the query so that it can be retained when our own fragment is
324
+ // subscribed to. This merges with any existing query results:
325
+
326
+ !(this._clientEdgeQueryResultsCache != null) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Client edge query result cache should exist when ENABLE_CLIENT_EDGES is on.') : invariant(false) : void 0;
327
+
328
+ this._clientEdgeQueryResultsCache.recordQueryResults(fragmentIdentifier, queryResults);
329
+ }
165
330
 
331
+ var clientEdgePromises = null;
166
332
 
167
- var networkPromise = this._getAndSavePromiseForFragmentRequestInFlight(fragmentIdentifier, fragmentOwner);
333
+ if (RelayFeatureFlags.ENABLE_CLIENT_EDGES && clientEdgeRequests) {
334
+ clientEdgePromises = clientEdgeRequests.map(function (request) {
335
+ return getPromiseForActiveRequest(_this3._environment, request);
336
+ }).filter(function (p) {
337
+ return p != null;
338
+ });
339
+ } // Finally look for operations in flight for our parent query:
340
+
341
+
342
+ var fragmentOwner = fragmentSelector.kind === 'PluralReaderSelector' ? fragmentSelector.selectors[0].owner : fragmentSelector.owner;
168
343
 
169
- if (networkPromise != null) {
170
- throw networkPromise;
344
+ var parentQueryPromiseResult = this._getAndSavePromiseForFragmentRequestInFlight(fragmentIdentifier, fragmentNode, fragmentOwner, fragmentResult);
345
+
346
+ var parentQueryPromiseResultPromise = parentQueryPromiseResult === null || parentQueryPromiseResult === void 0 ? void 0 : parentQueryPromiseResult.promise; // for refinement
347
+
348
+ if (((_clientEdgePromises = clientEdgePromises) === null || _clientEdgePromises === void 0 ? void 0 : _clientEdgePromises.length) || isPromise(parentQueryPromiseResultPromise)) {
349
+ var _parentQueryPromiseRe, _clientEdgeRequests2, _clientEdgePromises2;
350
+
351
+ environment.__log({
352
+ name: 'suspense.fragment',
353
+ data: fragmentResult.data,
354
+ fragment: fragmentNode,
355
+ isRelayHooks: true,
356
+ isPromiseCached: false,
357
+ isMissingData: fragmentResult.isMissingData,
358
+ pendingOperations: [].concat((0, _toConsumableArray2["default"])((_parentQueryPromiseRe = parentQueryPromiseResult === null || parentQueryPromiseResult === void 0 ? void 0 : parentQueryPromiseResult.pendingOperations) !== null && _parentQueryPromiseRe !== void 0 ? _parentQueryPromiseRe : []), (0, _toConsumableArray2["default"])((_clientEdgeRequests2 = clientEdgeRequests) !== null && _clientEdgeRequests2 !== void 0 ? _clientEdgeRequests2 : []))
359
+ });
360
+
361
+ throw ((_clientEdgePromises2 = clientEdgePromises) === null || _clientEdgePromises2 === void 0 ? void 0 : _clientEdgePromises2.length) ? Promise.all([parentQueryPromiseResultPromise].concat((0, _toConsumableArray2["default"])(clientEdgePromises))) : parentQueryPromiseResultPromise;
171
362
  }
172
363
 
173
364
  this._reportMissingRequiredFieldsInSnapshot(snapshot);
174
365
 
175
- return getFragmentResult(fragmentIdentifier, snapshot);
366
+ return getFragmentResult(fragmentIdentifier, snapshot, storeEpoch);
176
367
  };
177
368
 
178
- _proto._reportMissingRequiredFieldsInSnapshot = function _reportMissingRequiredFieldsInSnapshot(snapshot) {
179
- var _this = this;
369
+ _proto2._performClientEdgeQuery = function _performClientEdgeQuery(queryResource, fragmentNode, fragmentRef, request, clientEdgeDestinationID) {
370
+ var originalVariables = getVariablesFromFragment(fragmentNode, fragmentRef);
371
+ var variables = (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, originalVariables), {}, {
372
+ id: clientEdgeDestinationID // TODO should be a reserved name
373
+
374
+ });
375
+ var operation = createOperationDescriptor(request, variables, {} // TODO cacheConfig should probably inherent from parent operation
376
+ );
377
+ var fetchObservable = fetchQuery(this._environment, operation);
378
+ var queryResult = queryResource.prepare(operation, fetchObservable // TODO should inherent render policy etc. from parent operation
379
+ );
380
+ return {
381
+ requestDescriptor: operation.request,
382
+ queryResult: queryResult
383
+ };
384
+ };
385
+
386
+ _proto2._reportMissingRequiredFieldsInSnapshot = function _reportMissingRequiredFieldsInSnapshot(snapshot) {
387
+ var _this4 = this;
180
388
 
181
389
  if (Array.isArray(snapshot)) {
182
390
  snapshot.forEach(function (s) {
183
391
  if (s.missingRequiredFields != null) {
184
- reportMissingRequiredFields(_this._environment, s.missingRequiredFields);
392
+ reportMissingRequiredFields(_this4._environment, s.missingRequiredFields);
185
393
  }
186
394
  });
187
395
  } else {
@@ -191,17 +399,18 @@ var FragmentResourceImpl = /*#__PURE__*/function () {
191
399
  }
192
400
  };
193
401
 
194
- _proto.readSpec = function readSpec(fragmentNodes, fragmentRefs, componentDisplayName) {
195
- var _this2 = this;
402
+ _proto2.readSpec = function readSpec(fragmentNodes, fragmentRefs, componentDisplayName) {
403
+ var result = {};
196
404
 
197
- return mapObject(fragmentNodes, function (fragmentNode, fragmentKey) {
198
- var fragmentRef = fragmentRefs[fragmentKey];
199
- return _this2.read(fragmentNode, fragmentRef, componentDisplayName, fragmentKey);
200
- });
405
+ for (var _key in fragmentNodes) {
406
+ result[_key] = this.read(fragmentNodes[_key], fragmentRefs[_key], componentDisplayName, _key);
407
+ }
408
+
409
+ return result;
201
410
  };
202
411
 
203
- _proto.subscribe = function subscribe(fragmentResult, callback) {
204
- var _this3 = this;
412
+ _proto2.subscribe = function subscribe(fragmentResult, callback) {
413
+ var _this5 = this;
205
414
 
206
415
  var environment = this._environment;
207
416
  var cacheKey = fragmentResult.cacheKey;
@@ -226,42 +435,62 @@ var FragmentResourceImpl = /*#__PURE__*/function () {
226
435
  } // 3. Establish subscriptions on the snapshot(s)
227
436
 
228
437
 
229
- var dataSubscriptions = [];
438
+ var disposables = [];
230
439
 
231
440
  if (Array.isArray(renderedSnapshot)) {
232
441
  !Array.isArray(currentSnapshot) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Relay: Expected snapshots to be plural. ' + "If you're seeing this, this is likely a bug in Relay.") : invariant(false) : void 0;
233
442
  currentSnapshot.forEach(function (snapshot, idx) {
234
- dataSubscriptions.push(environment.subscribe(snapshot, function (latestSnapshot) {
235
- _this3._updatePluralSnapshot(cacheKey, currentSnapshot, latestSnapshot, idx);
443
+ disposables.push(environment.subscribe(snapshot, function (latestSnapshot) {
444
+ var storeEpoch = environment.getStore().getEpoch();
445
+
446
+ _this5._updatePluralSnapshot(cacheKey, currentSnapshot, latestSnapshot, idx, storeEpoch);
236
447
 
237
448
  callback();
238
449
  }));
239
450
  });
240
451
  } else {
241
452
  !(currentSnapshot != null && !Array.isArray(currentSnapshot)) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Relay: Expected snapshot to be singular. ' + "If you're seeing this, this is likely a bug in Relay.") : invariant(false) : void 0;
242
- dataSubscriptions.push(environment.subscribe(currentSnapshot, function (latestSnapshot) {
243
- _this3._cache.set(cacheKey, getFragmentResult(cacheKey, latestSnapshot));
453
+ disposables.push(environment.subscribe(currentSnapshot, function (latestSnapshot) {
454
+ var storeEpoch = environment.getStore().getEpoch();
455
+
456
+ _this5._cache.set(cacheKey, {
457
+ kind: 'done',
458
+ result: getFragmentResult(cacheKey, latestSnapshot, storeEpoch)
459
+ });
244
460
 
245
461
  callback();
246
462
  }));
247
463
  }
248
464
 
465
+ if (RelayFeatureFlags.ENABLE_CLIENT_EDGES) {
466
+ var _this$_clientEdgeQuer, _this$_clientEdgeQuer2;
467
+
468
+ var clientEdgeQueryResults = (_this$_clientEdgeQuer = (_this$_clientEdgeQuer2 = this._clientEdgeQueryResultsCache) === null || _this$_clientEdgeQuer2 === void 0 ? void 0 : _this$_clientEdgeQuer2.get(cacheKey)) !== null && _this$_clientEdgeQuer !== void 0 ? _this$_clientEdgeQuer : undefined;
469
+
470
+ if (clientEdgeQueryResults === null || clientEdgeQueryResults === void 0 ? void 0 : clientEdgeQueryResults.length) {
471
+ var queryResource = getQueryResourceForEnvironment(this._environment);
472
+ clientEdgeQueryResults.forEach(function (queryResult) {
473
+ disposables.push(queryResource.retain(queryResult));
474
+ });
475
+ }
476
+ }
477
+
249
478
  return {
250
479
  dispose: function dispose() {
251
- dataSubscriptions.map(function (s) {
480
+ disposables.forEach(function (s) {
252
481
  return s.dispose();
253
482
  });
254
483
 
255
- _this3._cache["delete"](cacheKey);
484
+ _this5._cache["delete"](cacheKey);
256
485
  }
257
486
  };
258
487
  };
259
488
 
260
- _proto.subscribeSpec = function subscribeSpec(fragmentResults, callback) {
261
- var _this4 = this;
489
+ _proto2.subscribeSpec = function subscribeSpec(fragmentResults, callback) {
490
+ var _this6 = this;
262
491
 
263
492
  var disposables = Object.keys(fragmentResults).map(function (key) {
264
- return _this4.subscribe(fragmentResults[key], callback);
493
+ return _this6.subscribe(fragmentResults[key], callback);
265
494
  });
266
495
  return {
267
496
  dispose: function dispose() {
@@ -272,18 +501,26 @@ var FragmentResourceImpl = /*#__PURE__*/function () {
272
501
  };
273
502
  };
274
503
 
275
- _proto.checkMissedUpdates = function checkMissedUpdates(fragmentResult) {
504
+ _proto2.checkMissedUpdates = function checkMissedUpdates(fragmentResult) {
276
505
  var environment = this._environment;
277
- var cacheKey = fragmentResult.cacheKey;
278
506
  var renderedSnapshot = fragmentResult.snapshot;
279
507
 
280
508
  if (!renderedSnapshot) {
281
509
  return [false, null];
282
510
  }
283
511
 
284
- var didMissUpdates = false;
512
+ var storeEpoch = null; // Bail out if the store hasn't been written since last read
513
+
514
+ storeEpoch = environment.getStore().getEpoch();
515
+
516
+ if (fragmentResult.storeEpoch === storeEpoch) {
517
+ return [false, fragmentResult.snapshot];
518
+ }
519
+
520
+ var cacheKey = fragmentResult.cacheKey;
285
521
 
286
522
  if (Array.isArray(renderedSnapshot)) {
523
+ var didMissUpdates = false;
287
524
  var currentSnapshots = [];
288
525
  renderedSnapshot.forEach(function (snapshot, idx) {
289
526
  var currentSnapshot = environment.lookup(snapshot.selector);
@@ -299,10 +536,14 @@ var FragmentResourceImpl = /*#__PURE__*/function () {
299
536
  }
300
537
 
301
538
  currentSnapshots[idx] = currentSnapshot;
302
- });
539
+ }); // Only update the cache when the data is changed to avoid
540
+ // returning different `data` instances
303
541
 
304
542
  if (didMissUpdates) {
305
- this._cache.set(cacheKey, getFragmentResult(cacheKey, currentSnapshots));
543
+ this._cache.set(cacheKey, {
544
+ kind: 'done',
545
+ result: getFragmentResult(cacheKey, currentSnapshots, storeEpoch)
546
+ });
306
547
  }
307
548
 
308
549
  return [didMissUpdates, currentSnapshots];
@@ -312,60 +553,71 @@ var FragmentResourceImpl = /*#__PURE__*/function () {
312
553
  var renderData = renderedSnapshot.data;
313
554
  var currentData = currentSnapshot.data;
314
555
  var updatedData = recycleNodesInto(renderData, currentData);
315
- currentSnapshot = {
556
+ var updatedCurrentSnapshot = {
316
557
  data: updatedData,
317
558
  isMissingData: currentSnapshot.isMissingData,
559
+ missingClientEdges: currentSnapshot.missingClientEdges,
318
560
  seenRecords: currentSnapshot.seenRecords,
319
561
  selector: currentSnapshot.selector,
320
562
  missingRequiredFields: currentSnapshot.missingRequiredFields
321
563
  };
322
564
 
323
565
  if (updatedData !== renderData) {
324
- this._cache.set(cacheKey, getFragmentResult(cacheKey, currentSnapshot));
325
-
326
- didMissUpdates = true;
566
+ this._cache.set(cacheKey, {
567
+ kind: 'done',
568
+ result: getFragmentResult(cacheKey, updatedCurrentSnapshot, storeEpoch)
569
+ });
327
570
  }
328
571
 
329
- return [didMissUpdates, currentSnapshot];
572
+ return [updatedData !== renderData, updatedCurrentSnapshot];
330
573
  };
331
574
 
332
- _proto.checkMissedUpdatesSpec = function checkMissedUpdatesSpec(fragmentResults) {
333
- var _this5 = this;
575
+ _proto2.checkMissedUpdatesSpec = function checkMissedUpdatesSpec(fragmentResults) {
576
+ var _this7 = this;
334
577
 
335
578
  return Object.keys(fragmentResults).some(function (key) {
336
- return _this5.checkMissedUpdates(fragmentResults[key])[0];
579
+ return _this7.checkMissedUpdates(fragmentResults[key])[0];
337
580
  });
338
581
  };
339
582
 
340
- _proto._getAndSavePromiseForFragmentRequestInFlight = function _getAndSavePromiseForFragmentRequestInFlight(cacheKey, fragmentOwner) {
341
- var _this6 = this;
583
+ _proto2._getAndSavePromiseForFragmentRequestInFlight = function _getAndSavePromiseForFragmentRequestInFlight(cacheKey, fragmentNode, fragmentOwner, fragmentResult) {
584
+ var _this8 = this;
342
585
 
343
- var _getPromiseForActiveR;
586
+ var pendingOperationsResult = getPendingOperationsForFragment(this._environment, fragmentNode, fragmentOwner);
344
587
 
345
- var environment = this._environment;
346
- var networkPromise = (_getPromiseForActiveR = getPromiseForActiveRequest(environment, fragmentOwner)) !== null && _getPromiseForActiveR !== void 0 ? _getPromiseForActiveR : getPromiseForPendingOperationAffectingOwner(environment, fragmentOwner);
347
-
348
- if (!networkPromise) {
588
+ if (pendingOperationsResult == null) {
349
589
  return null;
350
590
  } // When the Promise for the request resolves, we need to make sure to
351
591
  // update the cache with the latest data available in the store before
352
592
  // resolving the Promise
353
593
 
354
594
 
595
+ var networkPromise = pendingOperationsResult.promise;
596
+ var pendingOperations = pendingOperationsResult.pendingOperations;
355
597
  var promise = networkPromise.then(function () {
356
- _this6._cache["delete"](cacheKey);
598
+ _this8._cache["delete"](cacheKey);
357
599
  })["catch"](function (error) {
358
- _this6._cache["delete"](cacheKey);
359
- });
600
+ _this8._cache["delete"](cacheKey);
601
+ }); // $FlowExpectedError[prop-missing] Expando to annotate Promises.
360
602
 
361
- this._cache.set(cacheKey, promise); // $FlowExpectedError[prop-missing] Expando to annotate Promises.
603
+ promise.displayName = networkPromise.displayName;
362
604
 
605
+ this._cache.set(cacheKey, {
606
+ kind: 'pending',
607
+ pendingOperations: pendingOperations,
608
+ promise: promise,
609
+ result: fragmentResult
610
+ });
363
611
 
364
- promise.displayName = 'Relay(' + fragmentOwner.node.params.name + ')';
365
- return promise;
612
+ return {
613
+ promise: promise,
614
+ pendingOperations: pendingOperations
615
+ };
366
616
  };
367
617
 
368
- _proto._updatePluralSnapshot = function _updatePluralSnapshot(cacheKey, baseSnapshots, latestSnapshot, idx) {
618
+ _proto2._updatePluralSnapshot = function _updatePluralSnapshot(cacheKey, baseSnapshots, latestSnapshot, idx, storeEpoch) {
619
+ var _currentFragmentResul;
620
+
369
621
  var currentFragmentResult = this._cache.get(cacheKey);
370
622
 
371
623
  if (isPromise(currentFragmentResult)) {
@@ -373,7 +625,7 @@ var FragmentResourceImpl = /*#__PURE__*/function () {
373
625
  return;
374
626
  }
375
627
 
376
- var currentSnapshot = currentFragmentResult === null || currentFragmentResult === void 0 ? void 0 : currentFragmentResult.snapshot;
628
+ var currentSnapshot = currentFragmentResult === null || currentFragmentResult === void 0 ? void 0 : (_currentFragmentResul = currentFragmentResult.result) === null || _currentFragmentResul === void 0 ? void 0 : _currentFragmentResul.snapshot;
377
629
 
378
630
  if (currentSnapshot && !Array.isArray(currentSnapshot)) {
379
631
  reportInvalidCachedData(latestSnapshot.selector.node.name);
@@ -383,7 +635,10 @@ var FragmentResourceImpl = /*#__PURE__*/function () {
383
635
  var nextSnapshots = currentSnapshot ? (0, _toConsumableArray2["default"])(currentSnapshot) : (0, _toConsumableArray2["default"])(baseSnapshots);
384
636
  nextSnapshots[idx] = latestSnapshot;
385
637
 
386
- this._cache.set(cacheKey, getFragmentResult(cacheKey, nextSnapshots));
638
+ this._cache.set(cacheKey, {
639
+ kind: 'done',
640
+ result: getFragmentResult(cacheKey, nextSnapshots, storeEpoch)
641
+ });
387
642
  };
388
643
 
389
644
  return FragmentResourceImpl;
@@ -11,7 +11,7 @@
11
11
  // flowlint ambiguous-object-type:error
12
12
  'use strict';
13
13
 
14
- var invariant = require("fbjs/lib/invariant");
14
+ var invariant = require('invariant');
15
15
 
16
16
  /**
17
17
  * JS maps (both plain objects and Map) maintain key insertion