1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @flow
    
  8.  */
    
  9. 
    
  10. import type {ReactContext, Thenable} from 'shared/ReactTypes';
    
  11. 
    
  12. import * as React from 'react';
    
  13. import {createContext} from 'react';
    
  14. 
    
  15. // TODO (cache) Remove this cache; it is outdated and will not work with newer APIs like startTransition.
    
  16. 
    
  17. // Cache implementation was forked from the React repo:
    
  18. // https://github.com/facebook/react/blob/main/packages/react-cache/src/ReactCache.js
    
  19. //
    
  20. // This cache is simpler than react-cache in that:
    
  21. // 1. Individual items don't need to be invalidated.
    
  22. //    Profiling data is invalidated as a whole.
    
  23. // 2. We didn't need the added overhead of an LRU cache.
    
  24. //    The size of this cache is bounded by how many renders were profiled,
    
  25. //    and it will be fully reset between profiling sessions.
    
  26. 
    
  27. export type {Thenable};
    
  28. 
    
  29. interface Suspender {
    
  30.   then(resolve: () => mixed, reject: () => mixed): mixed;
    
  31. }
    
  32. 
    
  33. type PendingResult = {
    
  34.   status: 0,
    
  35.   value: Suspender,
    
  36. };
    
  37. 
    
  38. type ResolvedResult<Value> = {
    
  39.   status: 1,
    
  40.   value: Value,
    
  41. };
    
  42. 
    
  43. type RejectedResult = {
    
  44.   status: 2,
    
  45.   value: mixed,
    
  46. };
    
  47. 
    
  48. type Result<Value> = PendingResult | ResolvedResult<Value> | RejectedResult;
    
  49. 
    
  50. export type Resource<Input, Key, Value> = {
    
  51.   clear(): void,
    
  52.   invalidate(Key): void,
    
  53.   read(Input): Value,
    
  54.   preload(Input): void,
    
  55.   write(Key, Value): void,
    
  56.   ...
    
  57. };
    
  58. 
    
  59. const Pending = 0;
    
  60. const Resolved = 1;
    
  61. const Rejected = 2;
    
  62. 
    
  63. const ReactCurrentDispatcher = (React: any)
    
  64.   .__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher;
    
  65. 
    
  66. function readContext(Context: ReactContext<null>) {
    
  67.   const dispatcher = ReactCurrentDispatcher.current;
    
  68.   if (dispatcher === null) {
    
  69.     throw new Error(
    
  70.       'react-cache: read and preload may only be called from within a ' +
    
  71.         "component's render. They are not supported in event handlers or " +
    
  72.         'lifecycle methods.',
    
  73.     );
    
  74.   }
    
  75.   return dispatcher.readContext(Context);
    
  76. }
    
  77. 
    
  78. const CacheContext = createContext(null);
    
  79. 
    
  80. type Config = {useWeakMap?: boolean, ...};
    
  81. 
    
  82. const entries: Map<
    
  83.   Resource<any, any, any>,
    
  84.   Map<any, any> | WeakMap<any, any>,
    
  85. > = new Map();
    
  86. const resourceConfigs: Map<Resource<any, any, any>, Config> = new Map();
    
  87. 
    
  88. function getEntriesForResource(
    
  89.   resource: any,
    
  90. ): Map<any, any> | WeakMap<any, any> {
    
  91.   let entriesForResource: Map<any, any> | WeakMap<any, any> = ((entries.get(
    
  92.     resource,
    
  93.   ): any): Map<any, any>);
    
  94.   if (entriesForResource === undefined) {
    
  95.     const config = resourceConfigs.get(resource);
    
  96.     entriesForResource =
    
  97.       config !== undefined && config.useWeakMap ? new WeakMap() : new Map();
    
  98.     entries.set(resource, entriesForResource);
    
  99.   }
    
  100.   return entriesForResource;
    
  101. }
    
  102. 
    
  103. function accessResult<Input, Key, Value>(
    
  104.   resource: any,
    
  105.   fetch: Input => Thenable<Value>,
    
  106.   input: Input,
    
  107.   key: Key,
    
  108. ): Result<Value> {
    
  109.   const entriesForResource = getEntriesForResource(resource);
    
  110.   const entry = entriesForResource.get(key);
    
  111.   if (entry === undefined) {
    
  112.     const thenable = fetch(input);
    
  113.     thenable.then(
    
  114.       value => {
    
  115.         if (newResult.status === Pending) {
    
  116.           const resolvedResult: ResolvedResult<Value> = (newResult: any);
    
  117.           resolvedResult.status = Resolved;
    
  118.           resolvedResult.value = value;
    
  119.         }
    
  120.       },
    
  121.       error => {
    
  122.         if (newResult.status === Pending) {
    
  123.           const rejectedResult: RejectedResult = (newResult: any);
    
  124.           rejectedResult.status = Rejected;
    
  125.           rejectedResult.value = error;
    
  126.         }
    
  127.       },
    
  128.     );
    
  129.     const newResult: PendingResult = {
    
  130.       status: Pending,
    
  131.       value: thenable,
    
  132.     };
    
  133.     entriesForResource.set(key, newResult);
    
  134.     return newResult;
    
  135.   } else {
    
  136.     return entry;
    
  137.   }
    
  138. }
    
  139. 
    
  140. export function createResource<Input, Key, Value>(
    
  141.   fetch: Input => Thenable<Value>,
    
  142.   hashInput: Input => Key,
    
  143.   config?: Config = {},
    
  144. ): Resource<Input, Key, Value> {
    
  145.   const resource = {
    
  146.     clear(): void {
    
  147.       entries.delete(resource);
    
  148.     },
    
  149. 
    
  150.     invalidate(key: Key): void {
    
  151.       const entriesForResource = getEntriesForResource(resource);
    
  152.       entriesForResource.delete(key);
    
  153.     },
    
  154. 
    
  155.     read(input: Input): Value {
    
  156.       // Prevent access outside of render.
    
  157.       readContext(CacheContext);
    
  158. 
    
  159.       const key = hashInput(input);
    
  160.       const result: Result<Value> = accessResult(resource, fetch, input, key);
    
  161.       switch (result.status) {
    
  162.         case Pending: {
    
  163.           const suspender = result.value;
    
  164.           throw suspender;
    
  165.         }
    
  166.         case Resolved: {
    
  167.           const value = result.value;
    
  168.           return value;
    
  169.         }
    
  170.         case Rejected: {
    
  171.           const error = result.value;
    
  172.           throw error;
    
  173.         }
    
  174.         default:
    
  175.           // Should be unreachable
    
  176.           return (undefined: any);
    
  177.       }
    
  178.     },
    
  179. 
    
  180.     preload(input: Input): void {
    
  181.       // Prevent access outside of render.
    
  182.       readContext(CacheContext);
    
  183. 
    
  184.       const key = hashInput(input);
    
  185.       accessResult(resource, fetch, input, key);
    
  186.     },
    
  187. 
    
  188.     write(key: Key, value: Value): void {
    
  189.       const entriesForResource = getEntriesForResource(resource);
    
  190. 
    
  191.       const resolvedResult: ResolvedResult<Value> = {
    
  192.         status: Resolved,
    
  193.         value,
    
  194.       };
    
  195. 
    
  196.       entriesForResource.set(key, resolvedResult);
    
  197.     },
    
  198.   };
    
  199. 
    
  200.   resourceConfigs.set(resource, config);
    
  201. 
    
  202.   return resource;
    
  203. }
    
  204. 
    
  205. export function invalidateResources(): void {
    
  206.   entries.clear();
    
  207. }