replies timeline only, appview-less bluesky client
1import { Cache, type CacheOptions } from '@wora/cache-persist'; 2import { LRUCache } from 'lru-cache'; 3 4export interface PersistedLRUOptions { 5 prefix?: string; 6 max: number; 7 ttl?: number; 8 persistOptions?: CacheOptions; 9} 10 11// eslint-disable-next-line @typescript-eslint/no-empty-object-type 12export class PersistedLRU<K extends string, V extends {}> { 13 private memory: LRUCache<K, V>; 14 private storage: Cache; 15 private signals: Map<K, ((data: V) => void)[]>; 16 17 private prefix = ''; 18 19 constructor(opts: PersistedLRUOptions) { 20 this.memory = new LRUCache<K, V>({ 21 max: opts.max, 22 ttl: opts.ttl 23 }); 24 this.storage = new Cache(opts.persistOptions); 25 this.prefix = opts.prefix ? `${opts.prefix}%` : ''; 26 this.signals = new Map(); 27 28 this.init(); 29 } 30 31 async init(): Promise<void> { 32 await this.storage.restore(); 33 34 const state = this.storage.getState(); 35 for (const [key, val] of Object.entries(state)) { 36 try { 37 // console.log('restoring', key); 38 const k = this.unprefix(key) as unknown as K; 39 const v = val as V; 40 this.memory.set(k, v); 41 } catch (err) { 42 console.warn('skipping invalid persisted entry', key, err); 43 } 44 } 45 } 46 47 get(key: K): V | undefined { 48 return this.memory.get(key); 49 } 50 getSignal(key: K): Promise<V> { 51 return new Promise<V>((resolve) => { 52 if (!this.signals.has(key)) { 53 this.signals.set(key, [resolve]); 54 return; 55 } 56 const signals = this.signals.get(key)!; 57 signals.push(resolve); 58 this.signals.set(key, signals); 59 }); 60 } 61 set(key: K, value: V): void { 62 this.memory.set(key, value); 63 this.storage.set(this.prefixed(key), value); 64 const signals = this.signals.get(key); 65 let signal = signals?.pop(); 66 while (signal) { 67 signal(value); 68 signal = signals?.pop(); 69 } 70 this.storage.flush(); // TODO: uh evil and fucked up (this whole file is evil honestly) 71 } 72 has(key: K): boolean { 73 return this.memory.has(key); 74 } 75 delete(key: K): void { 76 this.memory.delete(key); 77 this.storage.delete(this.prefixed(key)); 78 this.storage.flush(); 79 } 80 clear(): void { 81 this.memory.clear(); 82 this.storage.purge(); 83 this.storage.flush(); 84 } 85 86 private prefixed(key: K): string { 87 return this.prefix + key; 88 } 89 private unprefix(prefixed: string): string { 90 return prefixed.slice(this.prefix.length); 91 } 92}