humblejs
Version:
HumbleDB for Javascript
957 lines (864 loc) • 26.9 kB
JavaScript
// 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);