react-relay 12.0.0 → 13.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (169) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +47 -0
  3. package/ReactRelayContainerUtils.js.flow +1 -1
  4. package/ReactRelayContext.js +2 -2
  5. package/ReactRelayContext.js.flow +3 -4
  6. package/ReactRelayFragmentContainer.js.flow +11 -17
  7. package/ReactRelayFragmentMockRenderer.js.flow +2 -2
  8. package/ReactRelayLocalQueryRenderer.js.flow +7 -8
  9. package/ReactRelayPaginationContainer.js.flow +30 -40
  10. package/ReactRelayQueryFetcher.js.flow +10 -11
  11. package/ReactRelayQueryRenderer.js.flow +16 -16
  12. package/ReactRelayQueryRendererContext.js.flow +1 -1
  13. package/ReactRelayRefetchContainer.js.flow +25 -33
  14. package/ReactRelayTestMocker.js.flow +17 -15
  15. package/ReactRelayTypes.js.flow +11 -11
  16. package/RelayContext.js.flow +4 -4
  17. package/__flowtests__/ReactRelayFragmentContainer-flowtest.js.flow +2 -3
  18. package/__flowtests__/ReactRelayPaginationContainer-flowtest.js.flow +2 -3
  19. package/__flowtests__/ReactRelayRefetchContainer-flowtest.js.flow +2 -3
  20. package/__flowtests__/RelayModern-flowtest.js.flow +79 -47
  21. package/__flowtests__/RelayModernFlowtest_badref.graphql.js.flow +6 -5
  22. package/__flowtests__/RelayModernFlowtest_notref.graphql.js.flow +6 -5
  23. package/__flowtests__/RelayModernFlowtest_user.graphql.js.flow +5 -4
  24. package/__flowtests__/RelayModernFlowtest_users.graphql.js.flow +5 -4
  25. package/__flowtests__/__generated__/ReactRelayFragmentContainerFlowtest_viewer.graphql.js.flow +14 -11
  26. package/__flowtests__/__generated__/ReactRelayFragmentContainerFlowtest_viewer2.graphql.js.flow +14 -11
  27. package/__flowtests__/__generated__/ReactRelayPaginationContainerFlowtestQuery.graphql.js.flow +14 -9
  28. package/__flowtests__/__generated__/ReactRelayPaginationContainerFlowtest_viewer.graphql.js.flow +14 -11
  29. package/__flowtests__/__generated__/ReactRelayRefetchContainerFlowtestQuery.graphql.js.flow +14 -9
  30. package/__flowtests__/__generated__/ReactRelayRefetchContainerFlowtest_viewer.graphql.js.flow +14 -11
  31. package/__flowtests__/__generated__/RelayModernFlowtest_badref.graphql.js.flow +16 -13
  32. package/__flowtests__/__generated__/RelayModernFlowtest_notref.graphql.js.flow +16 -13
  33. package/__flowtests__/__generated__/RelayModernFlowtest_user.graphql.js.flow +14 -11
  34. package/__flowtests__/__generated__/RelayModernFlowtest_users.graphql.js.flow +14 -11
  35. package/assertFragmentMap.js.flow +3 -3
  36. package/buildReactRelayContainer.js.flow +12 -11
  37. package/getRootVariablesForFragments.js.flow +3 -5
  38. package/hooks.js +2 -2
  39. package/hooks.js.flow +4 -6
  40. package/index.js +2 -2
  41. package/index.js.flow +5 -7
  42. package/isRelayEnvironment.js.flow +1 -1
  43. package/jest-react/enqueueTask.js.flow +2 -2
  44. package/jest-react/index.js.flow +1 -1
  45. package/jest-react/internalAct.js.flow +2 -4
  46. package/legacy.js +2 -2
  47. package/legacy.js.flow +1 -1
  48. package/lib/ReactRelayContainerUtils.js +1 -1
  49. package/lib/ReactRelayContext.js +1 -1
  50. package/lib/ReactRelayFragmentContainer.js +5 -5
  51. package/lib/ReactRelayFragmentMockRenderer.js +3 -3
  52. package/lib/ReactRelayLocalQueryRenderer.js +8 -9
  53. package/lib/ReactRelayPaginationContainer.js +19 -23
  54. package/lib/ReactRelayQueryFetcher.js +3 -3
  55. package/lib/ReactRelayQueryRenderer.js +5 -5
  56. package/lib/ReactRelayQueryRendererContext.js +1 -1
  57. package/lib/ReactRelayRefetchContainer.js +13 -15
  58. package/lib/ReactRelayTestMocker.js +8 -9
  59. package/lib/ReactRelayTypes.js +1 -1
  60. package/lib/RelayContext.js +4 -3
  61. package/lib/assertFragmentMap.js +3 -2
  62. package/lib/buildReactRelayContainer.js +8 -8
  63. package/lib/getRootVariablesForFragments.js +2 -3
  64. package/lib/hooks.js +6 -6
  65. package/lib/index.js +8 -8
  66. package/lib/isRelayEnvironment.js +1 -1
  67. package/lib/jest-react/enqueueTask.js +1 -1
  68. package/lib/jest-react/internalAct.js +3 -4
  69. package/lib/legacy.js +1 -1
  70. package/lib/multi-actor/ActorChange.js +3 -3
  71. package/lib/multi-actor/index.js +1 -1
  72. package/lib/multi-actor/useRelayActorEnvironment.js +3 -3
  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 +242 -46
  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 +84 -5
  83. package/lib/relay-hooks/RelayEnvironmentProvider.js +1 -1
  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 +9 -10
  87. package/lib/relay-hooks/preloadQuery_DEPRECATED.js +25 -11
  88. package/lib/relay-hooks/prepareEntryPoint_DEPRECATED.js +1 -1
  89. package/lib/relay-hooks/useBlockingPaginationFragment.js +3 -3
  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 +5 -5
  99. package/lib/relay-hooks/useLoadMoreFunction.js +8 -10
  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 +1 -1
  104. package/lib/relay-hooks/usePreloadedQuery.js +6 -6
  105. package/lib/relay-hooks/useQueryLoader.js +5 -5
  106. package/lib/relay-hooks/useRefetchableFragment.js +1 -1
  107. package/lib/relay-hooks/useRefetchableFragmentNode.js +11 -13
  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 +1 -1
  112. package/multi-actor/ActorChange.js.flow +4 -5
  113. package/multi-actor/index.js.flow +1 -1
  114. package/multi-actor/useRelayActorEnvironment.js.flow +6 -8
  115. package/package.json +3 -3
  116. package/react-relay-hooks.js +2 -2
  117. package/react-relay-hooks.min.js +3 -3
  118. package/react-relay-legacy.js +2 -2
  119. package/react-relay-legacy.min.js +3 -3
  120. package/react-relay.js +2 -2
  121. package/react-relay.min.js +3 -3
  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 +19 -25
  125. package/relay-hooks/FragmentResource.js.flow +221 -35
  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 +120 -18
  132. package/relay-hooks/RelayEnvironmentProvider.js.flow +10 -10
  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 +23 -24
  145. package/relay-hooks/preloadQuery_DEPRECATED.js.flow +30 -14
  146. package/relay-hooks/prepareEntryPoint_DEPRECATED.js.flow +7 -13
  147. package/relay-hooks/useBlockingPaginationFragment.js.flow +13 -14
  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 +12 -14
  157. package/relay-hooks/useLoadMoreFunction.js.flow +20 -28
  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 +38 -47
  162. package/relay-hooks/usePreloadedQuery.js.flow +14 -20
  163. package/relay-hooks/useQueryLoader.js.flow +14 -17
  164. package/relay-hooks/useRefetchableFragment.js.flow +8 -9
  165. package/relay-hooks/useRefetchableFragmentNode.js.flow +23 -31
  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 +7 -8
