Comparing version 0.0.5 to 0.1.0
@@ -1,3 +0,2 @@ | ||
var sys = require( 'sys' ), | ||
nMemcached = require( '../' ), | ||
var nMemcached = require( '../' ), | ||
assert = require('assert'), | ||
@@ -9,11 +8,11 @@ memcached; | ||
memcached.set( "hello_world", "greetings from planet node", 1000, function( err, success ){ | ||
// check if the data was stored | ||
assert.equal( success, true, "Successfully stored data" ) | ||
memcached.get( "hello_world", function( err, success ){ | ||
assert.equal( success, "greetings from planet node", "Failed to fetched data" ) | ||
sys.puts( success ); | ||
process.stdout.write( success ); | ||
memcached.end() | ||
}); | ||
}); | ||
}); |
@@ -1,7 +0,6 @@ | ||
var sys = require( 'sys' ), | ||
HashRing = require( '../lib/hashring' ).HashRing; | ||
var HashRing = require( '../lib/hashring' ).HashRing; | ||
var Ring = new HashRing( | ||
[ '192.168.0.102:11212', '192.168.0.103:11212', '192.168.0.104:11212' ], | ||
// Weights are optional, but can be usefull if you have memcached server that can use allot of memory | ||
@@ -16,10 +15,10 @@ { | ||
// Return the server based on the key | ||
sys.puts( Ring.getNode( "my-super-secret-cache-key" ) ); | ||
sys.puts( Ring.getNode( "hello-world" ) ); | ||
sys.puts( Ring.getNode( "my-super-secret-cache-key" ) ); | ||
process.stdout.write( Ring.getNode( "my-super-secret-cache-key" ) ); | ||
process.stdout.write( Ring.getNode( "hello-world" ) ); | ||
process.stdout.write( Ring.getNode( "my-super-secret-cache-key" ) ); | ||
// Different algorithms produce different hash maps. So choose wisely | ||
// Different algorithms produce different hash maps. So choose wisely | ||
var sha1Ring = new HashRing( | ||
[ '192.168.0.102:11212', '192.168.0.103:11212', '192.168.0.104:11212' ], | ||
// Weights are optional, but can be usefull if you have memcached server that can use allot of memory | ||
@@ -30,9 +29,9 @@ { | ||
'192.168.0.104:11212': 1 | ||
}, | ||
}, | ||
'sha1' // optional algorithm for key hashing | ||
); | ||
sys.puts( sha1Ring.getNode( "my-super-secret-cache-key" ) ); | ||
sys.puts( sha1Ring.getNode( "hello-world" ) ); | ||
sys.puts( sha1Ring.getNode( "my-super-secret-cache-key" ) ); | ||
process.stdout.write( sha1Ring.getNode( "my-super-secret-cache-key" ) ); | ||
process.stdout.write( sha1Ring.getNode( "hello-world" ) ); | ||
process.stdout.write( sha1Ring.getNode( "my-super-secret-cache-key" ) ); |
@@ -1,6 +0,5 @@ | ||
var sys = require( 'sys' ), | ||
nMemcached = require( '../' ), | ||
var nMemcached = require( '../' ), | ||
memcached; | ||
// connect to our memcached server on host 10.211.55.5, port 11211 | ||
memcached = new nMemcached( "10.211.55.5:11211" ); | ||
memcached = new nMemcached( "10.211.55.5:11211" ); |
@@ -1,4 +0,4 @@ | ||
var sys = require( 'sys' ), | ||
nMemcached = require( '../' ), | ||
memcached; | ||
var nMemcached = require( '../' ), | ||
memcached1, | ||
memcached2; | ||
@@ -11,3 +11,3 @@ // Set a global configuration | ||
sys.puts( memcached1.poolSize ); // 25 | ||
sys.puts( memcached2.poolSize == memcached1.poolSize ); // true | ||
process.stdout.write( memcached1.poolSize ); // 25 | ||
process.stdout.write( memcached2.poolSize == memcached1.poolSize ); // true |
@@ -1,4 +0,5 @@ | ||
var sys = require( 'sys' ), | ||
nMemcached = require( '../' ), | ||
memcached; | ||
var nMemcached = require( '../' ), | ||
memcached1, | ||
memcached2, | ||
memcached3; | ||
@@ -14,4 +15,4 @@ // Set a global configuration: | ||
// test the output | ||
sys.puts( memcached1.poolSize ); // 25 | ||
sys.puts( memcached2.poolSize == memcached1.poolSize ); // true | ||
sys.puts( memcached3.poolSize == 35 ); // true | ||
process.stdout.write( memcached1.poolSize ); // 25 | ||
process.stdout.write( memcached2.poolSize == memcached1.poolSize ); // true | ||
process.stdout.write( memcached3.poolSize == 35 ); // true |
@@ -0,3 +1,5 @@ | ||
"use strict"; | ||
var EventEmitter = require('events').EventEmitter | ||
, Spawn = require('child_process').spawn | ||
, spawn = require('child_process').spawn | ||
, Utils = require('./utils'); | ||
@@ -9,58 +11,58 @@ | ||
function ping(host, callback){ | ||
var pong = Spawn('ping', [host]); | ||
pong.stdout.on('data', function(data) { | ||
function ping (host, callback) { | ||
var pong = spawn('ping', [host]); | ||
pong.stdout.on('data', function stdoutdata (data) { | ||
callback(false, data.toString().split('\n')[0].substr(14)); | ||
pong.kill(); | ||
}); | ||
pong.stderr.on('data', function(data) { | ||
pong.stderr.on('data', function stderrdata (data) { | ||
callback(data.toString().split('\n')[0].substr(14), false); | ||
pong.kill(); | ||
}); | ||
}; | ||
} | ||
function IssueLog(args){ | ||
function IssueLog (args) { | ||
this.config = args; | ||
this.messages = []; | ||
this.failed = false; | ||
this.totalRetries = 0; | ||
this.totalReconnectsAttempted = 0; | ||
this.totalReconnectsSuccess = 0; | ||
Utils.merge(this, args); | ||
EventEmitter.call(this); | ||
}; | ||
} | ||
var issues = IssueLog.prototype = new EventEmitter; | ||
issues.log = function(message){ | ||
issues.log = function log (message) { | ||
var issue = this; | ||
this.failed = true; | ||
this.messages.push(message || 'No message specified'); | ||
if (this.retries){ | ||
setTimeout(Utils.curry(issue, issue.attemptRetry), this.retry); | ||
if (this.retries) { | ||
setTimeout(issue.attemptRetry.bind(issue), this.retry); | ||
return this.emit('issue', this.details); | ||
} | ||
if (this.remove) return this.emit('remove', this.details) | ||
setTimeout(Utils.curry(issue, issue.attemptReconnect), this.reconnect); | ||
if (this.remove) return this.emit('remove', this.details); | ||
setTimeout(issue.attemptReconnect.bind(issue), this.reconnect); | ||
}; | ||
Object.defineProperty(issues, 'details', { | ||
get: function(){ | ||
get: function getDetails () { | ||
var res = {}; | ||
res.server = this.serverAddress; | ||
res.tokens = this.tokens; | ||
res.messages = this.messages; | ||
if (this.retries){ | ||
if (this.retries) { | ||
res.retries = this.retries; | ||
res.totalRetries = this.totalRetries | ||
res.totalRetries = this.totalRetries; | ||
} else { | ||
@@ -72,3 +74,3 @@ res.totalReconnectsAttempted = this.totalReconnectsAttempted; | ||
} | ||
return res; | ||
@@ -78,3 +80,3 @@ } | ||
issues.attemptRetry = function(){ | ||
issues.attemptRetry = function attemptRetry () { | ||
this.totalRetries++; | ||
@@ -85,21 +87,21 @@ this.retries--; | ||
issues.attemptReconnect = function(){ | ||
issues.attemptReconnect = function attemptReconnect () { | ||
var issue = this; | ||
this.totalReconnectsAttempted++; | ||
this.emit('reconnecting', this.details); | ||
// Ping the server | ||
ping(this.tokens[1], function(err){ | ||
ping(this.tokens[1], function pingpong (err) { | ||
// still no access to the server | ||
if (err){ | ||
this.messages.push(message || 'No message specified'); | ||
return setTimeout(Utils.curry(issue, issue.attemptReconnect), issue.reconnect); | ||
if (err) { | ||
this.messages.push(err.message || 'No message specified'); | ||
return setTimeout(issue.attemptReconnect.bind(issue), issue.reconnect); | ||
} | ||
issue.emit('reconnected', issue.details); | ||
issue.totalReconnectsSuccess++; | ||
issue.messages.length = 0; | ||
issue.failed = false; | ||
// we connected again, so we are going through the whole cycle again | ||
@@ -110,3 +112,3 @@ Utils.merge(issue, JSON.parse(JSON.stringify(issue.config))); | ||
function ConnectionManager(name, limit, constructor){ | ||
function ConnectionManager (name, limit, constructor) { | ||
this.name = name; | ||
@@ -116,53 +118,60 @@ this.total = limit; | ||
this.connections = []; | ||
}; | ||
} | ||
var Manager = ConnectionManager.prototype; | ||
Manager.allocate = function(callback){ | ||
var total | ||
, i = total = this.connections.length | ||
Manager.allocate = function allocate (callback) { | ||
var total, i | ||
, Manager = this; | ||
i = total = this.connections.length; | ||
// check for available | ||
while(i--){ | ||
if (this.isAvailable(this.connections[i])){ | ||
while (i--){ | ||
if (this.isAvailable(this.connections[i])) { | ||
return callback(false, this.connections[i]); | ||
} | ||
} | ||
// create new | ||
if (total < this.total){ | ||
if (total < this.total) { | ||
return this.connections.push(this.factory.apply(this, arguments)); | ||
} | ||
// wait untill the next event loop tick, to try again | ||
process.nextTick(function(){Manager.allocate(callback)}); | ||
// give up and don't saturate the node.js process by retying #43 | ||
var full = new Error("All the connections in the memcached pool are busy"); | ||
full.connectionPool = true; | ||
callback(full); | ||
}; | ||
Manager.isAvailable = function(connection){ | ||
Manager.isAvailable = function isAvailable (connection) { | ||
var readyState = connection.readyState; | ||
return (readyState == 'open' || readyState == 'writeOnly') && !(connection._writeQueue && connection._writeQueue.length); | ||
return (readyState === 'open' || readyState === 'writeOnly') | ||
&& !(connection._writeQueue && connection._writeQueue.length) | ||
&& !(connection._handle && connection._handle.writeQueueSize); | ||
}; | ||
Manager.remove = function(connection){ | ||
Manager.remove = function remove (connection) { | ||
var position = this.connections.indexOf(connection); | ||
if (position !== -1) this.connections.splice(position, 1); | ||
if (connection.readyState && connection.readyState !== 'closed' && connection.end) connection.end(); | ||
if (connection.readyState && connection.readyState !== 'closed' && connection.end) { | ||
connection.end(); | ||
} | ||
}; | ||
Manager.free = function(keep){ | ||
Manager.free = function freemymemories (keep) { | ||
var save = 0 | ||
, connection; | ||
while(this.connections.length){ | ||
while (this.connections.length) { | ||
connection = this.connections.shift(); | ||
if(save < keep && this.isAvailable(this.connection[0])){ | ||
save++ | ||
if (save < keep && this.isAvailable(this.connection[0])) { | ||
save++; | ||
continue; | ||
} | ||
this.remove(connection); | ||
} | ||
}; |
@@ -0,5 +1,15 @@ | ||
"use strict"; | ||
/** | ||
* Node's native modules | ||
*/ | ||
var EventEmitter = require('events').EventEmitter | ||
, Stream = require('net').Stream | ||
, Buffer = require('buffer').Buffer; | ||
, Socket = require('net').Socket; | ||
/** | ||
* External or custom modules | ||
*/ | ||
var HashRing = require('hashring') | ||
@@ -12,2 +22,8 @@ , Connection = require('./connection') | ||
/** | ||
* Variable lookups | ||
*/ | ||
var curry = Utils.curry; | ||
/** | ||
* Constructs a new memcached client | ||
@@ -21,24 +37,27 @@ * | ||
function Client(args, options){ | ||
if(!(this && this.hasOwnProperty && (this instanceof Client))) this = new Client(); | ||
function Client (args, options) { | ||
var servers = [] | ||
, weights = {} | ||
, regular = 'localhost:11211' | ||
, key; | ||
// Parse down the connection arguments | ||
switch (Object.prototype.toString.call(args)){ | ||
case '[object String]': | ||
servers.push(args); | ||
break; | ||
// Parse down the connection arguments | ||
switch (Object.prototype.toString.call(args)) { | ||
case '[object Object]': | ||
weights = args; | ||
servers = Object.keys(args); | ||
break; | ||
case '[object Array]': | ||
servers = args.length ? args : [regular]; | ||
break; | ||
default: | ||
servers = args; | ||
servers.push(args || regular); | ||
break; | ||
} | ||
if (!servers.length) throw new Error('No servers where supplied in the arguments'); | ||
if (!servers.length) { | ||
throw new Error('No servers where supplied in the arguments'); | ||
} | ||
@@ -54,3 +73,3 @@ // merge with global and user config | ||
this.issues = []; | ||
}; | ||
} | ||
@@ -63,3 +82,3 @@ // Allows users to configure the memcached globally or per memcached client | ||
, algorithm: 'crc32' // hashing algorithm that is used for key mapping | ||
, algorithm: 'crc32' // hashing algorithm that is used for key mapping | ||
@@ -78,33 +97,49 @@ , poolSize: 10 // maximal parallel connections | ||
// There some functions we don't want users to touch so we scope them | ||
(function(nMemcached){ | ||
const LINEBREAK = '\r\n' | ||
, NOREPLY = ' noreply' | ||
, FLUSH = 1E3 | ||
, BUFFER = 1E2 | ||
, CONTINUE = 1E1 | ||
, FLAG_JSON = 1<<1 | ||
, FLAG_BINARY = 2<<1; | ||
(function (nMemcached) { | ||
var LINEBREAK = '\r\n' | ||
, NOREPLY = ' noreply' | ||
, FLUSH = 1E3 | ||
, BUFFER = 1E2 | ||
, CONTINUE = 1E1 | ||
, FLAG_JSON = 1<<1 | ||
, FLAG_BINARY = 1<<2 | ||
, FLAG_NUMERIC = 1<<3; | ||
var memcached = nMemcached.prototype = new EventEmitter | ||
, private = {} | ||
, privates = {} | ||
, undefined; | ||
// Creates or generates a new connection for the give server, the callback will receive the connection | ||
// if the operation was successful | ||
memcached.connect = function connect(server, callback){ | ||
// Creates or generates a new connection for the give server, the callback | ||
// will receive the connection if the operation was successful | ||
memcached.connect = function connect (server, callback) { | ||
// server is dead, bail out | ||
if (server in this.issues && this.issues[server].failed) return callback(false, false); | ||
if (server in this.issues && this.issues[server].failed) { | ||
return callback(false, false); | ||
} | ||
// fetch from connection pool | ||
if (server in this.connections) return this.connections[server].allocate(callback); | ||
if (server in this.connections) { | ||
return this.connections[server].allocate(callback); | ||
} | ||
// No connection factory created yet, so we must build one | ||
var serverTokens = /(.*):(\d+){1,}$/.exec(server).reverse() | ||
var serverTokens = server[0] === '/' | ||
? server | ||
: /(.*):(\d+){1,}$/.exec(server).reverse() | ||
, memcached = this; | ||
serverTokens.pop(); | ||
// Pop original string from array | ||
if (Array.isArray(serverTokens)) serverTokens.pop(); | ||
var sid = 0; | ||
this.connections[server] = new Manager(server, this.poolSize, function(callback){ | ||
var S = new Stream | ||
var sid = 0 | ||
, manager; | ||
/** | ||
* Generate a new connection pool manager. | ||
*/ | ||
manager = new Manager(server, this.poolSize, function factory (callback) { | ||
var S = Array.isArray(serverTokens) | ||
? new Stream | ||
: new Socket | ||
, Manager = this; | ||
@@ -116,2 +151,3 @@ | ||
S.setNoDelay(true); | ||
S.setEncoding('utf8'); | ||
S.metaData = []; | ||
@@ -121,3 +157,3 @@ S.responseBuffer = ""; | ||
S.serverAddress = server; | ||
S.tokens = serverTokens; | ||
S.tokens = [].concat(serverTokens); | ||
S.memcached = memcached; | ||
@@ -127,22 +163,33 @@ | ||
Utils.fuse(S, { | ||
connect: function streamConnect(){ callback(false, this) } | ||
, close: function streamClose(){ Manager.remove(this) } | ||
, error: function streamError(err){ memcached.connectionIssue(err, S, callback) } | ||
, data: Utils.curry(memcached, private.buffer, S) | ||
, timeout: function streamTimeout(){ Manager.remove(this) } | ||
, end: S.end | ||
connect: function streamConnect () { | ||
callback(false, this); | ||
} | ||
, close: function streamClose () { | ||
Manager.remove(this); | ||
} | ||
, error: function streamError (err) { | ||
memcached.connectionIssue(err, S, callback); | ||
} | ||
, data: curry(memcached, privates.buffer, S) | ||
, timeout: function streamTimeout () { | ||
Manager.remove(this); | ||
} | ||
, end: S.end | ||
}); | ||
// connect the net.Stream [port, hostname] | ||
S.connect.apply(S, serverTokens); | ||
// connect the net.Stream (or net.Socket) [port, hostname] | ||
S.connect.apply(S, S.tokens); | ||
return S; | ||
}); | ||
// now that we have setup our connection factory we can allocate a new connection | ||
this.connections[server] = manager; | ||
// now that we have setup our connection factory we can allocate a new | ||
// connection | ||
this.connections[server].allocate(callback); | ||
}; | ||
// Creates a multi stream, so it's easier to query agains | ||
// multiple memcached servers. | ||
memcached.multi = function memcachedMulti(keys, callback){ | ||
// Creates a multi stream, so it's easier to query agains multiple memcached | ||
// servers. | ||
memcached.multi = function memcachedMulti (keys, callback) { | ||
var map = {} | ||
@@ -155,5 +202,8 @@ , memcached = this | ||
// or just gives all servers if we don't have keys | ||
if (keys){ | ||
keys.forEach(function fetchMultipleServers(key){ | ||
var server = memcached.HashRing.getNode(key); | ||
if (keys) { | ||
keys.forEach(function fetchMultipleServers (key) { | ||
var server = memcached.servers.length === 1 | ||
? memcached.servers[0] | ||
: memcached.HashRing.getNode(key); | ||
if (map[server]){ | ||
@@ -165,2 +215,3 @@ map[server].push(key); | ||
}); | ||
// store the servers | ||
@@ -173,3 +224,4 @@ servers = Object.keys(map); | ||
i = servers.length; | ||
while(i--){ | ||
while (i--) { | ||
callback.call(this, servers[i], map[servers[i]], i, servers.length); | ||
@@ -179,29 +231,51 @@ } | ||
// Executes the command on the net.Stream, if no server is supplied it will use the query.key to get | ||
// the server from the HashRing | ||
memcached.command = function memcachedCommand(queryCompiler, server){ | ||
// Executes the command on the net.Stream, if no server is supplied it will | ||
// use the query.key to get the server from the HashRing | ||
memcached.command = function memcachedCommand (queryCompiler, server) { | ||
// generate a regular query, | ||
var query = queryCompiler() | ||
, redundancy = this.redundancy && this.redundancy < this.servers.length | ||
, queryRedundancy = query.redundancyEnabled | ||
, memcached = this; | ||
, redundancy = this.redundancy && this.redundancy < this.servers.length | ||
, queryRedundancy = query.redundancyEnabled | ||
, memcached = this; | ||
// validate the arguments | ||
if (query.validation && !Utils.validateArg(query, this)) return; | ||
if (query.validate && !Utils.validateArg(query, this)) return; | ||
// fetch servers | ||
server = server ? server : redundancy && queryRedundancy ? (redundancy = this.HashRing.createRange(query.key, (this.redundancy + 1), true)).shift() : this.HashRing.getNode(query.key); | ||
// try to find the correct server for this query | ||
if (!server) { | ||
// no need to do a hashring lookup if we only have one server assigned to | ||
// us | ||
if (this.servers.length === 1) { | ||
server = this.servers[0]; | ||
} else { | ||
if (redundancy && queryRedundancy) { | ||
redundancy = this.HashRing.createRange(query.key, (this.redundancy + 1), true); | ||
server = redundancy.shift(); | ||
} else { | ||
server = this.HashRing.getNode(query.key); | ||
} | ||
} | ||
} | ||
// check if the server is still alive | ||
if (server in this.issues && this.issues[server].failed) return query.callback && query.callback(false, false); | ||
if (server in this.issues && this.issues[server].failed) { | ||
return query.callback && query.callback('Server not available'); | ||
} | ||
this.connect(server, function allocateMemcachedConnection(error, S){ | ||
if (Client.config.debug) | ||
query.command.split(LINEBREAK).forEach(function(line) { console.log(S.streamID + ' \033[34m<<\033[0m ' + line); }); | ||
this.connect(server, function allocateMemcachedConnection (error, S) { | ||
if (memcached.debug) { | ||
query.command.split(LINEBREAK).forEach(function errors (line) { | ||
console.log(S.streamID + ' << ' + line); | ||
}); | ||
} | ||
// check for issues | ||
if (!S) return query.callback && query.callback(false, false); | ||
if (error) return query.callback && query.callback(error); | ||
if (S.readyState !== 'open') return query.callback && query.callback('Connection readyState is set to ' + S.readySate); | ||
if (!S) return query.callback && query.callback('Connect did not give a server'); | ||
if (S.readyState !== 'open') { | ||
return query.callback && query.callback('Connection readyState is set to ' + S.readySate); | ||
} | ||
// used for request timing | ||
@@ -213,21 +287,24 @@ query.start = Date.now(); | ||
// if we have redundancy enabled and the query is used for redundancy, than we are going loop over | ||
// the servers, check if we can reach them, and connect to the correct net connection. | ||
// because all redundancy queries are executed with "no reply" we do not need to store the callback | ||
// as there will be no value to parse. | ||
if (redundancy && queryRedundancy){ | ||
// if we have redundancy enabled and the query is used for redundancy, than | ||
// we are going loop over the servers, check if we can reach them, and | ||
// connect to the correct net connection. because all redundancy queries are | ||
// executed with "no reply" we do not need to store the callback as there | ||
// will be no value to parse. | ||
if (redundancy && queryRedundancy) { | ||
queryRedundancy = queryCompiler(queryRedundancy); | ||
redundancy.forEach(function(server){ | ||
redundancy.forEach(function each (server) { | ||
if (server in memcached.issues && memcached.issues[server].failed) return; | ||
memcached.connect(server, function allocateMemcachedConnection(error, S){ | ||
memcached.connect(server, function allocateMemcachedConnection (error, S) { | ||
if (!S || error || S.readyState !== 'open') return; | ||
S.write(queryRedundancy.command + LINEBREAK); | ||
}); | ||
}) | ||
}); | ||
} | ||
}; | ||
// Logs all connection issues, and handles them off. Marking all requests as cache misses. | ||
memcached.connectionIssue = function connectionIssue(error, S, callback){ | ||
// Logs all connection issues, and handles them off. Marking all requests as | ||
// cache misses. | ||
memcached.connectionIssue = function connectionIssue (error, S, callback) { | ||
// end connection and mark callback as cache miss | ||
@@ -242,12 +319,12 @@ if (S && S.end) S.end(); | ||
// check for existing issue logs, or create a new log | ||
if (server in this.issues){ | ||
if (server in this.issues) { | ||
issues = this.issues[server]; | ||
} else { | ||
issues = this.issues[server] = new IssueLog({ | ||
server: server | ||
, tokens: S.tokens | ||
, reconnect: this.reconnect | ||
, retries: this.retries | ||
, retry: this.retry | ||
, remove: this.remove | ||
server: server | ||
, tokens: S.tokens | ||
, reconnect: this.reconnect | ||
, retries: this.retries | ||
, retry: this.retry | ||
, remove: this.remove | ||
}); | ||
@@ -257,17 +334,25 @@ | ||
Utils.fuse(issues, { | ||
issue: function(details){ memcached.emit('issue', details) } | ||
, failure: function(details){ memcached.emit('failure', details) } | ||
, reconnecting: function(details){ memcached.emit('reconnecting', details) } | ||
, reconnected: function(details){ memcached.emit('reconnect', details) } | ||
, remove: function(details){ | ||
// emit event and remove servers | ||
memcached.emit('remove', details); | ||
memcached.connections[server].end(); | ||
issue: function issue (details) { | ||
memcached.emit('issue', details); | ||
} | ||
, failure: function failure (details) { | ||
memcached.emit('failure', details); | ||
} | ||
, reconnecting: function reconnect (details) { | ||
memcached.emit('reconnecting', details); | ||
} | ||
, reconnected: function reconnected (details) { | ||
memcached.emit('reconnect', details); | ||
} | ||
, remove: function remove (details) { | ||
// emit event and remove servers | ||
memcached.emit('remove', details); | ||
memcached.connections[server].end(); | ||
if (this.failOverServers && this.failOverServers.length){ | ||
memcached.HashRing.replaceServer(server, this.failOverServers.shift()); | ||
} else { | ||
memcached.HashRing.removeServer(server); | ||
if (this.failOverServers && this.failOverServers.length) { | ||
memcached.HashRing.replaceServer(server, this.failOverServers.shift()); | ||
} else { | ||
memcached.HashRing.removeServer(server); | ||
} | ||
} | ||
} | ||
}); | ||
@@ -281,6 +366,7 @@ } | ||
// Kills all active connections | ||
memcached.end = function endMemcached(){ | ||
memcached.end = function endMemcached () { | ||
var memcached = this; | ||
Object.keys(this.connections).forEach(function closeConnection(key){ | ||
memcached.connections[key].free(0) | ||
Object.keys(this.connections).forEach(function closeConnection (key) { | ||
memcached.connections[key].free(0); | ||
}); | ||
@@ -291,19 +377,43 @@ }; | ||
// parts of the whole client, the parser commands: | ||
private.parsers = { | ||
privates.parsers = { | ||
// handle error responses | ||
'NOT_FOUND': function(tokens, dataSet, err){ return [CONTINUE, false] } | ||
, 'NOT_STORED': function(tokens, dataSet, err){ return [CONTINUE, false] } | ||
, 'ERROR': function(tokens, dataSet, err){ err.push('Received an ERROR response'); return [FLUSH, false] } | ||
, 'CLIENT_ERROR': function(tokens, dataSet, err){ err.push(tokens.splice(1).join(' ')); return [CONTINUE, false] } | ||
, 'SERVER_ERROR': function(tokens, dataSet, err, queue, S, memcached){ (memcached || this.memcached).connectionIssue(tokens.splice(1).join(' '), S); return [CONTINUE, false] } | ||
'NOT_FOUND': function notfound (tokens, dataSet, err) { | ||
return [CONTINUE, false]; | ||
} | ||
, 'NOT_STORED': function notstored (tokens, dataSet, err) { | ||
return [CONTINUE, false]; | ||
} | ||
, 'ERROR': function error (tokens, dataSet, err) { | ||
err.push('Received an ERROR response'); | ||
return [FLUSH, false]; | ||
} | ||
, 'CLIENT_ERROR': function clienterror (tokens, dataSet, err) { | ||
err.push(tokens.splice(1).join(' ')); | ||
return [CONTINUE, false]; | ||
} | ||
, 'SERVER_ERROR': function servererror (tokens, dataSet, err, queue, S, memcached) { | ||
(memcached || this.memcached).connectionIssue(tokens.splice(1).join(' '), S); | ||
return [CONTINUE, false]; | ||
} | ||
// keyword based responses | ||
, 'STORED': function(tokens, dataSet){ return [CONTINUE, true] } | ||
, 'DELETED': function(tokens, dataSet){ return [CONTINUE, true] } | ||
, 'OK': function(tokens, dataSet){ return [CONTINUE, true] } | ||
, 'EXISTS': function(tokens, dataSet){ return [CONTINUE, false] } | ||
, 'END': function(tokens, dataSet, err, queue){ if (!queue.length) queue.push(false); return [FLUSH, true] } | ||
, 'STORED': function stored (tokens, dataSet) { | ||
return [CONTINUE, true]; | ||
} | ||
, 'DELETED': function deleted (tokens, dataSet) { | ||
return [CONTINUE, true]; | ||
} | ||
, 'OK': function ok (tokens, dataSet) { | ||
return [CONTINUE, true]; | ||
} | ||
, 'EXISTS': function exists (tokens, dataSet) { | ||
return [CONTINUE, false]; | ||
} | ||
, 'END': function end (tokens, dataSet, err, queue) { | ||
if (!queue.length) queue.push(false); | ||
return [FLUSH, true]; | ||
} | ||
// value parsing: | ||
, 'VALUE': function(tokens, dataSet, err, queue){ | ||
, 'VALUE': function value (tokens, dataSet, err, queue) { | ||
var key = tokens[1] | ||
@@ -313,9 +423,14 @@ , flag = +tokens[2] | ||
, cas = tokens[4] | ||
, multi = this.metaData[0] && this.metaData[0].multi || cas ? {} : false | ||
, multi = this.metaData[0] && this.metaData[0].multi || cas | ||
? {} | ||
: false | ||
, tmp; | ||
switch (flag){ | ||
switch (flag) { | ||
case FLAG_JSON: | ||
dataSet = JSON.parse(dataSet); | ||
break; | ||
case FLAG_NUMERIC: | ||
dataSet = +dataSet; | ||
break; | ||
case FLAG_BINARY: | ||
@@ -329,3 +444,3 @@ tmp = new Buffer(dataSet.length); | ||
// Add to queue as multiple get key key key key key returns multiple values | ||
if (!multi){ | ||
if (!multi) { | ||
queue.push(dataSet); | ||
@@ -338,10 +453,13 @@ } else { | ||
return [BUFFER, false] | ||
return [BUFFER, false]; | ||
} | ||
, 'INCRDECR': function(tokens){ return [CONTINUE, +tokens[1]] } | ||
, 'STAT': function(tokens, dataSet, err, queue){ | ||
, 'INCRDECR': function incrdecr (tokens) { | ||
return [CONTINUE, +tokens[1]]; | ||
} | ||
, 'STAT': function stat (tokens, dataSet, err, queue) { | ||
queue.push([tokens[1], /^\d+$/.test(tokens[2]) ? +tokens[2] : tokens[2]]); | ||
return [BUFFER, true] | ||
return [BUFFER, true]; | ||
} | ||
, 'VERSION': function(tokens, dataSet){ | ||
, 'VERSION': function version (tokens, dataSet) { | ||
var versionTokens = /(\d+)(?:\.)(\d+)(?:\.)(\d+)$/.exec(tokens.pop()); | ||
@@ -357,3 +475,3 @@ | ||
} | ||
, 'ITEM': function(tokens, dataSet, err, queue){ | ||
, 'ITEM': function item (tokens, dataSet, err, queue) { | ||
queue.push({ | ||
@@ -364,11 +482,16 @@ key: tokens[1] | ||
}); | ||
return [BUFFER, false] | ||
return [BUFFER, false]; | ||
} | ||
}; | ||
function resultSetIsEmpty (resultSet) { | ||
return !resultSet || (resultSet.length === 1 && !resultSet[0]); | ||
} | ||
// Parses down result sets | ||
private.resultParsers = { | ||
privates.resultParsers = { | ||
// combines the stats array, in to an object | ||
'stats': function(resultSet){ | ||
'stats': function stats (resultSet) { | ||
var response = {}; | ||
if (resultSetIsEmpty(resultSet)) return response; | ||
@@ -378,5 +501,5 @@ // add references to the retrieved server | ||
// Fill the object | ||
resultSet.forEach(function(statSet){ | ||
response[statSet[0]] = statSet[1]; | ||
// Fill the object | ||
resultSet.forEach(function each (statSet) { | ||
if (statSet) response[statSet[0]] = statSet[1]; | ||
}); | ||
@@ -388,6 +511,9 @@ | ||
// the settings uses the same parse format as the regular stats | ||
, 'stats settings': function(){ return private.resultParsers.stats.apply(this, arguments) } | ||
, 'stats settings': function settings () { | ||
return privates.resultParsers.stats.apply(this, arguments); | ||
} | ||
// Group slabs by slab id | ||
, 'stats slabs': function(resultSet){ | ||
, 'stats slabs': function slabs (resultSet) { | ||
var response = {}; | ||
if (resultSetIsEmpty(resultSet)) return response; | ||
@@ -397,8 +523,10 @@ // add references to the retrieved server | ||
// Fill the object | ||
resultSet.forEach(function(statSet){ | ||
var identifier = statSet[0].split(':'); | ||
// Fill the object | ||
resultSet.forEach(function each (statSet) { | ||
if (statSet) { | ||
var identifier = statSet[0].split(':'); | ||
if (!response[identifier[0]]) response[identifier[0]] = {}; | ||
response[identifier[0]][identifier[1]] = statSet[1]; | ||
if (!response[identifier[0]]) response[identifier[0]] = {}; | ||
response[identifier[0]][identifier[1]] = statSet[1]; | ||
} | ||
}); | ||
@@ -408,4 +536,5 @@ | ||
} | ||
, 'stats items': function(resultSet){ | ||
, 'stats items': function items (resultSet) { | ||
var response = {}; | ||
if (resultSetIsEmpty(resultSet)) return response; | ||
@@ -415,9 +544,10 @@ // add references to the retrieved server | ||
// Fill the object | ||
resultSet.forEach(function(statSet){ | ||
var identifier = statSet[0].split(':'); | ||
// Fill the object | ||
resultSet.forEach(function each (statSet) { | ||
if (statSet) { | ||
var identifier = statSet[0].split(':'); | ||
if (!response[identifier[1]]) response[identifier[1]] = {}; | ||
response[identifier[1]][identifier[2]] = statSet[1]; | ||
if (!response[identifier[1]]) response[identifier[1]] = {}; | ||
response[identifier[1]][identifier[2]] = statSet[1]; | ||
} | ||
}); | ||
@@ -430,30 +560,38 @@ | ||
// Generates a RegExp that can be used to check if a chunk is memcached response identifier | ||
private.allCommands = new RegExp('^(?:' + Object.keys(private.parsers).join('|') + '|\\d' + ')'); | ||
private.bufferedCommands = new RegExp('^(?:' + Object.keys(private.parsers).join('|') + ')'); | ||
privates.allCommands = new RegExp('^(?:' + Object.keys(privates.parsers).join('|') + '|\\d' + ')'); | ||
privates.bufferedCommands = new RegExp('^(?:' + Object.keys(privates.parsers).join('|') + ')'); | ||
// When working with large chunks of responses, node chunks it in to pieces. So we might have | ||
// half responses. So we are going to buffer up the buffer and user our buffered buffer to query | ||
// against. Also when you execute allot of .writes to the same stream, node will combine the responses | ||
// in to one response stream. With no indication where it had cut the data. So it can be it cuts inside the value response, | ||
// or even right in the middle of a line-break, so we need to make sure, the last piece in the buffer is a LINEBREAK | ||
// because that is all what is sure about the Memcached Protocol, all responds end with them. | ||
private.buffer = function BufferBuffer(S, BufferStream){ | ||
// When working with large chunks of responses, node chunks it in to pieces. | ||
// So we might have half responses. So we are going to buffer up the buffer | ||
// and user our buffered buffer to query // against. Also when you execute | ||
// allot of .writes to the same stream, node will combine the responses in to | ||
// one response stream. With no indication where it had cut the data. So it | ||
// can be it cuts inside the value response, or even right in the middle of | ||
// a line-break, so we need to make sure, the last piece in the buffer is | ||
// a LINEBREAK because that is all what is sure about the Memcached Protocol, | ||
// all responds end with them. | ||
privates.buffer = function BufferBuffer (S, BufferStream) { | ||
S.responseBuffer += BufferStream; | ||
// only call transform the data once we are sure, 100% sure, that we valid response ending | ||
if (S.responseBuffer.substr(S.responseBuffer.length - 2) === LINEBREAK){ | ||
// only call transform the data once we are sure, 100% sure, that we valid | ||
// response ending | ||
if (S.responseBuffer.substr(S.responseBuffer.length - 2) === LINEBREAK) { | ||
var chunks = S.responseBuffer.split(LINEBREAK); | ||
if (Client.config.debug) | ||
chunks.forEach(function(line) { console.log(S.streamID + ' \033[35m>>\033[0m ' + line); }); | ||
if (this.debug) { | ||
chunks.forEach(function each (line) { | ||
console.log(S.streamID + ' >> ' + line); | ||
}); | ||
} | ||
S.responseBuffer = ""; // clear! | ||
this.rawDataReceived(S, S.bufferArray = S.bufferArray.concat(chunks)); | ||
} | ||
} | ||
}; | ||
// The actual parsers function that scan over the responseBuffer in search of Memcached response | ||
// identifiers. Once we have found one, we will send it to the dedicated parsers that will transform | ||
// the data in a human readable format, deciding if we should queue it up, or send it to a callback fn. | ||
memcached.rawDataReceived = function rawDataReceived(S){ | ||
// The actual parsers function that scan over the responseBuffer in search of | ||
// Memcached response identifiers. Once we have found one, we will send it to | ||
// the dedicated parsers that will transform the data in a human readable | ||
// format, deciding if we should queue it up, or send it to a callback fn. | ||
memcached.rawDataReceived = function rawDataReceived (S) { | ||
var queue = [] | ||
@@ -468,4 +606,3 @@ , token | ||
while(S.bufferArray.length && private.allCommands.test(S.bufferArray[0])){ | ||
while(S.bufferArray.length && privates.allCommands.test(S.bufferArray[0])) { | ||
token = S.bufferArray.shift(); | ||
@@ -478,5 +615,5 @@ tokenSet = token.split(' '); | ||
// special case for value, it's required that it has a second response! | ||
// add the token back, and wait for the next response, we might be handling a big | ||
// ass response here. | ||
if (tokenSet[0] == 'VALUE' && S.bufferArray.indexOf('END') == -1){ | ||
// add the token back, and wait for the next response, we might be | ||
// handling a big ass response here. | ||
if (tokenSet[0] === 'VALUE' && S.bufferArray.indexOf('END') === -1) { | ||
return S.bufferArray.unshift(token); | ||
@@ -486,17 +623,17 @@ } | ||
// check for dedicated parser | ||
if (private.parsers[tokenSet[0]]){ | ||
if (privates.parsers[tokenSet[0]]) { | ||
// fetch the response content | ||
if (tokenSet[0] == 'VALUE') { | ||
while(S.bufferArray.length){ | ||
if (private.bufferedCommands.test(S.bufferArray[0])) break; | ||
if (tokenSet[0] === 'VALUE') { | ||
while (S.bufferArray.length) { | ||
if (privates.bufferedCommands.test(S.bufferArray[0])) break; | ||
dataSet += S.bufferArray.shift(); | ||
}; | ||
} | ||
} | ||
resultSet = private.parsers[tokenSet[0]].call(S, tokenSet, dataSet || token, err, queue, this); | ||
resultSet = privates.parsers[tokenSet[0]].call(S, tokenSet, dataSet || token, err, queue, this); | ||
// check how we need to handle the resultSet response | ||
switch(resultSet.shift()){ | ||
switch (resultSet.shift()) { | ||
case BUFFER: | ||
@@ -510,10 +647,12 @@ break; | ||
// if we have a callback, call it | ||
if (metaData && metaData.callback){ | ||
if (metaData && metaData.callback) { | ||
metaData.execution = Date.now() - metaData.start; | ||
metaData.callback.call( | ||
metaData, err.length ? err : err[0], | ||
metaData | ||
, err.length ? err : err[0] | ||
// see if optional parsing needs to be applied to make the result set more readable | ||
private.resultParsers[metaData.type] ? private.resultParsers[metaData.type].call(S, resultSet, err) : | ||
!Array.isArray(queue) || queue.length > 1 ? queue : queue[0] | ||
// see if optional parsing needs to be applied to make the result set more readable | ||
, privates.resultParsers[metaData.type] | ||
? privates.resultParsers[metaData.type].call(S, resultSet, err) | ||
: !Array.isArray(queue) || queue.length > 1 ? queue : queue[0] | ||
); | ||
@@ -525,7 +664,6 @@ } | ||
case CONTINUE: | ||
default: | ||
metaData = S.metaData.shift(); | ||
if (metaData && metaData.callback){ | ||
if (metaData && metaData.callback) { | ||
metaData.execution = Date.now() - metaData.start; | ||
@@ -548,29 +686,31 @@ metaData.callback.call(metaData, err.length > 1 ? err : err[0], resultSet[0]); | ||
// cleanup | ||
dataSet = '' | ||
dataSet = ''; | ||
tokenSet = metaData = undefined; | ||
// check if we need to remove an empty item from the array, as splitting on /r/n might cause an empty | ||
// item at the end.. | ||
// item at the end.. | ||
if (S.bufferArray[0] === '') S.bufferArray.shift(); | ||
}; | ||
} | ||
}; | ||
// Small wrapper function that only executes errors when we have a callback | ||
private.errorResponse = function errorResponse(error, callback){ | ||
if (typeof callback == 'function') callback(error, false); | ||
privates.errorResponse = function errorResponse (error, callback) { | ||
if (typeof callback === 'function') callback(error, false); | ||
return false; | ||
}; | ||
// This is where the actual Memcached API layer begins: | ||
memcached.get = function get(key, callback){ | ||
memcached.get = function get(key, callback) { | ||
if (Array.isArray(key)) return this.getMulti.apply(this, arguments); | ||
this.command(function getCommand(noreply){ return { | ||
key: key | ||
, callback: callback | ||
, validate: [['key', String], ['callback', Function]] | ||
, type: 'get' | ||
, command: 'get ' + key | ||
}}); | ||
this.command(function getCommand (noreply) { | ||
return { | ||
key: key | ||
, callback: callback | ||
, validate: [['key', String], ['callback', Function]] | ||
, type: 'get' | ||
, command: 'get ' + key | ||
}; | ||
}); | ||
}; | ||
@@ -580,128 +720,213 @@ | ||
// and gets doesn't support multi-gets at this moment. | ||
memcached.gets = function get(key, callback){ | ||
this.command(function getCommand(noreply){ return { | ||
key: key | ||
, callback: callback | ||
, validate: [['key', String], ['callback', Function]] | ||
, type: 'gets' | ||
, command: 'gets ' + key | ||
}}); | ||
memcached.gets = function get(key, callback) { | ||
this.command(function getCommand(noreply) { | ||
return { | ||
key: key | ||
, callback: callback | ||
, validate: [['key', String], ['callback', Function]] | ||
, type: 'gets' | ||
, command: 'gets ' + key | ||
}; | ||
}); | ||
}; | ||
// Handles get's with multiple keys | ||
memcached.getMulti = function getMulti(keys, callback){ | ||
memcached.getMulti = function getMulti(keys, callback) { | ||
var memcached = this | ||
, responses = {} | ||
, errors = [] | ||
, calls | ||
, calls; | ||
// handle multiple responses and cache them untill we receive all. | ||
, handle = function(err, results){ | ||
if (err) errors.push(err); | ||
// handle multiple responses and cache them untill we receive all. | ||
function handle (err, results) { | ||
if (err) { | ||
errors.push(err); | ||
} | ||
// add all responses to the array | ||
(Array.isArray(results) ? results : [results]).forEach(function(value){ Utils.merge(responses, value) }); | ||
// add all responses to the array | ||
(Array.isArray(results) ? results : [results]).forEach(function each (value) { | ||
Utils.merge(responses, value); | ||
}); | ||
if (!--calls) callback(errors.length ? errors : false, responses); | ||
}; | ||
if (!--calls) callback(errors.length ? errors : false, responses); | ||
} | ||
this.multi(keys, function(server, key, index, totals){ | ||
this.multi(keys, function multi (server, key, index, totals) { | ||
if (!calls) calls = totals; | ||
memcached.command(function getMultiCommand(noreply){ return { | ||
callback: handle | ||
, multi:true | ||
, type: 'get' | ||
, command: 'get ' + key.join(' ') | ||
}}, | ||
server | ||
); | ||
memcached.command(function getMultiCommand (noreply) { | ||
return { | ||
callback: handle | ||
, multi:true | ||
, type: 'get' | ||
, command: 'get ' + key.join(' ') | ||
}; | ||
}, server); | ||
}); | ||
}; | ||
// As all command nearly use the same syntax we are going to proxy them all to this | ||
// function to ease maintenance. This is possible because most set commands will use the same | ||
// syntax for the Memcached server. Some commands do not require a lifetime and a flag, but the | ||
// memcached server is smart enough to ignore those. | ||
private.setters = function setters(type, validate, key, value, lifetime, callback, cas){ | ||
// As all command nearly use the same syntax we are going to proxy them all to | ||
// this function to ease maintenance. This is possible because most set | ||
// commands will use the same syntax for the Memcached server. Some commands | ||
// do not require a lifetime and a flag, but the memcached server is smart | ||
// enough to ignore those. | ||
privates.setters = function setters (type, validate, key, value, lifetime, callback, cas) { | ||
var flag = 0 | ||
, memcached = this | ||
, valuetype = typeof value | ||
, length; | ||
if (Buffer.isBuffer(value)){ | ||
if (Buffer.isBuffer(value)) { | ||
flag = FLAG_BINARY; | ||
value = value.toString('binary'); | ||
} else if (valuetype !== 'string' && valuetype !== 'number'){ | ||
} else if (valuetype === 'number') { | ||
flag = FLAG_NUMERIC; | ||
value = value.toString(); | ||
} else if (valuetype !== 'string') { | ||
flag = FLAG_JSON; | ||
value = JSON.stringify(value); | ||
} else { | ||
value = value.toString(); | ||
} | ||
length = Buffer.byteLength(value); | ||
if (length > memcached.maxValue) return private.errorResponse('The length of the value is greater than ' + memcached.maxValue, callback); | ||
if (length > this.maxValue) { | ||
return privates.errorResponse('The length of the value is greater than ' + this.maxValue, callback); | ||
} | ||
memcached.command(function settersCommand(noreply){ return { | ||
key: key | ||
, callback: callback | ||
, lifetime: lifetime | ||
, value: value | ||
, cas: cas | ||
, validate: validate | ||
, type: type | ||
, redundancyEnabled: true | ||
, command: [type, key, flag, lifetime, length].join(' ') + | ||
(cas ? ' ' + cas : '') + | ||
(noreply ? NOREPLY : '') + | ||
LINEBREAK + value | ||
}}); | ||
this.command(function settersCommand (noreply) { | ||
return { | ||
key: key | ||
, callback: callback | ||
, lifetime: lifetime | ||
, value: value | ||
, cas: cas | ||
, validate: validate | ||
, type: type | ||
, redundancyEnabled: false | ||
, command: [type, key, flag, lifetime, length].join(' ') + | ||
(cas ? ' ' + cas : '') + | ||
(noreply ? NOREPLY : '') + | ||
LINEBREAK + value | ||
}; | ||
}); | ||
}; | ||
// Curry the function and so we can tell the type our private set function | ||
memcached.set = Utils.curry(false, private.setters, 'set', [['key', String], ['lifetime', Number], ['value', String], ['callback', Function]]); | ||
memcached.replace = Utils.curry(false, private.setters, 'replace', [['key', String], ['lifetime', Number], ['value', String], ['callback', Function]]); | ||
memcached.add = Utils.curry(false, private.setters, 'add', [['key', String], ['lifetime', Number], ['value', String], ['callback', Function]]); | ||
memcached.set = curry(undefined, privates.setters | ||
, 'set' | ||
, [ | ||
['key', String] | ||
, ['lifetime', Number] | ||
, ['value', String] | ||
, ['callback', Function] | ||
] | ||
); | ||
memcached.cas = function checkandset(key, value, cas, lifetime, callback){ | ||
private.setters.call(this, 'cas', [['key', String], ['lifetime', Number], ['value', String], ['callback', Function]], key, value, lifetime, callback, cas); | ||
memcached.replace = curry(undefined, privates.setters | ||
, 'replace' | ||
, [ | ||
['key', String] | ||
, ['lifetime', Number] | ||
, ['value', String] | ||
, ['callback', Function] | ||
] | ||
); | ||
memcached.add = curry(undefined, privates.setters | ||
, 'add' | ||
, [ | ||
['key', String] | ||
, ['lifetime', Number] | ||
, ['value', String] | ||
, ['callback', Function] | ||
] | ||
); | ||
memcached.cas = function checkandset (key, value, cas, lifetime, callback) { | ||
privates.setters.call(this | ||
, 'cas' | ||
, [ | ||
['key', String] | ||
, ['lifetime', Number] | ||
, ['value', String] | ||
, ['callback', Function] | ||
] | ||
, key | ||
, value | ||
, lifetime | ||
, callback | ||
, cas | ||
); | ||
}; | ||
memcached.append = function append(key, value, callback){ | ||
private.setters.call(this, 'append', [['key', String], ['lifetime', Number], ['value', String], ['callback', Function]], key, value, 0, callback); | ||
memcached.append = function append (key, value, callback) { | ||
privates.setters.call(this | ||
, 'append' | ||
, [ | ||
['key', String] | ||
, ['lifetime', Number] | ||
, ['value', String] | ||
, ['callback', Function] | ||
] | ||
, key | ||
, value | ||
, 0 | ||
, callback | ||
); | ||
}; | ||
memcached.prepend = function prepend(key, value, callback){ | ||
private.setters.call(this, 'prepend', [['key', String], ['lifetime', Number], ['value', String], ['callback', Function]], key, value, 0, callback); | ||
memcached.prepend = function prepend (key, value, callback) { | ||
privates.setters.call(this | ||
, 'prepend' | ||
, [ | ||
['key', String] | ||
, ['lifetime', Number] | ||
, ['value', String] | ||
, ['callback', Function] | ||
] | ||
, key | ||
, value | ||
, 0 | ||
, callback | ||
); | ||
}; | ||
// Small handler for incr and decr's | ||
private.incrdecr = function incrdecr(type, key, value, callback){ | ||
this.command(function incredecrCommand(noreply){ return { | ||
key: key | ||
, callback: callback | ||
, value: value | ||
, validate: [['key', String], ['value', Number], ['callback', Function]] | ||
, type: type | ||
, redundancyEnabled: true | ||
, command: [type, key, value].join(' ') + | ||
(noreply ? NOREPLY : '') | ||
}}); | ||
privates.incrdecr = function incrdecr (type, key, value, callback) { | ||
this.command(function incredecrCommand (noreply) { | ||
return { | ||
key: key | ||
, callback: callback | ||
, value: value | ||
, validate: [ | ||
['key', String] | ||
, ['value', Number] | ||
, ['callback', Function] | ||
] | ||
, type: type | ||
, redundancyEnabled: true | ||
, command: [type, key, value].join(' ') + | ||
(noreply ? NOREPLY : '') | ||
}; | ||
}); | ||
}; | ||
// Curry the function and so we can tell the type our private incrdecr | ||
memcached.increment = memcached.incr = Utils.curry(false, private.incrdecr, 'incr'); | ||
memcached.decrement = memcached.decr = Utils.curry(false, private.incrdecr, 'decr'); | ||
memcached.increment = memcached.incr = curry(undefined, privates.incrdecr, 'incr'); | ||
memcached.decrement = memcached.decr = curry(undefined, privates.incrdecr, 'decr'); | ||
// Deletes the keys from the servers | ||
memcached.del = function del(key, callback){ | ||
this.command(function deleteCommand(noreply){ return { | ||
key: key | ||
, callback: callback | ||
, validate: [['key', String], ['callback', Function]] | ||
, type: 'delete' | ||
, redundancyEnabled: true | ||
, command: 'delete ' + key + | ||
(noreply ? NOREPLY : '') | ||
}}); | ||
memcached.del = function del (key, callback){ | ||
this.command(function deleteCommand (noreply) { | ||
return { | ||
key: key | ||
, callback: callback | ||
, validate: [ | ||
['key', String] | ||
, ['callback', Function] | ||
] | ||
, type: 'delete' | ||
, redundancyEnabled: true | ||
, command: 'delete ' + key + | ||
(noreply ? NOREPLY : '') | ||
}; | ||
}); | ||
}; | ||
@@ -711,27 +936,30 @@ memcached['delete'] = memcached.del; | ||
// Small wrapper that handle single keyword commands such as FLUSH ALL, VERSION and STAT | ||
private.singles = function singles(type, callback){ | ||
privates.singles = function singles (type, callback) { | ||
var memcached = this | ||
, responses = [] | ||
, errors = [] | ||
, calls | ||
, errors | ||
, calls; | ||
// handle multiple servers | ||
, handle = function(err, results){ | ||
if (err) errors.push(err); | ||
if (results) responses = responses.concat(results); | ||
function handle (err, results) { | ||
if (err) { | ||
errors = errors || []; | ||
errors.push(err); | ||
} | ||
if (results) responses = responses.concat(results); | ||
// multi calls should ALWAYS return an array! | ||
if (!--calls) callback(errors, responses); | ||
}; | ||
// multi calls should ALWAYS return an array! | ||
if (!--calls) callback(errors && errors.length ? errors.pop() : undefined, responses); | ||
} | ||
this.multi(false, function(server, keys, index, totals){ | ||
this.multi(false, function multi (server, keys, index, totals) { | ||
if (!calls) calls = totals; | ||
memcached.command(function singlesCommand(noreply){ return { | ||
callback: handle | ||
, type: type | ||
, command: type | ||
}}, | ||
server | ||
); | ||
memcached.command(function singlesCommand (noreply) { | ||
return { | ||
callback: handle | ||
, type: type | ||
, command: type | ||
}; | ||
}, server); | ||
}); | ||
@@ -741,26 +969,36 @@ }; | ||
// Curry the function and so we can tell the type our private singles | ||
memcached.version = Utils.curry(false, private.singles, 'version'); | ||
memcached.flush = Utils.curry(false, private.singles, 'flush_all'); | ||
memcached.stats = Utils.curry(false, private.singles, 'stats'); | ||
memcached.settings = Utils.curry(false, private.singles, 'stats settings'); | ||
memcached.slabs = Utils.curry(false, private.singles, 'stats slabs'); | ||
memcached.items = Utils.curry(false, private.singles, 'stats items'); | ||
memcached.version = curry(undefined, privates.singles, 'version'); | ||
memcached.flush = curry(undefined, privates.singles, 'flush_all'); | ||
memcached.stats = curry(undefined, privates.singles, 'stats'); | ||
memcached.settings = curry(undefined, privates.singles, 'stats settings'); | ||
memcached.slabs = curry(undefined, privates.singles, 'stats slabs'); | ||
memcached.items = curry(undefined, privates.singles, 'stats items'); | ||
// aliases | ||
memcached.flushAll = memcached.flush; | ||
memcached.statsSettings = memcached.settings; | ||
memcached.statsSlabs = memcached.slabs; | ||
memcached.statsItems = memcached.items; | ||
// You need to use the items dump to get the correct server and slab settings | ||
// see simple_cachedump.js for an example | ||
memcached.cachedump = function cachedump(server, slabid, number, callback){ | ||
this.command(function cachedumpCommand(noreply){ return { | ||
callback: callback | ||
, number: number | ||
, slabid: slabid | ||
, validate: [['number', Number], ['slabid', Number], ['callback', Function]] | ||
, type: 'stats cachedump' | ||
, command: 'stats cachedump ' + slabid + ' ' + number | ||
}}, | ||
server | ||
); | ||
memcached.cachedump = function cachedump (server, slabid, number, callback) { | ||
this.command(function cachedumpCommand (noreply) { | ||
return { | ||
callback: callback | ||
, number: number | ||
, slabid: slabid | ||
, validate: [ | ||
['number', Number] | ||
, ['slabid', Number] | ||
, ['callback', Function] | ||
] | ||
, type: 'stats cachedump' | ||
, command: 'stats cachedump ' + slabid + ' ' + number | ||
}; | ||
}, server); | ||
}; | ||
})(Client); | ||
module.exports = Client; |
130
lib/utils.js
@@ -1,54 +0,78 @@ | ||
var CreateHash = require('crypto').createHash; | ||
"use strict"; | ||
exports.validateArg = function validateArg(args, config){ | ||
var toString = Object.prototype.toString | ||
, err | ||
var createHash = require('crypto').createHash | ||
, toString = Object.prototype.toString; | ||
exports.validateArg = function validateArg (args, config) { | ||
var err | ||
, callback; | ||
args.validate.forEach(function(tokens){ | ||
args.validate.forEach(function (tokens) { | ||
var key = tokens[0] | ||
, value = args[key]; | ||
switch(tokens[1]){ | ||
case Number: | ||
if (toString.call(value) !== '[object Number]') err = 'Argument "' + key + '" is not a valid Number.'; | ||
if (toString.call(value) !== '[object Number]') { | ||
err = 'Argument "' + key + '" is not a valid Number.'; | ||
} | ||
break; | ||
case Boolean: | ||
if (toString.call(value) !== '[object Boolean]') err = 'Argument "' + key + '" is not a valid Boolean.'; | ||
if (toString.call(value) !== '[object Boolean]') { | ||
err = 'Argument "' + key + '" is not a valid Boolean.'; | ||
} | ||
break; | ||
case Array: | ||
if (toString.call(value) !== '[object Array]') err = 'Argument "' + key + '" is not a valid Array.'; | ||
if (toString.call(value) !== '[object Array]') { | ||
err = 'Argument "' + key + '" is not a valid Array.'; | ||
} | ||
break; | ||
case Object: | ||
if (toString.call(value) !== '[object Object]') err = 'Argument "' + key + '" is not a valid Object.'; | ||
if (toString.call(value) !== '[object Object]') { | ||
err = 'Argument "' + key + '" is not a valid Object.'; | ||
} | ||
break; | ||
case Function: | ||
if (toString.call(value) !== '[object Function]') err = 'Argument "' + key + '" is not a valid Function.'; | ||
if (toString.call(value) !== '[object Function]') { | ||
err = 'Argument "' + key + '" is not a valid Function.'; | ||
} | ||
break; | ||
case String: | ||
if (toString.call(value) !== '[object String]') err = 'Argument "' + key + '" is not a valid String.'; | ||
if (!err && key == 'key' && value.length > config.maxKeySize){ | ||
if (config.keyCompression){ | ||
args[key] = CreateHash('md5').update(value).digest('hex'); | ||
// also make sure you update the command | ||
config.command.replace(new RegExp('^(' + value + ')'), args[key]); | ||
} else { | ||
err = 'Argument "' + key + '" is longer than the maximum allowed length of ' + config.maxKeySize; | ||
case String: | ||
if (toString.call(value) !== '[object String]') { | ||
err = 'Argument "' + key + '" is not a valid String.'; | ||
} | ||
if (!err && key === 'key') { | ||
if (value.length > config.maxKeySize) { | ||
if (config.keyCompression){ | ||
args[key] = createHash('md5').update(value).digest('hex'); | ||
// also make sure you update the command | ||
args.command.replace(value, args[key]); | ||
} else { | ||
err = 'Argument "' + key + '" is longer than the maximum allowed length of ' + config.maxKeySize; | ||
} | ||
} else if (/[\s\n\r]/.test(value)) { | ||
err = 'The key should not contain any whitespace or new lines'; | ||
} | ||
} | ||
break; | ||
default: | ||
if (toString.call(value) == '[object global]' && !tokens[2]) err = 'Argument "' + key + '" is not defined.'; | ||
if (toString.call(value) === '[object global]' && !tokens[2]) { | ||
err = 'Argument "' + key + '" is not defined.'; | ||
} | ||
} | ||
}); | ||
if (err){ | ||
@@ -58,18 +82,8 @@ if(callback) callback(err, false); | ||
} | ||
return true; | ||
}; | ||
// currys a function | ||
exports.curry = function curry(context, func){ | ||
var copy = Array.prototype.slice | ||
, args = copy.call(arguments, 2); | ||
return function(){ | ||
return func.apply(context || this, args.concat(copy.call(arguments))); | ||
} | ||
}; | ||
// a small util to use an object for eventEmitter | ||
exports.fuse = function fuse(target, handlers){ | ||
exports.fuse = function fuse (target, handlers) { | ||
for(var i in handlers) | ||
@@ -82,12 +96,22 @@ if (handlers.hasOwnProperty(i)){ | ||
// merges a object's proppertys / values with a other object | ||
exports.merge = function merge(target, obj){ | ||
for(var i in obj){ | ||
exports.merge = function merge (target, obj) { | ||
for (var i in obj) { | ||
target[i] = obj[i]; | ||
} | ||
return target; | ||
}; | ||
// curry/bind functions | ||
exports.curry = function curry (context, fn) { | ||
var copy = Array.prototype.slice | ||
, args = copy.call(arguments, 2); | ||
return function bowlofcurry () { | ||
return fn.apply(context || this, args.concat(copy.call(arguments))); | ||
}; | ||
}; | ||
// a small items iterator | ||
exports.Iterator = function iterator(collection, callback){ | ||
exports.Iterator = function iterator (collection, callback) { | ||
var arr = Array.isArray(collection) | ||
@@ -98,13 +122,13 @@ , keys = !arr ? Object.keys(collection) : false | ||
, self = this; | ||
// returns next item | ||
this.next = function(){ | ||
this.next = function next () { | ||
var obj = arr ? collection[index] : { key: keys[index], value: collection[keys[index]] }; | ||
callback(obj, index++, collection, self); | ||
}; | ||
// check if we have more items | ||
this.hasNext = function(){ | ||
this.hasNext = function hasNext () { | ||
return index < maximum; | ||
}; | ||
}; | ||
}; |
{ | ||
"name": "memcached" | ||
, "version": "0.0.5" | ||
, "author": "Arnout Kazemier" | ||
, "description": "A fully featured Memcached API client, supporting both single and clustered Memcached servers through consistent hashing and failover/failure. Memcached is rewrite of nMemcached, which will be deprecated in the near future." | ||
, "main": "index" | ||
, "keywords":[ | ||
"memcached" | ||
, "client" | ||
, "hashing" | ||
, "failover" | ||
, "cluster" | ||
, "nMemcached" | ||
, "memcache" | ||
, "cache" | ||
, "nosql" | ||
, "membase" | ||
, "InnoDB memcached API" | ||
] | ||
, "directories": { | ||
"lib": "./lib" | ||
} | ||
, "maintainers": [{ | ||
"name":"Arnout Kazemier" | ||
, "email":"info@3rd-Eden.com" | ||
, "web":"http://www.3rd-Eden.com" | ||
}] | ||
, "license": { | ||
"type": "MIT" | ||
, "url": "http://github.com/3rd-Eden/node-memcached/blob/master/LICENSE" | ||
} | ||
, "repository": { | ||
"type": "git" | ||
, "url" : "http://github.com/3rd-Eden/node-memcached.git" | ||
} | ||
, "dependencies": { | ||
"hashring": "" | ||
} | ||
"name": "memcached" | ||
, "version": "0.1.0" | ||
, "author": "Arnout Kazemier" | ||
, "description": "A fully featured Memcached API client, supporting both single and clustered Memcached servers through consistent hashing and failover/failure. Memcached is rewrite of nMemcached, which will be deprecated in the near future." | ||
, "main": "index" | ||
, "keywords":[ | ||
"memcached" | ||
, "client" | ||
, "hashing" | ||
, "failover" | ||
, "cluster" | ||
, "nMemcached" | ||
, "memcache" | ||
, "cache" | ||
, "nosql" | ||
, "membase" | ||
, "InnoDB memcached API" | ||
] | ||
, "directories": { | ||
"lib": "./lib" | ||
} | ||
, "maintainers": [{ | ||
"name": "Arnout Kazemier" | ||
, "email": "info@3rd-Eden.com" | ||
, "url": "http://www.3rd-Eden.com" | ||
}] | ||
, "license": { | ||
"type": "MIT" | ||
, "url": "http://github.com/3rd-Eden/node-memcached/blob/master/LICENSE" | ||
} | ||
, "repository": { | ||
"type": "git" | ||
, "url" : "http://github.com/3rd-Eden/node-memcached.git" | ||
} | ||
, "dependencies": { | ||
"hashring": "*" | ||
} | ||
, "devDependencies": { | ||
"mocha": "*" | ||
, "should": "*" | ||
} | ||
} |
@@ -11,2 +11,6 @@ #Memcached | ||
### protocol | ||
This module uses the ASCII protocol to communicate with the server, this makes it easier to debug for you are user as you can see what is send over the wire but also for me as developer. But this also means that SASL auth is not supported in this driver as that requires the use of the binary protocol. The ASCII protocol not only used by memcached but also by other databases and message queues, so that is a nice extra. | ||
## Setting up the client | ||
@@ -78,3 +82,3 @@ | ||
``` js | ||
var Memcached = require( 'memcached' ).Client; | ||
var Memcached = require('memcached'); | ||
// all global configurations should be applied to the .config object of the Client. | ||
@@ -81,0 +85,0 @@ Memcached.config.poolSize = 25; |
@@ -35,2 +35,2 @@ /** | ||
return a; | ||
}; | ||
}; |
@@ -6,7 +6,6 @@ /** | ||
var assert = require('assert') | ||
, should = require('should') | ||
, common = require('./common') | ||
, Memcached = require('../'); | ||
global.testnumbers = global.testnumbers || +(Math.random(10) * 1000).toFixed(); | ||
global.testnumbers = global.testnumbers || +(Math.random(10) * 1000000).toFixed(); | ||
@@ -17,122 +16,71 @@ /** | ||
*/ | ||
describe("Memcached CAS", function() { | ||
/** | ||
* For a proper CAS update in memcached you will need to know the CAS value | ||
* of a given key, this is done by the `gets` command. So we will need to make | ||
* sure that a `cas` key is given. | ||
*/ | ||
it("set and gets for cas result", function(done) { | ||
var memcached = new Memcached(common.servers.single) | ||
, message = common.alphabet(256) | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
module.exports = { | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
/** | ||
* For a proper CAS update in memcached you will need to know the CAS value | ||
* of a given key, this is done by the `gets` command. So we will need to make | ||
* sure that a `cas` key is given. | ||
*/ | ||
"set and gets for cas result": function(exit){ | ||
var memcached = new Memcached(common.servers.single) | ||
, message = common.alphabet(256) | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.gets("test:" + testnr, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(typeof answer === 'object'); | ||
assert.ok(!!answer.cas); | ||
answer["test:" + testnr].should.eql(message); | ||
memcached.end(); // close connections | ||
}); | ||
}); | ||
// make sure all callbacks are called | ||
exit(function(){ | ||
assert.equal(callbacks, 2); | ||
}); | ||
} | ||
ok.should.be.true; | ||
/** | ||
* Create a successful cas update, so we are sure we send a cas request correctly. | ||
*/ | ||
, "successful cas update" : function(exit){ | ||
var memcached = new Memcached(common.servers.single) | ||
, message = common.alphabet(256) | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.gets("test:" + testnr, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(!!answer.cas); | ||
// generate new message for the cas update | ||
message = common.alphabet(256); | ||
memcached.cas("test:" + testnr, message, answer.cas, 1000, function(error, answer){ | ||
memcached.gets("test:" + testnr, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(!!answer); | ||
memcached.get("test:" + testnr, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
answer.should.eql(message); | ||
memcached.end(); // close connections | ||
}) | ||
assert.ok(typeof answer === 'object'); | ||
assert.ok(!!answer.cas); | ||
answer["test:" + testnr].should.eql(message); | ||
memcached.end(); // close connections | ||
assert.equal(callbacks, 2); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
// make sure all callbacks are called | ||
exit(function(){ | ||
assert.equal(callbacks, 4); | ||
}); | ||
} | ||
/** | ||
* Create a unsuccessful cas update, which would indicate that the server has changed | ||
* while we where doing nothing. | ||
*/ | ||
, "unsuccessful cas update" : function(exit){ | ||
}); | ||
/** | ||
* Create a successful cas update, so we are sure we send a cas request correctly. | ||
*/ | ||
it("successful cas update", function(done) { | ||
var memcached = new Memcached(common.servers.single) | ||
, message = common.alphabet(256) | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.gets("test:" + testnr, function(error, answer){ | ||
, message = common.alphabet(256) | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(!!answer.cas); | ||
// generate new message | ||
message = common.alphabet(256); | ||
memcached.set("test:" + testnr, message, 1000, function(){ | ||
ok.should.be.true; | ||
memcached.gets("test:" + testnr, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(!!answer.cas); | ||
// generate new message for the cas update | ||
message = common.alphabet(256); | ||
memcached.cas("test:" + testnr, message, answer.cas, 1000, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(!answer); | ||
assert.ok(!!answer); | ||
memcached.get("test:" + testnr, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
answer.should.eql(message); | ||
memcached.end(); // close connections | ||
assert.equal(callbacks, 4); | ||
done(); | ||
}) | ||
@@ -142,9 +90,49 @@ }); | ||
}); | ||
}); | ||
// make sure all callbacks are called | ||
exit(function(){ | ||
assert.equal(callbacks, 5); | ||
}); | ||
} | ||
}; | ||
}); | ||
/** | ||
* Create a unsuccessful cas update, which would indicate that the server has changed | ||
* while we where doing nothing. | ||
*/ | ||
it("unsuccessful cas update", function(done) { | ||
var memcached = new Memcached(common.servers.single) | ||
, message = common.alphabet(256) | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.gets("test:" + testnr, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(!!answer.cas); | ||
// generate new message | ||
message = common.alphabet(256); | ||
memcached.set("test:" + testnr, message, 1000, function(){ | ||
++callbacks; | ||
memcached.cas("test:" + testnr, message, answer.cas, 1000, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(!answer); | ||
memcached.get("test:" + testnr, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
answer.should.eql(message); | ||
memcached.end(); // close connections | ||
assert.equal(callbacks, 5); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -6,3 +6,2 @@ /** | ||
var assert = require('assert') | ||
, should = require('should') | ||
, fs = require('fs') | ||
@@ -12,3 +11,3 @@ , common = require('./common') | ||
global.testnumbers = global.testnumbers || +(Math.random(10) * 1000).toFixed(); | ||
global.testnumbers = global.testnumbers || +(Math.random(10) * 1000000).toFixed(); | ||
@@ -19,214 +18,160 @@ /** | ||
*/ | ||
describe("Memcached GET SET", function() { | ||
/** | ||
* Make sure that the string that we send to the server is correctly | ||
* stored and retrieved. We will be storing random strings to ensure | ||
* that we are not retrieving old data. | ||
*/ | ||
it("set and get a regular string", function(done) { | ||
var memcached = new Memcached(common.servers.single) | ||
, message = common.alphabet(256) | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
module.exports = { | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
/** | ||
* Make sure that the string that we send to the server is correctly | ||
* stored and retrieved. We will be storing random strings to ensure | ||
* that we are not retrieving old data. | ||
*/ | ||
"set and get a regular string": function(exit){ | ||
var memcached = new Memcached(common.servers.single) | ||
, message = common.alphabet(256) | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.get("test:" + testnr, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(typeof answer === 'string'); | ||
answer.should.eql(message); | ||
memcached.end(); // close connections | ||
ok.should.be.true; | ||
memcached.get("test:" + testnr, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(typeof answer === 'string'); | ||
answer.should.eql(message); | ||
memcached.end(); // close connections | ||
assert.equal(callbacks, 2); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
// make sure all callbacks are called | ||
exit(function(){ | ||
assert.equal(callbacks, 2); | ||
}); | ||
} | ||
}); | ||
/** | ||
* Set a stringified JSON object, and make sure we only return a string | ||
* this should not be flagged as JSON object | ||
*/ | ||
, "set and get a JSON.stringify string": function(exit){ | ||
/** | ||
* Set a stringified JSON object, and make sure we only return a string | ||
* this should not be flagged as JSON object | ||
*/ | ||
it("set and get a JSON.stringify string", function(done) { | ||
var memcached = new Memcached(common.servers.single) | ||
, message = JSON.stringify({numbers:common.numbers(256),alphabet:common.alphabet(256),dates:new Date(),arrays: [1,2,3, 'foo', 'bar']}) | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.get("test:" + testnr, function(error, answer){ | ||
, message = JSON.stringify({numbers:common.numbers(256),alphabet:common.alphabet(256),dates:new Date(),arrays: [1,2,3, 'foo', 'bar']}) | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(typeof answer === 'string'); | ||
answer.should.eql(message); | ||
memcached.end(); // close connections | ||
ok.should.be.true; | ||
memcached.get("test:" + testnr, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(typeof answer === 'string'); | ||
answer.should.eql(message); | ||
memcached.end(); // close connections | ||
assert.equal(callbacks, 2); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
// make sure all callbacks are called | ||
exit(function(){ | ||
assert.equal(callbacks, 2); | ||
}); | ||
} | ||
/** | ||
* Setting and getting a unicode value should just work, we need to make sure | ||
* that we send the correct byteLength because utf8 chars can contain more bytes | ||
* than "str".length would show, causing the memcached server to complain. | ||
*/ | ||
, "set and get a regular string": function(exit){ | ||
}); | ||
/** | ||
* Setting and getting a unicode value should just work, we need to make sure | ||
* that we send the correct byteLength because utf8 chars can contain more bytes | ||
* than "str".length would show, causing the memcached server to complain. | ||
*/ | ||
it("set and get a regular string", function(done) { | ||
var memcached = new Memcached(common.servers.single) | ||
, message = 'привет мир, Memcached и nodejs для победы' | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.get("test:" + testnr, function(error, answer){ | ||
, message = 'привет мир, Memcached и nodejs для победы' | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(typeof answer === 'string'); | ||
answer.should.eql(message); | ||
memcached.end(); // close connections | ||
ok.should.be.true; | ||
memcached.get("test:" + testnr, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(typeof answer === 'string'); | ||
answer.should.eql(message); | ||
memcached.end(); // close connections | ||
assert.equal(callbacks, 2); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
// make sure all callbacks are called | ||
exit(function(){ | ||
assert.equal(callbacks, 2); | ||
}); | ||
} | ||
/** | ||
* A common action when working with memcached servers, getting a key | ||
* that does not exist anymore. | ||
*/ | ||
, "get a non existing key": function(exit){ | ||
}); | ||
/** | ||
* A common action when working with memcached servers, getting a key | ||
* that does not exist anymore. | ||
*/ | ||
it("get a non existing key", function(done) { | ||
var memcached = new Memcached(common.servers.single) | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.get("test:" + testnr, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
answer.should.be.false; | ||
memcached.end(); // close connections | ||
}); | ||
// make sure all callbacks are called | ||
exit(function(){ | ||
assert.equal(callbacks, 1); | ||
}); | ||
} | ||
/** | ||
* Make sure that Numbers are correctly send and stored on the server | ||
* retrieval of the number based values can be tricky as the client might | ||
* think that it was a INCR and not a SET operation.. So just to make sure.. | ||
*/ | ||
, "set and get a regular number": function(exit){ | ||
var memcached = new Memcached(common.servers.single) | ||
, message = common.numbers(256) | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.get("test:" + testnr, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(typeof answer === 'string'); | ||
answer.should.eql(answer); | ||
answer.should.be.false; | ||
memcached.end(); // close connections | ||
assert.equal(callbacks, 1); | ||
done(); | ||
}); | ||
}); | ||
// make sure all callbacks are called | ||
exit(function(){ | ||
assert.equal(callbacks, 2); | ||
}); | ||
} | ||
}); | ||
/** | ||
* Objects should be converted to a JSON string, send to the server | ||
* and be automagically JSON.parsed when they are retrieved. | ||
*/ | ||
, "set and get a object": function(exit){ | ||
/** | ||
* Make sure that Numbers are correctly send and stored on the server | ||
* retrieval of the number based values can be tricky as the client might | ||
* think that it was a INCR and not a SET operation.. So just to make sure.. | ||
*/ | ||
it("set and get a regular number", function(done) { | ||
var memcached = new Memcached(common.servers.single) | ||
, message = { | ||
numbers: common.numbers(256) | ||
, alphabet: common.alphabet(256) | ||
, dates: new Date() | ||
, arrays: [1,2,3, 'foo', 'bar'] | ||
} | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.get("test:" + testnr, function(error, answer){ | ||
, message = common.numbers(256) | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(!Array.isArray(answer) && typeof answer == 'object'); | ||
assert.ok(JSON.stringify(message) == JSON.stringify(answer)); | ||
memcached.end(); // close connections | ||
ok.should.be.true; | ||
memcached.get("test:" + testnr, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(typeof answer === 'number'); | ||
answer.should.eql(message); | ||
memcached.end(); // close connections | ||
assert.equal(callbacks, 2); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
// make sure all callbacks are called | ||
exit(function(){ | ||
assert.equal(callbacks, 2); | ||
}); | ||
} | ||
}); | ||
/** | ||
* Arrays should be converted to a JSON string, send to the server | ||
* and be automagically JSON.parsed when they are retrieved. | ||
*/ | ||
, "set and get a array": function(exit){ | ||
var memcached = new Memcached(common.servers.single) | ||
, message = [ | ||
{ | ||
/** | ||
* Objects should be converted to a JSON string, send to the server | ||
* and be automagically JSON.parsed when they are retrieved. | ||
*/ | ||
it("set and get a object", function(done) { | ||
var memcached = new Memcached(common.servers.single) | ||
, message = { | ||
numbers: common.numbers(256) | ||
@@ -237,225 +182,244 @@ , alphabet: common.alphabet(256) | ||
} | ||
, { | ||
numbers: common.numbers(256) | ||
, alphabet: common.alphabet(256) | ||
, dates: new Date() | ||
, arrays: [1,2,3, 'foo', 'bar'] | ||
} | ||
] | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.get("test:" + testnr, function(error, answer){ | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(Array.isArray(answer)); | ||
assert.ok(JSON.stringify(answer) == JSON.stringify(message)); | ||
memcached.end(); // close connections | ||
ok.should.be.true; | ||
memcached.get("test:" + testnr, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(!Array.isArray(answer) && typeof answer == 'object'); | ||
assert.ok(JSON.stringify(message) == JSON.stringify(answer)); | ||
memcached.end(); // close connections | ||
assert.equal(callbacks, 2); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
// make sure all callbacks are called | ||
exit(function(){ | ||
assert.equal(callbacks, 2); | ||
}); | ||
} | ||
/** | ||
* Buffers are commonly used for binary transports So we need to make sure | ||
* we support them properly. But please note, that we need to compare the | ||
* strings on a "binary" level, because that is the encoding the Memcached | ||
* client will be using, as there is no indication of what encoding the | ||
* buffer is in. | ||
*/ | ||
, "set and get <buffers> with a binary image": function(exit){ | ||
}); | ||
/** | ||
* Arrays should be converted to a JSON string, send to the server | ||
* and be automagically JSON.parsed when they are retrieved. | ||
*/ | ||
it("set and get a array", function(done) { | ||
var memcached = new Memcached(common.servers.single) | ||
, message = fs.readFileSync(__dirname + '/fixtures/hotchicks.jpg') | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.get("test:" + testnr, function(error, answer){ | ||
, message = [ | ||
{ | ||
numbers: common.numbers(256) | ||
, alphabet: common.alphabet(256) | ||
, dates: new Date() | ||
, arrays: [1,2,3, 'foo', 'bar'] | ||
} | ||
, { | ||
numbers: common.numbers(256) | ||
, alphabet: common.alphabet(256) | ||
, dates: new Date() | ||
, arrays: [1,2,3, 'foo', 'bar'] | ||
} | ||
] | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(answer.toString('binary') === answer.toString('binary')); | ||
memcached.end(); // close connections | ||
ok.should.be.true; | ||
memcached.get("test:" + testnr, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(Array.isArray(answer)); | ||
assert.ok(JSON.stringify(answer) == JSON.stringify(message)); | ||
memcached.end(); // close connections | ||
assert.equal(callbacks, 2); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
// make sure all callbacks are called | ||
exit(function(){ | ||
assert.equal(callbacks, 2); | ||
}); | ||
} | ||
}); | ||
/** | ||
* Get binary of the lipsum.txt, send it over the connection and see | ||
* if after we retrieved it, it's still the same when we compare the | ||
* original with the memcached based version. | ||
* | ||
* A use case for this would be storing <buffers> with HTML data in | ||
* memcached as a single cache pool.. | ||
*/ | ||
, "set and get <buffers> with a binary text file": function(exit){ | ||
/** | ||
* Buffers are commonly used for binary transports So we need to make sure | ||
* we support them properly. But please note, that we need to compare the | ||
* strings on a "binary" level, because that is the encoding the Memcached | ||
* client will be using, as there is no indication of what encoding the | ||
* buffer is in. | ||
*/ | ||
it("set and get <buffers> with a binary image", function(done) { | ||
var memcached = new Memcached(common.servers.single) | ||
, message = fs.readFileSync(__dirname + '/fixtures/lipsum.txt') | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.get("test:" + testnr, function(error, answer){ | ||
, message = fs.readFileSync(__dirname + '/fixtures/hotchicks.jpg') | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(answer.toString('utf8') === answer.toString('utf8')); | ||
assert.ok(answer.toString('ascii') === answer.toString('ascii')); | ||
memcached.end(); // close connections | ||
}); | ||
}); | ||
// make sure all callbacks are called | ||
exit(function(){ | ||
assert.equal(callbacks, 2); | ||
}); | ||
} | ||
ok.should.be.true; | ||
/** | ||
* Not only small strings, but also large strings should be processed | ||
* without any issues. | ||
*/ | ||
, "set and get large text files": function(exit){ | ||
var memcached = new Memcached(common.servers.single) | ||
, message = fs.readFileSync(__dirname + '/fixtures/lipsum.txt', 'utf8') | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.get("test:" + testnr, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(typeof answer === 'string'); | ||
answer.should.eql(message); | ||
memcached.end(); // close connections | ||
memcached.get("test:" + testnr, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(answer.toString('binary') === message.toString('binary')); | ||
memcached.end(); // close connections | ||
assert.equal(callbacks, 2); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
// make sure all callbacks are called | ||
exit(function(){ | ||
assert.equal(callbacks, 2); | ||
}); | ||
} | ||
}); | ||
/** | ||
* A multi get on a single server is different than a multi server multi get | ||
* as a multi server multi get will need to do a multi get over multiple servers | ||
* yes, that's allot of multi's in one single sentence thanks for noticing | ||
*/ | ||
, "multi get single server": function(exit){ | ||
/** | ||
* Get binary of the lipsum.txt, send it over the connection and see | ||
* if after we retrieved it, it's still the same when we compare the | ||
* original with the memcached based version. | ||
* | ||
* A use case for this would be storing <buffers> with HTML data in | ||
* memcached as a single cache pool.. | ||
*/ | ||
it("set and get <buffers> with a binary text file", function(done) { | ||
var memcached = new Memcached(common.servers.single) | ||
, message = common.alphabet(256) | ||
, message2 = common.alphabet(256) | ||
, message = fs.readFileSync(__dirname + '/fixtures/lipsum.txt') | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test1:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.set("test2:" + testnr, message2, 1000, function(error, ok){ | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.get(["test1:" + testnr, "test2:" + testnr], function(error, answer){ | ||
memcached.get("test:" + testnr, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(typeof answer === 'object'); | ||
answer["test1:" + testnr].should.eql(message); | ||
answer["test2:" + testnr].should.eql(message2); | ||
assert.ok(answer.toString('utf8') === answer.toString('utf8')); | ||
assert.ok(answer.toString('ascii') === answer.toString('ascii')); | ||
memcached.end(); // close connections | ||
assert.equal(callbacks, 2); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
// make sure all callbacks are called | ||
exit(function(){ | ||
assert.equal(callbacks, 3); | ||
}); | ||
} | ||
/** | ||
* A multi get on a single server is different than a multi server multi get | ||
* as a multi server multi get will need to do a multi get over multiple servers | ||
* yes, that's allot of multi's in one single sentence thanks for noticing | ||
*/ | ||
, "multi get multi server": function(exit){ | ||
var memcached = new Memcached(common.servers.multi) | ||
, message = common.alphabet(256) | ||
, message2 = common.alphabet(256) | ||
}); | ||
/** | ||
* Not only small strings, but also large strings should be processed | ||
* without any issues. | ||
*/ | ||
it("set and get large text files", function(done) { | ||
var memcached = new Memcached(common.servers.single) | ||
, message = fs.readFileSync(__dirname + '/fixtures/lipsum.txt', 'utf8') | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test1:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.set("test2:" + testnr, message2, 1000, function(error, ok){ | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.get(["test1:" + testnr,"test2:" + testnr], function(error, answer){ | ||
memcached.get("test:" + testnr, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(typeof answer === 'object'); | ||
answer["test1:" + testnr].should.eql(message); | ||
answer["test2:" + testnr].should.eql(message2); | ||
assert.ok(typeof answer === 'string'); | ||
answer.should.eql(message); | ||
memcached.end(); // close connections | ||
assert.equal(callbacks, 2); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
/** | ||
* A multi get on a single server is different than a multi server multi get | ||
* as a multi server multi get will need to do a multi get over multiple servers | ||
* yes, that's allot of multi's in one single sentence thanks for noticing | ||
*/ | ||
it("multi get single server", function(done) { | ||
var memcached = new Memcached(common.servers.single) | ||
, message = common.alphabet(256) | ||
, message2 = common.alphabet(256) | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test1:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.set("test2:" + testnr, message2, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.get(["test1:" + testnr, "test2:" + testnr], function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(typeof answer === 'object'); | ||
answer["test1:" + testnr].should.eql(message); | ||
answer["test2:" + testnr].should.eql(message2); | ||
memcached.end(); // close connections | ||
assert.equal(callbacks, 3); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
/** | ||
* A multi get on a single server is different than a multi server multi get | ||
* as a multi server multi get will need to do a multi get over multiple servers | ||
* yes, that's allot of multi's in one single sentence thanks for noticing | ||
*/ | ||
it("multi get multi server", function(done) { | ||
var memcached = new Memcached(common.servers.multi) | ||
, message = common.alphabet(256) | ||
, message2 = common.alphabet(256) | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test1:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.set("test2:" + testnr, message2, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.get(["test1:" + testnr,"test2:" + testnr], function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(typeof answer === 'object'); | ||
answer["test1:" + testnr].should.eql(message); | ||
answer["test2:" + testnr].should.eql(message2); | ||
memcached.end(); // close connections | ||
assert.equal(callbacks, 3); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
// make sure all callbacks are called | ||
exit(function(){ | ||
assert.equal(callbacks, 3); | ||
}); | ||
} | ||
}; | ||
}); |
@@ -6,7 +6,6 @@ /** | ||
var assert = require('assert') | ||
, should = require('should') | ||
, common = require('./common') | ||
, Memcached = require('../'); | ||
global.testnumbers = global.testnumbers || +(Math.random(10) * 1000).toFixed(); | ||
global.testnumbers = global.testnumbers || +(Math.random(10) * 1000000).toFixed(); | ||
@@ -17,158 +16,141 @@ /** | ||
*/ | ||
describe("Memcached INCR DECR", function() { | ||
/** | ||
* Simple increments.. Just because.. we can :D | ||
*/ | ||
it("simple incr", function(done) { | ||
var memcached = new Memcached(common.servers.single) | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
module.exports = { | ||
memcached.set("test:" + testnr, 1, 1000, function(error, ok){ | ||
++callbacks; | ||
/** | ||
* Simple increments.. Just because.. we can :D | ||
*/ | ||
"simple incr": function(exit){ | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.incr("test:" + testnr, 1, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.equal(2); | ||
memcached.end(); // close connections | ||
assert.equal(callbacks, 2); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
/** | ||
* Simple decrement.. So we know that works as well. Nothing special here | ||
* move on. | ||
*/ | ||
it("simple decr", function(done) { | ||
var memcached = new Memcached(common.servers.single) | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, 1, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.incr("test:" + testnr, 1, function(error, ok){ | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, 0, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.equal(2); | ||
memcached.end(); // close connections | ||
ok.should.be.true; | ||
memcached.incr("test:" + testnr, 10, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.equal(10); | ||
memcached.decr("test:" + testnr, 1, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
answer.should.be.equal(9); | ||
memcached.end(); // close connections | ||
assert.equal(callbacks, 3); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
// make sure all callbacks are called | ||
exit(function(){ | ||
assert.equal(callbacks, 2); | ||
}); | ||
} | ||
/** | ||
* Simple decrement.. So we know that works as well. Nothing special here | ||
* move on. | ||
*/ | ||
, "simple decr": function(exit){ | ||
}); | ||
/** | ||
* According to the spec, incr should just work fine on keys that | ||
* have intergers.. So lets test that. | ||
*/ | ||
it("simple increment on a large number", function(done) { | ||
var memcached = new Memcached(common.servers.single) | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, 0, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.incr("test:" + testnr, 10, function(error, ok){ | ||
, message = common.numbers(10) | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.equal(10); | ||
memcached.decr("test:" + testnr, 1, function(error, answer){ | ||
ok.should.be.true; | ||
memcached.incr("test:" + testnr, 1, function(error, answer){ | ||
++callbacks; | ||
assert.ok(!error); | ||
answer.should.be.equal(9); | ||
assert.ok(+answer === (message + 1)); | ||
memcached.end(); // close connections | ||
assert.equal(callbacks, 2); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
// make sure all callbacks are called | ||
exit(function(){ | ||
assert.equal(callbacks, 3); | ||
}); | ||
} | ||
}); | ||
/** | ||
* According to the spec, incr should just work fine on keys that | ||
* have intergers.. So lets test that. | ||
*/ | ||
, "Simple increment on a large number": function(exit){ | ||
/** | ||
* decrementing on a unkonwn key should fail. | ||
*/ | ||
it("decrement on a unknown key", function(done) { | ||
var memcached = new Memcached(common.servers.single) | ||
, message = common.numbers(10) | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, message, 1000, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.incr("test:" + testnr, 1, function(error, answer){ | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.decr("test:" + testnr, 1, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
assert.ok(+answer === (message + 1)); | ||
ok.should.be.false; | ||
memcached.end(); // close connections | ||
assert.equal(callbacks, 1); | ||
done(); | ||
}); | ||
}); | ||
// make sure all callbacks are called | ||
exit(function(){ | ||
assert.equal(callbacks, 2); | ||
}); | ||
} | ||
}); | ||
/** | ||
* decrementing on a unkonwn key should fail. | ||
*/ | ||
, "decrement on a unknown key": function(exit){ | ||
/** | ||
* We can only increment on a integer, not on a string. | ||
*/ | ||
it("incrementing on a non string value throws a client_error", function(done) { | ||
var memcached = new Memcached(common.servers.single) | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.decr("test:" + testnr, 1, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.false; | ||
memcached.end(); // close connections | ||
}); | ||
// make sure all callbacks are called | ||
exit(function(){ | ||
assert.equal(callbacks, 1); | ||
}); | ||
} | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
/** | ||
* We can only increment on a integer, not on a string. | ||
*/ | ||
, "Incrementing on a non string value throws a client_error": function(exit){ | ||
var memcached = new Memcached(common.servers.single) | ||
, testnr = ++global.testnumbers | ||
, callbacks = 0; | ||
memcached.set("test:" + testnr, "zing!", 0, function(error, ok){ | ||
++callbacks; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.incr("test:" + testnr, 1, function(error, ok){ | ||
memcached.set("test:" + testnr, "zing!", 0, function(error, ok){ | ||
++callbacks; | ||
assert.ok(error); | ||
ok.should.be.false; | ||
memcached.end(); // close connections; | ||
assert.ok(!error); | ||
ok.should.be.true; | ||
memcached.incr("test:" + testnr, 1, function(error, ok){ | ||
++callbacks; | ||
assert.ok(error); | ||
ok.should.be.false; | ||
memcached.end(); // close connections; | ||
assert.equal(callbacks, 2); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
// make sure all callbacks are called | ||
exit(function(){ | ||
assert.equal(callbacks, 2); | ||
}); | ||
} | ||
}; | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
Non-existent author
Supply chain riskThe package was published by an npm account that no longer exists.
Found 1 instance in 1 package
Wildcard dependency
QualityPackage has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version.
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
Wildcard dependency
QualityPackage has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
248
6
5
430006
2
57
2186
1
Updatedhashring@*