UNPKG

humblejs

Version:
957 lines (864 loc) 26.9 kB
// Generated by CoffeeScript 1.12.7 /* * HumbleJS * ======== * * ODM for MongoDB. * */ (function() { var Cursor, Database, Document, Embed, EmbeddedDocument, SparseReport, _, _getDefault, _invertSchema, _map, _pad, _proxy, _transform, _transformDotted, moment, mongojs, util, slice = [].slice, extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, hasProp = {}.hasOwnProperty, indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; _ = require('lodash'); util = require('util'); moment = require('moment'); mongojs = require('mongojs'); exports.auto_map_queries = true; /* * The database class */ Database = (function() { function Database() { var args, callable, db; args = 1 <= arguments.length ? slice.call(arguments, 0) : []; db = mongojs.apply(mongojs, args); Object.defineProperty(this, 'collection', { get: function() { return function() { var args; args = 1 <= arguments.length ? slice.call(arguments, 0) : []; return db.collection.apply(db, args); }; } }); callable = function(name) { if (name == null) { return db; } console.log(name); return db.collection(name); }; callable.__proto__ = this; return callable; } Database.prototype.document = function() { var args, collection, i, mapping; args = 2 <= arguments.length ? slice.call(arguments, 0, i = arguments.length - 1) : (i = 0, []), mapping = arguments[i++]; collection = this.collection.apply(this.collection, args); return new Document(collection, mapping); }; return Database; })(); exports.Database = Database; /* * The document class */ Document = (function() { var _wrap; function Document(collection, mapping) { var _document, callable; Object.defineProperty(this, 'collection', { get: function() { return collection; } }); if (mapping == null) { mapping = {}; } _document = this; this.instanceProto = {}; Object.defineProperties(this.instanceProto, { __schema: { value: mapping }, save: { get: function() { return function() { var args; args = 1 <= arguments.length ? slice.call(arguments, 0) : []; args.unshift(this); return _document.save.apply(_document, args); }; } }, insert: { get: function() { return function() { var args; args = 1 <= arguments.length ? slice.call(arguments, 0) : []; args.unshift(this); return _document.insert.apply(_document, args); }; } }, update: { get: function() { return function() { var args; args = 1 <= arguments.length ? slice.call(arguments, 0) : []; if (this._id == null) { throw new Error("Cannot update without '_id' field"); } args.unshift({ _id: this._id }); return _document.update.apply(_document, args); }; } }, remove: { get: function() { return function() { var args; args = 1 <= arguments.length ? slice.call(arguments, 0) : []; if (this._id == null) { throw new Error("Cannot remove without '_id' field"); } args.unshift({ _id: this._id }); return _document.remove.apply(_document, args); }; } }, forJson: { get: function() { return function(allowDefault) { var _forJson; if (allowDefault == null) { allowDefault = true; } _forJson = function(json) { var fn, name, ref, value; ref = this; fn = function(name, value) { var _json, key, ref1, ref2; if (_.isObject(value)) { if (_.isArray(value)) { ref1 = value, key = ref1[0], value = ref1[1]; if (!(key in json)) { if (!allowDefault) { return; } value = _getDefault(json, key, name, value); } if (!(key in json) && !_.isArray(json)) { json[name] = value; } return; } _json = (ref2 = json[name]) != null ? ref2 : {}; if (!_.isObject(_json)) { return; } _forJson.call(value, _json); if (!_.isEmpty(_json)) { json[name] = _json; } } }; for (name in ref) { value = ref[name]; fn(name, value); } return json; }; return _forJson.call(this.__schema, _transform(this, this.__inverse_schema, {})); }; } } }); _map(this, mapping, this.instanceProto); Object.defineProperty(this.instanceProto, '__inverse_schema', { value: _invertSchema(this.instanceProto.__schema) }); callable = (function(_this) { return function(doc) { return _this["new"](doc); }; })(this); callable.__proto__ = this; return callable; } _wrap = function(method) { return { get: function() { return (function(_this) { return function() { var args, cb, i, q, query; query = arguments[0], args = 3 <= arguments.length ? slice.call(arguments, 1, i = arguments.length - 1) : (i = 1, []), cb = arguments[i++]; if (exports.auto_map_queries) { if (method === 'findAndModify') { if (query.query != null) { query.query = _this._(query.query); } if (query.update != null) { query.update = _this._(query.update); } } else if (util.isArray(query)) { query = (function() { var j, len, results; results = []; for (j = 0, len = query.length; j < len; j++) { q = query[j]; results.push(this._(q)); } return results; }).call(_this); } else { query = _this._(query); } if (args.length) { args[0] = _this._(args[0]); } } if (!(cb instanceof Function) && (cb != null)) { if (exports.auto_map_queries && (cb === Object(cb)) && (!args.length)) { cb = _this._(cb); } args.push(cb); cb = null; } args.unshift(query); if (method === 'find' && !(cb instanceof Function)) { return new Cursor(_this, _this.collection.find.apply(_this.collection, args)); } args.push(_this.cb(cb)); return _this.collection[method].apply(_this.collection, args); }; })(this); } }; }; Object.defineProperties(Document.prototype, { /* * Return a new document instance. * * This is the same as doing ``new MyDocument()``. */ "new": { value: function(doc) { if (doc == null) { doc = {}; } return this.wrap(doc); } /* * MongoJS method wrappers */ }, find: _wrap('find'), findOne: _wrap('findOne'), findAndModify: _wrap('findAndModify'), insert: _wrap('insert'), update: _wrap('update'), // count: _wrap('count'), remove: _wrap('remove'), save: _wrap('save'), /* * Helper for wrapping documents in a callback. */ cb: { value: function(cb) { return (function(_this) { return function(err, doc) { var d; if (!doc || !cb) { return typeof cb === "function" ? cb(err, doc) : void 0; } if (util.isArray(doc)) { doc = (function() { var i, len, results; results = []; for (i = 0, len = doc.length; i < len; i++) { d = doc[i]; results.push(this.wrap(d)); } return results; }).call(_this); } else { doc = _this.wrap(doc); } return typeof cb === "function" ? cb(err, doc) : void 0; }; })(this); } /* * Wrap an individual document */ }, wrap: { value: function(doc) { return _proxy(doc, this.instanceProto); } /* * Transform keys in a query document into their mapped counterpart */ }, _: { value: function(doc) { var dest, schema; schema = this.instanceProto.__schema; dest = {}; return _transform(doc, schema, dest); } } }); return Document; })(); exports.Document = Document; /* * Cursor which wraps MongoJS cursors ensuring we get document mappings. */ Cursor = (function() { var _wrap, _wrap_doc; function Cursor(document, cursor) { this.document = document; this.cursor = cursor; } _wrap = function(method) { return { get: function() { return function() { var args, cb, i; args = 2 <= arguments.length ? slice.call(arguments, 0, i = arguments.length - 1) : (i = 0, []), cb = arguments[i++]; if (!(cb instanceof Function)) { if (cb != null) { args.push(cb); } return new Cursor(this.document, this.cursor[method].apply(this.cursor, args)); } args.push(this.document.cb(cb)); return this.cursor[method].apply(this.cursor, args); }; } }; }; _wrap_doc = function(method) { return { get: function() { return function() { var args, cb, i; args = 2 <= arguments.length ? slice.call(arguments, 0, i = arguments.length - 1) : (i = 0, []), cb = arguments[i++]; if (!(cb instanceof Function)) { if (cb != null) { args.push(cb); } return new Cursor(this.document, this.cursor[method].apply(this.cursor, args)); } args.push(this.document.cb(cb)); return this.cursor[method].apply(this.cursor, args); }; } }; }; Object.defineProperties(Cursor.prototype, { batchSize: _wrap('batchSize'), // count: _wrap_doc('count'), explain: _wrap_doc('explain'), forEach: _wrap('forEach'), limit: _wrap('limit'), map: _wrap_doc('map'), next: _wrap_doc('next'), skip: _wrap('skip'), sort: _wrap('sort'), toArray: _wrap_doc('toArray'), rewind: _wrap('rewind'), destroy: _wrap('destroy') }); return Cursor; })(); /* * Embedded documents */ EmbeddedDocument = (function() { function EmbeddedDocument(key, mapping) { var value; Object.defineProperties(this, { key: { value: key }, mapping: { value: mapping }, isEmbed: { value: true } }); for (key in mapping) { value = mapping[key]; this[key] = value; } } return EmbeddedDocument; })(); Embed = function(key, mapping) { return new EmbeddedDocument(key, mapping); }; exports.Embed = Embed; /* * Sparse reports */ SparseReport = (function(superClass) { extend(SparseReport, superClass); SparseReport.MINUTE = 'minute'; SparseReport.HOUR = 'hour'; SparseReport.DAY = 'day'; SparseReport.WEEK = 'week'; SparseReport.MONTH = 'month'; SparseReport.YEAR = 'year'; function SparseReport(collection, mapping, options) { var callable; this.options = options; mapping = _.defaults(mapping, { total: 'total', all: 'all', events: 'events', timestamp: 'timestamp', expires: 'expires' }); SparseReport.__super__.constructor.call(this, collection, mapping); if (this.options == null) { this.options = {}; } this.options = _.defaults(this.options, { sum: true, id_mark: '#' }); callable = (function(_this) { return function(doc) { return _this["new"](doc); }; })(this); callable.__proto__ = this; return callable; } SparseReport.prototype.getTimestamp = function(timestamp) { if (timestamp == null) { timestamp = new Date(); } timestamp = this.getPeriod(timestamp); return timestamp = timestamp.unix(); }; SparseReport.prototype.getPeriod = function(timestamp) { return moment.utc(timestamp).startOf(this.options.period); }; SparseReport.prototype.getId = function(identifier, timestamp) { timestamp = this.getTimestamp(timestamp); return "" + identifier + this.options.id_mark + (_pad(timestamp, 15)); }; SparseReport.prototype.getExpiry = function(timestamp) { var day, offset, week; day = 60 * 60 * 24; week = day * 7; offset = Math.random() * (week - day) + day; timestamp = moment.utc(timestamp).add(1, this.options.period); return moment.utc(timestamp).add(offset, 'seconds'); }; /* * Record events */ SparseReport.prototype.record = function(identifier, events, timestamp, callback) { var _id, expiry, key, update, updateTimestamp; if (_.isFunction(timestamp)) { callback = timestamp; timestamp = void 0; } _id = this.getId(identifier, timestamp); timestamp = this.getPeriod(timestamp).toDate(); expiry = this.getExpiry(timestamp).toDate(); update = {}; update[this.total] = 1; if (_.isNumber(events)) { update[this.total] = events; } else { for (key in events) { update[this.events + '.' + key] = events[key]; if (events[key] > update[this.total]) { update[this.total] = events[key]; } } } updateTimestamp = {}; updateTimestamp[this.timestamp] = timestamp; updateTimestamp[this.expires] = expiry; return this.findAndModify({ query: { _id: _id }, update: { $set: updateTimestamp, $inc: update }, "new": true, upsert: true }, callback); }; /* * Return a report for `identifier` between `start` and `end` dates */ SparseReport.prototype.get = function(identifier, start, end, callback) { var _this, endId, ref, ref1, startId; _this = this; if (_.isFunction(start)) { ref = [void 0, start], start = ref[0], callback = ref[1]; } if (_.isFunction(end)) { ref1 = [void 0, end], end = ref1[0], callback = ref1[1]; } if (start == null) { start = new Date(); } if (end == null) { end = new Date(); } startId = this.getId(identifier, start); endId = this.getId(identifier, end); return this.find({ _id: { $gte: startId, $lte: endId } }, function(err, docs) { var all, compiled, compiler, doc, i, j, len, len1, period, periods, ref2, ref3; if (err) { return callback(err, docs); } compiled = { total: 0, all: [], events: {} }; periods = _this.dateRange(start, end); all = {}; for (i = 0, len = periods.length; i < len; i++) { period = periods[i]; all[period.getTime()] = 0; } compiler = function(comp, doc) { var key, results, val; results = []; for (key in doc) { val = doc[key]; if (_.isNumber(val)) { if (comp[key] == null) { comp[key] = 0; } results.push(comp[key] += val); } else if (_.isObject(val)) { if (comp[key] == null) { comp[key] = {}; } results.push(compiler(comp[key], val)); } else { throw new Error("Expected number, got " + (typeof val)); } } return results; }; for (j = 0, len1 = docs.length; j < len1; j++) { doc = docs[j]; compiled.total += (ref2 = doc.total) != null ? ref2 : 0; all[doc.timestamp.getTime()] = (ref3 = doc.total) != null ? ref3 : 0; compiler(compiled.events, doc.events); } for (period in all) { compiled.all.push(all[period]); } return callback(err, compiled); }); }; /* * Return a range of period dates between `start` and `end`, inclusive. */ SparseReport.prototype.dateRange = function(start, end) { var current, range; start = this.getPeriod(start); end = this.getPeriod(end); range = []; range.push(start.toDate()); current = moment(start); current.add(1, this.options.period); while (current.isBefore(end) || current.isSame(end)) { range.push(moment(current).toDate()); current.add(1, this.options.period); } return range; }; return SparseReport; })(Document); exports.SparseReport = SparseReport; /* * Helper to pad a number with leading zeros as a string. */ _pad = function(num, size) { if (!(num.toString().length < size)) { return num; } if (num >= 0) { return (Math.pow(10, size) + Math.floor(num)).toString().substring(1); } return '-' + (Math.pow(10, size - 1) + Math.floor(0 - num)).toString().substring(1); }; /* * Helper to proxy an object prototype for instances */ _proxy = function(obj, proto) { var proxy; proxy = {}; proxy.__proto__ = proto; obj.__proto__ = proxy; return obj; }; /* * Helper to recursively map properties on and build instance prototypes */ _map = function(obj, mapping, proto, parent_key) { var key, name, results; results = []; for (name in mapping) { key = mapping[name]; results.push((function(name, key) { var array_proto, class_key, embed, embed_key, embed_proto, ref, value; if (key.isEmbed) { embed = key; key = embed.key; embed_key = parent_key ? parent_key + '.' + key : key; embed_key = new String(embed_key); Object.defineProperty(obj, name, { value: embed_key, enumerable: true }); embed_proto = {}; array_proto = {}; array_proto.__proto__ = Array.prototype; Object.defineProperty(array_proto, 'new', { value: function() { var new_obj; new_obj = {}; this.push(new_obj); return _proxy(new_obj, embed_proto); } }); Object.defineProperty(proto, name, { get: function() { var existing, i, item, len, value; if (key in this) { if (name === key) { this.__proto__ = Object.prototype; existing = this[key]; this.__proto__ = proto; if (existing == null) { this[key] = existing = {}; } } else { existing = this[key]; } if (util.isArray(existing)) { for (i = 0, len = existing.length; i < len; i++) { item = existing[i]; _proxy(item, embed_proto); } existing = _proxy(existing, array_proto); } else { existing = _proxy(existing, embed_proto); } return existing; } value = {}; this[key] = value; return _proxy(value, embed_proto); }, set: function(value) { if (name === key) { this.__proto__ = Object.prototype; this[key] = value; this.__proto__ = proto; return this[key]; } return this[key] = value; } }); Object.defineProperty(embed_proto, '___schema', { value: embed.mapping }); return _map(embed_key, embed.mapping, embed_proto, embed_key); } else { if (util.isArray(key)) { ref = key, key = ref[0], value = ref[1]; } if (parent_key) { class_key = new String(parent_key + '.' + key); Object.defineProperty(obj, name, { value: class_key, enumerable: true }); Object.defineProperty(obj[name], 'key', { value: key }); } else { Object.defineProperty(obj, name, { value: key, enumerable: true }); } return Object.defineProperty(proto, name, { configurable: true, get: function() { return _getDefault(this, name, key, value); }, set: function(value) { if (name === key) { this.__proto__ = Object.prototype; this[key] = value; this.__proto__ = proto; return this[key]; } return this[key] = value; } }); } })(name, key)); } return results; }; /* * Helper to get a default value */ _getDefault = function(doc, name, key, value) { var proto, val; if (name === key) { proto = doc.__proto__; doc.__proto__ = Object.prototype; if (key in doc) { val = doc[key]; } doc.__proto__ = proto; if (val) { return val; } } else if (key in doc) { return doc[key]; } val = typeof value === "function" ? value() : void 0; if (val !== void 0) { doc[key] = val; return val; } return value; }; /* * Helper to map a document or query to a key set */ _transform = function(doc, schema, dest) { var key, key_name, name, v, value; if (typeof doc !== 'object') { return doc; } for (name in doc) { value = doc[name]; if (name[0] === '$') { if (util.isArray(value)) { dest[name] = (function() { var i, len, results; results = []; for (i = 0, len = value.length; i < len; i++) { v = value[i]; results.push(_transform(v, schema, {})); } return results; })(); } else { dest[name] = {}; _transform(value, schema, dest[name]); } continue; } if (indexOf.call(name, '.') >= 0) { name = _transformDotted(name, schema); } if (name in schema) { key = schema[name]; if (util.isArray(key)) { key = key[0]; } key_name = key.isEmbed ? key.key : key; if (key.isEmbed) { if (util.isArray(value)) { dest[key_name] = (function() { var i, len, results; results = []; for (i = 0, len = value.length; i < len; i++) { v = value[i]; results.push(_transform(v, key, {})); } return results; })(); } else { if (value === Object(value)) { dest[key_name] = {}; _transform(value, key, dest[key_name]); } else { dest[key_name] = value; } } } else { dest[key_name] = value; } } else { dest[name] = value; } } return dest; }; /* * Helper to map dotted notation property names to key names */ _transformDotted = function(name, schema) { var default_value, dot, key, parts, ref; if (indexOf.call(name, '.') < 0) { if (schema instanceof Object && name in schema) { key = schema[name]; if (util.isArray(key)) { ref = key, key = ref[0], default_value = ref[1]; } key = key.isEmbed ? key.key : key; return key; } else { return name; } } dot = name.indexOf('.'); parts = name.slice(dot + 1); name = name.slice(0, dot); if (schema instanceof Object && name in schema) { key = schema[name]; key = key.isEmbed ? key.key : key; return key + '.' + _transformDotted(parts, schema[name]); } else { return name + '.' + parts; } }; /* * Helper to invert object keys */ _invertSchema = function(schema) { var default_value, inverse, key, ref, value; inverse = {}; for (key in schema) { value = schema[key]; if (value.isEmbed) { inverse[value.key] = Embed(key, _invertSchema(value.mapping)); } else if (util.isArray(value)) { ref = value, value = ref[0], default_value = ref[1]; inverse[value] = key; } else { inverse[value] = key; } } return inverse; }; }).call(this);