@@ -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,28 +13,35 @@
13
13
 
14
14
  'use strict';
15
15
 
16
- const LRUCache = require('./LRUCache');
16
+ import type {Cache} from './LRUCache';
17
+ import type {QueryResource, QueryResult} from './QueryResource';
18
+ import type {
19
+ ConcreteRequest,
20
+ DataID,
21
+ Disposable,
22
+ IEnvironment,
23
+ ReaderFragment,
24
+ RequestDescriptor,
25
+ Snapshot,
26
+ } from 'relay-runtime';
17
27
 
28
+ const LRUCache = require('./LRUCache');
29
+ const {getQueryResourceForEnvironment} = require('./QueryResource');
30
+ const SuspenseResource = require('./SuspenseResource');
18
31
  const invariant = require('invariant');
19
-
20
32
  const {
33
+ RelayFeatureFlags,
34
+ __internal: {fetchQuery, getPromiseForActiveRequest},
35
+ createOperationDescriptor,
21
36
  getFragmentIdentifier,
22
37
  getPendingOperationsForFragment,
23
38
  getSelector,
39
+ getVariablesFromFragment,
24
40
  isPromise,
25
41
  recycleNodesInto,
26
42
  reportMissingRequiredFields,
27
43
  } = require('relay-runtime');
28
44
 
