UNPKG

rxdb

Version:

A local-first realtime NoSQL Database for JavaScript applications - https://rxdb.info/

141 lines (126 loc) 4.76 kB
import type { RxCleanupPolicy, RxCollection } from '../../types/index.d.ts'; import { PROMISE_RESOLVE_TRUE, getFromMapOrCreate } from '../../plugins/utils/index.ts'; import { REPLICATION_STATE_BY_COLLECTION } from '../replication/index.ts'; import { DEFAULT_CLEANUP_POLICY } from './cleanup-helper.ts'; import { runAsyncPluginHooks } from '../../hooks.ts'; import { filter, firstValueFrom } from 'rxjs'; /** * Even on multiple databases, * the calls to RxStorage().cleanup() * must never run in parallel. * The cleanup is a background task which should * not affect the performance of other, more important tasks. */ let RXSTORAGE_CLEANUP_QUEUE: Promise<boolean> = PROMISE_RESOLVE_TRUE; export async function startCleanupForRxCollection( rxCollection: RxCollection ) { const rxDatabase = rxCollection.database; const cleanupPolicy = Object.assign( {}, DEFAULT_CLEANUP_POLICY, rxDatabase.cleanupPolicy ? rxDatabase.cleanupPolicy : {} ); await initialCleanupWait(rxCollection, cleanupPolicy); if (rxCollection.closed) { return; } // initially cleanup the collection await cleanupRxCollection(rxCollection, cleanupPolicy); /** * Afterwards we listen to deletes * and only re-run the cleanup after * minimumDeletedTime is reached. */ await runCleanupAfterDelete(rxCollection, cleanupPolicy); } export async function initialCleanupWait(collection: RxCollection, cleanupPolicy: RxCleanupPolicy) { /** * Wait until minimumDatabaseInstanceAge is reached * or collection is closed. */ await collection.promiseWait(cleanupPolicy.minimumCollectionAge); if (collection.closed) { return; } if (collection.database.multiInstance && cleanupPolicy.waitForLeadership) { await collection.database.waitForLeadership(); } } /** * Runs the cleanup for a single RxCollection */ export async function cleanupRxCollection( rxCollection: RxCollection, cleanupPolicy: RxCleanupPolicy ) { const rxDatabase = rxCollection.database; const storageInstance = rxCollection.storageInstance; // run cleanup() until it returns true let isDone = false; while (!isDone && !rxCollection.closed) { if (cleanupPolicy.awaitReplicationsInSync) { const replicationStates = getFromMapOrCreate( REPLICATION_STATE_BY_COLLECTION, rxCollection, () => [] ); await Promise.all( replicationStates.map(replicationState => { if (!replicationState.isStopped()) { return replicationState.awaitInSync(); } }) ); } if (rxCollection.closed) { return; } RXSTORAGE_CLEANUP_QUEUE = RXSTORAGE_CLEANUP_QUEUE .then(async () => { if (rxCollection.closed) { return true; } await rxDatabase.requestIdlePromise(); const allDone: Promise<boolean>[] = []; allDone.push(storageInstance.cleanup(cleanupPolicy.minimumDeletedTime)); const replicationStates = getFromMapOrCreate( REPLICATION_STATE_BY_COLLECTION, rxCollection, () => [] ); for (const replicationState of replicationStates) { const meta = replicationState.metaInstance; if (meta) { allDone.push(meta.cleanup(cleanupPolicy.minimumDeletedTime)); } } const hasFalse = (await Promise.all(allDone)).find(v => !v); return !hasFalse; }); isDone = await RXSTORAGE_CLEANUP_QUEUE; } await runAsyncPluginHooks('postCleanup', { collectionName: rxCollection.name, databaseName: rxDatabase.name }); } export async function runCleanupAfterDelete( rxCollection: RxCollection, cleanupPolicy: RxCleanupPolicy ) { while (!rxCollection.closed) { /** * In theory we should wait here until a document is deleted. * But this would mean we have to search through all events ever processed. * So instead we just wait for any write event and then we anyway throttle * the calls with the promiseWait() below. */ await firstValueFrom(rxCollection.eventBulks$).catch(() => { }); await rxCollection.promiseWait(cleanupPolicy.runEach); if (rxCollection.closed) { return; } await cleanupRxCollection(rxCollection, cleanupPolicy); } }