@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
JavaScript
/**
* @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