UNPKG

@daiso-tech/core

Version:

The library offers flexible, framework-agnostic solutions for modern web applications, built on adaptable components that integrate seamlessly with popular frameworks like Next Js.

714 lines 24.7 kB
/** * @module Collection */ import { ItemNotFoundCollectionError, MultipleItemsFoundCollectionError, TypeCollectionError, EmptyCollectionError, } from "../../../collection/contracts/_module-exports.js"; import { CrossJoinIterable, SlidingIteralbe, ShuffleIterable, EntriesIterable, FilterIterable, ChunkIterable, ChunkWhileIterable, CollapseIterable, CountByIterable, FlatMapIterable, GroupByIterable, InsertAfterIterable, InsertBeforeIterable, MapIterable, MergeIterable, PadEndIterable, PadStartIterable, PartionIterable, SkipIterable, SkipUntilIterable, SortIterable, SplitIterable, TakeIterable, TakeUntilIterable, TapIterable, UniqueIterable, ChangeIterable, WhenIterable, ZipIterable, ReverseIterable, SliceIterable, RepeatIterable, ValidateIterable, } from "../../../collection/implementations/iterable-collection/_shared/_module.js"; import { isInvokable, resolveInvokable, } from "../../../utilities/_module-exports.js"; import { resolveLazyable } from "../../../utilities/_module-exports.js"; import { // eslint-disable-next-line @typescript-eslint/no-unused-vars UnexpectedError, } from "../../../utilities/_module-exports.js"; /** * All methods that return {@link ICollection | `ICollection`} are executed lazly, meaning the execution will occur iterating the items withthe `forEach` method or `for of` loop. * * IMPORT_PATH: `"@daiso-tech/core/collection"` * @group Adapters */ export class IterableCollection { iterable; /** * The `concat` static method is a convenient utility for easily concatenating multiple {@link Iterable | `Iterable`}. * @example * ```ts * import { IterableCollection } from "@daiso-tech/core/collection"; * * class MyIterable implements Iterable<number> { * *[Symbol.iterator](): Iterator<number> { * yield 1; * yield 2; * yield 3; * } * } * * const collection = IterableCollection.concat([ * new MyIterable(), * new Set([1, 2, 3]), * new Map([["a", 1], ["b", 2]]), * ["a", "b", "c"] * ]); * collection.toArray(); * // [1, 2, 3, 1, 2, 3, ["a", 1], ["b", 2], "a", "b", "c"] * ``` */ static concat(iterables) { return new IterableCollection(new MergeIterable(iterables)); } /** * The `difference` static method is used to compute the difference between two {@link Iterable | `Iterable`} instances. By default, the equality check is performed on each item. * @example * ```ts * import { IterableCollection } from "@daiso-tech/core/collection"; * * const collection = IterableCollection.difference( * [1, 2, 2, 3, 4, 5], * [2, 4, 6, 8] * ); * collection.toArray(); * // [1, 3, 5] * ``` * @example * ```ts * import { IterableCollection } from "@daiso-tech/core/collection"; * * const collection = IterableCollection.difference( * [ * { name: "iPhone 6", brand: "Apple", type: "phone" }, * { name: "iPhone 5", brand: "Apple", type: "phone" }, * { name: "Apple Watch", brand: "Apple", type: "watch" }, * { name: "Galaxy S6", brand: "Samsung", type: "phone" }, * { name: "Galaxy Gear", brand: "Samsung", type: "watch" }, * ], * [ * { name: "Apple Watch", brand: "Apple", type: "watch" }, * ], * (product) => product.type * ); * collection.toArray(); * // [ * // { name: "iPhone 6", brand: "Apple", type: "phone" }, * // { name: "iPhone 5", brand: "Apple", type: "phone" }, * // { name: "Galaxy S6", brand: "Samsung", type: "phone" }, * // ] * ``` */ static difference(iterableA, iterableB, selectFn) { return new IterableCollection(iterableA).difference(iterableB, selectFn); } /** * The `zip` static method merges together the values of `iterableA` with the values of the `iterableB` at their corresponding index. * The returned collection has size of the shortest collection. * @example * ```ts * import { IterableCollection } from "@daiso-tech/core/collection"; * * const collection = IterableCollection.zip(["Chair", "Desk"], [100, 200]); * collection.toArray(); * // [["Chair", 100], ["Desk", 200]] * ``` * @example * ```ts * import { IterableCollection } from "@daiso-tech/core/collection"; * * const collection = IterableCollection.zip(["Chair", "Desk", "Couch"], [100, 200]); * collection.toArray(); * // [["Chair", 100], ["Desk", 200]] * ``` * @example * ```ts * import { IterableCollection } from "@daiso-tech/core/collection"; * * const collection = IterableCollection.zip(["Chair", "Desk"], [100, 200, 300]); * collection.toArray(); * // [["Chair", 100], ["Desk", 200]] * ``` */ static zip(iterableA, iterableB) { return new IterableCollection(iterableA).zip(iterableB); } static deserialize(serializedValue) { return new IterableCollection(serializedValue); } static DEFAULT_CHUNK_SIZE = 1024; static makeCollection = (iterable = []) => { return new IterableCollection(iterable); }; /** * The `constructor` takes an {@link Iterable | `Iterable`}. * * Works with `Array`. * @example * ```ts * import { IterableCollection } from "@daiso-tech/core/collection"; * * const collection = new IterableCollection([1, 2, 3, 4]); * ``` * * Works with `String`. * @example * ```ts * import { IterableCollection } from "@daiso-tech/core/collection"; * * const collection = new IterableCollection("ABCDE"); * ``` * * Works with `Set`. * @example * ```ts * import { IterableCollection } from "@daiso-tech/core/collection"; * * const collection = new IterableCollection(new Set([1, 2, 2 4])); * ``` * * Works with `Map`. * @example * ```ts * import { IterableCollection } from "@daiso-tech/core/collection"; * * const collection = new IterableCollection(new Map([["a", 1], ["b", 2]])); * ``` * * Works with any `Iterable`. * @example * ```ts * import { IterableCollection } from "@daiso-tech/core/collection"; * * class MyIterable implements Iterable<number> { * *[Symbol.iterator](): Iterator<number> { * yield 1; * yield 2; * yield 3; * } * } * const collection = new IterableCollection(new MyIterable()); * ``` */ constructor(iterable) { this.iterable = iterable; } serialize() { return this.toArray(); } *[Symbol.iterator]() { yield* this.iterable; } toIterator() { return this[Symbol.iterator](); } entries() { return new IterableCollection(new EntriesIterable(this)); } keys() { return this.entries().map(([key]) => key); } copy() { return this.entries().map(([_key, value]) => value); } filter(predicateFn) { return new IterableCollection(new FilterIterable(this, predicateFn)); } validate(schema) { return new IterableCollection(new ValidateIterable(this, schema)); } reject(predicateFn) { return this.filter((...arguments_) => !resolveInvokable(predicateFn)(...arguments_)); } map(mapFn) { return new IterableCollection(new MapIterable(this, mapFn)); } reduce(reduceFn, initialValue) { if (initialValue === undefined && this.isEmpty()) { throw new TypeCollectionError("Reduce of empty array must be inputed a initial value"); } if (initialValue !== undefined) { let output = initialValue, index = 0; for (const item of this) { output = resolveInvokable(reduceFn)(output, item, index, this); index++; } return output; } // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-explicit-any let output = this.firstOrFail(), index = 0, isFirstIteration = true; for (const item of this) { if (!isFirstIteration) { output = resolveInvokable(reduceFn)(output, item, index, this); } isFirstIteration = false; index++; } return output; } join(separator = ",") { let str = null; for (const item of this) { if (typeof item !== "string") { throw new TypeCollectionError("Item type is invalid must be string"); } if (str === null) { str = item; } else { str = str + separator + item; } } return str; } collapse() { return new IterableCollection(new CollapseIterable(this)); } flatMap(mapFn) { return new IterableCollection(new FlatMapIterable(this, mapFn)); } change(predicateFn, mapFn) { return new IterableCollection(new ChangeIterable(this, predicateFn, mapFn)); } set(index, value) { if (index < 0) { return this; } let fn; if (isInvokable(value)) { fn = value; } else { fn = () => value; } return this.change((_, indexToMatch) => indexToMatch === index, fn); } get(index) { return this.first((_item, indexToMatch) => indexToMatch === index); } getOrFail(index) { return this.firstOrFail((_item, indexToMatch) => indexToMatch === index); } page(page, pageSize) { if (page < 0) { return this.skip(page * pageSize).take(pageSize); } return this.skip((page - 1) * pageSize).take(pageSize); } sum() { if (this.isEmpty()) { throw new EmptyCollectionError("Collection is empty therby operation cannot be performed"); } let sum = 0; for (const item of this) { if (typeof item !== "number") { throw new TypeCollectionError("Item type is invalid must be number"); } sum += item; } return sum; } average() { if (this.isEmpty()) { throw new EmptyCollectionError("Collection is empty therby operation cannot be performed"); } let size = 0, sum = 0; for (const item of this) { if (typeof item !== "number") { throw new TypeCollectionError("Item type is invalid must be number"); } size++; sum += item; } return (sum / size); } median() { if (this.isEmpty()) { throw new EmptyCollectionError("Collection is empty therby operation cannot be performed"); } const size = this.size(); if (size === 0) { return 0; } const isEven = size % 2 === 0, items = this.map((item) => { if (typeof item !== "number") { throw new TypeCollectionError("Item type is invalid must be number"); } return item; }) .filter((_item, index) => { if (isEven) { return index === size / 2 || index === size / 2 - 1; } return index === Math.floor(size / 2); }) .toArray(); if (isEven) { const [a, b] = items; if (a === undefined) { throw new UnexpectedError("Is in invalid state"); } if (b === undefined) { throw new UnexpectedError("Is in invalid state"); } return ((a + b) / 2); } const [median] = items; if (median === undefined) { throw new UnexpectedError("Is in invalid state"); } return median; } min() { if (this.isEmpty()) { throw new EmptyCollectionError("Collection is empty therby operation cannot be performed"); } let min = 0; for (const item of this) { if (typeof item !== "number") { throw new TypeCollectionError("Item type is invalid must be number"); } if (min === 0) { min = item; } else if (min > item) { min = item; } } return min; } max() { if (this.isEmpty()) { throw new EmptyCollectionError("Collection is empty therby operation cannot be performed"); } let max = 0; for (const item of this) { if (typeof item !== "number") { throw new TypeCollectionError("Item type is invalid must be number"); } if (max === 0) { max = item; } else if (max < item) { max = item; } } return max; } percentage(predicateFn) { if (this.isEmpty()) { throw new EmptyCollectionError("Collection is empty therby operation cannot be performed"); } let part = 0, total = 0; for (const item of this) { if (resolveInvokable(predicateFn)(item, total, this)) { part++; } total++; } return (part / total) * 100; } some(predicateFn) { let index = 0; for (const item of this) { if (resolveInvokable(predicateFn)(item, index, this)) { return true; } index++; } return false; } every(predicateFn) { let index = 0, isTrue = true; for (const item of this) { isTrue &&= resolveInvokable(predicateFn)(item, index, this); if (!isTrue) { break; } index++; } return isTrue; } take(limit) { return new IterableCollection(new TakeIterable(this, limit)); } takeUntil(predicateFn) { return new IterableCollection(new TakeUntilIterable(this, predicateFn)); } takeWhile(predicateFn) { return this.takeUntil((...arguments_) => !resolveInvokable(predicateFn)(...arguments_)); } skip(offset) { return new IterableCollection(new SkipIterable(this, offset)); } skipUntil(predicateFn) { return new IterableCollection(new SkipUntilIterable(this, predicateFn)); } skipWhile(predicateFn) { return this.skipUntil((...arguments_) => !resolveInvokable(predicateFn)(...arguments_)); } when(condition, callback) { return new IterableCollection(new WhenIterable(this, () => condition, callback)); } whenEmpty(callback) { return new IterableCollection(new WhenIterable(this, () => this.isEmpty(), callback)); } whenNot(condition, callback) { return this.when(!condition, callback); } whenNotEmpty(callback) { return new IterableCollection(new WhenIterable(this, () => this.isNotEmpty(), callback)); } pipe(callback) { return resolveInvokable(callback)(this); } tap(callback) { return new IterableCollection(new TapIterable(this, callback)); } chunk(chunkSize) { return new IterableCollection(new ChunkIterable(this, chunkSize, IterableCollection.makeCollection)); } chunkWhile(predicateFn) { return new IterableCollection(new ChunkWhileIterable(this, predicateFn, IterableCollection.makeCollection)); } split(chunkAmount) { return new IterableCollection(new SplitIterable(this, chunkAmount, IterableCollection.makeCollection)); } partition(predicateFn) { return new IterableCollection(new PartionIterable(this, predicateFn, IterableCollection.makeCollection)); } sliding(chunkSize, step = chunkSize - 1) { return new IterableCollection(new SlidingIteralbe(this, chunkSize, step)); } groupBy(selectFn) { return new IterableCollection(new GroupByIterable(this, selectFn, IterableCollection.makeCollection)); } countBy(selectFn) { return new IterableCollection(new CountByIterable(this, selectFn)); } unique(selectFn) { return new IterableCollection(new UniqueIterable(this, selectFn)); } difference(iterable, mapFn = (item) => item) { const differenceCollection = new IterableCollection(iterable); return this.filter((item, index, collection) => { return !differenceCollection.some((matchItem, matchIndex, matchCollection) => { return (resolveInvokable(mapFn)(item, index, collection) === resolveInvokable(mapFn)(matchItem, matchIndex, matchCollection)); }); }); } repeat(amount) { return new IterableCollection(new RepeatIterable(this, amount, IterableCollection.makeCollection)); } padStart(maxLength, fillItems) { return new IterableCollection(new PadStartIterable(this, maxLength, fillItems, IterableCollection.makeCollection)); } padEnd(maxLength, fillItems) { return new IterableCollection(new PadEndIterable(this, maxLength, fillItems, IterableCollection.makeCollection)); } slice(start, end) { return new IterableCollection(new SliceIterable(this, start, end)); } prepend(iterable) { return new IterableCollection(new MergeIterable([iterable, this])); } append(iterable) { return new IterableCollection(new MergeIterable([this, iterable])); } insertBefore(predicateFn, iterable) { return new IterableCollection(new InsertBeforeIterable(this, predicateFn, iterable)); } insertAfter(predicateFn, iterable) { return new IterableCollection(new InsertAfterIterable(this, predicateFn, iterable)); } crossJoin(iterable) { return new IterableCollection(new CrossJoinIterable(this, iterable, IterableCollection.makeCollection)); } zip(iterable) { return new IterableCollection(new ZipIterable(this, iterable)); } sort(comparator) { return new IterableCollection(new SortIterable(this, comparator)); } reverse(chunkSize = IterableCollection.DEFAULT_CHUNK_SIZE) { return new IterableCollection(new ReverseIterable(this, chunkSize, IterableCollection.makeCollection)); } shuffle(mathRandom = Math.random) { return new IterableCollection(new ShuffleIterable(this, mathRandom)); } first(predicateFn) { return this.firstOr(null, predicateFn); } firstOr(defaultValue, predicateFn = () => true) { let index = 0; for (const item of this) { if (resolveInvokable(predicateFn)(item, index, this)) { return item; } index++; } return resolveLazyable(defaultValue); } firstOrFail(predicateFn) { const item = this.first(predicateFn); if (item === null) { throw new ItemNotFoundCollectionError("Item was not found"); } return item; } last(predicateFn) { return this.lastOr(null, predicateFn); } lastOr(defaultValue, predicateFn = () => true) { let index = 0; let matchedItem = null; for (const item of this) { if (resolveInvokable(predicateFn)(item, index, this)) { matchedItem = item; } index++; } if (matchedItem !== null) { return matchedItem; } return resolveLazyable(defaultValue); } lastOrFail(predicateFn) { const item = this.last(predicateFn); if (item === null) { throw new ItemNotFoundCollectionError("Item was not found"); } return item; } before(predicateFn) { return this.beforeOr(null, predicateFn); } beforeOr(defaultValue, predicateFn) { let beforeItem = null, index = 0; for (const item of this) { if (resolveInvokable(predicateFn)(item, index, this) && beforeItem !== null) { return beforeItem; } index++; beforeItem = item; } return resolveLazyable(defaultValue); } beforeOrFail(predicateFn) { const item = this.before(predicateFn); if (item === null) { throw new ItemNotFoundCollectionError("Item was not found"); } return item; } after(predicateFn) { return this.afterOr(null, predicateFn); } afterOr(defaultValue, predicateFn) { let hasMatched = false, index = 0; for (const item of this) { if (hasMatched) { return item; } hasMatched = resolveInvokable(predicateFn)(item, index, this); index++; } return resolveLazyable(defaultValue); } afterOrFail(predicateFn) { const item = this.after(predicateFn); if (item === null) { throw new ItemNotFoundCollectionError("Item was not found"); } return item; } sole(predicateFn) { let index = 0, matchedItem = null; for (const item of this) { if (resolveInvokable(predicateFn)(item, index, this)) { if (matchedItem !== null) { throw new MultipleItemsFoundCollectionError("Multiple items were found"); } matchedItem = item; } index++; } if (matchedItem === null) { throw new ItemNotFoundCollectionError("Item was not found"); } return matchedItem; } nth(step) { return this.filter((_item, index) => index % step === 0); } count(predicateFn) { let size = 0; for (const item of this) { if (resolveInvokable(predicateFn)(item, size, this)) { size++; } } return size; } size() { return this.count(() => true); } isEmpty() { for (const _ of this) { return false; } return true; } isNotEmpty() { return !this.isEmpty(); } searchFirst(predicateFn) { let index = 0; for (const item of this) { if (resolveInvokable(predicateFn)(item, index, this)) { return index; } index++; } return -1; } searchLast(predicateFn) { let index = 0; let matchedIndex = -1; for (const item of this) { if (resolveInvokable(predicateFn)(item, index, this)) { matchedIndex = index; } index++; } return matchedIndex; } forEach(callback) { let index = 0; for (const item of this) { resolveInvokable(callback)(item, index, this); index++; } } toArray() { return [...this]; } toRecord() { const record = {}; for (const item of this) { if (!Array.isArray(item)) { throw new TypeCollectionError("Item type is invalid must be a tuple of size 2 where first tuple item is a string or number or symbol"); } if (item.length !== 2) { throw new TypeCollectionError("Item type is invalid must be a tuple of size 2 where first tuple item is a string or number or symbol"); } const [key, value] = item; if (!(typeof key === "string" || typeof key === "number" || typeof key === "symbol")) { throw new TypeCollectionError("Item type is invalid must be a tuple of size 2 where first tuple item is a string or number or symbol"); } record[key] = value; } return record; } toMap() { const map = new Map(); for (const item of this) { if (!Array.isArray(item)) { throw new TypeCollectionError("Item type is invalid must be a tuple of size 2"); } if (item.length !== 2) { throw new TypeCollectionError("Item type is invalid must be a tuple of size 2"); } const [key, value] = item; map.set(key, value); } return map; } } //# sourceMappingURL=iterable-collection.js.map