Created
November 18, 2020 16:40
-
-
Save bdombro/31563cebd5186198654bef62585af0cc to your computer and use it in GitHub Desktop.
KeyValueStore.ts - Class to make Indexed Database's easy for Key-Value Stores with maxAge feature
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Class to make Indexed Database's easy for Key-Value Stores | |
* | |
* In addition to wrapping IDB, it also adds a feature for maxAge, | |
* which can discard records if createdAt + maxAge > now | |
* | |
* Adapted from https://github.com/elias551/simple-kvs | |
* - Adds timestamp field and garbage collection feature. | |
*/ | |
export default class KeyValueStore { | |
config: Config = { | |
dbName: "kv", | |
tableName: "default", | |
maxAge: -1, | |
} | |
store: IDBObjectStore | undefined; | |
constructor(config: Partial<Config>) { | |
this.config = {...this.config, ...config}; | |
this.getStore().then(() => { | |
if (this.config.maxAge > 0) setInterval(this.clearExpired, 60000); | |
}) | |
} | |
public async get <T>(key: string) { | |
const store = await this.getStore() | |
return new Promise<T | undefined>((resolve, reject) => { | |
const request = store.get(key); | |
request.onerror = reject; | |
request.onsuccess = function() { | |
let result = this.result?.v; | |
resolve(typeof result !== "undefined" ? (result as T) : undefined); | |
}; | |
}) | |
} | |
public async getKeys () { | |
const store = await this.getStore() | |
return new Promise((resolve, reject) => { | |
const request = store.getAllKeys(); | |
request.onerror = reject; | |
request.onsuccess = function() { | |
const keys = this.result.map((v) => v.toString()); | |
resolve(keys); | |
}; | |
}) | |
} | |
public async set (key: string, value: any) { | |
const store = await this.getStore() | |
return new Promise<void>((resolve, reject) => { | |
const request = store.put({ k: key, v: value, t: Date.now() }); | |
request.onsuccess = () => resolve(); | |
request.onerror = reject; | |
}) | |
} | |
public async remove (key: string) { | |
const store = await this.getStore() | |
return new Promise((resolve, reject) => { | |
const request = store.delete(key); | |
request.onsuccess = () => resolve(); | |
request.onerror = reject; | |
}) | |
} | |
public async clear () { | |
const store = await this.getStore() | |
new Promise((resolve, reject) => { | |
const request = store.clear(); | |
request.onsuccess = () => resolve(); | |
request.onerror = reject; | |
}) | |
} | |
public async getStore(): Promise<IDBObjectStore> { | |
if (!this.store) { | |
const { dbName, tableName, maxAge } = this.config | |
const db = await getKeyValueDb(dbName, tableName); | |
const store = db.transaction(tableName, "readwrite").objectStore(tableName); | |
this.store = store | |
} | |
return this.store; | |
function getKeyValueDb(dbName: string, tableName: string) { | |
return new Promise<IDBDatabase>((resolve, reject) => { | |
const indexedDB = (window && | |
(window.indexedDB || | |
(window as any).mozIndexedDB || | |
(window as any).webkitIndexedDB || | |
(window as any).msIndexedDB)); | |
if (!indexedDB) { | |
reject("indexedDB not supported"); | |
return; | |
} | |
const request = indexedDB.open(dbName, 1); | |
request.onsuccess = function() { | |
resolve(this.result); | |
}; | |
request.onerror = function(event: any) { | |
reject("indexedDB request error"); | |
console.error(event); | |
}; | |
request.onupgradeneeded = function() { | |
const store = this.result.createObjectStore(tableName, { | |
keyPath: "k", | |
}); | |
store.createIndex("t", "t", { unique: false }); | |
store.transaction.oncomplete = function() { | |
resolve(this.db); | |
}; | |
}; | |
}); | |
} | |
} | |
public async clearExpired () { | |
const { maxAge } = this.config; | |
const store = await this.getStore(); | |
return new Promise((resolve, reject) => { | |
const request = store | |
.index("t") | |
.openCursor(IDBKeyRange.upperBound(Date.now() - maxAge!)); | |
request.onsuccess = (event) => { | |
// @ts-ignore | |
const cursor = event.target!.result; | |
if (cursor) { | |
store.delete(cursor.primaryKey); | |
cursor.continue(); | |
} else { | |
resolve(); | |
} | |
}; | |
request.onerror = reject; | |
}); | |
} | |
} | |
interface Config { | |
dbName: string, | |
tableName: string, | |
maxAge: number | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment