Source: Select.js

'use strict';

/**
 * Return a function based on a condition.
 * Functional alternative to switch-case.
 * @projectname Select-Return
 * @version 1.0.0
 * @author Muthu Kumar (@MKRhere)
 */

/**
 * Creates a SelectValue instance with a value and optional resolve function.
 * Created internally from SelectIterable constructor, and not exported.
 * @class SelectValue
 */
class SelectValue {
	/**
	 * @param {any} value - the input value
	 * @param {function} resolve - optional resolve function
	 * @constructs SelectIterable
	 */
	constructor(value, resolve) {
		this.value = value;
		if (resolve) this.resolve = (...args) => resolve(...args, value);
	}

	/**
	 * Default resolve prototype. Returns null when called.
	 * Used in case a resolve is never set.
	 * @returns {object} null
	 * @memberof SelectValue
	 */
	resolve() {
		return null;
	}
}

/**
 * Creates a SelectIterable instance from an array.
 * Created internally from Select.prototype.for, and not exported.
 * @class SelectIterable
 */
class SelectIterable {
	/**
	 * @param {array} values - array created from Select.prototype.for
	 * @param {Array<function>} tests - array of { test, consequent } objects
	 * @param {function} tests[].test - test function
	 * @param {function} tests[].consequent - consequent function
	 * @constructs SelectIterable
	 */
	constructor(values, tests) {
		this.values = values
			.map(x => x instanceof SelectValue
				? x
				: new SelectValue(x)
			);
		this.tests = tests;
	}

	/**
	 * Accepts a test and consequent function each and returns a new
	 * SelectIterable instance.
	 * @param {Test} test - test callback function
	 * @param {function} consequent - consequent callback function
	 * @returns {SelectIterable} - an instance of SelectIterable
	 * @memberof SelectIterable
	 */
	for(test, consequent) {
		/* SelectIterable.prototype.for works a little
			differently than Select.prototype.for,
			by accumulating the tests and resolving
			all the values when .resolve() is called */
		return new SelectIterable(
			this.values,
			[ ...this.tests, { test, consequent } ]
		);
	}

	resolve(...args) {
		/* When .resolve() is called, a resolved value
			is generated for each value in the array */
		return this.values.map(item => {
			const resolver = this
				.tests
				.find(x => x.test(item.value)
					? x.consequent
					: null);
			return resolver
				? resolver.consequent(...args, item.value)
				: null;
		});
	}
}

/**
 * Creates a new Select instance.
 * @class Select
 * @extends {SelectValue}
 */
class Select extends SelectValue {
	/**
	 * @param {any|array} value - the value or array of values to check against
	 * @param {function} resolve - optional resolve function
	 * @constructs Select
	 */
	constructor(value, resolve) {
		super(value, resolve);
		this.iterable = typeof value === "object" && Symbol.iterator in value;
	}

	/**
	 * Accepts a test and consequent function each and returns a new
	 * Select or SelectIterable instance.
	 * @param {function} test - test callback function
	 * @param {function} consequent - consequent callback function
	 * @returns {Select|SelectIterable} - Returns a SelectIterable instance
	 * if value was array, or a Select instance otherwise
	 * @memberof Select
	 */
	for(test, consequent) {
		if (this.iterable) {
			/* If the value passed to the constructor is
				an array, initialise a new SelectIterable
				with the array and { test, consequent } pair
				and return */
			return new SelectIterable(
				Array.from(this.value),
				[ { test, consequent } ],
			);
		}
		if (test(this.value)) return new Select(this.value, consequent);
		/* If the test doesn't pass, just pass the Select
			instance along the chain until a test passes,
			or .resolve() is called */
		return this;
	}
}

module.exports = Select;