-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Angel Nikolov
committed
Mar 23, 2019
1 parent
628366e
commit 43aa04d
Showing
6 changed files
with
263 additions
and
290 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,27 @@ | ||
import { ICacheBusterConfig } from './common/ICacheBusterConfig'; | ||
import { ICacheable } from './common'; | ||
import { Observable } from 'rxjs'; | ||
import { tap } from 'rxjs/operators'; | ||
import { makeCacheBusterDecorator } from './common'; | ||
import { ICacheBusterConfig } from './common/ICacheBusterConfig'; | ||
|
||
export const CacheBuster = makeCacheBusterDecorator<Observable<any>>( | ||
(propertyDescriptor, oldMethod, cacheBusterConfig: ICacheBusterConfig) => { | ||
/* use function instead of an arrow function to keep context of invocation */ | ||
(propertyDescriptor.value as any) = function(...parameters) { | ||
return (oldMethod.call(this, ...parameters) as Observable<any>).pipe( | ||
tap(() => { | ||
if (cacheBusterConfig.cacheBusterNotifier) { | ||
cacheBusterConfig.cacheBusterNotifier.next(); | ||
} | ||
}) | ||
); | ||
export function CacheBuster(cacheBusterConfig?: ICacheBusterConfig) { | ||
return function ( | ||
_target: Object, | ||
_propertyKey: string, | ||
propertyDescriptor: TypedPropertyDescriptor<ICacheable<Observable<any>>> | ||
) { | ||
const oldMethod = propertyDescriptor.value; | ||
if (propertyDescriptor && propertyDescriptor.value) { | ||
/* use function instead of an arrow function to keep context of invocation */ | ||
(propertyDescriptor.value as any) = function (...parameters) { | ||
return (oldMethod.call(this, ...parameters) as Observable<any>).pipe( | ||
tap(() => { | ||
if (cacheBusterConfig.cacheBusterNotifier) { | ||
cacheBusterConfig.cacheBusterNotifier.next(); | ||
} | ||
}) | ||
); | ||
}; | ||
}; | ||
} | ||
); | ||
return propertyDescriptor; | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,127 +1,132 @@ | ||
import { empty, merge, Observable, of, Subject } from 'rxjs'; | ||
import { delay, finalize, shareReplay, tap } from 'rxjs/operators'; | ||
import { DEFAULT_CACHE_RESOLVER, makeCacheableDecorator } from './common'; | ||
import { DEFAULT_CACHE_RESOLVER, ICacheable } from './common'; | ||
import { IObservableCacheConfig } from './common/IObservableCacheConfig'; | ||
import { ICachePair } from './common/ICachePair'; | ||
export const globalCacheBusterNotifier = new Subject<void>(); | ||
|
||
export const Cacheable = makeCacheableDecorator<Observable<any>, IObservableCacheConfig>( | ||
( | ||
propertyDescriptor, | ||
oldMethod, | ||
cachePairs, | ||
pendingCachePairs, | ||
cacheConfig | ||
) => { | ||
/** | ||
* subscribe to the globalCacheBuster | ||
* if a custom cacheBusterObserver is passed, subscribe to it as well | ||
* subscribe to the cacheBusterObserver and upon emission, clear all caches | ||
*/ | ||
merge( | ||
globalCacheBusterNotifier.asObservable(), | ||
cacheConfig.cacheBusterObserver | ||
? cacheConfig.cacheBusterObserver | ||
: empty() | ||
).subscribe(_ => { | ||
cachePairs.length = 0; | ||
pendingCachePairs.length = 0; | ||
}); | ||
|
||
cacheConfig.cacheResolver = cacheConfig.cacheResolver | ||
? cacheConfig.cacheResolver | ||
: DEFAULT_CACHE_RESOLVER; | ||
|
||
/* use function instead of an arrow function to keep context of invocation */ | ||
(propertyDescriptor.value as any) = function(..._parameters) { | ||
let parameters = JSON.parse(JSON.stringify(_parameters)); | ||
let _foundCachePair = cachePairs.find(cp => | ||
cacheConfig.cacheResolver(cp.parameters, parameters) | ||
); | ||
const _foundPendingCachePair = pendingCachePairs.find(cp => | ||
cacheConfig.cacheResolver(cp.parameters, parameters) | ||
); | ||
export function Cacheable(cacheConfig: IObservableCacheConfig = {}) { | ||
return function ( | ||
_target: Object, | ||
_propertyKey: string, | ||
propertyDescriptor: TypedPropertyDescriptor<ICacheable<Observable<any>>> | ||
) { | ||
const oldMethod = propertyDescriptor.value; | ||
if (propertyDescriptor && propertyDescriptor.value) { | ||
const cachePairs: Array<ICachePair<Observable<any>>> = []; | ||
const pendingCachePairs: Array<ICachePair<Observable<any>>> = []; | ||
/** | ||
* check if maxAge is passed and cache has actually expired | ||
* subscribe to the globalCacheBuster | ||
* if a custom cacheBusterObserver is passed, subscribe to it as well | ||
* subscribe to the cacheBusterObserver and upon emission, clear all caches | ||
*/ | ||
if (cacheConfig.maxAge && _foundCachePair && _foundCachePair.created) { | ||
if ( | ||
new Date().getTime() - _foundCachePair.created.getTime() > | ||
cacheConfig.maxAge | ||
) { | ||
/** | ||
* cache duration has expired - remove it from the cachePairs array | ||
*/ | ||
cachePairs.splice(cachePairs.indexOf(_foundCachePair), 1); | ||
_foundCachePair = null; | ||
} else if (cacheConfig.slidingExpiration) { | ||
/** | ||
* renew cache duration | ||
*/ | ||
_foundCachePair.created = new Date(); | ||
} | ||
} | ||
merge( | ||
globalCacheBusterNotifier.asObservable(), | ||
cacheConfig.cacheBusterObserver | ||
? cacheConfig.cacheBusterObserver | ||
: empty() | ||
).subscribe(_ => { | ||
cachePairs.length = 0; | ||
pendingCachePairs.length = 0; | ||
}); | ||
|
||
cacheConfig.cacheResolver = cacheConfig.cacheResolver | ||
? cacheConfig.cacheResolver | ||
: DEFAULT_CACHE_RESOLVER; | ||
|
||
if (_foundCachePair) { | ||
const cached$ = of(_foundCachePair.response); | ||
return cacheConfig.async ? cached$.pipe(delay(0)) : cached$; | ||
} else if (_foundPendingCachePair) { | ||
return _foundPendingCachePair.response; | ||
} else { | ||
const response$ = (oldMethod.call(this, ...parameters) as Observable< | ||
any | ||
>).pipe( | ||
finalize(() => { | ||
/* use function instead of an arrow function to keep context of invocation */ | ||
(propertyDescriptor.value as any) = function (..._parameters) { | ||
let parameters = JSON.parse(JSON.stringify(_parameters)); | ||
let _foundCachePair = cachePairs.find(cp => | ||
cacheConfig.cacheResolver(cp.parameters, parameters) | ||
); | ||
const _foundPendingCachePair = pendingCachePairs.find(cp => | ||
cacheConfig.cacheResolver(cp.parameters, parameters) | ||
); | ||
/** | ||
* check if maxAge is passed and cache has actually expired | ||
*/ | ||
if (cacheConfig.maxAge && _foundCachePair && _foundCachePair.created) { | ||
if ( | ||
new Date().getTime() - _foundCachePair.created.getTime() > | ||
cacheConfig.maxAge | ||
) { | ||
/** | ||
* if there has been an observable cache pair for these parameters, when it completes or errors, remove it | ||
* cache duration has expired - remove it from the cachePairs array | ||
*/ | ||
const _pendingCachePairToRemove = pendingCachePairs.find(cp => | ||
cacheConfig.cacheResolver(cp.parameters, parameters) | ||
); | ||
pendingCachePairs.splice( | ||
pendingCachePairs.indexOf(_pendingCachePairToRemove), | ||
1 | ||
); | ||
}), | ||
tap(response => { | ||
cachePairs.splice(cachePairs.indexOf(_foundCachePair), 1); | ||
_foundCachePair = null; | ||
} else if (cacheConfig.slidingExpiration) { | ||
/** | ||
* if no maxCacheCount has been passed | ||
* if maxCacheCount has not been passed, just shift the cachePair to make room for the new one | ||
* if maxCacheCount has been passed, respect that and only shift the cachePairs if the new cachePair will make them exceed the count | ||
* renew cache duration | ||
*/ | ||
if ( | ||
!cacheConfig.shouldCacheDecider || | ||
cacheConfig.shouldCacheDecider(response) | ||
) { | ||
_foundCachePair.created = new Date(); | ||
} | ||
} | ||
|
||
if (_foundCachePair) { | ||
const cached$ = of(_foundCachePair.response); | ||
return cacheConfig.async ? cached$.pipe(delay(0)) : cached$; | ||
} else if (_foundPendingCachePair) { | ||
return _foundPendingCachePair.response; | ||
} else { | ||
const response$ = (oldMethod.call(this, ...parameters) as Observable< | ||
any | ||
>).pipe( | ||
finalize(() => { | ||
/** | ||
* if there has been an observable cache pair for these parameters, when it completes or errors, remove it | ||
*/ | ||
const _pendingCachePairToRemove = pendingCachePairs.find(cp => | ||
cacheConfig.cacheResolver(cp.parameters, parameters) | ||
); | ||
pendingCachePairs.splice( | ||
pendingCachePairs.indexOf(_pendingCachePairToRemove), | ||
1 | ||
); | ||
}), | ||
tap(response => { | ||
/** | ||
* if no maxCacheCount has been passed | ||
* if maxCacheCount has not been passed, just shift the cachePair to make room for the new one | ||
* if maxCacheCount has been passed, respect that and only shift the cachePairs if the new cachePair will make them exceed the count | ||
*/ | ||
if ( | ||
!cacheConfig.maxCacheCount || | ||
cacheConfig.maxCacheCount === 1 || | ||
(cacheConfig.maxCacheCount && | ||
cacheConfig.maxCacheCount < cachePairs.length + 1) | ||
!cacheConfig.shouldCacheDecider || | ||
cacheConfig.shouldCacheDecider(response) | ||
) { | ||
cachePairs.shift(); | ||
if ( | ||
!cacheConfig.maxCacheCount || | ||
cacheConfig.maxCacheCount === 1 || | ||
(cacheConfig.maxCacheCount && | ||
cacheConfig.maxCacheCount < cachePairs.length + 1) | ||
) { | ||
cachePairs.shift(); | ||
} | ||
cachePairs.push({ | ||
parameters, | ||
response, | ||
created: cacheConfig.maxAge ? new Date() : null | ||
}); | ||
} | ||
cachePairs.push({ | ||
parameters, | ||
response, | ||
created: cacheConfig.maxAge ? new Date() : null | ||
}); | ||
} | ||
}), | ||
}), | ||
/** | ||
* replay cached observable, so we don't enter finalize and tap for every cached observable subscription | ||
*/ | ||
shareReplay() | ||
); | ||
/** | ||
* replay cached observable, so we don't enter finalize and tap for every cached observable subscription | ||
* cache the stream | ||
*/ | ||
shareReplay() | ||
); | ||
/** | ||
* cache the stream | ||
*/ | ||
pendingCachePairs.push({ | ||
parameters: parameters, | ||
response: response$, | ||
created: new Date() | ||
}); | ||
return response$; | ||
} | ||
}; | ||
pendingCachePairs.push({ | ||
parameters: parameters, | ||
response: response$, | ||
created: new Date() | ||
}); | ||
return response$; | ||
} | ||
}; | ||
} | ||
return propertyDescriptor; | ||
} | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,26 @@ | ||
import { makeCacheBusterDecorator } from './common'; | ||
import { ICacheBusterConfig } from './common/ICacheBusterConfig'; | ||
import { ICacheable } from './common'; | ||
|
||
export const PCacheBuster = makeCacheBusterDecorator<Promise<any>>( | ||
(propertyDescriptor, oldMethod, cacheBusterConfig: ICacheBusterConfig) => { | ||
/* use function instead of an arrow function to keep context of invocation */ | ||
(propertyDescriptor.value as any) = function(...parameters) { | ||
return (oldMethod.call(this, ...parameters) as Promise<any>).then( | ||
response => { | ||
if (cacheBusterConfig.cacheBusterNotifier) { | ||
cacheBusterConfig.cacheBusterNotifier.next(); | ||
export function PCacheBuster(cacheBusterConfig?: ICacheBusterConfig) { | ||
return function ( | ||
_target: Object, | ||
_propertyKey: string, | ||
propertyDescriptor: TypedPropertyDescriptor<ICacheable<Promise<any>>> | ||
) { | ||
const oldMethod = propertyDescriptor.value; | ||
if (propertyDescriptor && propertyDescriptor.value) { | ||
/* use function instead of an arrow function to keep context of invocation */ | ||
(propertyDescriptor.value as any) = function (...parameters) { | ||
return (oldMethod.call(this, ...parameters) as Promise<any>).then( | ||
response => { | ||
if (cacheBusterConfig.cacheBusterNotifier) { | ||
cacheBusterConfig.cacheBusterNotifier.next(); | ||
} | ||
return response; | ||
} | ||
return response; | ||
} | ||
); | ||
); | ||
}; | ||
}; | ||
} | ||
); | ||
return propertyDescriptor; | ||
}; | ||
}; |
Oops, something went wrong.