UNPKG

@thisisagile/easy

Version:

Straightforward library for building domain-driven microservice architectures

299 lines (235 loc) • 9.47 kB
import { ArrayLike, toArray } from './Array'; import { Constructor, on } from './Constructor'; import type { Json } from './Json'; import { isArray, isDefined, isEmpty } from './Is'; import { isA } from './IsA'; import { Get, GetProperty, ofGet, ofProperty } from './Get'; import type { Id } from './Id'; import { asString } from './Text'; import { Optional } from './Types'; import { ifDefined, ifTrue } from '../utils/If'; export class List<T = unknown> extends Array<T> { get ids(): List<Id> { return this.mapDefined(i => (i as any).id as Id); } isSubSetOf(...items: ArrayLike<T>): boolean { return this.diff(items).length === 0; } isSuperSetOf(...items: ArrayLike<T>): boolean { return this.length > items.length && toList(...items).isSubSetOf(...this); } isIntersectingWith(...items: ArrayLike<T>): boolean { return this.intersect(items).length > 0; } isDisjointWith(...items: ArrayLike<T>): boolean { return !this.isIntersectingWith(...items); } areEqual(...items: ArrayLike<T> ): boolean { return this.isSubSetOf(...items) && toList(...items).isSubSetOf(...this); } asc(p: GetProperty<T, any>): List<T> { return this.sort((e1, e2) => (ofProperty(e1, p) > ofProperty(e2, p) ? 1 : -1)); } desc(p: GetProperty<T, any>): List<T> { return this.sort((e1, e2) => (ofProperty(e1, p) < ofProperty(e2, p) ? 1 : -1)); } first(p?: (value: T, index: number, array: T[]) => unknown, params?: unknown): T { return (p ? this.find(p, params) : this[0]) as T; } firstValue<V>(f: (t: T) => V, alt?: Get<V, T>): Optional<V> { return ifDefined( this.first(t => !!f(t)), f, v => ofGet(alt, v) ); } isFirst(value: T): boolean { return value === this.first(); } next(p?: (value: T, index: number, array: T[]) => unknown, params?: unknown): T { return p ? this[this.findIndex(p, params) + 1] : this[0]; } prev(p?: (value: T, index: number, array: T[]) => unknown, params?: unknown): T { return p ? this[this.findIndex(p, params) - 1] : this[0]; } last(p?: (value: T, index: number, array: T[]) => unknown, params?: unknown): T { return p ? this.filter(p, params).last() : this[this.length - 1]; } isLast(value: T): boolean { return value === this.last(); } overlaps(...items: ArrayLike<T>): boolean { return toList<T>(...items).some(i => this.some(t => i === t)); } diff(others: ArrayLike<T>): List<T> { return this.filter(i => !others.includes(i)); } diffByKey<U = T>(others: ArrayLike<U>, key: keyof T & keyof U): List<T> { return this.filter((i: any) => !others.some((o: any) => o[key] === i[key])); } symmetricDiff(others: ArrayLike<T>): List<T> { return this.diff(others).concat(toList<T>(...others).diff(this)); } symmetricDiffByKey(others: ArrayLike<T>, key: keyof T): List<T> { return this.diffByKey(others, key).concat(toList<T>(...others).diffByKey(this, key)); } intersect(others: ArrayLike<T>): List<T> { return this.filter(i => others.includes(i)); } intersectByKey<U>(others: ArrayLike<U>, key: keyof T & keyof U): List<T> { return this.filter((i: any) => others.some((o: any) => o[key] === i[key])); } intersectBy<U>(others: ArrayLike<U>, f: (value: T, value2: U) => boolean): List<T> { return this.filter(i => others.some(o => f(i, o as U))); } accumulate(...keys: (keyof T)[]): List<T> { return this.map((d, i, arr) => { const acc = keys.reduce((acc, v) => { (acc as any)[v] = i === 0 ? d[v] : (arr[i - 1][v] as number) + (d[v] as number); return acc; }, d); arr[i] = acc; return acc; }); } toJSON(): Json[] { return this.reduce((a, i) => { a.push(JSON.parse(JSON.stringify(i ?? {}))); return a; }, new Array<Json>()); } map<U>(f: (value: T, index: number, array: T[]) => U, params?: unknown): List<U> { return toList<U>(super.map(f, params)); } flatMap<U, This = unknown>(f: (this: This, value: T, index: number, array: T[]) => ReadonlyArray<U> | U, params?: This): List<U> { return toList<U>(super.flatMap(f, params)); } mapDefined<U>(f: (value: T, index: number, array: T[]) => U, params?: unknown): List<NonNullable<U>> { return this.map(f, params).defined(); } mapAsync(f: (i: T) => Promise<T>): Promise<List<T>> { return Promise.all(super.map(e => f(e))).then(a => toList<T>(a)); } mapSerial<U>(f: (i: T) => Promise<U>): Promise<List<U>> { return super.reduce((p, item) => p.then(results => on(results, async r => r.push(await f(item)))), Promise.resolve(toList<U>())); } distinct(): List<T> { return this.filter((i, index) => this.indexOf(i) === index); } distinctByKey(key: keyof T): List<T> { const seen = new Set<T[keyof T]>(); return this.filter(item => !seen.has(item[key]) && seen.add(item[key])); } distinctByValue(): List<T> { const seen = new Set<string>(); return this.filter(item => !seen.has(JSON.stringify(item)) && seen.add(JSON.stringify(item))); } filter(p: (value: T, index: number, array: T[]) => unknown, params?: unknown): List<T> { return toList<T>(super.filter(p, params)); } sum(p: (t: T) => number): number { return this.reduce((sum: number, i) => sum + p(i), 0); } max(key: keyof T): T { return this.desc(key).first(); } min(key: keyof T): T { return this.asc(key).first(); } byId(id: Id): T { return this.first(i => asString((i as any).id) === asString(id)); } byKey(key: keyof T, value: unknown): T { return this.first(i => i[key] == value); } add(...items: ArrayLike<T>): this { super.push(...toArray(...items)); return this; } concat(...items: ConcatArray<T>[]): List<T>; concat(...items: (T | ConcatArray<T>)[]): List<T>; concat(...items: (T | ConcatArray<T>)[]): List<T> { return toList<T>(super.concat(...items)); } reverse(): List<T> { return toList<T>(super.reverse()); } splice(start: number, deleteCount?: number): List<T>; splice(start: number, deleteCount: number, ...items: T[]): List<T>; splice(start: number, deleteCount: number, ...items: T[]): List<T> { return toList<T>(super.splice(start, deleteCount, ...items)); } remove(item: T): List<T> { const index = this.indexOf(item); if (index > -1) { this.splice(index, 1); } return this; } replace(key: keyof T, item: T): List<T> { const index = this.findIndex(i => i[key] === item?.[key]); ifTrue(index != -1, () => this.splice(index, 1, item)); return this; } switch(item: T): List<T> { return this.includes(item) ? this.remove(item) : this.add(item); } defined(): List<NonNullable<T>> { return this.reduce((l, v) => (isDefined(v) ? l.add(v) : l), toList<NonNullable<T>>()); } toObject(key: keyof T, options: { deleteKey?: boolean } = {}): Record<string | number | symbol, T> { return this.reduce((o: any, i) => { o[i[key]] = i; if (options.deleteKey) delete o[i[key]][key]; return o; }, {}); } toObjectList(key: keyof T): Record<string | number | symbol, List<T>> { return this.reduce( (a, t) => { const k = t[key] as unknown as string | number | symbol; a[k] = a[k] ?? toList(); a[k].push(t); return a; }, {} as Record<string | number | symbol, List<T>> ); } orElse(...alt: ArrayLike<T>): Optional<List<T>> { return !isEmpty(this) ? this : !isEmpty(...alt) ? toList<T>(...alt) : undefined; } weave(insertFrom: T[], interval: number): this { for (let i = interval, n = 0; i <= this.length && n < insertFrom.length; i += interval + 1) { this.splice(i, 0, insertFrom[n++]); } return this; } slice(start?: number, end?: number): List<T> { return toList(super.slice(start, end)); } none(p: (t: T) => boolean): boolean { return !this.some(p); } chunk(chunkSize: number): List<List<T>> { return this.reduce((acc, _, index) => (index % chunkSize === 0 ? on(acc, a => a.push(this.slice(index, index + chunkSize))) : acc), toList<List<T>>()); } //we needed to add U because of a Typescript issue with generics update<U = T>(p: (value: T, index: number, array: T[]) => unknown, val: T | ((v: U) => T)): List<T> { return this.map((v, i, a) => (p(v, i, a) ? ofGet<T>(val, v, i, a) : v)); } updateFirst<U = T>(p: (value: T, index: number, array: T[]) => unknown, val: T | ((v: U) => T)) { const index = this.findIndex(p); return this.update((t, i) => p(t, i, this) && i == index, val); } updateFirstById<U = T>(id: Id, val: T | ((v: U) => T)) { return this.updateFirst(i => asString((i as any)?.id) === asString(id), val); } updateById<U = T>(id: Id, val: T | ((v: U) => T)) { return this.update(i => asString((i as any)?.id) === asString(id), val); } } export const toList = <T = unknown>(...items: ArrayLike<T>): List<T> => new List<T>().add(...items); export const isList = <T>(l?: unknown): l is List<T> => isDefined(l) && isArray(l) && isA<List<T>>(l, 'first', 'last', 'asc', 'desc'); export const asList = <T>(c: Constructor<T>, items: unknown = []): List<T> => toList<T>(toArray(items).map(i => new c(i))); export const maxValue = <T>(l: List<T>, key: keyof T): T[keyof T] | undefined => l.desc(key).first()?.[key]; export const minValue = <T>(l: List<T>, key: keyof T): T[keyof T] | undefined => l.asc(key).first()?.[key];