mirror of
https://github.com/coracle-social/flotilla.git
synced 2025-12-10 02:47:06 +00:00
122 lines
3.0 KiB
TypeScript
122 lines
3.0 KiB
TypeScript
import {openDB, deleteDB} from "idb"
|
|
import type {IDBPDatabase} from "idb"
|
|
import {throttle} from "throttle-debounce"
|
|
import {writable} from "svelte/store"
|
|
import type {Unsubscriber, Writable} from "svelte/store"
|
|
import {randomInt} from "@welshman/lib"
|
|
import {withGetter} from "@welshman/store"
|
|
|
|
export type Item = Record<string, any>
|
|
|
|
export type IndexedDbAdapter = {
|
|
keyPath: string
|
|
store: Writable<Item[]>
|
|
}
|
|
|
|
export let db: IDBPDatabase
|
|
|
|
export const dead = withGetter(writable(false))
|
|
|
|
export const subs: Unsubscriber[] = []
|
|
|
|
export const DB_NAME = "flotilla"
|
|
|
|
export const getAll = async (name: string) => {
|
|
const tx = db.transaction(name, "readwrite")
|
|
const store = tx.objectStore(name)
|
|
const result = await store.getAll()
|
|
|
|
await tx.done
|
|
|
|
return result
|
|
}
|
|
|
|
export const bulkPut = async (name: string, data: any[]) => {
|
|
const tx = db.transaction(name, "readwrite")
|
|
const store = tx.objectStore(name)
|
|
|
|
await Promise.all(data.map(item => store.put(item)))
|
|
await tx.done
|
|
}
|
|
|
|
export const bulkDelete = async (name: string, ids: string[]) => {
|
|
const tx = db.transaction(name, "readwrite")
|
|
const store = tx.objectStore(name)
|
|
|
|
await Promise.all(ids.map(id => store.delete(id)))
|
|
await tx.done
|
|
}
|
|
|
|
export const initIndexedDbAdapter = async (name: string, adapter: IndexedDbAdapter) => {
|
|
let copy = await getAll(name)
|
|
|
|
adapter.store.set(copy)
|
|
|
|
adapter.store.subscribe(
|
|
throttle(randomInt(3000, 5000), async (data: Item[]) => {
|
|
if (dead.get()) {
|
|
return
|
|
}
|
|
|
|
const prevIds = new Set(copy.map(item => item[adapter.keyPath]))
|
|
const currentIds = new Set(data.map(item => item[adapter.keyPath]))
|
|
const newRecords = data.filter(r => !prevIds.has(r[adapter.keyPath]))
|
|
const removedRecords = copy.filter(r => !currentIds.has(r[adapter.keyPath]))
|
|
|
|
copy = data
|
|
|
|
if (newRecords.length > 0) {
|
|
await bulkPut(name, newRecords)
|
|
}
|
|
|
|
if (removedRecords.length > 0) {
|
|
await bulkDelete(
|
|
name,
|
|
removedRecords.map(item => item[adapter.keyPath]),
|
|
)
|
|
}
|
|
}),
|
|
)
|
|
}
|
|
|
|
export const initStorage = async (version: number, adapters: Record<string, IndexedDbAdapter>) => {
|
|
if (!window.indexedDB) return
|
|
|
|
window.addEventListener("beforeunload", () => closeStorage())
|
|
|
|
db = await openDB(DB_NAME, version, {
|
|
upgrade(db: IDBPDatabase) {
|
|
const names = Object.keys(adapters)
|
|
|
|
for (const name of db.objectStoreNames) {
|
|
if (!names.includes(name)) {
|
|
db.deleteObjectStore(name)
|
|
}
|
|
}
|
|
|
|
for (const [name, {keyPath}] of Object.entries(adapters)) {
|
|
try {
|
|
db.createObjectStore(name, {keyPath})
|
|
} catch (e) {
|
|
console.warn(e)
|
|
}
|
|
}
|
|
},
|
|
})
|
|
|
|
await Promise.all(
|
|
Object.entries(adapters).map(([name, config]) => initIndexedDbAdapter(name, config)),
|
|
)
|
|
}
|
|
|
|
export const closeStorage = async () => {
|
|
dead.set(true)
|
|
subs.forEach(unsub => unsub())
|
|
await db?.close()
|
|
}
|
|
|
|
export const clearStorage = async () => {
|
|
await closeStorage()
|
|
await deleteDB(DB_NAME)
|
|
}
|