UNPKG

rxdb

Version:

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

140 lines (118 loc) 4.37 kB
/** * the query-cache makes sure that on every query-state, exactly one instance can exist * if you use the same mango-query more then once, it will reuse the first RxQuery */ import type { RxQuery, RxCacheReplacementPolicy, RxCollection } from './types/index.d.ts'; import { getFromMapOrCreate, nextTick, now, requestIdlePromise } from './plugins/utils/index.ts'; export class QueryCache { public _map: Map<string, RxQuery> = new Map(); /** * check if an equal query is in the cache, * if true, return the cached one, * if false, save the given one and return it */ getByQuery(rxQuery: RxQuery): RxQuery { const stringRep = rxQuery.toString(); const ret = getFromMapOrCreate( this._map, stringRep, () => rxQuery ); return ret; } } export function createQueryCache() { return new QueryCache(); } export function uncacheRxQuery(queryCache: QueryCache, rxQuery: RxQuery) { rxQuery.uncached = true; const stringRep = rxQuery.toString(); queryCache._map.delete(stringRep); } export function countRxQuerySubscribers(rxQuery: RxQuery): number { return rxQuery.refCount$.observers.length; } export const DEFAULT_TRY_TO_KEEP_MAX = 100; export const DEFAULT_UNEXECUTED_LIFETIME = 30 * 1000; /** * The default cache replacement policy * See docs-src/query-cache.md to learn how it should work. * Notice that this runs often and should block the cpu as less as possible * This is a monad which makes it easier to unit test */ export const defaultCacheReplacementPolicyMonad: ( tryToKeepMax: number, unExecutedLifetime: number ) => RxCacheReplacementPolicy = ( tryToKeepMax, unExecutedLifetime ) => ( _collection: RxCollection, queryCache: QueryCache ) => { if (queryCache._map.size < tryToKeepMax) { return; } const minUnExecutedLifetime = now() - unExecutedLifetime; const maybeUncache: RxQuery[] = []; const queriesInCache = Array.from(queryCache._map.values()); for (const rxQuery of queriesInCache) { // filter out queries with subscribers if (countRxQuerySubscribers(rxQuery) > 0) { continue; } // directly uncache queries that never executed and are older than unExecutedLifetime if (rxQuery._lastEnsureEqual === 0 && rxQuery._creationTime < minUnExecutedLifetime) { uncacheRxQuery(queryCache, rxQuery); continue; } maybeUncache.push(rxQuery); } const mustUncache = maybeUncache.length - tryToKeepMax; if (mustUncache <= 0) { return; } const sortedByLastUsage = maybeUncache.sort((a, b) => a._lastEnsureEqual - b._lastEnsureEqual); const toRemove = sortedByLastUsage.slice(0, mustUncache); toRemove.forEach(rxQuery => uncacheRxQuery(queryCache, rxQuery)); }; export const defaultCacheReplacementPolicy: RxCacheReplacementPolicy = defaultCacheReplacementPolicyMonad( DEFAULT_TRY_TO_KEEP_MAX, DEFAULT_UNEXECUTED_LIFETIME ); export const COLLECTIONS_WITH_RUNNING_CLEANUP: WeakSet<RxCollection> = new WeakSet(); /** * Triggers the cache replacement policy after waitTime has passed. * We do not run this directly because at exactly the time a query is created, * we need all CPU to minimize latency. * Also this should not be triggered multiple times when waitTime is still waiting. */ export function triggerCacheReplacement( rxCollection: RxCollection ) { if (COLLECTIONS_WITH_RUNNING_CLEANUP.has(rxCollection)) { // already started return; } COLLECTIONS_WITH_RUNNING_CLEANUP.add(rxCollection); /** * Do not run directly to not reduce result latency of a new query */ nextTick() // wait at least one tick .then(() => requestIdlePromise(200)) // and then wait for the CPU to be idle .then(() => { if (!rxCollection.closed) { rxCollection.cacheReplacementPolicy(rxCollection, rxCollection._queryCache); } COLLECTIONS_WITH_RUNNING_CLEANUP.delete(rxCollection); }); }