@ -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 ( ) )
* } )
* } )
* < / c o d e >
* < / c o d e >
* @ 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 ( ) ) ;
} ;
} ;