Add JSDoc to server/modules/apicache/*

Signed-off-by: Matthew Nickson <mnickson@sidingsmedia.com>
This commit is contained in:
Matthew Nickson 2022-04-21 19:55:01 +01:00
parent c2f6c5b42e
commit 9996ba1636
No known key found for this signature in database
GPG Key ID: BF229DCFD4748E05
2 changed files with 206 additions and 80 deletions

View File

@ -13,27 +13,49 @@ let t = {
let instances = []; let instances = [];
/**
* Does a === b
* @param {any} a
* @returns {function(any): boolean}
*/
let matches = function (a) { let matches = function (a) {
return function (b) { return function (b) {
return a === b; return a === b;
}; };
}; };
/**
* Does a!==b
* @param {any} a
* @returns {function(any): boolean}
*/
let doesntMatch = function (a) { let doesntMatch = function (a) {
return function (b) { return function (b) {
return !matches(a)(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 logDuration = function (d, prefix) {
let str = d > 1000 ? (d / 1000).toFixed(2) + "sec" : d + "ms"; let str = d > 1000 ? (d / 1000).toFixed(2) + "sec" : d + "ms";
return "\x1b[33m- " + (prefix ? prefix + " " : "") + str + "\x1b[0m"; return "\x1b[33m- " + (prefix ? prefix + " " : "") + str + "\x1b[0m";
}; };
/**
* Get safe headers
* @param {Object} res Express response object
* @returns {Object}
*/
function getSafeHeaders(res) { function getSafeHeaders(res) {
return res.getHeaders ? res.getHeaders() : res._headers; return res.getHeaders ? res.getHeaders() : res._headers;
} }
/** Constructor for ApiCache instance */
function ApiCache() { function ApiCache() {
let memCache = new MemoryCache(); let memCache = new MemoryCache();
@ -70,10 +92,10 @@ function ApiCache() {
/** /**
* Logs a message to the console if the `DEBUG` environment variable is set. * Logs a message to the console if the `DEBUG` environment variable is set.
* @param {string} a - The first argument to log. * @param {string} a The first argument to log.
* @param {string} b - The second argument to log. * @param {string} b The second argument to log.
* @param {string} c - The third 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} d The fourth argument to log, and so on... (optional)
* *
* Generated by Trelent * Generated by Trelent
*/ */
@ -90,8 +112,8 @@ function ApiCache() {
* Returns true if the given request and response should be logged. * Returns true if the given request and response should be logged.
* @param {Object} request The HTTP request object. * @param {Object} request The HTTP request object.
* @param {Object} response The HTTP response object. * @param {Object} response The HTTP response object.
* * @param {function(Object, Object):boolean} toggle
* Generated by Trelent * @returns {boolean}
*/ */
function shouldCacheResponse(request, response, toggle) { function shouldCacheResponse(request, response, toggle) {
let opt = globalOptions; let opt = globalOptions;
@ -116,10 +138,9 @@ function ApiCache() {
} }
/** /**
* Adds a key to the index. * Add key to index array
* @param {string} key The key to add. * @param {string} key Key to add
* * @param {Object} req Express request object
* Generated by Trelent
*/ */
function addIndexEntries(key, req) { function addIndexEntries(key, req) {
let groupName = req.apicacheGroup; let groupName = req.apicacheGroup;
@ -135,8 +156,11 @@ function ApiCache() {
/** /**
* Returns a new object containing only the whitelisted headers. * Returns a new object containing only the whitelisted headers.
* @param {Object} headers The original object of header names and values. * @param {Object} headers The original object of header names and
* @param {Array.<string>} globalOptions.headerWhitelist An array of strings representing the whitelisted header names to keep in the output object. * values.
* @param {string[]} globalOptions.headerWhitelist An array of
* strings representing the whitelisted header names to keep in the
* output object.
* *
* Generated by Trelent * Generated by Trelent
*/ */
@ -152,8 +176,10 @@ function ApiCache() {
} }
/** /**
* Create a cache object
* @param {Object} headers The response headers to filter. * @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 * Generated by Trelent
*/ */
@ -170,8 +196,9 @@ function ApiCache() {
/** /**
* Sets a cache value for the given key. * Sets a cache value for the given key.
* @param {string} key The cache key to set. * @param {string} key The cache key to set.
* @param {*} value The cache value to set. * @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). * @param {number} duration How long in milliseconds the cached
* response should be valid for (defaults to 1 hour).
* *
* Generated by Trelent * Generated by Trelent
*/ */
@ -199,7 +226,8 @@ function ApiCache() {
/** /**
* Appends content to the response. * 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 * Generated by Trelent
*/ */
@ -229,11 +257,15 @@ function ApiCache() {
} }
/** /**
* Monkeypatches the response object to add cache control headers and create a cache object. * Monkeypatches the response object to add cache control headers
* @param {Object} req - The request object. * and create a cache object.
* @param {Object} res - The response object. * @param {Object} req Express request object
* * @param {Object} res Express response object
* Generated by Trelent * @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) { function makeResponseCacheable(req, res, next, key, duration, strDuration, toggle) {
// monkeypatch res.end to create cache object // monkeypatch res.end to create cache object
@ -302,11 +334,15 @@ function ApiCache() {
} }
/** /**
* @param {Request} request * Send a cached response to client
* @param {Response} response * @param {Request} request Express request object
* @returns {boolean|undefined} true if the request should be cached, false otherwise. If undefined, defaults to true. * @param {Response} response Express response object
* * @param {object} cacheObject Cache object to send
* Generated by Trelent * @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) { function sendCachedResponse(request, response, cacheObject, toggle, next, duration) {
if (toggle && !toggle(request, response)) { if (toggle && !toggle(request, response)) {
@ -348,12 +384,19 @@ function ApiCache() {
return response.end(data, cacheObject.encoding); return response.end(data, cacheObject.encoding);
} }
/** Sync caching options */
function syncOptions() { function syncOptions() {
for (let i in middlewareOptions) { for (let i in middlewareOptions) {
Object.assign(middlewareOptions[i].options, globalOptions, middlewareOptions[i].localOptions); 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) { this.clear = function (target, isAutomatic) {
let group = index.groups[target]; let group = index.groups[target];
let redis = globalOptions.redisClient; let redis = globalOptions.redisClient;
@ -430,10 +473,11 @@ function ApiCache() {
/** /**
* Converts a duration string to an integer number of milliseconds. * Converts a duration string to an integer number of milliseconds.
* @param {string} duration - The string to convert. * @param {(string|number)} duration The string to convert.
* @returns {number} The converted value in milliseconds, or the defaultDuration if it can't be parsed. * @param {number} defaultDuration The default duration to return if
* * can't parse duration
* Generated by Trelent * @returns {number} The converted value in milliseconds, or the
* defaultDuration if it can't be parsed.
*/ */
function parseDuration(duration, defaultDuration) { function parseDuration(duration, defaultDuration) {
if (typeof duration === "number") { if (typeof duration === "number") {
@ -457,17 +501,24 @@ function ApiCache() {
return defaultDuration; return defaultDuration;
} }
/**
* Parse duration
* @param {(number|string)} duration
* @returns {number} Duration parsed to a number
*/
this.getDuration = function (duration) { this.getDuration = function (duration) {
return parseDuration(duration, globalOptions.defaultDuration); 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:
* <code> * <code>
* app.get('/api/cache/performance', (req, res) => { * app.get('/api/cache/performance', (req, res) => {
* res.json(apicache.getPerformance()) * res.json(apicache.getPerformance())
* }) * })
* </code> * </code>
* @returns {any[]}
*/ */
this.getPerformance = function () { this.getPerformance = function () {
return performanceArray.map(function (p) { 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) { this.getIndex = function (group) {
if (group) { if (group) {
return index.groups[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) { this.middleware = function cache(strDuration, middlewareToggle, localOptions) {
let duration = instance.getDuration(strDuration); let duration = instance.getDuration(strDuration);
let opt = {}; let opt = {};
@ -506,63 +570,72 @@ function ApiCache() {
options(localOptions); options(localOptions);
/** /**
* A Function for non tracking performance * A Function for non tracking performance
*/ */
function NOOPCachePerformance() { function NOOPCachePerformance() {
this.report = this.hit = this.miss = function () {}; // noop; 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() { function CachePerformance() {
/** /**
* Tracks the hit rate for the last 100 requests. * Tracks the hit rate for the last 100 requests. If there
* If there have been fewer than 100 requests, the hit rate just considers the requests that have happened. * 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 this.hitsLast100 = new Uint8Array(100 / 4); // each hit is 2 bits
/** /**
* Tracks the hit rate for the last 1000 requests. * Tracks the hit rate for the last 1000 requests. If there
* If there have been fewer than 1000 requests, the hit rate just considers the requests that have happened. * 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 this.hitsLast1000 = new Uint8Array(1000 / 4); // each hit is 2 bits
/** /**
* Tracks the hit rate for the last 10000 requests. * Tracks the hit rate for the last 10000 requests. If there
* If there have been fewer than 10000 requests, the hit rate just considers the requests that have happened. * 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 this.hitsLast10000 = new Uint8Array(10000 / 4); // each hit is 2 bits
/** /**
* Tracks the hit rate for the last 100000 requests. * Tracks the hit rate for the last 100000 requests. If
* If there have been fewer than 100000 requests, the hit rate just considers the requests that have happened. * 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 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; this.callCount = 0;
/** /**
* The total number of hits since the server started * The total number of hits since the server started
*/ */
this.hitCount = 0; 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; 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; this.lastCacheMiss = null;
/** /**
* Return performance statistics * Return performance statistics
*/ * @returns {Object}
*/
this.report = function () { this.report = function () {
return { return {
lastCacheHit: this.lastCacheHit, lastCacheHit: this.lastCacheHit,
@ -579,10 +652,13 @@ function ApiCache() {
}; };
/** /**
* Computes a cache hit rate from an array of hits and misses. * Computes a cache hit rate from an array of hits and
* @param {Uint8Array} array An array representing hits and misses. * misses.
* @returns a number between 0 and 1, or null if the array has no hits or 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) { this.hitRate = function (array) {
let hits = 0; let hits = 0;
let misses = 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 * Record a hit or miss in the given array. It will be
* by the current value of the callCount variable. * recorded at a position determined by the current value of
* @param {Uint8Array} array An array representing hits and misses. * the callCount variable.
* @param {boolean} hit true for a hit, false for a miss * @param {Uint8Array} array An array representing hits and
* Each element in the array is 8 bits, and encodes 4 hit/miss records. * misses.
* Each hit or miss is encoded as to bits as follows: * @param {boolean} hit true for a hit, false for a miss
* 00 means no hit or miss has been recorded in these bits * Each element in the array is 8 bits, and encodes 4
* 01 encodes a hit * hit/miss records. Each hit or miss is encoded as to bits
* 10 encodes a miss * 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) { this.recordHitInArray = function (array, hit) {
let arrayIndex = ~~(this.callCount / 4) % array.length; let arrayIndex = ~~(this.callCount / 4) % array.length;
let bitOffset = (this.callCount % 4) * 2; // 2 bits per record, 4 records per uint8 array element 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. * Records the hit or miss in the tracking arrays and
* @param {boolean} hit true records a hit, false records a miss * increments the call count.
*/ * @param {boolean} hit true records a hit, false records a
* miss
*/
this.recordHit = function (hit) { this.recordHit = function (hit) {
this.recordHitInArray(this.hitsLast100, hit); this.recordHitInArray(this.hitsLast100, hit);
this.recordHitInArray(this.hitsLast1000, hit); this.recordHitInArray(this.hitsLast1000, hit);
@ -642,18 +721,18 @@ function ApiCache() {
}; };
/** /**
* Records a hit event, setting lastCacheMiss to the given key * Records a hit event, setting lastCacheMiss to the given key
* @param {string} key The key that had the cache hit * @param {string} key The key that had the cache hit
*/ */
this.hit = function (key) { this.hit = function (key) {
this.recordHit(true); this.recordHit(true);
this.lastCacheHit = key; this.lastCacheHit = key;
}; };
/** /**
* Records a miss event, setting lastCacheMiss to the given key * Records a miss event, setting lastCacheMiss to the given key
* @param {string} key The key that had the cache miss * @param {string} key The key that had the cache miss
*/ */
this.miss = function (key) { this.miss = function (key) {
this.recordHit(false); this.recordHit(false);
this.lastCacheMiss = key; this.lastCacheMiss = key;
@ -664,6 +743,13 @@ function ApiCache() {
performanceArray.push(perf); 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) { let cache = function (req, res, next) {
function bypass() { function bypass() {
debug("bypass detected, skipping cache."); debug("bypass detected, skipping cache.");
@ -771,6 +857,11 @@ function ApiCache() {
return cache; return cache;
}; };
/**
* Process options
* @param {Object} options
* @returns {Object}
*/
this.options = function (options) { this.options = function (options) {
if (options) { if (options) {
Object.assign(globalOptions, options); Object.assign(globalOptions, options);
@ -791,6 +882,7 @@ function ApiCache() {
} }
}; };
/** Reset the index */
this.resetIndex = function () { this.resetIndex = function () {
index = { index = {
all: [], 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) { this.newInstance = function (config) {
let instance = new ApiCache(); let instance = new ApiCache();
@ -808,6 +905,7 @@ function ApiCache() {
return instance; return instance;
}; };
/** Clone this instance */
this.clone = function () { this.clone = function () {
return this.newInstance(this.options()); return this.newInstance(this.options());
}; };

View File

@ -3,6 +3,15 @@ function MemoryCache() {
this.size = 0; 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) { MemoryCache.prototype.add = function (key, value, time, timeoutCallback) {
let old = this.cache[key]; let old = this.cache[key];
let instance = this; let instance = this;
@ -22,6 +31,11 @@ MemoryCache.prototype.add = function (key, value, time, timeoutCallback) {
return entry; return entry;
}; };
/**
* Delete a cache entry
* @param {string} key Key to delete
* @returns {null}
*/
MemoryCache.prototype.delete = function (key) { MemoryCache.prototype.delete = function (key) {
let entry = this.cache[key]; let entry = this.cache[key];
@ -36,18 +50,32 @@ MemoryCache.prototype.delete = function (key) {
return null; return null;
}; };
/**
* Get value of key
* @param {string} key
* @returns {Object}
*/
MemoryCache.prototype.get = function (key) { MemoryCache.prototype.get = function (key) {
let entry = this.cache[key]; let entry = this.cache[key];
return entry; return entry;
}; };
/**
* Get value of cache entry
* @param {string} key
* @returns {any}
*/
MemoryCache.prototype.getValue = function (key) { MemoryCache.prototype.getValue = function (key) {
let entry = this.get(key); let entry = this.get(key);
return entry && entry.value; return entry && entry.value;
}; };
/**
* Clear cache
* @returns {boolean}
*/
MemoryCache.prototype.clear = function () { MemoryCache.prototype.clear = function () {
Object.keys(this.cache).forEach(function (key) { Object.keys(this.cache).forEach(function (key) {
this.delete(key); this.delete(key);