66 lines
1.5 KiB
TypeScript
66 lines
1.5 KiB
TypeScript
import addSeconds from 'date-fns/addSeconds';
|
|
import isAfter from 'date-fns/isAfter';
|
|
|
|
export interface Storage {
|
|
getItem(key: string): string | null;
|
|
setItem(key: string, value: string): void;
|
|
removeItem(key: string): void;
|
|
}
|
|
|
|
export class InMemoryStorage {
|
|
private readonly data = new Map<string, string>();
|
|
|
|
getItem(key: string): string | null {
|
|
const d = this.data.get(key);
|
|
return d ? d : null;
|
|
}
|
|
|
|
setItem(key: string, value: string) {
|
|
this.data.set(key, value);
|
|
}
|
|
|
|
removeItem(key: string) {
|
|
this.data.delete(key);
|
|
}
|
|
}
|
|
|
|
export class Cache {
|
|
constructor(private readonly storage: Storage) {}
|
|
|
|
// load first tries to load data from storage, if there is no data or it is expired then it executes the given function and caches the result
|
|
async load<T>(key: string, ttl: number, func: () => Promise<T>): Promise<T> {
|
|
const data = this.get<T>(key);
|
|
if (data) {
|
|
return data;
|
|
}
|
|
|
|
const res = await func();
|
|
|
|
this.storage.setItem(
|
|
key,
|
|
JSON.stringify({
|
|
data: res,
|
|
exp: addSeconds(new Date(), ttl),
|
|
})
|
|
);
|
|
|
|
return res;
|
|
}
|
|
|
|
get<T>(key: string): T | null {
|
|
try {
|
|
const fromStorage = JSON.parse(this.storage.getItem(key) ?? '');
|
|
if (!fromStorage.exp || !fromStorage.data) {
|
|
return null;
|
|
}
|
|
if (isAfter(new Date(), new Date(fromStorage.exp))) {
|
|
this.storage.removeItem(key);
|
|
return null;
|
|
}
|
|
return fromStorage.data;
|
|
} catch (err) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|