const DELIMITER = '.';
/**
* Determine whether a path argument is valid
* @param path
* @return {boolean}
*/
function isValidPath(path) {
if (path === null || path === undefined) return false;
return typeof path === 'string' && path.length > 0;
}
/**
* Private constructor. Use <code>Maybe.of()</code> instead.
* @param val {*} Object, string, or number
* @constructor
*/
const Maybe = function Maybe(val) {
this.__value = val;
};
/**
* Public constructor. Creates an instance of Maybe.
* @param val {*} Object, string, number, or function (direct object access)
* @example const exampleObj = {
* foo: 'bar',
* baz: [1,2,3]
* };
*
* const maybe1 = Maybe.of(exampleObj);
* const maybe2 = Maybe.of(() => exampleObj.baz.1);
* @returns {Maybe} A Maybe monad
*/
Maybe.of = function (val) {
try {
return new Maybe(
typeof val === 'function' ? val() : val,
);
} catch (error) {
return new Maybe(undefined);
}
};
/**
* Get the monad's value
* @example const maybe1 = Maybe.of(123);
* const maybe2 = Maybe.of(null);
*
* maybe1.join(); // 123
* maybe2.join(); // null
* @returns {*} Returns the value of the monad
*/
Maybe.prototype.join = function () {
return this.__value;
};
/**
* Determine whether the monad's value exists
* @example const maybe1 = Maybe.of(123);
* const maybe2 = Maybe.of(undefined);
*
* maybe1.isJust(); // true
* maybe2.isJust(); // false
* @returns {boolean} <code>true</code> if the value is defined,
* <code>false</code> if the monad is null or undefined.
*/
Maybe.prototype.isJust = function () {
return !this.isNothing();
};
/**
* Determine whether the monad's value is null or undefined
* @example const maybe1 = Maybe.of(null);
* const maybe2 = Maybe.of(123);
*
* maybe1.isNothing(); // true
* maybe2.isNothing() // false
* @returns {boolean} <code>true</code> if the value is null or
* undefined, <code>false</code> if the value is defined.
*/
Maybe.prototype.isNothing = function () {
return this.__value === null || this.__value === undefined;
};
/**
* Chain to the end of a monad as the default value to return if the <code>isNothing()</code> is true
* @param defaultValue {string} Return this value when
* <code>join()</code> is called and <code>isNothing()</code> is true
* @example const maybe1 = Maybe.of(null);
*
* maybe1.orElse('N/A');
* maybe1.join(); // 'N/A'
* @returns {Maybe} A monad containing the default value
*/
Maybe.prototype.orElse = function (defaultValue) {
if (this.isNothing()) {
return Maybe.of(defaultValue);
}
return this;
};
/**
* Get a value on the monad given a single property
* @param property {string|number} Look for this property on the monad
* @example const exampleObj = {
* foo: 'bar',
* baz: [1,2,3]
* };
*
* const maybeBar = Maybe.of(exampleObj).prop('foo');
*
* maybeBar.join(); // 'bar'
* @returns {Maybe} A monad containing the value of a given property or index
*/
Maybe.prototype.prop = function (property) {
return this.map(value => value[property]);
};
/**
* Get a value on the monad given a property path in argument form
* @param properties {string|number} Argument list that represents the property path to search
* @example const exampleObj = {
* foo: 'bar',
* baz: [1,2,3]
* };
*
* const maybeArrayVal = Maybe.of(exampleObj).props('baz', 0);
*
* maybeArrayVal.join(); // 1
* @returns {Maybe} A monad containing the value at a given path
*/
Maybe.prototype.props = function (...properties) {
if (properties.length === 0) {
return Maybe.of(undefined);
}
const maybeValue = this.prop(properties.shift());
return properties.length > 0
? maybeValue.props(...properties)
: maybeValue;
};
/**
* Get a value on the monad given a property path in string form
* @param path {string} A period delimited string representing the path (e.g. 'foo.bar.baz)
* to search
* @example const exampleObj = {
* foo: 'bar',
* baz: [1,2,3]
* };
*
* const maybeArrayVal = Maybe.of(exampleObj).path('baz.0');
*
* maybeArrayVal.join(); // 1
* @returns {Maybe} A monad containing the value at a given path
*/
Maybe.prototype.path = function (path) {
return isValidPath(path)
? this.props(...path.split(DELIMITER))
: Maybe.of(undefined);
};
/**
* Apply a transformation to the monad
* @param transform {function} The transformation function to apply to the monad
* @example Maybe.of(1).map(val => val + 1);
* @returns {Maybe} A monad created from the result of the transformation
*/
Maybe.prototype.map = function (transform) {
if (typeof transform !== 'function') throw new Error('transform must be a function');
if (this.isNothing()) {
return Maybe.of(undefined);
}
return Maybe.of(transform(this.join()));
};
/**
* Chain together functions that return Maybe monads
* @param fn {function} Function that is passed the value of the calling monad, and returns a monad.
* @example function addOne (val) {
* return Maybe.of(val + 1);
* }
*
* const three = Maybe.of(1)
* .chain(addOne)
* .chain(addOne)
* .join();
* @returns {Maybe} A monad created from the result of the transformation
*/
Maybe.prototype.chain = function (fn) {
return this.map(fn).join();
};
module.exports = Maybe;