react-relay 12.0.0 → 13.0.0-rc.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. package/ReactRelayContext.js +1 -1
  2. package/ReactRelayContext.js.flow +2 -3
  3. package/ReactRelayFragmentContainer.js.flow +10 -16
  4. package/ReactRelayFragmentMockRenderer.js.flow +1 -1
  5. package/ReactRelayLocalQueryRenderer.js.flow +6 -7
  6. package/ReactRelayPaginationContainer.js.flow +29 -39
  7. package/ReactRelayQueryFetcher.js.flow +9 -10
  8. package/ReactRelayQueryRenderer.js.flow +15 -15
  9. package/ReactRelayRefetchContainer.js.flow +24 -32
  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 +1 -2
  15. package/__flowtests__/ReactRelayRefetchContainer-flowtest.js.flow +1 -2
  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 +13 -10
  22. package/__flowtests__/__generated__/ReactRelayFragmentContainerFlowtest_viewer2.graphql.js.flow +13 -10
  23. package/__flowtests__/__generated__/ReactRelayPaginationContainerFlowtestQuery.graphql.js.flow +13 -8
  24. package/__flowtests__/__generated__/ReactRelayPaginationContainerFlowtest_viewer.graphql.js.flow +13 -10
  25. package/__flowtests__/__generated__/ReactRelayRefetchContainerFlowtestQuery.graphql.js.flow +13 -8
  26. package/__flowtests__/__generated__/ReactRelayRefetchContainerFlowtest_viewer.graphql.js.flow +13 -10
  27. package/__flowtests__/__generated__/RelayModernFlowtest_badref.graphql.js.flow +15 -12
  28. package/__flowtests__/__generated__/RelayModernFlowtest_notref.graphql.js.flow +15 -12
  29. package/__flowtests__/__generated__/RelayModernFlowtest_user.graphql.js.flow +13 -10
  30. package/__flowtests__/__generated__/RelayModernFlowtest_users.graphql.js.flow +13 -10
  31. package/assertFragmentMap.js.flow +2 -2
  32. package/buildReactRelayContainer.js.flow +11 -10
  33. package/getRootVariablesForFragments.js.flow +2 -4
  34. package/hooks.js +1 -1
  35. package/hooks.js.flow +3 -5
  36. package/index.js +1 -1
  37. package/index.js.flow +4 -6
  38. package/jest-react/enqueueTask.js.flow +1 -1
  39. package/jest-react/internalAct.js.flow +1 -2
  40. package/legacy.js +1 -1
  41. package/lib/ReactRelayFragmentContainer.js +4 -4
  42. package/lib/ReactRelayFragmentMockRenderer.js +2 -2
  43. package/lib/ReactRelayLocalQueryRenderer.js +7 -8
  44. package/lib/ReactRelayPaginationContainer.js +18 -22
  45. package/lib/ReactRelayQueryFetcher.js +2 -2
  46. package/lib/ReactRelayQueryRenderer.js +4 -4
  47. package/lib/ReactRelayRefetchContainer.js +12 -14
  48. package/lib/ReactRelayTestMocker.js +7 -8
  49. package/lib/RelayContext.js +3 -2
  50. package/lib/assertFragmentMap.js +2 -1
  51. package/lib/buildReactRelayContainer.js +7 -7
  52. package/lib/getRootVariablesForFragments.js +1 -2
  53. package/lib/hooks.js +5 -5
  54. package/lib/index.js +7 -7
  55. package/lib/jest-react/internalAct.js +2 -2
  56. package/lib/multi-actor/ActorChange.js +2 -2
  57. package/lib/multi-actor/useRelayActorEnvironment.js +2 -2
  58. package/lib/relay-hooks/EntryPointContainer.react.js +3 -3
  59. package/lib/relay-hooks/FragmentResource.js +241 -45
  60. package/lib/relay-hooks/LazyLoadEntryPointContainer_DEPRECATED.react.js +4 -4
  61. package/lib/relay-hooks/MatchContainer.js +1 -1
  62. package/lib/relay-hooks/QueryResource.js +83 -4
  63. package/lib/relay-hooks/SuspenseResource.js +130 -0
  64. package/lib/relay-hooks/loadQuery.js +8 -9
  65. package/lib/relay-hooks/preloadQuery_DEPRECATED.js +24 -10
  66. package/lib/relay-hooks/useBlockingPaginationFragment.js +2 -2
  67. package/lib/relay-hooks/useEntryPointLoader.js +2 -2
  68. package/lib/relay-hooks/useFetchTrackingRef.js +2 -1
  69. package/lib/relay-hooks/useFragment.js +8 -7
  70. package/lib/relay-hooks/useFragmentNode.js +4 -4
  71. package/lib/relay-hooks/useIsOperationNodeActive.js +2 -2
  72. package/lib/relay-hooks/useLazyLoadQuery.js +3 -3
  73. package/lib/relay-hooks/useLazyLoadQueryNode.js +4 -4
  74. package/lib/relay-hooks/useLoadMoreFunction.js +7 -9
  75. package/lib/relay-hooks/useMemoOperationDescriptor.js +2 -2
  76. package/lib/relay-hooks/useMemoVariables.js +2 -2
  77. package/lib/relay-hooks/useMutation.js +17 -6
  78. package/lib/relay-hooks/usePreloadedQuery.js +5 -5
  79. package/lib/relay-hooks/useQueryLoader.js +4 -4
  80. package/lib/relay-hooks/useRefetchableFragmentNode.js +10 -12
  81. package/lib/relay-hooks/useRelayEnvironment.js +2 -2
  82. package/lib/relay-hooks/useStaticFragmentNodeWarning.js +2 -2
  83. package/lib/relay-hooks/useSubscribeToInvalidationState.js +2 -1
  84. package/multi-actor/ActorChange.js.flow +3 -4
  85. package/multi-actor/useRelayActorEnvironment.js.flow +5 -7
  86. package/package.json +2 -2
  87. package/react-relay-hooks.js +2 -2
  88. package/react-relay-hooks.min.js +2 -2
  89. package/react-relay-legacy.js +2 -2
  90. package/react-relay-legacy.min.js +2 -2
  91. package/react-relay.js +2 -2
  92. package/react-relay.min.js +2 -2
  93. package/relay-hooks/EntryPointContainer.react.js.flow +8 -15
  94. package/relay-hooks/EntryPointTypes.flow.js.flow +18 -24
  95. package/relay-hooks/FragmentResource.js.flow +220 -34
  96. package/relay-hooks/LazyLoadEntryPointContainer_DEPRECATED.react.js.flow +32 -46
  97. package/relay-hooks/MatchContainer.js.flow +3 -2
  98. package/relay-hooks/QueryResource.js.flow +119 -17
  99. package/relay-hooks/RelayEnvironmentProvider.js.flow +9 -9
  100. package/relay-hooks/SuspenseResource.js.flow +115 -0
  101. package/relay-hooks/__flowtests__/EntryPointTypes/EntryPointElementConfig-flowtest.js.flow +4 -3
  102. package/relay-hooks/__flowtests__/EntryPointTypes/NestedEntrypoints-flowtest.js.flow +1 -1
  103. package/relay-hooks/__flowtests__/useBlockingPaginationFragment-flowtest.js.flow +10 -9
  104. package/relay-hooks/__flowtests__/useFragment-flowtest.js.flow +8 -7
  105. package/relay-hooks/__flowtests__/usePaginationFragment-flowtest.js.flow +10 -9
  106. package/relay-hooks/__flowtests__/useRefetchableFragment-flowtest.js.flow +10 -9
  107. package/relay-hooks/__flowtests__/utils.js.flow +8 -12
  108. package/relay-hooks/loadEntryPoint.js.flow +6 -12
  109. package/relay-hooks/loadQuery.js.flow +22 -23
  110. package/relay-hooks/preloadQuery_DEPRECATED.js.flow +29 -13
  111. package/relay-hooks/prepareEntryPoint_DEPRECATED.js.flow +6 -12
  112. package/relay-hooks/useBlockingPaginationFragment.js.flow +12 -13
  113. package/relay-hooks/useEntryPointLoader.js.flow +7 -10
  114. package/relay-hooks/useFetchTrackingRef.js.flow +2 -2
  115. package/relay-hooks/useFragment.js.flow +26 -46
  116. package/relay-hooks/useFragmentNode.js.flow +5 -7
  117. package/relay-hooks/useIsOperationNodeActive.js.flow +3 -5
  118. package/relay-hooks/useIsParentQueryActive.js.flow +3 -4
  119. package/relay-hooks/useLazyLoadQuery.js.flow +9 -10
  120. package/relay-hooks/useLazyLoadQueryNode.js.flow +11 -13
  121. package/relay-hooks/useLoadMoreFunction.js.flow +19 -27
  122. package/relay-hooks/useMemoOperationDescriptor.js.flow +5 -7
  123. package/relay-hooks/useMemoVariables.js.flow +6 -6
  124. package/relay-hooks/useMutation.js.flow +26 -26
  125. package/relay-hooks/usePaginationFragment.js.flow +37 -46
  126. package/relay-hooks/usePreloadedQuery.js.flow +13 -19
  127. package/relay-hooks/useQueryLoader.js.flow +13 -16
  128. package/relay-hooks/useRefetchableFragment.js.flow +7 -8
  129. package/relay-hooks/useRefetchableFragmentNode.js.flow +22 -30
  130. package/relay-hooks/useRelayEnvironment.js.flow +2 -4
  131. package/relay-hooks/useStaticFragmentNodeWarning.js.flow +2 -3
  132. package/relay-hooks/useSubscribeToInvalidationState.js.flow +3 -6
  133. package/relay-hooks/useSubscription.js.flow +6 -7
