UNPKG

@voiceflow/common

Version:

Junk drawer of utility functions

137 lines (136 loc) 5.57 kB
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())); };