Something went wrong. Try again.
appview-less bluesky client
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225import { createCache } from 'async-cache-dedupe';
const DB_NAME = 'nucleus-cache';const STORE_NAME = 'keyvalue';const DB_VERSION = 1;
type WriteOp = | { type: 'put'; key: string; value: { value: unknown; expires: number }; resolve: () => void; reject: (err: unknown) => void; } | { type: 'delete'; key: string; resolve: () => void; reject: (err: unknown) => void };type ReadOp = { key: string; resolve: (val: unknown) => void; reject: (err: unknown) => void;};
class IDBStorage { private dbPromise: Promise<IDBDatabase> | null = null;
private getBatch: ReadOp[] = []; private writeBatch: WriteOp[] = [];
private getFlushScheduled = false; private writeFlushScheduled = false;
constructor() { if (typeof indexedDB === 'undefined') return;
this.dbPromise = new Promise((resolve, reject) => { const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = () => { console.error('IDB open error:', request.error); reject(request.error); };
request.onsuccess = () => resolve(request.result);
request.onupgradeneeded = (event) => { const db = (event.target as IDBOpenDBRequest).result; if (!db.objectStoreNames.contains(STORE_NAME)) db.createObjectStore(STORE_NAME); }; }); }
async get(key: string): Promise<unknown> { // checking in-flight writes for (let i = this.writeBatch.length - 1; i >= 0; i--) { const op = this.writeBatch[i]; if (op.key === key) { if (op.type === 'delete') return undefined; if (op.type === 'put') { // if expired we dont want it if (op.value.expires < Date.now()) return undefined; return op.value.value; } } }
if (!this.dbPromise) return undefined;
return new Promise((resolve, reject) => { this.getBatch.push({ key, resolve, reject }); this.scheduleGetFlush(); }); }
private scheduleGetFlush() { if (this.getFlushScheduled) return; this.getFlushScheduled = true; queueMicrotask(() => this.flushGetBatch()); }
private async flushGetBatch() { this.getFlushScheduled = false; const batch = this.getBatch; this.getBatch = [];
if (batch.length === 0) return;
try { const db = await this.dbPromise; if (!db) throw new Error('DB not available');
const transaction = db.transaction(STORE_NAME, 'readonly'); const store = transaction.objectStore(STORE_NAME);
batch.forEach(({ key, resolve }) => { try { const request = store.get(key); request.onsuccess = () => { const result = request.result; if (!result) { resolve(undefined); return; } if (result.expires < Date.now()) { // Fire-and-forget removal for expired items this.remove(key).catch(() => {}); resolve(undefined); return; } resolve(result.value); }; request.onerror = () => resolve(undefined); } catch { resolve(undefined); } }); } catch (error) { batch.forEach(({ reject }) => reject(error)); } }
async set(key: string, value: unknown, ttl: number): Promise<void> { if (!this.dbPromise) return;
const expires = Date.now() + ttl * 1000; const storageValue = { value, expires };
return new Promise((resolve, reject) => { this.writeBatch.push({ type: 'put', key, value: storageValue, resolve, reject }); this.scheduleWriteFlush(); }); }
async remove(key: string): Promise<void> { if (!this.dbPromise) return;
return new Promise((resolve, reject) => { this.writeBatch.push({ type: 'delete', key, resolve, reject }); this.scheduleWriteFlush(); }); }
private scheduleWriteFlush() { if (this.writeFlushScheduled) return; this.writeFlushScheduled = true; queueMicrotask(() => this.flushWriteBatch()); }
private async flushWriteBatch() { this.writeFlushScheduled = false; const batch = this.writeBatch; this.writeBatch = [];
if (batch.length === 0) return;
try { const db = await this.dbPromise; if (!db) throw new Error('DB not available');
const transaction = db.transaction(STORE_NAME, 'readwrite'); const store = transaction.objectStore(STORE_NAME);
batch.forEach((op) => { try { let request: IDBRequest; if (op.type === 'put') request = store.put(op.value, op.key); else request = store.delete(op.key);
request.onsuccess = () => op.resolve(); request.onerror = () => op.reject(request.error); } catch (err) { op.reject(err); } }); } catch (error) { batch.forEach(({ reject }) => reject(error)); } }
async clear(): Promise<void> { if (!this.dbPromise) return; try { const db = await this.dbPromise; return new Promise<void>((resolve, reject) => { const transaction = db.transaction(STORE_NAME, 'readwrite'); const store = transaction.objectStore(STORE_NAME); const request = store.clear();
request.onerror = () => reject(request.error); request.onsuccess = () => resolve(); }); } catch (e) { console.error('IDB clear error', e); } }
async exists(key: string): Promise<boolean> { return (await this.get(key)) !== undefined; }
async invalidate(key: string): Promise<void> { return this.remove(key); }
// noops // eslint-disable-next-line @typescript-eslint/no-unused-vars async getTTL(key: string): Promise<void> { return; } async refresh(): Promise<void> { return; }}
export const ttl = 60 * 60 * 3; // 3 hours
export const cache = createCache({ storage: { type: 'custom', options: { storage: new IDBStorage() } }, ttl, onError: (err) => console.error(err)});