Re-work storage adapter a bit
This commit is contained in:
+59
-90
@@ -12,132 +12,101 @@ export type IDBAdapter = {
|
|||||||
|
|
||||||
export type IDBAdapters = IDBAdapter[]
|
export type IDBAdapters = IDBAdapter[]
|
||||||
|
|
||||||
export enum IDBStatus {
|
|
||||||
Ready = "ready",
|
|
||||||
Closed = "closed",
|
|
||||||
Opening = "opening",
|
|
||||||
Closing = "closing",
|
|
||||||
Initial = "initial",
|
|
||||||
}
|
|
||||||
|
|
||||||
export type IDBOptions = {
|
export type IDBOptions = {
|
||||||
name: string
|
name: string
|
||||||
version: number
|
version: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export class IDB {
|
export class IDB {
|
||||||
idbp: Maybe<Promise<IDBPDatabase>>
|
adapters: IDBAdapters = []
|
||||||
|
connection: Maybe<Promise<IDBPDatabase>>
|
||||||
unsubscribers: Maybe<Unsubscriber[]>
|
unsubscribers: Maybe<Unsubscriber[]>
|
||||||
status = IDBStatus.Initial
|
|
||||||
|
|
||||||
constructor(readonly options: IDBOptions) {}
|
constructor(readonly options: IDBOptions) {}
|
||||||
|
|
||||||
async init(adapters: IDBAdapters) {
|
async connect() {
|
||||||
if (this.idbp) {
|
if (!this.connection) {
|
||||||
throw new Error("Unable to initialize a database that isn't yet closed")
|
const {name, version} = this.options
|
||||||
}
|
const adapters = this.adapters
|
||||||
|
|
||||||
this.status = IDBStatus.Opening
|
this.connection = openDB(name, version, {
|
||||||
|
upgrade(idbDb: IDBPDatabase) {
|
||||||
|
const names = new Set(adapters.map(a => a.name))
|
||||||
|
|
||||||
this.idbp = openDB(this.options.name, this.options.version, {
|
for (const table of idbDb.objectStoreNames) {
|
||||||
upgrade(idbDb: IDBPDatabase) {
|
if (!names.has(table)) {
|
||||||
const names = new Set(adapters.map(a => a.name))
|
idbDb.deleteObjectStore(table)
|
||||||
|
}
|
||||||
for (const table of idbDb.objectStoreNames) {
|
|
||||||
if (!names.has(table)) {
|
|
||||||
idbDb.deleteObjectStore(table)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (const {name, keyPath} of adapters) {
|
for (const {name, keyPath} of adapters) {
|
||||||
try {
|
try {
|
||||||
idbDb.createObjectStore(name, {keyPath})
|
idbDb.createObjectStore(name, {keyPath})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e)
|
console.warn(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
blocked() {},
|
||||||
blocked() {},
|
blocking() {},
|
||||||
blocking() {},
|
})
|
||||||
})
|
|
||||||
|
|
||||||
return this.idbp.then(async idbp => {
|
|
||||||
window.addEventListener("beforeunload", () => idbp.close())
|
|
||||||
|
|
||||||
this.unsubscribers = await Promise.all(adapters.map(({name, init}) => init(this.table(name))))
|
this.unsubscribers = await Promise.all(adapters.map(({name, init}) => init(this.table(name))))
|
||||||
|
}
|
||||||
|
|
||||||
this.status = IDBStatus.Ready
|
return this.connection
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
table = <T>(name: string) => new IDBTable<T>(this, name)
|
table = <T>(name: string) => new IDBTable<T>(this, name)
|
||||||
|
|
||||||
_withIDBP = async <T>(f: (db: IDBPDatabase) => Promise<T>) => {
|
|
||||||
if (this.status === IDBStatus.Initial) {
|
|
||||||
throw new Error("Database was accessed in initial state")
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're closing, ignore any lingering requests
|
|
||||||
if ([IDBStatus.Closed, IDBStatus.Closing].includes(this.status)) return
|
|
||||||
|
|
||||||
return f(await this.idbp!)
|
|
||||||
}
|
|
||||||
|
|
||||||
getAll = async <T>(table: string): Promise<T[]> => {
|
getAll = async <T>(table: string): Promise<T[]> => {
|
||||||
const result = await this._withIDBP(async idbp => {
|
const connection = await this.connect()
|
||||||
const tx = idbp.transaction(table, "readwrite")
|
const tx = connection.transaction(table, "readwrite")
|
||||||
const store = tx.objectStore(table)
|
const store = tx.objectStore(table)
|
||||||
const result = await store.getAll()
|
const result = await store.getAll()
|
||||||
|
|
||||||
await tx.done
|
await tx.done
|
||||||
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
|
|
||||||
return result || []
|
return result || []
|
||||||
}
|
}
|
||||||
|
|
||||||
bulkPut = async <T>(table: string, data: Iterable<T>) =>
|
bulkPut = async <T>(table: string, data: Iterable<T>) => {
|
||||||
this._withIDBP(async idbp => {
|
const connection = await this.connect()
|
||||||
const tx = idbp.transaction(table, "readwrite")
|
const tx = connection.transaction(table, "readwrite")
|
||||||
const store = tx.objectStore(table)
|
const store = tx.objectStore(table)
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Array.from(data).map(item => {
|
Array.from(data).map(item => {
|
||||||
try {
|
try {
|
||||||
store.put(item)
|
store.put(item)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e, item)
|
console.error(e, item)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
await tx.done
|
await tx.done
|
||||||
})
|
}
|
||||||
|
|
||||||
bulkDelete = async (table: string, ids: Iterable<string>) =>
|
bulkDelete = async (table: string, ids: Iterable<string>) => {
|
||||||
this._withIDBP(async idbp => {
|
const connection = await this.connect()
|
||||||
const tx = idbp.transaction(table, "readwrite")
|
const tx = connection.transaction(table, "readwrite")
|
||||||
const store = tx.objectStore(table)
|
const store = tx.objectStore(table)
|
||||||
|
|
||||||
await Promise.all(Array.from(ids).map(id => store.delete(id)))
|
await Promise.all(Array.from(ids).map(id => store.delete(id)))
|
||||||
await tx.done
|
await tx.done
|
||||||
})
|
}
|
||||||
|
|
||||||
close = () =>
|
close = () => {
|
||||||
this._withIDBP(async idbp => {
|
this.unsubscribers?.forEach(call)
|
||||||
this.unsubscribers!.forEach(call)
|
this.unsubscribers = undefined
|
||||||
this.status = IDBStatus.Closing
|
|
||||||
|
|
||||||
await idbp.close()
|
this.connection?.then(c => c.close())
|
||||||
|
this.connection = undefined
|
||||||
this.idbp = undefined
|
}
|
||||||
this.unsubscribers = undefined
|
|
||||||
this.status = IDBStatus.Closed
|
|
||||||
})
|
|
||||||
|
|
||||||
clear = async () => {
|
clear = async () => {
|
||||||
await this.close()
|
await this.connection?.then(c => c.close())
|
||||||
await deleteDB(this.options.name, {
|
await deleteDB(this.options.name, {
|
||||||
blocked() {},
|
blocked() {},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -88,6 +88,9 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Cleanup on page close
|
||||||
|
window.addEventListener("beforeunload", () => db.close())
|
||||||
|
|
||||||
const unsubscribe = call(async () => {
|
const unsubscribe = call(async () => {
|
||||||
const unsubscribers: Unsubscriber[] = []
|
const unsubscribers: Unsubscriber[] = []
|
||||||
|
|
||||||
@@ -110,10 +113,14 @@
|
|||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// Set up our storage adapters
|
||||||
|
db.adapters = storage.adapters
|
||||||
|
|
||||||
// Wait until data storage is initialized before syncing other stuff
|
// Wait until data storage is initialized before syncing other stuff
|
||||||
if (!db.idbp) {
|
await db.connect()
|
||||||
await db.init(storage.adapters)
|
|
||||||
}
|
// Close the database connection on reload
|
||||||
|
unsubscribers.push(() => db.close())
|
||||||
|
|
||||||
// Add our extra policies now that we're set up
|
// Add our extra policies now that we're set up
|
||||||
defaultSocketPolicies.push(...policies)
|
defaultSocketPolicies.push(...policies)
|
||||||
|
|||||||
Reference in New Issue
Block a user