@splitsoftware/splitio-commons
Version:
Split JavaScript SDK common components
259 lines (258 loc) • 8.64 kB
JavaScript
import { __spreadArray } from "tslib";
/**
* Checks if the target string ends with the sub string.
*/
export function endsWith(target, sub, caseInsensitive) {
if (caseInsensitive === void 0) { caseInsensitive = false; }
if (!(isString(target) && isString(sub))) {
return false;
}
if (caseInsensitive) {
target = target.toLowerCase();
sub = sub.toLowerCase();
}
return target.slice(target.length - sub.length) === sub;
}
/**
* Loops through a source collection (an object or an array) running iteratee
* against each element. It returns the first element for which iteratee returned
* a truthy value and stops the loop.
* Iteratee receives three arguments (element, key/index, collection)
*/
export function find(source, iteratee) {
var res;
if (isObject(source)) {
var keys = Object.keys(source);
for (var i = 0; i < keys.length && !res; i++) {
var key = keys[i];
var iterateeResult = iteratee(source[key], key, source);
if (iterateeResult)
res = source[key];
}
}
else if (Array.isArray(source)) {
for (var i = 0; i < source.length && !res; i++) {
var iterateeResult = iteratee(source[i], i, source);
if (iterateeResult)
res = source[i];
}
}
return res;
}
/**
* Evaluates iteratee for each element of the source array. Returns the index of the first element
* for which iteratee returns truthy. If no element is found or there's an issue with the params it returns -1.
*/
export function findIndex(source, iteratee) {
if (Array.isArray(source) && typeof iteratee === 'function') {
for (var i = 0; i < source.length; i++) {
if (iteratee(source[i], i, source) === true) {
return i;
}
}
}
return -1;
}
/**
* Executes iteratee for given obj own props.
*/
export function forOwn(obj, iteratee) {
var keys = Object.keys(obj);
keys.forEach(function (key) { return iteratee(obj[key], key, obj); });
return obj;
}
/**
* Safely retrieve the specified prop from obj. If we can't retrieve
* that property value, we return the default value.
*/
export function get(obj, prop, val) {
var res = val;
try { // No risks nor lots of checks.
var pathPieces = prop.split('.');
var partial_1 = obj;
pathPieces.forEach(function (pathPiece) { return partial_1 = partial_1[pathPiece]; });
if (typeof partial_1 !== 'undefined')
res = partial_1;
}
catch (e) {
// noop
}
return res;
}
/**
* Parses an array into a map of different arrays, grouping by the specified prop value.
*/
export function groupBy(source, prop) {
var map = {};
if (Array.isArray(source) && isString(prop)) {
for (var i = 0; i < source.length; i++) {
var key = source[i][prop];
// Skip the element if the key is not a string.
if (isString(key)) {
if (!map[key])
map[key] = [];
map[key].push(source[i]);
}
}
}
return map;
}
/**
* Checks if a given value is a boolean.
*/
export function isBoolean(val) {
return val === true || val === false;
}
/**
* Checks if a given value is a finite value of number type or Number object.
* Unlike `Number.isFinite`, it also tests Number object instances.
* Unlike global `isFinite`, it returns false if the value is not a number or Number object instance.
*/
export function isFiniteNumber(val) {
if (val instanceof Number)
val = val.valueOf();
return typeof val === 'number' ?
Number.isFinite ? Number.isFinite(val) : isFinite(val) :
false;
}
/**
* Checks if a given value is an integer value of number type or Number object.
* Unlike `Number.isInteger`, it also tests Number object instances.
*/
export function isIntegerNumber(val) {
if (val instanceof Number)
val = val.valueOf();
return typeof val === 'number' ?
Number.isInteger ? Number.isInteger(val) : isFinite(val) && Math.floor(val) === val :
false;
}
/**
* Checks if a given value is a NaN value of number type or Number object.
* Unlike `Number.isNaN`, it also tests Number object instances.
* Unlike global `isNan`, it returns false if the value is not a number or Number object instance.
*/
export function isNaNNumber(val) {
if (val instanceof Number)
val = val.valueOf();
// @TODO replace with `Number.isNaN` once it is fully supported by targets
return val !== val;
}
/**
* Validates if a value is an object created by the Object constructor (plain object).
* It checks `constructor.name` to avoid false negatives when validating values on a separate VM context, which has its own global built-ins.
*/
export function isObject(obj) {
return obj !== null && typeof obj === 'object' && (obj.constructor === Object ||
(obj.constructor != null && obj.constructor.name === 'Object'));
}
/**
* Checks if a given value is a string.
*/
export function isString(val) {
return typeof val === 'string' || val instanceof String;
}
/**
* String sanitizer. Returns the provided value converted to uppercase if it is a string.
*/
export function stringToUpperCase(val) {
return isString(val) ? val.toUpperCase() : val;
}
/**
* Deep copy version of Object.assign using recursion.
* There are some assumptions here. It's for internal use and we don't need verbose errors
* or to ensure the data types or whatever. Parameters should always be correct (at least have a target and a source, of type object).
*/
export function merge(target, source) {
var rest = [];
for (var _i = 2; _i < arguments.length; _i++) {
rest[_i - 2] = arguments[_i];
}
var res = target;
isObject(source) && Object.keys(source).forEach(function (key) {
var val = source[key];
if (isObject(val)) {
if (res[key] && isObject(res[key])) { // If both are objects, merge into a new one.
val = merge({}, res[key], val);
}
else { // else make a copy.
val = merge({}, val);
}
}
// We skip undefined values.
if (val !== undefined)
res[key] = val;
});
if (rest && rest.length) {
var nextSource = rest.splice(0, 1)[0];
res = merge.apply(void 0, __spreadArray([res, nextSource], rest, false));
}
return res;
}
/**
* Checks if the target string starts with the sub string.
*/
export function startsWith(target, sub) {
if (!(isString(target) && isString(sub))) {
return false;
}
return target.slice(0, sub.length) === sub;
}
/**
* Transforms a value into a number.
* Note: We're not expecting anything fancy here. If we are at some point, add more type checks.
*/
export function toNumber(val) {
if (typeof val === 'number')
return val;
if (isObject(val) && typeof val.valueOf === 'function') {
var valOf = val.valueOf();
val = isObject(valOf) ? valOf + '' : valOf;
}
if (typeof val !== 'string') {
return val === 0 ? val : +val;
}
// Remove trailing whitespaces.
val = val.replace(/^\s+|\s+$/g, '');
return +val;
}
/**
* Transforms a value into it's string representation.
*/
export function toString(val) {
if (val == null)
return '';
if (typeof val === 'string')
return val;
if (Array.isArray(val))
return val.map(function (val) { return isString(val) ? val : ''; }) + '';
var result = val + '';
return (result === '0' && (1 / val) === Number.NEGATIVE_INFINITY) ? '-0' : result;
}
/**
* Removes duplicate items on an array of strings.
*/
export function uniq(arr) {
var seen = {};
return arr.filter(function (item) {
return Object.prototype.hasOwnProperty.call(seen, item) ? false : seen[item] = true;
});
}
/**
* Removes duplicate items on an array of objects using an optional `stringify` function as equality criteria.
* It uses JSON.stringify as default criteria.
*/
export function uniqAsStrings(arr, stringify) {
if (stringify === void 0) { stringify = JSON.stringify; }
var seen = {};
return arr.filter(function (item) {
var itemString = stringify(item);
return Object.prototype.hasOwnProperty.call(seen, itemString) ? false : seen[itemString] = true;
});
}
var uniqueIdCounter = -1;
/**
* Returns a number to be used as ID, which will be unique.
*/
export function uniqueId() {
return uniqueIdCounter++;
}