Socket
Socket
Sign inDemoInstall

memcached

Package Overview
Dependencies
3
Maintainers
1
Versions
33
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.0.5 to 0.1.0

.npmignore

11

examples/cluster_memcached_connect.js

@@ -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;

@@ -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

SocketSocket SOC 2 Logo

Product

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

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc