Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

ttl-mem-cache

Package Overview
Dependencies
Maintainers
1
Versions
19
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ttl-mem-cache - npm Package Compare versions

Comparing version 2.1.0 to 2.2.0

experiment/server.js

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()

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc