UNPKG

keyval-db

Version:

Indexdb, the way God intended it to be.

439 lines (438 loc) 23.6 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var _IDB_instances, _IDB_db_name, _IDB_storeName, _IDB_hasObjectStore, _IDB_db_Q, _IDB_runStores_Q, _IDB_update_db_status, _IDB_update_db, _IDB_updateDBinMap, _IDB_markObjectStoreConnected, _IDB_setupRequests, _IDB_bumpVersion, _IDB_objectStoreExists, _IDB_checkDBexists, _IDB_handleRetry, _IDB_handleNoDB, _IDB_process_get, _IDB_process_get_all, _IDB_process_get_keys, _IDB_process_set, _IDB_process_set_multiple, _IDB_process_delete, _IDB_process_db_clear, _IDB_init, _IDB_pre_init, _IDB_runOrQueue; const dbsMap = new Map(); /** * A class for interacting with IndexedDB through a simple key-value interface * * This class provides a Promise-based API for storing and retrieving data from * IndexedDB, with automatic retry logic and connection management. * * @example * ```typescript * const db = new IDB('myDatabase', 'myStore'); * * // Store a value * await db.set('key1', { data: 'example' }); * * // Retrieve a value * const data = await db.get<{ data: string }>('key1'); * ``` */ export class IDB { /** * Creates a new IDB instance to interact with IndexedDB * @param db_name - The name of the IndexedDB database to connect to * @param storeName - The name of the object store to use within the database */ constructor(db_name, storeName) { _IDB_instances.add(this); _IDB_db_name.set(this, void 0); _IDB_storeName.set(this, void 0); _IDB_hasObjectStore.set(this, false); _IDB_db_Q.set(this, new Set()); __classPrivateFieldSet(this, _IDB_db_name, db_name, "f"); __classPrivateFieldSet(this, _IDB_storeName, storeName, "f"); __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_pre_init).call(this); } /** * Retrieves a value from the database by its key * @template T - The type of value to be returned * @param key - The key to look up in the database * @returns A promise that resolves to the value of type T associated with the key */ get(key) { return new Promise((resolve, reject) => { __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_runOrQueue).call(this, () => __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_process_get).call(this, resolve, reject, key), reject); }); } /** * Retrieves all values stored in the database * @template T - The type of array to be returned, must extend Array * @returns A promise that resolves to an array of all values in the database */ // eslint-disable-next-line @typescript-eslint/no-explicit-any getValues() { return new Promise((resolve, reject) => { __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_runOrQueue).call(this, () => __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_process_get_all).call(this, resolve, reject), reject); }); } /** * Retrieves all keys stored in the database * @returns A promise that resolves to an array of all keys in the database */ getKeys() { return new Promise((resolve, reject) => { __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_runOrQueue).call(this, () => __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_process_get_keys).call(this, resolve, reject), reject); }); } /** * Stores a value in the database with the specified key * @param key - The key to store the value under * @param value - The value to store * @returns A promise that resolves to true when the operation is complete */ set(key, value) { return new Promise((resolve, reject) => { __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_runOrQueue).call(this, () => __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_process_set).call(this, resolve, reject, key, value), reject); }); } /** * Stores multiple key-value pairs in the database in a single transaction * @template T - The type of values being stored * @param items - An array of objects containing key-value pairs to store * @returns A promise that resolves to true when all items have been stored */ setMultiple(items) { return new Promise((resolve, reject) => { __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_runOrQueue).call(this, () => __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_process_set_multiple).call(this, resolve, reject, items), reject); }); } /** * Deletes a value from the database by its key * @param key - The key of the value to delete * @returns A promise that resolves to true when the value has been deleted */ del(key) { return new Promise((resolve, reject) => { __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_runOrQueue).call(this, () => __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_process_delete).call(this, resolve, reject, key), reject); }); } /** * Clears all data from the current object store * @returns A promise that resolves to true when the store has been cleared */ clearStore() { return new Promise((resolve, reject) => { __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_runOrQueue).call(this, () => __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_process_db_clear).call(this, resolve, reject), reject); }); } /** * Deletes the entire database * @returns A promise that resolves to true when the database has been deleted * @throws Event if the database cannot be dropped because it is currently bumping version */ dropDB() { return new Promise((resolve, reject) => { const item = dbsMap.get(__classPrivateFieldGet(this, _IDB_db_name, "f")); if (item.status === 'upgrading') { reject(new Event('Cannot drop DB while bumping version')); return; } const idb = item === null || item === void 0 ? void 0 : item.db; console.log('drop', __classPrivateFieldGet(this, _IDB_db_name, "f"), __classPrivateFieldGet(this, _IDB_storeName, "f")); idb === null || idb === void 0 ? void 0 : idb.close(); const deleteRequest = indexedDB.deleteDatabase(__classPrivateFieldGet(this, _IDB_db_name, "f")); deleteRequest.onsuccess = () => { const item = dbsMap.get(__classPrivateFieldGet(this, _IDB_db_name, "f")); if (!item) { return; } resolve(true); if ((idb === null || idb === void 0 ? void 0 : idb.objectStoreNames.length) === 0) { dbsMap.delete(__classPrivateFieldGet(this, _IDB_db_name, "f")); } }; deleteRequest.onerror = (e) => reject(e); }); } } _IDB_db_name = new WeakMap(), _IDB_storeName = new WeakMap(), _IDB_hasObjectStore = new WeakMap(), _IDB_db_Q = new WeakMap(), _IDB_instances = new WeakSet(), _IDB_runStores_Q = function _IDB_runStores_Q() { const item = dbsMap.get(__classPrivateFieldGet(this, _IDB_db_name, "f")); for (const fn of item.stores_Q) { const v = fn(); if (v === false) { break; } else { item.stores_Q.delete(fn); } } }, _IDB_update_db_status = function _IDB_update_db_status(s) { const i = dbsMap.get(__classPrivateFieldGet(this, _IDB_db_name, "f")); i.status = s; dbsMap.set(__classPrivateFieldGet(this, _IDB_db_name, "f"), i); }, _IDB_update_db = function _IDB_update_db(idb) { const i = dbsMap.get(__classPrivateFieldGet(this, _IDB_db_name, "f")); i.db = idb; dbsMap.set(__classPrivateFieldGet(this, _IDB_db_name, "f"), i); }, _IDB_updateDBinMap = function _IDB_updateDBinMap(evnt) { const idb = evnt.target.result; if (!idb) { return; } __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_update_db).call(this, idb); }, _IDB_markObjectStoreConnected = function _IDB_markObjectStoreConnected() { __classPrivateFieldSet(this, _IDB_hasObjectStore, true, "f"); for (const fn of __classPrivateFieldGet(this, _IDB_db_Q, "f")) { __classPrivateFieldGet(this, _IDB_db_Q, "f").delete(fn); fn(); } __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_update_db_status).call(this, 'connected'); return true; }, _IDB_setupRequests = function _IDB_setupRequests(req) { __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_update_db_status).call(this, 'connecting'); if (req.readyState === 'done') { __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_update_db).call(this, req.result); __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_markObjectStoreConnected).call(this); return; } req.onupgradeneeded = (event) => { const idb = event.target.result; if (!idb) return; if (idb.objectStoreNames.length === 0 || idb.objectStoreNames.contains(__classPrivateFieldGet(this, _IDB_storeName, "f")) === false) { idb.createObjectStore(__classPrivateFieldGet(this, _IDB_storeName, "f")); return; } }; req.onsuccess = (event) => { __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_updateDBinMap).call(this, event); __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_markObjectStoreConnected).call(this); __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_runStores_Q).call(this); }; req.onerror = (event) => { __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_updateDBinMap).call(this, event); __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_objectStoreExists).call(this); }; }, _IDB_bumpVersion = function _IDB_bumpVersion() { const item = dbsMap.get(__classPrivateFieldGet(this, _IDB_db_name, "f")); const idb = item === null || item === void 0 ? void 0 : item.db; if (!idb) { return; } item.status = 'upgrading'; idb.close(); dbsMap.set(__classPrivateFieldGet(this, _IDB_db_name, "f"), item); const req = indexedDB.open(__classPrivateFieldGet(this, _IDB_db_name, "f"), idb.version + 1); __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_setupRequests).call(this, req); }, _IDB_objectStoreExists = function _IDB_objectStoreExists() { const item = dbsMap.get(__classPrivateFieldGet(this, _IDB_db_name, "f")); const idb = item === null || item === void 0 ? void 0 : item.db; if (!idb) { __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_pre_init).call(this); return false; } if (idb.objectStoreNames.contains(__classPrivateFieldGet(this, _IDB_storeName, "f")) === false) { item.stores_Q.add(() => { const dbItem = dbsMap.get(__classPrivateFieldGet(this, _IDB_db_name, "f")); const idb = dbItem === null || dbItem === void 0 ? void 0 : dbItem.db; if (!idb) { return false; } if (idb.objectStoreNames.contains(__classPrivateFieldGet(this, _IDB_storeName, "f")) === false) { idb.createObjectStore(__classPrivateFieldGet(this, _IDB_storeName, "f")); dbItem.db = idb; dbsMap.set(__classPrivateFieldGet(this, _IDB_db_name, "f"), dbItem); __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_markObjectStoreConnected).call(this); } return true; }); __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_bumpVersion).call(this); return false; } else { __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_markObjectStoreConnected).call(this); return true; } }, _IDB_checkDBexists = function _IDB_checkDBexists() { return __awaiter(this, void 0, void 0, function* () { const dbs = yield indexedDB.databases(); const has_db = dbs.find((db) => db.name === __classPrivateFieldGet(this, _IDB_db_name, "f")); if (!has_db) { __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_update_db_status).call(this, 'init'); __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_init).call(this); } else { __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_objectStoreExists).call(this); } }); }, _IDB_handleRetry = function _IDB_handleRetry(err, cb, retryCount, reject) { if (retryCount < 5 && err instanceof DOMException) { if (err.message.includes('database connection is closing') || err.message.includes('the specified object stores was not found')) { __classPrivateFieldGet(this, _IDB_db_Q, "f").add(cb); __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_update_db_status).call(this, 'upgrading'); __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_checkDBexists).call(this); } else { reject(err); } } else { reject(err); } }, _IDB_handleNoDB = function _IDB_handleNoDB(cb, retryCount, reject) { if (retryCount < 5) { __classPrivateFieldGet(this, _IDB_db_Q, "f").add(cb); __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_checkDBexists).call(this); } else { reject(new Event('Database not found')); } }, _IDB_process_get = function _IDB_process_get(resolve, reject, key, retryCount = -1) { var _a; try { const idb = (_a = dbsMap.get(__classPrivateFieldGet(this, _IDB_db_name, "f"))) === null || _a === void 0 ? void 0 : _a.db; const req = idb.transaction([__classPrivateFieldGet(this, _IDB_storeName, "f")], 'readonly').objectStore(__classPrivateFieldGet(this, _IDB_storeName, "f")).get(key); req.onsuccess = () => resolve(req.result); req.onerror = (e) => reject(e); } catch (err) { retryCount = retryCount + 1; __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_handleRetry).call(this, err, () => __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_process_get).call(this, resolve, reject, key, retryCount), retryCount, reject); } }, _IDB_process_get_all = function _IDB_process_get_all(resolve, reject, retryCount = -1) { var _a; try { const idb = (_a = dbsMap.get(__classPrivateFieldGet(this, _IDB_db_name, "f"))) === null || _a === void 0 ? void 0 : _a.db; const req = idb.transaction([__classPrivateFieldGet(this, _IDB_storeName, "f")], 'readonly').objectStore(__classPrivateFieldGet(this, _IDB_storeName, "f")).getAll(); req.onsuccess = () => resolve(req.result); req.onerror = (e) => reject(e); } catch (err) { retryCount = retryCount + 1; __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_handleRetry).call(this, err, () => __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_process_get_all).call(this, resolve, reject, retryCount), retryCount, reject); } }, _IDB_process_get_keys = function _IDB_process_get_keys(resolve, reject, retryCount = -1) { var _a; try { const idb = (_a = dbsMap.get(__classPrivateFieldGet(this, _IDB_db_name, "f"))) === null || _a === void 0 ? void 0 : _a.db; const req = idb.transaction([__classPrivateFieldGet(this, _IDB_storeName, "f")], 'readonly').objectStore(__classPrivateFieldGet(this, _IDB_storeName, "f")).getAllKeys(); req.onsuccess = () => resolve(req.result); req.onerror = (e) => reject(e); } catch (err) { retryCount = retryCount + 1; __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_handleRetry).call(this, err, () => __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_process_get_keys).call(this, resolve, reject, retryCount), retryCount, reject); } }, _IDB_process_set = function _IDB_process_set(resolve, reject, key, value, retryCount = -1) { var _a; try { const idb = (_a = dbsMap.get(__classPrivateFieldGet(this, _IDB_db_name, "f"))) === null || _a === void 0 ? void 0 : _a.db; const req = idb.transaction([__classPrivateFieldGet(this, _IDB_storeName, "f")], 'readwrite').objectStore(__classPrivateFieldGet(this, _IDB_storeName, "f")).put(value, key); req.onsuccess = () => resolve(true); req.onerror = (e) => reject(e); } catch (err) { retryCount = retryCount + 1; __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_handleRetry).call(this, err, () => __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_process_set).call(this, resolve, reject, key, value, retryCount), retryCount, reject); } }, _IDB_process_set_multiple = function _IDB_process_set_multiple(resolve, reject, items, retryCount = -1) { var _a; try { const idb = (_a = dbsMap.get(__classPrivateFieldGet(this, _IDB_db_name, "f"))) === null || _a === void 0 ? void 0 : _a.db; const tx = idb.transaction([__classPrivateFieldGet(this, _IDB_storeName, "f")], 'readwrite'); if (items.length > 0) { for (const item of items) { tx.objectStore(__classPrivateFieldGet(this, _IDB_storeName, "f")).put(item.value, item.key); tx.onerror = (e) => reject(e); } tx.oncomplete = () => resolve(true); } } catch (err) { retryCount = retryCount + 1; __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_handleRetry).call(this, err, () => __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_process_set_multiple).call(this, resolve, reject, items, retryCount), retryCount, reject); } }, _IDB_process_delete = function _IDB_process_delete(resolve, reject, key, retryCount = -1) { var _a; try { const idb = (_a = dbsMap.get(__classPrivateFieldGet(this, _IDB_db_name, "f"))) === null || _a === void 0 ? void 0 : _a.db; const req = idb.transaction([__classPrivateFieldGet(this, _IDB_storeName, "f")], 'readwrite').objectStore(__classPrivateFieldGet(this, _IDB_storeName, "f")).delete(key); req.onsuccess = () => resolve(true); req.onerror = (e) => reject(e); } catch (err) { retryCount = retryCount + 1; __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_handleRetry).call(this, err, () => __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_process_delete).call(this, resolve, reject, key, retryCount), retryCount, reject); } }, _IDB_process_db_clear = function _IDB_process_db_clear(resolve, reject, retryCount = -1) { var _a; try { const idb = (_a = dbsMap.get(__classPrivateFieldGet(this, _IDB_db_name, "f"))) === null || _a === void 0 ? void 0 : _a.db; const req = idb.transaction([__classPrivateFieldGet(this, _IDB_storeName, "f")], 'readwrite').objectStore(__classPrivateFieldGet(this, _IDB_storeName, "f")).clear(); req.onsuccess = () => resolve(true); req.onerror = (e) => reject(e); } catch (err) { retryCount = retryCount + 1; __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_handleRetry).call(this, err, () => __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_process_db_clear).call(this, resolve, reject, retryCount), retryCount, reject); } }, _IDB_init = function _IDB_init() { return __awaiter(this, void 0, void 0, function* () { let req; let has_db; const dbs = yield indexedDB.databases(); if (dbs.length === 0) { req = indexedDB.open(__classPrivateFieldGet(this, _IDB_db_name, "f"), 1); } else { const item = dbsMap.get(__classPrivateFieldGet(this, _IDB_db_name, "f")); if (item.status !== 'init') { if (item.status === 'connected') { return __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_objectStoreExists).call(this); } item.stores_Q.add(() => __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_objectStoreExists).call(this)); return; } else { has_db = dbs.find((db) => db.name === __classPrivateFieldGet(this, _IDB_db_name, "f")); if (has_db) { has_db = undefined; req = indexedDB.open(__classPrivateFieldGet(this, _IDB_db_name, "f")); } else { req = indexedDB.open(__classPrivateFieldGet(this, _IDB_db_name, "f"), 1); } } } if (!has_db) { const item = dbsMap.get(__classPrivateFieldGet(this, _IDB_db_name, "f")); item.req = req; dbsMap.set(__classPrivateFieldGet(this, _IDB_db_name, "f"), item); __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_setupRequests).call(this, req); } }); }, _IDB_pre_init = function _IDB_pre_init() { const item = dbsMap.get(__classPrivateFieldGet(this, _IDB_db_name, "f")); if (!item) { dbsMap.set(__classPrivateFieldGet(this, _IDB_db_name, "f"), { db: undefined, // @ts-expect-error this is fine for now req: undefined, stores_Q: new Set(), status: 'init' }); } __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_init).call(this); }, _IDB_runOrQueue = function _IDB_runOrQueue(fn, reject) { const item = dbsMap.get(__classPrivateFieldGet(this, _IDB_db_name, "f")); if ((item && item.status !== 'connected') || __classPrivateFieldGet(this, _IDB_hasObjectStore, "f") === false) { __classPrivateFieldGet(this, _IDB_db_Q, "f").add(fn); } else if (!(item === null || item === void 0 ? void 0 : item.db)) { return __classPrivateFieldGet(this, _IDB_instances, "m", _IDB_handleNoDB).call(this, fn, 1, reject); } else { fn(); } };