cache-service-cache-module
Advanced tools
Comparing version 1.0.0 to 1.1.0
@@ -5,7 +5,10 @@ /** | ||
* @param config: { | ||
* type: {string | 'cache-module'} | ||
* verbose: {boolean | false}, | ||
* expiration: {integer | 900}, | ||
* readOnly: {boolean | false}, | ||
* checkOnPreviousEmpty {boolean | true} | ||
* type: {string | 'cache-module'} | ||
* verbose: {boolean | false}, | ||
* expiration: {integer | 900}, | ||
* readOnly: {boolean | false}, | ||
* checkOnPreviousEmpty {boolean | true}, | ||
* backgroundRefreshIntervalCheck {boolean | true}, | ||
* backgroundRefreshInterval {integer | 60000}, | ||
* backgroundRefreshMinTtl {integer | 70000} | ||
* } | ||
@@ -21,9 +24,19 @@ */ | ||
self.checkOnPreviousEmpty = (typeof config.checkOnPreviousEmpty === 'boolean') ? config.checkOnPreviousEmpty : true; | ||
self.backgroundRefreshIntervalCheck = (typeof config.backgroundRefreshIntervalCheck === 'boolean') ? config.backgroundRefreshIntervalCheck : true; | ||
self.backgroundRefreshInterval = config.backgroundRefreshInterval || 60000; | ||
self.backgroundRefreshMinTtl = config.backgroundRefreshMinTtl || 70000; | ||
var cache = { | ||
db: {}, | ||
expirations: {} | ||
expirations: {}, | ||
refreshKeys: {} | ||
}; | ||
var backgroundRefreshEnabled = false; | ||
log(false, 'Cache-module client created with the following defaults:', {expiration: this.expiration, verbose: this.verbose, readOnly: this.readOnly}); | ||
/** | ||
******************************************* PUBLIC FUNCTIONS ******************************************* | ||
*/ | ||
/** | ||
* Get the value associated with a given key | ||
@@ -35,13 +48,15 @@ * @param {string} key | ||
self.get = function(key, cb, cleanKey){ | ||
log(false, 'Attempting to get key:', {key: key}); | ||
if(arguments.length < 2){ | ||
throw new exception('INCORRECT_ARGUMENT_EXCEPTION', '.get() requires 2 arguments.'); | ||
} | ||
log(false, 'get() called:', {key: key}); | ||
try { | ||
var cacheKey = (cleanKey) ? cleanKey : key; | ||
log(false, 'Attempting to get key:', {key: cacheKey}); | ||
var now = Date.now(); | ||
var expiration = cache.expirations[key] || this.defaultExpiration; | ||
if(expiration && expiration > now){ | ||
var expiration = cache.expirations[key]; | ||
if(expiration > now){ | ||
cb(null, cache.db[key]); | ||
} | ||
else{ | ||
self.del(key); | ||
expire(key); | ||
cb(null, null); | ||
@@ -62,3 +77,6 @@ } | ||
self.mget = function(keys, cb, index){ | ||
log(false, 'Attempting to mget keys:', {keys: keys}); | ||
if(arguments.length < 2){ | ||
throw new exception('INCORRECT_ARGUMENT_EXCEPTION', '.mget() requires 2 arguments.'); | ||
} | ||
log(false, '.mget() called:', {keys: keys}); | ||
var values = {}; | ||
@@ -81,16 +99,31 @@ for(var i = 0; i < keys.length; i++){ | ||
* @param {integer} expiration | ||
* @param {function} refresh | ||
* @param {function} cb | ||
*/ | ||
self.set = function(key, value, expiration, cb){ | ||
log(false, 'Attempting to set key:', {key: key, value: value}); | ||
self.set = function(){ | ||
if(arguments.length < 2){ | ||
throw new exception('INCORRECT_ARGUMENT_EXCEPTION', '.set() requires a minimum of 2 arguments.'); | ||
} | ||
var key = arguments[0]; | ||
var value = arguments[1]; | ||
var expiration = arguments[2] || null; | ||
var refresh = (arguments.length == 5) ? arguments[3] : null; | ||
var cb = (arguments.length == 5) ? arguments[4] : arguments[3]; | ||
log(false, '.set() called:', {key: key, value: value}); | ||
try { | ||
if(!self.readOnly){ | ||
expiration = expiration || self.defaultExpiration; | ||
var exp = (expiration) ? (expiration * 1000) : self.defaultExpiration; | ||
cache.expirations[key] = Date.now() + exp; | ||
expiration = (expiration) ? (expiration * 1000) : self.defaultExpiration; | ||
var exp = expiration + Date.now(); | ||
cache.expirations[key] = exp; | ||
cache.db[key] = value; | ||
if(cb) cb(); | ||
if(refresh){ | ||
cache.refreshKeys[key] = {expiration: exp, lifeSpan: expiration, refresh: refresh}; | ||
if(!backgroundRefreshEnabled){ | ||
backgroundRefreshInit(); | ||
} | ||
} | ||
} | ||
} catch (err) { | ||
log(true, 'Set failed for cache of type ' + self.type, {name: 'NodeCacheSetException', message: err}); | ||
log(true, '.set() failed for cache of type ' + self.type, {name: 'CacheModuleSetException', message: err}); | ||
} | ||
@@ -106,3 +139,6 @@ } | ||
self.mset = function(obj, expiration, cb){ | ||
log(false, 'Attempting to mset data:', {data: obj}); | ||
if(arguments.length < 1){ | ||
throw new exception('INCORRECT_ARGUMENT_EXCEPTION', '.mset() requires a minimum of 1 argument.'); | ||
} | ||
log(false, '.mset() called:', {data: obj}); | ||
for(key in obj){ | ||
@@ -128,8 +164,12 @@ if(obj.hasOwnProperty(key)){ | ||
self.del = function(keys, cb){ | ||
log(false, 'Attempting to delete keys:', {keys: keys}); | ||
if(arguments.length < 1){ | ||
throw new exception('INCORRECT_ARGUMENT_EXCEPTION', '.del() requires a minimum of 1 argument.'); | ||
} | ||
log(false, '.del() called:', {keys: keys}); | ||
if(typeof keys === 'object'){ | ||
for(var i = 0; i < keys.length; i++){ | ||
var key = keys[i]; | ||
cache.db[key] = undefined; | ||
cache.expirations[key] = undefined; | ||
delete cache.db[key]; | ||
delete cache.expirations[key]; | ||
delete cache.refreshKeys[key]; | ||
} | ||
@@ -139,4 +179,5 @@ if(cb) cb(null, keys.length); | ||
else{ | ||
cache.db[keys] = undefined; | ||
cache.expirations[keys] = undefined; | ||
delete cache.db[keys]; | ||
delete cache.expirations[keys]; | ||
delete cache.refreshKeys[keys]; | ||
if(cb) cb(null, 1); | ||
@@ -151,5 +192,6 @@ } | ||
self.flush = function(cb){ | ||
log(false, 'Attempting to flush all data.'); | ||
log(false, '.flush() called'); | ||
cache.db = {}; | ||
cache.expirations = {}; | ||
cache.refreshKeys = {}; | ||
if(cb) cb(); | ||
@@ -159,2 +201,61 @@ } | ||
/** | ||
******************************************* PRIVATE FUNCTIONS ******************************************* | ||
*/ | ||
/** | ||
* Delete a given key from cache.db and cache.expirations but not from cache.refreshKeys | ||
* @param {string} key | ||
*/ | ||
function expire(key){ | ||
delete cache.db[key]; | ||
delete cache.expirations[key]; | ||
} | ||
/** | ||
* Initialize background refresh | ||
*/ | ||
function backgroundRefreshInit(){ | ||
if(!backgroundRefreshEnabled){ | ||
backgroundRefreshEnabled = true; | ||
if(self.backgroundRefreshIntervalCheck){ | ||
if(self.backgroundRefreshInterval > self.backgroundRefreshMinTtl){ | ||
throw new exception('BACKGROUND_REFRESH_INTERVAL_EXCEPTION', 'backgroundRefreshInterval cannot be greater than backgroundRefreshMinTtl.'); | ||
} | ||
} | ||
setInterval(function(){ | ||
backgroundRefresh(); | ||
}, self.backgroundRefreshInterval); | ||
} | ||
} | ||
/** | ||
* Refreshes all keys that were set with a refresh function | ||
*/ | ||
function backgroundRefresh(){ | ||
for(key in cache.refreshKeys){ | ||
if(cache.refreshKeys.hasOwnProperty(key)){ | ||
var data = cache.refreshKeys[key]; | ||
if(data.expiration - Date.now() < self.backgroundRefreshMinTtl){ | ||
data.refresh(key, function (err, response){ | ||
if(!err){ | ||
self.set(key, response, data.lifeSpan, data.refresh, noop); | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
/** | ||
* Instantates an exception to be thrown | ||
* @param {string} name | ||
* @param {string} message | ||
* @return {exception} | ||
*/ | ||
function exception(name, message){ | ||
this.name = name; | ||
this.message = message; | ||
} | ||
/** | ||
* Error logging logic | ||
@@ -172,4 +273,6 @@ * @param {boolean} isError | ||
} | ||
function noop(){} | ||
} | ||
module.exports = cacheModule; |
{ | ||
"name": "cache-service-cache-module", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "A cache plugin for cache-service.", | ||
@@ -5,0 +5,0 @@ "main": "cacheModule.js", |
@@ -6,2 +6,11 @@ # cache-service-cache-module | ||
#### Features | ||
* Background refresh | ||
* No external dependencies | ||
* Robust API | ||
* Built-in logging with a `verbose` flag. | ||
* Compatible with `cache-service` and `superagent-cache` | ||
* Excellent `.mset()` implementation which allows you to set expirations on a per key, per function call, and/or per `cache-service-cache-module` instance basis. | ||
# Basic Usage | ||
@@ -22,12 +31,6 @@ | ||
# Benefits of Using `cache-service-cache-module` | ||
# Cache Module Configuration Options | ||
If you're using `cache-service-cache-module` with `cache-service`, the benefits are obvious. However, there are also a couple of reasons you might like it as a standalone in-memory cache module: | ||
`cache-service-cache-module`'s constructor takes an optional config object with any number of the following properties: | ||
* No external dependencies. | ||
* It features an excellent `.mset()` implementation which allows you to set expirations on a per key, per function call, and/or per `cache-service-cache-module` instance basis. | ||
* Built-in logging with a `verbose` flag. | ||
# Cache Module Configuration Options | ||
## type | ||
@@ -42,3 +45,3 @@ | ||
The expiration to include when executing cache set commands. Can be overridden via `.set()`'s optional expiraiton param. | ||
The expiration to include when executing cache set commands. Can be overridden via `.set()`'s optional `expiraiton` param. | ||
@@ -49,2 +52,25 @@ * type: int | ||
## backgroundRefreshInterval | ||
How frequently should all background refresh-enabled keys be scanned to determine whether they should be refreshed. For a more thorough explanation on `background refresh`, see the [Using Background Refresh](#using-background-refresh) section. | ||
* type: int | ||
* default: 60000 | ||
* measure: milliseconds | ||
## backgroundRefreshMinTtl | ||
The maximum ttl a scanned background refresh-enabled key can have without triggering a refresh. This number should always be greater than `backgroundRefreshInterval`. | ||
* type: int | ||
* default: 70000 | ||
* measure: milliseconds | ||
## backgroundRefreshIntervalCheck | ||
Whether to throw an exception if `backgroundRefreshInterval` is greater than `backgroundRefreshMinTtl`. Setting this property to false is highly discouraged. | ||
* type: boolean | ||
* default: true | ||
## verbose | ||
@@ -81,9 +107,12 @@ | ||
## .set(key, value [, expiraiton, callback]) | ||
## .set(key, value, [expiraiton], [refresh(key, cb)], [callback]) | ||
> See the [Using Background Refresh](#using-background-refresh) section for more about the `refresh` and `callback` params. | ||
Set a value by a given key. | ||
* key: type: string | ||
* callback: type: function | ||
* value: type: string || objects | ||
* expiration: type: int, measure: seconds | ||
* refresh: type: function | ||
* callback: type: function | ||
@@ -103,3 +132,3 @@ | ||
## .del(keys [, callback (err, count)]) | ||
## .del(keys, [callback (err, count)]) | ||
@@ -118,1 +147,43 @@ Delete a key or an array of keys and their associated values. | ||
* callback: type: function | ||
# Using Background Refresh | ||
With a typical cache setup, you're left to find the perfect compromise between having a long expiration so that users don't have to suffer through the worst case load time, and a short expiration so data doesn't get stale. `cache-service-cache-module` eliminates the need to worry about users suffering through the longest wait time by automatically refreshing keys for you. Here's how it works: | ||
#### How do I turn it on? | ||
By default, background refresh is off. It will turn itself on the first time you pass a `refresh` param to `.set()`. | ||
#### Configure | ||
There are three options you can manipulate. See the API section for more information about them. | ||
* `backgroundRefreshInterval` | ||
* `backgroundRefreshMinTtl` | ||
* `backgroundRefreshIntervalCheck` | ||
#### Use | ||
Background refresh is exposed via the `.set()` command as follows: | ||
```javascript | ||
cacheModule.set('key', 'value', 300, refresh, cb); | ||
``` | ||
If you want to pass `refresh`, you must also pass `cb` because if only four params are passed, `cache-service-cache-module` will assume the fourth param is `cb`. | ||
#### The Refresh Param | ||
###### refresh(key, cb(err, response)) | ||
* key: type: string: this is the key that is being refreshed | ||
* cb: type: function: you must trigger this function to pass the data that should replace the current key's value | ||
The `refresh` param MUST be a function that accepts `key` and a callback function that accepts `err` and `response` as follows: | ||
```javascript | ||
var refresh = function(key, cb){ | ||
var response = goGetData(); | ||
cb(null, response); | ||
} | ||
``` |
var expect = require('expect'); | ||
var cModule = require('../../cacheModule'); | ||
var cacheModule = new cModule(); | ||
var cacheModule = new cModule({ | ||
backgroundRefreshInterval: 500 | ||
}); | ||
@@ -72,2 +74,15 @@ var key = 'key'; | ||
}); | ||
it('Using background refresh should reset a nearly expired key', function (done) { | ||
var refresh = function(key, cb){ | ||
cb(null, 1); | ||
} | ||
cacheModule.set(key, value, 1, refresh, function (err, result){ | ||
setTimeout(function(){ | ||
cacheModule.get(key, function (err, response){ | ||
expect(response).toBe(1); | ||
done(); | ||
}); | ||
}, 1500); | ||
}); | ||
}); | ||
}); |
17763
334
183