@appolo/cache
Version:
appolo cache module
308 lines (207 loc) • 8.34 kB
text/typescript
import {define, init, inject, lazy,Injector} from '@appolo/inject';
import {RedisProvider} from '@appolo/redis';
import {Cache as ACache} from "appolo-cache";
import {ILogger} from "@appolo/logger";
import {Numbers} from "@appolo/utils";
import {IInnerCacheOptions, IOptions} from "./IOptions";
import Timer = NodeJS.Timer;
const ResultSymbol = "@result";
export class Cache {
private redisProvider: RedisProvider;
private moduleOptions: IOptions;
private logger: ILogger;
private injector: Injector;
private _intervals = new Map<any, Timer>();
private _cache: ACache<string, any>;
private _promiseCache: Map<any, any> = new Map<any, any>();
constructor(private _options: IInnerCacheOptions, private _valueFn?: Function, private _scope?: any) {
}
private initialize() {
this._cache = new ACache<string, any>(this._options);
this._options.getMethod = this._options.peek ? "peek" : "get";
if (this._options.refresh) {
this._options.getMethod = this._options.peek ? "peekByExpire" : "getByExpire";
}
}
public get<T>(...args: any[]): Promise<T> | T {
let key = this._getKey(args);
if (this._options.interval && !this._intervals.get(key)) {
let interval = setInterval(this._refreshValue.bind(this, args, key), this._options.interval);
this._intervals.set(key, interval)
}
return this._options.db ? this.getAsyncWithRedis(args, key) : this._getSync(args, key)
}
public async del(...args: any[]): Promise<void> {
let key = this._getKey(args);
this._cache.del(key);
if (this._options.db) {
let redisKey = this._getRedisKey(key);
await this.getRedisProvider().del(redisKey);
}
}
public getRedisProvider():RedisProvider{
return this.moduleOptions.redisProviderId
? this.injector.get(this.moduleOptions.redisProviderId)
: this.redisProvider
}
public set(value: any, ...args: any[]): void | Promise<void> {
let key = this._getKey(args);
this._setMemoryValue(key, value);
if (this._options.db) {
return this._setRedisValue(key, value);
}
}
private _getSync(args: any[], key: string) {
let item = this._getValueFromMemory(args, key);
if (this._isValidItem(item)) {
return this._options.isPromise ? Promise.resolve(item[ResultSymbol]) : item[ResultSymbol]
}
let result = this._getValue(args, key);
return result
}
private _isValidItem(item: any): boolean {
if (!item || !item.hasOwnProperty || !item.hasOwnProperty(ResultSymbol)) {
return false
}
if (this._options.cacheNull) {
return true;
}
let value = item[ResultSymbol];
return value !== null && value !== undefined;
}
public async getAsyncWithRedis(args: any[], key: string) {
let item: any = null;
if (this._options.memory) {
item = this._getValueFromMemory(args, key);
if (this._isValidItem(item)) {
return item[ResultSymbol];
}
}
item = await this._getValueFromRedis(args, key);
if (this._isValidItem(item)) {
return item[ResultSymbol]
}
let result = this._getValue(args, key);
return result
}
private _getKey(args: any[]) {
if (this._options.resolver) {
return this._options.resolver.apply(this._scope, args)
}
if (this._options.multi) {
return JSON.stringify(args)
}
let arg = args[0] || "";
return typeof arg == "object" ? JSON.stringify(arg) : arg
}
private _getValueFromMemory(args: any[], key: string) {
let result = this._cache[this._options.getMethod](key, this._getMemoryMaxAge(), this._options.refreshTime);
if (!result) {
return null;
}
let value = this._needRefresh(result, args, key, !this._options.db);
if (this._options.db && this._options.refresh && !result.validExpire) {
this._getValueFromRedis(args, key).catch(e => this.logger.error(`failed to set redis cache ${key}`, {e}));
}
return this._options.clone ? JSON.parse(value) : value;
}
private async _getValueFromRedis(args: any[], key: string) {
let redisKey = this._getRedisKey(key);
let result: any;
try {
result = await (this._options.refresh && this._options.maxAge
? this.getRedisProvider().getByExpire(redisKey, this._getRedisMaxAge(), this._options.refreshTime)
: this.getRedisProvider().get(redisKey));
} catch (e) {
this.logger.error(`failed to get redis cache ${key}`, {e})
}
if (!result) {
return null;
}
let value = this._needRefresh(result, args, key);
this._setMemoryValue(key, value);
return value;
}
private _needRefresh(result: any, args: any[], key: string, refresh: boolean = true) {
if (!this._options.refresh) {
return result;
}
let value = result.value;
if (!result.validExpire && refresh) {
this._options.randomRefresh
? setTimeout(() => this._refreshValue(args, key), Numbers.random(this._options.randomRefresh))
: this._refreshValue(args, key)
}
return value;
}
private _getRedisMaxAge(): number {
let age = (this._options.dbMaxAge || this._options.maxAge);
age = Math.floor(age / 1000);
return age;
}
private _getMemoryMaxAge(): number {
let age = this._options.maxAge
return age
}
private _getRedisKey(key: any): string {
if (typeof key != "string") {
key = JSON.stringify(key);
}
key = key || "";
if (this._options.dbKeyPrefix) {
key = `${this._options.dbKeyPrefix}:${key}`
}
return key;
}
private _getValue(args: any[], key: any): Promise<any> | any {
if (!this._valueFn) {
return null
}
let promiseCached = this._promiseCache.get(key);
if (promiseCached) {
return promiseCached;
}
let result = this._valueFn.apply(this._scope, args);
if (!result || !result.then || !result.catch) {
this._setMemoryValue(key, result);
return result;
}
this._options.isPromise = true;
let value = result.then((data) => {
this._setMemoryValue(key, data);
this._setRedisValue(key, data);
this._promiseCache.delete(key);
return data;
}).catch((e) => {
this._promiseCache.delete(key);
throw e;
});
this._promiseCache.set(key, value);
return value
}
private _setMemoryValue(key: string, value: any) {
if (!this._options.memory) {
return;
}
let dto = value && value.hasOwnProperty && value.hasOwnProperty(ResultSymbol) ? value : {[ResultSymbol]: value};
this._cache.set(key, this._options.clone ? JSON.stringify(dto) : dto, this._getMemoryMaxAge());
}
private _setRedisValue(key: string, value: any) {
if (!this._options.db) {
return;
}
let redisKey = this._getRedisKey(key), age = this._getRedisMaxAge();
let dto = value && value.hasOwnProperty && value.hasOwnProperty(ResultSymbol) ? value : {[ResultSymbol]: value};
return ((this._options.maxAge || this._options.dbMaxAge) ? this.getRedisProvider().setWithExpire(redisKey, dto, age) : this.getRedisProvider().set(redisKey, dto))
.catch(e => this.logger.error(`failed to set redis cache ${key}`, {e}))
}
private _refreshValue(args: any[], key: any) {
let value = this._getValue(args, key);
Promise.resolve(value).catch((e) => this.logger.error(`failed to refresh cache ${key}`, {e}))
}
public get cache(): ACache<any, any> {
return this._cache;
}
}