import { Timer } from './Timer' ; export interface Logger { info : ( ... args : any [ ] ) => void ; } export interface ClassType < T > { name : string ; new ( ) : T ; } export interface CacheEntry < V > { key : string ; value : V ; modified : number ; } export interface CacheListener < V > { added ? : ( k : string , v : V | null ) => void ; retrieved ? : ( k : string , v : V | null ) => V | null | undefined ; removed ? : ( k : string , v : V | null ) => void ; evicted ? : ( k : string , v : V | null ) => void ; } export class DataCacheManager { static defaultMaxAge = 1 * 60 * 1000 ; log : Logger ; caches : DataCache < any > [ ] = [ ] ; evictionTimer : Timer ; constructor ( log = console ) { this . log = log ; this . evictionTimer = new Timer ( 2000 , this . runCacheEviction . bind ( this ) ) ; this . startEvictionTimer ( ) ; } startEvictionTimer ( ) { this . evictionTimer . start ( ) ; } stopEvictionTimer ( ) { this . evictionTimer . stop ( ) ; } register ( cache ) { cache . lastEviction = this . getTime ( ) ; this . caches . push ( cache ) ; } getTime ( ) { return Date . now ( ) ; } runCacheEviction ( ) { const now = this . getTime ( ) ; for ( let cacheIdx = 0 ; cacheIdx < this . caches . length ; cacheIdx ++ ) { const cache = this . caches [ cacheIdx ] ; if ( now - cache . lastEviction < cache . maxAge ) { continue ; } cache . lastEviction = now ; for ( const key of Object . keys ( cache . _data ) ) { const val = cache . _data [ key ] ; if ( val && now - val . modified >= cache . maxAge ) { delete cache . _data [ key ] ; if ( cache . listeners ) { for ( const l of cache . listeners ) { if ( l . evicted ) { l . evicted ( val . key , val . value ) ; } } } } } } } } export class DataCache < V > { manager : DataCacheManager ; _data : { [ key ] : CacheEntry < V > ; } = { } ; lastEviction : number ; maxAge : number ; listeners : CacheListener < V > [ ] ; constructor ( maxAge = DataCacheManager . defaultMaxAge , manager = new DataCacheManager ( console ) ) { this . maxAge = maxAge ; this . manager = manager ; this . manager . register ( this ) ; } addListener ( listener ) { this . listeners = this . listeners || [ ] ; this . listeners . push ( listener ) ; } get ( key ) { const entry = this . _data [ key ] ; let value = entry && entry . value ; if ( this . listeners ) { for ( const l of this . listeners ) { if ( l . retrieved ) { const v = l . retrieved ( key , value ) ; if ( v !== undefined ) { value = v ; } } } } return value ; } set ( key , value ) { this . _set ( key , value ) ; if ( this . listeners ) { for ( const l of this . listeners ) { if ( l . added ) { l . added ( key , value ) ; } } } } _set ( key , value ) { const cacheEntry = { key , value , modified : this . manager . getTime ( ) , } ; this . _data [ key ] = cacheEntry ; } remove ( key ) { const prev = this . _data [ key ] ; try { delete this . _data [ key ] ; return prev && prev . value ; } finally { if ( this . listeners ) { if ( prev !== undefined ) { for ( const l of this . listeners ) { if ( l . removed ) { l . removed ( key , prev . value ) ; } } } } } } get size ( ) { return Object . keys ( this . _data ) . length ; } }