UNPKG

sync-idb-kvs

Version:

Synchronous IndexedDB Key-Value Store(Requires async Initialization)

198 lines 6.95 kB
import MutablePromise from "mutable-promise"; const storeName = "kvStore"; export class SyncIDBStorage { getLoadingPromise(passive = false) { if (this.loadedAll) return Promise.resolve(); if (passive) return this.passiveLoadingPromise; this.loadingPromise = this.loadingPromise || this.asyncStorage.initDB(this).then(() => { this.loadedAll = true; this.passiveLoadingPromise.resolve(void 0); }); return this.loadingPromise; } static async create(dbName, initialData, opt = {}) { const a = new AsyncIDBStorage(dbName, initialData); const s = new SyncIDBStorage(a, dbName); opt.lazy = opt.lazy || 0; if (opt.lazy < 2) s.getLoadingPromise(); if (!opt.lazy) await s.getLoadingPromise(); return s; } ensureLoaded() { if (this.loadedAll) return; throw Object.assign(new Error(`${this.channelName}: Now loading. Try again later.`), { retryPromise: this.getLoadingPromise(), }); } constructor(asyncStorage, channelName) { this.asyncStorage = asyncStorage; this.channelName = channelName; this.storageType = "idb"; //private db: IDBDatabase | null = null; this.memoryCache = {}; // メモリキャッシュ //uncommitedCounter=new UncommitCounter(); this.loadedAll = false; this.passiveLoadingPromise = new MutablePromise(); } getItem(key) { return this.memoryCache[key] ?? null; } setItem(key, value) { this.ensureLoaded(); this.memoryCache[key] = value; //this._saveToIndexedDB(key, value); this.asyncStorage.setItem(key, value); } removeItem(key) { this.ensureLoaded(); delete this.memoryCache[key]; //this._deleteFromIndexedDB(key); this.asyncStorage.removeItem(key); } itemExists(key) { this.ensureLoaded(); return key in this.memoryCache; } keys() { this.ensureLoaded(); return Object.keys(this.memoryCache)[Symbol.iterator](); } async reload(key) { await this.getLoadingPromise(); //const value=await this._getFromIndexedDB(key); const value = await this.asyncStorage.getItem(key); if (value) { if (value !== this.memoryCache[key]) { this.memoryCache[key] = value; } } else { if (key in this.memoryCache) { delete this.memoryCache[key]; } } return value; } async waitForCommit() { return await this.asyncStorage.uncommitedCounter.wait(); } } export function idbReqPromise(request) { return new Promise((resolve, reject) => { request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); } export class AsyncIDBStorage { constructor(dbName = "SyncStorageDB", initialData) { this.dbName = dbName; this.initialData = initialData; this.db = null; this.uncommitedCounter = new UncommitCounter(); } async initDB(s) { return new Promise((resolve, reject) => { const request = indexedDB.open(this.dbName, 1); request.onupgradeneeded = (event) => { const db = event.target.result; if (!db.objectStoreNames.contains(storeName)) { db.createObjectStore(storeName); } }; request.onsuccess = (event) => { this.db = event.target.result; this.loadAllData(s).then(resolve).catch(reject); }; request.onerror = (event) => reject(event.target.error); }); } async loadAllData(s) { const transaction = this.db.transaction(storeName, "readonly"); const store = transaction.objectStore(storeName); // Get all keys and values in the same transaction const [keys, values] = await Promise.all([ idbReqPromise(store.getAllKeys()), idbReqPromise(store.getAll()) ]); // Both arrays have the same order keys.forEach((key, i) => { if (!(key in s.memoryCache)) { s.memoryCache[key] = values[i] ?? ""; } }); for (let key in this.initialData) { if (!(key in s.memoryCache)) { s.memoryCache[key] = this.initialData[key]; } } } async getItem(key) { return new Promise((resolve, reject) => { if (!this.db) return resolve(null); const transaction = this.db.transaction(storeName, "readonly"); const store = transaction.objectStore(storeName); const request = store.get(key); request.onsuccess = () => resolve(request.result ?? null); request.onerror = () => reject(request.error); }); } async setItem(key, value) { return new Promise((resolve, reject) => { this.uncommitedCounter.inc(); if (!this.db) return resolve(); const transaction = this.db.transaction(storeName, "readwrite"); const store = transaction.objectStore(storeName); const request = store.put(value, key); request.onsuccess = () => resolve(); request.onerror = (event) => reject(event.target.error); }).finally(() => { this.uncommitedCounter.dec(); }); } async removeItem(key) { return new Promise((resolve, reject) => { if (!this.db) return resolve(); this.uncommitedCounter.inc(); const transaction = this.db.transaction(storeName, "readwrite"); const store = transaction.objectStore(storeName); const request = store.delete(key); request.onsuccess = () => resolve(); request.onerror = (event) => reject(event.target.error); }).finally(() => { this.uncommitedCounter.dec(); }); } async waitForCommit() { return await this.uncommitedCounter.wait(); } } class UncommitCounter { constructor() { this.value = 0; } inc() { this.value++; if (!this.promise) this.promise = new MutablePromise(); } dec() { this.value--; if (this.value < 0) throw new Error("UncommitCounter: Invalid counter state."); if (this.value == 0) { if (!this.promise) throw new Error("UncommitCounter: Invalid promise state."); this.promise.resolve(); delete this.promise; } } async wait() { if (!this.promise) return; await this.promise; } } //# sourceMappingURL=index.js.map