Skip to content

Commit

Permalink
feat(kvs/indexeddb): add [Symbol.asyncIterator]
Browse files Browse the repository at this point in the history
  • Loading branch information
azu committed Aug 8, 2020
1 parent f45b71c commit 68392d4
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 45 deletions.
6 changes: 6 additions & 0 deletions packages/indexeddb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,9 @@ For bugs and feature requests, [please create an issue](https://github.com/azu/k
## License

MIT © azu

## Related

- https://github.com/tkdalic/indexed-kv
- https://github.com/GoogleChromeLabs/kv-storage-polyfill
- https://github.com/localForage/localForage/blob/master/src/drivers/indexeddb.js
62 changes: 54 additions & 8 deletions packages/indexeddb/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,9 @@ const openDB = ({
};
};
openRequest.addEventListener("blocked", () => {
console.log("blocked");
reject(openRequest.error);
});
openRequest.onerror = function () {
console.log("blocked");
reject(openRequest.error);
};
openRequest.onsuccess = function () {
Expand All @@ -54,16 +52,25 @@ const getItem = <K extends IndexedDBKey, V>(database: IDBDatabase, tableName: st
const objectStore = transaction.objectStore(tableName);
const request = objectStore.get(key);
request.onsuccess = () => {
resolve(request.result ? request.result : null);
resolve(request.result);
};
request.onerror = () => {
reject(request.error);
};
});
};
const hasItem = async <K extends IndexedDBKey>(database: IDBDatabase, tableName: string, key: K): Promise<boolean> => {
const value = await getItem(database, tableName, key);
return value !== undefined;
return new Promise((resolve, reject) => {
const transaction = database.transaction(tableName, "readonly");
const objectStore = transaction.objectStore(tableName);
const request = objectStore.count(key);
request.onsuccess = () => {
resolve(request.result !== 0);
};
request.onerror = () => {
reject(request.error);
};
});
};
const setItem = <K extends IndexedDBKey, V>(
database: IDBDatabase,
Expand Down Expand Up @@ -122,11 +129,48 @@ const clearItems = async (database: IDBDatabase, tableName: string): Promise<voi
};
});
};
const iterator = <K extends IndexedDBKey, V>(database: IDBDatabase, tableName: string): AsyncIterator<[K, V]> => {
const handleCursor = <T>(request: IDBRequest<T | null>): Promise<{ done: boolean; value?: T }> => {
return new Promise((resolve, reject) => {
request.onsuccess = () => {
const cursor = request.result;
if (!cursor) {
return resolve({
done: true
});
}
return resolve({
done: false,
value: cursor
});
};
request.onerror = () => {
reject(request.error);
};
});
};
const transaction = database.transaction(tableName, "readonly");
const objectStore = transaction.objectStore(tableName);
const request = objectStore.openCursor();
return {
async next() {
const { done, value } = await handleCursor(request);
if (!done) {
const storageKey = value?.key as K;
const storageValue = value?.value as V;
// next iterate
value?.continue();
return { done: false, value: [storageKey, storageValue] };
}
return { done: true, value: undefined };
}
};
};
type IndexedDBOptions = {
tableName?: string;
};
const createStore = <K extends IndexedDBKey, V>(database: IDBDatabase, tableName: string) => {
const store = {
const createStore = <K extends IndexedDBKey, V>(database: IDBDatabase, tableName: string): KVS<K, V> => {
const store: KVS<K, V> = {
delete(key: K): Promise<boolean> {
return deleteItem(database, tableName, key).then(() => true);
},
Expand All @@ -142,7 +186,9 @@ const createStore = <K extends IndexedDBKey, V>(database: IDBDatabase, tableName
clear(): Promise<void> {
return clearItems(database, tableName);
},
__debug__database__: database
[Symbol.asyncIterator]() {
return iterator(database, tableName);
}
};
return store;
};
Expand Down
147 changes: 111 additions & 36 deletions packages/indexeddb/test/index-browser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,77 @@ import { KVS } from "@kvs/types";

let kvs: KVS<any, any>;
const databaseName = "kvs-test";
const forceDeleteDB = async () => {
if (!("databases" in indexedDB)) {
return;
}
// @ts-ignore
const dbs = await window.indexedDB.databases();
const deleteDB = (name: string) => {
return new Promise((resolve, reject) => {
const transaction = window.indexedDB.deleteDatabase(name);
transaction.addEventListener("success", () => {
resolve();
});
transaction.addEventListener("upgradeneeded", () => {
reject(transaction.error);
});
transaction.addEventListener("blocked", () => {
reject(transaction.error);
});
transaction.addEventListener("error", () => {
reject(transaction.error);
});
});
};
return Promise.all(dbs.map((db: any) => deleteDB(db.name)));
};
const deleteAllDB = async () => {
// clear all data
if (!kvs) {
await forceDeleteDB();
return;
}
return kvs.clear();
};
describe("@kvs/indexedDB", () => {
before(deleteAllDB);
afterEach(deleteAllDB);
describe("set", () => {
const testDateList = [
{
name: "string",
value: "str"
},
{
name: "number",
value: 42
},
{
name: "boolean",
value: false
},
{
name: "date",
value: new Date(),
type: "object"
},
{
name: "object",
value: {
prop: "propValue"
},
type: "object"
},
{
name: "blob",
value: new Blob(["Hello, world!"], { type: "text/plain" }),
type: "object"
}
];
testDateList.forEach((item) => {
it(`${item.name}`, async () => {
kvs = await kvsIndexedDB({
name: databaseName,
version: 1
});
await kvs.set(item.name, item.value);
if (item.type === "object") {
assert.deepStrictEqual(await kvs.get(item.name), item.value);
} else {
assert.strictEqual(await kvs.get(item.name), item.value);
}
});
});
});
it("set → get", async () => {
kvs = await kvsIndexedDB({
name: databaseName,
version: 1
});
await kvs.set("key", "value");
const result = await kvs.get("key");
assert.strictEqual(result, "value");
assert.strictEqual(await kvs.get("key"), "value");
});
describe("set", async () => {
kvs = await kvsIndexedDB({
name: databaseName,
version: 1
});
await kvs.set("key", "value");
assert.strictEqual(await kvs.get("key"), "value");
});
it("update with set", async () => {
kvs = await kvsIndexedDB({
Expand All @@ -56,19 +83,67 @@ describe("@kvs/indexedDB", () => {
});
await kvs.set("key", "value1");
await kvs.set("key", "value2");
const result = await kvs.get("key");
assert.strictEqual(result, "value2");
assert.strictEqual(await kvs.get("key"), "value2");
});
it("test multiple set-get key-value", async () => {
kvs = await kvsIndexedDB({
name: databaseName,
version: 1
});
await kvs.set("key1", "value1");
await kvs.set("key2", "value2");
assert.strictEqual(await kvs.get("key1"), "value1");
assert.strictEqual(await kvs.get("key2"), "value2");
});
it("delete with key", async () => {
kvs = await kvsIndexedDB({
name: databaseName,
version: 1
});
await kvs.set("key", "value");
assert.ok(await kvs.get("key"));
await kvs.delete("key");
assert.ok((await kvs.has("key1")) === false);
});
it("set empty value and has return true", async () => {
kvs = await kvsIndexedDB({
name: databaseName,
version: 1
});
await kvs.set("key", "value");
assert.ok(await kvs.get("key"));
await kvs.set("key", undefined);
assert.ok(await kvs.has("key"), "should have key that is undefined");
});
it("clear all data", async () => {
kvs = await kvsIndexedDB({
name: databaseName,
version: 1
});
await kvs.set("key1", "value1");
await kvs.set("key2", "value2");
assert.ok(await kvs.has("key1"));
assert.ok(await kvs.has("key2"));
await kvs.clear();
assert.strictEqual(await kvs.has("key1"), false, "key 1 should be deleted");
assert.strictEqual(await kvs.has("key2"), false, "key 2 should be deleted");
});
it("test multiple key-value", async () => {
it("[Symbol.asyncIterator]", async () => {
kvs = await kvsIndexedDB({
name: databaseName,
version: 1
});
await kvs.set("key1", "value1");
await kvs.set("key2", "value2");
const result1 = await kvs.get("key1");
const result2 = await kvs.get("key2");
assert.strictEqual(result1, "value1");
assert.strictEqual(result2, "value2");
assert.ok(await kvs.has("key1"));
assert.ok(await kvs.has("key2"));
const results: [string, string][] = [];
for await (const [key, value] of kvs) {
results.push([key, value]);
}
assert.deepStrictEqual(results, [
["key1", "value1"],
["key2", "value2"]
]);
});
});
2 changes: 1 addition & 1 deletion packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export type KVS<K, V> = {
get(key: K): Promise<V | undefined>;
has(key: K): Promise<boolean>;
set(key: K, value: V): Promise<KVS<K, V>>;
};
} & AsyncIterable<[K, V]>;
export type KVSOptions<K, V> = {
name: string;
version: number;
Expand Down

0 comments on commit 68392d4

Please sign in to comment.