seneca-promisified-entity/src/index.js

import Promise from 'any-promise';

/**
 * This just wraps over the object which is created when you call `make`.
 *
 * The `fields$`, `is$`, `clone$`, `data$`, `canon$`, `native$` methods all
 * work the exact same way as the original seneca entity.
 */
class SenecaEntityWrapper {
	/**
	 * @param {Object} entity - The entity returned when you call the real
	 * `seneca.make$` method.
	 */
	constructor(entity) {
		Object.defineProperty(this, '_entity', {
			value: entity
		});
	}

	/** @private */
	_forDataInWrapper(fn) {
		for(var k in this) {
			if(k[k.length - 1] !== '$'
					&& k[0] !== '_') {
				fn(this[k], k);
			}
		}
	}

	/** @private */
	_forDataInEnt(fn) {
		for(var k in this._entity) {
			if(k[k.length - 1] !== '$') {
				fn(this._entity[k], k);
			}
		}
	}

	/** @private */
	_clearEnt() {
		this._forDataInEnt((_ , k) => {
			delete this._entity[k];
		});
	}

	/** @private */
	_fromWrapperToEnt() {
		this._forDataInWrapper((val, k) => {
			this._entity[k] = val;
		});
	}

	/** @private */
	_fromEntToWrapper() {
		this._forDataInEnt((val, k) => {
			this[k] = val;
		});
	}

	/** @private */
	_callWithOpt(opt, prop) {
		return new Promise((resolve, reject) => {
			const onComplete = (err, ent) => {
				if(err) return reject(err);
				const wrapped = new SenecaEntityWrapper(ent);
				wrapped._fromEntToWrapper();
				resolve(wrapped);
			};

			if(opt !== undefined) {
				this._entity[prop](opt, onComplete);
			} else {
				// clear it to remove items which may not be
				// present in the wrapper. If they're not in
				// the wrapper they won't be overriden.
				this._clearEnt(); 
				this._fromWrapperToEnt();
				this._entity[prop](onComplete);
			}
		});
	}

	/**
	 * Saves the stored entity
	 * @param {Object} obj - Optional object to use for saving. If not specified
	 * it will use the members on the entity object itself as the data to save.
	 * @returns {Promise}
	 * @example
	 * const ent = seneca.make('person');
	 * ent.id = 1;
	 * ent.name = 'foobar';
	 * ent.save$(); // => Promise
	 */
	save$(obj) {
		return this._callWithOpt(obj, 'save$');
	}

	/**
	 * Loads an entity.
	 * @param {Object} query - Query to pass down to the internal seneca
	 * instance.
	 * @returns {Promise}
	 * @example
	 * const funnyJoe = seneca
	 *   .make('person')
	 *   .load$({ funny: true, name: 'Joe' });
	 */
	load$(query) {
		return this._callWithOpt(query, 'load$');
	}

	/**
	 * Returns a list of entities.
	 * @param {Object} query - The query to pass down to the internal seneca
	 * instance. The query dsl will depdend on what kind of module you're using
	 * with the entity module.
	 * @returns {Promise}
	 * @example
	 * const allPersons = seneca.make('person').list$({ all$: true });
	 */
	list$(query) {
		return this._callWithOpt(query, 'list$');
	}

	/**
	 * Removes the given entity
	 * @param {Object} query - The query to pass to the internal seneca instance.
	 * If not specified will use the fields stored on the entity itself.
	 * @returns {Promise}
	 * @example
	 * // Creates then removes the entity immediately.
	 * seneca
	 *   .make('person')
	 *   .save$({ name: 'foobar' })
	 *   .then((ent) => ent.remove$());
	 */
	remove$(query) {
		return this._callWithOpt(query, 'remove$');
	}
	
}

const inheritsSymbolics = [
	'fields',
	'is',
	'canon',
	'native',
	'data',
	'clone'
];

inheritsSymbolics.forEach((key) => {
	const symbolicKey = key + '$';
	SenecaEntityWrapper.prototype[symbolicKey] = function() {
		this._entity[symbolicKey].apply(this._entity, arguments);
	};
});


const make = function (...args) {
	const entity = this._seneca.make.apply(this._entity, args);
	return new SenecaEntityWrapper(entity);
};

// Need to patch the item.
module.exports = (wrapper) => {
	wrapper.make = make;
	wrapper.make$ = make;
};

module.exports.make = make;
module.exports.SenecaEntityWrapper = SenecaEntityWrapper;