cache.ts 1.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243
  1. import { openDB, type IDBPDatabase } from 'idb'
  2. const memory = new Map<string, unknown>()
  3. let databasePromise: Promise<IDBPDatabase | null> | undefined
  4. async function database(): Promise<IDBPDatabase | null> {
  5. if (typeof indexedDB === 'undefined') return null
  6. databasePromise ??= openDB('hermes-yaoyao-cache', 1, {
  7. upgrade(database) {
  8. if (!database.objectStoreNames.contains('records')) database.createObjectStore('records')
  9. },
  10. }).catch(() => null)
  11. return databasePromise
  12. }
  13. export class ScopedCache<T> {
  14. constructor(private readonly namespace: string, private readonly maximumBytes = 16 * 1024 * 1024) {}
  15. private key(scope: string, key: string): string { return `${this.namespace}:${scope}:${key}` }
  16. async get(scope: string, key: string): Promise<T | undefined> {
  17. const storageKey = this.key(scope, key)
  18. const db = await database()
  19. if (!db) return memory.get(storageKey) as T | undefined
  20. try { return await db.get('records', storageKey) as T | undefined } catch { return memory.get(storageKey) as T | undefined }
  21. }
  22. async set(scope: string, key: string, value: T): Promise<void> {
  23. const serialized = JSON.stringify(value)
  24. if (new Blob([serialized]).size > this.maximumBytes) return
  25. const storageKey = this.key(scope, key)
  26. memory.set(storageKey, value)
  27. const db = await database()
  28. if (db) await db.put('records', value, storageKey).catch(() => undefined)
  29. }
  30. async delete(scope: string, key: string): Promise<void> {
  31. const storageKey = this.key(scope, key)
  32. memory.delete(storageKey)
  33. const db = await database()
  34. if (db) await db.delete('records', storageKey).catch(() => undefined)
  35. }
  36. }