From 9996ba1636e7ba679091dab3e0e2a5b47357ef10 Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Thu, 21 Apr 2022 19:55:01 +0100 Subject: [PATCH] Add JSDoc to server/modules/apicache/* Signed-off-by: Matthew Nickson --- server/modules/apicache/apicache.js | 258 ++++++++++++++++-------- server/modules/apicache/memory-cache.js | 28 +++ 2 files changed, 206 insertions(+), 80 deletions(-) diff --git a/server/modules/apicache/apicache.js b/server/modules/apicache/apicache.js index 25f0a54f..5d600500 100644 --- a/server/modules/apicache/apicache.js +++ b/server/modules/apicache/apicache.js @@ -13,27 +13,49 @@ let t = { let instances = []; +/** + * Does a === b + * @param {any} a + * @returns {function(any): boolean} + */ let matches = function (a) { return function (b) { return a === b; }; }; +/** + * Does a!==b + * @param {any} a + * @returns {function(any): boolean} + */ let doesntMatch = function (a) { return function (b) { return !matches(a)(b); }; }; +/** + * Get log duration + * @param {number} d Time in ms + * @param {string} prefix Prefix for log + * @returns {string} Coloured log string + */ let logDuration = function (d, prefix) { let str = d > 1000 ? (d / 1000).toFixed(2) + "sec" : d + "ms"; return "\x1b[33m- " + (prefix ? prefix + " " : "") + str + "\x1b[0m"; }; +/** + * Get safe headers + * @param {Object} res Express response object + * @returns {Object} + */ function getSafeHeaders(res) { return res.getHeaders ? res.getHeaders() : res._headers; } +/** Constructor for ApiCache instance */ function ApiCache() { let memCache = new MemoryCache(); @@ -70,10 +92,10 @@ function ApiCache() { /** * Logs a message to the console if the `DEBUG` environment variable is set. - * @param {string} a - The first argument to log. - * @param {string} b - The second argument to log. - * @param {string} c - The third argument to log. - * @param {string} d - The fourth argument to log, and so on... (optional) + * @param {string} a The first argument to log. + * @param {string} b The second argument to log. + * @param {string} c The third argument to log. + * @param {string} d The fourth argument to log, and so on... (optional) * * Generated by Trelent */ @@ -90,8 +112,8 @@ function ApiCache() { * Returns true if the given request and response should be logged. * @param {Object} request The HTTP request object. * @param {Object} response The HTTP response object. - * - * Generated by Trelent + * @param {function(Object, Object):boolean} toggle + * @returns {boolean} */ function shouldCacheResponse(request, response, toggle) { let opt = globalOptions; @@ -116,10 +138,9 @@ function ApiCache() { } /** - * Adds a key to the index. - * @param {string} key The key to add. - * - * Generated by Trelent + * Add key to index array + * @param {string} key Key to add + * @param {Object} req Express request object */ function addIndexEntries(key, req) { let groupName = req.apicacheGroup; @@ -135,8 +156,11 @@ function ApiCache() { /** * Returns a new object containing only the whitelisted headers. - * @param {Object} headers The original object of header names and values. - * @param {Array.} globalOptions.headerWhitelist An array of strings representing the whitelisted header names to keep in the output object. + * @param {Object} headers The original object of header names and + * values. + * @param {string[]} globalOptions.headerWhitelist An array of + * strings representing the whitelisted header names to keep in the + * output object. * * Generated by Trelent */ @@ -152,8 +176,10 @@ function ApiCache() { } /** + * Create a cache object * @param {Object} headers The response headers to filter. - * @returns {Object} A new object containing only the whitelisted response headers. + * @returns {Object} A new object containing only the whitelisted + * response headers. * * Generated by Trelent */ @@ -170,8 +196,9 @@ function ApiCache() { /** * Sets a cache value for the given key. * @param {string} key The cache key to set. - * @param {*} value The cache value to set. - * @param {number} duration How long in milliseconds the cached response should be valid for (defaults to 1 hour). + * @param {any} value The cache value to set. + * @param {number} duration How long in milliseconds the cached + * response should be valid for (defaults to 1 hour). * * Generated by Trelent */ @@ -199,7 +226,8 @@ function ApiCache() { /** * Appends content to the response. - * @param {string|Buffer} content The content to append. + * @param {Object} res Express response object + * @param {(string|Buffer)} content The content to append. * * Generated by Trelent */ @@ -229,11 +257,15 @@ function ApiCache() { } /** - * Monkeypatches the response object to add cache control headers and create a cache object. - * @param {Object} req - The request object. - * @param {Object} res - The response object. - * - * Generated by Trelent + * Monkeypatches the response object to add cache control headers + * and create a cache object. + * @param {Object} req Express request object + * @param {Object} res Express response object + * @param {function} next Function to call next + * @param {string} key Key to add response as + * @param {number} duration Time to cache response for + * @param {string} strDuration Duration in string form + * @param {function(Object, Object):boolean} toggle */ function makeResponseCacheable(req, res, next, key, duration, strDuration, toggle) { // monkeypatch res.end to create cache object @@ -302,11 +334,15 @@ function ApiCache() { } /** - * @param {Request} request - * @param {Response} response - * @returns {boolean|undefined} true if the request should be cached, false otherwise. If undefined, defaults to true. - * - * Generated by Trelent + * Send a cached response to client + * @param {Request} request Express request object + * @param {Response} response Express response object + * @param {object} cacheObject Cache object to send + * @param {function(Object, Object):boolean} toggle + * @param {function} next Function to call next + * @param {number} duration Not used + * @returns {boolean|undefined} true if the request should be + * cached, false otherwise. If undefined, defaults to true. */ function sendCachedResponse(request, response, cacheObject, toggle, next, duration) { if (toggle && !toggle(request, response)) { @@ -348,12 +384,19 @@ function ApiCache() { return response.end(data, cacheObject.encoding); } + /** Sync caching options */ function syncOptions() { for (let i in middlewareOptions) { Object.assign(middlewareOptions[i].options, globalOptions, middlewareOptions[i].localOptions); } } + /** + * Clear key from cache + * @param {string} target Key to clear + * @param {boolean} isAutomatic Is the key being cleared automatically + * @returns {number} + */ this.clear = function (target, isAutomatic) { let group = index.groups[target]; let redis = globalOptions.redisClient; @@ -430,10 +473,11 @@ function ApiCache() { /** * Converts a duration string to an integer number of milliseconds. - * @param {string} duration - The string to convert. - * @returns {number} The converted value in milliseconds, or the defaultDuration if it can't be parsed. - * - * Generated by Trelent + * @param {(string|number)} duration The string to convert. + * @param {number} defaultDuration The default duration to return if + * can't parse duration + * @returns {number} The converted value in milliseconds, or the + * defaultDuration if it can't be parsed. */ function parseDuration(duration, defaultDuration) { if (typeof duration === "number") { @@ -457,17 +501,24 @@ function ApiCache() { return defaultDuration; } + /** + * Parse duration + * @param {(number|string)} duration + * @returns {number} Duration parsed to a number + */ this.getDuration = function (duration) { return parseDuration(duration, globalOptions.defaultDuration); }; /** - * Return cache performance statistics (hit rate). Suitable for putting into a route: + * Return cache performance statistics (hit rate). Suitable for + * putting into a route: * * app.get('/api/cache/performance', (req, res) => { * res.json(apicache.getPerformance()) * }) * + * @returns {any[]} */ this.getPerformance = function () { return performanceArray.map(function (p) { @@ -475,6 +526,11 @@ function ApiCache() { }); }; + /** + * Get index of a group + * @param {string} group + * @returns {number} + */ this.getIndex = function (group) { if (group) { return index.groups[group]; @@ -483,6 +539,14 @@ function ApiCache() { } }; + /** + * Express middleware + * @param {(string|number)} strDuration Duration to cache responses + * for. + * @param {function(Object, Object):boolean} middlewareToggle + * @param {Object} localOptions Options for APICache + * @returns + */ this.middleware = function cache(strDuration, middlewareToggle, localOptions) { let duration = instance.getDuration(strDuration); let opt = {}; @@ -506,63 +570,72 @@ function ApiCache() { options(localOptions); /** - * A Function for non tracking performance - */ + * A Function for non tracking performance + */ function NOOPCachePerformance() { this.report = this.hit = this.miss = function () {}; // noop; } /** - * A function for tracking and reporting hit rate. These statistics are returned by the getPerformance() call above. - */ + * A function for tracking and reporting hit rate. These + * statistics are returned by the getPerformance() call above. + */ function CachePerformance() { /** - * Tracks the hit rate for the last 100 requests. - * If there have been fewer than 100 requests, the hit rate just considers the requests that have happened. - */ + * Tracks the hit rate for the last 100 requests. If there + * have been fewer than 100 requests, the hit rate just + * considers the requests that have happened. + */ this.hitsLast100 = new Uint8Array(100 / 4); // each hit is 2 bits /** - * Tracks the hit rate for the last 1000 requests. - * If there have been fewer than 1000 requests, the hit rate just considers the requests that have happened. - */ + * Tracks the hit rate for the last 1000 requests. If there + * have been fewer than 1000 requests, the hit rate just + * considers the requests that have happened. + */ this.hitsLast1000 = new Uint8Array(1000 / 4); // each hit is 2 bits /** - * Tracks the hit rate for the last 10000 requests. - * If there have been fewer than 10000 requests, the hit rate just considers the requests that have happened. - */ + * Tracks the hit rate for the last 10000 requests. If there + * have been fewer than 10000 requests, the hit rate just + * considers the requests that have happened. + */ this.hitsLast10000 = new Uint8Array(10000 / 4); // each hit is 2 bits /** - * Tracks the hit rate for the last 100000 requests. - * If there have been fewer than 100000 requests, the hit rate just considers the requests that have happened. - */ + * Tracks the hit rate for the last 100000 requests. If + * there have been fewer than 100000 requests, the hit rate + * just considers the requests that have happened. + */ this.hitsLast100000 = new Uint8Array(100000 / 4); // each hit is 2 bits /** - * The number of calls that have passed through the middleware since the server started. - */ + * The number of calls that have passed through the + * middleware since the server started. + */ this.callCount = 0; /** - * The total number of hits since the server started - */ + * The total number of hits since the server started + */ this.hitCount = 0; /** - * The key from the last cache hit. This is useful in identifying which route these statistics apply to. - */ + * The key from the last cache hit. This is useful in + * identifying which route these statistics apply to. + */ this.lastCacheHit = null; /** - * The key from the last cache miss. This is useful in identifying which route these statistics apply to. - */ + * The key from the last cache miss. This is useful in + * identifying which route these statistics apply to. + */ this.lastCacheMiss = null; /** - * Return performance statistics - */ + * Return performance statistics + * @returns {Object} + */ this.report = function () { return { lastCacheHit: this.lastCacheHit, @@ -579,10 +652,13 @@ function ApiCache() { }; /** - * Computes a cache hit rate from an array of hits and misses. - * @param {Uint8Array} array An array representing hits and misses. - * @returns a number between 0 and 1, or null if the array has no hits or misses - */ + * Computes a cache hit rate from an array of hits and + * misses. + * @param {Uint8Array} array An array representing hits and + * misses. + * @returns {number} a number between 0 and 1, or null if + * the array has no hits or misses + */ this.hitRate = function (array) { let hits = 0; let misses = 0; @@ -608,16 +684,17 @@ function ApiCache() { }; /** - * Record a hit or miss in the given array. It will be recorded at a position determined - * by the current value of the callCount variable. - * @param {Uint8Array} array An array representing hits and misses. - * @param {boolean} hit true for a hit, false for a miss - * Each element in the array is 8 bits, and encodes 4 hit/miss records. - * Each hit or miss is encoded as to bits as follows: - * 00 means no hit or miss has been recorded in these bits - * 01 encodes a hit - * 10 encodes a miss - */ + * Record a hit or miss in the given array. It will be + * recorded at a position determined by the current value of + * the callCount variable. + * @param {Uint8Array} array An array representing hits and + * misses. + * @param {boolean} hit true for a hit, false for a miss + * Each element in the array is 8 bits, and encodes 4 + * hit/miss records. Each hit or miss is encoded as to bits + * as follows: 00 means no hit or miss has been recorded in + * these bits 01 encodes a hit 10 encodes a miss + */ this.recordHitInArray = function (array, hit) { let arrayIndex = ~~(this.callCount / 4) % array.length; let bitOffset = (this.callCount % 4) * 2; // 2 bits per record, 4 records per uint8 array element @@ -627,9 +704,11 @@ function ApiCache() { }; /** - * Records the hit or miss in the tracking arrays and increments the call count. - * @param {boolean} hit true records a hit, false records a miss - */ + * Records the hit or miss in the tracking arrays and + * increments the call count. + * @param {boolean} hit true records a hit, false records a + * miss + */ this.recordHit = function (hit) { this.recordHitInArray(this.hitsLast100, hit); this.recordHitInArray(this.hitsLast1000, hit); @@ -642,18 +721,18 @@ function ApiCache() { }; /** - * Records a hit event, setting lastCacheMiss to the given key - * @param {string} key The key that had the cache hit - */ + * Records a hit event, setting lastCacheMiss to the given key + * @param {string} key The key that had the cache hit + */ this.hit = function (key) { this.recordHit(true); this.lastCacheHit = key; }; /** - * Records a miss event, setting lastCacheMiss to the given key - * @param {string} key The key that had the cache miss - */ + * Records a miss event, setting lastCacheMiss to the given key + * @param {string} key The key that had the cache miss + */ this.miss = function (key) { this.recordHit(false); this.lastCacheMiss = key; @@ -664,6 +743,13 @@ function ApiCache() { performanceArray.push(perf); + /** + * Cache a request + * @param {Object} req Express request object + * @param {Object} res Express response object + * @param {function} next Function to call next + * @returns {any} + */ let cache = function (req, res, next) { function bypass() { debug("bypass detected, skipping cache."); @@ -771,6 +857,11 @@ function ApiCache() { return cache; }; + /** + * Process options + * @param {Object} options + * @returns {Object} + */ this.options = function (options) { if (options) { Object.assign(globalOptions, options); @@ -791,6 +882,7 @@ function ApiCache() { } }; + /** Reset the index */ this.resetIndex = function () { index = { all: [], @@ -798,6 +890,11 @@ function ApiCache() { }; }; + /** + * Create a new instance of ApiCache + * @param {Object} config Config to pass + * @returns {ApiCache} + */ this.newInstance = function (config) { let instance = new ApiCache(); @@ -808,6 +905,7 @@ function ApiCache() { return instance; }; + /** Clone this instance */ this.clone = function () { return this.newInstance(this.options()); }; diff --git a/server/modules/apicache/memory-cache.js b/server/modules/apicache/memory-cache.js index ad831e2e..a91eee32 100644 --- a/server/modules/apicache/memory-cache.js +++ b/server/modules/apicache/memory-cache.js @@ -3,6 +3,15 @@ function MemoryCache() { this.size = 0; } +/** + * + * @param {string} key Key to store cache as + * @param {any} value Value to store + * @param {number} time Time to store for + * @param {function(any, string)} timeoutCallback Callback to call in + * case of timeout + * @returns {Object} + */ MemoryCache.prototype.add = function (key, value, time, timeoutCallback) { let old = this.cache[key]; let instance = this; @@ -22,6 +31,11 @@ MemoryCache.prototype.add = function (key, value, time, timeoutCallback) { return entry; }; +/** + * Delete a cache entry + * @param {string} key Key to delete + * @returns {null} + */ MemoryCache.prototype.delete = function (key) { let entry = this.cache[key]; @@ -36,18 +50,32 @@ MemoryCache.prototype.delete = function (key) { return null; }; +/** + * Get value of key + * @param {string} key + * @returns {Object} + */ MemoryCache.prototype.get = function (key) { let entry = this.cache[key]; return entry; }; +/** + * Get value of cache entry + * @param {string} key + * @returns {any} + */ MemoryCache.prototype.getValue = function (key) { let entry = this.get(key); return entry && entry.value; }; +/** + * Clear cache + * @returns {boolean} + */ MemoryCache.prototype.clear = function () { Object.keys(this.cache).forEach(function (key) { this.delete(key);