UNPKG

@softvisio/core

Version:
233 lines (168 loc) • 5.41 kB
import CacheLruEntry from "#lib/cache/lru/entry"; import List from "#lib/data-structures/list"; import Events from "#lib/events"; import Interval from "#lib/interval"; export default class CacheLru { #maxSize; #maxAge = 0; #autoDeleteExpiredEntries; #list = new List(); #map = new Map(); #events = new Events(); constructor ( { maxSize, maxAge, autoDeleteExpiredEntries = true } = {} ) { this.#setMaxSize( maxSize ); if ( maxAge ) this.#setMaxAge( maxAge ); this.#autoDeleteExpiredEntries = autoDeleteExpiredEntries; } // properties get maxSize () { return this.#maxSize; } set maxSize ( value ) { if ( !this.#setMaxSize( value ) ) return; // truncate cache if ( this.#maxSize > this.#map.size ) { // remove expired entries this.deleteExpiredEntries(); while ( this.#map.size > this.#maxSize ) { this.#delete( this.#list.firstEntry ); } } } get maxAge () { return this.#maxAge; } set maxAge ( value ) { this.#setMaxAge( value ); } get autoDeleteExpiredEntries () { return this.#autoDeleteExpiredEntries; } get size () { return this.#map.size; } // public has ( key ) { const listEntry = this.#map.get( key ); if ( !listEntry ) return false; if ( listEntry.value.isExpired ) { this.#delete( listEntry, true ); return false; } else { return true; } } get ( key, { silent } = {} ) { const listEntry = this.#map.get( key ); if ( !listEntry ) return; // entry is expired if ( listEntry.value.isExpired ) { // delete entry this.#delete( listEntry ); } // entry is not expired else { // move key to the top of the cache if ( !silent ) this.#list.push( listEntry ); // return value return listEntry.value.value; } } set ( key, value, maxAge ) { if ( maxAge == null ) { maxAge = this.#maxAge; } else if ( !maxAge ) { maxAge = 0; } else { maxAge = Interval.new( maxAge ).toMilliseconds(); } const add = maxAge >= 0; var listEntry = this.#map.get( key ); // key is exists if ( listEntry ) { // delete old entry, emit "delete" event if not add this.#delete( listEntry, add ); } // key is not exists, cache size limit reached else if ( add && this.#maxSize && this.#map.size >= this.#maxSize ) { // remove first cached entry this.#delete( this.#list.firstEntry ); } // do not add entry if it was expired if ( !add ) return; const cacheEntry = new CacheLruEntry( this, key, value, maxAge ); listEntry = this.#list.push( cacheEntry ); this.#map.set( key, listEntry ); } delete ( key, { silent } = {} ) { const listEntry = this.#map.get( key ); if ( !listEntry ) return; this.#delete( listEntry, silent ); return listEntry.value.value; } clear ( { silent } = {} ) { this.#map = new Map(); const list = this.#list; this.#list = new List(); const doDelete = this.#autoDeleteExpiredEntries, emit = !silent && this.#events.hasListeners( "delete" ); if ( doDelete || emit ) { for ( const listEntry of list ) { if ( doDelete ) { listEntry.value.delete(); } if ( emit ) { this.#events.emit( "delete", listEntry.value.key, listEntry.value.value ); } } } } deleteExpiredEntries ( { silent } = {} ) { for ( const listEntry of this.#list ) { if ( listEntry.value.isExpired ) this.#delete( listEntry, silent ); } } on ( name, listener ) { this.#events.on( name, listener ); return this; } once ( name, listener ) { this.#events.once( name, listener ); return this; } off ( name, listener ) { this.#events.off( name, listener ); return this; } // protected _getListEntry ( key ) { const listEntry = this.#map.get( key ); if ( listEntry && listEntry.value.isExpired ) this.#delete( listEntry, true ); return listEntry; } // private #setMaxSize ( value ) { if ( !value || value === Infinity ) { value = null; } else if ( !Number.isInteger( value ) || value < 0 ) { throw TypeError( "CacheLru maxSize value is invalid" ); } // not updated if ( value === this.#maxSize ) return; this.#maxSize = value; return true; } #setMaxAge ( value ) { this.#maxAge = Interval.new( value ).toMilliseconds(); } #delete ( listEntry, silent ) { this.#map.delete( listEntry.value.key ); listEntry.delete(); listEntry.value.delete(); if ( !silent ) this.#events.emit( "delete", listEntry.value.key, listEntry.value.value ); } }