From 2f30959953dbfc5c655b901117eb00a990f96cf8 Mon Sep 17 00:00:00 2001 From: hw Date: Sun, 10 Mar 2024 12:40:39 +0900 Subject: [PATCH 1/3] feat: add `fx` --- src/Lazy/fx.ts | 567 +++++++++++++++++++++++++++++++++++ src/Lazy/index.ts | 2 + test/Lazy/append.spec.ts | 15 + test/Lazy/concurrent.spec.ts | 9 + test/Lazy/drop.spec.ts | 21 ++ test/Lazy/filter.spec.ts | 17 ++ test/Lazy/flat.spec.ts | 19 ++ test/Lazy/flatMap.spec.ts | 20 +- test/Lazy/fx.spec.ts | 27 ++ test/Lazy/map.spec.ts | 21 +- test/Lazy/peek.spec.ts | 26 +- test/Lazy/slice.spec.ts | 18 +- test/Lazy/take.spec.ts | 21 ++ test/Lazy/takeUntil.spec.ts | 21 ++ test/Lazy/takeWhile.spec.ts | 22 ++ test/each.spec.ts | 22 +- test/every.spec.ts | 36 ++- test/find.spec.ts | 18 +- test/findIndex.spec.ts | 18 +- test/groupBy.spec.ts | 18 +- test/indexBy.spec.ts | 16 +- test/join.spec.ts | 18 +- test/reduce.spec.ts | 23 +- test/some.spec.ts | 46 ++- type-check/Lazy/fx.test.ts | 32 ++ 25 files changed, 1058 insertions(+), 15 deletions(-) create mode 100644 src/Lazy/fx.ts create mode 100644 test/Lazy/fx.spec.ts create mode 100644 type-check/Lazy/fx.test.ts diff --git a/src/Lazy/fx.ts b/src/Lazy/fx.ts new file mode 100644 index 00000000..d2d8cae7 --- /dev/null +++ b/src/Lazy/fx.ts @@ -0,0 +1,567 @@ +import { isAsyncIterable, isIterable } from "../_internal/utils"; +import consume from "../consume"; +import each from "../each"; +import every from "../every"; +import find from "../find"; +import findIndex from "../findIndex"; +import groupBy from "../groupBy"; +import indexBy from "../indexBy"; +import isUndefined from "../isUndefined"; +import join from "../join"; +import reduce from "../reduce"; +import some from "../some"; +import type Cast from "../types/Cast"; +import type IterableInfer from "../types/IterableInfer"; +import type Key from "../types/Key"; +import type { SyncReducer } from "../types/Reducer"; +import concurrent from "./concurrent"; +import drop from "./drop"; +import filter from "./filter"; +import flat from "./flat"; +import flatMap from "./flatMap"; +import map from "./map"; +import peek from "./peek"; +import reject from "./reject"; +import slice from "./slice"; +import take from "./take"; +import takeUntil from "./takeUntil"; +import takeWhile from "./takeWhile"; +import toAsync from "./toAsync"; + +class FxAsyncIterable { + private asyncIterable: AsyncIterable; + + constructor(asyncIterable: AsyncIterable) { + this.asyncIterable = asyncIterable; + } + + private [Symbol.asyncIterator]() { + return this.asyncIterable; + } + + /** + * Returns AsyncIterable of values by running each applying `f`. + * + * see {@link https://fxts.dev/docs/map | map} + */ + map(f: (a: A) => B) { + return new FxAsyncIterable(map(f, this.asyncIterable)); + } + + /** + * Returns flattened AsyncIterable of values by running each element + * flattening the mapped results. + * + * see {@link https://fxts.dev/docs/flatMap | flatMap} + */ + flatMap(f: (a: A) => B) { + return new FxAsyncIterable(flatMap(f, this.asyncIterable)); + } + + /** + * Returns flattened AsyncIterable. + * If first argument is number, more perform flatten `flat(2, [[[1,2]]]) // [1,2]` + * + * see {@link https://fxts.dev/docs/flat | flat} + */ + flat(depth?: number) { + return new FxAsyncIterable(flat(this.asyncIterable, depth)); + } + + /** + * Return AsyncIterable of all elements `f` returns truthy for + * + * see {@link https://fxts.dev/docs/filter | filter} + */ + filter(f: (a: A) => unknown): FxAsyncIterable { + return new FxAsyncIterable(filter(f, this.asyncIterable)); + } + + /** + * The opposite of filter method + * AsyncIterable of all elements `f` returns falsy for + * + * see {@link https://fxts.dev/docs/reject | reject} + */ + reject(f: (a: A) => unknown): FxAsyncIterable { + return new FxAsyncIterable(reject(f, this.asyncIterable)); + } + + /** + * Returns AsyncIterable that taken the first argument `l` values from asyncIterable + * + * see {@link https://fxts.dev/docs/take | take} + */ + take(n: number): FxAsyncIterable { + return new FxAsyncIterable(take(n, this.asyncIterable)); + } + + /** + * Returns AsyncIterable that taken values until truthy when given `f` is applied. + * + * see {@link https://fxts.dev/docs/takeUntil | takeUntil} + */ + takeUntil(f: (a: A) => unknown): FxAsyncIterable { + return new FxAsyncIterable(takeUntil(f, this.asyncIterable)); + } + + /** + * Returns AsyncIterable that taken values as long as each value satisfies the give `f`. + * + * see {@link https://fxts.dev/docs/takeWhile | takeWhile} + */ + takeWhile(f: (a: A) => unknown): FxAsyncIterable { + return new FxAsyncIterable(takeWhile(f, this.asyncIterable)); + } + + /** + * Iterate over an input list, + * calling a provided `f` for each element in the AsyncIterable. + * + * see {@link https://fxts.dev/docs/peek | peek} + */ + peek(f: (a: A) => unknown): FxAsyncIterable { + return new FxAsyncIterable(peek(f, this.asyncIterable)); + } + + /** + * Returns all but the first `length` elements of the given asyncIterable. + * + * see {@link https://fxts.dev/docs/drop | drop} + */ + drop(length: number): FxAsyncIterable { + return new FxAsyncIterable(drop(length, this.asyncIterable)); + } + + /** + * Returns AsyncIterable of the given elements from startIndex(inclusive) to endIndex(exclusive). + * + * see {@link https://fxts.dev/docs/slice | slice} + */ + slice(start: number, end?: number): FxAsyncIterable { + return isUndefined(end) + ? new FxAsyncIterable(slice(start, this.asyncIterable)) + : new FxAsyncIterable(slice(start, end, this.asyncIterable)); + } + + /** + * + * `chain` allows you to use functions that are not provided in method chaining. + * The functions available for the `chain` argument return an iterable. + * + * @example + * ``` + * await fx(toAsync(range(1, 4))) + * .chain(append(4)) + * .chain(append(5)) + * .toArray(); // [1, 2, 3, 4, 5] + * ``` + */ + chain( + f: (asyncIterable: AsyncIterable) => AsyncIterable>, + ): FxAsyncIterable { + return new FxAsyncIterable(f(this.asyncIterable)); + } + + /** + * Concurrent is used to balance the load of multiple asynchronous requests. + * The first argument receives a number that controls the number of loads, and the second argument is an AsyncIterable. + * + * see {@link https://fxts.dev/docs/concurrent | concurrent} + */ + concurrent(length: number) { + return new FxAsyncIterable(concurrent(length, this.asyncIterable)); + } + + async consume() { + return consume(this.asyncIterable); + } + + /** + * Splits AsyncIterable into sets, grouped by the result of running each value through `f`. + * + * see {@link https://fxts.dev/docs/groupBy | groupBy} + */ + async groupBy(f: (a: A) => Key) { + return groupBy(f, this.asyncIterable); + } + + /** + * Given `f` that generates a key, + * turns a list of objects into an object indexing the objects by the given key. + * Note that if multiple objects generate the same value for the indexing key only the last value will be included in the generated object. + * + * see {@link https://fxts.dev/docs/indexBy | indexBy} + */ + async indexBy(f: (a: A) => Key) { + return indexBy(f, this.asyncIterable); + } + + /** + * Returns true if any of the values in AsyncIterable pass `f` truth test + * + * see {@link https://fxts.dev/docs/some | some} + */ + async some(f: (a: A) => unknown): Promise { + return some(f, this.asyncIterable); + } + + /** + * Returns true if all of the values in AsyncIterable pass the `f` truth test. + * + * see {@link https://fxts.dev/docs/every | every} + */ + async every(f: (a: A) => unknown): Promise { + return every(f, this.asyncIterable); + } + + /** + * Returns all elements in the given iterable into a string separated by separator. + * + * see {@link https://fxts.dev/docs/join | join} + */ + async join(sep: string): Promise { + return join(sep, this.asyncIterable); + } + + /** + * Looks through each value in AsyncIterable, returning the first one that passes a truth test `f`, + * or `undefined` if no value passes the test. + * + * see {@link https://fxts.dev/docs/find | find} + */ + async find(f: (a: A) => unknown): Promise { + return find(f, this.asyncIterable); + } + + /** + * Returns the index of the first element of AsyncIterable which matches f, or -1 if no element matches. + * + * see {@link https://fxts.dev/docs/findIndex | findIndex} + */ + async findIndex(f: (a: A) => unknown): Promise { + return findIndex(f, this.asyncIterable); + } + + /** + * Also known as foldl, this method boils down a list of values into a single value. + * + * see {@link https://fxts.dev/docs/reduce | reduce} + */ + async reduce( + f: SyncReducer, A>, + seed?: B, + ): Promise> { + return isUndefined(seed) + ? reduce(f, this.asyncIterable) + : reduce(f, seed as any, this.asyncIterable); + } + + /** + * Iterates over AsyncIterable, applying each in turn to `f`. + * + * see {@link https://fxts.dev/docs/each | each} + */ + async each(f: (a: A) => unknown): Promise { + return each(f, this.asyncIterable); + } + + /** + * Takes item from AsyncIterable and returns an array. + * + * see {@link https://fxts.dev/docs/toArray | toArray} + */ + async toArray(): Promise>> { + const array: Awaited[] = []; + for await (const a of this.asyncIterable) { + array.push(a); + } + return array; + } +} + +export class FxIterable { + private iterable: Iterable; + + constructor(iterable: Iterable) { + this.iterable = iterable; + } + + private [Symbol.iterator]() { + return this.iterable; + } + + /** + * Returns Iterable of values by running each applying `f`. + * + * see {@link https://fxts.dev/docs/map | map} + */ + map(f: (a: A) => B): FxIterable { + return new FxIterable(map(f, this.iterable)); + } + + /** + * Returns flattened Iterable of values by running each element + * flattening the mapped results. + * + * see {@link https://fxts.dev/docs/flatMap | flatMap} + */ + flatMap(f: (a: A) => B) { + return new FxIterable(flatMap(f, this.iterable)); + } + + /** + * Returns flattened Iterable. + * If first argument is number, more perform flatten `flat(2, [[[1,2]]]) // [1,2]` + * + * see {@link https://fxts.dev/docs/flat | flat} + */ + flat(depth?: number) { + return new FxIterable(flat(this.iterable, depth)); + } + + /** + * Return Iterable of all elements `f` returns truthy for + * + * see {@link https://fxts.dev/docs/filter | filter} + */ + filter(f: (a: A) => unknown): FxIterable { + return new FxIterable(filter(f, this.iterable)); + } + + /** + * The opposite of filter method + * Iterable of all elements `f` returns falsy for + * + * see {@link https://fxts.dev/docs/reject | reject} + */ + reject(f: (a: A) => unknown): FxIterable { + return new FxIterable(reject(f, this.iterable)); + } + + /** + * Returns Iterable that taken the first argument `l` values from iterable + * + * see {@link https://fxts.dev/docs/take | take} + */ + take(n: number): FxIterable { + return new FxIterable(take(n, this.iterable)); + } + + /** + * Returns Iterable that taken values until truthy when given `f` is applied. + * + * see {@link https://fxts.dev/docs/takeUntil | takeUntil} + */ + takeUntil(f: (a: A) => unknown): FxIterable { + return new FxIterable(takeUntil(f, this.iterable)); + } + + /** + * Returns Iterable that taken values as long as each value satisfies the give `f`. + * + * see {@link https://fxts.dev/docs/takeWhile | takeWhile} + */ + takeWhile(f: (a: A) => unknown): FxIterable { + return new FxIterable(takeWhile(f, this.iterable)); + } + + /** + * Iterate over an input list, + * calling a provided `f` for each element in the Iterable. + * + * see {@link https://fxts.dev/docs/peek | peek} + */ + peek(f: (a: A) => unknown): FxIterable { + return new FxIterable(peek(f, this.iterable)); + } + + /** + * Returns all but the first `length` elements of the given iterable. + * + * see {@link https://fxts.dev/docs/drop | drop} + */ + drop(length: number): FxIterable { + return new FxIterable(drop(length, this.iterable)); + } + + /** + * Returns Iterable of the given elements from startIndex(inclusive) to endIndex(exclusive). + * + * see {@link https://fxts.dev/docs/slice | slice} + */ + slice(start: number, end?: number): FxIterable { + return isUndefined(end) + ? new FxIterable(slice(start, this.iterable)) + : new FxIterable(slice(start, end, this.iterable)); + } + + /** + * + * `chain` allows you to use functions that are not provided in method chaining. + * The functions available for the `chain` argument return an asyncIterable. + * + * @example + * ``` + * fx(range(1, 4)) + * .chain(append(4)) + * .chain(append(5)) + * .toArray(); // [1, 2, 3, 4, 5] + * ``` + */ + chain(f: (iterable: Iterable) => Iterable): FxIterable { + return new FxIterable(f(this.iterable)); + } + + /** + * Returns AsyncIterable, `toAsync` used when you want to handle Promise values inside Iterable. + * + * see {@link https://fxts.dev/docs/toAsync | toAsync} + */ + toAsync(): FxAsyncIterable { + return new FxAsyncIterable(toAsync(this.iterable)); + } + + /** + * Splits Iterable into sets, grouped by the result of running each value through `f`. + * + * see {@link https://fxts.dev/docs/groupBy | groupBy} + */ + groupBy(f: (a: A) => Key) { + return groupBy(f, this.iterable); + } + + /** + * Given `f` that generates a key, + * turns a list of objects into an object indexing the objects by the given key. + * Note that if multiple objects generate the same value for the indexing key only the last value will be included in the generated object. + * + * see {@link https://fxts.dev/docs/indexBy | indexBy} + */ + indexBy(f: (a: A) => Key) { + return indexBy(f, this.iterable); + } + + /** + * Returns true if any of the values in AsyncIterable pass `f` truth test + * + * see {@link https://fxts.dev/docs/some | some} + */ + some(f: (a: A) => unknown): boolean { + return some(f, this.iterable); + } + + /** + * Returns true if all of the values in AsyncIterable pass the `f` truth test. + * + * see {@link https://fxts.dev/docs/every | every} + */ + every(f: (a: A) => unknown): boolean { + return every(f, this.iterable); + } + + /** + * Returns all elements in the given iterable into a string separated by separator. + * + * see {@link https://fxts.dev/docs/join | join} + */ + join(sep: string): string { + return join(sep, this.iterable); + } + + /** + * Looks through each value in AsyncIterable, returning the first one that passes a truth test `f`, + * or `undefined` if no value passes the test. + * + * see {@link https://fxts.dev/docs/find | find} + */ + find(f: (a: A) => unknown): A | undefined { + return find(f, this.iterable); + } + + /** + * Returns the index of the first element of AsyncIterable which matches f, or -1 if no element matches. + * + * see {@link https://fxts.dev/docs/findIndex | findIndex} + */ + findIndex(f: (a: A) => unknown): number { + return findIndex(f, this.iterable); + } + + /** + * Also known as foldl, this method boils down a list of values into a single value. + * + * see {@link https://fxts.dev/docs/reduce | reduce} + */ + reduce(f: SyncReducer, A>, seed?: B): Cast { + return isUndefined(seed) + ? reduce(f, this.iterable) + : reduce(f, seed as any, this.iterable); + } + + /** + * Iterates over Iterable, applying each in turn to `f`. + * + * see {@link https://fxts.dev/docs/each | each} + */ + each(f: (a: A) => unknown): void { + return each(f, this.iterable); + } + + /** + * Takes item from Iterable and returns an array. + * + * see {@link https://fxts.dev/docs/toArray | toArray} + */ + toArray(): Array { + return Array.from(this.iterable); + } + + toIterator(): Array { + return Array.from(this.iterable); + } +} + +/** + * `fx` allows functions provided by existing `fxts` to be used in a method chaining. + * Not all functions are provided as methods and can be connected through `chain` if necessary. + * + * see {@link https://fxts.dev/docs/method-chaining | guide} + * + * @example + * ```ts + * const syncArr1 = fx([1, 2, 3, 4]) + * .map((a) => a + 10) + * .toArray(); // [11, 12, 13, 14] + * + * // If you want to use another function that is not provided for the method, use `chain`. + * const syncArr2 = fx([1, 2, 3, 4]) + * .chain(append(5)) + * .map((a) => a + 10) + * .toArray(); // [11, 12, 13, 14, 15] + * + * const asyncArr1 = await fx([1, 2, 3, 4]) + * .toAsync() + * .map((a) => a + 10) + * .toArray(); // [11, 12, 13, 14] + * + * const asyncArr2 = await fx(toAsync([1, 2, 3, 4])); + * .map((a) => a + 10) + * .toArray(); // [11, 12, 13, 14] + * ``` + */ +function fx | AsyncIterable>( + a: T, +): T extends Iterable + ? FxIterable> + : FxAsyncIterable> { + if (isAsyncIterable(a)) { + return new FxAsyncIterable(a) as any; + } else if (isIterable(a)) { + return new FxIterable(a) as any; + } + + throw new TypeError(`'fx' must be type of Iterable or AsyncIterable`); +} + +export default fx; diff --git a/src/Lazy/index.ts b/src/Lazy/index.ts index 222cb7f0..2f67d87d 100644 --- a/src/Lazy/index.ts +++ b/src/Lazy/index.ts @@ -15,6 +15,7 @@ import entries from "./entries"; import filter from "./filter"; import flat from "./flat"; import flatMap from "./flatMap"; +import fx from "./fx"; import intersection from "./intersection"; import intersectionBy from "./intersectionBy"; import keys from "./keys"; @@ -60,6 +61,7 @@ export { filter, flat, flatMap, + fx, intersection, intersectionBy, keys, diff --git a/test/Lazy/append.spec.ts b/test/Lazy/append.spec.ts index ee94019a..88caee10 100644 --- a/test/Lazy/append.spec.ts +++ b/test/Lazy/append.spec.ts @@ -2,6 +2,7 @@ import { append, concurrent, delay, + fx, map, pipe, range, @@ -25,6 +26,11 @@ describe("append", function () { const res = pipe(range(1, 4), append(4), append(5), append(6), toArray); expect(res).toEqual([1, 2, 3, 4, 5, 6]); }); + + it("should be able to be used chaining method with chain in the `fx`", function () { + const res = fx(range(1, 4)).chain(append(4)).chain(append(5)).toArray(); + expect(res).toEqual([1, 2, 3, 4, 5]); + }); }); describe("async", function () { @@ -42,6 +48,15 @@ describe("append", function () { expect(res).toEqual([1, 2, 3, 4]); }); + it("should be able to be used chaining method with chain in the `fx`", async function () { + const res = await fx(range(1, 4)) + .toAsync() + .chain(append(4)) + .chain(append(5)) + .toArray(); + expect(res).toEqual([1, 2, 3, 4, 5]); + }); + it("should be appended sequentially", async function () { let chainedPromise: Promise = Promise.resolve(); const res = await pipe( diff --git a/test/Lazy/concurrent.spec.ts b/test/Lazy/concurrent.spec.ts index 95cf033c..65ad9887 100644 --- a/test/Lazy/concurrent.spec.ts +++ b/test/Lazy/concurrent.spec.ts @@ -2,6 +2,7 @@ import { concurrent, delay, filter, + fx, map, peek, pipe, @@ -52,6 +53,14 @@ describe("concurrent", function () { expect(arr).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); }, 550); + it("should be able to be used as a chaining method in the `fx`", async function () { + const arr = await fx(toAsync(range(1, 11))) + .map((a) => delay(100, a)) + .concurrent(2) + .toArray(); + expect(arr).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + }, 550); + it("should be affected only one concurrent below it, when nested concurrent", async function () { let concurrent10Count = 0; let concurrent2Count = 0; diff --git a/test/Lazy/drop.spec.ts b/test/Lazy/drop.spec.ts index 0b8265f4..a11adb75 100644 --- a/test/Lazy/drop.spec.ts +++ b/test/Lazy/drop.spec.ts @@ -3,6 +3,7 @@ import { delay, drop, filter, + fx, map, pipe, toArray, @@ -33,6 +34,16 @@ describe("drop", function () { expect(res).toEqual([16, 18]); }); + + it("should be able to be used as a chaining method in the `fx`", function () { + const res = fx([1, 2, 3, 4, 5, 6, 7, 8]) + .map((a) => a + 10) + .filter((a) => a % 2 === 0) + .drop(2) + .toArray(); + + expect(res).toEqual([16, 18]); + }); }); describe("async", function () { @@ -60,6 +71,16 @@ describe("drop", function () { expect(res).toEqual([16, 18]); }); + it("should be able to be used as a chaining method in the `fx`", async function () { + const res = await fx(toAsync([1, 2, 3, 4, 5, 6, 7, 8])) + .map((a) => a + 10) + .filter((a) => a % 2 === 0) + .drop(2) + .toArray(); + + expect(res).toEqual([16, 18]); + }); + it("should be discarded elements by length concurrently", async function () { const res = await pipe( toAsync([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), diff --git a/test/Lazy/filter.spec.ts b/test/Lazy/filter.spec.ts index 64800f23..9ec9d251 100644 --- a/test/Lazy/filter.spec.ts +++ b/test/Lazy/filter.spec.ts @@ -3,6 +3,7 @@ import { concurrent, delay, filter, + fx, map, pipe, range, @@ -54,6 +55,14 @@ describe("filter", function () { expect(res).toEqual([2, 4]); }); + + it("should be able to be used as a chaining method in the `fx`", function () { + const res = fx([1, 2, 3, 4]) + .filter((a) => a % 2 === 0) + .toArray(); + + expect(res).toEqual([2, 4]); + }); }); describe("async", function () { @@ -263,6 +272,14 @@ describe("filter", function () { expect(res).toEqual([2, 4]); }); + it("should be able to be used as a chaining method in the `fx`", async function () { + const res = await fx(toAsync([1, 2, 3, 4])) + .filter((a) => a % 2 === 0) + .toArray(); + + expect(res).toEqual([2, 4]); + }); + it("should be consumed 'AsyncIterable' as many times as called with 'next'", async function () { const iterator = toAsync(range(1, 21)); const res = pipe( diff --git a/test/Lazy/flat.spec.ts b/test/Lazy/flat.spec.ts index cb685004..9b06c7fe 100644 --- a/test/Lazy/flat.spec.ts +++ b/test/Lazy/flat.spec.ts @@ -4,6 +4,7 @@ import { delay, filter, flat, + fx, map, pipe, range, @@ -37,6 +38,15 @@ describe("flat", function () { expect(res).toEqual([11, 12, 13, 14, 15]); }); + + it("should be able to be used as a chaining method in the `fx`", function () { + const res = fx([1, 2, 3, [4, 5]]) + .flat() + .map((a) => a + 10) + .toArray(); + + expect(res).toEqual([11, 12, 13, 14, 15]); + }); }); describe("async", function () { @@ -64,6 +74,15 @@ describe("flat", function () { expect(res).toEqual([11, 12, 13, 14, 15]); }); + it("should be able to be used as a chaining method in the `fx`", async function () { + const res = await fx(toAsync([1, 2, 3, [4, 5]])) + .flat() + .map((a) => a + 10) + .toArray(); + + expect(res).toEqual([11, 12, 13, 14, 15]); + }); + it("should be flattened concurrently", async function () { const iterator = toAsync([ [1], diff --git a/test/Lazy/flatMap.spec.ts b/test/Lazy/flatMap.spec.ts index 59cabcbe..14b31008 100644 --- a/test/Lazy/flatMap.spec.ts +++ b/test/Lazy/flatMap.spec.ts @@ -1,4 +1,4 @@ -import { flatMap, map, pipe, toArray, toAsync } from "../../src/index"; +import { flatMap, fx, map, pipe, toArray, toAsync } from "../../src/index"; import { Concurrent } from "../../src/Lazy/concurrent"; import { generatorMock } from "../utils"; @@ -25,6 +25,15 @@ describe("flatMap", function () { expect(res).toEqual(["IT", "IS", "A", "GOOD", "DAY"]); }); + + it("should be able to be used as a chaining method in the `fx`", function () { + const res = fx(["It is", "a good", "day"]) + .flatMap((s) => s.split(" ")) + .map((a) => a.toUpperCase()) + .toArray(); + + expect(res).toEqual(["IT", "IS", "A", "GOOD", "DAY"]); + }); }); describe("async", function () { @@ -48,6 +57,15 @@ describe("flatMap", function () { expect(res).toEqual(["IT", "IS", "A", "GOOD", "DAY"]); }); + it("should be able to be used as a chaining method in the `fx`", async function () { + const res = await fx(toAsync(["It is", "a good", "day"])) + .flatMap((s) => s.split(" ")) + .map((a) => a.toUpperCase()) + .toArray(); + + expect(res).toEqual(["IT", "IS", "A", "GOOD", "DAY"]); + }); + it("should be passed concurrent object when job works concurrently", async function () { const mock = generatorMock(); const iter = flatMap((a) => a, mock); diff --git a/test/Lazy/fx.spec.ts b/test/Lazy/fx.spec.ts new file mode 100644 index 00000000..bc6174f1 --- /dev/null +++ b/test/Lazy/fx.spec.ts @@ -0,0 +1,27 @@ +import { fx, toAsync } from "../../src"; + +describe("fx", function () { + describe("sync", function () { + it("handle fx iterable", function () { + const res = fx([1, 2, 3, 4, 5]) + .map((a) => a + 10) + .toArray(); + expect(res).toEqual([11, 12, 13, 14, 15]); + }); + }); + + describe("async", () => { + it("handle fx asyncIterable", async function () { + const res1 = await fx(toAsync([1, 2, 3, 4, 5])) + .map(async (a) => a + 10) + .toArray(); + expect(res1).toEqual([11, 12, 13, 14, 15]); + + const res2 = await fx([1, 2, 3, 4, 5]) + .toAsync() + .map(async (a) => a + 10) + .toArray(); + expect(res2).toEqual([11, 12, 13, 14, 15]); + }); + }); +}); diff --git a/test/Lazy/map.spec.ts b/test/Lazy/map.spec.ts index 7ce468a6..f2ae9f19 100644 --- a/test/Lazy/map.spec.ts +++ b/test/Lazy/map.spec.ts @@ -1,5 +1,5 @@ import { AsyncFunctionException } from "../../src/_internal/error"; -import { map, pipe, range, toArray, toAsync } from "../../src/index"; +import { fx, map, pipe, range, toArray, toAsync } from "../../src/index"; import { Concurrent } from "../../src/Lazy/concurrent"; import { generatorMock } from "../utils"; @@ -32,6 +32,16 @@ describe("map", function () { expect(res).toEqual(["1", "2", "3", "4"]); }); + + it("should be able to be used as a chaining method in the `fx`", function () { + const res = fx([1, 2, 3, 4]) + .map((a) => a) + .map((a) => String(a)) + .map((a) => a) + .toArray(); + + expect(res).toEqual(["1", "2", "3", "4"]); + }); }); describe("async", function () { @@ -83,6 +93,15 @@ describe("map", function () { expect(res).toEqual(["1", "2", "3", "4"]); }); + it("should be able to be used as a chaining method in the `fx`", async function () { + const res = await fx(toAsync([1, 2, 3, 4])) + .map((a) => Promise.resolve(a)) + .map((a) => String(a)) + .toArray(); + + expect(res).toEqual(["1", "2", "3", "4"]); + }); + it("should be passed concurrent object when job works concurrently", async function () { const mock = generatorMock(); const iter = map((a) => a, mock); diff --git a/test/Lazy/peek.spec.ts b/test/Lazy/peek.spec.ts index 88acab4b..b86b33fc 100644 --- a/test/Lazy/peek.spec.ts +++ b/test/Lazy/peek.spec.ts @@ -1,4 +1,4 @@ -import { map, peek, pipe, toArray, toAsync } from "../../src/index"; +import { fx, map, peek, pipe, toArray, toAsync } from "../../src/index"; import { Concurrent } from "../../src/Lazy/concurrent"; import { generatorMock } from "../utils"; @@ -27,6 +27,18 @@ describe("peek", function () { expect(res).toEqual([21, 22, 23, 24]); }); + it("should be able to be used as a chaining method in the `fx`", function () { + let sum = 0; + const res = fx([1, 2, 3, 4]) + .map((a) => a + 10) + .peek((a) => (sum = sum + a)) + .map((a) => a + 10) + .toArray(); + + expect(sum).toEqual(50); + expect(res).toEqual([21, 22, 23, 24]); + }); + it("should be able to handle an error", function () { const res: number[] = []; expect(() => @@ -70,6 +82,18 @@ describe("peek", function () { expect(res).toEqual([21, 22, 23, 24]); }); + it("should be able to be used as a chaining method in the `fx`", async function () { + let sum = 0; + const res = await fx(toAsync([1, 2, 3, 4])) + .map((a) => a + 10) + .peek((a) => (sum = sum + a)) + .map((a) => a + 10) + .toArray(); + + expect(sum).toEqual(50); + expect(res).toEqual([21, 22, 23, 24]); + }); + it("should be able to handle an error", async function () { const res: number[] = []; diff --git a/test/Lazy/slice.spec.ts b/test/Lazy/slice.spec.ts index a6023bae..cb60e583 100644 --- a/test/Lazy/slice.spec.ts +++ b/test/Lazy/slice.spec.ts @@ -1,4 +1,4 @@ -import { pipe, slice, toArray, toAsync } from "../../src"; +import { fx, pipe, slice, toArray, toAsync } from "../../src"; import { Concurrent } from "../../src/Lazy/concurrent"; import { generatorMock } from "../utils"; @@ -41,6 +41,14 @@ describe("slice", function () { const res2 = pipe([1, 2, 3, 4, 5], slice(1, 3), toArray); expect(res2).toEqual([2, 3]); }); + + it("should be able to be used as a chaining method in the `fx`", function () { + const res1 = fx([1, 2, 3, 4, 5]).slice(2).toArray(); + expect(res1).toEqual([3, 4, 5]); + + const res2 = fx([1, 2, 3, 4, 5]).slice(1, 3).toArray(); + expect(res2).toEqual([2, 3]); + }); }); describe("async", function () { @@ -84,6 +92,14 @@ describe("slice", function () { expect(res2).toEqual([2, 3]); }); + it("should be able to be used as a chaining method in the `fx`", async function () { + const res1 = await fx([1, 2, 3, 4, 5]).toAsync().slice(2).toArray(); + expect(res1).toEqual([3, 4, 5]); + + const res2 = await fx([1, 2, 3, 4, 5]).toAsync().slice(1, 3).toArray(); + expect(res2).toEqual([2, 3]); + }); + it("should be passed concurrent object when job works concurrently", async function () { const mock = generatorMock(); const iter = slice(1, 2, mock); diff --git a/test/Lazy/take.spec.ts b/test/Lazy/take.spec.ts index 7c96fa82..cd23419c 100644 --- a/test/Lazy/take.spec.ts +++ b/test/Lazy/take.spec.ts @@ -1,6 +1,7 @@ import { delay, filter, + fx, map, pipe, range, @@ -51,6 +52,16 @@ describe("take", function () { expect(res1).toEqual([12, 14]); }); + it("should be able to be used as a chaining method in the `fx`", function () { + const res1 = fx([1, 2, 3, 4]) + .map((a) => a + 10) + .filter((a) => a % 2 === 0) + .take(2) + .toArray(); + + expect(res1).toEqual([12, 14]); + }); + it("should be able to take the rest element", async function () { const iter = take(5, range(1, 11)); iter.next(); @@ -101,6 +112,16 @@ describe("take", function () { expect(res1).toEqual([12, 14]); }); + + it("should be able to be used as a chaining method in the `fx`", async function () { + const res1 = await fx(toAsync([1, 2, 3, 4])) + .map((a) => a + 10) + .filter((a) => a % 2 === 0) + .take(2) + .toArray(); + + expect(res1).toEqual([12, 14]); + }); it("should be able to take the element concurrently", async function () { const asyncIterator = take( 3, diff --git a/test/Lazy/takeUntil.spec.ts b/test/Lazy/takeUntil.spec.ts index 8deeddd0..a1720b2c 100644 --- a/test/Lazy/takeUntil.spec.ts +++ b/test/Lazy/takeUntil.spec.ts @@ -3,6 +3,7 @@ import { concurrent, delay, filter, + fx, map, peek, pipe, @@ -46,6 +47,16 @@ describe("takeUntil", function () { expect(res).toEqual([12, 14]); }); + + it("should be able to be used as a chaining method in the `fx`", function () { + const res = fx([1, 2, 3, 4]) + .map((a) => a + 10) + .filter((a) => a % 2 === 0) + .takeUntil((a) => a > 12) + .toArray(); + + expect(res).toEqual([12, 14]); + }); }); describe("async", function () { @@ -96,6 +107,16 @@ describe("takeUntil", function () { expect(res).toEqual([12, 14]); }); + it("should be able to be used as a chaining method in the `fx`", async function () { + const res = await fx(toAsync([1, 2, 3, 4])) + .map((a) => a + 10) + .filter((a) => a % 2 === 0) + .takeUntil((a) => a > 12) + .toArray(); + + expect(res).toEqual([12, 14]); + }); + it("should be able to take the element", async function () { const fn = jest.fn(); const res = await pipe( diff --git a/test/Lazy/takeWhile.spec.ts b/test/Lazy/takeWhile.spec.ts index da3c5138..6cde1e70 100644 --- a/test/Lazy/takeWhile.spec.ts +++ b/test/Lazy/takeWhile.spec.ts @@ -3,6 +3,7 @@ import { concurrent, delay, filter, + fx, map, pipe, range, @@ -45,6 +46,16 @@ describe("takeWhile", function () { expect(res).toEqual([12, 14, 16, 18]); }); + + it("should be able to be used as a chaining method in the `fx`", function () { + const res = fx(range(1, 20)) + .map((a) => a + 10) + .filter((a) => a % 2 === 0) + .takeWhile((a) => a < 20) + .toArray(); + + expect(res).toEqual([12, 14, 16, 18]); + }); }); describe("async", function () { @@ -95,6 +106,17 @@ describe("takeWhile", function () { expect(res).toEqual([12, 14, 16, 18]); }); + it("should be able to be used as a chaining method in the `fx`", async function () { + const res = await fx(range(1, 20)) + .toAsync() + .map((a) => a + 10) + .filter((a) => a % 2 === 0) + .takeWhile((a) => a < 20) + .toArray(); + + expect(res).toEqual([12, 14, 16, 18]); + }); + it("should be consumed 'AsyncIterable' as many times as called with 'next'", async function () { const res = pipe( toAsync(range(1, 500)), diff --git a/test/each.spec.ts b/test/each.spec.ts index 37e75e1a..57af41d8 100644 --- a/test/each.spec.ts +++ b/test/each.spec.ts @@ -1,4 +1,4 @@ -import { each, map, pipe, range, toAsync } from "../src"; +import { each, fx, map, pipe, range, toAsync } from "../src"; describe("each", function () { describe("sync", function () { @@ -24,6 +24,16 @@ describe("each", function () { ); expect(acc).toEqual(50); }); + + it("should be able to be used as a chaining method in the `fx`", function () { + let acc = 0; + fx([1, 2, 3, 4]) + .map((a) => a + 10) + .each((a) => { + acc += a; + }); + expect(acc).toEqual(50); + }); }); describe("async", function () { @@ -101,5 +111,15 @@ describe("each", function () { ); expect(res2).toEqual(50); }); + + it("should be able to be used as a chaining method in the `fx`", async function () { + let acc = 0; + await fx(toAsync([1, 2, 3, 4])) + .map((a) => a + 10) + .each((a) => { + acc += a; + }); + expect(acc).toEqual(50); + }); }); }); diff --git a/test/every.spec.ts b/test/every.spec.ts index a8b1d590..fdbeb929 100644 --- a/test/every.spec.ts +++ b/test/every.spec.ts @@ -1,4 +1,4 @@ -import { every, filter, map, pipe, toAsync } from "../src"; +import { every, filter, fx, map, pipe, toAsync } from "../src"; import { AsyncFunctionException } from "../src/_internal/error"; describe("every", function () { @@ -39,6 +39,23 @@ describe("every", function () { expect(res3).toEqual(false); }); + it("should be able to be used as a chaining method in the `fx`", function () { + const res1 = fx([1, 2, 3, 4, 5, 6, 7, 8, 9]) + .filter((a) => a % 2 === 0) + .every((a) => a % 2 === 0); + expect(res1).toEqual(true); + + const res2 = fx([1, 2, 3, 4, 5, 6, 7, 8, 9]) + .map((a) => a + 10) + .every((a) => a > 10); + expect(res2).toEqual(true); + + const res3 = fx([1, 2, 3, 4, 5, 6, 7, 8, 9]) + .map((a) => a + 10) + .every((a) => a < 10); + expect(res3).toEqual(false); + }); + it("should throw an error when the callback is asynchronous", function () { expect(() => pipe( @@ -86,4 +103,21 @@ describe("every", function () { ); expect(res3).toEqual(false); }); + + it("should be able to be used as a chaining method in the `fx`", async function () { + const res1 = await fx(toAsync([1, 2, 3, 4, 5, 6, 7, 8, 9])) + .filter((a) => a % 2 === 0) + .every((a) => a % 2 === 0); + expect(res1).toEqual(true); + + const res2 = await fx(toAsync([1, 2, 3, 4, 5, 6, 7, 8, 9])) + .map((a) => a + 10) + .every((a) => a > 10); + expect(res2).toEqual(true); + + const res3 = await fx(toAsync([1, 2, 3, 4, 5, 6, 7, 8, 9])) + .map((a) => a + 10) + .every((a) => a < 10); + expect(res3).toEqual(false); + }); }); diff --git a/test/find.spec.ts b/test/find.spec.ts index f70ff03c..bfb65d4d 100644 --- a/test/find.spec.ts +++ b/test/find.spec.ts @@ -1,4 +1,4 @@ -import { filter, find, map, pipe, toAsync } from "../src"; +import { filter, find, fx, map, pipe, toAsync } from "../src"; import { AsyncFunctionException } from "../src/_internal/error"; import type Arrow from "../src/types/Arrow"; @@ -25,6 +25,14 @@ describe("find", function () { expect(res1).toEqual(14); }); + it("should be able to be used as a chaining method in the `fx`", function () { + const res1 = fx([1, 2, 3, 4]) + .map((a) => a + 10) + .filter((a) => a % 2 === 0) + .find((a) => a === 14); + expect(res1).toEqual(14); + }); + it("should throw an error when the callback is asynchronous", function () { const res = () => pipe( @@ -56,4 +64,12 @@ describe("find", function () { ); expect(res1).toEqual(14); }); + + it("should be able to be used as a chaining method in the `fx`", async function () { + const res1 = await fx(toAsync([1, 2, 3, 4])) + .map((a) => a + 10) + .filter((a) => a % 2 === 0) + .find((a) => a === 14); + expect(res1).toEqual(14); + }); }); diff --git a/test/findIndex.spec.ts b/test/findIndex.spec.ts index e11a0e98..26c6f77b 100644 --- a/test/findIndex.spec.ts +++ b/test/findIndex.spec.ts @@ -1,4 +1,4 @@ -import { filter, findIndex, map, pipe, toAsync } from "../src"; +import { filter, findIndex, fx, map, pipe, toAsync } from "../src"; import { AsyncFunctionException } from "../src/_internal/error"; import type Arrow from "../src/types/Arrow"; @@ -25,6 +25,14 @@ describe("findIndex", function () { expect(res1).toEqual(1); }); + it("should be able to be used as a chaining method in the `fx`", function () { + const res1 = fx([1, 2, 3, 4]) + .map((a) => a + 10) + .filter((a) => a % 2 === 0) + .findIndex((a) => a === 14); + expect(res1).toEqual(1); + }); + it("should throw an error when the callback is asynchronous", function () { const res = () => pipe( @@ -56,4 +64,12 @@ describe("findIndex", function () { ); expect(res1).toEqual(1); }); + + it("should be able to be used as a chaining method in the `fx`", async function () { + const res1 = await fx(toAsync([1, 2, 3, 4])) + .map((a) => a + 10) + .filter((a) => a % 2 === 0) + .findIndex((a) => a === 14); + expect(res1).toEqual(1); + }); }); diff --git a/test/groupBy.spec.ts b/test/groupBy.spec.ts index 39ba5d53..f42aecd5 100644 --- a/test/groupBy.spec.ts +++ b/test/groupBy.spec.ts @@ -1,4 +1,4 @@ -import { filter, groupBy, pipe, toAsync } from "../src"; +import { filter, fx, groupBy, pipe, toAsync } from "../src"; import { AsyncFunctionException } from "../src/_internal/error"; type Obj = { @@ -52,6 +52,14 @@ describe("groupBy", function () { expect(res).toEqual(then2); }); + it("should be able to be used as a chaining method in the `fx`", function () { + const res = fx(given) + .filter((a) => a.category !== "clothes") + .groupBy((a) => a.category); + + expect(res).toEqual(then2); + }); + it("should throw an error when the callback is asynchronous", function () { const res = () => pipe( @@ -78,5 +86,13 @@ describe("groupBy", function () { expect(res).toEqual(then2); }); + + it("should be able to be used as a chaining method in the `fx`", async function () { + const res = await fx(toAsync(given)) + .filter((a) => a.category !== "clothes") + .groupBy((a) => a.category); + + expect(res).toEqual(then2); + }); }); }); diff --git a/test/indexBy.spec.ts b/test/indexBy.spec.ts index b9cca15c..775b09d9 100644 --- a/test/indexBy.spec.ts +++ b/test/indexBy.spec.ts @@ -1,4 +1,4 @@ -import { filter, indexBy, pipe, toAsync } from "../src"; +import { filter, fx, indexBy, pipe, toAsync } from "../src"; import { AsyncFunctionException } from "../src/_internal/error"; type Obj = { @@ -36,6 +36,13 @@ describe("indexBy", function () { expect(res).toEqual(then2); }); + it("should be able to be used as a chaining method in the `fx`", function () { + const res = fx(given) + .filter((a) => a.category !== "clothes") + .indexBy((a) => a.category); + expect(res).toEqual(then2); + }); + it("should throw an error when the callback is asynchronous", function () { const res = () => pipe( @@ -61,5 +68,12 @@ describe("indexBy", function () { ); expect(res).toEqual(then2); }); + + it("should be able to be used as a chaining method in the `fx`", async function () { + const res = await fx(toAsync(given)) + .filter((a) => a.category !== "clothes") + .indexBy((a) => a.category); + expect(res).toEqual(then2); + }); }); }); diff --git a/test/join.spec.ts b/test/join.spec.ts index de7e86a9..0a58f569 100644 --- a/test/join.spec.ts +++ b/test/join.spec.ts @@ -1,4 +1,4 @@ -import { filter, join, map, pipe, toAsync } from "../src"; +import { filter, fx, join, map, pipe, toAsync } from "../src"; import { asyncEmpty, empty } from "../src/_internal/utils"; describe("join", function () { @@ -28,6 +28,14 @@ describe("join", function () { expect(res).toEqual("12-14-16"); }); + it("should be able to be used as a chaining method in the `fx`", function () { + const res = fx([1, 2, 3, 4, 5, 6, 7]) + .map((a) => a + 10) + .filter((a) => a % 2 === 0) + .join("-"); + expect(res).toEqual("12-14-16"); + }); + it("should return an empty string when it is an empty array", function () { expect(join("~", [])).toEqual(""); }); @@ -54,6 +62,14 @@ describe("join", function () { expect(res).toEqual("12-14-16"); }); + it("should be able to be used as a chaining method in the `fx`", async function () { + const res = await fx(toAsync([1, 2, 3, 4, 5, 6, 7])) + .map((a) => a + 10) + .filter((a) => a % 2 === 0) + .join("-"); + expect(res).toEqual("12-14-16"); + }); + it("should be able to handle an error when asynchronous", async function () { await expect( pipe( diff --git a/test/reduce.spec.ts b/test/reduce.spec.ts index 5c1335ea..28539fd2 100644 --- a/test/reduce.spec.ts +++ b/test/reduce.spec.ts @@ -1,4 +1,4 @@ -import { filter, map, pipe, range, reduce, toAsync } from "../src"; +import { filter, fx, map, pipe, range, reduce, toAsync } from "../src"; const addNumber = (a: number, b: number) => a + b; const addNumberAsync = async (a: number, b: number) => a + b; @@ -30,6 +30,14 @@ describe("reduce", function () { ); expect(res).toEqual(1 + 3 + 5); }); + + it("should be able to be used as a chaining method in the `fx`", function () { + const res = fx(["1", "2", "3", "4", "5"]) + .map((a) => Number(a)) + .filter((a) => a % 2) + .reduce(addNumber); + expect(res).toEqual(1 + 3 + 5); + }); }); describe("async", function () { @@ -109,8 +117,17 @@ describe("reduce", function () { filter((a) => a % 2), reduce(addNumberAsync), ); - expect(res1).toEqual(1 + 3 + 5); - expect(res2).toEqual(1 + 3 + 5); + expect(res1).toEqual(9); + expect(res2).toEqual(9); + }); + + it("should be able to be used as a chaining method in the `fx`", async function () { + const res1 = await fx(toAsync(["1", "2", "3", "4", "5"])) + .map((a) => Number(a)) + .filter((a) => a % 2) + .reduce(addNumber); + + expect(res1).toEqual(9); }); }); }); diff --git a/test/some.spec.ts b/test/some.spec.ts index df3f4aff..dcf6b974 100644 --- a/test/some.spec.ts +++ b/test/some.spec.ts @@ -1,4 +1,4 @@ -import { filter, map, pipe, some, toAsync } from "../src"; +import { filter, fx, map, pipe, some, toAsync } from "../src"; import { AsyncFunctionException } from "../src/_internal/error"; describe("some", function () { @@ -50,6 +50,28 @@ describe("some", function () { expect(res4).toEqual(true); }); + it("should be able to be used as a chaining method in the `fx`", function () { + const res1 = fx([1, 2, 3, 4, 5, 6, 7, 8, 9]) + .filter((a) => a % 2 === 0) + .some((a) => a % 2 === 0); + expect(res1).toEqual(true); + + const res2 = fx([1, 2, 3, 4, 5, 6, 7, 8, 9]) + .map((a) => a + 10) + .some((a) => a > 10); + expect(res2).toEqual(true); + + const res3 = fx([1, 2, 3, 4, 5, 6, 7, 8, 9]) + .map((a) => a + 10) + .some((a) => a < 10); + expect(res3).toEqual(false); + + const res4 = fx([1, 2, 3, 4, 5, 6, 7, 8, 9]) + .map((a) => a + 10) + .some((a) => a < 15); + expect(res4).toEqual(true); + }); + it("should throw an error when the callback is asynchronous", function () { expect(() => pipe( @@ -136,5 +158,27 @@ describe("some", function () { ); expect(res4).toEqual(true); }); + + it("should be able to be used as a chaining method in the `fx`", async function () { + const res1 = await fx(toAsync([1, 2, 3, 4, 5, 6, 7, 8, 9])) + .filter((a) => a % 2 === 0) + .some((a) => a % 2 === 0); + expect(res1).toEqual(true); + + const res2 = await fx(toAsync([1, 2, 3, 4, 5, 6, 7, 8, 9])) + .map((a) => a + 10) + .some((a) => a > 10); + expect(res2).toEqual(true); + + const res3 = await fx(toAsync([1, 2, 3, 4, 5, 6, 7, 8, 9])) + .map((a) => a + 10) + .some((a) => Promise.resolve(a < 10)); + expect(res3).toEqual(false); + + const res4 = await fx(toAsync([1, 2, 3, 4, 5, 6, 7, 8, 9])) + .map((a) => a + 10) + .some((a) => Promise.resolve(a < 15)); + expect(res4).toEqual(true); + }); }); }); diff --git a/type-check/Lazy/fx.test.ts b/type-check/Lazy/fx.test.ts new file mode 100644 index 00000000..ecbf9d1b --- /dev/null +++ b/type-check/Lazy/fx.test.ts @@ -0,0 +1,32 @@ +import { fx, toAsync } from "../../src"; +import type Cast from "../../src/types/Cast"; +import * as Test from "../../src/types/Test"; + +const { checks, check } = Test; + +const res1 = fx([1, 2, 3]); +const res2 = fx([1, 2, 3]) + .map((a) => a) + .toArray(); +const res3 = fx([1, 2, 3]) + .map((a) => String(a)) + .toArray(); +const res4 = fx([1, 2, 3]) + .map((a) => String(a)) + .join(""); + +const res5 = fx([1, 2, 3]).toAsync(); +const res6 = fx(toAsync([1, 2, 3])); +const res7 = fx(toAsync([1, 2, 3])) + .map((a) => a) + .toArray(); + +checks([ + check, typeof res1>, Test.Pass>(), + check(), + check(), + check(), + check, typeof res5>, Test.Pass>(), + check, typeof res5>, Test.Pass>(), + check, Test.Pass>(), +]); From aae86eaa4a682be04d499a953c49659d1c7c8cf0 Mon Sep 17 00:00:00 2001 From: hw Date: Tue, 12 Mar 2024 16:46:16 +0900 Subject: [PATCH 2/3] docs: add `fx` --- website/docs_md/method-chaining.md | 81 ++++++++++++++++++++++++++++++ website/function.json | 1 + website/sidebars.js | 5 ++ website/tsdoc-metadata.json | 2 +- 4 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 website/docs_md/method-chaining.md diff --git a/website/docs_md/method-chaining.md b/website/docs_md/method-chaining.md new file mode 100644 index 00000000..d65ac208 --- /dev/null +++ b/website/docs_md/method-chaining.md @@ -0,0 +1,81 @@ +--- +id: method-chaining +--- + +# Method Chaining + +You can handle Iterable/AsyncIterable through a [pipe](https://fxts.dev/docs/pipe), but `fxts` also provides data change in the form of method chaining. + +```ts +fx([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + .filter((a) => a % 2 === 0) // [0, 2] + .map((a) => a * a) // [0, 4] + .take(2) // [0, 4] + .reduce(sum); // 4 + +fx("abc") + .map((a) => a.toUpperCase()) // ["a", "b"] + .take(2) + .toArray(); // ["a", "b"] +``` + +### Note + +Since `fx` defaults to lazy evaluation, it is not actually evaluated until strict evaluation methods such as `toArray`, `groupBy`, `indexBy`, and `some` are executed. + +For details on lazy evaluation, please refer to https://fxts.dev/docs/lazy-evaluation. + +### Support for handling AsyncIterable + +`fx` can also handle [AsyncIterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncIterator) values. `toAsync` is used in the example below to create an `AsyncIterator` value. + +```ts +await fx(toAsync([1, 2, 3, 4])) + .filter(async (a) => a % 2 === 0) + .map(async (a) => a * a) + .reduce(sum); + +await fx([1, 2, 3, 4]) + .filter((a) => a % 2 === 0) + .toAsync() // if async function returns + .map(async (a) => a * a) + .reduce(sum); +``` + +### Handle Concurrency + +`fx` supports concurrent operation. As we saw in concurrent, concurrent can only be used in asyncIterable. + +For details on handling concurrent with `fxts`, please refer to https://fxts.dev/docs/handle-concurrency + +```ts +/** + * + * evaluation + * ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ + * │ 1 │──│ 2 │──│ 3 │──│ 4 │──│ 5 │──│ 6 │ + * └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ + * map │ │ │ │ │ │ + * concurrent(2) (1) (1) (2) (2) (3) (3) + * │ │ │ │ │ │ + * ▼ ▼ ▼ ▼ ▼ ▼ + */ +await fx(toAsync(range(1, 7))) + // async function returns + .map(async (a) => delay(100, a)) + .concurrent(2) + .consume(); // It takes approximately 300ms. +``` + +### Etc + +`fx` does not provide all the functions of `fxts` as methods. + +If you want to use the `fxts` function which is not provided or additional functions, you can use the `chain` method. + +```ts +fx([1, 2, 3, 4]) + .chain(append(5)) + .map((a) => a + 10) + .toArray(); // [11, 12, 13, 14, 15] +``` diff --git a/website/function.json b/website/function.json index d6d33600..902836aa 100644 --- a/website/function.json +++ b/website/function.json @@ -17,6 +17,7 @@ "filter", "flat", "flatMap", + "fx", "intersection", "intersectionBy", "keys", diff --git a/website/sidebars.js b/website/sidebars.js index 257f7044..39615f04 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -71,6 +71,11 @@ const sidebars = { id: "error-handling", label: "Error handling", }, + { + type: "doc", + id: "method-chaining", + label: "Method Chaining", + }, ], }, { diff --git a/website/tsdoc-metadata.json b/website/tsdoc-metadata.json index 5e804b3b..22735db1 100644 --- a/website/tsdoc-metadata.json +++ b/website/tsdoc-metadata.json @@ -5,7 +5,7 @@ "toolPackages": [ { "packageName": "@microsoft/api-extractor", - "packageVersion": "7.18.19" + "packageVersion": "7.42.3" } ] } From 6e4ec999fc8c56a610a04e177748e16ce1a774e0 Mon Sep 17 00:00:00 2001 From: hw Date: Thu, 14 Mar 2024 21:20:05 +0900 Subject: [PATCH 3/3] docs: update README --- README.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e58915d0..25dad6cc 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Please review the [API documentation](https://fxts.dev/docs/index) ## Usage ```ts -import { each, filter, map, pipe, range, take } from "@fxts/core"; +import { each, filter, fx, map, pipe, range, take } from "@fxts/core"; pipe( range(10), @@ -34,18 +34,25 @@ pipe( take(2), each((a) => console.log(a)), ); + +// chaining +fx(range(10)) + .map((a) => a + 10) + .filter((a) => a % 2 === 0) + .take(2) + .each((a) => console.log(a)); ``` ## Usage(concurrent) ```ts -import { concurrent, countBy, flat, map, pipe, toAsync } from "@fxts/core"; +import { concurrent, countBy, flat, fx, map, pipe, toAsync } from "@fxts/core"; // maybe 1 seconds api const fetchWiki = (page: string) => fetch(`https://en.wikipedia.org/w/api.php?action=parse&page=${page}`); -const countWords = async (concurrency = 1) => +const countWords = async (concurrency: number) => pipe( ["html", "css", "javascript", "typescript"], toAsync,