UNPKG

iridium

Version:

A custom lightweight ORM for MongoDB designed for power-users

263 lines 10.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const Validators_1 = require("./Validators"); const _ = require("lodash"); const Bluebird = require("bluebird"); /** * 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 {any} TDocument The interface representing the structure of the documents in the collection. * @param {any} 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. */ class Instance { /** * Creates a new instance which represents the given document as a type of model * @param {Model} model The model that dictates the collection the document originated from as well as how validations are performed. * @param {Object} document The document which should be wrapped by this instance * @param {Boolean} [isNew] Whether the document is new (doesn't exist in the database) or not * @param {Boolean} [isPartial] Whether the document has only a subset of its fields populated * */ constructor(model, document, isNew = true, isPartial = false) { this._model = model; this._isNew = !!isNew; this._isPartial = isPartial; this._original = document; this._modified = model.helpers.cloneDocument(document); _.each(model.core.plugins, (plugin) => { if (plugin.newInstance) plugin.newInstance(this, model); }); } /** * Gets the underlying document representation of this instance */ get document() { return this._modified; } save(...args) { let callback = undefined; let changes = null; let conditions = {}; Array.prototype.slice.call(args, 0).reverse().forEach((arg) => { if (typeof arg == "function") callback = arg; else if (typeof arg == "object") { if (!changes) changes = arg; else conditions = arg; } }); return Bluebird.resolve().then(() => { conditions = this._model.helpers.cloneConditions(conditions); _.merge(conditions, { _id: this._modified._id }); if (!changes) { let validation = this._model.helpers.validate(this._modified); if (validation.failed) return Bluebird.reject(validation.error).bind(this).nodeify(callback); let original = this._model.helpers.cloneDocument(this._original); let modified = this._model.helpers.cloneDocument(this._modified); modified = this._model.helpers.transformToDB(modified, { document: true }); changes = this._model.helpers.diff(original, modified); } if (!_.keys(changes).length) return null; return changes; }).then((changes) => { if (!changes && !this._isNew) return changes; return this._model.handlers.savingDocument(this, changes).then(() => changes); }).then((changes) => { if (!changes && !this._isNew) return false; if (this._isNew) { return new Bluebird((resolve, reject) => { this._model.collection.insertOne(this._modified, { w: "majority" }, (err, doc) => { if (err) return reject(err); return resolve(!!doc); }); }); } else { return new Bluebird((resolve, reject) => { this._model.collection.updateOne(conditions, changes, { w: "majority" }, (err, changed) => { if (err) { err["conditions"] = conditions; err["changes"] = changes; return reject(err); } return resolve(!!changed.modifiedCount); }); }); } }).catch(err => { err["original"] = this._original; err["modified"] = this._modified; return Bluebird.reject(err); }).then((changed) => { conditions = { _id: this._modified._id }; if (!changed) return this._modified; return new Bluebird((resolve, reject) => { this._model.collection.find(conditions).limit(1).next((err, latest) => { if (err) return reject(err); return resolve(latest); }); }); }).then((latest) => { if (!latest) { this._isNew = true; this._original = this._model.helpers.cloneDocument(this._modified); return Bluebird.resolve(this); } return this._model.handlers.documentReceived(conditions, latest, (value) => { this._isPartial = false; this._isNew = false; this._modified = value; this._original = this._model.helpers.cloneDocument(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>} */ update(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>} */ refresh(callback) { let conditions = { _id: this._original._id }; return Bluebird.resolve().then(() => { return new Bluebird((resolve, reject) => { this._model.collection.find(conditions).limit(1).next((err, doc) => { if (err) return reject(err); return resolve(doc); }); }); }).then((newDocument) => { if (!newDocument) { this._isPartial = true; this._isNew = true; this._original = this._model.helpers.cloneDocument(this._modified); return this; } return this._model.handlers.documentReceived(conditions, newDocument, (doc) => { this._isNew = false; this._isPartial = false; this._original = doc; this._modified = this._model.helpers.cloneDocument(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>} */ delete(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>} */ remove(callback) { let conditions = { _id: this._original._id }; return Bluebird.resolve().then(() => { if (this._isNew) return 0; return new Bluebird((resolve, reject) => { this._model.collection.deleteOne(conditions, { w: "majority" }, (err, removed) => { if (err) return reject(err); return resolve(removed); }); }); }).then((removed) => { if (removed) return this._model.cache.clear(conditions); return false; }).then(() => { this._isNew = true; return this; }).nodeify(callback); } first(collection, predicate) { let result = null; _.each(collection, (value, key) => { if (predicate.call(this, value, key)) { result = value; return false; } }); return result; } select(collection, predicate) { let isArray = Array.isArray(collection); let results = isArray ? [] : {}; _.each(collection, (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} */ toJSON() { return this.document; } /** * Gets a string representation of this instance * @returns {String} */ toString() { 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 = Validators_1.DefaultValidators(); /** * 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 = []; exports.Instance = Instance; //# sourceMappingURL=Instance.js.map