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.

780 lines 29.5 kB
/** * @module Collection */ import { ItemNotFoundCollectionError, MultipleItemsFoundCollectionError, UnexpectedCollectionError, TypeCollectionError, EmptyCollectionError, } from "../../../collection/contracts/_module-exports.js"; import { AsyncCrossJoinIterable, AsyncSlidingIteralbe, AsyncShuffleIterable, AsyncEntriesIterable, AsyncFilterIterable, AsyncChunkIterable, AsyncChunkWhileIterable, AsyncCollapseIterable, AsyncCountByIterable, AsyncFlatMapIterable, AsyncGroupByIterable, AsyncInsertAfterIterable, AsyncInsertBeforeIterable, AsyncMapIterable, AsyncMergeIterable, AsyncPadEndIterable, AsyncPadStartIterable, AsyncPartionIterable, AsyncSkipIterable, AsyncSkipUntilIterable, AsyncSortIterable, AsyncSplitIterable, AsyncTakeIterable, AsyncTakeUntilIterable, AsyncTapIterable, AsyncUniqueIterable, AsyncChangeIterable, AsyncWhenIterable, AsyncZipIterable, AsyncReverseIterable, AsyncSliceIterable, AsyncRepeatIterable, } from "../../../collection/implementations/async-iterable-collection/_shared/_module.js"; import { isInvokable, resolveFactory, resolveInvokable, } from "../../../utilities/_module-exports.js"; import { resolveAsyncLazyable } from "../../../utilities/_module-exports.js"; import { LazyPromise } from "../../../async/_module-exports.js"; /** * All methods that return {@link IAsyncCollection | `IAsyncCollection`} are executed lazly, meaning the execution will occur iterating the items withthe `forEach` method or `for await` loop. * * IMPORT_PATH: `"@daiso-tech/core/collection"` * @group Adapters */ export class AsyncIterableCollection { iterable; /** * The `concat` static method is a convenient utility for easily concatenating multiple {@link Iterable | `Iterable`} or {@link AsyncIterable | `AsyncIterable`}. * @example * ```ts * import { AsyncIterableCollection } from "@daiso-tech/core"; * * class MyAsyncIterable implements AsyncIterable<number> { * async *[Symbol.iterator](): Iterator<number> { * yield "a"; * yield "b"; * yield "c"; * } * } * * class MyIterable implements Iterable<number> { * *[Symbol.iterator](): Iterator<number> { * yield 1; * yield 2; * yield 3; * } * } * * const collection = AsyncIterableCollection.concat([ * new MyAsyncIterable(), * new MyIterable(), * new Set([1, 2, 3]), * new Map([["a", 1], ["b", 2]]), * ["a", "b", "c"] * ]); * await collection.toArray(); * // ["a", "b", "c", 1, 2, 3, 1, 2, 3, ["a", 1], ["b", 2], "a", "b", "c"] * ``` */ static concat(iterables) { return new AsyncIterableCollection(new AsyncMergeIterable(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 { AsyncIterableCollection } from "@daiso-tech/core"; * * const collection = AsyncIterableCollection.difference( * [1, 2, 2, 3, 4, 5], * [2, 4, 6, 8] * ); * await collection.toArray(); * // [1, 3, 5] * ``` * @example * ```ts * import { AsyncIterableCollection } from "@daiso-tech/core"; * * const collection = AsyncIterableCollection.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 * ); * await 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 AsyncIterableCollection(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 { AsyncIterableCollection } from "@daiso-tech/core";; * * const collection = AsyncIterableCollection.zip(["Chair", "Desk"], [100, 200]); * await collection.toArray(); * // [["Chair", 100], ["Desk", 200]] * ``` * @example * ```ts * import { AsyncIterableCollection } from "@daiso-tech/core";; * * const collection = AsyncIterableCollection.zip(["Chair", "Desk", "Couch"], [100, 200]); * await collection.toArray(); * // [["Chair", 100], ["Desk", 200]] * ``` * @example * ```ts * import { AsyncIterableCollection } from "@daiso-tech/core";; * * const collection = AsyncIterableCollection.zip(["Chair", "Desk"], [100, 200, 300]); * await collection.toArray(); * // [["Chair", 100], ["Desk", 200]] * ``` */ static zip(iterableA, iterableB) { return new AsyncIterableCollection(iterableA).zip(iterableB); } static DEFAULT_CHUNK_SIZE = 1024; static makeCollection = (iterable) => { return new AsyncIterableCollection(iterable); }; lazyPromiseFactory; /** * The `constructor` takes an {@link Iterable | `Iterable`} or {@link AsyncIterable | `AsyncIterable`}. * * Works with `Array`. * @example * ```ts * import { AsyncIterableCollection } from "@daiso-tech/core"; * * const collection = new AsyncIterableCollection([1, 2, 3, 4]); * ``` * * Works with `String`. * @example * ```ts * import { AsyncIterableCollection } from "@daiso-tech/core"; * * const collection = new AsyncIterableCollection("ABCDE"); * ``` * * Works with `Set`. * @example * ```ts * import { AsyncIterableCollection } from "@daiso-tech/core"; * * const collection = new AsyncIterableCollection(new Set([1, 2, 2 4])); * ``` * * Works with `Map`. * @example * ```ts * import { AsyncIterableCollection } from "@daiso-tech/core"; * * const collection = new AsyncIterableCollection(new Map([["a", 1], ["b", 2]])); * ``` * * Works with any `Iterable`. * @example * ```ts * import { AsyncIterableCollection } from "@daiso-tech/core"; * * class MyIterable implements Iterable<number> { * *[Symbol.iterator](): Iterator<number> { * yield 1; * yield 2; * yield 3; * } * } * const collection = new AsyncIterableCollection(new MyIterable()); * ``` * * Works with any `AsyncIterable`. * @example * ```ts * import { AsyncIterableCollection } from "@daiso-tech/core"; * * class MyIterable implements AsyncIterable<number> { * async *[Symbol.iterator](): Iterator<number> { * yield 1; * yield 2; * yield 3; * } * } * const collection = new AsyncIterableCollection(new MyIterable()); * ``` */ constructor(iterable = [], settings = {}) { this.iterable = iterable; const { lazyPromiseFactory = (invokable) => new LazyPromise(invokable), } = settings; this.lazyPromiseFactory = resolveFactory(lazyPromiseFactory); } createLazyPromise(asyncFn) { return this.lazyPromiseFactory(asyncFn); } async *[Symbol.asyncIterator]() { yield* this.iterable; } toIterator() { return this[Symbol.asyncIterator](); } entries() { return new AsyncIterableCollection(new AsyncEntriesIterable(this)); } keys() { return this.entries().map(([key]) => key); } values() { return this.entries().map(([_key, value]) => value); } filter(predicateFn) { return new AsyncIterableCollection(new AsyncFilterIterable(this, predicateFn)); } reject(predicateFn) { return this.filter(async (...arguments_) => !(await resolveInvokable(predicateFn)(...arguments_))); } map(mapFn) { return new AsyncIterableCollection(new AsyncMapIterable(this, mapFn)); } reduce(reduce, initialValue) { return this.createLazyPromise(async () => { if (initialValue === undefined && (await this.isEmpty())) { throw new TypeCollectionError("AsyncReduce of empty array must be inputed a initial value"); } if (initialValue !== undefined) { let output = initialValue; for await (const [index, item] of this.entries()) { output = await resolveInvokable(reduce)(output, item, index, this); } return output; } // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment let output = (await this.firstOrFail()), index = 0, isFirstIteration = true; for await (const item of this) { if (!isFirstIteration) { output = await resolveInvokable(reduce)(output, item, index, this); } isFirstIteration = false; index++; } return output; }); } join(separator = ",") { return this.createLazyPromise(async () => { let str = null; for await (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 AsyncIterableCollection(new AsyncCollapseIterable(this)); } flatMap(mapFn) { return new AsyncIterableCollection(new AsyncFlatMapIterable(this, mapFn)); } change(predicateFn, mapFn) { return new AsyncIterableCollection(new AsyncChangeIterable(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() { return this.createLazyPromise(async () => { if (await this.isEmpty()) { throw new EmptyCollectionError("Collection is empty therby operation cannot be performed"); } let sum = 0; for await (const item of this) { if (typeof item !== "number") { throw new TypeCollectionError("Item type is invalid must be number"); } sum += item; } return sum; }); } average() { return this.createLazyPromise(async () => { if (await this.isEmpty()) { throw new EmptyCollectionError("Collection is empty therby operation cannot be performed"); } let size = 0, sum = 0; for await (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() { return this.createLazyPromise(async () => { if (await this.isEmpty()) { throw new EmptyCollectionError("Collection is empty therby operation cannot be performed"); } const size = await this.size(); if (size === 0) { return 0; } const isEven = size % 2 === 0, items = await 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 UnexpectedCollectionError("Is in invalid state"); } if (b === undefined) { throw new UnexpectedCollectionError("Is in invalid state"); } return ((a + b) / 2); } const [median] = items; if (median === undefined) { throw new UnexpectedCollectionError("Is in invalid state"); } return median; }); } min() { return this.createLazyPromise(async () => { if (await this.isEmpty()) { throw new EmptyCollectionError("Collection is empty therby operation cannot be performed"); } let min = 0; for await (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() { return this.createLazyPromise(async () => { if (await this.isEmpty()) { throw new EmptyCollectionError("Collection is empty therby operation cannot be performed"); } let max = 0; for await (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) { return this.createLazyPromise(async () => { if (await this.isEmpty()) { throw new EmptyCollectionError("Collection is empty therby operation cannot be performed"); } let part = 0, total = 0; for await (const item of this) { if (await resolveInvokable(predicateFn)(item, total, this)) { part++; } total++; } return (part / total) * 100; }); } some(predicateFn) { return this.createLazyPromise(async () => { for await (const [index, item] of this.entries()) { if (await resolveInvokable(predicateFn)(item, index, this)) { return true; } } return false; }); } every(predicateFn) { return this.createLazyPromise(async () => { let isTrue = true; for await (const [index, item] of this.entries()) { isTrue &&= await resolveInvokable(predicateFn)(item, index, this); if (!isTrue) { break; } } return isTrue; }); } take(limit) { return new AsyncIterableCollection(new AsyncTakeIterable(this, limit)); } takeUntil(predicateFn) { return new AsyncIterableCollection(new AsyncTakeUntilIterable(this, predicateFn)); } takeWhile(predicateFn) { return this.takeUntil(async (...arguments_) => !(await resolveInvokable(predicateFn)(...arguments_))); } skip(offset) { return new AsyncIterableCollection(new AsyncSkipIterable(this, offset)); } skipUntil(predicateFn) { return new AsyncIterableCollection(new AsyncSkipUntilIterable(this, predicateFn)); } skipWhile(predicateFn) { return this.skipUntil(async (...arguments_) => !(await resolveInvokable(predicateFn)(...arguments_))); } when(condition, callback) { return new AsyncIterableCollection(new AsyncWhenIterable(this, () => condition, callback)); } whenEmpty(callback) { return new AsyncIterableCollection(new AsyncWhenIterable(this, () => this.isEmpty(), callback)); } whenNot(condition, callback) { return new AsyncIterableCollection(new AsyncWhenIterable(this, () => !condition, callback)); } whenNotEmpty(callback) { return new AsyncIterableCollection(new AsyncWhenIterable(this, () => this.isNotEmpty(), callback)); } pipe(callback) { return this.createLazyPromise(async () => { return resolveInvokable(callback)(this); }); } tap(callback) { return new AsyncIterableCollection(new AsyncTapIterable(this, callback)); } chunk(chunkSize) { return new AsyncIterableCollection(new AsyncChunkIterable(this, chunkSize, AsyncIterableCollection.makeCollection)); } chunkWhile(predicateFn) { return new AsyncIterableCollection(new AsyncChunkWhileIterable(this, predicateFn, AsyncIterableCollection.makeCollection)); } split(chunkAmount) { return new AsyncIterableCollection(new AsyncSplitIterable(this, chunkAmount, AsyncIterableCollection.makeCollection)); } partition(predicateFn) { return new AsyncIterableCollection(new AsyncPartionIterable(this, predicateFn, AsyncIterableCollection.makeCollection)); } sliding(chunkSize, step = chunkSize - 1) { return new AsyncIterableCollection(new AsyncSlidingIteralbe(this, chunkSize, step)); } groupBy(selectFn) { return new AsyncIterableCollection(new AsyncGroupByIterable(this, selectFn, AsyncIterableCollection.makeCollection)); } countBy(selectFn) { return new AsyncIterableCollection(new AsyncCountByIterable(this, selectFn)); } unique(selectFn) { return new AsyncIterableCollection(new AsyncUniqueIterable(this, selectFn)); } difference(iterable, selectFn = (item) => item) { const differenceCollection = new AsyncIterableCollection(iterable); return this.filter(async (item, index, collection) => { return !(await differenceCollection.some(async (matchItem, matchIndex, matchCollection) => { return ((await resolveInvokable(selectFn)(item, index, collection)) === (await resolveInvokable(selectFn)(matchItem, matchIndex, matchCollection))); })); }); } repeat(amount) { return new AsyncIterableCollection(new AsyncRepeatIterable(this, amount, AsyncIterableCollection.makeCollection)); } padStart(maxLength, fillItems) { return new AsyncIterableCollection(new AsyncPadStartIterable(this, maxLength, fillItems, AsyncIterableCollection.makeCollection)); } padEnd(maxLength, fillItems) { return new AsyncIterableCollection(new AsyncPadEndIterable(this, maxLength, fillItems, AsyncIterableCollection.makeCollection)); } slice(start, end) { return new AsyncIterableCollection(new AsyncSliceIterable(this, start, end)); } prepend(iterable) { return new AsyncIterableCollection(new AsyncMergeIterable([iterable, this])); } append(iterable) { return new AsyncIterableCollection(new AsyncMergeIterable([this, iterable])); } insertBefore(predicateFn, iterable) { return new AsyncIterableCollection(new AsyncInsertBeforeIterable(this, predicateFn, iterable)); } insertAfter(predicateFn, iterable) { return new AsyncIterableCollection(new AsyncInsertAfterIterable(this, predicateFn, iterable)); } crossJoin(iterable) { return new AsyncIterableCollection(new AsyncCrossJoinIterable(this, iterable, AsyncIterableCollection.makeCollection)); } zip(iterable) { return new AsyncIterableCollection(new AsyncZipIterable(this, iterable)); } sort(comparator) { return new AsyncIterableCollection(new AsyncSortIterable(this, comparator)); } reverse(chunkSize) { return new AsyncIterableCollection(new AsyncReverseIterable(this, chunkSize ?? AsyncIterableCollection.DEFAULT_CHUNK_SIZE, AsyncIterableCollection.makeCollection)); } shuffle(mathRandom = Math.random) { return new AsyncIterableCollection(new AsyncShuffleIterable(this, mathRandom)); } first(predicateFn) { return this.firstOr(null, predicateFn); } firstOr(defaultValue, predicateFn = () => true) { return this.createLazyPromise(async () => { for await (const [index, item] of this.entries()) { if (await resolveInvokable(predicateFn)(item, index, this)) { return item; } } return await resolveAsyncLazyable(defaultValue); }); } firstOrFail(predicateFn) { return this.createLazyPromise(async () => { const item = await 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) { return this.createLazyPromise(async () => { let matchedItem = null; for await (const [index, item] of this.entries()) { if (await resolveInvokable(predicateFn)(item, index, this)) { matchedItem = item; } } if (matchedItem) { return matchedItem; } return await resolveAsyncLazyable(defaultValue); }); } lastOrFail(predicateFn) { return this.createLazyPromise(async () => { const item = await 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) { return this.createLazyPromise(async () => { let beforeItem = null, index = 0; for await (const item of this) { if ((await resolveInvokable(predicateFn)(item, index, this)) && beforeItem) { return beforeItem; } index++; beforeItem = item; } return await resolveAsyncLazyable(defaultValue); }); } beforeOrFail(predicateFn) { return this.createLazyPromise(async () => { const item = await 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) { return this.createLazyPromise(async () => { let hasMatched = false, index = 0; for await (const item of this) { if (hasMatched) { return item; } hasMatched = await resolveInvokable(predicateFn)(item, index, this); index++; } return await resolveAsyncLazyable(defaultValue); }); } afterOrFail(predicateFn) { return this.createLazyPromise(async () => { const item = await this.after(predicateFn); if (item === null) { throw new ItemNotFoundCollectionError("Item was not found"); } return item; }); } sole(predicateFn) { return this.createLazyPromise(async () => { let matchedItem = null; for await (const [index, item] of this.entries()) { if (await resolveInvokable(predicateFn)(item, index, this)) { if (matchedItem !== null) { throw new MultipleItemsFoundCollectionError("Multiple items were found"); } matchedItem = item; } } if (matchedItem === null) { throw new ItemNotFoundCollectionError("Item was not found"); } return matchedItem; }); } nth(step) { return this.filter((_item, index) => index % step === 0); } count(predicateFn) { return this.createLazyPromise(async () => { let size = 0; for await (const item of this) { if (await resolveInvokable(predicateFn)(item, size, this)) { size++; } } return size; }); } size() { return this.count(() => true); } isEmpty() { return this.createLazyPromise(async () => { for await (const _ of this) { return false; } return true; }); } isNotEmpty() { return this.createLazyPromise(async () => { return !(await this.isEmpty()); }); } searchFirst(predicateFn) { return this.createLazyPromise(async () => { for await (const [index, item] of this.entries()) { if (await resolveInvokable(predicateFn)(item, index, this)) { return index; } } return -1; }); } searchLast(predicateFn) { return this.createLazyPromise(async () => { let matchedIndex = -1; for await (const [index, item] of this.entries()) { if (await resolveInvokable(predicateFn)(item, index, this)) { matchedIndex = index; } } return matchedIndex; }); } forEach(callback) { return this.createLazyPromise(async () => { for await (const [index, item] of this.entries()) { await resolveInvokable(callback)(item, index, this); } }); } toArray() { return this.createLazyPromise(async () => { const items = []; for await (const item of this) { items.push(item); } return items; }); } toRecord() { return this.createLazyPromise(async () => { const record = {}; for await (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() { return this.createLazyPromise(async () => { const map = new Map(); for await (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=async-iterable-collection.js.map