Redback

A high-level Redis library for Node.JS

Redback

lib/Redback.js

Module dependencies.

var redis = require('redis'),
    Structure = require('./Structure'),
    Channel = require('./Channel').Channel,
    Cache = require('./Cache').Cache;

Define the available Redis structures.

var base = ['Hash','List','Set','SortedSet','Bitfield'];

Define the available advanced structures.

var advanced = ['KeyPair','DensitySet','CappedList','SocialGraph',
                'FullText', 'Queue', 'RateLimit'];

The Redback object wraps the Redis client and acts as a factory for structures.

  • param: string host (optional)

  • param: int port (optional)

  • param: Object options (optional)

  • api: public

var Redback = exports.Redback = function (host, port, options) {
    this.client = redis.createClient.apply(this, arguments);
    this.namespace = '';
    if (typeof options === 'object') {
        this.namespace = options.namespace || '';
    }
}

Create a new Cache.

  • param: string namespace (optional)

  • return: Cache

  • api: public

Redback.prototype.createCache = function (namespace) {
    namespace = namespace || 'cache';
    if (this.namespace.length) {
        namespace = this.namespace + ':' + namespace;
    }
    return new Cache(this.client, namespace);
}

Create a new Channel.

  • param: string channel - the channel name

  • return: Channel

  • api: public

Redback.prototype.createChannel = function (channel) {
    if (!channel) {
        throw new Error('A channel key is required');
    }
    if (this.namespace.length) {
        channel = this.namespace + ':' + channel;
    }
    return new Channel(this.client, channel);
}

Send a (BG)SAVE command to Redis.

  • param: string background (optional - default is false)

  • param: Function callback

  • return: this

  • api: public

Redback.prototype.save = function (background, callback) {
    if (typeof background === 'function') {
        callback = background;
        this.client.save(callback);
    } else {
        this.client.bgsave(callback);
    }
    return this;
}

Close the connection to Redis.

  • return: this

  • api: public

Redback.prototype.quit = function () {
    this.client.quit();
    return this;
}

Create a new Redback client.

  • param: string host (optional)

  • param: int port (optional)

  • param: Object options (optional)

  • api: public

exports.createClient = function (host, port, options) {
    return new Redback(host, port, options);
}

Add the Redis structures from ./base_structures

base.forEach(function (structure) {
    Redback.prototype.addStructure(structure,
        require('./base_structures/' + structure)[structure]);
});

Add the advanced structures from ./advanced_structures

advanced.forEach(function (structure) {
    Redback.prototype.addStructure(structure,
        require('./advanced_structures/' + structure)[structure]);
});

Redis constants.

Redback.prototype.INF  = exports.INF = '+inf';
Redback.prototype.NINF = exports.NINF = '-inf';

Export prototypes so that they can be extended.

exports.Client = redis.RedisClient;
exports.Structure = Structure.Structure;
exports.Cache = Cache;
exports.Channel = Channel;

Structure

lib/Structure.js

All Redback structures inherit from this.

var Structure = exports.Structure = function () {};

Create a new Structure by extending the base Structure.

  • param: Object methods (optional)

  • return: structure

  • api: public

exports.new = function (methods) {
    return Structure.prototype.extend(methods);
}

Expire the structure after a certain number of seconds.

  • param: int ttl

  • param: Function callback (optional)

  • return: this

  • api: public

Structure.prototype.expire = function (ttl, callback) {
    callback = callback || function () {};
    this.client.expire(this.key, ttl, callback);
    return this;
}

Expire the structure at a certain date.

  • param: Date when

  • param: Function callback (optional)

  • return: this

  • api: public

Structure.prototype.expireAt = function (when, callback) {
    callback = callback || function () {};
    if (typeof when.getTime === 'function') {
        when = Math.round(when.getTime() / 1000); //ms => s
    }
    this.client.expireat(this.key, when, callback);
    return this;
}

Get the number of seconds before the structure expires.

  • param: Function callback

  • return: this

  • api: public

Structure.prototype.ttl = function (callback) {
    this.client.ttl(this.key, callback);
    return this;
}

Checks whether the structure has an expiry.

  • param: Function callback

  • return: this

  • api: public

Structure.prototype.isVolatile = function (callback) {
    this.client.ttl(this.key, function (err, ttl) {
        if (err) return callback(err, null);
        callback(null, ttl != -1);
    });
    return this;
}

Remove the structure's associated expiry.

  • param: Function callback (optional)

  • return: this

  • api: public

Structure.prototype.persist = function (callback) {
    callback = callback || function () {};
    this.client.persist(this.key, callback);
    return this;
}

Remove the structure from the Redis database.

  • param: Function callback (optional)

  • return: this

  • api: public

Structure.prototype.destroy =
Structure.prototype.flush = function (callback) {
    callback = callback || function () {};
    this.client.del(this.key, callback);
    return this;
}

A helper for creating atomically auto-incrementing keys.

  • param: Function callback

  • return: this

  • api: public

Structure.prototype.autoincrement = function (callback) {
    var key = this.key + ':_autoinc',
        multi = this.client.multi();
    multi.setnx(key, 1).get(key).incr(key);
    multi.exec(function (err, replies) {
        if (err) return callback(err, null);
        callback(null, replies[1]);
    });
    return this;
}

Takes a redback structure or key string and returns the key.

  • param: string | Object key

  • return: string key

  • api: public

Structure.prototype.getKey = function (key, which) {
    which = which || 'key';
    if (typeof key[which] !== 'undefined') {
        return key[which];
    }
    return key;
}

A helper that extracts the Redis keys from many Structure or string arguments.

  • param: Array structures

  • param: Function callback

  • return: this

  • api: public

Structure.prototype.getKeys = function (structures, which) {
    var structures = Array.prototype.slice.call(structures),
        callback = structures.pop(),
        self = this,
        keys = [];
    for (var i = 0, l = structures.length; i < l; i++) {
        if (Array.isArray(structures[i])) {
            structures[i].forEach(function (structure) {
                keys.push(self.getKey(structure, which));
            });
        } else {
            keys.push(this.getKey(structures[i], which));
        }
    }
    keys.push(callback);
    return keys;
}

Add the namespace on to a key.

  • param: string key

  • return: string namespaced_key

  • api: public

Structure.prototype.namespaceKey = function (key) {
    key = key || '';
    if (this.namespace.length) {
        key = this.namespace + ':' + key;
    }
    return key;
}

Extend the structure.

  • param: Object methods (optional)

  • return: this

  • api: public

Structure.prototype.extend = function (methods) {
    var structure = function (client, key, namespace, init_args) {
        this.client = client;
        this.namespace = namespace || '';
        if (!key) {
            throw new Error('A key is required');
        }
        if (Array.isArray(key)) {
            key = key.join(':');
        }
        this.id = key;
        if (this.namespace.length) {
            key = this.namespace + ':' + key;
        }
        this.key = key;
        if (typeof this.init === 'function') {
            this.init.apply(this, init_args);
        }
    }, ctor = function () {
        this.constructor = structure;
    }
    ctor.prototype = this;
    structure.prototype = new ctor;
    structure.__super__ = this;
    if (typeof methods === 'object') {
        for (var i in methods) {
            structure.prototype[i] = methods[i];
        }
    }
    return structure;
}

Create a random key for temporary use.

  • return: string random_key

  • api: public

Structure.prototype.randomKey = function () {
    return Math.random();
}

Get the type of the current structure.

  • param: Function callback

  • return: this

  • api: public

Structure.prototype.type = function (callback) {
    this.client.type(this.key, callback);
    return this;
}

Rename the structure (change the Redis key).

  • param: string new_key

  • param: Function callback

  • return: this

  • api: public

Structure.prototype.rename = function (new_key, callback) {
    var self = this;
    new_key = this.namespaceKey(new_key);
    this.client.rename(this.key, new_key, function (err) {
        if (err) return callback(err, null);
        self.key = new_key;
        callback();
    });
    return this;
}

Sort all elements in the structure.

Options

limit, offset, by, get, alpha, desc, store

Reference

http://redis.io/commands/sort

  • param: object options

  • param: Function callback

  • return: this

  • api: public

