UNPKG

@house-agency/brewstore

Version:
176 lines (155 loc) 4.51 kB
/** * Handles Key/Value database storage ~= Redis */ const _ = require('lodash'); const conf = require('@house-agency/brewtils/config'); const errors = require('./errors'); const format = require('util').format; const log = require('@house-agency/brewtils/log'); const q = require('q'); const redis = require('redis'); const rx = require('rx'); const listeners = {}; /** * Creates a client for the key-value database * It takes database connections info from config * and starts listen for incoming calls and errors * * @return {object} Key-value database client (redis) */ function get_client() { return conf('storage.keyvalue') .then(props => { log('debug', 'Connecting to key/value storage', props); const deferred = q.defer(); const client = redis.createClient( props.port, props.host ); // Selects which database to use client.select(props.db); // Listen for ready and error events. rx.Observable.merge( rx.Observable.fromEvent(client, 'ready') .first(), rx.Observable.fromEvent(client, 'error') .map(error => { throw new Error(error); }) ) .subscribe( () => { deferred.resolve(client); }, error => { log('fatal', 'Redis error', error); deferred.reject(error); } ); return deferred.promise; }); } /** * Creates a new client, starts listen on messages and * returns an obvervable. * * @return {object} Promise with database client */ function get_listener() { return get_client() .then(db => { return q.all([ db, rx.Observable.fromEvent( db, 'message', (channel, message) => { log('debug', 'Publishing', channel, message); return { db: db, channel: channel, message: message }; } ) ]); }); } /** * Wraps database calls to make the normalized * and throws error when nothing found. * * @param {object} client * @param {string} key Identification key string * @param {string} method Method to run at database * @param {array} args Method arguments * @return {object} Results or throws error if empty results */ function run(client, key, method, args) { if (!args) { args = []; } if (!_.isArray(args)) { args = _.toArray(arguments).slice(3); } return client .then(db => { args.unshift(key); return q.npost(db, method, args); }) .catch(error => { log('fatal', 'Redis error while executing', method, 'with', key, 'for', args, error); return q.reject(error); }) .then(val => { if (val === null) { throw new errors.EmptyRecordError(format('%s not found', key)); } return val; }); } /** * Creates or finds an already existing listener as a rx observable * returned in a promise. * If creating new clients and observables for every listeners, the * number or file descriptors will run out! * * @param {object} client Client to use for listening, created and wrapped in wrapper() * @param {string} channel Channel to listen to * @return {object} Promise with an observable */ function listen(client, channel) { return client .spread((db, listener) => { log('debug', 'Starts listen to', channel); // Check for already created listeners if (!listeners[channel]) { log('debug', 'Created new listener for', channel); listeners[channel] = listener // Filter for specified channel .filter(obj => { return obj.channel === channel; }) // Map returned data to message .map(obj => { return obj.message; }); // And then subscribe on channel db.subscribe(channel); } return listeners[channel]; }); } /** * Wraps client with runner and listener functions */ const client = get_client(); const wrapper = Object.assign( client, { get_client: get_client, run:_.wrap(client, run), listen:_.wrap(get_listener(), listen) } ); module.exports = wrapper;