UNPKG

cachetree

Version:

A scoped, fluent API for easily interacting with hierarchical, key-value data

275 lines (260 loc) 6.64 kB
/** * Module imports */ var util = require('util'), EventEmitter = require('events').EventEmitter; /** * Array.prototype.slice reference */ var slice = Array.prototype.slice; /** * Constructor * * @param {Stirng} key Hash key * @param {Cachetree} root Root Cachetree instance (null if top node) */ function Hash(key, root) { var type = typeof key, len = 0; if ((type === 'string' && key) || type === 'number') { key = [key]; } else if (Array.isArray(key)) { key = slice.call(key); } if (Array.isArray(key)) { len = key.length; } if (!(!root && len === 1) && !(root && len > 1)) { throw new Error('Invalid key'); } if (!root) { root = this; } // Define properties Object.defineProperties(this, { key: { value: key, writable: false }, root: { value: root, writable: false } }); EventEmitter.call(this); } util.inherits(Hash, EventEmitter); /** * Export hash */ module.exports = Hash; /** * Execute a store function that takes variable arguments * * @param {String} name Function name to execute * @param {Array} args Arguments array * @return {this} for chaining */ Hash.prototype._exec = function(name, args) { args = slice.call(args); var len = args.length, cb; if (len > 0 && typeof args[len - 1] === 'function') { cb = args.pop(); } // Unwrap the first argument if its an array if (args.length === 1 && Array.isArray(args[0])) { args = args[0]; } this.root.store[name](this.key, args, cb); return this; }; /** * Get the values of all the given hash fields * * @param {...String} field Hash field(s) or omit for all * @param {Function} cb Callback function * @return {this} for chaining */ Hash.prototype.get = function() { return this._exec('get', arguments); }; /** * Set the values of given hash fields * * @param {String|Object} field Hash field or object * @param {Object} value Value (if field is not object) * @param {Function} cb Callback function * @return {this} for chaining */ Hash.prototype.set = function() { return this._exec('set', arguments); }; /** * Determine if a hash field exists * * @param {String} field Hash field * @param {Function} cb Callback function * @return {this} for chaining */ Hash.prototype.exists = function(field, cb) { if (typeof field === 'function') { cb = field; field = null; } this.root.store.exists(this.key, field, cb); return this; }; /** * Delete given hash fields * * @param {...String} field Hash field(s) * @param {Function} cb Callback function * @return {this} for chaining */ Hash.prototype.del = function() { return this._exec('del', arguments); }; /** * Clear the hash and all descendent hashes * * @param {Function} cb Callback function * @return {this} for chaining */ Hash.prototype.flush = function(cb) { var self = this; this.root.store.keys(this.childKey('*'), function(err, keys) { if (!Array.isArray(keys)) { keys = []; } keys.unshift(self.key); self.root.store.flush(keys, cb); }); return this; }; /** * Add child node(s) * * @param {String|Object} name Child node name or object * @param {Object} def Child definition (if name is string) * @return {this} for chaining */ Hash.prototype.add = function(name, def) { var type = typeof name; if (type === 'string' || type === 'number') { this._add(name, def); } else if (name === Object(name)) { Object.keys(name).forEach(function(val) { this._add(val, name[val]); }, this); } return this; }; /** * Add a child * * @param {String} name Child name * @param {Object} def Child definition * @return {this} for chaining */ Hash.prototype._add = function(name, def) { var self = this, type = typeof def, validateFn = false, children = [], prefixType = null, prefix = '', create = function(key) { var hash = new Hash(self.childKey(key), self.root); children.forEach(function(val) { hash.add(val, def[val]); }); return hash; }; if (name && !this[name]) { // Validate the child definitiom if ((type === 'string' && def) || type === 'number') { def = { __key__: def }; } else if (type === 'function' || def instanceof RegExp) { def = { __validate__: def }; } // Extract child names if (!def) { def = {}; } children = Object.keys(def).filter(function(value) { return value !== '__key__' && value !== '__validate__'; }); // Add the child if (def.__key__) { if (this.root.useProperties === true) { Object.defineProperty(this, name, { get: function() { return create(def.__key__); } }); } else { this[name] = function() { return create(def.__key__); }; } } else { if (def.__validate__ && typeof def.__validate__ === 'function') { validateFn = true; } prefixType = typeof def.__prefix__; if (prefixType === 'string' && def.__prefix__) { prefix = def.__prefix__; } else if (prefixType === 'number') { prefix = def.__prefix__.toString(); } this[name] = function(value) { var isValid = false, type = typeof value; if (type === 'string' || type === 'number') { if (def.__validate__) { if (validateFn) { isValid = def.__validate__(value); } else { isValid = def.__validate__.test(value); } } else { if ((type === 'string' && value) || type === 'number') { isValid = true; } } } if (isValid !== true) { throw new Error('Invalid key'); } return create(prefix + value); }; } } return this; }; /** * Generate a child key * * @param {String|Number} key Child key * @return {Array} Child key array */ Hash.prototype.childKey = function(key) { var type = typeof key, childKey; if ((type === 'string' && key) || type === 'number') { childKey = slice.call(this.key); childKey.push(key); return childKey; } }; /** * Return a list of field keys * * @param {Function} cb Callback function * @return {this} for chaining */ Hash.prototype.fields = function(cb) { this.root.store.fields(this.key, cb); return this; };