UNPKG

shelving

Version:

Toolkit for using data in JavaScript.

179 lines (178 loc) 6.64 kB
import { RequiredError } from "../error/RequiredError.js"; import { omitItems, pickItems } from "./iterate.js"; export function isArray(value, min = 0, max = Number.POSITIVE_INFINITY) { return Array.isArray(value) && value.length >= min && value.length <= max; } export function assertArray(value, min, max, caller = assertArray) { if (!isArray(value, min, max)) throw new RequiredError(`Must be array${min !== undefined || max !== undefined ? ` with ${min ?? 0} to ${max ?? "∞"} items` : ""}`, { received: value, caller, }); } /** Convert a possible array to an array. */ export function getArray(list) { return Array.isArray(list) ? list : Array.from(list); } export function requireArray(list, min, max, caller = requireArray) { const arr = getArray(list); assertArray(arr, min, max, caller); return arr; } /** Is an unknown value an item in a specified array or iterable? */ export function isItem(list, item) { if (isArray(list)) list.includes(item); for (const i of list) if (i === item) return true; return false; } /** Assert that an unknown value is an item in a specified array. */ export function assertItem(arr, item, caller = assertItem) { if (!isItem(arr, item)) throw new RequiredError("Item must exist in array", { item, array: arr, caller }); } /** Add multiple items to an array (immutably) and return a new array with those items (or the same array if no changes were made). */ export function withArrayItems(list, ...add) { const arr = Array.from(list); const extras = add.filter(_doesNotInclude, arr); return extras.length ? [...arr, ...extras] : isArray(list) ? list : arr; } function _doesNotInclude(value) { return !this.includes(value); } /** Pick multiple items from an array (immutably) and return a new array without those items (or the same array if no changes were made). */ export function pickArrayItems(items, ...pick) { const arr = Array.from(pickItems(items, ...pick)); return isArray(items) && arr.length === items.length ? items : arr; } /** Remove multiple items from an array (immutably) and return a new array without those items (or the same array if no changes were made). */ export function omitArrayItems(items, ...omit) { const filtered = Array.from(omitItems(items, ...omit)); return isArray(items) && filtered.length === items.length ? items : filtered; } /** Toggle an item in and out of an array (immutably) and return a new array with or without the specified items (or the same array if no changes were made). */ export function toggleArrayItems(items, ...toggle) { const arr = Array.from(items); const extras = toggle.filter(_doesNotInclude, arr); const filtered = arr.filter(_doesNotInclude, toggle); return extras.length ? [...filtered, ...extras] : filtered.length !== arr.length ? filtered : isArray(items) ? items : arr; } /** Return a shuffled version of an array or iterable. */ export function shuffleArray(items) { const arr = Array.from(items); for (let i = arr.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [arr[i], arr[j]] = [arr[j], arr[i]]; } return arr; } /** * Add an item to an array (by reference) and return the item. * - Skip items that already exist. */ export function addArrayItem(arr, item) { if (arr.indexOf(item) < 0) arr.push(item); return item; } /** * Add multiple items to an array (by reference). * - Skip items that already exist. */ export function addArrayItems(arr, ...items) { for (const item of items) if (arr.indexOf(item) < 0) arr.push(item); } /** * Remove multiple items from an array (by reference). * - Skip items that already exist. */ export function deleteArrayItems(arr, ...items) { for (let i = arr.length - 1; i >= 0; i--) if (i in arr && items.includes(arr[i])) arr.splice(i, 1); } /** Return an array of the unique items in an array. */ export function getUniqueArray(list) { const output = []; for (const item of list) if (!output.includes(item)) output.push(item); return isArray(list) && list.length === output.length ? list : output; } /** Apply a limit to an array. */ export function limitArray(list, limit) { const arr = requireArray(list, undefined, undefined, limitArray); return limit > arr.length ? arr : arr.slice(0, limit); } /** Count the items in an array. */ export function countArray(arr) { return arr.length; } /** Get the first item from an array or iterable, or `undefined` if it didn't exist. */ export function getFirst(items) { if (isArray(items)) return items[0]; for (const i of items) return i; } /** Get the first item from an array or iterable. */ export function requireFirst(items, caller = requireFirst) { const item = getFirst(items); if (item === undefined) throw new RequiredError("First item is required", { items: items, caller }); return item; } /** Get the last item from an array or iterable, or `undefined` if it didn't exist. */ export function getLast(items) { if (isArray(items)) return items[items.length - 1]; let last; for (const i of items) { last = i; } return last; } /** Get the last item from an array or iterable. */ export function requireLast(items, caller = requireLast) { const item = getLast(items); if (item === undefined) throw new RequiredError("Last item is required", { items, caller }); return item; } /** Get the next item in an array or iterable. */ export function getNext(items, item) { let found = false; for (const i of items) { if (found) return i; if (i === item) found = true; } } /** Get the next item from an array or iterable. */ export function requireNext(items, item, caller = requireNext) { const next = getNext(items, item); if (next === undefined) throw new RequiredError("Next item is required", { item, items, caller }); return next; } /** Get the previous item in an array or iterable. */ export function getPrev(items, value) { let last; for (const i of items) { if (i === value) return last; last = i; } } /** Get the previous item from an array or iterable. */ export function requirePrev(items, item, caller = requirePrev) { const prev = getPrev(items, item); if (prev === undefined) throw new RequiredError("Previous item is required", { item, items, caller }); return prev; }