UNPKG

sync-idb-kvs

Version:

Synchronous IndexedDB Key-Value Store(Requires async Initialization)

117 lines (116 loc) 5.08 kB
export interface IStorage { setItem(key: string, value: string): void; getItem(key: string): string | null; removeItem(key: string): void; itemExists(key: string): boolean; keys(): IterableIterator<string>; reload(key:string):Promise<string|null>; } export class SyncIDBStorage implements IStorage { private db: IDBDatabase | null = null; memoryCache: Record<string, string> = {}; // メモリキャッシュ uncommited=0; static async create(dbName = "SyncStorageDB", storeName = "kvStore"): Promise<SyncIDBStorage> { const s=new SyncIDBStorage(dbName, storeName); await s._initDB(); return s; } constructor( public dbName = "SyncStorageDB", public storeName = "kvStore") {} private async _initDB(): Promise<void> { return new Promise<void>((resolve, reject) => { const request = indexedDB.open(this.dbName, 1); request.onupgradeneeded = (event: IDBVersionChangeEvent) => { const db = (event.target as IDBOpenDBRequest).result; if (!db.objectStoreNames.contains(this.storeName)) { db.createObjectStore(this.storeName); } }; request.onsuccess = (event: Event) => { this.db = (event.target as IDBOpenDBRequest).result; this._loadAllData().then(resolve).catch(reject); }; request.onerror = (event: Event) => reject((event.target as IDBOpenDBRequest).error); }); } private async _loadAllData(): Promise<void> { return new Promise((resolve, reject) => { const transaction = this.db!.transaction(this.storeName, "readonly"); const store = transaction.objectStore(this.storeName); const request = store.getAllKeys(); request.onsuccess = async () => { const keys = request.result as string[]; const values = await Promise.all(keys.map(key => this._getFromIndexedDB(key))); keys.forEach((key, i) => { if (!(key in this.memoryCache)) { this.memoryCache[key] = values[i] ?? ""; } }); resolve(); }; request.onerror = (event) => reject((event.target as IDBRequest).error); }); } getItem(key: string): string | null { return this.memoryCache[key] ?? null; } setItem(key: string, value: string): void { this.memoryCache[key] = value; this._saveToIndexedDB(key, value); } removeItem(key: string): void { delete this.memoryCache[key]; this._deleteFromIndexedDB(key); } itemExists(key: string): boolean { return key in this.memoryCache; } keys(): IterableIterator<string> { return Object.keys(this.memoryCache)[Symbol.iterator](); } async reload(key: string): Promise<string|null> { const value=await this._getFromIndexedDB(key); if (value){ if (value!==this.memoryCache[key]){ this.memoryCache[key]=value; } } else { if (key in this.memoryCache) { delete this.memoryCache[key]; } } return value; } private async _getFromIndexedDB(key: string): Promise<string | null> { return new Promise((resolve, reject) => { if (!this.db) return resolve(null); const transaction = this.db.transaction(this.storeName, "readonly"); const store = transaction.objectStore(this.storeName); const request = store.get(key); request.onsuccess = () => resolve(request.result ?? null); request.onerror = () => reject(request.error); }); } private async _saveToIndexedDB(key: string, value: string): Promise<void> { return new Promise<void>((resolve, reject) => { this.uncommited++; if (!this.db) return resolve(); const transaction = this.db.transaction(this.storeName, "readwrite"); const store = transaction.objectStore(this.storeName); const request = store.put(value, key); request.onsuccess = () => resolve(); request.onerror = (event) => reject((event.target as IDBRequest).error); }).finally(()=>{this.uncommited--;}); } private async _deleteFromIndexedDB(key: string): Promise<void> { return new Promise((resolve, reject) => { if (!this.db) return resolve(); const transaction = this.db.transaction(this.storeName, "readwrite"); const store = transaction.objectStore(this.storeName); const request = store.delete(key); request.onsuccess = () => resolve(); request.onerror = (event) => reject((event.target as IDBRequest).error); }); } }