From 11c4afb6e3ccee5a8275b1c738b240a445982965 Mon Sep 17 00:00:00 2001 From: Angel Nikolov Date: Sun, 18 Apr 2021 12:19:15 +0300 Subject: [PATCH] Expose context to storage strategy methods. --- cacheable.decorator.ts | 14 +- common/IAsyncStorageStrategy.ts | 16 +- common/IStorageStrategy.ts | 16 +- common/InMemoryStorageStrategy.ts | 4 +- promise.cacheable.decorator.ts | 14 +- specs/observable-cacheable.decorator.spec.ts | 196 +++++++++++-------- specs/promise-cacheable.decorator.spec.ts | 21 +- specs/service.interface.ts | 1 + 8 files changed, 161 insertions(+), 121 deletions(-) diff --git a/cacheable.decorator.ts b/cacheable.decorator.ts index a0c6c2f..33c7e77 100644 --- a/cacheable.decorator.ts +++ b/cacheable.decorator.ts @@ -19,7 +19,7 @@ export function Cacheable(cacheConfig: IObservableCacheConfig = {}) { : new cacheConfig.storageStrategy(); const pendingCachePairs: Array>> = []; if (cacheConfig.cacheModifier) { - cacheConfig.cacheModifier.subscribe(callback => storageStrategy.addMany(callback(storageStrategy.getAll(cacheKey)), cacheKey)) + cacheConfig.cacheModifier.subscribe(callback => storageStrategy.addMany(callback(storageStrategy.getAll(cacheKey, this)), cacheKey, this)) } /** * subscribe to the globalCacheBuster @@ -32,7 +32,7 @@ export function Cacheable(cacheConfig: IObservableCacheConfig = {}) { ? cacheConfig.cacheBusterObserver : empty() ).subscribe(_ => { - storageStrategy.removeAll(cacheKey); + storageStrategy.removeAll(cacheKey, this); pendingCachePairs.length = 0; }); const cacheResolver = cacheConfig.cacheResolver || GlobalCacheConfig.cacheResolver; @@ -46,7 +46,7 @@ export function Cacheable(cacheConfig: IObservableCacheConfig = {}) { /* use function instead of an arrow function to keep context of invocation */ (propertyDescriptor.value as any) = function(...parameters: Array) { - const cachePairs: Array>> = storageStrategy.getAll(cacheKey); + const cachePairs: Array>> = storageStrategy.getAll(cacheKey, this); let cacheParameters = cacheConfig.cacheHasher(parameters); let _foundCachePair = cachePairs.find(cp => cacheConfig.cacheResolver(cp.parameters, cacheParameters)); @@ -64,14 +64,14 @@ export function Cacheable(cacheConfig: IObservableCacheConfig = {}) { /** * cache duration has expired - remove it from the cachePairs array */ - storageStrategy.remove ? storageStrategy.remove(cachePairs.indexOf(_foundCachePair), _foundCachePair, cacheKey) : storageStrategy.removeAtIndex(cachePairs.indexOf(_foundCachePair), cacheKey); + storageStrategy.remove ? storageStrategy.remove(cachePairs.indexOf(_foundCachePair), _foundCachePair, cacheKey, this) : storageStrategy.removeAtIndex(cachePairs.indexOf(_foundCachePair), cacheKey, this); _foundCachePair = null; } else if (cacheConfig.slidingExpiration || GlobalCacheConfig.slidingExpiration) { /** * renew cache duration */ _foundCachePair.created = new Date(); - storageStrategy.update ? storageStrategy.update(cachePairs.indexOf(_foundCachePair), _foundCachePair, cacheKey) : storageStrategy.updateAtIndex(cachePairs.indexOf(_foundCachePair), _foundCachePair, cacheKey); + storageStrategy.update ? storageStrategy.update(cachePairs.indexOf(_foundCachePair), _foundCachePair, cacheKey, this) : storageStrategy.updateAtIndex(cachePairs.indexOf(_foundCachePair), _foundCachePair, cacheKey, this); } } @@ -111,13 +111,13 @@ export function Cacheable(cacheConfig: IObservableCacheConfig = {}) { ((cacheConfig.maxCacheCount || GlobalCacheConfig.maxCacheCount) && (cacheConfig.maxCacheCount || GlobalCacheConfig.maxCacheCount) < cachePairs.length + 1) ) { - storageStrategy.remove ? storageStrategy.remove(0, cachePairs[0], cacheKey) : storageStrategy.removeAtIndex(0, cacheKey); + storageStrategy.remove ? storageStrategy.remove(0, cachePairs[0], cacheKey, this) : storageStrategy.removeAtIndex(0, cacheKey, this); } storageStrategy.add({ parameters: cacheParameters, response, created: (cacheConfig.maxAge || GlobalCacheConfig.maxAge) ? new Date() : null - }, cacheKey); + }, cacheKey, this); } }), publishReplay(1), diff --git a/common/IAsyncStorageStrategy.ts b/common/IAsyncStorageStrategy.ts index acfcf44..dbcd8d2 100644 --- a/common/IAsyncStorageStrategy.ts +++ b/common/IAsyncStorageStrategy.ts @@ -1,18 +1,18 @@ import {ICachePair} from '.'; export abstract class IAsyncStorageStrategy { - abstract getAll(cacheKey: string): Array> | Promise>>; - abstract add(entity: ICachePair, cacheKey: string): void | Promise; + abstract getAll(cacheKey: string, ctx?: any): Array> | Promise>>; + abstract add(entity: ICachePair, cacheKey: string, ctx?: any): void | Promise; /** * @deprecated Use update instead. */ - abstract updateAtIndex(index: number, entity: ICachePair, cacheKey: string): void | Promise; - abstract update?(index: number, entity: ICachePair, cacheKey: string): Promise; + abstract updateAtIndex(index: number, entity: ICachePair, cacheKey: string, ctx?: any): void | Promise; + abstract update?(index: number, entity: ICachePair, cacheKey: string, ctx?: any): Promise; /** * @deprecated Use remove instead. */ - abstract removeAtIndex(index: number, cacheKey: string): void | Promise; - abstract remove?(index: number, entity: ICachePair, cacheKey: string): Promise; - abstract removeAll(cacheKey: string): void | Promise; - abstract addMany(entities: ICachePair[], cacheKey: string): Promise; + abstract removeAtIndex(index: number, cacheKey: string, ctx?: any): void | Promise; + abstract remove?(index: number, entity: ICachePair, cacheKey: string, ctx?: any): Promise; + abstract removeAll(cacheKey: string, ctx?: any): void | Promise; + abstract addMany(entities: ICachePair[], cacheKey: string, ctx?: any): Promise; } \ No newline at end of file diff --git a/common/IStorageStrategy.ts b/common/IStorageStrategy.ts index 5c1536a..7341139 100644 --- a/common/IStorageStrategy.ts +++ b/common/IStorageStrategy.ts @@ -1,18 +1,18 @@ import {ICachePair} from '.'; export abstract class IStorageStrategy { - abstract getAll(cacheKey: string): Array>; - abstract add(entity: ICachePair, cacheKey: string): void; + abstract getAll(cacheKey: string, ctx?: any): Array>; + abstract add(entity: ICachePair, cacheKey: string, ctx?: any): void; /** * @deprecated Use update instead. */ - abstract updateAtIndex(index: number, entity: ICachePair, cacheKey: string): void; - abstract update?(index: number, entity: ICachePair, cacheKey: string): void; + abstract updateAtIndex(index: number, entity: ICachePair, cacheKey: string, ctx?: any): void; + abstract update?(index: number, entity: ICachePair, cacheKey: string, ctx?: any): void; /** * @deprecated Use remove instead. */ - abstract removeAtIndex(index: number, cacheKey: string): void; - abstract remove?(index: number, entity: ICachePair, cacheKey: string): void; - abstract removeAll(cacheKey: string): void; - abstract addMany(entities: ICachePair[], cacheKey: string): void; + abstract removeAtIndex(index: number, cacheKey: string, ctx?: any): void; + abstract remove?(index: number, entity: ICachePair, cacheKey: string, ctx?: any): void; + abstract removeAll(cacheKey: string, ctx?: any): void; + abstract addMany(entities: ICachePair[], cacheKey: string, ctx?: any): void; } \ No newline at end of file diff --git a/common/InMemoryStorageStrategy.ts b/common/InMemoryStorageStrategy.ts index 4404d51..ad8a53b 100644 --- a/common/InMemoryStorageStrategy.ts +++ b/common/InMemoryStorageStrategy.ts @@ -4,8 +4,8 @@ import {ICachePair} from '.'; export class InMemoryStorageStrategy extends IStorageStrategy { private cachePairs: Array> = []; - add(cachePair: ICachePair) { - this.cachePairs.push(cachePair) + add(cachePair: ICachePair, cacheKey: string, ctx?: any) { + this.cachePairs.push(cachePair); }; addMany(cachePairs: ICachePair[]) { diff --git a/promise.cacheable.decorator.ts b/promise.cacheable.decorator.ts index 444e037..2ff0a83 100644 --- a/promise.cacheable.decorator.ts +++ b/promise.cacheable.decorator.ts @@ -25,14 +25,14 @@ const getResponse = (oldMethod: Function, cacheKey: string, cacheConfig: ICacheC /** * cache duration has expired - remove it from the cachePairs array */ - storageStrategy.remove ? storageStrategy.remove(cachePairs.indexOf(_foundCachePair), _foundCachePair, cacheKey) : storageStrategy.removeAtIndex(cachePairs.indexOf(_foundCachePair), cacheKey); + storageStrategy.remove ? storageStrategy.remove(cachePairs.indexOf(_foundCachePair), _foundCachePair, cacheKey, this) : storageStrategy.removeAtIndex(cachePairs.indexOf(_foundCachePair), cacheKey, this); _foundCachePair = null; } else if (cacheConfig.slidingExpiration || GlobalCacheConfig.slidingExpiration) { /** * renew cache duration */ _foundCachePair.created = new Date(); - storageStrategy.update ? storageStrategy.update(cachePairs.indexOf(_foundCachePair), _foundCachePair, cacheKey) : storageStrategy.updateAtIndex(cachePairs.indexOf(_foundCachePair), _foundCachePair, cacheKey); + storageStrategy.update ? storageStrategy.update(cachePairs.indexOf(_foundCachePair), _foundCachePair, cacheKey, this) : storageStrategy.updateAtIndex(cachePairs.indexOf(_foundCachePair), _foundCachePair, cacheKey, this); } } @@ -60,13 +60,13 @@ const getResponse = (oldMethod: Function, cacheKey: string, cacheConfig: ICacheC ((cacheConfig.maxCacheCount || GlobalCacheConfig.maxCacheCount) && (cacheConfig.maxCacheCount || GlobalCacheConfig.maxCacheCount) < cachePairs.length + 1) ) { - storageStrategy.remove ? storageStrategy.remove(0, cachePairs[0], cacheKey) : storageStrategy.removeAtIndex(0, cacheKey); + storageStrategy.remove ? storageStrategy.remove(0, cachePairs[0], cacheKey, this) : storageStrategy.removeAtIndex(0, cacheKey, this); } storageStrategy.add({ parameters: cacheParameters, response, created: (cacheConfig.maxAge || GlobalCacheConfig.maxAge) ? new Date() : null - }, cacheKey); + }, cacheKey, this); } return response; @@ -116,7 +116,7 @@ export function PCacheable(cacheConfig: ICacheConfig = {}) { : new cacheConfig.storageStrategy(); const pendingCachePairs: Array>> = []; if (cacheConfig.cacheModifier) { - cacheConfig.cacheModifier.subscribe(async callback => storageStrategy.addMany(callback(await storageStrategy.getAll(cacheKey)), cacheKey)) + cacheConfig.cacheModifier.subscribe(async callback => storageStrategy.addMany(callback(await storageStrategy.getAll(cacheKey, this)), cacheKey, this)) } /** * subscribe to the promiseGlobalCacheBusterNotifier @@ -129,7 +129,7 @@ export function PCacheable(cacheConfig: ICacheConfig = {}) { ? cacheConfig.cacheBusterObserver : empty() ).subscribe(_ => { - storageStrategy.removeAll(cacheKey); + storageStrategy.removeAll(cacheKey, this); pendingCachePairs.length = 0; }); @@ -147,7 +147,7 @@ export function PCacheable(cacheConfig: ICacheConfig = {}) { const promiseImplementation = typeof GlobalCacheConfig.promiseImplementation === 'function' && (GlobalCacheConfig.promiseImplementation !== Promise) ? (GlobalCacheConfig.promiseImplementation as () => PromiseConstructorLike).call(this) : GlobalCacheConfig.promiseImplementation as PromiseConstructorLike; - let cachePairs = storageStrategy.getAll(cacheKey); + let cachePairs = storageStrategy.getAll(cacheKey, this); if (!(cachePairs instanceof promiseImplementation)) { cachePairs = promiseImplementation.resolve(cachePairs); } diff --git a/specs/observable-cacheable.decorator.spec.ts b/specs/observable-cacheable.decorator.spec.ts index dd05ab9..effc7c3 100644 --- a/specs/observable-cacheable.decorator.spec.ts +++ b/specs/observable-cacheable.decorator.spec.ts @@ -1,15 +1,22 @@ -import { combineLatest, forkJoin, Observable } from 'rxjs'; -import { startWith } from 'rxjs/operators'; -import { globalCacheBusterNotifier } from '../cacheable.decorator'; -import { Cacheable } from '../cacheable.decorator'; -import { CacheBuster } from '../cache-buster.decorator'; -import { timer, Subject } from 'rxjs'; -import { mapTo } from 'rxjs/operators'; -import { GlobalCacheConfig } from '../common'; -import { LocalStorageStrategy } from '../common/LocalStorageStrategy'; -import { InMemoryStorageStrategy } from '../common/InMemoryStorageStrategy'; -import { IService } from './service.interface'; -import { Cat } from './cat'; +import {combineLatest, forkJoin, Observable} from 'rxjs'; +import {startWith} from 'rxjs/operators'; +import {globalCacheBusterNotifier} from '../cacheable.decorator'; +import {Cacheable} from '../cacheable.decorator'; +import {CacheBuster} from '../cache-buster.decorator'; +import {timer, Subject} from 'rxjs'; +import {mapTo} from 'rxjs/operators'; +import {GlobalCacheConfig, ICachePair} from '../common'; +import {LocalStorageStrategy} from '../common/LocalStorageStrategy'; +import {InMemoryStorageStrategy} from '../common/InMemoryStorageStrategy'; +import {IService} from './service.interface'; +import {Cat} from './cat'; +let customStrategySpy: jasmine.Spy = jasmine.createSpy(); +export class CustomContextStrategy extends InMemoryStorageStrategy { + add(cachePair: ICachePair, cacheKey: string, ctx?: any) { + customStrategySpy(ctx); + super.add(cachePair, cacheKey, ctx); + }; +} const strategies: any[] = [ null, LocalStorageStrategy @@ -26,14 +33,14 @@ strategies.forEach(s => { const cacheBusterNotifier = new Subject(); class Service { mockServiceCall(parameter: any) { - return timer(1000).pipe(mapTo({ payload: parameter })); + return timer(1000).pipe(mapTo({payload: parameter})); } mockSaveServiceCall() { return timer(1000).pipe(mapTo('SAVED')); } mockServiceCallWithMultipleParameters(parameter1: any, parameter2: any) { - return timer(1000).pipe(mapTo({ payload: [parameter1, parameter2] })); + return timer(1000).pipe(mapTo({payload: [parameter1, parameter2]})); } @Cacheable() @@ -104,7 +111,7 @@ strategies.forEach(s => { }) getDataWithCustomCacheResolver( parameter: string, - _cacheRerouterParameter?: { straightToLastCache: boolean } + _cacheRerouterParameter?: {straightToLastCache: boolean} ) { return this.mockServiceCall(parameter); } @@ -129,7 +136,7 @@ strategies.forEach(s => { } @Cacheable({ - shouldCacheDecider: (response: { payload: string }) => { + shouldCacheDecider: (response: {payload: string}) => { return response.payload === 'test'; } }) @@ -176,6 +183,14 @@ strategies.forEach(s => { getMutableData(parameter: string) { return this.mockServiceCall(parameter); } + + @Cacheable({ + storageStrategy: CustomContextStrategy, + maxCacheCount: 3 + }) + getDataWithCustomContextStorageStrategy(parameter: string = 'Parameter1') { + return this.mockServiceCall(parameter); + } } jasmine.clock().install(); service = new Service(); @@ -200,11 +215,11 @@ strategies.forEach(s => { service.getData('test'), 1000 ); - expect(asyncFreshData).toEqual({ payload: 'test' }); + expect(asyncFreshData).toEqual({payload: 'test'}); expect(mockServiceCallSpy).toHaveBeenCalledTimes(1); const cachedResponse = _timedStreamAsyncAwait(service.getData('test')); - expect(cachedResponse).toEqual({ payload: 'test' }); + expect(cachedResponse).toEqual({payload: 'test'}); /** * response acquired from cache, so no incrementation on the service spy call counter is expected here */ @@ -226,7 +241,7 @@ strategies.forEach(s => { /** * service call is made and waited out */ - expect(cachedResponse3).toEqual({ payload: 'test3' }); + expect(cachedResponse3).toEqual({payload: 'test3'}); /** * this should NOT return cached response, since the currently cached one should be 'test3' @@ -270,7 +285,7 @@ strategies.forEach(s => { * call the service endpoint five hundred times with the same parameter * but the service should only be called once, since the observable will be cached */ - for (let i = 0; i < 500; i++) { + for (let i = 0;i < 500;i++) { service.getDataAndReturnCachedStream('test'); } @@ -294,7 +309,7 @@ strategies.forEach(s => { service.getDataAsync('test'), 1000 ); - expect(asyncFreshData).toEqual({ payload: 'test' }); + expect(asyncFreshData).toEqual({payload: 'test'}); expect(mockServiceCallSpy).toHaveBeenCalledTimes(1); const cachedResponseTry1 = _timedStreamAsyncAwait( @@ -312,7 +327,7 @@ strategies.forEach(s => { service.getDataAsync('test'), 1 ); - expect(cachedResponseTry2).toEqual({ payload: 'test' }); + expect(cachedResponseTry2).toEqual({payload: 'test'}); /** * response acquired from cache, so no incrementation on the service spy call counter is expected here */ @@ -340,7 +355,7 @@ strategies.forEach(s => { /** * service call is made and waited out */ - expect(cachedResponse3).toEqual({ payload: 'test3' }); + expect(cachedResponse3).toEqual({payload: 'test3'}); /** * this should return cached response, since the currently cached one should be 'test3' @@ -361,7 +376,7 @@ strategies.forEach(s => { 1000 ); - expect(asyncFreshData).toEqual({ payload: 'test' }); + expect(asyncFreshData).toEqual({payload: 'test'}); expect(mockServiceCallSpy).toHaveBeenCalledTimes(1); const cachedResponse = _timedStreamAsyncAwait( @@ -371,7 +386,7 @@ strategies.forEach(s => { * service shouldn't be called and we should route directly to cache */ expect(mockServiceCallSpy).toHaveBeenCalledTimes(1); - expect(cachedResponse).toEqual({ payload: 'test' }); + expect(cachedResponse).toEqual({payload: 'test'}); /** * progress in time for 7501 ms, e.g - one millisecond after the maxAge would expire @@ -392,7 +407,7 @@ strategies.forEach(s => { asyncFreshDataAfterCacheBust = data; }); jasmine.clock().tick(1000); - expect(asyncFreshDataAfterCacheBust).toEqual({ payload: 'test' }); + expect(asyncFreshDataAfterCacheBust).toEqual({payload: 'test'}); }); it('return cached data up until the maxAge period but renew the expiration if called within the period', () => { @@ -401,13 +416,13 @@ strategies.forEach(s => { service.getDataWithSlidingExpiration('test'), 1000 ); - expect(asyncFreshData).toEqual({ payload: 'test' }); + expect(asyncFreshData).toEqual({payload: 'test'}); expect(mockServiceCallSpy).toHaveBeenCalledTimes(1); const cachedResponse = _timedStreamAsyncAwait( service.getDataWithSlidingExpiration('test') ); - expect(cachedResponse).toEqual({ payload: 'test' }); + expect(cachedResponse).toEqual({payload: 'test'}); /** * call count should still be one, since we rerouted to cache, instead of service call */ @@ -430,7 +445,7 @@ strategies.forEach(s => { const cachedResponse2 = _timedStreamAsyncAwait( service.getDataWithSlidingExpiration('test') ); - expect(cachedResponse2).toEqual({ payload: 'test' }); + expect(cachedResponse2).toEqual({payload: 'test'}); /** * call count is still one, because we renewed the cache 4501ms ago */ @@ -467,7 +482,7 @@ strategies.forEach(s => { const cachedResponse = _timedStreamAsyncAwait( service.getDataWithMaxCacheCount('test1') ); - expect(cachedResponse).toEqual({ payload: 'test1' }); + expect(cachedResponse).toEqual({payload: 'test1'}); /** call count still 5 */ expect(mockServiceCallSpy).toHaveBeenCalledTimes(5); @@ -479,11 +494,11 @@ strategies.forEach(s => { ); expect(cachedResponseAll).toEqual([ - { payload: 'test1' }, - { payload: 'test2' }, - { payload: 'test3' }, - { payload: 'test4' }, - { payload: 'test5' } + {payload: 'test1'}, + {payload: 'test2'}, + {payload: 'test3'}, + {payload: 'test4'}, + {payload: 'test5'} ]); /** call count still 5 */ expect(mockServiceCallSpy).toHaveBeenCalledTimes(5); @@ -493,7 +508,7 @@ strategies.forEach(s => { 1000 ); - expect(asyncData).toEqual({ payload: 'test6' }); + expect(asyncData).toEqual({payload: 'test6'}); /** call count incremented by one */ expect(mockServiceCallSpy).toHaveBeenCalledTimes(6); @@ -513,11 +528,11 @@ strategies.forEach(s => { ); expect(cachedResponseAll2).toEqual([ - { payload: 'test2' }, - { payload: 'test3' }, - { payload: 'test4' }, - { payload: 'test5' }, - { payload: 'test6' } + {payload: 'test2'}, + {payload: 'test3'}, + {payload: 'test4'}, + {payload: 'test5'}, + {payload: 'test6'} ]); /** no service calls will be made, since we have all the responses still cached even after 1s (1000ms) */ @@ -529,7 +544,7 @@ strategies.forEach(s => { service.getDataWithMaxCacheCount('test7'), 1000 ); - expect(nonCachedResponse).toEqual({ payload: 'test7' }); + expect(nonCachedResponse).toEqual({payload: 'test7'}); expect(mockServiceCallSpy).toHaveBeenCalledTimes(7); /** @@ -568,11 +583,11 @@ strategies.forEach(s => { expect(mockServiceCallSpy).toHaveBeenCalledTimes(5); expect(cachedResponse2).toEqual([ - { payload: 'test1' }, - { payload: 'test2' }, - { payload: 'test3' }, - { payload: 'test4' }, - { payload: 'test5' } + {payload: 'test1'}, + {payload: 'test2'}, + {payload: 'test3'}, + {payload: 'test4'}, + {payload: 'test5'} ]); /** @@ -635,7 +650,7 @@ strategies.forEach(s => { expect(mockServiceCallSpy).toHaveBeenCalledTimes(9); expect(cachedResponse).toEqual([ null, - { payload: 'test2' }, + {payload: 'test2'}, null, null, null @@ -648,14 +663,14 @@ strategies.forEach(s => { service.getDataWithCustomCacheResolver('test1'), 1000 ); - expect(asyncFreshData).toEqual({ payload: 'test1' }); + expect(asyncFreshData).toEqual({payload: 'test1'}); expect(mockServiceCallSpy).toHaveBeenCalled(); const asyncFreshData2 = _timedStreamAsyncAwait( service.getDataWithCustomCacheResolver('test2'), 1000 ); - expect(asyncFreshData2).toEqual({ payload: 'test2' }); + expect(asyncFreshData2).toEqual({payload: 'test2'}); expect(mockServiceCallSpy).toHaveBeenCalledTimes(2); const cachedResponse = _timedStreamAsyncAwait( @@ -663,7 +678,7 @@ strategies.forEach(s => { straightToLastCache: true }) ); - expect(cachedResponse).toEqual({ payload: 'test2' }); + expect(cachedResponse).toEqual({payload: 'test2'}); /** * call count still 2, since we rerouted directly to cache */ @@ -699,7 +714,7 @@ strategies.forEach(s => { }; class Service { mockServiceCall(parameter: any) { - return timer(1000).pipe(mapTo({ payload: parameter })); + return timer(1000).pipe(mapTo({payload: parameter})); } @Cacheable() getData(parameter: string) { @@ -745,7 +760,7 @@ strategies.forEach(s => { service.getDataWithCustomCacheDecider('test1'), 1000 ); - expect(asyncData).toEqual({ payload: 'test1' }); + expect(asyncData).toEqual({payload: 'test1'}); expect(mockServiceCallSpy).toHaveBeenCalledTimes(1); /** @@ -765,7 +780,7 @@ strategies.forEach(s => { service.getDataWithCustomCacheDecider('test'), 1000 ); - expect(asyncData2).toEqual({ payload: 'test' }); + expect(asyncData2).toEqual({payload: 'test'}); expect(mockServiceCallSpy).toHaveBeenCalledTimes(3); /** @@ -774,7 +789,7 @@ strategies.forEach(s => { const cachedData2 = _timedStreamAsyncAwait( service.getDataWithCustomCacheDecider('test') ); - expect(cachedData2).toEqual({ payload: 'test' }); + expect(cachedData2).toEqual({payload: 'test'}); /** * the service call count won't be incremented */ @@ -786,13 +801,13 @@ strategies.forEach(s => { service.getDataWithCacheBusting('test'), 1000 ); - expect(asyncFreshData).toEqual({ payload: 'test' }); + expect(asyncFreshData).toEqual({payload: 'test'}); expect(mockServiceCallSpy).toHaveBeenCalledTimes(1); const cachedResponse = _timedStreamAsyncAwait( service.getDataWithCacheBusting('test') ); - expect(cachedResponse).toEqual({ payload: 'test' }); + expect(cachedResponse).toEqual({payload: 'test'}); /** * response acquired from cache, so no incrementation on the service spy call counter is expected here */ @@ -823,7 +838,7 @@ strategies.forEach(s => { */ expect( _timedStreamAsyncAwait(service.getDataWithCacheBusting('test')) - ).toEqual({ payload: 'test' }); + ).toEqual({payload: 'test'}); }); it('should clear all caches when the global cache buster is called', () => { @@ -835,9 +850,9 @@ strategies.forEach(s => { service.getData('test1'), 1000 ); - expect(asyncFreshData1).toEqual({ payload: 'test1' }); + expect(asyncFreshData1).toEqual({payload: 'test1'}); const cachedResponse1 = _timedStreamAsyncAwait(service.getData('test1')); - expect(cachedResponse1).toEqual({ payload: 'test1' }); + expect(cachedResponse1).toEqual({payload: 'test1'}); /** * even though we called getData twice, this should only be called once * since the second call went straight to the cache @@ -849,9 +864,9 @@ strategies.forEach(s => { service.getData('test2'), 1000 ); - expect(asyncFreshData2).toEqual({ payload: 'test2' }); + expect(asyncFreshData2).toEqual({payload: 'test2'}); const cachedResponse2 = _timedStreamAsyncAwait(service.getData('test2')); - expect(cachedResponse2).toEqual({ payload: 'test2' }); + expect(cachedResponse2).toEqual({payload: 'test2'}); expect(mockServiceCallSpy).toHaveBeenCalledTimes(2); @@ -860,9 +875,9 @@ strategies.forEach(s => { service.getData('test3'), 1000 ); - expect(asyncFreshData3).toEqual({ payload: 'test3' }); + expect(asyncFreshData3).toEqual({payload: 'test3'}); const cachedResponse3 = _timedStreamAsyncAwait(service.getData('test3')); - expect(cachedResponse3).toEqual({ payload: 'test3' }); + expect(cachedResponse3).toEqual({payload: 'test3'}); expect(mockServiceCallSpy).toHaveBeenCalledTimes(3); /** @@ -901,7 +916,7 @@ strategies.forEach(s => { 1000 ); - expect(asyncData).toEqual({ payload: ['Parameter1', 'Parameter2'] }); + expect(asyncData).toEqual({payload: ['Parameter1', 'Parameter2']}); expect(mockServiceCallWithMultipleParametersSpy).toHaveBeenCalledWith('Parameter1', 'Parameter2'); service.getDataWithMultipleUndefinedParameters(undefined, undefined); @@ -925,7 +940,7 @@ strategies.forEach(s => { ); // called removeAtIndex once, because of how the cache works, it always removes the last cached pair with this method expect(removeSpy).toHaveBeenCalledTimes(1); - expect(asyncFreshData).toEqual({ payload: 'test' }); + expect(asyncFreshData).toEqual({payload: 'test'}); expect(mockServiceCallSpy).toHaveBeenCalledTimes(1); // one add call, one getAll call expect(getAllSpy).toHaveBeenCalledTimes(1); @@ -936,7 +951,7 @@ strategies.forEach(s => { ); // this call will renew the updateAtIndex call count since it's used to renew the cache expect(updateSpy).toHaveBeenCalledTimes(1); - expect(cachedResponse).toEqual({ payload: 'test' }); + expect(cachedResponse).toEqual({payload: 'test'}); /** * call count should still be one, since we rerouted to cache, instead of service call */ @@ -972,7 +987,7 @@ strategies.forEach(s => { // one more getAll call, and still just one add call, since the cache was renewed due to sliding expiration expect(getAllSpy).toHaveBeenCalledTimes(4); expect(addSpy).toHaveBeenCalledTimes(1); - expect(cachedResponse2).toEqual({ payload: 'test' }); + expect(cachedResponse2).toEqual({payload: 'test'}); /** * call count is still one, because we renewed the cache 4501ms ago */ @@ -1015,13 +1030,13 @@ strategies.forEach(s => { service.getData('test'), 1000 ); - expect(asyncFreshData).toEqual({ payload: 'test' }); + expect(asyncFreshData).toEqual({payload: 'test'}); expect(mockServiceCallSpy).toHaveBeenCalledTimes(1); const cachedResponse = _timedStreamAsyncAwait( service.getData('test') ); - expect(cachedResponse).toEqual({ payload: 'test' }); + expect(cachedResponse).toEqual({payload: 'test'}); /** * call count should still be one, since we rerouted to cache, instead of service call */ @@ -1044,7 +1059,7 @@ strategies.forEach(s => { const cachedResponse2 = _timedStreamAsyncAwait( service.getData('test') ); - expect(cachedResponse2).toEqual({ payload: 'test' }); + expect(cachedResponse2).toEqual({payload: 'test'}); /** * call count is still one, because we renewed the cache 4501ms ago */ @@ -1084,7 +1099,7 @@ strategies.forEach(s => { const cachedResponse = _timedStreamAsyncAwait( service.getData('test1') ); - expect(cachedResponse).toEqual({ payload: 'test1' }); + expect(cachedResponse).toEqual({payload: 'test1'}); /** call count still 5 */ expect(mockServiceCallSpy).toHaveBeenCalledTimes(5); @@ -1096,11 +1111,11 @@ strategies.forEach(s => { ); expect(cachedResponseAll).toEqual([ - { payload: 'test1' }, - { payload: 'test2' }, - { payload: 'test3' }, - { payload: 'test4' }, - { payload: 'test5' } + {payload: 'test1'}, + {payload: 'test2'}, + {payload: 'test3'}, + {payload: 'test4'}, + {payload: 'test5'} ]); /** call count still 5 */ expect(mockServiceCallSpy).toHaveBeenCalledTimes(5); @@ -1110,7 +1125,7 @@ strategies.forEach(s => { 1000 ); - expect(asyncData).toEqual({ payload: 'test6' }); + expect(asyncData).toEqual({payload: 'test6'}); /** call count incremented by one */ expect(mockServiceCallSpy).toHaveBeenCalledTimes(6); @@ -1129,11 +1144,11 @@ strategies.forEach(s => { 1000 ); expect(cachedResponseAll2).toEqual([ - { payload: 'test2' }, - { payload: 'test3' }, - { payload: 'test4' }, - { payload: 'test5' }, - { payload: 'test6' } + {payload: 'test2'}, + {payload: 'test3'}, + {payload: 'test4'}, + {payload: 'test5'}, + {payload: 'test6'} ]); /** no service calls will be made, since we have all the responses still cached even after 1s (1000ms) */ @@ -1145,7 +1160,7 @@ strategies.forEach(s => { service.getData('test7'), 1000 ); - expect(nonCachedResponse).toEqual({ payload: 'test7' }); + expect(nonCachedResponse).toEqual({payload: 'test7'}); expect(mockServiceCallSpy).toHaveBeenCalledTimes(7); /** @@ -1171,7 +1186,7 @@ strategies.forEach(s => { 1000 ); - expect(asyncFreshData).toEqual({ payload: 'test' }); + expect(asyncFreshData).toEqual({payload: 'test'}); expect(mockServiceCallSpy).toHaveBeenCalledTimes(1); const cachedResponse = _timedStreamAsyncAwait( @@ -1181,7 +1196,7 @@ strategies.forEach(s => { * service shouldn't be called and we should route directly to cache */ expect(mockServiceCallSpy).toHaveBeenCalledTimes(1); - expect(cachedResponse).toEqual({ payload: 'test' }); + expect(cachedResponse).toEqual({payload: 'test'}); /** * progress in time for 10001 ms, e.g - one millisecond after the maxAge would expire @@ -1202,7 +1217,7 @@ strategies.forEach(s => { asyncFreshDataAfterCacheBust = data; }); jasmine.clock().tick(1000); - expect(asyncFreshDataAfterCacheBust).toEqual({ payload: 'test' }); + expect(asyncFreshDataAfterCacheBust).toEqual({payload: 'test'}); GlobalCacheConfig.maxAge = undefined; }); @@ -1227,6 +1242,13 @@ strategies.forEach(s => { const cachedResponse2 = _timedStreamAsyncAwait(service.getMutableData('test')); expect(cachedResponse2).toEqual({payload: 'test_modified'}); }); + it('should work with a custom context storage strategy', () => { + _timedStreamAsyncAwait( + service.getDataWithCustomContextStorageStrategy('test'), + 1000 + ); + expect(customStrategySpy).toHaveBeenCalledWith(jasmine.any(Object)); + }); }); function _timedStreamAsyncAwait(stream$: Observable, skipTime?: number): any { diff --git a/specs/promise-cacheable.decorator.spec.ts b/specs/promise-cacheable.decorator.spec.ts index 411bbad..a202eb2 100644 --- a/specs/promise-cacheable.decorator.spec.ts +++ b/specs/promise-cacheable.decorator.spec.ts @@ -8,6 +8,13 @@ import {InMemoryStorageStrategy} from '../common/InMemoryStorageStrategy'; import {IAsyncStorageStrategy} from 'common/IAsyncStorageStrategy'; import {IService} from './service.interface'; import {Cat} from './cat'; +let customStrategySpy: jasmine.Spy = jasmine.createSpy(); +export class CustomContextStrategy extends InMemoryStorageStrategy { + add(cachePair: ICachePair, cacheKey: string, ctx?: any) { + customStrategySpy(ctx); + super.add(cachePair, cacheKey, ctx); + }; +} jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000; class AsyncStorageStrategy extends IAsyncStorageStrategy { private cachePairs: Array> = []; @@ -16,7 +23,7 @@ class AsyncStorageStrategy extends IAsyncStorageStrategy { this.cachePairs.push(cachePair); return Promise.resolve(); }; - + addMany(cachePairs: ICachePair[]) { this.cachePairs = cachePairs; return Promise.resolve(); @@ -236,6 +243,12 @@ strategies.forEach(s => { getMutableData(parameter: string) { return this.mockServiceCall(parameter); } + @PCacheable({ + storageStrategy: CustomContextStrategy + }) + getDataWithCustomContextStorageStrategy(parameter: string = 'Parameter1') { + return this.mockServiceCall(parameter); + } } beforeEach(() => { service = new Service(); @@ -887,6 +900,10 @@ strategies.forEach(s => { * response acquired from cache, so no incrementation on the service spy call counter is expected here */ expect(mockServiceCallSpy).toHaveBeenCalledTimes(1); - }) + }); + it('should work with a custom context storage strategy', async () => { + await service.getDataWithCustomContextStorageStrategy('test'); + expect(customStrategySpy).toHaveBeenCalledWith(jasmine.any(Object)); + }); }); }); \ No newline at end of file diff --git a/specs/service.interface.ts b/specs/service.interface.ts index 208cbc5..3919b6a 100644 --- a/specs/service.interface.ts +++ b/specs/service.interface.ts @@ -27,6 +27,7 @@ export interface IService { getDataWithUndefinedParameter(parameter?: string): T; getDataWithMultipleUndefinedParameters(parameter: string, parameter1: string): T; getDateWithCustomStorageStrategyProvided(parameter: string): T; + getDataWithCustomContextStorageStrategy(parameter: string): T; getDataAsync?(parameter: string): T; getData1?(parameter: string): T; getData2?(parameter: string): T;