react-relay 12.0.0 → 13.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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.