-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: support multiple stores per database
- Loading branch information
1 parent
0ac2a53
commit e40222c
Showing
9 changed files
with
199 additions
and
99 deletions.
There are no files selected for viewing
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,95 @@ | ||
import { clear, createStore, del, entries, getMany, setMany } from 'idb-keyval' | ||
import { DBDriver, DBKey, DBValue } from './abstract' | ||
import { openDB, IDBPDatabase } from 'idb' | ||
import { DBDriver, DBKey } from './abstract' | ||
|
||
export default (dbName: string, storeName: string): IndexedDBDriver => new IndexedDBDriver(dbName, storeName) | ||
export default function ( | ||
dbName: string, | ||
storeName: string, | ||
): DBDriver { | ||
return new IndexedDB(() => getDBWithStore(dbName, storeName), storeName) | ||
} | ||
|
||
const dbs: Record<string, Promise<IDBPDatabase>> = {} | ||
|
||
export async function resetConnections(): Promise<void> { | ||
for(const [name, db] of Object.entries(dbs)) { | ||
(await db).close() | ||
delete dbs[name] | ||
} | ||
} | ||
|
||
class IndexedDBDriver implements DBDriver { | ||
constructor(dbName: string, storeName: string) { | ||
this.store = createStore(dbName, storeName) | ||
export async function getDBWithStore( | ||
dbName: string, | ||
storeName: string, | ||
): Promise<IDBPDatabase> { | ||
const db = await (dbs[dbName] ?? open(dbName, storeName)) | ||
|
||
if (!db.objectStoreNames.contains(storeName)) { | ||
return open(dbName, storeName, db.version + 1) | ||
} | ||
store: ReturnType<typeof createStore> | ||
|
||
clear(): Promise<void> { | ||
return clear(this.store) | ||
return db | ||
} | ||
|
||
async function open(dbName: string, storeName: string, version: number | undefined = undefined) { | ||
if (dbName in dbs) { | ||
(await dbs[dbName]).close() | ||
} | ||
return dbs[dbName] = openDB(dbName, version, { | ||
upgrade(db) { | ||
db.createObjectStore(storeName) | ||
}, | ||
}) | ||
} | ||
|
||
del(key: DBKey): Promise<void> { | ||
return del(key, this.store) | ||
export class IndexedDB { | ||
private getDB: () => Promise<IDBPDatabase> | ||
private storeName: string | ||
constructor(getDB: () => Promise<IDBPDatabase>, storeName: string) { | ||
this.getDB = getDB | ||
this.storeName = storeName | ||
} | ||
|
||
entries<T extends unknown>(): Promise<[DBKey, NonNullable<DBValue<T>>][]> { | ||
return entries(this.store) | ||
async clear(): Promise<void> { | ||
return (await this.getDB()).clear(this.storeName) | ||
} | ||
|
||
getMany<T extends unknown>(keys: DBKey[]): Promise<DBValue<T>[]> { | ||
return getMany(keys, this.store) | ||
async del(key: DBKey): Promise<void> { | ||
return (await this.getDB()).delete(this.storeName, key) | ||
} | ||
|
||
setMany<T extends unknown>(entries: [DBKey, DBValue<T>][]): Promise<void> { | ||
return setMany(entries, this.store) | ||
async entries<T>(): Promise<Array<[IDBValidKey, T]>> { | ||
const items: Array<[IDBValidKey, T]> = [] | ||
const transaction = (await this.getDB()).transaction(this.storeName, 'readonly') | ||
|
||
let cursor = await transaction.store.openCursor() | ||
while(cursor) { | ||
items.push([cursor.key, cursor.value]) | ||
cursor = await cursor.continue() | ||
} | ||
|
||
await transaction.done | ||
return items | ||
} | ||
|
||
async getMany<T>(keys: DBKey[]): Promise<Array<T>> { | ||
const transaction = (await this.getDB()).transaction(this.storeName, 'readonly') | ||
|
||
const r = await Promise.all(keys.map(k => transaction.store.get(k))) | ||
|
||
await transaction.done | ||
return r | ||
} | ||
|
||
async setMany(entries: [DBKey, unknown][]): Promise<void> { | ||
const transaction = (await this.getDB()).transaction(this.storeName, 'readwrite') | ||
|
||
await Promise.all<unknown>(entries.map(([key, value]) => ( | ||
value !== undefined | ||
? transaction.store.put(value, key) | ||
: transaction.store.delete(key) | ||
))) | ||
|
||
transaction.commit() | ||
return transaction.done | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { clear, createStore, del, entries, getMany, setMany } from 'idb-keyval' | ||
import { DBDriver, DBKey, DBValue } from './abstract' | ||
|
||
export default (dbName: string, storeName: string): IndexedDBDriver => new IndexedDBDriver(dbName, storeName) | ||
|
||
class IndexedDBDriver implements DBDriver { | ||
constructor(dbName: string, storeName: string) { | ||
this.store = createStore(dbName, storeName) | ||
} | ||
store: ReturnType<typeof createStore> | ||
|
||
clear(): Promise<void> { | ||
return clear(this.store) | ||
} | ||
|
||
del(key: DBKey): Promise<void> { | ||
return del(key, this.store) | ||
} | ||
|
||
entries<T extends unknown>(): Promise<[DBKey, NonNullable<DBValue<T>>][]> { | ||
return entries(this.store) | ||
} | ||
|
||
getMany<T extends unknown>(keys: DBKey[]): Promise<DBValue<T>[]> { | ||
return getMany(keys, this.store) | ||
} | ||
|
||
setMany<T extends unknown>(entries: [DBKey, DBValue<T>][]): Promise<void> { | ||
return setMany(entries, this.store) | ||
} | ||
} |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import IndexedDB from '../../src/driver/IndexedDB' | ||
import IndexedKeyvalDB from '../../src/driver/IndexedKeyvalDB' | ||
|
||
test.each([ | ||
IndexedDB, | ||
IndexedKeyvalDB, | ||
])('store and retrieve data', async (driverFactory) => { | ||
const driver = driverFactory('test', 'teststorage') | ||
|
||
await expect(driver.setMany([ | ||
['foo', { data: 'someValue', meta: {} }], | ||
['bar', { data: 'anotherValue', meta: {} }], | ||
])).resolves.toBe(undefined) | ||
|
||
await expect(driver.getMany(['bar', 'foo'])).resolves.toEqual([ | ||
{ data: 'anotherValue', meta: {} }, | ||
{ data: 'someValue', meta: {} }, | ||
]) | ||
|
||
await expect(driver.entries()).resolves.toEqual(expect.arrayContaining([ | ||
['foo', { data: 'someValue', meta: {} }], | ||
['bar', { data: 'anotherValue', meta: {} }], | ||
])) | ||
|
||
await expect(driver.del('foo')).resolves.toBe(undefined) | ||
|
||
await expect(driver.getMany(['bar', 'foo'])).resolves.toEqual([ | ||
{ data: 'anotherValue', meta: {} }, | ||
undefined, | ||
]) | ||
|
||
await expect(driver.clear()).resolves.toBe(undefined) | ||
|
||
await expect(driver.entries()).resolves.toEqual([]) | ||
}) |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,20 @@ | ||
import { clear, createStore } from 'idb-keyval' | ||
import IndexedDB from '../../src/driver/IndexedDB' | ||
|
||
const storeParams: Parameters<typeof createStore> = ['test', 'teststorage'] | ||
test('use multiple stores', async () => { | ||
const driverA = IndexedDB('test', 'teststorageA') | ||
const driverB = IndexedDB('test', 'teststorageB') | ||
|
||
beforeEach(async () => { | ||
await clear(createStore(...storeParams)) | ||
}) | ||
|
||
test('relay calls to idb-keyval', async () => { | ||
const driver = IndexedDB(...storeParams) | ||
|
||
await expect(driver.setMany([ | ||
['foo', { data: 'someValue', meta: {} }], | ||
['bar', { data: 'anotherValue', meta: {} }], | ||
])).resolves.toBe(undefined) | ||
|
||
await expect(driver.getMany(['bar', 'foo'])).resolves.toEqual([ | ||
{ data: 'anotherValue', meta: {} }, | ||
{ data: 'someValue', meta: {} }, | ||
await driverA.setMany([ | ||
['key1', {data: 'value1', meta: {}}], | ||
]) | ||
|
||
await expect(driver.entries()).resolves.toEqual(expect.arrayContaining([ | ||
['foo', { data: 'someValue', meta: {} }], | ||
['bar', { data: 'anotherValue', meta: {} }], | ||
])) | ||
|
||
await expect(driver.del('foo')).resolves.toBe(undefined) | ||
|
||
await expect(driver.getMany(['bar', 'foo'])).resolves.toEqual([ | ||
{ data: 'anotherValue', meta: {} }, | ||
undefined, | ||
await driverB.setMany([ | ||
['key2', {data: 'value2', meta: {}}], | ||
]) | ||
|
||
await expect(driver.clear()).resolves.toBe(undefined) | ||
|
||
await expect(driver.entries()).resolves.toEqual([]) | ||
await expect(driverA.entries()).resolves.toEqual([ | ||
['key1', {data: 'value1', meta: {}}], | ||
]) | ||
await expect(driverA.entries()).resolves.toEqual([ | ||
['key1', {data: 'value1', meta: {}}], | ||
]) | ||
}) |
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
Oops, something went wrong.