import Dexie from 'dexie'; import { DBlobAsset, DBlobAssetId, DBlobAssetType, DBlobDBAsset } from './dblobs.types'; /** * Dexie DB for Big-AGI * - assets: we store large assets like images/audio/video/documents... * * [DEV NOTE] To delete the full DB (don't do it!): * - indexedDB.deleteDatabase('NAME').onsuccess = console.log; * * [DEV NOTE] This is related to storageUtils.ts's requestPersistentStorage, * aswe need to request persistent storage for the current origin, sot that * indexedDB's content is not evicted. */ class BigAgiDB extends Dexie { largeAssets!: Dexie.Table; constructor() { super('Big-AGI'); this.version(1).stores({ // Index common properties (and compound indexes) largeAssets: 'id, [contextId+scopeId], assetType, [assetType+contextId+scopeId], data.mimeType, origin.ot, origin.source, createdAt, updatedAt', }); // Note: can re-add wId and uId in version 2 if needed } } // In development mode, reuse the same instance of the DB to avoid re-creating it on every hot reload const globalForDexie = globalThis as unknown as { bigAgiDB: BigAgiDB | undefined; }; const _db = globalForDexie.bigAgiDB ?? new BigAgiDB(); if (process.env.NODE_ENV !== 'production') globalForDexie.bigAgiDB = _db; const assetsTable = _db.largeAssets; // CRUD export async function _addDBAsset(asset: T, contextId: DBlobDBAsset['contextId'], scopeId: DBlobDBAsset['scopeId']): Promise { try { // returns the id of the added asset return await assetsTable.add({ ...asset, contextId, scopeId, }); } catch (error) { console.error('addDBAsset: Error adding asset', error); throw error; } } // READ // export async function getDBlobAssetIds(): Promise { // return assetsTable.toCollection().primaryKeys(); // } // async function _getDBlobAssetIdsByScope(contextId: DBlobDBAsset['contextId'], scopeId: DBlobDBAsset['scopeId']): Promise { // return assetsTable.where({ // contextId: contextId, // scopeId: scopeId, // }).primaryKeys(); // } export async function getDBAsset(id: DBlobAssetId) { return await assetsTable.get(id) as T | undefined; } /** * Warning: this function all the matching assets data in memory - not suitable for large datasets. */ // export async function getDBAssetsByType(assetType: T['assetType']) { // return await assetsTable.where({ // assetType: assetType, // }).toArray() as unknown as T[]; // } /** * Warning: this function all the matching assets data in memory - not suitable for large datasets. */ export async function getDBAssetsByScopeAndType(assetType: T['assetType'], contextId: DBlobDBAsset['contextId'], scopeId: DBlobDBAsset['scopeId']) { const assets = await assetsTable.where({ assetType: assetType, contextId: contextId, scopeId: scopeId, }).sortBy('createdAt'); return assets.reverse() as unknown as T[]; } // UPDATE export async function updateDBAsset(id: DBlobAssetId, updates: Partial) { return assetsTable.update(id, updates); } // DELETE export async function deleteDBAsset(id: DBlobAssetId) { return assetsTable.delete(id); } // export async function deleteDBAssets(ids: DBlobAssetId[]) { // return assetsTable.bulkDelete(ids); // } // export async function deleteAllScopedAssets(contextId: DBlobDBAsset['contextId'], scopeId: DBlobDBAsset['scopeId']) { // return (contextId && scopeId) ? assetsTable.where({ // contextId: contextId, // scopeId: scopeId, // }).delete() : 0; // } export async function gcDBAssetsByScope(contextId: DBlobDBAsset['contextId'], scopeId: DBlobDBAsset['scopeId'], assetType: DBlobAssetType | null, keepIds: DBlobAssetId[]) { // get all the DB keys const dbAssetIds = await assetsTable.where((assetType !== null) ? { assetType: assetType, contextId: contextId, scopeId: scopeId, } : { contextId: contextId, scopeId: scopeId, }).primaryKeys(); // find the unreferenced keys const unreferencedAssetIds = keepIds.length ? dbAssetIds.filter(id => !keepIds.includes(id)) : dbAssetIds; // delete the unreferenced keys if (unreferencedAssetIds.length > 0) await assetsTable.bulkDelete(unreferencedAssetIds); }