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

zcache

Package Overview
Dependencies
Maintainers
7
Versions
49
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

zcache - npm Package Compare versions

Comparing version 0.2.2 to 0.2.5

lib/InMemoryCache.js

9

index.js
module.exports = {
CacheCluster: require('./lib/CacheCluster'),
CacheInstance: require('./lib/CacheInstance'),
ConnectionPool: require('./lib/ConnectionPool'),
ConnectionWrapper: require('./lib/ConnectionWrapper'),
MemcacheConnection: require('./lib/MemcacheConnection'),
InMemoryCache: require('./lib/InMemoryCache'),
RedisConnection: require('./lib/RedisConnection'),
RedundantCacheGroup: require('./lib/RedundantCacheGroup')
}
}
var events = require('events')
var util = require('util')
var metrics = require('metrics')
var util = require('util')
var Q = require('kew')
function CacheInstance() {}
/**
* A generic cache instance.
* @constructor
*/
function CacheInstance() {
this._stats = {
get: new metrics.Timer,
set: new metrics.Timer,
mget: new metrics.Timer,
mset: new metrics.Timer,
del: new metrics.Timer
}
}
util.inherits(CacheInstance, events.EventEmitter)
/**
* Indicate if this cache instance is available.
*
* @return{boolean}
*/
CacheInstance.prototype.isAvailable = function () {

@@ -11,2 +31,5 @@ throw new Error("isAvailable() must be implemented by any class extending CacheInstance")

/**
* Connect this cache instance.
*/
CacheInstance.prototype.connect = function () {

@@ -16,2 +39,5 @@ throw new Error("connect() must be implemented by any class extending CacheInstance")

/**
* Disconnect this cache instance.
*/
CacheInstance.prototype.disconnect = function () {

@@ -21,2 +47,5 @@ throw new Error("disconnect() must be implemented by any class extending CacheInstance")

/**
* Destroy this cache instance.
*/
CacheInstance.prototype.destroy = function () {

@@ -26,2 +55,9 @@ throw new Error("destroy() must be implemented by any class extending CacheInstance")

/**
* Get the values of multiple keys.
*
* @param {Array.<string>} keys A list of keys
* @return {Promise.<Array.<string>>} The fetched values. For keys that do not exist,
* returns 'undefined's.
*/
CacheInstance.prototype.mget = function (keys) {

@@ -31,2 +67,19 @@ throw new Error("mget() must be implemented by any class extending CacheInstance")

/**
* Set values to multiple keys.
*
* @param {Array.<{key: string, value: string}} keys Key-value pairs to set.
* @param {number=} maxAgeMs The living time of keys, in milliseconds.
* @return {Promise}
*/
CacheInstance.prototype.mset = function (keys, maxAgeMs) {
throw new Error("mget() must be implemented by any class extending CacheInstance")
}
/**
* Get the value of a given key.
*
* @param {string} key The key to get value of.
* @return {Promise.<string>} The fetched value. Returns 'undefined' if the doesn't exist.
*/
CacheInstance.prototype.get = function (key) {

@@ -36,2 +89,10 @@ throw new Error("get() must be implemented by any class extending CacheInstance")

/**
* Set a key.
*
* @param {string} key
* @param {string} value
* @param {number=} maxAgeMs The living time of this key, in milliseconds.
* @return {Promise}
*/
CacheInstance.prototype.set = function (key, val, maxAgeMs) {

@@ -41,2 +102,8 @@ throw new Error("set() must be implemented by any class extending CacheInstance")

/**
* Delete a key.
*
* @param {string} key
* @param {Promise}
*/
CacheInstance.prototype.del = function (key) {

@@ -46,2 +113,52 @@ throw new Error("del() must be implemented by any class extending CacheInstance")

module.exports = CacheInstance
/**
* Get service information about this cache instance if it is a standlone service.
*
* @param {string} key
* @param {Promise.<ServerInfo>} A promise that returns the server information. Returns
* null if the server info is not available.
*/
CacheInstance.prototype.getServerInfo = function (key) {
return Q.resolve(null)
}
/**
* Update the stats of a certain operation on this cache.
*
* @param {string} op The name of the operation, e.g., get, set, etc.
* @return {Function} A function that updates the stats and returns the parameter
* that is passed into it. It is handy to chain to a promise.
*/
CacheInstance.prototype.updateStats = function(op) {
var startTime = Date.now()
var self = this
return function (result) {
if (self._stats[op]) self._stats[op].update(Date.now() - startTime)
return result
}
}
/**
* Get the stats of a certain operation.
*
* @param {string} op The name of the operation, e.g., get, set, etc.
* @return {metrics.Timer}
*/
CacheInstance.prototype.getStats = function (op) {
return this._stats[op]
}
/**
* Get the stats of a certain operation in a human-readable format.
*
* @param {string} op The name of the operation, e.g., get, set, etc.
* @return {string}
*/
CacheInstance.prototype.getPrettyStatsString = function (op) {
var m = this.getStats(op)
return util.format('%d ops min/max/avg %d/%d/%d 1min/5min/15min %d/%d/%d',
m.count(), m.min(), m.max(), m.mean(),
m.oneMinuteRate(), m.fiveMinuteRate(), m.fifteenMinuteRate())
}
module.exports = CacheInstance

@@ -6,3 +6,12 @@ var redis = require('redis')

var CacheInstance = require('./CacheInstance')
var ServerInfo = require('./ServerInfo')
/**
* A connection to a Redis server.
*
* @constructor
* @param {string} host The host that runs the redis-server
* @param {string} port The port that the redis-server listens to
* @implements {CacheInstance}
*/
function RedisConnection(host, port) {

@@ -13,6 +22,4 @@ CacheInstance.call(this)

this._client = null
this._host = host || null
this._port = port || null
this._bound_onConnect = this._onConnect.bind(this)

@@ -24,2 +31,3 @@ this._bound_onError = this._onError.bind(this)

/** @inheritDoc */
RedisConnection.prototype.isAvailable = function () {

@@ -29,10 +37,12 @@ return this._isAvailable

/** @inheritDoc */
RedisConnection.prototype.set = function (key, val, maxAgeMs) {
var deferred = Q.defer()
this._client.setex(key, Math.floor(maxAgeMs / 1000), val, deferred.makeNodeResolver())
return deferred.promise
.fail(warnOnError)
.then(this.updateStats('set'))
}
/** @inheritDoc */
RedisConnection.prototype.mset = function (items, maxAgeMs) {

@@ -53,17 +63,22 @@ var deferred = Q.defer()

.fail(warnOnError)
.then(this.updateStats('mset'))
}
/** @inheritDoc */
RedisConnection.prototype.del = function (key) {
var deferred = Q.defer()
this._client.del(key, deferred.makeNodeResolver())
return deferred.promise
.fail(warnOnError)
.then(this.updateStats('del'))
}
/** @inheritDoc */
RedisConnection.prototype.get = function (key) {
return this.mget([key])
.then(returnFirstResult)
.then(this.updateStats('get'))
}
/** @inheritDoc */
RedisConnection.prototype.mget = function (keys) {

@@ -73,7 +88,34 @@ if (!keys || !keys.length) return Q.resolve([])

var deferred = Q.defer()
var args = keys.concat(deferred.makeNodeResolver())
this._client.mget.apply(this._client, args)
this._client.mget(keys, deferred.makeNodeResolver())
return deferred.promise
.then(this.updateStats('mget'))
}
/** @inheritDoc */
RedisConnection.prototype.getServerInfo = function () {
var deferred = Q.defer()
this._client.info(deferred.makeNodeResolver())
return deferred.promise
.then(function (infoCmdOutput) {
var items = {}
infoCmdOutput.split('\n')
.filter(function(str) {return str.indexOf(':') > 0})
.map(function(str) {return str.trim().split(':')})
.map(function(item) {items[item[0]] = item[1]})
var serverInfo = new ServerInfo
try {
serverInfo.memoryBytes = parseInt(items['used_memory'], 10)
serverInfo.memoryRssBytes = parseInt(items['used_memory_rss'], 10)
serverInfo.evictedKeys = parseInt(items['evicted_keys'], 10)
serverInfo.numOfConnections = parseInt(items['connected_clients'], 10)
// The db0 key's value is something like: 'keys=12,expires=20'
serverInfo.numOfKeys = parseInt(items['db0'].split(',')[0].split('=')[1], 10)
} catch (e) {
Q.reject('Malformatted output from the "INFO" command of Redis')
}
return Q.resolve(serverInfo)
})
}
/** @inheritDoc */
RedisConnection.prototype.disconnect = function () {

@@ -85,2 +127,3 @@ this._isAvailable = false

/** @inheritDoc */
RedisConnection.prototype.destroy = function () {

@@ -92,2 +135,3 @@ this.disconnect()

/** @inheritDoc */
RedisConnection.prototype.connect = function () {

@@ -94,0 +138,0 @@ if (this._isAvailable) return

@@ -27,8 +27,13 @@ var util = require('util')

RedundantCacheGroup.prototype._getAvailableInstance = function () {
for (var i = 0; i < this._cacheInstances.length; i++) {
RedundantCacheGroup.prototype._getAvailableInstance = function (start) {
var instanceIndex = this._getIndexOfFirstAvailableInstance(start)
if (this._cacheInstances[instanceIndex]) return this._cacheInstances[instanceIndex].instance
return null
}
RedundantCacheGroup.prototype._getIndexOfFirstAvailableInstance = function (start) {
for (var i = (start || 0); i < this._cacheInstances.length; i++) {
var instance = this._cacheInstances[i].instance
if (instance.isAvailable()) {
return instance
}
if (instance.isAvailable()) return i
}

@@ -75,20 +80,46 @@ return null

RedundantCacheGroup.prototype.mget = function (keys) {
var instance = this._getAvailableInstance()
if (!instance) return Q.resolve([])
var instanceIndex = this._getIndexOfFirstAvailableInstance()
if (instanceIndex == null) return Q.resolve([])
return instance.mget(keys)
// try the first cache
var ret = this._getAvailableInstance(instanceIndex).mget(keys)
var nextInstance = this._getAvailableInstance(instanceIndex + 1)
return ret.then(function (res) {
for (var i = 0; i < res.length; i++) {
if (!res[i] && nextInstance) {
var rollUp = []
while (!res[i] && i < res.length) rollUp.push(keys[i++])
nextInstance.mget(rollUp)
.then(function (data) {
for (var k = 0; k < data.length; k++) res[k] = data[k]
})
}
}
return res
}.bind(this))
}
RedundantCacheGroup.prototype.get = function (key) {
var instance = this._getAvailableInstance()
if (!instance) return Q.resolve(undefined)
var instanceIndex = this._getIndexOfFirstAvailableInstance()
return instance.get(key)
for (var i = instanceIndex, instance; instance = this._getAvailableInstance(i); i++) {
var ret = instance.get(key)
if (ret) return Q.resolve(ret)
}
return Q.resolve(undefined)
}
RedundantCacheGroup.prototype.mset = function (items, maxAgeMs) {
var instance = this._getAvailableInstance()
if (!instance) return Q.resolve(undefined)
var instances = this._getAllInstances()
var promises = []
return instance.mset(items, maxAgeMs)
for (var i = 0; i < instances.length; i++) {
promises.push(instances[i].mset(items, maxAgeMs))
}
return Q.all(promises)
.then(returnTrue)
}

@@ -99,2 +130,3 @@

var promises = []
for (var i = 0; i < instances.length; i++) {

@@ -128,2 +160,2 @@ promises.push(instances[i].set(key, val, maxAgeMs))

module.exports = RedundantCacheGroup
module.exports = RedundantCacheGroup
{
"name": "zcache"
, "description": "AWS zone-aware caching"
, "version": "0.2.2"
, "homepage": "https://github.com/azulus/zcache"
, "authors": [
"Jeremy Stanley <github@azulus.com> (https://github.com/azulus)"
]
, "contributors": [
]
, "keywords": ["zcache"]
, "main": "lib/zcache.js"
, "repository": {
"type": "git"
, "url": "https://github.com/azulus/zcache.git"
"name": "zcache",
"description": "AWS zone-aware multi-layer cache",
"version": "0.2.5",
"homepage": "https://github.com/Obvious/zcache",
"authors": [
"Jeremy Stanley <github@azulus.com> (https://github.com/azulus)",
"Artem Titoulenko <artem@medium.com> (https://github.com/ArtemTitoulenko)",
"Xiao Ma <x@medium.com> (https://github.com/x-ma)"
],
"keywords": ["zcache", "cache", "redis"],
"main": "lib/zcache.js",
"repository": {
"type": "git",
"url": "https://github.com/Obvious/zcache.git"
},
"dependencies": {
"node-memcache-parser-obvfork": "0.1.1",
"generic-pool": "2.0.3",
"kew": "0.1.7",
"redis": "0.8.2",
"metrics": "0.1.6"
},
"devDependencies": {
"nodeunit": "0.7.4"
},
"scripts": {
"test": "./node_modules/nodeunit/bin/nodeunit test"
}
, "dependencies": {
"node-memcache-parser-obvfork": "0.1.1",
"generic-pool": "2.0.3",
"kew": "0.1.7",
"redis": "0.8.2"
}
, "devDependencies": {
"nodeunit": "0.7.4"
}
, "scripts": {
"test": "./node_modules/nodeunit/bin/nodeunit test"
}
}
var zcache = require('../index')
var ServerInfo = require('../lib/ServerInfo')
var Q = require('kew')
exports.testRedisConnection = function (test) {
var cacheInstance = new zcache.RedisConnection("localhost", 6379)
var cacheInstance = new zcache.RedisConnection('localhost', 6379)
test.equal(cacheInstance.isAvailable(), false, "Connection should not be available")
test.equal(cacheInstance.isAvailable(), false, 'Connection should not be available')

@@ -12,3 +13,3 @@ cacheInstance.on('connect', function () {

test.equal(cacheInstance.isAvailable(), true, "Connection should be available")
test.equal(cacheInstance.isAvailable(), true, 'Connection should be available')

@@ -62,5 +63,14 @@ cacheInstance.set('abc', '123', 300000)

.then(function (vals) {
test.equal(vals.length, 2, "Should have precisely 2 results")
test.equal(vals.length, 2, 'Should have precisely 2 results')
test.equal(vals[0], '456')
test.equal(vals[1], '789')
test.equal(1, cacheInstance.getStats('set').count(), 'set() is called for once')
test.equal(1, cacheInstance.getStats('mset').count(), 'mset() is called for once')
test.equal(0, cacheInstance.getStats('get').count(), 'get() is not called')
test.equal(3, cacheInstance.getStats('mget').count(), 'mget() is called for three times')
test.equal(1, cacheInstance.getStats('del').count(), 'del() is call for once')
return cacheInstance.getServerInfo()
})
.then(function (info) {
test.ok(info instanceof ServerInfo, 'The returned object should be a ServerInfo')
cacheInstance.destroy()

@@ -76,3 +86,3 @@ })

cacheInstance.on('destroy', function () {
test.equal(cacheInstance.isAvailable(), false, "Connection should not be available")
test.equal(cacheInstance.isAvailable(), false, 'Connection should not be available')
test.done()

@@ -79,0 +89,0 @@ })

var zcache = require('../index')
var Q = require('kew')
exports.testRedundantCacheGroup = function (test) {
var memcacheCluster = new zcache.CacheCluster({
create: function (uri, opts, callback) {
var parts = uri.split(':')
var host = parts[0]
var port = parseInt(parts[1], 10)
module.exports = {
setUp: function (callback) {
this.memoryInstance1 = new zcache.InMemoryCache()
this.memoryInstance1.connect()
this.memoryInstance2 = new zcache.InMemoryCache()
this.memoryInstance2.connect()
var poolInstance = new zcache.ConnectionPool({
create: function (callback) {
var wrappedCacheInstance = new zcache.MemcacheConnection(host, port)
var wrapperCacheInstance = new zcache.ConnectionWrapper(wrappedCacheInstance)
wrapperCacheInstance.on('connect', function () {
callback(null, wrapperCacheInstance)
})
this.cacheInstance = new zcache.RedundantCacheGroup()
this.cacheInstance.add(this.memoryInstance1, 2)
this.cacheInstance.add(this.memoryInstance2, 1)
callback()
},
wrapperCacheInstance.connect()
},
destroy: function (client) {
client.destroy()
}
})
tearDown: function (callback) {
if (this.cacheInstance) this.cacheInstance.destroy()
callback()
},
poolInstance.on('connect', function () {
callback(null, poolInstance)
})
testCacheWriteThrough: function (test) {
this.cacheInstance.set('foo', 'bar', 10000)
test.equal(this.memoryInstance1.get('foo'), 'bar', 'bar should be in memory')
test.equal(this.memoryInstance2.get('foo'), 'bar', 'bar should be in memory')
test.done()
},
poolInstance.connect()
}
})
memcacheCluster.setNodeCapacity('localhost:11212', 5, {memcache: true}, 0)
memcacheCluster.setNodeCapacity('localhost:11213', 5, {memcache: true}, 0)
testCacheGetStagger: function (test) {
this.cacheInstance.set('foo', 'bar', 10000)
this.memoryInstance1.del('foo')
test.equal(this.memoryInstance1._data['foo'], undefined, 'foo should have been deleted in memoryInstance1')
test.equal(this.memoryInstance2._data['foo'], 'bar', 'foo should still be in memoryInstance2')
var redisInstance = new zcache.RedisConnection('localhost', 6379)
this.cacheInstance.get('foo')
.then(function (data) {
test.equal(data, 'bar', 'bar should have been read from memoryInstance2')
test.done()
})
},
var cacheInstance = new zcache.RedundantCacheGroup()
cacheInstance.add(redisInstance, 1)
cacheInstance.add(memcacheCluster, 2)
testCacheMgetStagger: function (test) {
this.cacheInstance.set('foo', 'bar', 10000)
this.cacheInstance.set('fah', 'baz', 10000)
this.memoryInstance1.del('foo')
test.equal(this.memoryInstance1._data['foo'], undefined, 'foo should have been deleted in memoryInstance1')
test.equal(this.memoryInstance2._data['foo'], 'bar', 'foo should still be in memoryInstance2')
test.equal(cacheInstance.isAvailable(), false, "Connection should not be available")
this.cacheInstance.mget(['foo', 'fah'])
.then(function (results) {
test.equal(results[0], 'bar', 'bar should have been returned')
test.equal(results[1], 'baz', 'baz should have been returned')
test.done()
})
},
cacheInstance.on('connect', function () {
cacheInstance.removeAllListeners('connect')
testCacheMsetStagger: function (test) {
this.cacheInstance.mset([{key: 'foo', value: 'bar'}, {key: 'fah', value: 'bah'}])
var defer = Q.defer()
setTimeout(function () {
defer.resolve(true)
}, 100)
test.equal(this.memoryInstance1._data['foo'], 'bar', 'bar should be in memoryInstance1')
test.equal(this.memoryInstance2._data['foo'], 'bar', 'bar should be in memoryInstance2')
defer
.then(function () {
test.equal(cacheInstance.isAvailable(), true, "Connection should be available")
test.equal(this.memoryInstance1._data['fah'], 'bah', 'bah should be in memoryInstance1')
test.equal(this.memoryInstance2._data['fah'], 'bah', 'bah should be in memoryInstance2')
var promises = []
promises.push(cacheInstance.set('abc', '123', 300000))
promises.push(cacheInstance.set('def', '456', 300000))
promises.push(cacheInstance.set('ghi', '789', 300000))
promises.push(cacheInstance.set('jkl', '234', 300000))
promises.push(cacheInstance.set('mno', '567', 300000))
test.done()
},
return Q.all(promises)
testCacheDelStagger: function (test) {
this.cacheInstance.set('foo', 'bar', 10000)
this.cacheInstance.get('foo')
.then(function (data) {
test.equal(data, 'bar', 'bar should be in the cache')
})
.then(function () {
return cacheInstance.mget(['abc', 'def', 'ghi', 'jkl', 'mno'])
})
.then(function (vals) {
test.equal(vals[0], '123')
test.equal(vals[1], '456')
test.equal(vals[2], '789')
test.equal(vals[3], '234')
test.equal(vals[4], '567')
this.cacheInstance.del('foo')
//disconnect the memcache cluster
memcacheCluster.disconnect()
// wait to ensure disconnection
var defer = Q.defer()
setTimeout(function () {
defer.resolve(true)
}, 100)
return defer.promise
})
.then(function () {
return cacheInstance.del('abc')
})
.then(function () {
return cacheInstance.mget(['abc'])
})
.then(function (vals) {
test.equal(vals[0], undefined)
// reconnect the memcache cluster
memcacheCluster.connect()
// wait to ensure reconnection
var defer = Q.defer()
setTimeout(function () {
defer.resolve(true)
}, 200)
return defer.promise
})
.then(function () {
return cacheInstance.mget(['abc'])
})
.then(function (val) {
test.equal(val, '123')
cacheInstance.destroy()
})
.fail(function (e) {
console.error(e)
test.fail(e.message, e.stack)
this.cacheInstance.get('foo')
.then(function (data) {
test.equal(data, undefined, 'bar should not be in the cache')
test.done()
})
})
cacheInstance.on('destroy', function () {
test.equal(cacheInstance.isAvailable(), false, "Connection should not be available")
test.done()
})
cacheInstance.connect()
}
}
}
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