29
- import type {Cache} from './LRUCache';
30
- import type {
31
- Disposable,
32
- IEnvironment,
33
- ReaderFragment,
34
- RequestDescriptor,
35
- Snapshot,
36
- } from 'relay-runtime';
37
-
38
45
  export type FragmentResource = FragmentResourceImpl;
39
46
 
40
47
  type FragmentResourceCache = Cache<
@@ -78,6 +85,24 @@ function isMissingData(snapshot: SingularOrPluralSnapshot): boolean {
78
85
  return snapshot.isMissingData;
79
86
  }
80
87
 
88
+ function hasMissingClientEdges(snapshot: SingularOrPluralSnapshot): boolean {
89
+ if (Array.isArray(snapshot)) {
90
+ return snapshot.some(s => (s.missingClientEdges?.length ?? 0) > 0);
91
+ }
92
+ return (snapshot.missingClientEdges?.length ?? 0) > 0;
93
+ }
94
+
95
+ function singularOrPluralForEach(
96
+ snapshot: SingularOrPluralSnapshot,
97
+ f: Snapshot => void,
98
+ ): void {
99
+ if (Array.isArray(snapshot)) {
100
+ snapshot.forEach(f);
101
+ } else {
102
+ f(snapshot);
103
+ }
104
+ }
105
+
81
106
  function getFragmentResult(
82
107
  cacheKey: string,
83
108
  snapshot: SingularOrPluralSnapshot,
@@ -101,13 +126,75 @@ function getFragmentResult(
101
126
  };
102
127
  }
103
128
 
129
+ /**
130
+ * The purpose of this cache is to allow information to be passed from an
131
+ * initial read which suspends through to the commit that follows a subsequent
132
+ * successful read. Specifically, the QueryResource result for the data fetch
133
+ * is passed through so that that query can be retained on commit.
134
+ */
135
+ class ClientEdgeQueryResultsCache {
136
+ _cache: Map<string, [Array<QueryResult>, SuspenseResource]> = new Map();
137
+ _retainCounts: Map<string, number> = new Map();
138
+ _environment: IEnvironment;
139
+
140
+ constructor(environment: IEnvironment) {
141
+ this._environment = environment;
142
+ }
143
+
144
+ get(fragmentIdentifier: string): void | Array<QueryResult> {
145
+ return this._cache.get(fragmentIdentifier)?.[0] ?? undefined;
146
+ }
147
+
148
+ recordQueryResults(
149
+ fragmentIdentifier: string,
150
+ value: Array<QueryResult>, // may be mutated after being passed here
151
+ ): void {
152
+ const existing = this._cache.get(fragmentIdentifier);
153
+ if (!existing) {
154
+ const suspenseResource = new SuspenseResource(() =>
155
+ this._retain(fragmentIdentifier),
156
+ );
157
+ this._cache.set(fragmentIdentifier, [value, suspenseResource]);
158
+ suspenseResource.temporaryRetain(this._environment);
159
+ } else {
160
+ const [existingResults, suspenseResource] = existing;
161
+ value.forEach(queryResult => {
162
+ existingResults.push(queryResult);
163
+ });
164
+ suspenseResource.temporaryRetain(this._environment);
165
+ }
166
+ }
167
+
168
+ _retain(id) {
169
+ const retainCount = (this._retainCounts.get(id) ?? 0) + 1;
170
+ this._retainCounts.set(id, retainCount);
171
+ return {
172
+ dispose: () => {
173
+ const newRetainCount = (this._retainCounts.get(id) ?? 0) - 1;
174
+ if (newRetainCount > 0) {
175
+ this._retainCounts.set(id, newRetainCount);
176
+ } else {
177
+ this._retainCounts.delete(id);
178
+ this._cache.delete(id);
179
+ }
180
+ },
181
+ };
182
+ }
183
+ }
184
+
104
185
  class FragmentResourceImpl {
105
186
  _environment: IEnvironment;
106
187
  _cache: FragmentResourceCache;
188
+ _clientEdgeQueryResultsCache: void | ClientEdgeQueryResultsCache;
107
189
 
108
190
  constructor(environment: IEnvironment) {
109
191
  this._environment = environment;
110
192
  this._cache = LRUCache.create(CACHE_CAPACITY);
193
+ if (RelayFeatureFlags.ENABLE_CLIENT_EDGES) {
194
+ this._clientEdgeQueryResultsCache = new ClientEdgeQueryResultsCache(
195
+ environment,
196
+ );
197
+ }
111
198
  }
112
199
 
113
200
  /**
@@ -252,24 +339,75 @@ class FragmentResourceImpl {
252
339
  return fragmentResult;
253
340
  }
254
341
 
255
- // 3. If we don't have data in the store, check if a request is in
256
- // flight for the fragment's parent query, or for another operation
257
- // that may affect the parent's query data, such as a mutation
258
- // or subscription. If a promise exists, cache the promise and use it
259
- // to suspend.
342
+ // 3. If we don't have data in the store, there's two cases where we should
343
+ // suspend to await the data: First if any client edges were traversed where
344
+ // the destination record was missing data; in that case we initiate a query
345
+ // here to fetch the missing data. Second, there may already be a request
346
+ // in flight for the fragment's parent query, or for another operation that
347
+ // may affect the parent's query data, such as a mutation or subscription.
348
+ // For any of these cases we can get a promise, which we will cache and
349
+ // suspend on.
350
+
351
+ // First, initiate a query for any client edges that were missing data:
352
+ let clientEdgeRequests: ?Array<RequestDescriptor> = null;
353
+ if (
354
+ RelayFeatureFlags.ENABLE_CLIENT_EDGES &&
355
+ hasMissingClientEdges(snapshot)
356
+ ) {
357
+ clientEdgeRequests = [];
358
+ const queryResource = getQueryResourceForEnvironment(this._environment);
359
+ const queryResults = [];
360
+ singularOrPluralForEach(snapshot, snap => {
361
+ snap.missingClientEdges?.forEach(
362
+ ({request, clientEdgeDestinationID}) => {
363
+ const {queryResult, requestDescriptor} =
364
+ this._performClientEdgeQuery(
365
+ queryResource,
366
+ fragmentNode,
367
+ fragmentRef,
368
+ request,
369
+ clientEdgeDestinationID,
370
+ );
371
+ queryResults.push(queryResult);
372
+ clientEdgeRequests?.push(requestDescriptor);
373
+ },
374
+ );
375
+ });
376
+ // Store the query so that it can be retained when our own fragment is
377
+ // subscribed to. This merges with any existing query results:
378
+ invariant(
379
+ this._clientEdgeQueryResultsCache != null,
380
+ 'Client edge query result cache should exist when ENABLE_CLIENT_EDGES is on.',
381
+ );
382
+ this._clientEdgeQueryResultsCache.recordQueryResults(
383
+ fragmentIdentifier,
384
+ queryResults,
385
+ );
386
+ }
387
+ let clientEdgePromises = null;
388
+ if (RelayFeatureFlags.ENABLE_CLIENT_EDGES && clientEdgeRequests) {
389
+ clientEdgePromises = clientEdgeRequests
390
+ .map(request => getPromiseForActiveRequest(this._environment, request))
391
+ .filter(p => p != null);
392
+ }
393
+
394
+ // Finally look for operations in flight for our parent query:
260
395
  const fragmentOwner =
261
396
  fragmentSelector.kind === 'PluralReaderSelector'
262
397
  ? fragmentSelector.selectors[0].owner
263
398
  : fragmentSelector.owner;
264
- const networkPromiseResult = this._getAndSavePromiseForFragmentRequestInFlight(
265
- fragmentIdentifier,
266
- fragmentNode,
267
- fragmentOwner,
268
- fragmentResult,
269
- );
399
+ const parentQueryPromiseResult =
400
+ this._getAndSavePromiseForFragmentRequestInFlight(
401
+ fragmentIdentifier,
402
+ fragmentNode,
403
+ fragmentOwner,
404
+ fragmentResult,
405
+ );
406
+ const parentQueryPromiseResultPromise = parentQueryPromiseResult?.promise; // for refinement
407
+
270
408
  if (
271
- networkPromiseResult != null &&
272
- isPromise(networkPromiseResult.promise)
409
+ clientEdgePromises?.length ||
410
+ isPromise(parentQueryPromiseResultPromise)
273
411
  ) {
274
412
  environment.__log({
275
413
  name: 'suspense.fragment',
@@ -278,15 +416,52 @@ class FragmentResourceImpl {
278
416
  isRelayHooks: true,
279
417
  isPromiseCached: false,
280
418
  isMissingData: fragmentResult.isMissingData,
281
- pendingOperations: networkPromiseResult.pendingOperations,
419
+ pendingOperations: [
420
+ ...(parentQueryPromiseResult?.pendingOperations ?? []),
421
+ ...(clientEdgeRequests ?? []),
422
+ ],
282
423
  });
283
- throw networkPromiseResult.promise;
424
+ throw clientEdgePromises?.length
425
+ ? Promise.all([parentQueryPromiseResultPromise, ...clientEdgePromises])
426
+ : parentQueryPromiseResultPromise;
284
427
  }
285
428
 
286
429
  this._reportMissingRequiredFieldsInSnapshot(snapshot);
287
430
  return getFragmentResult(fragmentIdentifier, snapshot, storeEpoch);
288
431
  }
289
432
 
433
+ _performClientEdgeQuery(
434
+ queryResource: QueryResource,
435
+ fragmentNode: ReaderFragment,
436
+ fragmentRef: mixed,
437
+ request: ConcreteRequest,
438
+ clientEdgeDestinationID: DataID,
439
+ ) {
440
+ const originalVariables = getVariablesFromFragment(
441
+ fragmentNode,
442
+ fragmentRef,
443
+ );
444
+ const variables = {
445
+ ...originalVariables,
446
+ id: clientEdgeDestinationID, // TODO should be a reserved name
447
+ };
448
+ const operation = createOperationDescriptor(
449
+ request,
450
+ variables,
451
+ {}, // TODO cacheConfig should probably inherent from parent operation
452
+ );
453
+ const fetchObservable = fetchQuery(this._environment, operation);
454
+ const queryResult = queryResource.prepare(
455
+ operation,
456
+ fetchObservable,
457
+ // TODO should inherent render policy etc. from parent operation
458
+ );
459
+ return {
460
+ requestDescriptor: operation.request,
461
+ queryResult,
462
+ };
463
+ }
464
+
290
465
  _reportMissingRequiredFieldsInSnapshot(snapshot: SingularOrPluralSnapshot) {
291
466
  if (Array.isArray(snapshot)) {
292
467
  snapshot.forEach(s => {
@@ -334,9 +509,8 @@ class FragmentResourceImpl {
334
509
 
335
510
  // 1. Check for any updates missed during render phase
336
511
  // TODO(T44066760): More efficiently detect if we missed an update
337
- const [didMissUpdates, currentSnapshot] = this.checkMissedUpdates(
338
- fragmentResult,
339
- );
512
+ const [didMissUpdates, currentSnapshot] =
513
+ this.checkMissedUpdates(fragmentResult);
340
514
 
341
515
  // 2. If an update was missed, notify the component so it updates with
342
516
  // the latest data.
@@ -345,7 +519,7 @@ class FragmentResourceImpl {
345
519
  }
346
520
 
347
521
  // 3. Establish subscriptions on the snapshot(s)
348
- const dataSubscriptions = [];
522
+ const disposables = [];
349
523
  if (Array.isArray(renderedSnapshot)) {
350
524
  invariant(
351
525
  Array.isArray(currentSnapshot),
@@ -353,7 +527,7 @@ class FragmentResourceImpl {
353
527
  "If you're seeing this, this is likely a bug in Relay.",
354
528
  );
355
529
  currentSnapshot.forEach((snapshot, idx) => {
356
- dataSubscriptions.push(
530
+ disposables.push(
357
531
  environment.subscribe(snapshot, latestSnapshot => {
358
532
  const storeEpoch = environment.getStore().getEpoch();
359
533
  this._updatePluralSnapshot(
@@ -373,7 +547,7 @@ class FragmentResourceImpl {
373
547
  'Relay: Expected snapshot to be singular. ' +
374
548
  "If you're seeing this, this is likely a bug in Relay.",
375
549
  );
376
- dataSubscriptions.push(
550
+ disposables.push(
377
551
  environment.subscribe(currentSnapshot, latestSnapshot => {
378
552
  const storeEpoch = environment.getStore().getEpoch();
379
553
  this._cache.set(cacheKey, {
@@ -385,9 +559,20 @@ class FragmentResourceImpl {
385
559
  );
386
560
  }
387
561
 
562
+ if (RelayFeatureFlags.ENABLE_CLIENT_EDGES) {
563
+ const clientEdgeQueryResults =
564
+ this._clientEdgeQueryResultsCache?.get(cacheKey) ?? undefined;
565
+ if (clientEdgeQueryResults?.length) {
566
+ const queryResource = getQueryResourceForEnvironment(this._environment);
567
+ clientEdgeQueryResults.forEach(queryResult => {
568
+ disposables.push(queryResource.retain(queryResult));
569
+ });
570
+ }
571
+ }
572
+
388
573
  return {
389
574
  dispose: () => {
390
- dataSubscriptions.map(s => s.dispose());
575
+ disposables.forEach(s => s.dispose());
391
576
  this._cache.delete(cacheKey);
392
577
  },
393
578
  };
@@ -458,6 +643,7 @@ class FragmentResourceImpl {
458
643
  const updatedCurrentSnapshot: Snapshot = {
459
644
  data: updatedData,
460
645
  isMissingData: currentSnapshot.isMissingData,
646
+ missingClientEdges: currentSnapshot.missingClientEdges,
461
647
  seenRecords: currentSnapshot.seenRecords,
462
648
  selector: currentSnapshot.selector,
463
649
  missingRequiredFields: currentSnapshot.missingRequiredFields,
@@ -473,7 +659,7 @@ class FragmentResourceImpl {
473
659
 
474
660
  checkMissedUpdatesSpec(fragmentResults: {
475
661
  [string]: FragmentResult,
476
- ...,
662
+ ...
477
663
  }): boolean {
478
664
  return Object.keys(fragmentResults).some(
479
665
  key => this.checkMissedUpdates(fragmentResults[key])[0],
@@ -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.
@@ -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.