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