/**
* Abstraction around IndexedDB storage to provide the same simple interface as
* localStorage. Because I'm lazy and I don't want to deal with real database
* magic. It will create a database with a single table. Both the database and
* the table will have the name you pass to `connect`. The key-value pairs will
* be stored in that table.
*
* Main differences with the localStorage API: this is async and it can store
* complicated objects, not just strings.
*
* @example
* import IndexedDB from 'indexedDB.js';
* const db = await IndexedDB.connect("my-database");
* await db.setItem('some key', 'some value');
* console.log(await db.getItem('some key'));
* await db.removeItem('some key');
*
* @module
*/
export default class IndexedDB {
constructor(db, name) {
this._db = db;
this._name = name;
}
/**
* Connect to an IndexedDB database
* @public
* @param {string} name Name of the database and the table to use
* @returns {Promise<IndexedDB>} A promise to a connected database
*/
static async connect(name) {
return new Promise((resolve, reject) => {
const dbOpenRequest = indexedDB.open(name, 4);
dbOpenRequest.addEventListener("error", reject);
dbOpenRequest.addEventListener("upgradeneeded", (event) => {
const db = event.target.result;
const objectStore = db.createObjectStore(name, { keyPath: "key" });
});
dbOpenRequest.addEventListener("success", (event) => {
resolve(new IndexedDB(dbOpenRequest.result, name));
});
});
}
/**
* Store or overwrite the `value` for the given `key`
* @param {string} key Where to store the value
* @param {any} value The value to store
* @returns {Promise} A promise that resolves if the value is stored
*/
async setItem(key, value) {
return new Promise((resolve, reject) => {
const transaction = this._db.transaction(this._name, "readwrite");
transaction.addEventListener("complete", () => resolve());
transaction.addEventListener("error", reject);
const objectStore = transaction.objectStore(this._name);
const objectStoreRequest = objectStore.get(key);
objectStoreRequest.addEventListener("error", reject);
objectStoreRequest.addEventListener("success", () => {
const data = objectStoreRequest.result;
if (data) {
data.value = value;
const updateValueRequest = objectStore.put(data);
updateValueRequest.addEventListener("error", reject);
updateValueRequest.addEventListener("success", () => resolve());
} else {
objectStore.add({ key, value });
resolve();
}
});
});
}
/**
* Get the value that belongs to this key from the database
* @param {string} key What to retrieve
* @returns {Promise<any>} A promise that resolves to the value
*/
async getItem(key) {
return new Promise((resolve, reject) => {
const transaction = this._db.transaction(this._name, "readonly");
transaction.addEventListener("error", reject);
const objectStore = transaction.objectStore(this._name);
const objectStoreRequest = objectStore.get(key);
objectStoreRequest.addEventListener("error", reject);
objectStoreRequest.addEventListener("success", () => {
resolve(objectStoreRequest.result?.value);
});
});
}
/**
* Delete the value that belongs to this key from the database
* @param {string} key What to delete
* @returns {Promise} A promise that resolves if the value is deleted
*/
async removeItem(key) {
return new Promise((resolve, reject) => {
const transaction = this._db.transaction(this._name, "readwrite");
transaction.addEventListener("complete", () => resolve());
transaction.addEventListener("error", reject);
transaction.objectStore(this._name).delete(key);
});
}
}