Replace idb with idb-keyval for draft media storage

Simplifies web IndexedDB storage by using idb-keyval instead of the
full idb library. This reduces bundle size and aligns with the pattern
used in src/storage/archive/db/index.web.ts.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Samuel Newman
2026-01-27 19:30:15 +02:00
parent e807e49b09
commit 57d7223e42
3 changed files with 14 additions and 43 deletions
-1
View File
@@ -172,7 +172,6 @@
"history": "^5.3.0",
"hls.js": "^1.6.2",
"idb-keyval": "^6.2.2",
"idb": "^8.0.3",
"js-sha256": "^0.9.0",
"jwt-decode": "^4.0.0",
"lande": "^1.0.10",
@@ -2,37 +2,19 @@
* Web IndexedDB storage for draft media.
* Media is stored by localRefPath key (unique identifier stored in server draft).
*/
import {type DBSchema, type IDBPDatabase, openDB} from 'idb'
import {createStore, del, get, keys, set} from 'idb-keyval'
import {logger} from './logger'
const DB_NAME = 'bsky-draft-media'
const DB_VERSION = 1
const STORE_NAME = 'media'
interface DraftMediaDB extends DBSchema {
media: {
key: string // localRefPath
value: {
blob: Blob
createdAt: string
}
}
type MediaRecord = {
blob: Blob
createdAt: string
}
let dbPromise: Promise<IDBPDatabase<DraftMediaDB>> | null = null
async function getDB(): Promise<IDBPDatabase<DraftMediaDB>> {
if (!dbPromise) {
dbPromise = openDB<DraftMediaDB>(DB_NAME, DB_VERSION, {
upgrade(db) {
if (!db.objectStoreNames.contains('media')) {
db.createObjectStore('media')
}
},
})
}
return dbPromise
}
const store = createStore(DB_NAME, STORE_NAME)
/**
* Convert a path/URL to a Blob
@@ -73,8 +55,6 @@ export async function saveMediaToLocal(
localRefPath: string,
sourcePath: string,
): Promise<void> {
const db = await getDB()
let blob: Blob
try {
blob = await toBlob(sourcePath)
@@ -88,13 +68,13 @@ export async function saveMediaToLocal(
}
try {
await db.put(
'media',
await set(
localRefPath,
{
blob,
createdAt: new Date().toISOString(),
},
localRefPath,
store,
)
// Update cache
mediaExistsCache.set(localRefPath, true)
@@ -111,8 +91,7 @@ export async function saveMediaToLocal(
export async function loadMediaFromLocal(
localRefPath: string,
): Promise<string> {
const db = await getDB()
const record = await db.get('media', localRefPath)
const record = await get<MediaRecord>(localRefPath, store)
if (!record) {
throw new Error(`Media file not found: ${localRefPath}`)
@@ -127,8 +106,7 @@ export async function loadMediaFromLocal(
export async function deleteMediaFromLocal(
localRefPath: string,
): Promise<void> {
const db = await getDB()
await db.delete('media', localRefPath)
await del(localRefPath, store)
mediaExistsCache.delete(localRefPath)
}
@@ -152,10 +130,9 @@ export function mediaExists(localRefPath: string): boolean {
async function populateCacheInternal(): Promise<void> {
try {
const db = await getDB()
const keys = await db.getAllKeys('media')
for (const key of keys) {
mediaExistsCache.set(key, true)
const allKeys = await keys(store)
for (const key of allKeys) {
mediaExistsCache.set(key as string, true)
}
cachePopulated = true
} catch (e) {
-5
View File
@@ -12886,11 +12886,6 @@ idb-keyval@^6.2.2:
resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.2.2.tgz#b0171b5f73944854a3291a5cdba8e12768c4854a"
integrity sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==
idb@^8.0.3:
version "8.0.3"
resolved "https://registry.yarnpkg.com/idb/-/idb-8.0.3.tgz#c91e558f15a8d53f1d7f53a094d226fc3ad71fd9"
integrity sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==
ieee754@^1.1.13, ieee754@^1.1.4, ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"