view who was fronting when a record was made
1interface CachedItem<T> {
2 data: T;
3 timestamp: number;
4}
5
6export const decodeStorageKey = (storageKey: string) =>
7 atob(storageKey.split("_")[1]);
8
9export class PersistentCache<T = any> {
10 private readonly keyPrefix: string;
11 private readonly expiryHours: number;
12 private readonly keysSetKey: `local:${string}`;
13
14 constructor(keyPrefix: string, expiryHours: number) {
15 this.keyPrefix = keyPrefix;
16 this.expiryHours = expiryHours;
17 this.keysSetKey = `local:${keyPrefix}_keys`;
18 }
19
20 private getCacheKey(key: string): `local:${string}` {
21 const safeKey = btoa(key);
22 return `local:${this.keyPrefix}_${safeKey}`;
23 }
24
25 private async getStoredKeys(): Promise<Set<string>> {
26 const keys = await storage.getItem<string[]>(this.keysSetKey);
27 return new Set(keys || []);
28 }
29
30 private async addKeyToSet(key: string): Promise<void> {
31 const keys = await this.getStoredKeys();
32 keys.add(key);
33 await storage.setItem(this.keysSetKey, Array.from(keys));
34 }
35
36 private async removeKeyFromSet(...key: string[]): Promise<void> {
37 const keys = await this.getStoredKeys();
38 for (const k of key) keys.delete(k);
39 await storage.setItem(this.keysSetKey, Array.from(keys));
40 }
41
42 async get(key: string): Promise<T | undefined> {
43 const cacheKey = this.getCacheKey(key);
44 const cached = await storage.getItem<CachedItem<T>>(cacheKey);
45
46 if (!cached) return undefined;
47
48 const now = Date.now();
49 const expiryTime = cached.timestamp + this.expiryHours * 60 * 60 * 1000;
50
51 if (this.expiryHours > 0 && now > expiryTime) {
52 await storage.removeItem(cacheKey);
53 return undefined;
54 }
55
56 return cached.data;
57 }
58
59 async set(key: string, value: T): Promise<void> {
60 const cacheKey = this.getCacheKey(key);
61 const cachedItem: CachedItem<T> = {
62 data: value,
63 timestamp: Date.now(),
64 };
65 await storage.setItem(cacheKey, cachedItem);
66 await this.addKeyToSet(key);
67 }
68
69 async remove(key: string): Promise<void> {
70 const cacheKey = this.getCacheKey(key);
71 await storage.removeItem(cacheKey);
72 await this.removeKeyFromSet(key);
73 }
74
75 async getAll(): Promise<Map<string, T>> {
76 const keys = await this.getStoredKeys();
77
78 if (keys.size === 0) {
79 return new Map();
80 }
81
82 const cacheKeys = Array.from(keys).map((key) => this.getCacheKey(key));
83 const items = await storage.getItems(cacheKeys);
84
85 const result = new Map<string, T>();
86 const now = Date.now();
87 const keysToRemove: string[] = [];
88
89 for (const { key, value } of items) {
90 const expiryTime = value.timestamp + this.expiryHours * 60 * 60 * 1000;
91
92 if (this.expiryHours > 0 && now > expiryTime) {
93 keysToRemove.push(key);
94 } else {
95 result.set(key, value.data);
96 }
97 }
98
99 // Clean up expired or missing items
100 if (keysToRemove.length > 0) {
101 const expiredCacheKeys = keysToRemove.map((key) => this.getCacheKey(key));
102 await Promise.all([
103 storage.removeItems(expiredCacheKeys),
104 this.removeKeyFromSet(...keysToRemove),
105 ]);
106 }
107
108 return result;
109 }
110}