UNPKG

@etsoo/shared

Version:

TypeScript shared utilities and functions

288 lines (252 loc) 7.23 kB
import isEqual from "lodash.isequal"; import { DataTypes, IdType } from "./DataTypes"; declare global { interface Array<T> { /** * Items do not exist in target array or reverse match * @param target Target array * @param round A round for both matches */ different(target: Array<T>, round?: boolean): Array<T>; /** * Get max number item or number item property * @param field Object field to calculate */ max( ...field: T extends number ? [undefined?] : T extends object ? [DataTypes.Keys<T, number>] : [never] ): number; /** * Get max field value item * @param field Object field to calculate */ maxItem( field: T extends object ? DataTypes.Keys<T, number> : never ): T | undefined; /** * Get min number item or number item property * @param field Object field to calculate */ min( ...field: T extends number ? [undefined?] : T extends object ? [DataTypes.Keys<T, number>] : [never] ): number; /** * Get min field value item * @param field Object field to calculate */ minItem( field: T extends object ? DataTypes.Keys<T, number> : never ): T | undefined; /** * Remove items by value or condition * @param items Items to remove */ remove( ...items: ((T & (DataTypes.Basic | object)) | ((item: T) => boolean))[] ): T[]; /** * Sort by property * @param property Property * @param values Property values */ sortByProperty<P extends keyof T>(property: P, values: T[P][]): T[]; /** * Sum number items or number item properties * @param field Object field to calculate */ sum( ...field: T extends number ? [undefined?] : T extends object ? [DataTypes.Keys<T, number>] : [never] ): number; /** * Toggle item in array * @param item Item to toggle * @param add If true, add the item, otherwise remove it * @param idField If item is an object, use this field to check for existence */ toggleItem< const A extends true | false, const F = T extends object ? keyof T : never >( item: T | (F extends keyof T ? (A extends true ? T : T | T[F]) : T), add: A, idField?: F ): Array<T>; /** * Make all items are unique * @param this Input array */ toUnique(): Array<T>; } } Array.prototype.different = function <T>( this: Array<T>, target: Array<T>, round?: boolean ) { return ArrayUtils.differences(this, target, round); }; Array.prototype.toggleItem = function < T, const A extends true | false, const F = T extends object ? keyof T : never >( this: Array<T>, item: T | (F extends keyof T ? (A extends true ? T : T | T[F]) : T), add: A, idField?: F ) { const isObject = typeof item === "object" && item !== null; const index = this.findIndex((i) => { if (idField) { if (isObject) { return i[idField as keyof T] === (item as T)[idField as keyof T]; } else { return i[idField as keyof T] === item; } } return isEqual(i, item); }); if (add) { if (index < 0) { // Ignore type checking as the type assertion is ready // when add is true, item should be T, not a field value this.push(item as T); } } else { if (index >= 0) this.splice(index, 1); } return this; }; Array.prototype.toUnique = function <T>(this: Array<T>) { if (this.length === 0 || typeof this[0] !== "object") return Array.from(new Set(this)); const newArray: T[] = []; this.forEach((item) => { if (newArray.some((newItem) => isEqual(item, newItem))) return; newArray.push(item); }); return newArray; }; Array.prototype.max = function <T>( this: Array<T>, field: T extends object ? DataTypes.Keys<T, number> : undefined ) { if (field == null) { return Math.max(...(this as Array<number>)); } return Math.max(...this.map((item) => item[field] as number)); }; Array.prototype.maxItem = function <T>( this: Array<T>, field: T extends object ? DataTypes.Keys<T, number> : never ) { if (this.length === 0) return undefined; return this.reduce((prev, curr) => (prev[field] > curr[field] ? prev : curr)); }; Array.prototype.min = function <T>( this: Array<T>, field: T extends object ? DataTypes.Keys<T, number> : undefined ) { if (field == null) { return Math.min(...(this as Array<number>)); } return Math.min(...this.map((item) => item[field] as number)); }; Array.prototype.minItem = function <T>( this: Array<T>, field: T extends object ? DataTypes.Keys<T, number> : never ) { if (this.length === 0) return undefined; return this.reduce((prev, curr) => (prev[field] < curr[field] ? prev : curr)); }; Array.prototype.remove = function <T>( this: Array<T>, ...items: ((T & (DataTypes.Basic | object)) | ((item: T) => boolean))[] ) { const funs: ((item: T) => boolean)[] = []; const results: T[] = []; items.forEach((item) => { if (typeof item === "function") { funs.push(item); } else { // For object items, should be removed by reference, not by value const index = this.indexOf(item); if (index >= 0) results.push(...this.splice(index, 1)); } }); if (funs.length > 0) { // Reduce check loops for performance for (let i = this.length - 1; i >= 0; i--) { if (funs.some((fun) => fun(this[i]))) results.push(...this.splice(i, 1)); } } return results; }; Array.prototype.sortByProperty = function <T, P extends keyof T>( this: Array<T>, property: P, values: T[P][] ) { return this.sort((a, b) => { const ai = values.indexOf(a[property]); const bi = values.indexOf(b[property]); if (ai === bi) return 0; if (ai < 0 || bi < 0) return bi === 0 ? 1 : bi; return ai - bi; }); }; Array.prototype.sum = function <T>( this: Array<T>, field: T extends object ? DataTypes.Keys<T, number> : undefined ) { if (field == null) { return this.reduce((total, num) => total + (num as number), 0); } return this.reduce((total, item) => total + (item[field] as number), 0); }; /** * Array Utilities */ export namespace ArrayUtils { /** * Array 1 items do not exist in Array 2 or reverse match * @param a1 Array 1 * @param a2 Array 2 * @param round A round for both matches */ export function differences<T>(a1: T[], a2: T[], round?: boolean) { const diff = a1.filter((x) => !a2.includes(x)); if (round) return [...diff, ...a2.filter((x) => !a1.includes(x))]; return diff; } /** * Merge arrays, remove duplicates, and sort by the first array * @param sort Array to sort * @param param All arrays to merge * @returns Result */ export function mergeArrays<T>(sort: T[], ...param: T[][]): T[] { const result = [...sort]; for (let i = 0; i < param.length; i++) { const arr = param[i]; for (let j = 0; j < arr.length; j++) { const item = arr[j]; if (!result.includes(item)) { result.push(item); } } } return result; } }