Structure.prototype.sort = function (options, callback) {
    var args = [this.key];

    //SORT key [BY pattern] [LIMIT offset count]
    //   [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]
    if (typeof options.by !== 'undefined') {
        args.push('BY', options.by);
    }
    if (typeof options.limit !== 'undefined') {
        args.push('LIMIT');
        if (typeof options.offset !== 'undefined') {
            args.push(options.offset);
        }
        args.push(options.limit);
    }
    if (typeof options.get !== 'undefined') {
        if (Array.isArray(options.get)) {
            options.get.forEach(function (pattern) {
                args.push('GET', pattern);
            });
        } else {
            args.push('GET', options.get);
        }
    }
    if (typeof options.desc !== 'undefined' && options.desc) {
        args.push('DESC');
    }
    if (typeof options.alpha !== 'undefined' && options.alpha) {
        args.push('ALPHA');
    }
    if (typeof options.store !== 'undefined') {
        args.push('STORE', options.store);
    }
    this.client.sort.apply(this.client, args);
    return this;
}

Bitfield

lib/base_structures/Bitfield.js

Module dependencies.

var Structure = require('../Structure');

Wrap the Redis bit commands.

Usage

redback.createBitfield(key);

Reference

http://redis.io/commands#string

Redis Structure

(namespace:)key = string

var Bitfield = exports.Bitfield = Structure.new();

Get a single bit

  • param: int bit

  • param: Function callback (optional)

  • return: this

  • api: public

Bitfield.prototype.get = function (bit, callback) {
    callback = callback || function () {};
    this.client.getbit(this.key, bit, callback);
    return this;
}

Set a single bit. The callback receives the previous value.

  • param: int bit

  • param: bool value

  • param: Function callback (optional)

  • return: this

  • api: public

Bitfield.prototype.set = function (bit, value, callback) {
    callback = callback || function () {};
    this.client.setbit(this.key, bit, value ? 1 : 0, callback);
    return this;
}

Hash

lib/base_structures/Hash.js

Module dependencies.

var Structure = require('../Structure');

A wrapper for the Redis hash type.

Usage

redback.createHash(key);

Reference

http://redis.io/topics/data-types#hashes

Redis Structure

(namespace:)key = hash(key => value)

var Hash = exports.Hash = Structure.new();

Get an array of hash keys.

  • param: Function callback

  • return: this

  • api: public

Hash.prototype.keys = function (callback) {
    this.client.hkeys(this.key, callback);
    return this;
}

Get an array of hash values.

  • param: Function callback

  • return: this

  • api: public

Hash.prototype.values = function (callback) {
    this.client.hvals(this.key, callback);
    return this;
}

Get the number of hash keys.

  • param: Function callback

  • return: this

  • api: public

Hash.prototype.length = function (callback) {
    this.client.hlen(this.key, callback);
    return this;
}

Delete a hash key.

  • param: string hash_key

  • param: Function callback (optional)

  • return: this

  • api: public

Hash.prototype.delete = Hash.prototype.del = function (hash_key, callback) {
    callback = callback || function () {};
    this.client.hdel(this.key, hash_key, callback);
    return this;
}

Checks whether a hash key exists.

  • param: string hash_key

  • param: Function callback

  • return: this

  • api: public

Hash.prototype.exists = function (hash_key, callback) {
    this.client.hexists(this.key, hash_key, callback);
    return this;
}

Sets one or more key/value pairs.

To set one key/value pair: hash.set('foo', 'bar', callback);

