UNPKG

nodiak

Version:

Nodiak is a Node.js client for the Riak Distributed Database

591 lines (537 loc) 20.9 kB
// (The MIT License) // Copyright (c) 2012 Coradine Aviation Systems // Copyright (c) 2012 Nathan Aschbacher // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // 'Software'), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. var events = require('events'), helpers = require('./helpers'), RObject = require('./robject'), async = require('async'); var Bucket = function Bucket(name, client) { this.name = name; this.props = {}; this.resolver = Bucket.siblingLastWriteWins; this.getSiblingsSync = false; this.client = client; this.search = Object.create(Bucket.prototype.search, { // Establish namespace 'search' _this: { // used as a placeholder for namespaced functions, referenced using this._this in those functions. enumerable: false, configurable: true, writable: true, value: this } }); this.objects = this.object = Object.create(Bucket.prototype.objects, { // Establish namespace 'objects' and 'object' _this: { // used as a placeholder for namespaced functions, referenced using this._this in those functions. enumerable: false, configurable: true, writable: true, value: this } }); }; Bucket.prototype.getProps = function(_return) { _return = _return !== undefined ? _return : function(){}; var _this = this; _this.client._bucket.props(_this.name, function(err, props) { if(err) _return(err, props); else { _this.props = props; _return(err, _this.props); } }); return this; }; Bucket.prototype.saveProps = function(/* [merge], [_return] */) { var merge = typeof(arguments[0]) == 'boolean' ? arguments[0] : true; var _return = typeof(arguments[arguments.length - 1]) == 'function' ? arguments[arguments.length - 1] : function(){}; var _this = this; if(merge === true) { _this.client._bucket.props(_this.name, function(err, props) { if(err) _return(err, props); else { _this.props = helpers.merge(props, _this.props); _this.client._bucket.save(_this.name, _this.props, function(err, response) { if(err) _return(err, response); else { _return(err, _this); } }); } }); } else { _this.client._bucket.save(_this.name, _this.props, function(err, response) { if(err) _return(err, response); else { _return(err, _this); } }); } return this; }; Bucket.prototype.search = {}; Bucket.prototype.search.solr = function(solr_query, _return) { var _this = this._this; if(_return) { _this.client._bucket.solr(_this.name, solr_query, function(err, results) { if(err) _return(err, results); else _return(err, results.data); }); } else { return { stream: function() { var emitter = new events.EventEmitter(); _this.client._bucket.solr(_this.name, solr_query, function(err, results) { if(err) { err.data = results; emitter.emit('error', err); } else { var compiled_keys = []; for(var i = 0, length = results.data.response.docs.length; i < length; i++) { compiled_keys.push(results.data.response.docs[i].id); } _this.objects.get(compiled_keys).stream() .on('data', function(data) { emitter.emit('data', data); }) .on('error', function(error) { emitter.emit('error', error); }) .on('end', function() { emitter.emit('end'); }); } }); return emitter; } }; } return _this; }; Bucket.prototype.search.twoi = function(twoi_query, index, options, _return) { twoi_query = twoi_query instanceof Array ? twoi_query : [twoi_query]; _return = typeof(options) === 'function' ? options : _return; options = typeof(options) === 'object' ? options : {}; var _this = this._this; if(index !== '$key') { // handle $key special case. Issue # index = typeof(twoi_query[0]) === 'number' && twoi_query[0].toString().search(/[^0-9]/) === -1 ? index+'_int' : index+'_bin'; } if(_return) { // don't return stream _this.client._bucket.twoi(_this.name, twoi_query, index, options, function(err, results, continuation) { if(err) _return(err, results); else { _return(err, helpers.removeDuplicates(results), continuation); } }); } else { // return stream return { stream: function() { var req_cache = {}; var emitter = new events.EventEmitter(); _this.client._bucket.twoi(_this.name, twoi_query, index, options).stream() .on('error', function(err) { emitter.emit('error', err); emitter.removeAllListeners(); }) .on('data', function(keys) { for(var i=0, length=keys.length; i < length; i++) { if(!req_cache[keys[i]]) { req_cache[keys[i]] = 1; emitter.emit('data', keys[i]); } } }) .on('end', function(continuation) { emitter.emit('end', continuation); emitter.removeAllListeners(); }); return emitter; } }; } return _this; }; Bucket.prototype.objects = {}; Bucket.prototype.objects.new = function(key, data, metadata) { var _this = this._this; return new RObject(_this, key, data, metadata); }; Bucket.prototype.objects.all = function(_return) { var _this = this._this; var compiled_keys = []; _this.client._bucket.keys(_this.name).stream() .on('data', function(data) { compiled_keys = compiled_keys.concat(data); }) .on('end', function() { var compiled_robjs = []; _this.objects.get(compiled_keys).stream() .on('data', function(r_obj) { compiled_robjs.push(r_obj); }) .on('error', function(error) { //_return(error, compiled_robjs); }) .on('end', function() { _return(null, compiled_robjs); }); }) .on('error', function(err) { _return(err, undefined); }); return _this; }; Bucket.prototype.objects.exists = function(/* key, [options], _return */) { var key = arguments[0] || ""; var options = typeof(arguments[1]) == 'object' ? arguments[1] : {}; var _return = typeof(arguments[arguments.length - 1]) == 'function' ? arguments[arguments.length - 1] : function(){}; var _this = this._this; _this.client._object.exists(_this.name, key, options, _return); return _this; }; Bucket.prototype.objects.get = function(/* keys, [options], [resolver_fn], [_return] */) { var keys = arguments[0]; var options = typeof(arguments[1]) == 'object' ? arguments[1] : {}; var resolver_fn; var _return; if(typeof(arguments[arguments.length - 1]) == 'function' || typeof(arguments[arguments.length - 2]) == 'function') { resolver_fn = typeof(arguments[arguments.length - 2]) == 'function' ? arguments[arguments.length - 2] : this._this.resolver; _return = typeof(arguments[arguments.length - 1]) == 'function' ? arguments[arguments.length - 1] : function(){}; } var _this = this._this; if(_return === undefined) { // return object with stream function. return { stream: function(/* resolver_fn */) { resolver_fn = arguments.length == 1 ? arguments[0] : _this.resolver; return _this.objects._getStreaming(keys, options, resolver_fn); } }; } else { // non-streamed response. var compiled_results = []; var compiled_errors = []; if(keys) { var not_array_input = false; if(!(keys instanceof Array)) { not_array_input = true; keys = [keys]; } async.each(keys, function(key, _intermediate) { _this.client._object.get(_this.name, key, null, options, function(err, obj) { if(err) { err.data = key; compiled_errors.push(err); _intermediate(null); } else if(obj.siblings && obj.siblings.length > 0 ) { _this.object.getSiblings(obj.key, obj.siblings, function(err, siblings) { if(err) { err.data = key; compiled_errors.push(err); } else { var resolved = resolver_fn(siblings); resolved.siblings = siblings; compiled_results.push(resolved); } _intermediate(null); }); } else { compiled_results.push(new RObject(_this, key, obj.data, obj.metadata)); _intermediate(null); } }); }, function(err) { Bucket.prepareResult(compiled_results, compiled_errors, not_array_input, _return); } ); } else { _return(new TypeError("At least one key required."), null); } } return _this; }; Bucket.prototype.objects._getStreaming = function(keys, options, resolver_fn) { var _this = this._this; var emitter = new events.EventEmitter(); keys = keys instanceof Array ? keys : [keys]; if(keys.length) { async.each(keys, function(key, _intermediate) { _this.client._object.get(_this.name, key, null, options, function(err, obj) { if(err) { err.data = key; emitter.emit('error', err); _intermediate(null); } else if(obj.siblings && obj.siblings.length > 0 ) { _this.object.getSiblings(obj.key, obj.siblings, function(err, siblings) { if(err) { err.data = key; emitter.emit('error', err); } else { var resolved = resolver_fn(siblings); resolved.siblings = siblings; emitter.emit('data', resolved); } _intermediate(null); }); } else { emitter.emit('data', new RObject(_this, key, obj.data, obj.metadata)); _intermediate(null); } }); }, function(err) { emitter.emit('end'); emitter.removeAllListeners(); } ); } else { process.nextTick(function() { emitter.emit('error', new TypeError("Empty object get request.")); emitter.emit('end'); emitter.removeAllListeners(); }); } return emitter; }; Bucket.prototype.objects.save = function(/* r_objects, _return */) { var r_objects = arguments[0]; var options = typeof(arguments[1]) == 'object' ? arguments[1] : {}; var _return = typeof(arguments[arguments.length - 1]) == 'function' ? arguments[arguments.length - 1] : undefined; var _this = this._this; if(_return === undefined) { // return object with stream function. return { stream: function() { return _this.objects._saveStreaming(r_objects); } }; } else { // non-streamed response. var compiled_results = []; var compiled_errors = []; if(r_objects) { var not_array_input = false; if(!(r_objects instanceof Array)) { not_array_input = true; r_objects = [r_objects]; } async.each(r_objects, function(r_object, _intermediate) { _this.client._object.save(_this.name, r_object.key, r_object.data, r_object.metadata, r_object.options, function(err, obj) { if(err) { err.data = r_object; compiled_errors.push(err); } else { if(!err && obj.metadata.status_code != 204) { r_object.data = obj.data; r_object.metadata = obj.metadata; r_object.key = obj.key; } compiled_results.push(r_object); } _intermediate(null); }); }, function(err) { Bucket.prepareResult(compiled_results, compiled_errors, not_array_input, _return); } ); } else { _return(new TypeError("At least one RObject required."), null); } } return _this; }; Bucket.prototype.objects._saveStreaming = function(r_objects) { var _this = this._this; var emitter = new events.EventEmitter(); r_objects = r_objects instanceof Array ? r_objects : [r_objects]; if(r_objects.length) { async.each(r_objects, function(r_object, _intermediate) { r_object.save(function(err, obj) { if(err) { err.data = obj; emitter.emit('error', err); } else { emitter.emit('data', obj); } _intermediate(null); }); }, function(err) { emitter.emit('end'); emitter.removeAllListeners(); } ); } else { process.nextTick(function() { emitter.emit('error', new TypeError("At least one RObject required."), null); emitter.emit('end'); emitter.removeAllListeners(); }); } return emitter; }; Bucket.prototype.objects.delete = function(/* r_objects, _return */) { var r_objects = arguments[0]; var options = typeof(arguments[1]) == 'object' ? arguments[1] : {}; var _return = typeof(arguments[arguments.length - 1]) == 'function' ? arguments[arguments.length - 1] : function(){}; var _this = this._this; if(_return === undefined) { // return object with stream function. return { stream: function() { return _this.objects._deleteStreaming(r_objects); } }; } else { // non-streamed response. var compiled_results = []; var compiled_errors = []; if(r_objects) { var not_array_input = false; if(!(r_objects instanceof Array)) { not_array_input = true; r_objects = [r_objects]; } async.each(r_objects, function(r_object, _intermediate) { r_object.delete(function(err, obj) { if(err) { err.data = obj; compiled_errors.push(err); } else { compiled_results.push(obj); } _intermediate(null); }); }, function(err) { Bucket.prepareResult(compiled_results, compiled_errors, not_array_input, _return); } ); } else { _return(new TypeError("At least one RObject required."), null); } } return _this; }; Bucket.prototype.objects._deleteStreaming = function(r_objects) { var _this = this._this; var emitter = new events.EventEmitter(); r_objects = r_objects instanceof Array ? r_objects : [r_objects]; if(r_objects.length) { async.each(r_objects, function(r_object, _intermediate) { r_object.delete(function(err, obj) { if(err) { err.data = obj; emitter.emit('error', err.data = obj); } else { emitter.emit('data', obj); } _intermediate(null); }); }, function(err) { emitter.emit('end'); emitter.removeAllListeners(); } ); } else { process.nextTick(function() { emitter.emit('error', new TypeError("At least one RObject required."), null); emitter.emit('end'); emitter.removeAllListeners(); }); } return emitter; }; Bucket.prototype.objects.getSiblings = function(key, vtags, _return) { var _this = this._this; if(_this.getSiblingsSync) { _this.client._object.get(_this.name, key, { accept: 'multipart/mixed' }, function(err, response) { if(err) _return(err, response); else { async.map(response.siblings, function(sibling, _intermediate) { _intermediate(err, new RObject(_this, key, sibling.data, sibling.metadata)); }, function(err, siblings) { _return(err, siblings); } ); } }); } else { async.map(vtags, function(vtag, _intermediate) { _this.objects.get(key, { vtag: vtag }, function(err, obj) { _intermediate(err, obj); }); }, function(err, siblings) { _return(err, siblings); } ); } return _this; }; Bucket.prepareResult = function prepareResult(results, errors, not_array_input, _return) { if(results.length === 0) { results = null; } else if(not_array_input) { results = results[0]; } if(errors.length === 0) { errors = null; } else if(not_array_input) { errors = errors[0]; } _return(errors, results); }; Bucket.siblingLastWriteWins = function siblingLastWriteWins(siblings) { function siblingLastModifiedSort(a, b) { if(!a.metadata.last_modified || new Date(a.metadata.last_modified) < new Date(b.metadata.last_modified)) { return 1; } else { return -1; } } siblings.sort(siblingLastModifiedSort); return siblings[0]; }; module.exports = Bucket;