/* * Simple async key-value storage interface plus implementations. * * All methods are Promise-based to accommodate back-ends like * IndexedDB, SQLite, remote HTTP APIs, etc. For environments where you only * need ephemeral data (tests, Node scripts) the InMemoryStorageAdapter can be * used directly. */ export interface StorageAdapter { /** Retrieve a value by key. */ get(key: string): Promise /** Store a value by key. */ put(key: string, value: T): Promise /** Delete a stored value by key. */ del(key: string): Promise /** List all keys that start with the given prefix. */ list(prefix?: string): Promise } export class InMemoryStorageAdapter implements StorageAdapter { private store = new Map() async get(key: string): Promise { return this.store.get(key) as T | undefined } async put(key: string, value: T): Promise { this.store.set(key, value) } async del(key: string): Promise { this.store.delete(key) } async list(prefix = ""): Promise { const keys: string[] = [] const storeKeys = Array.from(this.store.keys()) for (const k of storeKeys) { if (k.startsWith(prefix)) keys.push(k) } return keys } } export class LocalStorageAdapter implements StorageAdapter { private keyPrefix: string constructor(keyPrefix = "session_") { this.keyPrefix = keyPrefix } private getFullKey(key: string): string { return `${this.keyPrefix}${key}` } async get(key: string): Promise { try { const item = localStorage.getItem(this.getFullKey(key)) return item ? JSON.parse(item) : undefined } catch { return undefined } } async put(key: string, value: T): Promise { try { localStorage.setItem(this.getFullKey(key), JSON.stringify(value)) } catch (e) { const err = e instanceof Error ? e : new Error(String(e)) throw err } } async del(key: string): Promise { try { localStorage.removeItem(this.getFullKey(key)) } catch { // Ignore deletion failures } } async list(prefix = ""): Promise { const keys: string[] = [] const searchPrefix = this.getFullKey(prefix) try { for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i) if (key && key.startsWith(searchPrefix)) { // Remove our prefix to return the original key keys.push(key.substring(this.keyPrefix.length)) } } } catch { // Ignore list failures } return keys } }