cache-memory
Version:
Node module for in memory caching
263 lines • 9.63 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const logger_1 = require("./logger");
const clone = require('clone');
let globalId = 0;
function default_1(options) {
const cacherOptions = options || {};
// @ts-ignore
const self = this;
self.id = options.id;
if (!self.id) {
globalId++;
self.id = globalId;
}
self.id = self.id.toString();
self.ttl = options.ttl || 0;
self.cachedData = {};
self.storeUndefinedObjects = (cacherOptions.storeUndefinedObjects !== undefined && cacherOptions.storeUndefinedObjects === true);
self.clone = (cacherOptions.clone !== undefined && cacherOptions.clone === true);
self.hits = 0;
self.misses = 0;
self.hitCallback = undefined;
self.missCallback = undefined;
self.addedCallback = undefined;
self.removedCallback = undefined;
self.countCallback = undefined;
const isFunction = (argument) => argument && typeof argument === 'function';
if (isFunction(cacherOptions.hit)) {
self.hitCallback = cacherOptions.hit;
}
if (isFunction(cacherOptions.miss)) {
self.missCallback = cacherOptions.miss;
}
if (isFunction(cacherOptions.added)) {
self.addedCallback = cacherOptions.added;
}
if (isFunction(cacherOptions.removed)) {
self.removedCallback = cacherOptions.removed;
}
if (isFunction(cacherOptions.count)) {
self.countCallback = cacherOptions.count;
}
const cleanKey = (key) => key.replace(/[^a-zA-Z0-9_]/g, '_');
const get = (key, options) => {
const cacherOptions = options || {};
// @ts-ignore
if (!cacherOptions.keyCleaned) {
key = cleanKey(key);
}
let data = self.cachedData[key];
if (data && data.expires < new Date()) {
data = undefined;
// @ts-ignore
remove(key, { keyCleaned: true });
raiseEvent(self.removedCallback, key);
}
let value = undefined;
if (data) {
self.hits++;
raiseEvent(self.hitCallback, key);
if (self.clone === true) {
value = clone(data.value);
}
else {
value = data.value;
}
}
else {
self.misses++;
raiseEvent(self.missCallback, key);
}
(0, logger_1.log)(`get - cacher id: ${self.id}, key: ${key}`, { value });
return value;
};
const getExpiry = (key) => {
key = cleanKey(key);
let data = self.cachedData[key];
if (data && data.expires < new Date()) {
data = undefined;
// @ts-ignore
remove(key, { keyCleaned: true });
raiseEvent(self.removedCallback, key);
}
if (data) {
(0, logger_1.log)(`getExpiry - cacher id: ${self.id}, key: ${key}, expiry: ${data.expires}`);
}
else {
(0, logger_1.log)(`getExpiry - item does not exist - cacher id: ${self.id}, key: ${key}`);
}
return data ? data.expires : undefined;
};
const getAndSet = async (key, getter, options) => {
const cacherOptions = clone(options || {});
// @ts-ignore
cacherOptions.keyCleaned = true;
if (!isFunction(getter)) {
(0, logger_1.log)(`getAndSet - no getter function is defined - cacher id: ${self.id}, key: ${key}`, { cacherOptions });
return undefined;
}
key = cleanKey(key);
let value = get(key, cacherOptions);
if (!value) {
(0, logger_1.log)(`getAndSet - retrieving value from getter - cacher id: ${self.id}, key: ${key}`, { cacherOptions });
value = await getter();
setWithRefresh(key, value, getter, options);
}
(0, logger_1.log)(`getAndSet - cacher id: ${self.id}, key: ${key}`, { value });
return value;
};
const setWithRefresh = (key, value, getter, options) => {
setInternal(key, value, true, options);
if (options?.refreshIntervalInMilliseconds === undefined || options?.refreshIntervalInMilliseconds === 0)
return;
let data = self.cachedData[key];
const timeoutFunc = (milliseconds) => {
data = self.cachedData[key];
data.timeout = setTimeout(async () => {
if (!data.timeout)
return;
(0, logger_1.log)(`getAndSet - refresh cacher id: ${self.id}, key: ${key}. Getting data to update cache'`);
try {
const value = await getter();
set(key, value, options);
timeoutFunc(milliseconds);
}
catch (error) {
(0, logger_1.log)(`getAndSet - refresh cacher id: ${self.id}, key: ${key}. Failed to update cache, error message was '${error.message}'`);
(0, logger_1.log)(`Failed to update cache cacher id: ${self.id}, key: '${key}', error message was '${error.message}'`);
timeoutFunc(options.refreshIntervalWhenRefreshFailsInMilliseconds ?? milliseconds);
}
}, milliseconds);
};
timeoutFunc(options.refreshIntervalInMilliseconds);
};
const set = (key, value, options) => {
setInternal(key, value, false, options);
};
const setInternal = (key, value, refresh, options) => {
const cacherOptions = options || {};
if (!self.storeUndefinedObjects && (value === undefined || value === null || (typeof value.isNull === "function" && value.isNull()))) {
(0, logger_1.log)(`set - not storing value as it is not defined - cacher id: ${self.id}, key: ${key}`, { value, cacherOptions });
remove(key, cacherOptions);
raiseEvent(self.removedCallback, key);
return;
}
// @ts-ignore
if (!cacherOptions.keyCleaned) {
key = cleanKey(key);
}
let timeout;
if (self.cachedData[key]) {
if (refresh) {
timeout = self.cachedData[key].timeout;
}
else {
if (self.cachedData[key].timeout) {
clearTimeout(self.cachedData[key].timeout);
delete self.cachedData[key].timeout;
}
}
}
let ttl = cacherOptions.ttl || self.ttl;
let expiryDate;
if (ttl === 0) {
expiryDate = new Date(8640000000000000);
}
else {
expiryDate = new Date(new Date().getTime() + (ttl * 1000));
}
let valueToCache = self.clone === true ? clone(value) : value;
let cacheValue = {
value: valueToCache,
expires: expiryDate,
timeout: undefined
};
if (refresh) {
cacheValue.timeout = timeout;
}
self.cachedData[key] = cacheValue;
(0, logger_1.log)(`set - stored value - cacher id: ${self.id}, key: ${key}`, { cachedData: cacheValue });
raiseEvent(self.addedCallback, key);
raiseCountEvent(self.countCallback);
};
const clear = () => {
(0, logger_1.log)(`clear - cacher id: ${self.id}`);
self.hits = 0;
self.misses = 0;
Object.keys(self.cachedData).forEach(key => {
if (self.cachedData[key] !== undefined && self.cachedData[key].timeout) {
clearTimeout(self.cachedData[key].timeout);
delete self.cachedData[key].timeout;
}
});
self.cachedData = {};
};
const remove = (key, options) => {
const cacherOptions = options || {};
// @ts-ignore
if (!cacherOptions.keyCleaned) {
key = cleanKey(key);
}
(0, logger_1.log)(`remove - key: ${key}, cacher id: ${self.id}`);
if (self.cachedData[key] && self.cachedData[key].timeout) {
clearTimeout(self.cachedData[key].timeout);
delete self.cachedData[key].timeout;
}
delete self.cachedData[key];
raiseEvent(self.removedCallback, key);
raiseCountEvent(self.countCallback);
};
const stats = () => {
let hits = self.hits;
let misses = self.misses;
let stats = {
count: Object.keys(self.cachedData).length,
hits,
misses,
hitRate: hits / (hits + misses)
};
if (isNaN(stats.hitRate)) {
stats.hitRate = 0;
}
(0, logger_1.log)(`stats - ${JSON.stringify(stats)}, cacher id: ${self.id}`);
return stats;
};
const keys = () => {
let keys = Object.keys(self.cachedData);
(0, logger_1.log)(`keys - cacher id: ${self.id}`, { keys });
return keys;
};
const getOptions = () => {
return {
ttl: self.ttl,
clone: self.clone,
storeUndefinedObjects: self.storeUndefinedObjects
};
};
const raiseEvent = (callback, key) => {
if (!callback)
return;
callback({ id: self.id, key: key });
};
const raiseCountEvent = (callback) => {
if (!callback)
return;
callback({ id: self.id, count: Object.keys(self.cachedData).length });
};
return {
id: self.id,
get,
getExpiry,
getAndSet,
set,
clear,
remove,
stats,
keys,
options: getOptions
};
}
exports.default = default_1;
;
//# sourceMappingURL=cacher.js.map