package.dist.index.cjs Maven / Gradle / Ivy
The newest version!
'use strict';
/**
* Flatten array, one level deep.
*
* @template T
*
* @param {T[][] | T[] | null} [arr]
*
* @return {T[]}
*/
function flatten(arr) {
return Array.prototype.concat.apply([], arr);
}
const nativeToString = Object.prototype.toString;
const nativeHasOwnProperty = Object.prototype.hasOwnProperty;
function isUndefined(obj) {
return obj === undefined;
}
function isDefined(obj) {
return obj !== undefined;
}
function isNil(obj) {
return obj == null;
}
function isArray(obj) {
return nativeToString.call(obj) === '[object Array]';
}
function isObject(obj) {
return nativeToString.call(obj) === '[object Object]';
}
function isNumber(obj) {
return nativeToString.call(obj) === '[object Number]';
}
/**
* @param {any} obj
*
* @return {boolean}
*/
function isFunction(obj) {
const tag = nativeToString.call(obj);
return (
tag === '[object Function]' ||
tag === '[object AsyncFunction]' ||
tag === '[object GeneratorFunction]' ||
tag === '[object AsyncGeneratorFunction]' ||
tag === '[object Proxy]'
);
}
function isString(obj) {
return nativeToString.call(obj) === '[object String]';
}
/**
* Ensure collection is an array.
*
* @param {Object} obj
*/
function ensureArray(obj) {
if (isArray(obj)) {
return;
}
throw new Error('must supply array');
}
/**
* Return true, if target owns a property with the given key.
*
* @param {Object} target
* @param {String} key
*
* @return {Boolean}
*/
function has(target, key) {
return nativeHasOwnProperty.call(target, key);
}
/**
* @template T
* @typedef { (
* ((e: T) => boolean) |
* ((e: T, idx: number) => boolean) |
* ((e: T, key: string) => boolean) |
* string |
* number
* ) } Matcher
*/
/**
* @template T
* @template U
*
* @typedef { (
* ((e: T) => U) | string | number
* ) } Extractor
*/
/**
* @template T
* @typedef { (val: T, key: any) => boolean } MatchFn
*/
/**
* @template T
* @typedef { T[] } ArrayCollection
*/
/**
* @template T
* @typedef { { [key: string]: T } } StringKeyValueCollection
*/
/**
* @template T
* @typedef { { [key: number]: T } } NumberKeyValueCollection
*/
/**
* @template T
* @typedef { StringKeyValueCollection | NumberKeyValueCollection } KeyValueCollection
*/
/**
* @template T
* @typedef { KeyValueCollection | ArrayCollection } Collection
*/
/**
* Find element in collection.
*
* @template T
* @param {Collection} collection
* @param {Matcher} matcher
*
* @return {Object}
*/
function find(collection, matcher) {
const matchFn = toMatcher(matcher);
let match;
forEach(collection, function(val, key) {
if (matchFn(val, key)) {
match = val;
return false;
}
});
return match;
}
/**
* Find element index in collection.
*
* @template T
* @param {Collection} collection
* @param {Matcher} matcher
*
* @return {number}
*/
function findIndex(collection, matcher) {
const matchFn = toMatcher(matcher);
let idx = isArray(collection) ? -1 : undefined;
forEach(collection, function(val, key) {
if (matchFn(val, key)) {
idx = key;
return false;
}
});
return idx;
}
/**
* Filter elements in collection.
*
* @template T
* @param {Collection} collection
* @param {Matcher} matcher
*
* @return {T[]} result
*/
function filter(collection, matcher) {
const matchFn = toMatcher(matcher);
let result = [];
forEach(collection, function(val, key) {
if (matchFn(val, key)) {
result.push(val);
}
});
return result;
}
/**
* Iterate over collection; returning something
* (non-undefined) will stop iteration.
*
* @template T
* @param {Collection} collection
* @param { ((item: T, idx: number) => (boolean|void)) | ((item: T, key: string) => (boolean|void)) } iterator
*
* @return {T} return result that stopped the iteration
*/
function forEach(collection, iterator) {
let val,
result;
if (isUndefined(collection)) {
return;
}
const convertKey = isArray(collection) ? toNum : identity;
for (let key in collection) {
if (has(collection, key)) {
val = collection[key];
result = iterator(val, convertKey(key));
if (result === false) {
return val;
}
}
}
}
/**
* Return collection without element.
*
* @template T
* @param {ArrayCollection} arr
* @param {Matcher} matcher
*
* @return {T[]}
*/
function without(arr, matcher) {
if (isUndefined(arr)) {
return [];
}
ensureArray(arr);
const matchFn = toMatcher(matcher);
return arr.filter(function(el, idx) {
return !matchFn(el, idx);
});
}
/**
* Reduce collection, returning a single result.
*
* @template T
* @template V
*
* @param {Collection} collection
* @param {(result: V, entry: T, index: any) => V} iterator
* @param {V} result
*
* @return {V} result returned from last iterator
*/
function reduce(collection, iterator, result) {
forEach(collection, function(value, idx) {
result = iterator(result, value, idx);
});
return result;
}
/**
* Return true if every element in the collection
* matches the criteria.
*
* @param {Object|Array} collection
* @param {Function} matcher
*
* @return {Boolean}
*/
function every(collection, matcher) {
return !!reduce(collection, function(matches, val, key) {
return matches && matcher(val, key);
}, true);
}
/**
* Return true if some elements in the collection
* match the criteria.
*
* @param {Object|Array} collection
* @param {Function} matcher
*
* @return {Boolean}
*/
function some(collection, matcher) {
return !!find(collection, matcher);
}
/**
* Transform a collection into another collection
* by piping each member through the given fn.
*
* @param {Object|Array} collection
* @param {Function} fn
*
* @return {Array} transformed collection
*/
function map(collection, fn) {
let result = [];
forEach(collection, function(val, key) {
result.push(fn(val, key));
});
return result;
}
/**
* Get the collections keys.
*
* @param {Object|Array} collection
*
* @return {Array}
*/
function keys(collection) {
return collection && Object.keys(collection) || [];
}
/**
* Shorthand for `keys(o).length`.
*
* @param {Object|Array} collection
*
* @return {Number}
*/
function size(collection) {
return keys(collection).length;
}
/**
* Get the values in the collection.
*
* @param {Object|Array} collection
*
* @return {Array}
*/
function values(collection) {
return map(collection, (val) => val);
}
/**
* Group collection members by attribute.
*
* @param {Object|Array} collection
* @param {Extractor} extractor
*
* @return {Object} map with { attrValue => [ a, b, c ] }
*/
function groupBy(collection, extractor, grouped = {}) {
extractor = toExtractor(extractor);
forEach(collection, function(val) {
let discriminator = extractor(val) || '_';
let group = grouped[discriminator];
if (!group) {
group = grouped[discriminator] = [];
}
group.push(val);
});
return grouped;
}
function uniqueBy(extractor, ...collections) {
extractor = toExtractor(extractor);
let grouped = {};
forEach(collections, (c) => groupBy(c, extractor, grouped));
let result = map(grouped, function(val, key) {
return val[0];
});
return result;
}
const unionBy = uniqueBy;
/**
* Sort collection by criteria.
*
* @template T
*
* @param {Collection} collection
* @param {Extractor} extractor
*
* @return {Array}
*/
function sortBy(collection, extractor) {
extractor = toExtractor(extractor);
let sorted = [];
forEach(collection, function(value, key) {
let disc = extractor(value, key);
let entry = {
d: disc,
v: value
};
for (var idx = 0; idx < sorted.length; idx++) {
let { d } = sorted[idx];
if (disc < d) {
sorted.splice(idx, 0, entry);
return;
}
}
// not inserted, append (!)
sorted.push(entry);
});
return map(sorted, (e) => e.v);
}
/**
* Create an object pattern matcher.
*
* @example
*
* ```javascript
* const matcher = matchPattern({ id: 1 });
*
* let element = find(elements, matcher);
* ```
*
* @template T
*
* @param {T} pattern
*
* @return { (el: any) => boolean } matcherFn
*/
function matchPattern(pattern) {
return function(el) {
return every(pattern, function(val, key) {
return el[key] === val;
});
};
}
/**
* @param {string | ((e: any) => any) } extractor
*
* @return { (e: any) => any }
*/
function toExtractor(extractor) {
/**
* @satisfies { (e: any) => any }
*/
return isFunction(extractor) ? extractor : (e) => {
// @ts-ignore: just works
return e[extractor];
};
}
/**
* @template T
* @param {Matcher} matcher
*
* @return {MatchFn}
*/
function toMatcher(matcher) {
return isFunction(matcher) ? matcher : (e) => {
return e === matcher;
};
}
function identity(arg) {
return arg;
}
function toNum(arg) {
return Number(arg);
}
/* global setTimeout clearTimeout */
/**
* @typedef { {
* (...args: any[]): any;
* flush: () => void;
* cancel: () => void;
* } } DebouncedFunction
*/
/**
* Debounce fn, calling it only once if the given time
* elapsed between calls.
*
* Lodash-style the function exposes methods to `#clear`
* and `#flush` to control internal behavior.
*
* @param {Function} fn
* @param {Number} timeout
*
* @return {DebouncedFunction} debounced function
*/
function debounce(fn, timeout) {
let timer;
let lastArgs;
let lastThis;
let lastNow;
function fire(force) {
let now = Date.now();
let scheduledDiff = force ? 0 : (lastNow + timeout) - now;
if (scheduledDiff > 0) {
return schedule(scheduledDiff);
}
fn.apply(lastThis, lastArgs);
clear();
}
function schedule(timeout) {
timer = setTimeout(fire, timeout);
}
function clear() {
if (timer) {
clearTimeout(timer);
}
timer = lastNow = lastArgs = lastThis = undefined;
}
function flush() {
if (timer) {
fire(true);
}
clear();
}
/**
* @type { DebouncedFunction }
*/
function callback(...args) {
lastNow = Date.now();
lastArgs = args;
lastThis = this;
// ensure an execution is scheduled
if (!timer) {
schedule(timeout);
}
}
callback.flush = flush;
callback.cancel = clear;
return callback;
}
/**
* Throttle fn, calling at most once
* in the given interval.
*
* @param {Function} fn
* @param {Number} interval
*
* @return {Function} throttled function
*/
function throttle(fn, interval) {
let throttling = false;
return function(...args) {
if (throttling) {
return;
}
fn(...args);
throttling = true;
setTimeout(() => {
throttling = false;
}, interval);
};
}
/**
* Bind function against target .
*
* @param {Function} fn
* @param {Object} target
*
* @return {Function} bound function
*/
function bind(fn, target) {
return fn.bind(target);
}
/**
* Convenience wrapper for `Object.assign`.
*
* @param {Object} target
* @param {...Object} others
*
* @return {Object} the target
*/
function assign(target, ...others) {
return Object.assign(target, ...others);
}
/**
* Sets a nested property of a given object to the specified value.
*
* This mutates the object and returns it.
*
* @template T
*
* @param {T} target The target of the set operation.
* @param {(string|number)[]} path The path to the nested value.
* @param {any} value The value to set.
*
* @return {T}
*/
function set(target, path, value) {
let currentTarget = target;
forEach(path, function(key, idx) {
if (typeof key !== 'number' && typeof key !== 'string') {
throw new Error('illegal key type: ' + typeof key + '. Key should be of type number or string.');
}
if (key === 'constructor') {
throw new Error('illegal key: constructor');
}
if (key === '__proto__') {
throw new Error('illegal key: __proto__');
}
let nextKey = path[idx + 1];
let nextTarget = currentTarget[key];
if (isDefined(nextKey) && isNil(nextTarget)) {
nextTarget = currentTarget[key] = isNaN(+nextKey) ? {} : [];
}
if (isUndefined(nextKey)) {
if (isUndefined(value)) {
delete currentTarget[key];
} else {
currentTarget[key] = value;
}
} else {
currentTarget = nextTarget;
}
});
return target;
}
/**
* Gets a nested property of a given object.
*
* @param {Object} target The target of the get operation.
* @param {(string|number)[]} path The path to the nested value.
* @param {any} [defaultValue] The value to return if no value exists.
*
* @return {any}
*/
function get(target, path, defaultValue) {
let currentTarget = target;
forEach(path, function(key) {
// accessing nil property yields
if (isNil(currentTarget)) {
currentTarget = undefined;
return false;
}
currentTarget = currentTarget[key];
});
return isUndefined(currentTarget) ? defaultValue : currentTarget;
}
/**
* Pick properties from the given target.
*
* @template T
* @template {any[]} V
*
* @param {T} target
* @param {V} properties
*
* @return Pick
*/
function pick(target, properties) {
let result = {};
let obj = Object(target);
forEach(properties, function(prop) {
if (prop in obj) {
result[prop] = target[prop];
}
});
return result;
}
/**
* Pick all target properties, excluding the given ones.
*
* @template T
* @template {any[]} V
*
* @param {T} target
* @param {V} properties
*
* @return {Omit} target
*/
function omit(target, properties) {
let result = {};
let obj = Object(target);
forEach(obj, function(prop, key) {
if (properties.indexOf(key) === -1) {
result[key] = prop;
}
});
return result;
}
/**
* Recursively merge `...sources` into given target.
*
* Does support merging objects; does not support merging arrays.
*
* @param {Object} target
* @param {...Object} sources
*
* @return {Object} the target
*/
function merge(target, ...sources) {
if (!sources.length) {
return target;
}
forEach(sources, function(source) {
// skip non-obj sources, i.e. null
if (!source || !isObject(source)) {
return;
}
forEach(source, function(sourceVal, key) {
if (key === '__proto__') {
return;
}
let targetVal = target[key];
if (isObject(sourceVal)) {
if (!isObject(targetVal)) {
// override target[key] with object
targetVal = {};
}
target[key] = merge(targetVal, sourceVal);
} else {
target[key] = sourceVal;
}
});
});
return target;
}
exports.assign = assign;
exports.bind = bind;
exports.debounce = debounce;
exports.ensureArray = ensureArray;
exports.every = every;
exports.filter = filter;
exports.find = find;
exports.findIndex = findIndex;
exports.flatten = flatten;
exports.forEach = forEach;
exports.get = get;
exports.groupBy = groupBy;
exports.has = has;
exports.isArray = isArray;
exports.isDefined = isDefined;
exports.isFunction = isFunction;
exports.isNil = isNil;
exports.isNumber = isNumber;
exports.isObject = isObject;
exports.isString = isString;
exports.isUndefined = isUndefined;
exports.keys = keys;
exports.map = map;
exports.matchPattern = matchPattern;
exports.merge = merge;
exports.omit = omit;
exports.pick = pick;
exports.reduce = reduce;
exports.set = set;
exports.size = size;
exports.some = some;
exports.sortBy = sortBy;
exports.throttle = throttle;
exports.unionBy = unionBy;
exports.uniqueBy = uniqueBy;
exports.values = values;
exports.without = without;