import { Injectable } from '@angular/core' ; import { ProcessStorageService , StorageService } from '@app/storage' ; import { Observable , of , throwError } from 'rxjs' ; import { catchError , map , mergeMap , shareReplay , tap } from 'rxjs/operators' ; interface CacheEntry < TValue > { value : TValue ; expiry : number ; } const STORAGE_CACHE_KEY = 'STORAGE_CACHE' ; @ Injectable ( { providedIn : 'root' } ) export class StorageCacheService { private readonly cache : { [ key ] : Observable < any > ; } = { } ; private readonly values : { [ key ] : CacheEntry < any > ; } ; constructor ( private readonly storage , processStorage ) { this . values = processStorage . get ( STORAGE_CACHE_KEY , ( ) => ( { } ) ) ; } public proxy < TValue > ( key , valueFn , expiry ) { return this . get ( key ) . pipe ( mergeMap ( entry => { const now = Date . now ( ) ; if ( entry && entry . expiry > now ) { return of ( entry . value ) ; } if ( ! this . cache [ key ] ) { this . cache [ key ] = valueFn ( ) . pipe ( catchError ( error => { if ( entry ) { console . warn ( `Could not update value for key: '${ key }'. ` + `Using cached value from: '${ new Date ( entry . expiry ) . toISOString ( ) }'.` , error ) ; return of ( entry . value ) ; } return throwError ( error ) ; } ) , tap ( value => { this . cache [ key ] = undefined ; this . store ( key , value , expiry ) ; } ) , shareReplay ( 1 ) ) ; } return this . cache [ key ] ; } ) ) ; } public store < TValue > ( key , value , expiry ) { const now = Date . now ( ) ; const entry = { value , expiry : now + expiry } ; this . values [ key ] = entry ; this . storage . put ( key , entry ) ; return value ; } public retrieve < TValue > ( key ) { return this . get < TValue > ( key ) . pipe ( map ( entry => entry ?. value ) ) ; } public clear ( path ) { const now = Date . now ( ) ; return this . storage . delete < CacheEntry < any > > ( ( key , value ) => { const markAsDeleted = key . startsWith ( path ) && value && value . expiry <= now ; if ( markAsDeleted ) { delete this . values [ key ] ; } return markAsDeleted ; } ) ; } private get < TValue > ( key ) { if ( this . values [ key ] ) { return of ( this . values [ key ] ) ; } return this . storage . get ( key ) ; } }