-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstaleWhileRevalidate.ts
100 lines (82 loc) · 3.38 KB
/
staleWhileRevalidate.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import {Clock, Instant} from '@croct/time';
import {DefaultClockProvider} from '@croct/time/defaultClockProvider';
import {CacheLoader, CacheProvider} from './cacheProvider';
import {TimestampedCacheEntry} from './timestampedCacheEntry';
type Configuration<K, V> = {
/**
* The underlying cache provider to use for storing the cached data.
*/
cacheProvider: CacheProvider<K, TimestampedCacheEntry<V>>,
/**
* The freshness period of the cached data in seconds.
*
* It defines the time duration for which the cache entry
* is considered fresh. Once the freshness period expires,
* the data will be revalidated in the background while
* still serving the stale value on subsequent gets.
*
* For example, if freshPeriod is set to 60 seconds,
* the cache entry will be considered fresh for 60 seconds
* after its creation or update. After 60 seconds, it will be
* evaluated in the background on subsequent gets, but the stale value
* will still be served until the revalidation is complete.
*/
freshPeriod: number,
/**
* The clock to use for time-related operations.
*
* It is used for retrieving the current time and for time calculations.
* If not provided, the default clock is used.
*
* @default DefaultClockProvider.getClock()
*/
clock?: Clock,
/**
* The handler for background revalidation errors.
*
* It is a function that is called when an error occurs during
* background revalidation of a cache entry. If not provided,
* errors during background revalidation are ignored.
*
* The most common use case for this handler is logging.
*/
errorHandler?: (error: Error) => void,
};
export class StaleWhileRevalidateCache<K, V> implements CacheProvider<K, V> {
private readonly cacheProvider: Configuration<K, V>['cacheProvider'];
private readonly freshPeriod: number;
private readonly clock: Clock;
private readonly errorHandler: (error: Error) => void;
public constructor(config: Configuration<K, V>) {
this.cacheProvider = config.cacheProvider;
this.freshPeriod = config.freshPeriod;
this.clock = config.clock ?? DefaultClockProvider.getClock();
this.errorHandler = config.errorHandler ?? ((): void => { /* noop */ });
}
public async get(key: K, loader: CacheLoader<K, V>): Promise<V> {
const now = Instant.now(this.clock);
const retrieveAndSave = async (): Promise<TimestampedCacheEntry<V>> => {
const entry: TimestampedCacheEntry<V> = {
value: await loader(key),
timestamp: now,
};
await this.cacheProvider.set(key, entry);
return entry;
};
const possiblyStaleEntry = await this.cacheProvider.get(key, retrieveAndSave);
if (now.isAfter(possiblyStaleEntry.timestamp.plusSeconds(this.freshPeriod))) {
// If expired revalidate on the background and return cached value
retrieveAndSave().catch(this.errorHandler);
}
return possiblyStaleEntry.value;
}
public set(key: K, value: V): Promise<void> {
return this.cacheProvider.set(key, {
value: value,
timestamp: Instant.now(this.clock),
});
}
public delete(key: K): Promise<void> {
return this.cacheProvider.delete(key);
}
}