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 for (const signal of this.signals.get(key) ?? []) { 65 signal(value); 66 } 67 this.storage.flush(); // TODO: uh evil and fucked up (this whole file is evil honestly) 68 } 69 has(key: K): boolean { 70 return this.memory.has(key); 71 } 72 delete(key: K): void { 73 this.memory.delete(key); 74 this.storage.delete(this.prefixed(key)); 75 this.storage.flush(); 76 } 77 clear(): void { 78 this.memory.clear(); 79 this.storage.purge(); 80 this.storage.flush(); 81 } 82 83 private prefixed(key: K): string { 84 return this.prefix + key; 85 } 86 private unprefix(prefixed: string): string { 87 return prefixed.slice(this.prefix.length); 88 } 89}