A Node.js library for streamlining the configuration and maintenance of your Redis namespace
Switch branches/tags
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
lib
test
.editorconfig
.eslintrc
.gitignore
.travis.yml
CHANGELOG.md
README.md
index.js
package.json

README.md

RedisKeychain

NPM version Dependency status Dev Dependency status Coverage Status Build Status

RedisKeychain is a Node.js library for streamlining the configuration and maintenance of your Redis namespace.

Features

  • template engine for key names;
  • expiry configuration utility;
  • key-to-client binding.

Table of contents

generated with DocToc

Installation

$ npm install node-redis-keychain

Typical usage

Typical usage involves defining a "configure" function and pass it as argument to the library's factory method .forge() in order to create a new instance of RedisKeychain.

Configuration

In the following example, "configure" function is defined as a module in ./configure.js for the sake of clarity.

Separating configuration from instantiation is also considered a best practice.

// module deps
var
    moment = require('moment'),
    redis = require('redis');

module.exports = function configure(config) {
    config.init({
        clients: {
            cache: redis.createClient()
        },
        expiries: {
            daily: '1 day'
        },
        fields: {
            day: {
                auto: function () {
                    return moment().format('YYYYMMDD');
                }
            }
        },
        keys: {
            userDailyScoreByLevel: {
                name: 'user:score:daily:{day}:level:{level}',
                type: 'string',
                fields: {
                    level: {
                        type: Number,
                        validate: function (value) {
                            return value >= 1 && value <= 5;
                        }
                    }
                },
                expiry: 'daily',
                client: 'cache'
            }
        }
    });
};

Every "configure" function uses this format and all of your configuration options must be specified inside this function:

module.exports = function configure(config) {

    var
        options = {
            // clients, expiries, fields, keys
        };

    config.init(options);
}

More on configuration options later.

Instantiation

In the following example, "configure" function module in ./configure.js is passed to library's factory method, .forge().

// module deps
var
    RedisKeychain = require('node-redis-keychain');

// file deps
var
    configure = require('./configure');

var
    keychain = RedisKeychain.forge(configure);

Usage

// centralize access to your Redis clients
keychain.clients.cache.dbsize(function (error, reply) {
    if (error) {
        throw error;
    }
    console.log(reply); // number of keys in db 0
});

// centralize access to your expiries
console.log(keychain.expiries.daily); // 86400000

var
    key = keychain.userDailyScoreByLevel;

// use template engine for key names feature
console.log(key.render({ level: 1 })); // 'path:to:key:with:20200101:1'

// use key-to-client binding
key.send({ level: 2 })
    .incrby(3, function (error, reply) {
        if (error) {
            throw error;
        }
        console.log(reply); // 3
    });

Configuration options

Configuration options are passed to Config#init() as an hash argument.

Configuration options are clients, expiries, fields and keys.

All of them are optional.

module.exports = function configure(config) {

    var
        options = {
            clients: {
                // all your clients here
            },
            expiries: {
                // all your expiries here
            },
            fields: {
                // all your fields here
            },
            keys: {
                // all your keys here
            }
        };

    config.init(options);
}

Clients

In the following example, two different Redis clients are registered to a RedisKeychain instance.

Client instances are then accessed from RedisKeychain#clients.

// module deps
var
    RedisKeychain = require('node-redis-keychain'),

    redis = require('redis'),
    Redis = require('ioredis');

var
    cache = redis.createClient(),
    persistent = new Redis();

persistent.select(1);

var
    configure = function (config) {
        config.init({
            clients: {
                cache: cache,
                persistent: persistent
            }
        });
    },

    keychain = RedisKeychain.forge(configure);

keychain.clients.cache.dbsize(function (error, reply) {
    if (error) {
        throw error;
    }

    console.log(reply); // number of keys in db 0
});

keychain.clients.persistent.dbsize()
    .then(function (reply) {
        console.log(reply); // number of keys in db 1
    });

Registering a client to a RedisKeychain instance, involves adding an alias-instance pair to the clients configuration option:

{
    clients: {
        alias: instance
    }
}

You then access the client by alias:

keychain.clients.alias;

Client registration is library agnostic: you can use your Redis client library of choice.

Expiries

Expiries are keys' time to live.

Expiries can have global or key scope.

The expiries configuration option is meant to register global expiries.

More on key-scoped expiries later.

