UNPKG

cache-coffee-express

Version:

This is a fork of "cache-express", with minor and specific changes to avoid caching in services that return code other than 2xx and others.

248 lines (221 loc) 7.2 kB
/** * Hashes a string to create a unique cache key. * @param {string} str - The input string to be hashed. * @returns {number} - The generated hash value. */ function hashString(str) { let hash = 0; for (let i = 0; i < str.length; i++) { const charCode = str.charCodeAt(i); hash = ((hash << 5) - hash + charCode) | 0; } return hash + 2147483647 + 1; } /** * MemoryCache class for caching data in memory. */ class MemoryCache { constructor() { this.cache = {}; this.dependencies = {}; } /** * Retrieves a value from the cache. * @param {string} key - The cache key. * @param {Array} depArrayValues - Dependency values for cache checking. * @returns {*} - The cached value if found and not expired, otherwise null. */ get(key, depArrayValues) { const item = this.cache[key]; const checkDepsChanged = this.dependenciesChanged(key, depArrayValues); if (checkDepsChanged) { if (item && item.timer) { clearInterval(item.timer); } delete this.cache[key]; return null; } if (item && (!item.expireTime || item.expireTime > Date.now())) { return item.value; } else { delete this.cache[key]; return null; } } /** * Sets a value in the cache with an optional timeout and callback. * @param {string} key - The cache key. * @param {*} value - The value to cache. * @param {number} timeoutMs - Timeout in milliseconds. * @param {function} callback - Callback function when the cache expires. * @param {Array} dependencies - Dependency values for cache checking. */ set(key, value, timeoutMs, callback, dependencies) { if (timeoutMs && timeoutMs > 0) { const expireTime = Date.now() + timeoutMs; this.cache[key] = { value, expireTime, dependencies, timeoutMs }; if (callback) { this.cache[key].timer = setTimeout(() => { if (this.cache[key]) { callback(key, this.cache[key].value); delete this.cache[key]; } }, timeoutMs); } } else { this.cache[key] = { value, dependencies }; } this.dependencies[key] = dependencies; } /** * Removes a value from the cache. * @param {string} key - The cache key to remove. */ remove(key) { delete this.cache[key]; delete this.dependencies[key]; } /** * Checks if a key exists in the cache. * @param {string} key - The cache key to check. * @returns {boolean} - True if the key exists in the cache, otherwise false. */ has(key) { return key in this.cache; } /** * Checks if the dependencies have changed. * @param {string} key - The cache key. * @param {Array} depArrayValues - Dependency values to compare. * @returns {boolean} - True if the dependencies have changed, otherwise false. */ dependenciesChanged(key, depArrayValues) { const dependencies = this.dependencies[key]; if (!dependencies) { return false; } const check = JSON.stringify(dependencies) === JSON.stringify(depArrayValues); if (check) { return false; } else { this.dependencies[key] = depArrayValues; return true; } } } const cache = new MemoryCache(); /** * Middleware function for Express.js to enable caching. * * @param {Object} [opts] - Options for caching. * @param {Function} [opts.dependsOn=() => []] - A function that returns an array of dependency values for cache checking. * @param {number} [opts.timeOut=3600000] - Timeout in milliseconds for cache expiration. Default is 1 hour (3600000 ms). * @param {boolean} [opts.enableHashKeys=true] - Enable hashing paths. * @param {string} [opts.namespace="c"] - namespace to prefix paths to store. * @param {Function} [opts.onTimeout=() => { console.log("Cache removed"); }] - A callback function to execute when a cached item expires. * @param {Function} [opts.onCacheMiss=(url: string) => { }] - A callback function to execute when a request is not found in cache. * @param {Function} [opts.onCacheServed=(url: string) => { }] - A callback function to execute when a cached item is served. * @param {Function} [opts.onCacheStored=(url: string) => { }] - A callback function to execute when a cached item is stored / updated. * @returns {function} - Middleware function. */ function expressCache(opts = {}) { const defaults = { dependsOn: () => [], timeOut: 60 * 60 * 1000, onTimeout: () => { console.log("Cache removed"); }, onCacheMiss: () => {}, onCacheServed: () => {}, onCacheStored: () => {}, enableHashKeys: true, namespace: "c", }; const options = { ...defaults, ...opts, }; const { dependsOn, timeOut, onTimeout, onCacheMiss, onCacheServed, onCacheStored, enableHashKeys, namespace, } = options; return function (req, res, next) { const cacheUrl = req.originalUrl || req.url; const cacheKey = namespace + "_" + (enableHashKeys ? hashString(cacheUrl) : cacheUrl); const depArrayValues = dependsOn(); const cachedResponse = cache.get(cacheKey, depArrayValues); if (cachedResponse) { const cachedBody = cachedResponse.body; const cachedHeaders = cachedResponse.headers; const cachedStatusCode = cachedResponse.statusCode; // Set headers that we cached if (cachedHeaders) { res.set(JSON.parse(cachedHeaders)); } if (typeof cachedBody === "string") { try { const jsonData = JSON.parse(cachedBody); onCacheServed(cacheUrl); res.status(cachedStatusCode).json(jsonData); } catch (error) { onCacheServed(cacheUrl); res.status(cachedStatusCode).send(cachedBody); } } else { onCacheServed(cacheUrl); res.status(cachedStatusCode).send(cachedBody); } } else { onCacheMiss(cacheUrl); const originalSend = res.send; const originalJson = res.json; res.send = function (body) { const status = (typeof body === "object" ? body : JSON.parse(body)).status; if (!status || status < 300) { cache.set( cacheKey, { body: typeof body === "object" ? JSON.stringify(body) : body, headers: JSON.stringify(res.getHeaders()), statusCode: res.statusCode, }, timeOut, onTimeout, depArrayValues ); onCacheStored(cacheUrl); } originalSend.call(this, body); }; res.json = function (body) { if (!body.status || body.status < 300) { cache.set( cacheKey, { body: body, headers: JSON.stringify(res.getHeaders()), statusCode: body.status || res.statusCode, }, timeOut, onTimeout, depArrayValues ); onCacheStored(cacheUrl); } originalJson.call(this, body); }; next(); } }; } module.exports = expressCache; module.exports.hash = hashString; module.exports.MemoryCache = MemoryCache;