view who was fronting when a record was made
at main 3.1 kB view raw
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}