UNPKG

@openfinance/cache

Version:

A simple, in-memory cache for javascript projects

148 lines 5.64 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); class Cache { constructor(config, _log) { this._log = _log; this._cache = {}; this._groomingCache = false; config = config || {}; config.maxLength = typeof config.maxLength !== "undefined" ? config.maxLength : 1000; config.ttlSec = config.ttlSec || 0; this.config = config; } /** * Public method to clear the cache * * This may be used in an administrative endpoint when, for example, the database is manually * manipulated, or it may be used internally to clear a specific key on change. */ clear(key) { if (!key) { this.log("notice", `Clearing all cache keys`); this._cache = {}; } else { this.log("notice", `Clearing cache key ${key.toString()}`); if (typeof key === "string") { if (this._cache.hasOwnProperty(key)) { this.log("info", "String key found. Deleting value."); delete this._cache[key]; } else { this.log("info", "String key not found in cache. Not deleting anything."); } } else { this.log("info", "RegExp key. Matching against all cache keys."); for (let x in this._cache) { if (key.test(x)) { this.log("debug", `Key matched: ${x}. Deleting value.`); delete this._cache[x]; } } } } } get(key, q, ttlSec) { if (!q) { const val = this._cache.hasOwnProperty(key) ? this._cache[key].v : undefined; this.log("debug", `Returning value for cache key ${key}: ${JSON.stringify(val)}`); return val; } if (!this._cache.hasOwnProperty(key)) { this.log("info", `Cache not set for '${key}'. Getting result and caching.`); const t = Date.now(); let ttl = ((typeof ttlSec !== "undefined" && ttlSec !== null) ? ttlSec : (typeof this.config.ttlSec !== "undefined" && this.config.ttlSec !== null) ? this.config.ttlSec : 0) * 1000; const timeout = ttl > 0 ? setTimeout(() => { if (this._cache.hasOwnProperty(key)) { delete this._cache[key]; } }, ttl) : null; const val = q(); // If q returns a promise, then we have to await that if (isPromise(val)) { this.log("info", `Function returned promise. Awaiting....`); return new Promise((res, rej) => { val .then((v) => { this.log("info", `Got response. Setting cache and returning.`); this.log("debug", `Returning value for cache key ${key}: ${JSON.stringify(v)}`); this._cache[key] = { t, v, ttl: timeout }; this.groomCache(); res(v); }) .catch((e) => { rej(e); }); }); } else { this.log("info", `Function returned value. Returning immediately.`); this.log("debug", `Returning value for cache key ${key}: ${JSON.stringify(val)}`); this._cache[key] = { t, v: val, ttl: timeout }; this.groomCache(); return Promise.resolve(val); } } else { const val = this._cache[key].v; this._cache[key].t = Date.now(); this.log("debug", `Using cached value for '${key}'`); this.log("debug", `Returning value for cache key ${key}: ${JSON.stringify(val)}`); return Promise.resolve(val); } } /** * Remove old values if cache is getting too long */ groomCache() { return new Promise((res, rej) => { if (this._groomingCache) { res(); } // Set state to "grooming" this._groomingCache = true; // Groom if necessary const cacheLength = Object.keys(this._cache).length; if (cacheLength > this.config.maxLength) { this.log("notice", `Current cache is ${cacheLength} objects. Grooming.`); let kill = null; let oldest = Date.now(); for (let k in this._cache) { const t = this._cache[k].t; if (t < oldest) { oldest = t; kill = k; } } if (kill !== null) { this.log("info", `Destroying item at ${kill}, which was last used at ${new Date(oldest).toString()}`); delete this._cache[kill]; } } // Set state back to idle this._groomingCache = false; // Resolve the promise res(); }); } /** * Log at a given log level, if logger is available */ log(level, msg) { if (this._log) { this._log[level](msg); } } } exports.Cache = Cache; function isPromise(obj) { return typeof obj.then !== "undefined"; } //# sourceMappingURL=index.js.map