diff --git a/packages/common/nbstore/src/impls/idb/blob.ts b/packages/common/nbstore/src/impls/idb/blob.ts index 9abb02c773..d06292951d 100644 --- a/packages/common/nbstore/src/impls/idb/blob.ts +++ b/packages/common/nbstore/src/impls/idb/blob.ts @@ -1,9 +1,5 @@ import { share } from '../../connection'; -import { - type BlobRecord, - BlobStorageBase, - type ListedBlobRecord, -} from '../../storage'; +import { type BlobRecord, BlobStorageBase } from '../../storage'; import { IDBConnection, type IDBConnectionOptions } from './db'; export class IndexedDBBlobStorage extends BlobStorageBase { @@ -36,7 +32,9 @@ export class IndexedDBBlobStorage extends BlobStorageBase { } override async set(blob: BlobRecord) { - const trx = this.db.transaction(['blobs', 'blobData'], 'readwrite'); + const trx = this.db.transaction(['blobs', 'blobData'], 'readwrite', { + durability: 'relaxed', + }); await trx.objectStore('blobs').put({ key: blob.key, mime: blob.mime, @@ -52,11 +50,15 @@ export class IndexedDBBlobStorage extends BlobStorageBase { override async delete(key: string, permanently: boolean) { if (permanently) { - const trx = this.db.transaction(['blobs', 'blobData'], 'readwrite'); + const trx = this.db.transaction(['blobs', 'blobData'], 'readwrite', { + durability: 'relaxed', + }); await trx.objectStore('blobs').delete(key); await trx.objectStore('blobData').delete(key); } else { - const trx = this.db.transaction('blobs', 'readwrite'); + const trx = this.db.transaction('blobs', 'readwrite', { + durability: 'relaxed', + }); const blob = await trx.store.get(key); if (blob) { await trx.store.put({ @@ -68,29 +70,37 @@ export class IndexedDBBlobStorage extends BlobStorageBase { } override async release() { - const trx = this.db.transaction(['blobs', 'blobData'], 'readwrite'); + const trx = this.db.transaction(['blobs', 'blobData'], 'readwrite', { + durability: 'relaxed', + }); - const it = trx.objectStore('blobs').iterate(); + const store = trx.objectStore('blobs'); + const getAllRecords = store.getAllRecords?.bind(store); + const blobs = + typeof getAllRecords === 'function' + ? (await getAllRecords()).map(record => record.value) + : await store.getAll(); - for await (const item of it) { - if (item.value.deletedAt) { - await item.delete(); - await trx.objectStore('blobData').delete(item.value.key); - } - } + const deleted = blobs.filter(blob => blob.deletedAt); + + await Promise.all( + deleted.map(blob => + Promise.all([ + store.delete(blob.key), + trx.objectStore('blobData').delete(blob.key), + ]) + ) + ); } override async list() { const trx = this.db.transaction('blobs', 'readonly'); - const it = trx.store.iterate(); + const getAllRecords = trx.store.getAllRecords?.bind(trx.store); + const blobs = + typeof getAllRecords === 'function' + ? (await getAllRecords()).map(record => record.value) + : await trx.store.getAll(); - const blobs: ListedBlobRecord[] = []; - for await (const item of it) { - if (!item.value.deletedAt) { - blobs.push(item.value); - } - } - - return blobs; + return blobs.filter(blob => !blob.deletedAt); } } diff --git a/packages/common/nbstore/src/impls/idb/db.ts b/packages/common/nbstore/src/impls/idb/db.ts index 0326ab91e2..1e29f49d64 100644 --- a/packages/common/nbstore/src/impls/idb/db.ts +++ b/packages/common/nbstore/src/impls/idb/db.ts @@ -4,6 +4,38 @@ import { AutoReconnectConnection } from '../../connection'; import type { SpaceType } from '../../utils/universal-id'; import { type DocStorageSchema, migrator } from './schema'; +declare module 'idb' { + interface IDBPObjectStore { + getAllRecords?( + query?: IDBValidKey | IDBKeyRange | null, + count?: number | { direction?: IDBCursorDirection; count?: number } + ): Promise; + } + interface IDBPIndex { + getAllRecords?( + query?: IDBValidKey | IDBKeyRange | null, + count?: number | { direction?: IDBCursorDirection; count?: number } + ): Promise; + } + interface IDBObjectStore { + getAllRecords?( + query?: IDBValidKey | IDBKeyRange | null, + count?: number | { direction?: IDBCursorDirection; count?: number } + ): Promise; + } + interface IDBIndex { + getAllRecords?( + query?: IDBValidKey | IDBKeyRange | null, + count?: number | { direction?: IDBCursorDirection; count?: number } + ): Promise; + } + interface IDBRecord { + key: IDBValidKey; + primaryKey: IDBValidKey; + value: any; + } +} + export interface IDBConnectionOptions { flavour: string; type: SpaceType; diff --git a/packages/common/nbstore/src/impls/idb/doc.ts b/packages/common/nbstore/src/impls/idb/doc.ts index 46ca0bc4bc..c6977f6baa 100644 --- a/packages/common/nbstore/src/impls/idb/doc.ts +++ b/packages/common/nbstore/src/impls/idb/doc.ts @@ -37,7 +37,9 @@ export class IndexedDBDocStorage extends DocStorageBase { while (true) { try { - const trx = this.db.transaction(['updates', 'clocks'], 'readwrite'); + const trx = this.db.transaction(['updates', 'clocks'], 'readwrite', { + durability: 'relaxed', + }); await trx.objectStore('updates').add({ ...update, @@ -103,15 +105,15 @@ export class IndexedDBDocStorage extends DocStorageBase { override async deleteDoc(docId: string) { const trx = this.db.transaction( ['snapshots', 'updates', 'clocks'], - 'readwrite' + 'readwrite', + { durability: 'relaxed' } ); - const idx = trx.objectStore('updates').index('docId'); - const iter = idx.iterate(IDBKeyRange.only(docId)); + const updates = trx.objectStore('updates'); + const idx = updates.index('docId'); + const keys = await idx.getAllKeys(IDBKeyRange.only(docId)); - for await (const { value } of iter) { - await trx.objectStore('updates').delete([value.docId, value.createdAt]); - } + await Promise.all(keys.map(key => updates.delete(key))); await trx.objectStore('snapshots').delete(docId); await trx.objectStore('clocks').delete(docId); @@ -120,6 +122,18 @@ export class IndexedDBDocStorage extends DocStorageBase { override async getDocTimestamps(after: Date = new Date(0)) { const trx = this.db.transaction('clocks', 'readonly'); + const getAllRecords = trx.store.getAllRecords?.bind(trx.store); + + if (typeof getAllRecords === 'function') { + const records = await getAllRecords(); + return records.reduce((ret, cur) => { + if (cur.value.timestamp > after) { + ret[cur.value.docId] = cur.value.timestamp; + } + return ret; + }, {} as DocClocks); + } + const clocks = await trx.store.getAll(); return clocks.reduce((ret, cur) => { @@ -157,7 +171,19 @@ export class IndexedDBDocStorage extends DocStorageBase { protected override async getDocUpdates(docId: string): Promise { const trx = this.db.transaction('updates', 'readonly'); - const updates = await trx.store.index('docId').getAll(docId); + const idx = trx.store.index('docId'); + const getAllRecords = idx.getAllRecords?.bind(idx); + + if (typeof getAllRecords === 'function') { + const records = await getAllRecords(IDBKeyRange.only(docId)); + return records.map(record => ({ + docId, + bin: record.value.bin, + timestamp: record.value.createdAt, + })); + } + + const updates = await idx.getAll(docId); return updates.map(update => ({ docId, @@ -170,7 +196,9 @@ export class IndexedDBDocStorage extends DocStorageBase { docId: string, updates: DocRecord[] ): Promise { - const trx = this.db.transaction('updates', 'readwrite'); + const trx = this.db.transaction('updates', 'readwrite', { + durability: 'relaxed', + }); await Promise.all( updates.map(update => trx.store.delete([docId, update.timestamp]))