nsql-cache
Version:
Advanced cache layer for NoSQL databases
214 lines (182 loc) • 6.3 kB
JavaScript
'use strict';
const nodeCacheManager = require('cache-manager');
const arrify = require('arrify');
const extend = require('extend');
const cacheKeys = require('./keys');
const cacheQueries = require('./queries');
const utils = require('./utils');
const defaultStores = [
{
store: 'memory',
max: 100,
},
];
const defaultConfig = {
ttl: {
keys: 60 * 10, // 10 minutes
queries: 5, // 5 seconds
memory: {
keys: 60 * 10,
queries: 5,
},
redis: {
keys: 60 * 60 * 24, // 1 day
queries: 0, // infinite
},
},
cachePrefix: {
keys: 'gck:', // Gstore Cache Key
queries: 'gcq:', // Gstore Cache Query
},
hashCacheKeys: true,
wrapClient: true,
global: true, // For "wrapped" client, turn "ON" the cache globally or not
};
/**
* Check if the cache is a "Redis" cache.
* If it is, we return its client
*/
const checkRedis = cache => {
let client;
if (cache.store.name === 'redis') {
client = cache.store.getClient();
}
return client;
};
class NsqlCache {
constructor(settings) {
this._config = Object.assign({}, defaultConfig);
this._cacheManager = undefined;
this._redisClient = undefined;
this._db = undefined;
this._stores = undefined;
this.keys = cacheKeys(this);
this.queries = cacheQueries(this);
this.utils = utils;
this.init(settings);
}
init(settings = {}) {
const self = this;
let { config } = settings;
config = config || {};
// make a copy before merging the config into the defaultConfig
const ttlConfig = extend(true, {}, config.ttl);
this._config = Object.assign({}, defaultConfig, config);
extend(true, this._config.ttl, defaultConfig.ttl, ttlConfig);
this.__setDB(settings, this._config);
this._stores = settings.stores ? settings.stores : defaultStores;
if (this._stores.length > 1) {
this._stores = this._stores.map(store => nodeCacheManager.caching(store));
this._cacheManager = nodeCacheManager.multiCaching(this._stores);
this._stores.forEach(cache => {
self._redisClient = self._redisClient || checkRedis(cache);
});
if (self._redisClient) {
// We create a cacheManager instance without the Redis store
// so in queries we can target this instance even though we directy
// save into Redis client.
// See queries.read() method
const storeWithoutRedis = this._stores.filter(c => c.store.name !== 'redis');
this._cacheManagerNoRedis = nodeCacheManager.multiCaching(storeWithoutRedis);
}
} else {
this._cacheManager = nodeCacheManager.caching(this._stores[0]);
this._redisClient = this._redisClient || checkRedis(this._cacheManager);
/**
* If we *only* have a Redis store AND no ttl config has been passed
* we copy the default ttl config for "redis" to the "global" ttl
*/
if (this._redisClient && !config.ttl) {
this._config.ttl.keys = this._config.ttl.redis.keys;
this._config.ttl.queries = this._config.ttl.redis.queries;
}
}
this.__bindCacheManagerMethods();
}
/**
* Concatenate key|value pairs and
* call mset on the cacheManager
*/
primeCache(_keys, _values, options = {}) {
let keys = _keys;
let values;
if (!Array.isArray(_keys)) {
keys = [_keys];
/**
* If _keys passed is not an Array but "_values" is,
* we want to keep it that way...
*/
if (Array.isArray(_values)) {
values = [_values];
}
}
values = values || arrify(_values);
const cacheManager = options.cacheManager || this._cacheManager;
const keysValues = keys.reduce((acc, key, index) => [...acc, key, values[index]], []);
const args = [...keysValues, options];
return cacheManager
.mset(...args)
.then(response => (response && response.length === 1 ? response[0] : response));
}
__setDB(settings, { hashCacheKeys, wrapClient } = {}) {
if (settings.db) {
this._db = settings.db;
if (wrapClient && this.db.wrapClient) {
this.db.wrapClient(this);
}
}
if (!this._db) {
throw new Error('No valid Database adapter provided.');
}
// Set default handlers
this._db.getKeyFromEntity =
this._db.getKeyFromEntity ||
function getKeyFromEntity() {
return null;
};
this._db.addKeyToEntity =
this._db.addKeyToEntity ||
function addKeyToEntity(_, entity) {
return entity;
};
/**
* If config.hashCacheKeys is set to true, we wrap the keyToString & queryToString
* methods with our string.hash() util
*/
if (this._db && hashCacheKeys) {
const { keyToString, queryToString } = this._db;
this._db.keyToString = key => utils.string.hash(keyToString(key));
this._db.queryToString = query => utils.string.hash(queryToString(query));
}
}
/**
* Proxy cacheManager methods
*/
__bindCacheManagerMethods() {
this.get = this.cacheManager.get;
this.mget = this.cacheManager.mget;
this.set = this.cacheManager.set;
this.mset = this.cacheManager.mset;
this.del = this.cacheManager.del;
this.reset = this.cacheManager.reset;
}
get config() {
return this._config;
}
get redisClient() {
return this._redisClient;
}
get cacheManager() {
return this._cacheManager;
}
get cacheManagerNoRedis() {
return this._cacheManagerNoRedis;
}
get db() {
return this._db;
}
get stores() {
return this._stores;
}
}
module.exports = NsqlCache;