Source: storage.js

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

/**
 * Sets the storage configuration
 * @param {{perFree: number, size: number, expire: number}} conf
 * @returns {{find: find, store: store, remove: remove, clear: clear, size: size, free: free, all: all, on: on}}
 * @constructor
 */
var Storage = function(conf){
    conf = conf || {};

    var // function to return a unique now value
        _now = function(){
            var now = process.hrtime();
            return now[0] * 1E9 + now[1]
        },
        // object that holds all values
        _store = {},
        // map that stores timestamps and key for all values
        _expireMap = {},
        // method configurations
        _conf = {
            topic: conf.hasOwnProperty('topic') ? conf.topic : 'expired',
            // number of values that are freed
            perFree: conf.hasOwnProperty('perFree') ? conf.perFree : 100,
            // maximum size of storage
            size: conf.hasOwnProperty('size') ? conf.size : 1000,
            // time in milliseconds when values are expired
            expire: conf.hasOwnProperty('expire') ? conf.expire : 60000
        },
        _emitter = new EventEmitter();

    // "computed properties"
    _conf.expireNs = _conf.expire * 1E6;


    /**
     * Function that returns the storage size
     * @returns {number} storage size
     */
    function size() {
        return Object.getOwnPropertyNames(_store).length;
    }

    /**
     * Removes a value using a key
     * @param {string} key identifier for value
     */
    function remove(key) {
        if (_store.hasOwnProperty(key)) {
            // clear timeout
            clearTimeout(_store[key].id);
            // delete stored data
            delete _expireMap[_store[key].now];
            delete _store[key];
        }
    }

    /**
     * Function that tries to find a key and returns the stored value
     * @param {string} key identifier for value
     * @returns {*|undefined} result of value lookup
     */
    function find(key) {
        // check if store has key
        return _store.hasOwnProperty(key) ? _store[key].val : undefined;
    }

    /**
     * Frees a specified amount of values from the storage.
     * It removes the oldest stored values.
     * @param {number} [amount=1] number of values that needs to be freed
     */
    function free(amount) {
        amount = amount || 1;
        // oldest items from expire map
        Object.keys(_expireMap).slice(0, amount).forEach(function(timestamp){
            // remove each key
            remove(_expireMap[timestamp]);
        });
    }

    /**
     * Function that expires a value using a key. Emits an event.
     * @param {string} key
     * @private
     */
    function _expire(key) {
        if (_store.hasOwnProperty(key)){
            // emit event
            _emitter.emit(_conf.topic, {
                key: key,
                value: _store[key].val
            });
            remove(key);
        }
    }

    /**
     * Function that stores a value using a key
     * @param {string} key identifier for value
     * @param {*} value value that needs to be stored
     * @param {number} [expires]
     */
    function store(key, value, expires) {
        expires = expires || _conf.expire;

        if (size() + 1 > _conf.size) {
            // free storage
            free(_conf.perFree);
        }

        if (_store.hasOwnProperty(key)) {
            // already stored something

            // remove expiredmap entry
            delete _expireMap[_store[key].now];

            // clear old timeout
            clearTimeout(_store[key].id);

            // remove old stored value
            _store[key].val = value;
            _store[key].now = _now();

        } else {
            // hasn't stored anything for key
            // store new value
            _store[key] = {
                val: value,
                now: _now()
            };
        }

        // call remove with key in _conf.expire milliseconds
        _store[key].id = setTimeout(_expire.bind(null, key), expires);

        // add key to expired map
        _expireMap[_store[key].now] = key;
    }

    /**
     * Function that clears the storage
     */
    function clear() {
        // clear all timeouts
        Object.keys(_store).forEach(function(key){
            clearTimeout(_store[key].id);
        });

        // reset store and expiremap
        _store = {};
        _expireMap = {};
    }

    /**
     * Function to get all stored items
     * @returns {Array} All stored items with value and key
     */
    function all() {
        return Object.keys(_store).map(function(key){
            return {
                value: _store[key].val,
                key: key
            };
        });
    }

    /**
     * Function to register an listener for a specific topic
     * @param {string} topic
     * @param {function} callback Function that is called once the emitter emits a message under the topic
     * @returns {*} emitter.on result
     */
    function on(topic, callback) {
        return _emitter.on(topic, callback);
    }

    // return public methods
    return {
        find: find,
        store: store,
        remove: remove,
        clear: clear,
        size: size,
        free: free,
        all: all,
        on: on
    }
};

exports.Storage = Storage;