'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
/**
* @description Convert camelCase strings to snake_case.
* @param {string} x - string to convert
* @returns {string} converted string
*
* @example
* import { camelCaseToSnakeCase } from "@nextml/lodestar";
*
* camelCaseToSnakeCase("helloWorld"); // => "hello_world"
*/
const camelCaseToSnakeCase = (x) =>
x
.replace(/([A-Z])/g, " $1")
.split(" ")
.join("_")
.toLowerCase();
/**
* @description Convert the first letter of a string to uppercase
* and the rest to lowercase.
* @param {string} x - string to capitalize
* @returns {string} capitalized string
*
* @example
* import { capitalize } from "@nextml/lodestar";
*
* capitalize("foo"); // => "Foo"
* capitalize("FOO"); // => "Foo"
* capitalize("foo bar"); // => "Foo bar"
*/
const capitalize = (string) =>
string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
const EmptyArgumentListError = new Error("Argument list is empty");
/**
* Function composition left to right (sequential reading order).
* @param {...function} fns - functions to compose
* @returns {function} composed function
*
* @example
* import { compose } from "@nextml/lodestar";
*
* compose(addOne, multiplyByTwo)(2); // => 6
*/
const compose = (...fns) => {
if (fns.length === 0) {
throw EmptyArgumentListError;
}
return fns.reduce(
(f, g) =>
(...args) =>
g(f(...args))
);
};
/**
* Asynchronous function composition left to right (sequential reading order).
* @param {...function} fns - functions to compose, including asynchronous
* @returns {function} composed asynchronous function
*
* @example
* import { composeAsync } from "@nextml/lodestar";
*
* composeAsync(fetch, doSomething)(url); // => 6
*/
const composeAsync = (...fns) => {
if (fns.length === 0) {
throw EmptyArgumentListError;
}
return (x) => fns.reduce((f, g) => f.then(g), Promise.resolve(x));
};
const isFunctionWithoutArguments = (fn) => fn.length === 0;
const hasAppliedAllArguments = (fn, args) => fn.length <= args.length;
const partialApplication =
(fn, appliedArguments) =>
(...args) => {
const nextAppliedArguments = [...appliedArguments, ...args];
if (hasAppliedAllArguments(fn, nextAppliedArguments)) {
return fn(...nextAppliedArguments);
}
// Still waiting for more arguments
return partialApplication(fn, nextAppliedArguments);
};
/**
* @description Curry a function to enable partial application.
* @example <caption>Importing</caption>
* import { curry } from "@nextml/lodestar";
*
* curry((a, b, c) => a + b + c)(2, 3)(5) // => 10
* curry((a, b, c) => a + b + c)(2)(3, 5) // => 10
* curry((a, b, c) => a + b + c)(2)(3)(5) // => 10
* curry((a, b, c) => a + b + c)(2, 3, 5) // => 10
*
* @param {function} fn - the function to curry
* @returns {function} curried function
*/
const curry = (fn) => {
if (isFunctionWithoutArguments(fn)) {
return fn;
}
return partialApplication(fn, []);
};
/**
* @description Defer function execution.
* @param {function} fn - the function to defer
* @param {any} args - input arguments for the deferred function `fn`
* @returns {function} constructed function
*
* @example
* import { defer } from "@nextml/lodestar";
*
* const add = (a, b) => a + b;
* const addLater = defer(add, 2, 3);
* addLater(); // => 5
*/
const defer =
(fn, ...args) =>
(...callerArgs) =>
fn(...args, ...callerArgs);
/**
* @description filter values in an array
* @param {function} fn - filter function
* @param {array} xs - values to filter from
* @returns {array} array with filtered values
*
* @example
* import { filter } from "@nextml/lodestar";
*
* const nonZero = (x) => x !== 0
* filter(notZero, [1, 0, 1, 1, 0, 2, 3])
* // => [1, 1, 1, 2, 3]
*
* TODO: add more examples...
*/
const filter = curry((fn, xs) => xs.filter(fn));
/**
* @description Return input value.
* @param {any} x - any input value
* @returns {any} input value
*
* @example
* import { identity } from "@nextml/lodestar";
*
* identity(1); // => 1
* identity("hello"); // => "hello"
*/
const identity = (x) => x;
const UNIT_TYPES = [undefined, null];
/**
* @description Check if input is bottom value.
* @param {any} x - any input value
* @returns {boolean} if bottom value
*
* @example
* import { isUnitType } from "@nextml/lodestar";
*
* isUnitType(undefined); // => true
* isUnitType(null); // => true
*
* isUnitType(1); // => false
* isUnitType("foo"); // => false
*/
const isUnitType = (x) => UNIT_TYPES.includes(x);
/**
* @description Check if input arguments have a defined value
* e.g. is not null or undefined
* @param {...*} args - checked values
* @returns {boolean} if all input values are defined
*
* @example
* import { isDefined } from "@nextml/lodestar";
*
* isDefined(1); // => true
* isDefined("foo", () => {}); // => true
*
* isDefined(undefined); // => false
* isDefined(null); // => false
* isDefined("foo", null); // => false
*/
const isDefined = (...args) => {
if (args.length === 0) {
throw EmptyArgumentListError;
}
return !args.map(isUnitType).includes(true);
};
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors
const ERRORS = [
Error,
RangeError,
ReferenceError,
SyntaxError,
TypeError,
URIError,
];
/**
* @description Check if input is instance of any built-in error type.
* @param {any} x - checked value
* @returns {boolean} if checked value is error
*
* @example
* import { isError } from '@nextml/lodestar';
*
* isError(new Error('foo')); // true
* isError(new TypeError('bar')); // true
*
* isError(1); // false
* isError('foo'); // false
* isError(false); // false
*/
const isError = (x) =>
ERRORS.map((errorType) => x instanceof errorType).includes(true);
/**
* @description array mapping function
* @param {function} fn - mapper function
* @param {array} xs - array of values to map over
* @returns {any} array with mapped values
*
* @example
* import { map } from "@nextml/lodestar";
*
* map((x) => x + 1, [0, 1, 2])
* // => [1, 2, 3]
*
* TODO: add more examples...
*/
const map = curry((fn, xs) => xs.map(fn));
/**
* @description Check input type of defined value.
* @param {any} x - any non unit type value
* @returns {string} name of type
*
* @example
* import { typeOf } from "@nextml/lodestar";
*
* typeOf(1); // => "number"
* typeOf("hello"); // => "string"
* typeOf(false); // => "boolean"
* typeOf(() => {}); // => "function"
* typeOf([]); // => "array"
* typeOf({}); // => "object"
*/
const typeOf = (x) => {
if (x === null) {
return "null";
}
if (Array.isArray(x)) {
return "array";
}
return typeof x;
};
/**
* @description Takes an object with one (1) key value pair
* { key: value } and matches with functions mapped in the
* conditions argument.
* @param {object} conditions - functions mapped to enum tags
* @param {object} enum - value to be matched
* @returns {boolean} if all input values are defined
*
* @example
* import { match } from "@nextml/lodestar";
*
* const apiResult = { ok: { name: "foo" } }
*
* match({
* ok: success,
* error: popup,
* })(apiResult)
*
* // => success({ name: "foo" })
*/
const match = curry((conditions, enumm) => {
if (typeOf(conditions) !== "object") {
throw new TypeError(
`match conditions should be of type 'object' got ${typeOf(conditions)}`,
{ cause: conditions }
);
}
const isValidConditionSpec = Object.values(conditions)
.map(typeOf)
.every((x) => x === "function");
if (!isValidConditionSpec) {
throw new TypeError("All mapped conditions should be a function.");
}
const isSingletonEnum = Object.entries(enumm).length === 1;
if (!isSingletonEnum) {
throw new TypeError(
`Match enums should have exactly one { <key>: <value> } pair got ${JSON.stringify(
enumm
)}`
);
}
const [[key, value]] = Object.entries(enumm);
const thereIsNoMatch = !Object.keys(conditions).includes(key);
if (thereIsNoMatch) {
throw new Error(
`Failed to match ${JSON.stringify(
enumm
)} with any key in ${JSON.stringify(
Object.keys(conditions).reduce(
(acc, key) => ({ ...acc, [key]: "..." }),
{}
)
)}`
);
}
return conditions[key](value);
});
/**
* @description Function that ignores the function call
* @param {...*} _ - Any input arguments will be ignored
* @returns {undefined} Returns undefined
*
* @example
* import { noop } from "@nextml/lodestar";
*
* noop("foo") // => undefined
*/
const noop = (_) => undefined;
/**
* @description reduces array of values
* @param {function} fn - reducer function
* @param {any} initialValue - initial value for the accumulator
* @param {array} xs - values to reduce
* @returns {any} reduced value
*
* @example
* import { reduce } from "@nextml/lodestar";
*
* const add = (x, y) => x + y;
* reduce(add, 0, [1, 2, 3]);
* // => 6
*
* TODO: add more examples...
*/
const reduce = curry((fn, initialValue, xs) =>
xs.reduce(fn, initialValue)
);
/**
* @description Convert snake_case strings to camelCase.
* @param {string} x - string to convert
* @returns {string} converted string
*
* @example
* import { snakeCaseToCamelCase } from "@nextml/lodestar";
*
* snakeCaseToCamelCase("hello_world"); // => "helloWorld"
*/
const snakeCaseToCamelCase = (x) => {
const [first, ...rest] = x.split("_");
return [first.toLowerCase(), ...rest.map(capitalize)].join("");
};
/**
* @description Take input, run effect, and return the same input.
* @param {any} x - any value
* @returns {any} x
*
* @example
* import { sideEffect } from "@nextml/lodestar";
*
* const logAndReturnName = sideEffect((name) => {
* console.log('Hello my name is', name))
* };
*/
const sideEffect = (fn) => (x) => {
fn(x);
return x;
};
/**
* @description Log a value and return the same value
* @param {any} x - the value to log and return
* @returns {any} x, the same as input
*
* @example
* import { trace } from "@nextml/lodestar";
*
* const sum = trace("foo"); // > "foo"
* // => "foo"
*
* @example
* import { trace } from "@nextml/lodestar";
*
* fetch("/")
* .then(response => response.json())
* .then(trace) // log request body
* .then(handleData)
*/
const trace = sideEffect(console.log);
/**
* @description upate array or object by key/index
* @param {function} fn - function to apply to the value of the specified key
* @param {(string|number)} key - key/index (integer) in the Object/Array
* @param {(Object|Array)} x - Object or Array to update in
* @returns {(Object|Array)} Updated object with the update function applied to the value of the key
*/
const update = curry((fn, key, x) => {
switch (typeOf(x)) {
case "object": {
return {
...x,
[key]: fn(x[key]),
};
}
case "array": {
if (Number.isInteger(key)) {
return x.map((y, index) => {
if (index === key) {
return fn(y);
} else {
return y;
}
});
} else {
throw TypeError(
`the update key must be an integer, got ${typeOf(key)}`
);
}
}
default: {
throw TypeError(`byKey is not implemented for type ${typeOf(x)}`);
}
}
});
/**
* @description Update a nested structure by providing a "path"
* represented by an array of keys/indices.
* @param {function} fn - the function to apply to the target of the path
* @param {array} keys - list of keys/indices, the "path" in the nested structure
* @param {(Object|Array)} x - Obejct or Array to update in
* @returns {(Object|Array)} updated object with
*/
const updateIn = curry((fn, keys, x) => {
const [key, ...nextKeys] = keys;
if (keys.length === 0) {
return x;
}
if (keys.length === 1) {
return update(fn, key, x);
}
return update(updateIn(fn, nextKeys), key, x);
});
/**
* @description Use event target value point free in event handlers.
* @param {function} fn - arbitrary function that takes event target value
* @returns {function} that takes javascript event as parameter
*
* @example
* import { withEventTargetValue } from "@nextml/lodestar";
*
* const SomeComponent = () => {
* const [inputValue, setInputValue] = useState('');
* return (<input onChange={withEventTargetValue(setInputValue)} />);
* }
*/
const withEventTargetValue = (fn) => (event) => fn(event.target.value);
/**
* @description get first element in array
* @param {array} xs - array to pick element from
* @returns {any} first element in xs
*
* @example
* import { Pick } from "@nextml/lodestar";
*
* Pick.first([0, 1, 2])
* // => 0
*/
const first = (xs) => xs[0];
/**
* @description get last element in array
* @param {array} xs - array to pick element from
* @returns {any} last element in xs
*
* @example
* import { Pick } from "@nextml/lodestar";
*
* Pick.last([0, 1, 2])
* // => 2
*/
const last = (xs) => xs[xs.length - 1];
/**
* @description get value of key in object
* @param {string} key - object key to pick value from
* @param {object} x - object to pick value from
* @returns {any} value mapped to the given key
*
* @example
* import { Pick } from "@nextml/lodestar";
*
* Pick.key("foo", { foo: 123 });
* // => 123
*/
const key = curry((key, x) => x[key]);
/**
* @description Pick module. Fetch values from objects and arrays.
* See "first", "last", "key".
*
* @example
* import { Pick } from "@nextml/lodestar";
*
*/
const Pick = {
first,
last,
key,
};
exports.Pick = Pick;
exports.camelCaseToSnakeCase = camelCaseToSnakeCase;
exports.capitalize = capitalize;
exports.compose = compose;
exports.composeAsync = composeAsync;
exports.curry = curry;
exports.defer = defer;
exports.filter = filter;
exports.identity = identity;
exports.isDefined = isDefined;
exports.isError = isError;
exports.isUnitType = isUnitType;
exports.map = map;
exports.match = match;
exports.noop = noop;
exports.reduce = reduce;
exports.sideEffect = sideEffect;
exports.snakeCaseToCamelCase = snakeCaseToCamelCase;
exports.trace = trace;
exports.typeOf = typeOf;
exports.updateIn = updateIn;
exports.withEventTargetValue = withEventTargetValue;
Source