-
Notifications
You must be signed in to change notification settings - Fork 42
/
Copy pathcacheable.decorator.ts
140 lines (137 loc) · 6.44 KB
/
cacheable.decorator.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import {empty, merge, Observable, of, Subject} from 'rxjs';
import {delay, finalize, tap, publishReplay, refCount} from 'rxjs/operators';
import {DEFAULT_CACHE_RESOLVER, ICacheable, GlobalCacheConfig, IStorageStrategy, DEFAULT_HASHER} from './common';
import {IObservableCacheConfig} from './common/IObservableCacheConfig';
import {ICachePair} from './common/ICachePair';
export const globalCacheBusterNotifier = new Subject<void>();
export function Cacheable(cacheConfig: IObservableCacheConfig = {}) {
return function(
_target: Object,
_propertyKey: string,
propertyDescriptor: TypedPropertyDescriptor<ICacheable<Observable<any>>>
) {
const cacheKey = cacheConfig.cacheKey || _target.constructor.name + '#' + _propertyKey;
const oldMethod = propertyDescriptor.value;
if (propertyDescriptor && propertyDescriptor.value) {
let storageStrategy: IStorageStrategy = !cacheConfig.storageStrategy
? new GlobalCacheConfig.storageStrategy() as IStorageStrategy
: new cacheConfig.storageStrategy();
const pendingCachePairs: Array<ICachePair<Observable<any>>> = [];
if (cacheConfig.cacheModifier) {
cacheConfig.cacheModifier.subscribe(callback => storageStrategy.addMany(callback(storageStrategy.getAll(cacheKey, this)), cacheKey, this))
}
/**
* 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(_ => {
storageStrategy.removeAll(cacheKey, this);
pendingCachePairs.length = 0;
});
const cacheResolver = cacheConfig.cacheResolver || GlobalCacheConfig.cacheResolver;
cacheConfig.cacheResolver = cacheResolver
? cacheResolver
: DEFAULT_CACHE_RESOLVER;
const cacheHasher = cacheConfig.cacheHasher || GlobalCacheConfig.cacheHasher;
cacheConfig.cacheHasher = cacheHasher
? cacheHasher
: DEFAULT_HASHER;
/* use function instead of an arrow function to keep context of invocation */
(propertyDescriptor.value as any) = function(...parameters: Array<any>) {
const cachePairs: Array<ICachePair<Observable<any>>> = storageStrategy.getAll(cacheKey, this);
let cacheParameters = cacheConfig.cacheHasher(parameters);
let _foundCachePair = cachePairs.find(cp =>
cacheConfig.cacheResolver(cp.parameters, cacheParameters));
const _foundPendingCachePair = pendingCachePairs.find(cp =>
cacheConfig.cacheResolver(cp.parameters, cacheParameters)
);
/**
* check if maxAge is passed and cache has actually expired
*/
if ((cacheConfig.maxAge || GlobalCacheConfig.maxAge) && _foundCachePair && _foundCachePair.created) {
if (
new Date().getTime() - new Date(_foundCachePair.created).getTime() >
(cacheConfig.maxAge || GlobalCacheConfig.maxAge)
) {
/**
* cache duration has expired - remove it from the cachePairs array
*/
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, this) : storageStrategy.updateAtIndex(cachePairs.indexOf(_foundCachePair), _foundCachePair, cacheKey, this);
}
}
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, cacheParameters)
);
pendingCachePairs.splice(
pendingCachePairs.indexOf(_pendingCachePairToRemove),
1
);
}),
tap(response => {
/**
* 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.shouldCacheDecider ||
cacheConfig.shouldCacheDecider(response)
) {
if (
!(cacheConfig.maxCacheCount || GlobalCacheConfig.maxCacheCount) ||
(cacheConfig.maxCacheCount || GlobalCacheConfig.maxCacheCount) === 1 ||
((cacheConfig.maxCacheCount || GlobalCacheConfig.maxCacheCount) &&
(cacheConfig.maxCacheCount || GlobalCacheConfig.maxCacheCount) < cachePairs.length + 1)
) {
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, this);
}
}),
publishReplay(1),
refCount()
);
/**
* cache the stream
*/
pendingCachePairs.push({
parameters: cacheParameters,
response: response$,
created: new Date()
});
return response$;
}
};
}
return propertyDescriptor;
}
};