To set multiple: hash.set({key1:'value1', key2:'value2}, callback);

  • param: string | Object hash_key

  • param: string value (optional)

  • param: Function callback (optional)

  • return: this

  • api: public

Hash.prototype.set = function (hash_key, value, callback) {
    if (typeof hash_key === 'object') {
        callback = value || function () {};
        this.client.hmset(this.key, hash_key, callback);
    } else {
        callback = callback || function () {};
        this.client.hset(this.key, hash_key, value, callback);
    }
    return this;
}

Sets a key/value pair if the key doesn't already exist.

  • param: string hash_key

  • param: string value

  • param: Function callback

  • return: this

  • api: public

Hash.prototype.add = function (hash_key, value, callback) {
    callback = callback || function () {};
    this.client.hsetnx(this.key, hash_key, value, callback);
    return this;
}

Gets one or more key/value pairs.

To get all key/value pairs in the hash: hash.get('foo', callback);

To get certain key/value pairs: hash.get(['foo','bar'], callback); hash.get('foo', callback);

  • param: string hash_key (optional)

  • param: Function callback

  • return: this

  • api: public

Hash.prototype.get = function (hash_key, callback) {
    if (typeof hash_key === 'function') {
        callback = hash_key;
        this.client.hgetall(this.key, callback);
    } else if (Array.isArray(hash_key)) {
        this.client.hmget(this.key, hash_key, callback)
    } else {
        this.client.hget(this.key, hash_key, callback);
    }
    return this;
}

Increment the specified hash value.

  • param: string hash_key

  • param: int amount (optional - default is 1)

  • param: Function callback (optional)

  • return: this

  • api: public

Hash.prototype.increment =
Hash.prototype.incrBy = function (hash_key, amount, callback) {
    callback = callback || function () {};
    if (typeof amount === 'function') {
        callback = amount;
        amount = 1;
    }
    this.client.hincrby(this.key, hash_key, amount, callback);
    return this;
}

Decrement the specified hash value.

  • param: string hash_key

  • param: int amount (optional - default is 1)

  • param: Function callback (optional)

  • return: this

  • api: public

Hash.prototype.decrement =
Hash.prototype.decrBy = function (hash_key, amount, callback) {
    callback = callback || function () {};
    if (typeof amount === 'function') {
        callback = amount;
        amount = 1;
    }
    this.client.hincrby(this.key, hash_key, -1 * amount, callback);
    return this;
}

List

lib/base_structures/List.js

Module dependencies.

var Structure = require('../Structure');

A wrapper for the Redis list type.

Usage

redback.createList(key);

Reference

http://redis.io/topics/data-types#lists

Redis Structure

(namespace:)key = list(values)

var List = exports.List = Structure.new();

Get the list as an array.

  • param: Function callback

  • return: this

  • api: public

List.prototype.values = function (callback) {
    this.client.lrange(this.key, 0, -1, callback);
    return this;
}

Get a range of list elements.

  • param: int start

  • param: count end (optional - defaults to the last element)

  • param: Function callback

  • return: this

  • api: public

List.prototype.range = function (start, end, callback) {
    if (typeof end === 'function') {
        callback = end;
        end = -1;
    }
    this.client.lrange(this.key, start, end, callback);
    return this;
}

Get one or more elements starting at the specified index.

  • param: int index

  • param: count count (optional - default is 1)

  • param: Function callback

  • return: this

  • api: public

List.prototype.get = function (index, count, callback) {
    if (typeof count === 'function') {
        callback = count;
        this.client.lindex(this.key, index, callback);
    } else {
        this.client.lrange(this.key, index, index + count - 1, callback);
    }
    return this;
}

Cap the length of the list.

  • param: int length

  • param: bool keep_earliest (optional - default is false)

  • param: Function callback (optional)

  • return: this

  • api: public

List.prototype.cap = function (length, keep_earliest, callback) {
    callback = callback || function () {};
    var start = 0, end = -1;
    if (typeof keep_earliest === 'function') {
        //Keep the last `length` elements
        start = -1 * length;
        callback = keep_earliest;
    } else {
        //Keep the first `length` elements
        end = length - 1;
    }
    this.client.ltrim(this.key, start, end, callback);
    return this;
}

Remove one or more list elements matching the value.

  • param: string value

  • param: bool count (optional - default is 1)

  • param: Function callback (optional)

  • return: this

  • api: public

List.prototype.remove = function (value, count, callback) {
    callback = callback || function () {};
    if (typeof count === 'function') {
        callback = count;
        count = 1;
    }
    this.client.lrem(this.key, count, value, callback);
    return this;
}

Trim a list to the specified bounds.

  • param: int start

  • param: int end

  • param: Function callback (optional)

  • return: this

  • api: public

List.prototype.trim = function (start, end, callback) {
    callback = callback || function () {};
    this.client.ltrim(this.key, start, end, callback);
    return this;
}

Insert an element before the specified pivot.

  • param: int pivot

  • param: string value

  • param: Function callback (optional)

  • return: this

  • api: public

List.prototype.insertBefore = function (pivot, value, callback) {
    callback = callback || function () {};
    this.client.linsert(this.key, 'BEFORE', pivot, value, callback);
    return this;
}

Insert an element after the specified pivot.

  • param: int pivot

  • param: string value

  • param: Function callback (optional)

  • return: this

  • api: public

List.prototype.insertAfter = function (pivot, value, callback) {
    callback = callback || function () {};
    this.client.linsert(this.key, 'AFTER', pivot, value, callback);
    return this;
}

Set the element at the specified index.

  • param: int index

  • param: string value

  • param: Function callback (optional)

  • return: this

  • api: public

List.prototype.set = function (index, value, callback) {
    callback = callback || function () {};
    this.client.lset(this.key, index, value, callback);
    return this;
}

Get the number of elements in the list.

  • param: Function callback

  • return: this

  • api: public

List.prototype.length = function (callback) {
    this.client.llen(this.key, callback);
    return this;
}

Get and remove the last element in the list. The first param can be used to block the process and wait until list elements are available. If the list is empty in both examples below, the first example will return null, while the second will wait for up to 3 seconds. If a list element becomes available during the 3 seconds it will be returned, otherwise null will be returned.

Example

list.shift(callback);

Blocking Example

list.shift(3, callback)

  • param: int wait (optional) - seconds to block

  • param: Function callback

  • return: this

  • api: public

List.prototype.shift = function (wait, callback) {
    if (typeof wait === 'function') {
        callback = wait;
        this.client.lpop(this.key, callback);
    } else {
        this.client.blpop(this.key, wait, callback);
    }
    return this;
}

Get and remove the last element in the list. The first param can be used to block the process and wait until list elements are available. If the list is empty in both examples below, the first example will return null, while the second will wait for up to 3 seconds. If a list element becomes available during the 3 seconds it will be returned, otherwise null will be returned.

Example

list.pop(callback);

Blocking Example

list.pop(3, callback)

  • param: int wait (optional) - seconds to block

  • param: Function callback

  • return: this

  • api: public

List.prototype.pop = function (wait, callback) {
    if (typeof wait === 'function') {
        callback = wait;
        this.client.rpop(this.key, callback);
    } else {
        this.client.brpop(this.key, wait, callback);
    }
    return this;
}

Add one or more elements to the start of the list.

  • param: string | array value(s)

  • param: Function callback (optional)

  • return: this

  • api: public

List.prototype.unshift = List.prototype.lpush = function (values, callback) {
    callback = callback || function () {};
    if (Array.isArray(values)) {
        var multi = this.client.multi(), key = this.key;
        values.reverse().forEach(function (value) {
            multi.lpush(key, value);
        });
        multi.exec(callback);
    } else {
        this.client.lpush(this.key, values, callback);
    }
    return this;
}

Add one or more elements to the end of the list.

  • param: string | array value(s)

  • param: Function callback (optional)

  • return: this

  • api: public

List.prototype.push = List.prototype.add = function (values, callback) {
    callback = callback || function () {};
    if (Array.isArray(values)) {
        var multi = this.client.multi(), key = this.key;
        values.forEach(function (value) {
            multi.rpush(key, value);
        });
        multi.exec(callback);
    } else {
        this.client.rpush(this.key, values, callback);
    }
    return this;
}

Remove the last element of the list and add it to the start of another list.

  • param: String | List list

  • param: bool wait (optional) - seconds to block while waiting

  • param: Function callback (optional)

  • return: this

  • api: public

List.prototype.popShift = function (list, wait, callback) {
    callback = callback || function () {};
    list = this.getKey(list);
    if (typeof wait === 'function') {
        callback = wait;
        this.client.rpoplpush(this.key, list, callback);
    } else {
        this.client.brpoplpush(this.key, list, wait, callback);
    }
    return this;
}

Set

lib/base_structures/Set.js

Module dependencies.

var Structure = require('../Structure');

A wrapper for the Redis set type.

Usage

redback.createSet(key);

Reference

http://redis.io/topics/data-types#sets

Redis Structure

(namespace:)key = set(elements)

var Set = exports.Set = Structure.new();

Add one or more elements to the set.

  • param: string | Array element(s)

  • param: Function callback (optional)

  • return: this

  • api: public

Set.prototype.add = function (element, callback) {
    callback = callback || function () {};
    if (Array.isArray(element)) {
        return this.addAll(element, callback);
    }
    this.client.sadd(this.key, element, callback);
    return this;
}

Remove one or more elements from the set.

  • param: string | Array element(s)

  • param: Function callback (optional)

  • return: this

  • api: public

Set.prototype.remove = function (element, callback) {
    callback = callback || function () {};
    if (Array.isArray(element)) {
        return this.removeAll(element, callback);
    }
    this.client.srem(this.key, element, callback);
    return this;
}

Get an array of elements in the set.

  • param: Function callback

  • return: this

  • api: public

Set.prototype.elements = Set.prototype.members = function (callback) {
    this.client.smembers(this.key, callback);
    return this;
}

Move an element to another set.

  • param: string | Set dest

  • param: string element

  • param: Function callback (optional)

  • return: this

  • api: public

Set.prototype.move = function (dest, element, callback) {
    callback = callback || function () {};
    this.client.smove(this.key, this.getKey(dest), element, callback);
    return this;
}

Check whether an element exists in the set.

  • param: string element

  • param: Function callback

  • return: this

  • api: public

Set.prototype.exists = Set.prototype.contains = function (element, callback) {
    this.client.sismember(this.key, element, callback);
    return this;
}

Get the length (cardinality) of the set.

  • param: Function callback

  • return: this

  • api: public

Set.prototype.length = Set.prototype.cardinality = function (callback) {
    this.client.scard(this.key, callback);
    return this;
}

Get a random element from the set and optionally remove it.

  • param: bool remove (optional - default is false)

  • param: Function callback

  • return: this

  • api: public

Set.prototype.random = function (remove, callback) {
    if (typeof remove === 'function') {
        callback = remove;
        this.client.srandmember(this.key, callback);
    } else {
        this.client.spop(this.key, callback);
    }
    return this;
}

Get the intersection of one or more sets.

  • param: string | Set | Array set(s)

  • param: Function callback

  • return: this

  • api: public

Set.prototype.inter = function (sets, callback) {
    sets = this.getKeys(arguments);
    sets.unshift(this.key);
    this.client.sinter.apply(this.client, sets);
    return this;
}

Get the intersection of one or more sets and store it another set (dest).

  • param: string | Set dest

  • param: string | Set | Array set(s)

  • param: Function callback

  • return: this

  • api: public

Set.prototype.interStore = function (dest, sets, callback) {
    sets = this.getKeys(arguments);
    dest = sets.shift();
    sets.unshift(dest, this.key);
    this.client.sinterstore.apply(this.client, sets);
    return this;
}

Get the union of one or more sets.

  • param: string | Set | Array set(s)

  • param: Function callback

  • return: this

  • api: public

Set.prototype.union = function (sets, callback) {
    sets = this.getKeys(arguments);
    sets.unshift(this.key);
    this.client.sunion.apply(this.client, sets);
    return this;
}

Get the union of one or more sets and store it another set (dest).

  • param: string | Set dest

  • param: string | Set | Array set(s)

  • param: Function callback

  • return: this

  • api: public

Set.prototype.unionStore = function (dest, sets, callback) {
    sets = this.getKeys(arguments);
    dest = sets.shift();
    sets.unshift(dest, this.key);
    this.client.sunionstore.apply(this.client, sets);
    return this;
}

Get the difference of one or more sets.

  • param: string | Set | Array set(s)

  • param: Function callback

  • return: this

  • api: public

Set.prototype.diff = function (sets, callback) {
    sets = this.getKeys(arguments);
    sets.unshift(this.key);
    this.client.sdiff.apply(this.client, sets);
    return this;
}

Get the difference of one or more sets and store it another set (dest).

  • param: string | Set dest

  • param: string | Set | Array set(s)

  • param: Function callback

  • return: this

  • api: public

Set.prototype.diffStore = function (dest, sets, callback) {
    sets = this.getKeys(arguments);
    dest = sets.shift();
    sets.unshift(dest, this.key);
    this.client.sdiffstore.apply(this.client, sets);
    return this;
}

SortedSet

lib/base_structures/SortedSet.js

Module dependencies.

var Structure = require('../Structure');

A wrapper for the Redis sorted set (zset) type. Each element has a score which is used to rank and order all elements in the set. Elements are ranked from lowest score to highest (the lowest score has a rank of 0)

Usage

redback.createSortedSet(key);

Reference

http://redis.io/topics/data-types#sorted-sets

Redis Structure

(namespace:)key = zset(score => element)

var SortedSet = exports.SortedSet = Structure.new();

Add one or more elements to the set.

To add a single element and score: set.add(12, 'foo', callback);

To add multiple elements/scores: set.add({foo:12, bar:3}, callback);

  • param: int score (optional)

  • param: string | Object element(s)

  • param: Function callback (optional)

  • return: this

  • api: public

SortedSet.prototype.add = function (score, element, callback) {
    callback = callback || function () {};
    if (typeof score === 'object') {
        callback = element;
        element = score;
        return this.addAll(element, callback);
    }
    this.client.zadd(this.key, score, element, callback);
    return this;
}

Remove one or more elements from the set.

  • param: string | Array element(s)

  • param: Function callback (optional)

  • return: this

  • api: public

SortedSet.prototype.remove = function (element, callback) {
    callback = callback || function () {};
    if (Array.isArray(element)) {
        return this.removeAll(element, callback);
    }
    this.client.zrem(this.key, element, callback);
    return this;
}

Get the number of elements in the set.

  • param: Function callback

  • return: this

  • api: public

SortedSet.prototype.length = function (callback) {
    this.client.zcard(this.key, callback);
    return this;
}

Check whether an element exists in the set.

  • param: string element

  • param: Function callback

  • return: this

  • api: public

SortedSet.prototype.exists =
SortedSet.prototype.contains = function (element, callback) {
    this.client.zscore(this.key, element, function (err, score) {
        callback(err, score != null);
    });
    return this;
}

Get the rank of the specified element.

  • param: string element

  • param: Function callback

  • return: this

  • api: public

SortedSet.prototype.rank = function (element, callback) {
    this.client.zrank(this.key, element, callback)
    return this;
}

Get the score of the specified element.

  • param: string element

  • param: Function callback

  • return: this

  • api: public

SortedSet.prototype.score = function (element, callback) {
    this.client.zscore(this.key, element, callback)
    return this;
}

Increment the specified element's score.

  • param: string element

  • param: int amount (optional - default is 1)

  • param: Function callback (optional)

  • return: this ;

  • api: public

SortedSet.prototype.increment =
SortedSet.prototype.incrBy = function (element, amount, callback) {
    callback = callback || function () {};
    if (typeof amount === 'function') {
        callback = amount;
        amount = 1;
    }
    this.client.zincrby(this.key, amount, element, callback);
    return this;
}

Decrement the specified element's score.

  • param: string element

  • param: int amount (optional - default is 1)

  • param: Function callback (optional)

  • return: this ;

  • api: public

SortedSet.prototype.decrement =
SortedSet.prototype.decrBy = function (element, amount, callback) {
    callback = callback || function () {};
    if (typeof amount === 'function') {
        callback = amount;
        amount = 1;
    }
    this.client.zincrby(this.key, -1 * amount, element, callback);
    return this;
}

Get all elements in the set as an object {element: score, ...}. If without_scores is true then just an array of elements is returned.

  • param: bool without_scores (optional - scores are included by default)

  • param: Function callback

  • return: this

  • api: public

SortedSet.prototype.get = function (without_scores, callback) {
    if (typeof without_scores === 'function') {
        callback = without_scores;
        this.client.zrange(this.key, 0, -1, 'WITHSCORES', this.parseScores(callback));
    } else {
        this.client.zrange(this.key, 0, -1, callback);
    }
    return this;
}

Get elements with scores between the specified range. Elements are returned as an object {element: score, ..} and ordered from highest score to lowest.

Note that the start and end range is inclusive and can be an integer or the constants redback.INF to represent infinity, or redback.NINF to represent negative infinity. start must be <= end.

  • param: int start

  • param: int end

  • param: int count (optional) - the maximum number of elements to return

  • param: int offset (optional) - if using count, start at this offset

  • param: Function callback

  • return: this

  • api: public

SortedSet.prototype.getScores = function (start, end, count, offset, callback) {
    if (null === start) start = '-inf';
    if (null === end) end = '+inf';
    if (typeof count === 'function') {
        callback = count;
        this.client.zrangebyscore(this.key, start, end,
            'WITHSCORES', this.parseScores(callback));
        return this;
    } else if (typeof offset === 'function') {
        callback = offset;
        offset = 0;
    }
    this.client.zrangebyscore(this.key, start, end, 'WITHSCORES',
        'LIMIT', offset, count, this.parseScores(callback));
    return this;
}

The same as getScores() but elements are ordered from lowest score to highest.

Note that end must be <= start.

  • param: int start

  • param: int end

  • param: int count (optional) - the maximum number of elements to return

  • param: int offset (optional) - if using count, start at this offset

  • param: Function callback

  • return: this

  • api: public

SortedSet.prototype.getScoresReverse = function (start, end, count, offset, callback) {
    if (null === start) start = '+inf';
    if (null === end) end = '-inf';
    if (typeof count === 'function') {
        callback = count;
        this.client.zrevrangebyscore(this.key, start, end,
            'WITHSCORES', this.parseScores(callback));
        return this;
    } else if (typeof offset === 'function') {
        callback = offset;
        offset = 0;
    }
    this.client.zrevrangebyscore(this.key, start, end, 'WITHSCORES',
        'LIMIT', offset, count, this.parseScores(callback));
    return this;
}

Remove elements with scores between the specified range (inclusive).

  • param: int start

  • param: int end

  • param: Function callback (optional)

  • return: this

  • api: public

SortedSet.prototype.removeScores = function (start, end, callback) {
    callback = callback || function () {};
    if (null === start) start = '-inf';
    if (null === end) end = '+inf';
    this.client.zremrangebyscore(this.key, start, end, callback);
    return this;
}

Count the number of elements with scores between the specified range (inclusive).

  • param: int start

  • param: int end

  • param: Function callback

  • return: this

  • api: public

SortedSet.prototype.countScores = function (start, end, callback) {
    if (null === start) start = '-inf';
    if (null === end) end = '+inf';
    this.client.zcount(this.key, start, end, callback);
    return this;
}

Get elements with ranks between the specified range (inclusive).

To get the first 3 elements in the set (with the highest scores): set.getRanks(0, 2, callback);

To get the last 3 elements in the set (lowest scores): set.getRanks(-3, -1, callback);

  • param: int start

  • param: int end

  • param: Function callback

  • return: this

  • api: public

SortedSet.prototype.getRanks = function (start, end, callback) {
    if (null === start) start = 0;
    if (null === end) end = -1;
    this.client.zrange(this.key, start, end,
        'WITHSCORES', this.parseScores(callback));
    return this;
}

The same as getRanks() but elements are ordered from lowest score to the highest.

Note that start and end have been deliberately switched for consistency.

getScoresReverse(arg1, arg2, ..) expects arg1 >= arg2 and so does this method.

  • param: int end

  • param: int start

  • param: Function callback

  • return: this

  • api: public

SortedSet.prototype.getRanksReverse = function (end, start, callback) {
    if (null === start) start = -1;
    if (null === end) end = 0;
    this.client.zrevrange(this.key, start, end,
        'WITHSCORES', this.parseScores(callback));
    return this;
}

Remove elements with ranks between the specified range (inclusive).

  • param: int start

  • param: int end

  • param: Function callback (optional)

  • return: this

  • api: public

SortedSet.prototype.removeRanks = function (start, end, callback) {
    callback = callback || function () {};
    if (null === start) start = -1;
    if (null === end) end = 0;
    this.client.zremrangebyrank(this.key, start, end, callback);
    return this;
}

Get count elements with the highest scores.

  • param: int count

  • param: Function callback

  • return: this

  • api: public

SortedSet.prototype.highestScores = function (count, callback) {
    this.getRanks(-1 * count, -1, callback);
    return this;
}

Get count elements with the lowest scores.

  • param: int count

  • param: Function callback

  • return: this

  • api: public

SortedSet.prototype.lowestScores = function (count, callback) {
    this.getRanks(0, count - 1, callback);
    return this;
}

Get the intersection of one or more sets. For more information on weights, aggregate functions, etc. see: http://redis.io/commands/zinterstore

  • param: int dest

  • param: string | Set | Array set(s)

  • param: int | Array weights (optional)

  • param: string aggregate (optional) - either SUM, MIN or MAX

  • param: Function callback

  • return: this

  • api: public

SortedSet.prototype.inter = function (dest, sets, weights, aggregate, callback) {
    var args = [], self = this;
    args.push(this.getKey(dest));

    //weights/aggregate are optional
    if (typeof weights === 'function') {
        callback = weights;
        weights = aggregate = false;
    } else if (typeof aggregate === 'function') {
        callback = aggregate;
        aggregate = false;
    }

    //ZINTERSTORE destination numkeys key [key ...]
    //    [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
    if (Array.isArray(sets)) {
        args.push(sets.length);
        sets.forEach(function (set) {
            args.push(self.getKey(set));
        });
    } else {
        args.push(1, this.getKey(sets));
    }
    if (weights) {
        args.push('WEIGHTS');
        if (Array.isArray(weights)) {
            weights.forEach(function (weight) {
                args.push(weight);
            });
        } else {
            args.push(weights);
        }
    }
    if (aggregate) {
        args.push('AGGREGATE', aggregate);
    }
    args.push(callback);
    this.client.zinterstore.apply(this.client, args);
    return this;
}

Get the union of one or more sets. For more information on weights, aggregate functions, etc. see: http://redis.io/commands/zunionstore

  • param: int dest

  • param: string | Set | Array set(s)

  • param: int | Array weights (optional)

  • param: string aggregate (optional) - either SUM, MIN or MAX

  • param: Function callback

  • return: this

  • api: public

SortedSet.prototype.union = function (dest, sets, weights, aggregate, callback) {
    var args = [], self = this;
    args.push(this.getKey(dest));

    //weights/aggregate are optional
    if (typeof weights === 'function') {
        callback = weights;
        weights = aggregate = false;
    } else if (typeof aggregate === 'function') {
        callback = aggregate;
        aggregate = false;
    }

    //ZUNIONSTORE destination numkeys key [key ...]
    //    [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
    if (Array.isArray(sets)) {
        args.push(sets.length);
        sets.forEach(function (set) {
            args.push(self.getKey(set));
        });
    } else {
        args.push(1, this.getKey(sets));
    }
    if (weights) {
        args.push('WEIGHTS');
        if (Array.isArray(weights)) {
            weights.forEach(function (weight) {
                args.push(weight);
            });
        } else {
            args.push(weights);
        }
    }
    if (aggregate) {
        args.push('AGGREGATE', aggregate);
    }
    args.push(callback);
    this.client.zunionstore.apply(this.client, args);
    return this;
}

CappedList

lib/advanced_structures/CappedList.js

Module dependencies.

var List = require('../base_structures/List').List;

A Redis list with a fixed length. Each command that adds a value to the list is followed by an LTRIM command.

Usage

redback.createCappedList(key [, max_length]);

Reference

http://redis.io/topics/data-types#lists http://redis.io/commands/ltrim

Redis Structure

(namespace:)key = list(values)

var CappedList = exports.CappedList = List.prototype.extend();

Insert an element before the specified pivot.

  • param: int pivot

  • param: string value

  • param: Function callback (optional)

  • return: this

  • api: public

CappedList.prototype.insertBefore = function (pivot, value, callback) {
    callback = callback || function () {};
    var multi = this.client.multi()
    multi.linsert(this.key, 'BEFORE', pivot, value);
    multi.ltrim(this.key, -1 * this.len, -1);
    multi.exec(callback);
    return this;
}

Insert an element after the specified pivot.

  • param: int pivot

  • param: string value

  • param: Function callback (optional)

  • return: this

  • api: public

CappedList.prototype.insertAfter = function (pivot, value, callback) {
    callback = callback || function () {};
    var multi = this.client.multi()
    multi.linsert(this.key, 'AFTER', pivot, value);
    multi.ltrim(this.key, -1 * this.len, -1);
    multi.exec(callback);
    return this;
}

Add one or more elements to the start of the list.

  • param: string | array value(s)

  • param: Function callback (optional)

  • return: this

  • api: public

CappedList.prototype.unshift = CappedList.prototype.lpush = function (values, callback) {
    callback = callback || function () {};
    var multi = this.client.multi();
    if (Array.isArray(values)) {
        var key = this.key;
        values.reverse().forEach(function (value) {
            multi.lpush(key, value);
        });
    } else {
        multi.lpush(this.key, values);
    }
    multi.ltrim(this.key, -1 * this.len, -1);
    multi.exec(callback);
    return this;
}

Add one or more elements to the end of the list.

  • param: string | array value(s)

  • param: Function callback (optional)

  • return: this

  • api: public

CappedList.prototype.push = CappedList.prototype.add = function (values, callback) {
    callback = callback || function () {};
    var multi = this.client.multi();
    if (Array.isArray(values)) {
        var key = this.key;
        values.forEach(function (value) {
            multi.rpush(key, value);
        });
    } else {
        multi.rpush(this.key, values);
    }
    multi.ltrim(this.key, -1 * this.len, -1);
    multi.exec(callback);
    return this;
}

DensitySet

lib/advanced_structures/DensitySet.js

Module dependencies.

var SortedSet = require('../base_structures/SortedSet').SortedSet;

The DensitySet is similar to a SortedSet but the ability to explicitly set an element's score has been removed. Instead, adding/removing an element will increment/decrement its score, e.g. DensitySet.add(['foo','foo','foo'], ..) //'foo' has a score of 3

Usage

redback.createDensitySet(key);

Reference

http://redis.io/topics/data-types#sorted-sets

Redis Structure

(namespace:)key = zset(count => element)

var DensitySet = exports.DensitySet = SortedSet.prototype.extend();

Add one or more elements to the set.

  • param: string | Array element(s)

  • param: Function callback (optional)

  • return: this

  • api: public

DensitySet.prototype.add = function (element, callback) {
    callback = callback || function () {};
    if (Array.isArray(element)) {
        return this.addAll(element, callback);
    }
    this.client.zincrby(this.key, 1, element, callback);
    return this;
}

Remove one or more elements from the set.

  • param: string | Array element(s)

  • param: Function callback (optional)

  • return: this

  • api: public

DensitySet.prototype.remove = function (element, callback) {
    callback = callback || function () {};
    if (Array.isArray(element)) {
        return this.removeAll(element, callback);
    }
    var self = this;
    this.client.zincrby(this.key, -1, element, function (err, removed) {
        if (err) return callback(err, null);
        self.client.zremrangebyscore(self.key, '-inf', 0, callback);
    });
    return this;
}

FullText

lib/advanced_structures/FullText.js

Module dependencies.

var Structure = require('../Structure'),
    EventEmitter = require('events').EventEmitter,
    fs = require('fs');

Stop words - words that shouldn't be indexed.

var stopwords = {
    'a':1,'able':1,'about':1,'across':1,'after':1,'all':1,'almost':1,'also':1,'am':1,'among':1,
    'an':1,'and':1,'any':1,'are':1,'as':1,'at':1,'be':1,'because':1,'been':1,'but':1,'by':1,'can':1,
    'cannot':1,'could':1,'dear':1,'did':1,'do':1,'does':1,'either':1,'else':1,'ever':1,'every':1,
    'for':1,'from':1,'get':1,'got':1,'had':1,'has':1,'have':1,'he':1,'her':1,'hers':1,'him':1,'his':1,
    'how':1,'however':1,'i':1,'if':1,'in':1,'into':1,'is':1,'it':1,'its':1,'just':1,'least':1,'let':1,
    'like':1,'likely':1,'may':1,'me':1,'might':1,'most':1,'must':1,'my':1,'neither':1,'no':1,
    'nor':1,'not':1,'of':1,'off':1,'often':1,'on':1,'only':1,'or':1,'other':1,'our':1,'own':1,
    'rather':1,'said':1,'say':1,'says':1,'she':1,'should':1,'since':1,'so':1,'some':1,'than':1,
    'that':1,'the':1,'their':1,'them':1,'then':1,'there':1,'these':1,'they':1,'this':1,'tis':1,
    'to':1,'too':1,'twas':1,'us':1,'wants':1,'was':1,'we':1,'were':1,'what':1,'when':1,'where':1,
    'which':1,'while':1,'who':1,'whom':1,'why':1,'will':1,'with':1,'would':1,'yet':1,'you':1,
    'your':1,'':1
}

A full text index with support for stop words, stemming and a basic boolean search syntax.

Usage

redback.createFullText(key);

Reference

http://redis.io/topics/data-types#sets

Redis Structure

(namespace:)key:<word1> = set(docs) (namespace:)key:<word2> = set(docs) (namespace:)key:<wordN> = set(docs)

var FullText = exports.FullText = Structure.new();

Index one or more documents.

Index One Document

text.indexFile(1, 'document string ...', callback);

Index Many Documents

text.indexFile({1:'docstr1', 2:'docstr2'}, callback);

  • param: int | string id - the document's unique identifier

  • param: string | ReadableStream | Buffer document

  • param: Function callback (optional)

  • return: this

  • api: public

FullText.prototype.index = function (id, document, callback) {
    if (typeof id === 'object') {
        return this.indexAll(id, document);
    } else if (document instanceof EventEmitter &amp;&amp;
            typeof document.readable !== 'undefined' &amp;&amp; document.readable === true) {
        return this.indexStream(id, document, callback);
    } else if (document instanceof Buffer) {
        document = document.toString();
    }
    this.indexed_bytes += document.length;
    var stemmed = this.stemWords(this.extractWords(document));
    this.buildIndex(id, stemmed, callback || function () {});
}

Index one or more files.

Index One Document

text.indexFile(1, '/path/to/file', callback);

Index Many Documents

text.indexFile({1:'file1', 2:'file2'}, callback);

  • param: int | string | Object id

  • param: string filename (optional)

  • param: Function callback (optional)

  • return: this

  • api: public

FullText.prototype.indexFile =
FullText.prototype.indexFiles = function (id, filename, callback) {
    if (typeof id === 'object') {
        callback = filename;
        var self = this, files = id, ids = Object.keys(files),
            failed = false, remaining = ids.length;
        ids.forEach(function (id) {
            self.indexStream(id, fs.createReadStream(files[id]), function (err) {
                if (failed) {
                    return;
                } else if (err) {
                    return callback(err);
                } else {
                    if (!--remaining) callback();
                }
            });
        });
        return this;
    } else {
        return this.indexStream(id, fs.createReadStream(filename), callback);
    }
}

Search the full text index. Words will be extracted from the search string and used to filter search results. To exclude certain words, prefix them with a hyphen "-".

Basic Search

index.search('foo bar', callback);

Excluding Words

index.search('foo -bar -cool', callback);

  • param: string search

  • param: Function callback

  • api: public

FullText.prototype.search = function (search, callback) {
    var include = [], exclude = [];
    this.stemWords(this.extractWords(search)).forEach(function (word) {
        if (word[0] === '-') {
            exclude.push(word.substr(1));
        } else {
            include.push(word);
        }
    });
    return this._search(include, exclude, callback);
}

Clear the index.

  • param: Function callback (optional)

  • api: public

FullText.prototype.clear = function (callback) {
    var self = this;
    callback = callback || function () {};
    this.client.keys(this.key + ':*', function (err, keys) {
        if (err) return callback(err, null);
        var rem_count = 0, failed = false, remaining = keys.length;
        keys.forEach(function (key) {
            self.client.del(key, function (err, removed) {
                if (failed) {
                    return;
                } else if (err) {
                    failed = true;
                    return callback(err);
                } else {
                    if (removed) rem_count++;
                    if (!--remaining) callback(null, rem_count);
                }
            });
        });
    });
    return this;
}

KeyPair

lib/advanced_structures/KeyPair.js

Module dependencies.

var Structure = require('../Structure');

The KeyPair is a structure where unique values are assigned an ID (like a table with a primary auto-incrementing key and a single unique column). Internally, the KeyPair uses two Redis hashes to provide O(1) lookup by both ID and value.

Usage

redback.createKeyPair(key);

Reference

http://redis.io/topics/data-types#hashes

Redis Structure

(namespace:)key = hash(id => value) (namespace:)key:ids = hash(value => id)

var KeyPair = exports.KeyPair = Structure.new();

Add a unique value to the KeyPair and return its id. If the value already exists, the existing id is returned.

  • param: string | Array value(s)

  • param: Function callback

  • return: this

  • api: public

KeyPair.prototype.add = function (value, callback) {
    //Pass on an array of values to addAll()
    if (Array.isArray(value)) {
        return this.addAll(value, callback);
    }

    var self = this, hashed_value = this.hashValue(value);
    //Check if the value already has an id
    this.client.hget(this.idkey, value, function (err, id) {
        if (err) return callback(err, null);
        if (null !== id) {
            callback(null, id);
        } else {
            //If not, create a new id
            self.autoincrement(function (err, id) {
                if (err) return callback(err, null);

                //Set the id and value simultaneously
                var multi = self.client.multi();
                multi.hsetnx(self.idkey, hashed_value, id);
                multi.hsetnx(self.key, id, value);
                multi.exec(function(err, response) {
                    if (err) return callback(err, null);

                    //Another client may have add at exactly the same time, so do
                    //another get to get the actual stored id
                    self.client.hget(self.idkey, hashed_value, function (err, real_id) {
                        if (err) return callback(err, null);
                        if (real_id == id) {
                            return callback(null, real_id);
                        } else {
                            //Another client did beat us! remove the bad key
                            self.client.hdel(self.key, id, function (err) {
                                if (err) {
                                    callback(err, null);
                                } else {
                                    callback(null, real_id);
                                }
                            });
                        }
                    });
                });
            });
        }
    });
    return this;
}

Add multiple unique values to the KeyPair and return and object containing {value: id, ...}.

  • param: Array values

  • param: Function callback

  • return: this

  • api: public

KeyPair.prototype.addAll = function (values, callback) {
    var self = this,
        remaining = values.length,
        ids = {},
        failed = false;

    values.forEach(function (value) {
        self.add(value, function (err, id) {
            if (failed) {
                return;
            } else if (err) {
                failed = true;
                return callback(err, null);
            } else {
                ids[value] = id;
                if (!--remaining) callback(null, ids);
            }
        });
    });
}

Lookup a unique value and get the associated id.

  • param: string value

  • param: Function callback

  • return: this

  • api: public

KeyPair.prototype.get = function (value, callback) {
    if (typeof value === 'function') {
        callback = value;
        this.client.hgetall(this.key, callback);
    } else if (Array.isArray(value)) {
        for (var i = 0, l = value.length; i &lt; l; i++) {
            value[i] = this.hashValue(value[i]);
        }
        this.client.hmget(this.idkey, value, callback)
    } else {
        this.client.hget(this.idkey, this.hashValue(value), callback);
    }
    return this;
}

Get the value associated with the id.

  • param: int id

  • param: Function callback

  • return: this

  • api: public

KeyPair.prototype.getById = function (id, callback) {
    this.client.hget(this.key, id, callback);
    return this;
}

Get an array of ids.

  • param: Function callback

  • return: this

  • api: public

KeyPair.prototype.ids = function (callback) {
    this.client.hkeys(this.key, callback);
    return this;
}

Get an array of values.

  • param: string value

  • param: Function callback

  • return: this

  • api: public

KeyPair.prototype.values = function (callback) {
    this.client.hvals(this.key, callback);
    return this;
}

Check whether a unique value already exists and has an associated id.

  • param: string value

  • param: Function callback

  • return: this

  • api: public

KeyPair.prototype.exists = function (value, callback) {
    this.client.hexists(this.idkey, this.hashValue(value), callback);
    return this;
}

Checks whether an id exists.

  • param: string value

  • param: Function callback

  • return: this

  • api: public

KeyPair.prototype.idExists = function (id, callback) {
    this.client.hexists(this.key, id, callback);
    return this;
}

Deletes a unique value and its associated id.

  • param: string value

  • param: Function callback (optional)

  • return: this

  • api: public

KeyPair.prototype.delete = function (value, callback) {
    callback = callback || function () {};
    var self = this, value = this.hashValue(value);
    this.client.hget(this.idkey, value, function (err, id) {
        if (err || value == null) return callback(err);
        self._delete(id, value, callback);
    });
    return this;
}

Deletes an id and its associated unique value.

  • param: int id

  • param: Function callback (optional)

  • return: this

  • api: public

KeyPair.prototype.deleteById = function (id, callback) {
    callback = callback || function () {};
    var self = this;
    this.client.hget(this.key, id, function (err, value) {
        if (err || value == null) return callback(err);
        self._delete(id, self.hashValue(value), callback);
    });
    return this;
}

Get the number of unique values.

  • param: Function callback

  • return: this

  • api: public

KeyPair.prototype.length = function (callback) {
    this.client.hlen(this.key, callback);
    return this;
}

Override this method if you need to hash the unique value in the second internal hash (i.e. if values are large).

  • param: string value

  • return: string hashed_value

  • api: public

KeyPair.prototype.hashValue = function (value) {
    return value;
}

Queue

lib/advanced_structures/Queue.js

Module dependencies.

var Structure = require('../Structure'),
    List = require('../base_structures/List').List;

A simple FIFO/LIFO queue.

Usage

redback.createQueue(key [, is_fifo]);

Reference

http://redis.io/topics/data-types#lists http://en.wikipedia.org/wiki/Queue(datastructure)

Redis Structure

(namespace:)key = list(values)

var Queue = exports.Queue = Structure.new();

Add one or more elements to the queue.

  • param: string | Array value(s)

  • param: Function callback (optional)

  • api: public

Queue.prototype.enqueue = Queue.prototype.add = function (values, callback) {
    this.list.unshift(values, callback);
    return this;
}

Remove the next element from the queue.

  • param: int wait (optional) - block for this many seconds

  • param: Function callback

  • api: public

Queue.prototype.dequeue = Queue.prototype.next = function (wait, callback) {
    this.list[this.fifo ? 'pop' : 'shift'](wait, callback);
    return this;
}

RateLimit

lib/advanced_structures/RateLimit.js

Module dependencies.

var Structure = require('../Structure');

See http://chris6f.com/rate-limiting-with-redis

Count the number of times a subject performs an action over an interval in the immediate past - this can be used to rate limit the subject if the count goes over a certain threshold. For example, you could track how many times an IP (the subject) has viewed a page (the action) over a certain time frame and limit them accordingly.

Usage

redback.createRateLimit(action [, options]);

Options

bucket_interval - default is 5 seconds bucket_span - default is 10 minutes subject_expiry - default is 20 minutes

Reference

http://chris6f.com/rate-limiting-with-redis http://redis.io/topics/data-types#hash

Redis Structure

(namespace:)action:<subject1> = hash(bucket => count) (namespace:)action:<subject2> = hash(bucket => count) (namespace:)action:<subjectN> = hash(bucket => count)

var RateLimit = exports.RateLimit = Structure.new();

Increment the count for the specified subject.

  • param: string | Array subject(s)

  • param: Function callback (optional)

  • return: this

  • api: public

RateLimit.prototype.add = function (subject, callback) {
    if (Array.isArray(subject)) {
        return this.addAll(subject, callback);
    }
    var bucket = this.getBucket(), multi = this.client.multi();
    subject = this.key + ':' + subject;

    //Increment the current bucket
    multi.hincrby(subject, bucket, 1)

    //Clear the buckets ahead
    multi.hdel(subject, (bucket + 1) % this.bucket_count)
         .hdel(subject, (bucket + 2) % this.bucket_count)

    //Renew the key TTL
    multi.expire(subject, this.subject_expiry);

    multi.exec(function (err) {
        if (!callback) return;
        if (err) return callback(err);
        callback(null);
    });

    return this;
}

Count the number of times the subject has performed an action in the last interval seconds.

  • param: string subject

  • param: int interval

  • param: Function callback

  • return: this

  • api: public

RateLimit.prototype.count = function (subject, interval, callback) {
    var bucket = this.getBucket(),
        multi = this.client.multi(),
        count = Math.floor(interval / this.bucket_interval);

    subject = this.key + ':' + subject;

    //Get the counts from the previous `count` buckets
    multi.hget(subject, bucket);
    while (count--) {
        multi.hget(subject, (--bucket + this.bucket_count) % this.bucket_count);
    }

    //Add up the counts from each bucket
    multi.exec(function (err, counts) {
        if (err) return callback(err, null);
        for (var count = i = 0, l = counts.length; i &lt; l; i++) {
            if (counts[i]) {
                count += parseInt(counts[i], 10);
            }
        }
        callback(null, count);
    });

    return this;
}

SocialGraph

lib/advanced_structures/SocialGraph.js

Module dependencies.

var Structure = require('../Structure');

Build a social graph similar to Twitter's. User ID can be a string or integer, as long as they're unique.

Usage

redback.createSocialGraph(id [, prefix]);

Reference

http://redis.io/topics/data-types#sets

Redis Structure

(namespace:)(prefix:)id:following = set(ids) (namespace:)(prefix:)id:followers = set(ids)

var SocialGraph = exports.SocialGraph = Structure.new();

Follow one or more users.

  • param: int | SocialGraph | Array user(s)

  • param: Function callback (optional)

  • return: this

  • api: public

SocialGraph.prototype.follow = function (users, callback) {
    var self = this,
        users = this.getKeys(arguments, 'id'),
        multi = this.client.multi();
    if (typeof users[users.length-1] === 'function') {
        callback = users.pop();
    } else {
        callback = function () {};
    }
    users.forEach(function (user) {
        multi.sadd(self.key_prefix + user + ':followers', self.id);
        multi.sadd(self.following, user);
    });
    multi.exec(callback);
    return this;
}

Unfollow one or more users.

  • param: int | SocialGraph | Array user(s)

  • param: Function callback (optional)

  • return: this

  • api: public

SocialGraph.prototype.unfollow = function (users, callback) {
    var self = this,
        users = this.getKeys(arguments, 'id'),
        multi = this.client.multi();
    if (typeof users[users.length-1] === 'function') {
        callback = users.pop();
    } else {
        callback = function () {};
    }
    users.forEach(function (user) {
        multi.srem(self.key_prefix + user + ':followers', self.id);
        multi.srem(self.following, user);
    });
    multi.exec(callback);
    return this;
}

Gets the users whom the current users follows as an array.

  • param: Function callback

  • return: this

  • api: public

SocialGraph.prototype.getFollowing = function (callback) {
    this.client.smembers(this.following, callback);
    return this;
}

Gets an array of users who follow the current user.

  • param: Function callback

  • return: this

  • api: public

SocialGraph.prototype.getFollowers = function (callback) {
    this.client.smembers(this.followers, callback);
    return this;
}

Count how many users the current user follows.

  • param: Function callback

  • return: this

  • api: public

SocialGraph.prototype.countFollowing = function (callback) {
    this.client.scard(this.following, callback);
    return this;
}

Count how many users follow the current user.

  • param: Function callback

  • return: this

  • api: public

SocialGraph.prototype.countFollowers = function (callback) {
    this.client.scard(this.followers, callback);
    return this;
}

Checks whether the current user follows the specified user.

  • param: string | SocialGraph user

  • param: Function callback

  • return: this

  • api: public

SocialGraph.prototype.isFollowing = function (user, callback) {
    user = this.getKey(user, 'id');
    this.client.sismember(this.following, user, callback);
    return this;
}

Checks whether the specified user follows the current user.

  • param: string | SocialGraph user

  • param: Function callback

  • return: this

  • api: public

SocialGraph.prototype.hasFollower = function (user, callback) {
    user = this.getKey(user, 'id');
    this.client.sismember(this.followers, user, callback);
    return this;
}

Gets an array of common followers for one or more users.

  • param: string | SocialGraph | Array user(s)

  • param: Function callback

  • return: this

  • api: public

SocialGraph.prototype.getCommonFollowers = function (users, callback) {
    var users = this.getSocialKeys(arguments, 'followers');
    users.unshift(this.followers);
    this.client.sinter.apply(this.client, users);
    return this;
}

Gets an array of users who are followed by all of the specified user(s).

  • param: string | SocialGraph | Array user(s)

  • param: Function callback

  • return: this

  • api: public

SocialGraph.prototype.getCommonFollowing = function (users, callback) {
    var users = this.getSocialKeys(arguments, 'following');
    users.unshift(this.following);
    this.client.sinter.apply(this.client, users);
    return this;
}

Gets an array of users who follow the current user but do not follow any of the other specified users.

  • param: string | SocialGraph | Array user(s)

  • param: Function callback

  • return: this

  • api: public

SocialGraph.prototype.getDifferentFollowers = function (users, callback) {
    var users = this.getSocialKeys(arguments, 'followers');
    users.unshift(this.followers);
    this.client.sdiff.apply(this.client, users);
    return this;
}

Gets an array of users who are followed by the current user but not any of the other specified users.

  • param: string | SocialGraph | Array user(s)

  • param: Function callback

  • return: this

  • api: public

SocialGraph.prototype.getDifferentFollowing = function (users, callback) {
    var users = this.getSocialKeys(arguments, 'following');
    users.unshift(this.following);
    this.client.sdiff.apply(this.client, users);
    return this;
}

Channel

lib/Channel.js

Module dependencies.

var EventEmitter = require('events').EventEmitter;

Wrap the Redis pub/sub commands.

Usage

redback.createChannel(name);

Reference

http://redis.io/topics/pubsub

var Channel = exports.Channel = function (client, channel_name) {
    this.name = channel_name;
    this.setClient(client);
}

Channel is an event emitter.

Channel.prototype = new EventEmitter();

Bind a new Redis client (e.g. if not exclusively using pub/sub mode).

  • param: Object client

  • return: this

  • api: public

Channel.prototype.setClient = function (client) {
    this.client = client;
    var self = this;
    ['message','subscribe','unsubscribe'].forEach(function (event) {
        self.client.on(event, function (channel, arg) {
            if (channel == self.name) {
                self.emit(event, arg);
            }
        });
    });
    return this;
}

Publish a message to the channel.

  • param: string msg

  • param: Function callback

  • return: this

  • api: public

Channel.prototype.publish = function (msg, callback) {
    this.client.publish(this.name, msg, callback);
    return this;
}

Subscribe to the channel.

  • param: Function callback

  • return: this

  • api: public

Channel.prototype.subscribe = function (callback) {
    this.client.subscribe(this.name);
    if (typeof callback === 'function') {
        this.on('subscribe', callback);
    }
    return this;
}

Unsubscribe from the channel.

  • param: Function callback

  • return: this

  • api: public

Channel.prototype.unsubscribe = function (callback) {
    this.client.unsubscribe(this.name);
    if (typeof callback === 'function') {
        this.on('unsubscribe', callback);
    }
    return this;
}

Cache

lib/Cache.js

Use Redis as a cache backend.

Usage

redback.createCache(namespace);

Reference

http://redis.io/commands#string

Redis Structure

(namespace:)cache_namespace:key = string

var Cache = exports.Cache = function (client, namespace) {
    this.client = client;
    this.namespace = namespace;
}

Cache one or more values.

To cache a single value by key: cache.set('foo', 'bar', callback);

To set multiple cached values by key: cache.set({foo:'bar', key2:'value2'}, callback);

  • param: string key

  • param: string value

  • param: Function callback (optional)

  • return: this

  • api: public

Cache.prototype.set = function (key, value, expiry, callback) {
    callback = callback || function () {};
    if (typeof expiry === 'function') {
        callback = expiry;
        this.client.set(this.getKey(key), value, callback);
    } else if (typeof value === 'function') {
        callback = value;
        var i, set = [];
        for (i in key) {
            set.push(this.getKey(i));
            set.push(key[i]);
        }
        set.push(callback);
        this.client.mset.apply(this.client, set);
    } else {
        this.client.setex(this.getKey(key), expiry, value, callback);
    }
    return this;
}

Add one or more values to the cache, but only if the cache key(s) do not already exist.

  • param: string | Object key

  • param: string value (optional)

  • param: Function callback (optional)

  • return: this

  • api: public

Cache.prototype.add = function (key, value, callback) {
    callback = callback || function () {};
    if (typeof value === 'function') {
        callback = value;
        var i, set = [];
        for (i in key) {
            set.push(this.getKey(i));
            set.push(key[i]);
        }
        set.push(callback);
        this.client.msetnx.apply(this.client, set);
    } else {
        this.client.setnx(this.getKey(key), value, callback);
    }
    return this;
}

Get one or more values from the cache.

To get a single cached value by key: cache.get('foo', callback);

To get multiple cached values by key: cache.get(['foo','bar'], callback);

To get all cached values: cache.get(callback);

  • param: string key

  • param: string value

  • param: Function callback

  • return: this

  • api: public

Cache.prototype.get = function (key, callback) {
    var namespace_len = this.namespace.length + 1,
        self = this;

    var multi_get = function (keys) {
        var get_args = [];
        keys.forEach(function (key) {
            get_args.push(key);
        })
        get_args.push(function (err, values) {
            if (err) return callback(err, null);
            var i, l, ret = {};
            for (i = 0, l = keys.length; i &lt; l; i++) {
                ret[keys[i].substr(namespace_len)] = values[i];
            }
            callback(null, ret);
        });
        self.client.mget.apply(self.client, get_args);
    }

    if (typeof key === 'function') {
        callback = key;
        this.keys('*', true, function (err, keys) {
            if (err) callback(err, null);
            multi_get(keys);
        });
    } else if (Array.isArray(key)) {
        if (!key.length) callback(null, null);
        for (var get = [], i = 0, l = key.length; i &lt; l; i++) {
            key[i] = this.getKey(key[i]);
        }
        multi_get(key);
    } else {
        this.client.get(this.getKey(key), callback);
    }
    return this;
}

Set a cache key and return the current value.

  • param: string key

  • param: string value

  • param: Function callback

  • return: this

  • api: public

Cache.prototype.getSet = function (key, value, callback) {
    this.client.getset(this.getKey(key), value, callback);
    return this;
}

Check whether a cache key exists.

  • param: string key

  • param: Function callback

  • return: this

  • api: public

Cache.prototype.exists = function (key, callback) {
    this.client.exists(this.getKey(key), callback);
    return this;
}

Increment the specified cache value.

  • param: string key

  • param: int amount (optional - default is 1)

  • param: Function callback (optional)

  • return: this

  • api: public

Cache.prototype.increment =
Cache.prototype.incrBy = function (key, amount, callback) {
    callback = callback || function () {};
    if (typeof amount === 'function') {
        callback = amount;
        amount = 1;
    }
    this.client.incrby(this.getKey(key), amount, callback);
    return this;
}

Decrement the specified cache value.

  • param: string key

  • param: int amount (optional - default is 1)

  • param: Function callback (optional)

  • return: this

  • api: public

Cache.prototype.decrement =
Cache.prototype.decrBy = function (key, amount, callback) {
    callback = callback || function () {};
    if (typeof amount === 'function') {
        callback = amount;
        amount = 1;
    }
    this.client.decrby(this.getKey(key), amount, callback);
    return this;
}

Get all cache keys matching the pattern.

  • param: string pattern (optional - default is *)

  • param: bool keep_namespace (optional - default is false)

  • param: Function callback

  • return: this

  • api: public

Cache.prototype.keys = function (pattern, keep_namespace, callback) {
    if (typeof pattern === 'function') {
        keep_namespace = false;
        callback = pattern;
        pattern = '*';
    } else if (typeof keep_namespace === 'function') {
        callback = keep_namespace;
        keep_namespace = false;
    }
    var self = this;
    if (keep_namespace) {
        this.client.keys(this.namespace + ':' + pattern, function (err, keys) {
            if (err) return callback(err, null);
            if (!keys) return callback(null, []);
            callback(null, keys);
        });
    } else {
        var namespace_len = this.namespace.length + 1;
        this.client.keys(this.namespace + ':' + pattern, function (err, keys) {
            if (err) return callback(err, null);
            if (!keys) return callback(null, []);
            if (null == keys) return callback(null, []);
            for (var i = 0, l = keys.length; i &lt; l; i++) {
                keys[i] = keys[i].substr(namespace_len);
            }
            callback(null, keys);
        });
    }
}

Flush all cache keys matching the pattern.

  • param: string pattern (optional - default is *)

  • param: Function callback (optional)

  • return: this

  • api: public

Cache.prototype.flush = function (pattern, callback) {
    callback = callback || function () {};
    if (typeof pattern === 'function') {
        callback = pattern;
        pattern = '*';
    }
    var self = this;
    this.keys(pattern, true, function (err, keys) {
        if (err) return callback(err, null);
        if (!keys) return callback(err, []);
        var error = false, remaining = keys.length, del_count = 0;
        keys.forEach(function (key) {
            self.client.del(key, function (err, deleted) {
                if (error) {
                    return;
                } else if (err) {
                    error = true;
                    return callback(err, null);
                }
                del_count++;
                if (!--remaining) callback(err, del_count);
            });
        });
    });
}

Expire the cache key after a certain number of seconds.

  • param: int ttl

  • param: Function callback (optional)

  • return: this

  • api: public

Cache.prototype.expire = function (key, ttl, callback) {
    callback = callback || function () {};
    this.client.expire(this.getKey(key), ttl, callback);
    return this;
}

Expire the cache key at a certain date.

  • param: string key

  • param: Date when

  • param: Function callback (optional)

  • return: this

  • api: public

Cache.prototype.expireAt = function (key, when, callback) {
    callback = callback || function () {};
    if (typeof when.getTime === 'function') {
        when = Math.round(when.getTime() / 1000); //ms =&gt; s
    }
    this.client.expireat(this.getKey(key), when, callback);
    return this;
}

Get the number of seconds before the cache key expires.

  • param: string key

  • param: Function callback (optional)

  • return: this

  • api: public

Cache.prototype.ttl = function (key, callback) {
    callback = callback || function () {};
    this.client.ttl(this.getKey(key), callback);
    return this;
}

Checks whether a cache key has an expiry.

  • param: string key

  • param: Function callback

  • return: this

  • api: public

Cache.prototype.isVolatile = function (key, callback) {
    this.client.ttl(this.getKey(key), function (err, ttl) {
        if (err) return callback(err, null);
        callback(null, ttl != -1);
    });
    return this;
}

Remove the cache key's associated expiry.

  • param: string key

  • param: Function callback (optional)

  • return: this

  • api: public

Cache.prototype.persist = function (key, callback) {
    callback = callback || function () {};
    this.client.persist(this.getKey(key), callback);
    return this;
}