In the following example, five global expiries are registered to a RedisKeychain instance to show all possible configurations.

Expiries are then accessed from RedisKeychain#expiries.

// module deps
var
    RedisKeychain = require('node-redis-keychain');

var
    configure = function (config) {
        config.init({
            expiries: {
                daily: '1 day',
                minutely: 60000,
                at: new Date(2020, 0, 1),
                secondly: function () {
                    return 1000;
                },
                refreshing: {
                    value: '1 day',
                    refresh: true
                }
            }
        });
    },

    keychain = RedisKeychain.forge(configure);

console.log(keychain.expiries.daily); // 86400000
console.log(keychain.expiries.minutely); // 60000
console.log(keychain.expiries.at); // Wed Jan 01 2020 00:00:00 GMT+0100 (CET)
console.log(keychain.expiries.secondly); // 1000
console.log(keychain.expiries.refreshing); // 86400000

Registering a global expiry to a RedisKeychain instance, involves adding an alias-value pair to the expiries configuration option

{
    expiries: {
        alias: value
    }
}

where value can be of types String, Number, Date, Function, Object.

Use a String value to define an expiry just like 1 day, 2 weeks. More information at expiry-js.

Use a Number value to define an expiry using milliseconds.

Use a Date value to define an expiry by date.

Use a Function value to define a dynamic expiry. The function must return a value of types String, Number, Date or Object.

Use an Object value to set expiry value and refresh. More info on refresh later.

You then access the expiry by alias:

keychain.expiries.alias;

Fields

Fields are placeholders in key names.

Fields can have global or key scope.

The fields configuration option is meant to register global fields.

More on key-scoped fields later.

// module deps
var
    moment = require('moment'),
    RedisKeychain = require('node-redis-keychain');

var
    configure = function (config) {
        config.init({
            fields: {
                yearMonth: {
                    auto: function () {
                        return moment().format('YYYYMM');
                    }
                }
            },
            keys: {
                leaderboard: {
                    name: 'leaderboard:monthly:{yearMonth}',
                    type: 'zset'
                }
            }
        });
    },

    keychain = RedisKeychain.forge(configure);

console.log(keychain.leaderboard.render()); // leaderboard:monthly:202001

Registering a global field to a RedisKeychain instance involves adding a name-value pair to the fields configuration option.

{
    fields: {
        name: value
    }
}

where value is an object having at least one of the following properties: type, auto, process, validate.

type

Optional. It enables type-checking against actual field values.

It can be any value in Boolean, Number, String.

auto

Required for global fields. Optional for key fields. It is an automatic value for the field.

It can be any value of type Boolean, Function, Number, String.

If auto is of type Function then auto() will be invoked upon Key#render() or Key#send() to get the actual value.

process

Specify process if you want to process values before validation and rendering; process() will be invoked upon Key#render() or Key#send().

It must return a value of valid type and conforming to validate() constraints.

validate

Specify validate if you want to perform further validation on field value; validate() will be invoked upon Key#render() or Key#send() and after process().

It must return a Boolean value.

You can use validate in conjunction with type.

Keys

In the following example, a key is registered to a RedisKeychain instance.

// module deps
var
    RedisKeychain = require('node-redis-keychain');

var
    configure = function (config) {
        config.init({
            keys: {
                aKey: {
                    name: 'path:to:key:with:{field}',
                    type: 'string'
                }
            }
        });
    },

    keychain = RedisKeychain.forge(configure);

console.log(keychain.aKey.render('value')); // path:to:key:with:value

Registering a key to a RedisKeychain instance involves adding an alias-value pair to the keys configuration option

{
    keys: {
        alias: value
    }
}

where value is an object having at least name and type properties. Optional properties are fields, expiry, client.

name

Required. It is the name of the key in your Redis namespace.

It can be an actual name or a template.

Templates contain one or more placeholders known as fields.

Fields are indicated by brackets: {field}.

Fields can have global or key scope.

Global fields are registered to a RedisKeychain instance adding an entry to the fields configuration option. More on this here.

Key-scoped fields are implicitly registered if they appear in a key name and they are not already registered as global fields.

type

Required. It is the type of the key in your Redis namespace.

It can be any string value in hash, hyperloglog, list, set, string, zset.

fields

Optional. Use fields to configure a key-scoped field.