@@ -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],
@@ -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]);
@@ -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
@@ -13,19 +13,7 @@
13
13
 
14
14
  'use strict';
15
15
 
16
- const LRUCache = require('./LRUCache');
17
-
18
- const invariant = require('invariant');
19
- const warning = require('warning');
20
-
21
- const {isPromise} = require('relay-runtime');
22
-
23
- const CACHE_CAPACITY = 1000;
24
-
25
- const DEFAULT_FETCH_POLICY = 'store-or-network';
26
-
27
- const DATA_RETENTION_TIMEOUT = 5 * 60 * 1000;
28
-
16
+ import type {Cache} from './LRUCache';
29
17
  import type {
30
18
  Disposable,
31
19
  FetchPolicy,
@@ -40,7 +28,15 @@ import type {
40
28
  Snapshot,
41
29
  Subscription,
42
30
  } from 'relay-runtime';
43
- import type {Cache} from './LRUCache';
31
+
32
+ const LRUCache = require('./LRUCache');
33
+ const SuspenseResource = require('./SuspenseResource');
34
+ const invariant = require('invariant');
35
+ const {RelayFeatureFlags, isPromise} = require('relay-runtime');
36
+ const warning = require('warning');
37
+
38
+ const CACHE_CAPACITY = 1000;
39
+ const DEFAULT_FETCH_POLICY = 'store-or-network';
44
40
 
45
41
  export type QueryResource = QueryResourceImpl;
46
42
 
@@ -63,7 +59,7 @@ type QueryResourceCacheEntry = {|
63
59
  permanentRetain(environment: IEnvironment): Disposable,
64
60
  releaseTemporaryRetain(): void,
65
61
  |};
66
- opaque type QueryResult: {
62
+ export opaque type QueryResult: {
67
63
  fragmentNode: ReaderFragment,
68
64
  fragmentRef: mixed,
69
65
  ...
@@ -129,6 +125,106 @@ function createCacheEntry(
129
125
  value: Error | Promise<void> | QueryResult,
130
126
  networkSubscription: ?Subscription,
131
127
  onDispose: QueryResourceCacheEntry => void,
128
+ ): QueryResourceCacheEntry {
129
+ // There should be no behavior difference between createCacheEntry_new and
130
+ // createCacheEntry_old, and it doesn't directly relate to Client Edges.
131
+ // It was just a refactoring that was needed for Client Edges but that
132
+ // is behind the feature flag just in case there is any accidental breakage.
133
+ if (RelayFeatureFlags.REFACTOR_SUSPENSE_RESOURCE) {
134
+ return createCacheEntry_new(
135
+ cacheIdentifier,
136
+ operation,
137
+ operationAvailability,
138
+ value,
139
+ networkSubscription,
140
+ onDispose,
141
+ );
142
+ } else {
143
+ return createCacheEntry_old(
144
+ cacheIdentifier,
145
+ operation,
146
+ operationAvailability,
147
+ value,
148
+ networkSubscription,
149
+ onDispose,
150
+ );
151
+ }
152
+ }
153
+
154
+ function createCacheEntry_new(
155
+ cacheIdentifier: string,
156
+ operation: OperationDescriptor,
157
+ operationAvailability: ?OperationAvailability,
158
+ value: Error | Promise<void> | QueryResult,
159
+ networkSubscription: ?Subscription,
160
+ onDispose: QueryResourceCacheEntry => void,
161
+ ): QueryResourceCacheEntry {
162
+ const isLiveQuery = operationIsLiveQuery(operation);
163
+
164
+ let currentValue: Error | Promise<void> | QueryResult = value;
165
+ let currentNetworkSubscription: ?Subscription = networkSubscription;
166
+
167
+ const suspenseResource = new SuspenseResource(environment => {
168
+ const retention = environment.retain(operation);
169
+ return {
170
+ dispose: () => {
171
+ // Normally if this entry never commits, the request would've ended by the
172
+ // time this timeout expires and the temporary retain is released. However,
173
+ // we need to do this for live queries which remain open indefinitely.
174
+ if (isLiveQuery && currentNetworkSubscription != null) {
175
+ currentNetworkSubscription.unsubscribe();
176
+ }
177
+ retention.dispose();
178
+ onDispose(cacheEntry);
179
+ },
180
+ };
181
+ });
182
+
183
+ const cacheEntry = {
184
+ cacheIdentifier,
185
+ id: nextID++,
186
+ processedPayloadsCount: 0,
187
+ operationAvailability,
188
+ getValue() {
189
+ return currentValue;
190
+ },
191
+ setValue(val: QueryResult | Promise<void> | Error) {
192
+ currentValue = val;
193
+ },
194
+ getRetainCount() {
195
+ return suspenseResource.getRetainCount();
196
+ },
197
+ getNetworkSubscription() {
198
+ return currentNetworkSubscription;
199
+ },
200
+ setNetworkSubscription(subscription: ?Subscription) {
201
+ if (isLiveQuery && currentNetworkSubscription != null) {
202
+ currentNetworkSubscription.unsubscribe();
203
+ }
204
+ currentNetworkSubscription = subscription;
205
+ },
206
+ temporaryRetain(environment: IEnvironment): Disposable {
207
+ return suspenseResource.temporaryRetain(environment);
208
+ },
209
+ permanentRetain(environment: IEnvironment): Disposable {
210
+ return suspenseResource.permanentRetain(environment);
211
+ },
212
+ releaseTemporaryRetain() {
213
+ suspenseResource.releaseTemporaryRetain();
214
+ },
215
+ };
216
+
217
+ return cacheEntry;
218
+ }
219
+
220
+ const DATA_RETENTION_TIMEOUT = 5 * 60 * 1000;
221
+ function createCacheEntry_old(
222
+ cacheIdentifier: string,
223
+ operation: OperationDescriptor,
224
+ operationAvailability: ?OperationAvailability,
225
+ value: Error | Promise<void> | QueryResult,
226
+ networkSubscription: ?Subscription,
227
+ onDispose: QueryResourceCacheEntry => void,
132
228
  ): QueryResourceCacheEntry {
133
229
  const isLiveQuery = operationIsLiveQuery(operation);
134
230
 
@@ -168,7 +264,7 @@ function createCacheEntry(
168
264
  getValue() {
169
265
  return currentValue;
170
266
  },
171
- setValue(val) {
267
+ setValue(val: QueryResult | Promise<void> | Error) {
172
268
  currentValue = val;
173
269
  },
174
270
  getRetainCount() {
@@ -436,8 +532,14 @@ class QueryResourceImpl {
436
532
  }
437
533
 
438
534
  _clearCacheEntry = (cacheEntry: QueryResourceCacheEntry): void => {
439
- if (cacheEntry.getRetainCount() <= 0) {
535
+ // The new code does this retainCount <= 0 check within SuspenseResource
536
+ // before calling _clearCacheEntry, whereas with the old code we do it here.
537
+ if (RelayFeatureFlags.REFACTOR_SUSPENSE_RESOURCE) {
440
538
  this._cache.delete(cacheEntry.cacheIdentifier);
539
+ } else {
540
+ if (cacheEntry.getRetainCount() <= 0) {
541
+ this._cache.delete(cacheEntry.cacheIdentifier);
542
+ }
441
543
  }
442
544
  };
443
545