core.io-cache-redis
Advanced tools
Comparing version 0.3.2 to 0.4.0
129
lib/cache.js
@@ -57,21 +57,31 @@ 'use strict'; | ||
* we run `fallback` and cache the value returned from that. | ||
* | ||
* @param {String} key raw key | ||
* @param {Function} fallback Called on cache miss | ||
* @param {Int} ttl TTL for this key | ||
* @param {Boolean} [serialize=true] Retrieve content as JSON | ||
* @param {Boolean} [addTimestamp=true] Include a timestamp to payload | ||
* @param {Boolean} [throwOnError=false] Throw if fallback errors | ||
* | ||
* You can use `forceCacheMiss` to always execute the `fallback` | ||
* function thus bypassing the cache altogether. | ||
* | ||
* @param {String} key raw key | ||
* @param {Function} fallback Called on cache miss | ||
* @param {Object} options | ||
* @param {Int} [options.ttl=defaultTTL] TTL for this key | ||
* @param {Boolean} [options.deserialize=true] Retrieve content as JSON | ||
* @param {Boolean} [options.addTimestamp=true] Include a timestamp to payload | ||
* @param {Boolean} [options.throwOnError=false] Throw if fallback errors | ||
* @param {Boolean} [options.forceCacheMiss=false] Throw if fallback errors | ||
* @param {Function} [options.forceCacheMiss] Called with current key and options | ||
* @returns {Promise} | ||
*/ | ||
async tryGet(key, fallback, ttl = this.defaultTtl, serialize = true, addTimestamp = true, throwOnError = false) { | ||
async tryGet(key, fallback, options = {}) { | ||
options = extend({ ttl: this.defaultTTL }, this.tryGetOptions, options); | ||
key = this.hashKey(key); | ||
this.logger.info('try to fetch key "%s" from cache...', key); | ||
let value; | ||
let value = await this.get(key, false, serialize); | ||
if (this.shouldQueryCache(key, options)) { | ||
this.logger.info('fetching "%s"', key); | ||
value = await this.get(key, false, options.deserialize); | ||
} | ||
if (value) { | ||
this.logger.info('value was cached, return'); | ||
this.logger.info('returning cached value!'); | ||
return value; | ||
@@ -81,3 +91,6 @@ } | ||
try { | ||
this.logger.info('cache miss "%s"', key); | ||
value = await fallback(); | ||
/** | ||
@@ -87,5 +100,5 @@ * We want to mark when we last accessed | ||
*/ | ||
this.makeTimestamp(value, addTimestamp); | ||
this.makeTimestamp(value, options.addTimestamp); | ||
await this.set(key, value, ttl); | ||
await this.set(key, value, options.ttl); | ||
} catch (error) { | ||
@@ -96,3 +109,3 @@ value = { $error: error }; | ||
this.handleError(error, 'cache try error'); | ||
if (throwOnError) throw error; | ||
if (options.throwOnError) throw error; | ||
} | ||
@@ -105,3 +118,3 @@ | ||
* Retrieve key from store. | ||
* | ||
* | ||
* @param {String} key cache key | ||
@@ -127,3 +140,3 @@ * @param {Any} def Any value | ||
*/ | ||
set(key, value, ttl = this.defaultTtl) { | ||
set(key, value, ttl = this.defaultTTL) { | ||
key = this.hashKey(key); | ||
@@ -135,4 +148,5 @@ if (typeof value !== 'string') value = this.serialize(value); | ||
/** | ||
* Remove key from ache | ||
* Remove key from cache. | ||
* @param {String} key cache key | ||
* @param {Object} key cache key | ||
* @returns {Promise} | ||
@@ -146,5 +160,16 @@ */ | ||
/** | ||
* Format key. | ||
* @param {String} key raw value to hash | ||
* @param {Object} key raw value to hash | ||
* Format `key`. | ||
* | ||
* If `key` is an object it will be | ||
* `serialize`d into a string. | ||
* | ||
* If `key` is a string it will be hashed | ||
* and appended to `cacheKeyPrefix`. | ||
* | ||
* Keys look like: | ||
* ```js | ||
* cache:1239ecd04b073b8f4615d4077be5e263 | ||
* ``` | ||
* @param {String} key raw value to hash | ||
* @param {Object} key raw value to hash | ||
* @returns {String} Formatted key | ||
@@ -165,2 +190,66 @@ */ | ||
/** | ||
* Purge all keys matching the `match` pattern. | ||
* | ||
* @param {String} match Pattern to match | ||
* @param {Integer} count Number of keys per cycle | ||
* @returns {Promise} | ||
*/ | ||
purgeKeys(match = this.cacheKeyPrefix, count = 100) { | ||
const stream = this.client.scanStream({ | ||
match, | ||
count, | ||
}); | ||
let total = 0, | ||
step = 0; | ||
let pipeline = this.client.pipeline(); | ||
return new Promise((resolve, reject) => { | ||
stream.on('data', async(keys = []) => { | ||
step += keys.length; | ||
total += keys.length; | ||
for (let key of keys) pipeline.del(key); | ||
if (step > count) { | ||
await pipeline.exec(); | ||
step = 0; | ||
pipeline = this.client.pipeline(); | ||
this.logger.info('cache purging keys...'); | ||
} | ||
}); | ||
stream.on('end', async _ => { | ||
if (pipeline) await pipeline.exec(); | ||
this.logger.info('cache purged %s keys!', total); | ||
resolve({ | ||
match, | ||
total, | ||
}); | ||
}); | ||
stream.on('error', error => { | ||
this.logger.error('Error purging keys: %s', match); | ||
this.logger.error(error); | ||
reject(error); | ||
}); | ||
}); | ||
} | ||
shouldQueryCache(key, options) { | ||
if (typeof options.forceCacheMiss === 'function') { | ||
return options.forceCacheMiss(key, options); | ||
} | ||
/** | ||
* If we set `forceCacheMiss` to true | ||
* then we should skip cache and fallback | ||
* to our source function. | ||
*/ | ||
return options.forceCacheMiss !== true; | ||
} | ||
get timeUnit() { | ||
@@ -167,0 +256,0 @@ return this.ttlInSeconds ? 'EX' : 'PX'; |
@@ -10,3 +10,3 @@ /*jshint esversion:6, node:true*/ | ||
logger: extend.shim(console), | ||
defaultTtl: (1 * 24 * 60 * 60 * 1000), | ||
defaultTTL: (1 * 24 * 60 * 60 * 1000), | ||
lastError: null, | ||
@@ -18,2 +18,8 @@ ttlInSeconds: false, | ||
cacheKeyPrefix: 'cache:', | ||
tryGetOptions: { | ||
deserialize: true, | ||
addTimestamp: true, | ||
throwOnError: false, | ||
forceCacheMiss: false, | ||
}, | ||
/** | ||
@@ -20,0 +26,0 @@ * Matches the string `cache:` followed |
{ | ||
"name": "core.io-cache-redis", | ||
"version": "0.3.2", | ||
"version": "0.4.0", | ||
"description": "Redis cache module", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
## core.io Cache Redis | ||
This package provides a module for the [core.io](https://npmjs.com/package/core.io) library. | ||
@@ -13,4 +11,41 @@ | ||
### Usage | ||
The `CacheClient` exposes a `tryGet` function that takes a key, a `fallback` function and an options object. | ||
* `key`: Either a string or an object that will be used to create a cache identification key. If key is not found in the cache we call `fallback` and store the functions output in cache using key as identifier. Next time we call `tryGet` we return the cached value. | ||
* `fallback`: Some (expensive) function that we want to cache the outputs of its execution. | ||
**Options**: | ||
* `ttl` default(defaultTTL): Time to live for the key after which the key expires. | ||
* `deserialize` default(`true`): Call `deserialize` on the cached value | ||
* `addTimestamp` default(`true`): Add a time-stamp to the cached value | ||
* `throwOnError` default(`false`): If `true` any errors while calling `fallback` will be thrown, else returned in the value | ||
* `forceCacheMiss` default(`false`): Function or boolean to check if we want to force `fallback` call. | ||
```js | ||
result = await cache.tryGet(query, async _ => { | ||
return await service.query(query); | ||
}); | ||
``` | ||
#### Key Hashing | ||
We can use strings or objects as the raw source for the cache key. If the raw key is an object will be serialized to a string. | ||
Then the create an `md5` hash with the key and prepped the `cacheKeyPrefix`. | ||
By default the `serialize` and `deserialize` functions are mapped to `JSON.stringify` and `JSON.parse` respectively. | ||
If our raw key is the following object: | ||
```js | ||
const query = { limit: 100, order: 'DESC', where: { id: 23 } }; | ||
let key = cache.hashKey(query); | ||
assert(key === 'cache:1239ecd04b073b8f4615d4077be5e263'); | ||
``` | ||
## License | ||
® License by goliatone |
15522
304
51