@voiceflow/common
Version:
Junk drawer of utility functions
137 lines (136 loc) • 5.57 kB
JavaScript
export const unique = (items) => Array.from(new Set(items));
export const without = (items, index) => index < 0 ? items : [...items.slice(0, index), ...items.slice(index + 1)];
export const withoutValue = (items, value) => without(items, items.indexOf(value));
export const withoutValues = (items, values) => items.filter((item) => !values.includes(item));
export const replace = (items, index, item) => index < 0 ? items : [...items.slice(0, index), item, ...items.slice(index + 1)];
export const insert = (items, index, item) => index < 0 ? [item, ...items] : [...items.slice(0, index), item, ...items.slice(index)];
export const insertAll = (items, index, additionalItems) => index < 0 ? [...additionalItems, ...items] : [...items.slice(0, index), ...additionalItems, ...items.slice(index)];
export const append = (items, item) => (items.includes(item) ? items : [...items, item]);
export const toggleMembership = (items, item) => items.includes(item) ? withoutValue(items, item) : [...items, item];
export const head = (items) => {
const [first, ...rest] = items;
return [first, rest];
};
export const tail = (items) => {
const last = items[items.length - 1];
const rest = items.slice(0, -1);
return [rest, last];
};
export const reorder = (items, fromIndex, toIndex) => {
if (fromIndex < 0 || fromIndex >= items.length) {
return items;
}
if (toIndex <= 0) {
return [items[fromIndex], ...without(items, fromIndex)];
}
if (toIndex >= items.length) {
return [...without(items, fromIndex), items[fromIndex]];
}
return insert(without(items, fromIndex), toIndex, items[fromIndex]);
};
export const separate = (items, predicate) => items.reduce(([passAcc, failAcc], item, index) => {
if (predicate(item, index)) {
passAcc.push(item);
}
else {
failAcc.push(item);
}
return [passAcc, failAcc];
}, [[], []]);
export const createEntries = (array, getKey = (value) => value) => array.map((item) => [getKey(item), item]);
export const createMap = (array, getKey = (value) => value) => Object.fromEntries(createEntries(array, getKey));
export const findUnion = (lhs, rhs) => {
// using sets instead of arrays since .has is O(1)
const lSet = new Set(lhs);
const rSet = new Set(rhs);
const unionSet = new Set([...lhs, ...rhs]);
const result = { rhsOnly: [], lhsOnly: [], union: [] };
for (const item of unionSet) {
if (lSet.has(item)) {
if (rSet.has(item)) {
result.union.push(item);
}
else {
result.lhsOnly.push(item);
}
}
else {
result.rhsOnly.push(item);
}
}
return result;
};
export const diff = (lhs, rhs) => {
const { lhsOnly, rhsOnly } = findUnion(lhs, rhs);
return [...lhsOnly, ...rhsOnly];
};
export const hasIdenticalMembers = (lhs, rhs) => {
if (lhs.length !== rhs.length) {
return false;
}
if (!lhs.length && !rhs.length) {
return true;
}
return !lhs.some((value) => !rhs.includes(value));
};
export const asyncForEach = async (array, callback) => {
for (let index = 0; index < array.length; index++) {
// eslint-disable-next-line callback-return,no-await-in-loop
await callback(array[index], index, array);
}
};
export const isNullish = (value) => value === null || value === undefined;
export const isNotNullish = (value) => value !== null && value !== undefined;
/** @deprecated Use `array.filter(isNotNullish)` instead. */
export const filterOutNullish = (items) => items.filter(isNotNullish);
// mostly just saves us needing to traverse an array twice
export const filterAndGetLastRemovedValue = (list, filter) => {
let lastItem = null;
const filteredList = list.filter((a) => {
if (filter(a))
return true;
lastItem = a;
return false;
});
return [filteredList, lastItem];
};
export const inferUnion = (array) => array;
export const toArray = (valueOrArray) => (Array.isArray(valueOrArray) ? valueOrArray : [valueOrArray]);
/**
* Merge together two arrays, if two items have the same identity based on the {@link identify} function
* they will be merged together using the {@link merge} function provided.
* @param items Array of items as a starting base.
* @param newItems Array of items to merge in.
* @param identify Function returning how to identify an item in the array
* @param merge Function given two matching item identifiers, returning a single merged result
* @example
* const existingItems = [{a: 1, b: [1, 2, 3]}, {a: 2, b: [4]}];
* const newItems = [{a: 1, b: [5]}, {a: 3, b: [6, 7]}];
*
* const items = mergeByIdentifier(
* existingItems,
* newItems,
* (item) => item.a,
* (existingItem, newItem) => {
* return {
* ...existingItem,
* b: [...existingItem.b, ...newItem.b]
* }
* }
* );
*
* items == [{a: 1, b: [1, 2, 3, 5]}, {a: 2, b: [4]}, {a: 3, b: [6, 7]}];
*/
export const mergeByIdentifier = (items, newItems, identify, merge) => {
const newItemsMap = new Map(newItems.map((newItem, i) => [identify(newItem, i), newItem]));
const result = items.map((item, i) => {
const itemIdentity = identify(item, i);
const matchingNewItem = newItemsMap.get(itemIdentity);
if (matchingNewItem) {
newItemsMap.delete(itemIdentity);
return merge(item, matchingNewItem);
}
return item;
});
return result.concat(Array.from(newItemsMap.values()));
};