In the following example a key scoped field, yearMonth, has a Function auto value.

// module deps
var
    moment = require('moment'),
    RedisKeychain = require('node-redis-keychain');

var
    configure = function (config) {
        config.init({
            keys: {
                aKey: {
                    name: 'path:to:key:with:{yearMonth}',
                    type: 'string',
                    fields: {
                        field: {
                            auto: function () {
                                return moment().format('YYYYMM');
                            }
                        }
                    }
                }
            }
        });
    }

Configure key-scoped fields just like global fields.

expiry

Optional. It is the key's time to live.

It can be a reference to a global expiry or the configuration of a key-scoped expiry.

Configure key-scoped expiries just like global expiries.

Refresh

In the following example aKey with an expiry of 1 day is registered to a RedisKeychain instance. The key is also bound to a client instance.

Every Redis write command will refresh the expiry upon Key#send().

// module deps
var
    redis = require('redis'),
    RedisKeychain = require('node-redis-keychain');

var
    configure = function (config) {
        config.init({
            clients: {
                cache: redis.createClient()
            },
            keys: {
                aKey: {
                    name: 'path:to:key',
                    type: 'string',
                    expiry: {
                        value: '1 day',
                        refresh: true
                    },
                    client: 'cache'
                }
            }
        });
    },

    keychain = RedisKeychain.forge(configure),

    aKey = keychain.aKey;

aKey.send().set('value', function (error, reply) {
    if (error) {
        throw error;
    }
    console.log(reply); // OK
});

client

Optional. It is an alias to a client instance registered to the same RedisKeychain instance.

When you specify a client you enable the key-to-client binding feature and you can send commands to Redis using Key#send() as follows

// module deps
var
    redis = require('redis'),
    RedisKeychain = require('node-redis-keychain');

var
    configure = function (config) {
        config.init({
            clients: {
                cache: redis.createClient()
            },
            keys: {
                aKey: {
                    name: 'path:to:key:with:{field}',
                    type: 'string',
                    client: 'cache'
                }
            }
        });
    },

    keychain = RedisKeychain.forge(configure);

keychain.aKey.send('fieldValue').set('keyValue', function (error, reply) {
    if (error) {
        throw error;
    }
    console.log(reply); // OK
});

Key-to-client binding feature is library agnostic: you can use your Redis client library of choice.

Key-to-client binding feature has been tested using node_redis and ioredis.

Key#render()

Invoke Key#render to render a key name template. Arguments to Key#render can be a list of field values or a single Object argument having field-value pairs.

// module deps
var
    RedisKeychain = require('node-redis-keychain');

var
    configure = function (config) {
        config.init({
            keys: {
                aKey: {
                    name: 'path:to:key:with:{field1}:{field2}',
                    type: 'string'
                }
            }
        });
    },

    keychain = RedisKeychain.forge(configure),

    aKey = keychain.aKey;

// list of field values
console.log(aKey.render('value1', 'value2')); // path:to:key:with:value1:value2

// object having field-value pairs
console.log(aKey.render({ field1: 'value1', field2: 'value2' }));  // path:to:key:with:value1:value2

You can also invoke the shortcut method Key#r().

Key#send()

Invoke Key#send() to send a command to Redis using the client instance bound to the key. Arguments to Key#send() can be a list of field values or a single Object argument having field-value pairs to render the key name template.

// module deps
var
    redis = require('redis'),
    RedisKeychain = require('node-redis-keychain');

var
    configure = function (config) {
        config.init({
            clients: {
                cache: redis.createClient()
            },
            keys: {
                aKey: {
                    name: 'path:to:key:with:{field}',
                    type: 'string',
                    client: 'cache'
                }
            }
        });
    },

    keychain = RedisKeychain.forge(configure);

keychain.aKey.send('fieldValue').set('keyValue', function (error, reply) {
    if (error) {
        throw error;
    }
    console.log(reply); // OK
});

You can also invoke the shorcut method Key#s().

Running tests

The test suite is split into two parts: unit tests and command tests. The unit tests run on any machine while the command tests require a Redis server instance to be setup.

Please, be aware that each and every command test need to flushall your Redis instance.

In order to prevent accidental flushall, command tests won't run if no test/.flushall file is found. So, remember to create an empty .flushall file into test/ directory before running tests.

Run tests as follows

$ npm test

License

The project is licensed under the MIT license.