guidesmiths/block-sequence-redis

View on GitHub
index.js

Summary

Maintainability
A
0 mins
Test Coverage
var debug = require('debug')('block-sequence:redis')
var redis = require('redis')
var _ = require('lodash').runInContext()
var safeParse = require('safe-json-parse/callback')
var async = require('async')
var fs = require('fs')
var path = require('path')
var bigInt = require('big-integer')

module.exports = function init(config, cb) {

    if (Number.MAX_SAFE_INTEGER === undefined) Number.MAX_SAFE_INTEGER = 9007199254740991
    if (arguments.length === 1) return init({}, arguments[0])

    var scripts = {}
    var store = redis.createClient(config);

    function ensure(options, cb) {

        if (options.name === null || options.name === undefined) return cb(new Error('name is required'))

        var name = options.name.toLowerCase()
        var value = options.value || 0
        var metadata = options.metadata || {}

        serialize({ name: name, value: value, metadata: metadata }, function(err, sequence) {
            if (err) return cb(err)
            run('ensure',
                ['block-sequence:index', 'block-sequence:sequence:' + name],
                _.chain(sequence).toPairs().flatten().value(),
                function(err, results) {
                    if (err) return cb(err)
                    deserialize(results, cb)
                })
        })
    }

    function allocate(options, cb) {

        var size = options.size || 1

        ensure(options, function(err, sequence) {
            if (err) return cb(err)
            run('allocate', ['block-sequence:sequence:' + sequence.name], [ size ], function(err, results) {
                if (err) return cb(err)
                deserialize(results, function(err, sequence) {
                    if (err) return cb(err)
                    cb(null, _.chain({ next: sequence.value - size + 1, remaining: size })
                              .defaultsDeep(sequence)
                              .omit(['value'])
                              .value()
                    )
                })
            })
        })
    }

    function run(script, keys, params, cb) {
        var args = [].concat(scripts[script], keys.length, keys, params)
        args.push(cb)
        debug('Running script: %s with sha: %s and arguments: %s', script, args[0], args.slice(1).join(', '))
        store.evalsha.apply(store, args)
    }

    function remove(options, cb) {
        debug('Removing %s', options.name)
        if (options.name === null || options.name === undefined) return cb(new Error('name is required'))
        run('remove', ['block-sequence:index', 'block-sequence:sequence:' + options.name.toLowerCase()], [], cb)
    }

    function loadScripts(cb) {
        fs.readdir(path.join(__dirname, 'lua'), function(err, files) {
            if (err) return cb(err)
            async.each(files, function(file, cb) {
                debug('Loading %s', file)
                fs.readFile(path.join(__dirname, 'lua', file), { encoding: 'utf-8' }, function(err, script) {
                    if (err) return cb(err)
                    store.script('load', script, function(err, sha) {
                        if (err) return cb(err)
                        scripts[path.basename(file, '.lua')] = sha
                        cb()
                    })
                })
            }, cb)
        }, cb)
    }

    function serialize(obj, cb) {
        cb(null, { name: obj.name, value: obj.value, metadata: JSON.stringify(obj.metadata) })
    }

    function deserialize(list, cb) {
        var obj = {}
        for (var i = 0; i < list.length; i += 2) {
            obj[list[i]] = list[i+1]
        }
        safeParse(obj.metadata, function(err, metadata) {
            var value = bigInt(obj.value)
            if (value.greater(Number.MAX_SAFE_INTEGER)) return cb(new Error('Sequence value exceeds Number.MAX_SAFE_INTEGER'))
            cb(err, {
                name: obj.name,
                value: value.toJSNumber(),
                metadata: metadata
            })
        })
    }

    function close(cb) {
        store.quit(cb)
    }

    loadScripts(function(err) {
        cb(err, {
            remove: remove,
            allocate: allocate,
            ensure: ensure,
            close: close
        })
    })
}