iridium
Version:
A custom lightweight ORM for MongoDB designed for power-users
284 lines (282 loc) • 11.8 kB
JavaScript
var _ = require('lodash');
var MongoDB = require('mongodb');
var Bluebird = require('bluebird');
var Skmatc = require('skmatc');
/**
* The default Iridium Instance implementation which provides methods for saving, refreshing and
* removing the wrapped document from the collection, as well as integrating with Omnom, our
* built in document diff processor which allows clean, atomic, document updates to be performed
* without needing to write the update queries yourself.
*
* @param TDocument The interface representing the structure of the documents in the collection.
* @param TInstance The type of instance which wraps the documents, generally the subclass of this class.
*
* This class will be subclassed automatically by Iridium to create a model specific instance
* which takes advantage of some of v8's optimizations to boost performance significantly.
* The instance returned by the model, and all of this instance's methods, will be of type
* TInstance - which should represent the merger of TSchema and IInstance for best results.
*/
var Instance = (function () {
/**
* Creates a new instance which represents the given document as a type of model
* @param model The model that dictates the collection the document originated from as well as how validations are performed.
* @param document The document which should be wrapped by this instance
* @param isNew Whether the document is new (doesn't exist in the database) or not
* @param isPartial Whether the document has only a subset of its fields populated
*
*/
function Instance(model, document, isNew, isPartial) {
var _this = this;
if (isNew === void 0) { isNew = true; }
if (isPartial === void 0) { isPartial = false; }
this._model = model;
this._isNew = !!isNew;
this._isPartial = isPartial;
this._original = document;
this._modified = _.cloneDeep(document);
_.each(model.core.plugins, function (plugin) {
if (plugin.newInstance)
plugin.newInstance(_this, model);
});
}
Object.defineProperty(Instance.prototype, "document", {
/**
* Gets the underlying document representation of this instance
*/
get: function () {
return this._modified;
},
enumerable: true,
configurable: true
});
Instance.prototype.save = function () {
var _this = this;
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i - 0] = arguments[_i];
}
var callback = null;
var changes = null;
var conditions = {};
Array.prototype.slice.call(args, 0).reverse().forEach(function (arg) {
if (typeof arg == 'function')
callback = arg;
else if (typeof arg == 'object') {
if (!changes)
changes = arg;
else
conditions = arg;
}
});
return Bluebird.resolve().then(function () {
conditions = _.cloneDeep(conditions);
_.merge(conditions, { _id: _this._modified._id });
if (!changes) {
var validation = _this._model.helpers.validate(_this._modified);
if (validation.failed)
return Bluebird.reject(validation.error).bind(_this).nodeify(callback);
var original = _.cloneDeep(_this._original);
var modified = _.cloneDeep(_this._modified);
changes = _this._model.helpers.diff(original, modified);
}
if (!_.keys(changes).length)
return null;
return changes;
}).then(function (changes) {
if (!changes && !_this._isNew)
return changes;
return _this._model.handlers.savingDocument(_this, changes).then(function () { return changes; });
}).then(function (changes) {
if (!changes && !_this._isNew)
return false;
if (_this._isNew) {
return new Bluebird(function (resolve, reject) {
_this._model.collection.insertOne(_this._modified, { w: 'majority' }, function (err, doc) {
if (err)
return reject(err);
return resolve(!!doc);
});
});
}
else {
return new Bluebird(function (resolve, reject) {
_this._model.collection.updateOne(conditions, changes, { w: 'majority' }, function (err, changed) {
if (err) {
err['conditions'] = conditions;
err['changes'] = changes;
return reject(err);
}
return resolve(changed);
});
});
}
}).catch(function (err) {
err['original'] = _this._original;
err['modified'] = _this._modified;
return Bluebird.reject(err);
}).then(function (changed) {
conditions = { _id: _this._modified._id };
if (!changed)
return _this._modified;
return new Bluebird(function (resolve, reject) {
_this._model.collection.findOne(conditions, function (err, latest) {
if (err)
return reject(err);
return resolve(latest);
});
});
}).then(function (latest) {
if (!latest) {
_this._isNew = true;
_this._original = _.cloneDeep(_this._modified);
return Bluebird.resolve(_this);
}
return _this._model.handlers.documentReceived(conditions, latest, function (value) {
_this._isPartial = false;
_this._isNew = false;
_this._modified = value;
_this._original = _.cloneDeep(value);
return _this;
});
}).nodeify(callback);
};
/**
* Updates this instance to match the latest document available in the backing collection
* @param {function(Error, IInstance)} callback A callback which is triggered when the update completes
* @returns {Promise<TInstance>}
*/
Instance.prototype.update = function (callback) {
return this.refresh(callback);
};
/**
* Updates this instance to match the latest document available in the backing collection
* @param {function(Error, IInstance)} callback A callback which is triggered when the update completes
* @returns {Promise<TInstance>}
*/
Instance.prototype.refresh = function (callback) {
var _this = this;
var conditions = { _id: this._original._id };
return Bluebird.resolve().then(function () {
return new Bluebird(function (resolve, reject) {
_this._model.collection.findOne(conditions, function (err, doc) {
if (err)
return reject(err);
return resolve(doc);
});
});
}).then(function (newDocument) {
if (!newDocument) {
_this._isPartial = true;
_this._isNew = true;
_this._original = _.cloneDeep(_this._modified);
return _this;
}
return _this._model.handlers.documentReceived(conditions, newDocument, function (doc) {
_this._isNew = false;
_this._isPartial = false;
_this._original = doc;
_this._modified = _.cloneDeep(doc);
return _this;
});
}).nodeify(callback);
};
/**
* Removes this instance's document from the backing collection
* @param {function(Error, IInstance)} callback A callback which is triggered when the operation completes
* @returns {Promise<TInstance>}
*/
Instance.prototype.delete = function (callback) {
return this.remove(callback);
};
/**
* Removes this instance's document from the backing collection
* @param {function(Error, IInstance)} callback A callback which is triggered when the operation completes
* @returns {Promise<TInstance>}
*/
Instance.prototype.remove = function (callback) {
var _this = this;
var conditions = { _id: this._original._id };
return Bluebird.resolve().then(function () {
if (_this._isNew)
return 0;
return new Bluebird(function (resolve, reject) {
_this._model.collection.remove(conditions, { w: 'majority' }, function (err, removed) {
if (err)
return reject(err);
return resolve(removed);
});
});
}).then(function (removed) {
if (removed)
return _this._model.cache.clear(conditions);
return false;
}).then(function () {
_this._isNew = true;
return _this;
}).nodeify(callback);
};
Instance.prototype.first = function (collection, predicate) {
var _this = this;
var result = null;
_.each(collection, function (value, key) {
if (predicate.call(_this, value, key)) {
result = value;
return false;
}
});
return result;
};
Instance.prototype.select = function (collection, predicate) {
var _this = this;
var isArray = Array.isArray(collection);
var results = isArray ? [] : {};
_.each(collection, function (value, key) {
if (predicate.call(_this, value, key)) {
if (isArray)
results.push(value);
else
results[key] = value;
}
});
return results;
};
/**
* Gets the JSON representation of this instance
* @returns {TDocument}
*/
Instance.prototype.toJSON = function () {
return this.document;
};
/**
* Gets a string representation of this instance
* @returns {String}
*/
Instance.prototype.toString = function () {
return JSON.stringify(this.document, null, 2);
};
/**
* The schema used to validate documents of this type before being stored in the database.
*/
Instance.schema = {
_id: false
};
/**
* Additional which should be made available for use in the schema definition for this instance.
*/
Instance.validators = [
Skmatc.create(function (schema) { return schema === MongoDB.ObjectID; }, function (schema, data) {
return this.assert(!data || data instanceof MongoDB.ObjectID || (data._bsontype === 'ObjectID' && data.id));
}, { name: 'ObjectID validation' })
];
/**
* The transformations which should be applied to properties of documents of this type.
*/
Instance.transforms = {};
/**
* The indexes which should be managed by Iridium for the collection used by this type.
*/
Instance.indexes = [];
return Instance;
})();
exports.Instance = Instance;
//# sourceMappingURL=Instance.js.map