Skip to content

Commit

Permalink
Release 1.1.4
Browse files Browse the repository at this point in the history
  • Loading branch information
Angel Nikolov committed Mar 23, 2019
1 parent 628366e commit 43aa04d
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 290 deletions.
38 changes: 23 additions & 15 deletions cache-buster.decorator.ts
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;
};
};
223 changes: 114 additions & 109 deletions cacheable.decorator.ts
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;
}
);
};
54 changes: 1 addition & 53 deletions common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,56 +14,4 @@ export type IShouldCacheDecider = (response: any) => boolean;

export type ICacheable<T> = (...args) => T;

export const makeCacheableDecorator = <T, K extends ICacheConfig = ICacheConfig>(
decorate: (
propertyDescriptor: TypedPropertyDescriptor<ICacheable<T>>,
oldMethod: ICacheable<T>,
cachePairs: Array<ICachePair<any>>,
pendingCachePairs: Array<ICachePair<T>>,
cacheConfig: K
) => void
) => {
return function Cacheable(cacheConfig?: K) {
return function(
_target: Object,
_propertyKey: string,
propertyDescriptor: TypedPropertyDescriptor<ICacheable<T>>
) {
const oldMethod = propertyDescriptor.value;
if (propertyDescriptor && propertyDescriptor.value) {
const cachePairs: Array<ICachePair<any>> = [];
const pendingCachePairs: Array<ICachePair<T>> = [];
decorate(
propertyDescriptor,
oldMethod,
cachePairs,
pendingCachePairs,
cacheConfig ? cacheConfig : ({} as K)
);
}
return propertyDescriptor;
};
};
};

export const makeCacheBusterDecorator = <T>(
decorate: (
propertyDescriptor: TypedPropertyDescriptor<ICacheable<T>>,
oldMethod: ICacheable<T>,
cacheBusterConfig: ICacheBusterConfig
) => void
) => {
return function CacheBuster(cacheBusterConfig?: ICacheBusterConfig) {
return function(
_target: Object,
_propertyKey: string,
propertyDescriptor: TypedPropertyDescriptor<ICacheable<T>>
) {
const oldMethod = propertyDescriptor.value;
if (propertyDescriptor && propertyDescriptor.value) {
decorate(propertyDescriptor, oldMethod, cacheBusterConfig)
}
return propertyDescriptor;
};
};
};
export { ICacheBusterConfig, ICacheConfig, ICachePair };
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ngx-cacheable",
"version": "1.1.3",
"version": "1.1.4",
"description": "Rx Observable cache decorator",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
36 changes: 22 additions & 14 deletions promise.cache-buster.decorator.ts
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;
};
};
Loading

0 comments on commit 43aa04d

Please sign in to comment.