Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / packages / components / hooks / useCachedModelResult.ts
blob9eedc7e764a547abfe77f5f3a4b302ca3b0fd1a5
1 import type { Cache } from '@proton/shared/lib/helpers/cache';
2 import { STATUS } from '@proton/shared/lib/models/cache';
4 type Record<Value> = {
5     status: STATUS;
6     timestamp: number;
7     value?: Value;
8     promise?: Promise<Value>;
9 };
11 const getRecordPending = <Value>(promise: Promise<Value>): Record<Value> => {
12     return {
13         status: STATUS.PENDING,
14         value: undefined,
15         promise,
16         timestamp: Date.now(),
17     };
20 const getRecordThen = <Value>(promise: Promise<Value>): Promise<Record<Value>> => {
21     return promise
22         .then((value) => {
23             return {
24                 status: STATUS.RESOLVED,
25                 value,
26                 timestamp: Date.now(),
27                 promise: undefined,
28             };
29         })
30         .catch((error) => {
31             return {
32                 status: STATUS.REJECTED,
33                 value: error,
34                 timestamp: Date.now(),
35                 promise: undefined,
36             };
37         });
40 /**
41  * The strategy to re-fetch is:
42  * 1) When no record exists for that key.
43  * 2) If the old record has failed to fetch.
44  * This should only happen when:
45  * 1) When the component is initially mounted.
46  * 2) A mounted component that receives an update from the cache that the key has been removed.
47  * 3) A mounted component receives an update that the key has changed.
48  */
49 const update = <Value, Key>(cache: Cache<Key, Record<Value>>, key: Key, promise: Promise<Value>) => {
50     const record = getRecordPending(promise);
51     cache.set(key, record);
52     getRecordThen(promise).then((newRecord) => {
53         if (cache.get(key) === record) {
54             cache.set(key, newRecord);
55         }
56     });
57     return promise;
60 export const getIsRecordInvalid = <Value>(record: Record<Value> | undefined, lifetime = Number.MAX_SAFE_INTEGER) => {
61     return (
62         !record ||
63         record.status === STATUS.REJECTED ||
64         (record.status === STATUS.RESOLVED && Date.now() - record.timestamp > lifetime)
65     );
68 export const getPromiseValue = <Value, Key>(
69     cache: Cache<Key, Record<Value>>,
70     key: Key,
71     miss: (key: Key) => Promise<Value>,
72     lifetime?: number
73 ): Promise<Value> => {
74     const oldRecord = cache.get(key);
75     if (!oldRecord || getIsRecordInvalid(oldRecord, lifetime)) {
76         return update(cache, key, miss(key));
77     }
78     if (oldRecord.promise) {
79         return oldRecord.promise;
80     }
81     return Promise.resolve(oldRecord.value!);