Skip to content

Instantly share code, notes, and snippets.

@phistuck
Last active April 8, 2023 13:24
Show Gist options
  • Save phistuck/0d7a501b0704841f5109671327676e0e to your computer and use it in GitHub Desktop.
Save phistuck/0d7a501b0704841f5109671327676e0e to your computer and use it in GitHub Desktop.
A simplistic Promise-based API for IndexedDB
// License - no author warranty/responsibility/guarantee public domain where allowed. Otherwise -
// License - no author warranty/responsibility/guarantee, can do whatever you want with the code where allowed. Otherwise -
// License - MIT.
(function ()
{
// Let the fun begin.
"use strict";
const
/** @const {Object<string, string>} */
Name = {DB: "stuff", DETAILS_TABLE: "stuff-details", ID_KEY: "id"},
/** @const {Object<string, string>} */
Acts = {READWRITE: "readwrite"},
/** @const {Object<string, Function>} */
Actions =
{
THROW()
{
console.error.apply(console, arguments);
throw new Error();
},
LOG: console.log.bind(console)
};
class Store
{
/** @param {string} databaseName
@param {string} storeName
@param {string} indexKeyName */
constructor(databaseName, storeName, indexKeyName)
{
/** @type {IDBDatabase} */
this.database = null;
/** @type {string} */
this.databaseName = databaseName;
/** @type {string} */
this.storeName = storeName;
/** @type {string} */
this.indexKeyName = indexKeyName;
}
close()
{
if (!this.database)
{
return;
}
this.database.close();
this.database = null;
}
openDatabase(resolve, reject)
{
let
/** @type {IDBOpenDBRequest} */
database = indexedDB.open(this.databaseName, 1),
/** @type {boolean} */
waitForUpgrade = false;
database.onerror = () =>
{
reject();
};
database.onsuccess = () =>
{
if (waitForUpgrade)
{
return;
}
this.database = /** @type {IDBDatabase} */ (database.result);
resolve(this);
};
database.onupgradeneeded = () => {
waitForUpgrade = true;
this.database = /** @type {IDBDatabase} */ (database.result);
this.database
.createObjectStore(this.storeName, {keyPath: this.indexKeyName})
.transaction.oncomplete = () =>
{
resolve();
};
};
}
getObjectsByListUsingGetAll(acts, keys)
{
return new Promise((resolve, reject) =>
{
const actGetAll = acts.getAll();
actGetAll.onsuccess = () =>
{
const KEY_NAME = Name.ID_KEY;
resolve(actGetAll.result.reduce((objects, item) =>
{
const id = item[KEY_NAME];
if (keys.includes(id))
{
objects[id] = item;
}
return objects;
}, {}));
};
actGetAll.onerror = () =>
{
this.close();
reject(actGetAll.error);
};
});
}
getObjectsByListUsingCursor(acts, keys)
{
return new Promise((resolve, reject) =>
{
let
/** @type {IDBRequest<IDBCursorWithValue>} */
actCursor = acts.openCursor(),
/** @type {Object.<number, Object<string, ?>>} */
objects = {};
actCursor.onerror = () =>
{
this.close();
reject(actCursor.error);
}
actCursor.onsuccess = () =>
{
let result = actCursor.result;
if (result)
{
let data = result.value,
id = data[this.indexKeyName];
if (keys.indexOf(id) !== -1)
{
objects[id] = data;
}
result.continue();
}
else
{
this.close();
resolve(objects);
}
}
});
}
getObjectsByList(keys)
{
return new Promise((resolve, reject) =>
{
this.getActs().then(acts =>
{
if (acts.getAll)
{
this.getObjectsByListUsingGetAll(acts, keys).then(resolve, reject);
}
else
{
this.getObjectsByListUsingCursor(acts, keys).then(resolve, reject);
}
},
reject);
});
}
/** @private
@param {string=} scope
@returns {Promise<IDBObjectStore>} */
getActs(scope)
{
let get = () =>
{
var parameters = [this.storeName];
if (scope)
{
parameters.push(scope);
}
return this.database.transaction.apply(this.database, parameters).objectStore(this.storeName);
};
if (!this.database)
{
return this.initialize().then(get);
}
return Promise.resolve(get());
}
/** @param {number} id */
getObjectByKey(id)
{
return new Promise((resolve, reject) =>
{
this.getActs().then(acts =>
{
let actGet = acts.get(id);
actGet.onerror = () => reject(actGet.error);
actGet.onsuccess = () =>
{
if (actGet.result)
{
resolve(actGet.result);
}
else
{
reject(new ReferenceError(`The key ${id} did not yield and object.`));
}
}
});
});
}
addOrUpdateObject(data)
{
return new Promise((resolve, reject) =>
{
this.getActs(Acts.READWRITE).then(acts =>
{
let addOrUpdateAct = acts.put(data);
addOrUpdateAct.onerror = () =>
{
this.close();
reject(addOrUpdateAct.error);
}
addOrUpdateAct.onsuccess = () =>
{
this.close();
resolve(data);
};
},
reject);
});
}
initialize()
{
return new Promise(this.openDatabase.bind(this));
}
}
// Usage
const store = new Store(Name.DB, Name.DETAILS_TABLE, Name.ID_KEY);
await store.addOrUpdateObject({[Name.ID_KEY]: 1, stuff: 'fun'});
await store.getObjectByKey(1);
await store.getObjectsByList([1, 2, 3, 4, 5]);
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment