ttl-mem-cache
Advanced tools
Comparing version 2.1.0 to 2.2.0
128
lib/cache.js
'use strict'; | ||
const stream = require('readable-stream'); | ||
const stream = require('stream'); | ||
const crypto = require('crypto'); | ||
module.exports = class Cache extends stream.Duplex { | ||
constructor({ maxAge = 5 * 60 * 1000, stale = false, changefeed = false } = {}) { | ||
const _set = Symbol('_set'); | ||
const _del = Symbol('_del'); | ||
module.exports = class TtlMemCache extends stream.Duplex { | ||
constructor({ | ||
maxAge = 5 * 60 * 1000, stale = false, changefeed = false, id = undefined | ||
} = {}) { | ||
super({ | ||
objectMode: true | ||
}); | ||
this.maxAge = maxAge; | ||
this.stale = stale; | ||
this.changefeed = changefeed; | ||
this.store = new Map(); | ||
this.on('set', (obj) => { | ||
Object.defineProperty(this, 'maxAge', { | ||
value: maxAge, | ||
}); | ||
Object.defineProperty(this, 'stale', { | ||
value: stale, | ||
}); | ||
Object.defineProperty(this, 'changefeed', { | ||
value: changefeed, | ||
}); | ||
Object.defineProperty(this, 'store', { | ||
value: new Map(), | ||
}); | ||
Object.defineProperty(this, 'id', { | ||
value: id || crypto.randomBytes(3 * 4).toString('base64'), | ||
}); | ||
this.on('broadcast', (obj, options) => { | ||
if (this._readableState.flowing) { | ||
this.push(obj); | ||
const origin = options.origin ? options.origin : this.id; | ||
this.push({ | ||
key: obj.key, | ||
value: obj.value, | ||
origin, | ||
}); | ||
} | ||
@@ -22,19 +49,15 @@ }); | ||
get(key) { | ||
const item = this.store.get(key); | ||
if (item) { | ||
if (this.constructor._validate(item)) { | ||
return item.value; | ||
} | ||
/** | ||
* Meta | ||
*/ | ||
this.del(key); | ||
if (this.stale) { | ||
return item.value; | ||
} | ||
} | ||
return null; | ||
get [Symbol.toStringTag]() { | ||
return 'TtlMemCache'; | ||
} | ||
set(key, value, maxAge) { | ||
/** | ||
* Private methods | ||
*/ | ||
[_set](key, value, options = {}) { | ||
if (key === null || key === undefined) { | ||
@@ -47,3 +70,3 @@ throw new Error('Argument "key" cannot be null or undefined'); | ||
const expires = this.constructor._calculateExpire((maxAge || this.maxAge)); | ||
const expires = this.constructor._calculateExpire((options.maxAge || this.maxAge)); | ||
@@ -63,2 +86,3 @@ const eventObj = { | ||
this.store.set(key, { value, expires }); | ||
this.emit('broadcast', eventObj, options); | ||
this.emit('set', eventObj); | ||
@@ -68,6 +92,10 @@ return value; | ||
del(key) { | ||
[_del](key, options = {}) { | ||
const item = this.store.get(key); | ||
const success = this.store.delete(key); | ||
if (item) { | ||
this.emit('broadcast', { | ||
key, | ||
value: null | ||
}, options); | ||
this.emit('dispose', key, item.value); | ||
@@ -78,2 +106,32 @@ } | ||
/** | ||
* Public methods | ||
*/ | ||
get(key) { | ||
const item = this.store.get(key); | ||
if (item) { | ||
if (this.constructor._validate(item)) { | ||
return item.value; | ||
} | ||
this.del(key); | ||
if (this.stale) { | ||
return item.value; | ||
} | ||
} | ||
return null; | ||
} | ||
set(key, value, maxAge) { | ||
return this[_set](key, value, { | ||
maxAge | ||
}); | ||
} | ||
del(key) { | ||
return this[_del](key); | ||
} | ||
entries(mutator) { | ||
@@ -141,5 +199,17 @@ const mutate = (typeof mutator === 'function'); | ||
/** | ||
* Stream methods | ||
*/ | ||
_write(obj, enc, next) { | ||
const options = { | ||
origin: obj.origin ? obj.origin : this.id | ||
}; | ||
if (obj.origin === this.id) { | ||
return next(); | ||
} | ||
if (obj.key && obj.value) { | ||
this.set(obj.key, obj.value); | ||
this[_set](obj.key, obj.value, options); | ||
return next(); | ||
@@ -149,3 +219,3 @@ } | ||
if (obj.key && (obj.value === null || obj.value === undefined)) { | ||
this.del(obj.key); | ||
this[_del](obj.key, options); | ||
return next(); | ||
@@ -162,2 +232,6 @@ } | ||
/** | ||
* Static methods | ||
*/ | ||
static _calculateExpire(maxAge = 0) { | ||
@@ -164,0 +238,0 @@ if (maxAge === Infinity) { |
{ | ||
"name": "ttl-mem-cache", | ||
"version": "2.1.0", | ||
"version": "2.2.0", | ||
"description": "A in memory time to live cache with streaming support.", | ||
@@ -22,5 +22,8 @@ "main": "lib/cache.js", | ||
"memcache", | ||
"memory cache", | ||
"ttl", | ||
"time to live", | ||
"performance" | ||
"performance", | ||
"expire cache", | ||
"expiring" | ||
], | ||
@@ -34,12 +37,9 @@ "author": "Trygve Lie", | ||
"devDependencies": { | ||
"eslint": "4.10.0", | ||
"eslint": "4.11.0", | ||
"eslint-config-airbnb-base": "12.1.0", | ||
"eslint-plugin-import": "2.8.0", | ||
"benchmark": "2.1.4", | ||
"lolex": "2.1.3", | ||
"tap": "10.7.2" | ||
}, | ||
"dependencies": { | ||
"readable-stream": "2.3.3" | ||
"lolex": "2.3.0", | ||
"tap": "10.7.3" | ||
} | ||
} |
@@ -58,5 +58,6 @@ # ttl-mem-cache | ||
* maxAge - `Number` - Default max age in milliseconds all items in the cache should be cached before expiering | ||
* maxAge - `Number` - Default max age in milliseconds all items in the cache should be cached before expiering. | ||
* stale - `Boolean` - If expired items in cache should be returned when pruned from the cache. Default: `false`. | ||
* changelog - `Boolean` - If emitted `set` event and stream should contain both old and new value. Default: `false` | ||
* changelog - `Boolean` - If emitted `set` event and stream should contain both old and new value. Default: `false`. | ||
* id - `String` - Give the instanse a unique identifier. Default: `hash` | ||
@@ -70,7 +71,11 @@ If an option Object with a `maxAge` is not provided all items in the cache will by | ||
Pruning of items from the cache happend when they are touched by one of the methods | ||
for retrieving items from the cache. By default pruning happens before the method | ||
returns a value so if an item have expired, `null` will be returned for expired | ||
items. By setting `stale` to `true`, these methods will return the pruned item(s) | ||
before they are removed from the cache. | ||
for retrieving (`.get()` and `.entries()`) items from the cache. By default pruning | ||
happens before the method returns a value so if an item have expired, `null` will be | ||
returned for expired items. By setting `stale` to `true`, these methods will return | ||
the pruned item(s) before they are removed from the cache. | ||
Internally the cache has a unique ID created each time its instantiated. This ID is | ||
used to tell the origin of an cached item when streaming. The `id` will override | ||
this generated ID. When using this, be carefull to not provide the same ID to multiple | ||
instances of the cache. | ||
@@ -294,5 +299,35 @@ | ||
### Linking caches | ||
With the stream API its possible to link caches together and distribute cached items between them. | ||
```js | ||
const Cache = require('../'); | ||
const cacheA = new Cache(); | ||
const cacheB = new Cache(); | ||
const cacheC = new Cache(); | ||
// Link all caches together | ||
cacheA.pipe(cacheB).pipe(cacheC).pipe(cacheA); | ||
// Set a value in cache C | ||
cacheC.set('foo', 'bar'); | ||
// Retrieve the same value from all caches | ||
console.log(cacheA.get('foo'), cacheB.get('foo'), cacheC.get('foo')); | ||
// Delete the value in cache A | ||
cacheA.del('foo'); | ||
// The value is deleted from all caches | ||
console.log(cacheA.get('foo'), cacheB.get('foo'), cacheC.get('foo')); | ||
``` | ||
### Streaming Object type | ||
When writing to the cache, one can control what goes into the cache etc by a dedicated Object type. When reading from the cache, the stream will output the same Object type. | ||
When writing to the cache, one can control what goes into the cache etc by a dedicated Object type. | ||
When reading from the cache, the stream will output the same Object type. | ||
@@ -304,3 +339,4 @@ The Object type looks like this: | ||
key: 'item key', | ||
value: 'item value' | ||
value: 'item value', | ||
origin: 'cache instance ID' | ||
} | ||
@@ -316,2 +352,5 @@ ``` | ||
When the stream emits objects each object will also have a `origin` key. The value is the unique | ||
ID of the instance the object first was emitted on the stream. | ||
If the items you want to store in the cache does not fit your data type, its recommended to use | ||
@@ -318,0 +357,0 @@ a [Transform Stream](https://nodejs.org/api/stream.html#stream_implementing_a_transform_stream) |
@@ -44,2 +44,8 @@ 'use strict'; | ||
tap.test('cache() - object type - should be TtlMemCache', (t) => { | ||
const cache = new Cache(); | ||
t.equal(Object.prototype.toString.call(cache), '[object TtlMemCache]'); | ||
t.end(); | ||
}); | ||
tap.test('cache() - without maxAge - should set default maxAge', (t) => { | ||
@@ -58,4 +64,17 @@ const cache = new Cache(); | ||
tap.test('cache() - without id - should set default id', (t) => { | ||
const cache = new Cache(); | ||
t.ok(cache.id); | ||
t.end(); | ||
}); | ||
tap.test('cache() - with id - should set default id', (t) => { | ||
const id = 'foo'; | ||
const cache = new Cache({ id }); | ||
t.equal(cache.id, id); | ||
t.end(); | ||
}); | ||
/** | ||
@@ -858,4 +877,47 @@ * .set() | ||
tap.test('._write().pipe(_read()) - circular pipe - set item - objects should be set in all caches', (t) => { | ||
const cacheA = new Cache(); | ||
const cacheB = new Cache(); | ||
const cacheC = new Cache(); | ||
const cacheD = new Cache(); | ||
cacheA.pipe(cacheB).pipe(cacheC).pipe(cacheD).pipe(cacheA); | ||
cacheB.set('a', 'foo'); | ||
cacheA.on('set', () => { | ||
t.equal(cacheA.get('a'), 'foo'); | ||
t.equal(cacheB.get('a'), 'foo'); | ||
t.equal(cacheC.get('a'), 'foo'); | ||
t.equal(cacheD.get('a'), 'foo'); | ||
t.end(); | ||
}); | ||
}); | ||
tap.test('._write().pipe(_read()) - circular pipe - del item - objects should be removed from all caches', (t) => { | ||
const cacheA = new Cache(); | ||
const cacheB = new Cache(); | ||
const cacheC = new Cache(); | ||
const cacheD = new Cache(); | ||
cacheA.pipe(cacheB).pipe(cacheC).pipe(cacheD).pipe(cacheA); | ||
cacheB.set('a', 'foo'); | ||
cacheA.on('set', () => { | ||
t.equal(cacheA.get('a'), 'foo'); | ||
t.equal(cacheB.get('a'), 'foo'); | ||
t.equal(cacheC.get('a'), 'foo'); | ||
t.equal(cacheD.get('a'), 'foo'); | ||
cacheD.del('a'); | ||
}); | ||
cacheC.on('dispose', () => { | ||
t.equal(cacheA.get('a'), null); | ||
t.equal(cacheB.get('a'), null); | ||
t.equal(cacheC.get('a'), null); | ||
t.equal(cacheD.get('a'), null); | ||
t.end(); | ||
}); | ||
}); | ||
/** | ||
@@ -862,0 +924,0 @@ * ._calculateExpire() |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
58756
0
11
1105
457
- Removedreadable-stream@2.3.3
- Removedcore-util-is@1.0.3(transitive)
- Removedinherits@2.0.4(transitive)
- Removedisarray@1.0.0(transitive)
- Removedprocess-nextick-args@1.0.7(transitive)
- Removedreadable-stream@2.3.3(transitive)
- Removedsafe-buffer@5.1.2(transitive)
- Removedstring_decoder@1.0.3(transitive)
- Removedutil-deprecate@1.0.2(transitive)