@etsoo/shared
Version:
TypeScript shared utilities and functions
216 lines (187 loc) • 5.39 kB
text/typescript
import isEqual from "lodash.isequal";
import { DataTypes } 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;
/**
* 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.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;
}
}