Repository: marpple/FxTS (ts)

Issue:
Add `keys` function
# Suggestion

<!--
  Please fill in each section completely. Thank you!
-->

## ⭐ Suggestion

<!-- What would you like to be added? -->

Returns a `Iterable/AsyncIterable` containing the own properties of the given object 

## 💻 Use Cases

<!-- What do you want to use this for? -->

```ts
const iter = keys({a:1, b:2, c:3 });
iter.next(); // {value: 'a', done:false}
iter.next(); //  {value:'b', done:false}
iter.next(); //  {value:'c', done:false}
iter.next(); //  {value:undefined, done:true}
```


Pull request description:
feat: add `keys`, `values` and `entries` functions
Fixes #67 #69 #72


Patch:
diff --git a/src/Lazy/compress.ts b/src/Lazy/compress.ts
new file mode 100644
index 00000000..71d0a9fd
--- /dev/null
+++ b/src/Lazy/compress.ts
@@ -0,0 +1,69 @@
+import zip from "./zip";
+import pipe from "../pipe";
+import filter from "./filter";
+import map from "./map";
+import ReturnIterableIteratorType from "../types/ReturnIterableIteratorType";
+
+/**
+ * Returns Iterable/AsyncIterable that filters elements from 'iterable' returning only those that have a corresponding element in 'selectors' that evaluates to 'true'.
+ * Stops when either 'iterable' or 'selectors' has been exhausted.
+ *
+ * @example
+ * ```ts
+ * const iter1 = compress([false,true,false,false,true],  [1, 2, 3, 4, 5]);
+ * iter1.next(); // {value: 2, done:false}
+ * iter1.next(); // {value: 5, done:false}
+ * iter1.next(); // {value: undefined, done:true }
+ *
+ * const iter2 = compress([1,0,0,1,0],  "abcde");
+ * iter2.next(); // {value: "a", done:false}
+ * iter2.next(); // {value: "d", done:false}
+ * iter2.next(); // {value: undefined, done:true }
+ *
+ * // with pipe
+ * pipe(
+ *   [1, 2, 3],
+ *   compress([false, true, true]),
+ *   toArray,
+ * ); // [2, 3]
+ * ```
+ *
+ * see {@link https://fxts.dev/docs/pipe | pipe}, {@link https://fxts.dev/docs/toArray | toArray}
+ */
+function compress<A, B>(
+  selector: Array<A>,
+  iterable: Iterable<B>,
+): IterableIterator<B>;
+
+function compress<A, B>(
+  selector: Array<A>,
+  iterable: AsyncIterable<B>,
+): AsyncIterableIterator<B>;
+
+function compress<A, B extends Iterable<unknown> | AsyncIterable<unknown>>(
+  selector: Array<A>,
+): (iterable: B) => ReturnIterableIteratorType<B>;
+
+function compress<A, B extends Iterable<unknown> | AsyncIterable<unknown>>(
+  selectors: Array<A>,
+  iterable?: B,
+):
+  | ReturnIterableIteratorType<B>
+  | ((iterable: B) => ReturnIterableIteratorType<B>) {
+  if (iterable === undefined) {
+    return (iterable: B) => {
+      return compress(
+        selectors,
+        iterable as any,
+      ) as ReturnIterableIteratorType<B>;
+    };
+  }
+
+  return pipe(
+    zip(selectors, iterable),
+    filter(([selector]) => selector),
+    map(([, value]) => value),
+  ) as ReturnIterableIteratorType<B>;
+}
+
+export default compress;
diff --git a/src/Lazy/entries.ts b/src/Lazy/entries.ts
new file mode 100644
index 00000000..aa402734
--- /dev/null
+++ b/src/Lazy/entries.ts
@@ -0,0 +1,27 @@
+type Entries<T extends Record<string, any>, K extends keyof T> = K extends any
+  ? [K, T[K]]
+  : never;
+
+/**
+ *
+ * Returns an iterator of the own enumerable string keyed-value pairs.
+ *
+ * @example
+ * ```ts
+ *
+ * [...entries({ a: 1, b: "2", c: true })]
+ * // [["a", 1], ["b", "2"], ["c", true]]
+ * ```
+ */
+
+function* entries<T extends Record<string, any>>(
+  obj: T,
+): Generator<Entries<T, keyof T>, void> {
+  for (const k in obj) {
+    if (Object.prototype.hasOwnProperty.call(obj, k)) {
+      yield [k, obj[k]] as any;
+    }
+  }
+}
+
+export default entries;
diff --git a/src/Lazy/index.ts b/src/Lazy/index.ts
index e7dd61ae..b1635c0c 100644
--- a/src/Lazy/index.ts
+++ b/src/Lazy/index.ts
@@ -1,19 +1,22 @@
 import append from "./append";
 import chunk from "./chunk";
 import compact from "./compact";
+import compress from "./compress";
 import concat from "./concat";
 import concurrent from "./concurrent";
 import cycle from "./cycle";
 import difference from "./difference";
 import differenceBy from "./differenceBy";
 import drop from "./drop";
-import dropWhile from "./dropWhile";
 import dropUntil from "./dropUntil";
+import dropWhile from "./dropWhile";
+import entries from "./entries";
 import filter from "./filter";
 import flat from "./flat";
 import flatMap from "./flatMap";
 import intersection from "./intersection";
 import intersectionBy from "./intersectionBy";
+import keys from "./keys";
 import map from "./map";
 import peek from "./peek";
 import pluck from "./pluck";
@@ -31,6 +34,7 @@ import takeWhile from "./takeWhile";
 import toAsync from "./toAsync";
 import uniq from "./uniq";
 import uniqBy from "./uniqBy";
+import values from "./values";
 import zip from "./zip";
 import zipWith from "./zipWith";
 import zipWithIndex from "./zipWithIndex";
@@ -39,19 +43,22 @@ export {
   append,
   chunk,
   compact,
+  compress,
   concat,
   concurrent,
   cycle,
   difference,
   differenceBy,
   drop,
-  dropWhile,
   dropUntil,
+  dropWhile,
+  entries,
   filter,
   flat,
   flatMap,
   intersection,
   intersectionBy,
+  keys,
   map,
   peek,
   pluck,
@@ -69,6 +76,7 @@ export {
   toAsync,
   uniq,
   uniqBy,
+  values,
   zip,
   zipWith,
   zipWithIndex,
diff --git a/src/Lazy/keys.ts b/src/Lazy/keys.ts
new file mode 100644
index 00000000..447aa059
--- /dev/null
+++ b/src/Lazy/keys.ts
@@ -0,0 +1,20 @@
+/**
+ *
+ * Returns an iterator of the own enumerable property names of object.
+ *
+ * @example
+ * ```ts
+ * [...keys({ a: 1, b: "2", c: true })]
+ * // ["a", "b", "c"]
+ * ```
+ */
+
+function* keys<T extends Record<string, any>>(obj: T) {
+  for (const k in obj) {
+    if (Object.prototype.hasOwnProperty.call(obj, k)) {
+      yield k;
+    }
+  }
+}
+
+export default keys;
diff --git a/src/Lazy/values.ts b/src/Lazy/values.ts
new file mode 100644
index 00000000..1c0b5549
--- /dev/null
+++ b/src/Lazy/values.ts
@@ -0,0 +1,20 @@
+/**
+ *
+ * Returns an iterator of the own enumerable string keyed property values of object.
+ *
+ * @example
+ * ```ts
+ * [...values({ a: 1, b: "2", c: true })]
+ * // [1, "2", true]
+ * ```
+ */
+
+function* values<T extends Record<string, any>>(obj: T) {
+  for (const k in obj) {
+    if (Object.prototype.hasOwnProperty.call(obj, k)) {
+      yield obj[k];
+    }
+  }
+}
+
+export default values;
diff --git a/src/average.ts b/src/average.ts
new file mode 100644
index 00000000..bc6cf0e9
--- /dev/null
+++ b/src/average.ts
@@ -0,0 +1,50 @@
+import peek from "./Lazy/peek";
+import pipe from "./pipe";
+import sum from "./sum";
+import ReturnValueType from "./types/ReturnValueType";
+import { isAsyncIterable, isIterable } from "./_internal/utils";
+
+/**
+ * Returns the average of the given (Iterable/AsyncIterable)<number> (mean)
+ *
+ * @example
+ * ```ts
+ * average([1, 2, 3, 4, 5]); // 6
+ * await average(toAsync([1, 2, 3, 4, 5])); // 6
+ *
+ * // with pipe
+ * pipe(
+ *  [1, 2, 3, 4, 5],
+ *  average,
+ * ); // 6
+ * ```
+ *
+ * see {@link https://fxts.dev/docs/pipe | pipe}
+ */
+function average<T extends Iterable<number> | AsyncIterable<number>>(
+  iterable: T,
+): ReturnValueType<T> {
+  let size = 0;
+
+  if (isIterable(iterable)) {
+    return pipe(
+      iterable as Iterable<number>,
+      peek(() => size++),
+      sum,
+      (a) => (size === 0 ? NaN : a / size),
+    ) as ReturnValueType<T>;
+  }
+
+  if (isAsyncIterable(iterable)) {
+    return pipe(
+      iterable as AsyncIterable<number>,
+      peek(() => size++),
+      sum,
+      (a) => (size === 0 ? NaN : a / size),
+    ) as ReturnValueType<T>;
+  }
+
+  throw new TypeError("'iterable' must be type of Iterable or AsyncIterable");
+}
+
+export default average;
diff --git a/src/index.ts b/src/index.ts
index d33e4756..aa4c9952 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,5 +1,6 @@
 import add from "./add";
 import apply from "./apply";
+import average from "./average";
 import consume from "./consume";
 import countBy from "./countBy";
 import curry from "./curry";
@@ -39,6 +40,7 @@ import reduce from "./reduce";
 import size from "./size";
 import some from "./some";
 import sort from "./sort";
+import sortBy from "./sortBy";
 import sum from "./sum";
 import tap from "./tap";
 import toArray from "./toArray";
@@ -46,6 +48,8 @@ import toArray from "./toArray";
 export {
   add,
   apply,
+  average,
+  average as mean,
   consume,
   countBy,
   curry,
@@ -87,6 +91,7 @@ export {
   size,
   some,
   sort,
+  sortBy,
   sum,
   tap,
   toArray,
diff --git a/src/sort.ts b/src/sort.ts
index a4fd5f4a..27e3857f 100644
--- a/src/sort.ts
+++ b/src/sort.ts
@@ -6,7 +6,7 @@ import pipe1 from "./pipe1";
 import toArray from "./toArray";
 
 /**
- * Returns an array which is sorted by function `f`.
+ * Returns an array, sorted according to the comparator `f`, which should accept two values
  *
  * @example
  * ```ts
@@ -36,7 +36,7 @@ function sort<T extends Iterable<unknown> | AsyncIterable<unknown>>(
 ): (iterable: T) => ReturnValueType<T, IterableInfer<T>[]>;
 
 function sort<T extends Iterable<unknown> | AsyncIterable<unknown>>(
-  f: (a: T, b: T) => unknown,
+  f: (a: IterableInfer<T>, b: IterableInfer<T>) => unknown,
   iterable?: T,
 ):
   | IterableInfer<T>[]
diff --git a/src/sortBy.ts b/src/sortBy.ts
new file mode 100644
index 00000000..11518c9a
--- /dev/null
+++ b/src/sortBy.ts
@@ -0,0 +1,69 @@
+import isArray from "./isArray";
+import pipe1 from "./pipe1";
+import toArray from "./toArray";
+import IterableInfer from "./types/IterableInfer";
+import ReturnValueType from "./types/ReturnValueType";
+import { isAsyncIterable, isIterable } from "./_internal/utils";
+
+/**
+ * Returns an array which sorted according to the `f`.
+ *
+ * @example
+ * ```ts
+ * sortBy(a => a, [3, 4, 1, 2, 5, 2]);
+ * // [1, 2, 2, 3, 4, 5]
+ * sortBy((a) => a.id, [{id:4,name:'foo'}, {id:2,name:'bar'},{id:3, name:'lee'}]);
+ * // [{id:2,name:'bar'}, {id:3, name:'lee'}, {id:4,name:'foo'}]
+ * ```
+ */
+function sortBy(f: (a: any) => unknown, iterable: readonly []): any[];
+
+function sortBy<T>(f: (a: T) => unknown, iterable: Iterable<T>): T[];
+
+function sortBy<T>(
+  f: (a: T) => unknown,
+  iterable: AsyncIterable<T>,
+): Promise<T[]>;
+
+function sortBy<T extends Iterable<unknown> | AsyncIterable<unknown>>(
+  f: (a: IterableInfer<T>) => unknown,
+): (iterable: T) => ReturnValueType<T, IterableInfer<T>[]>;
+
+function sortBy<T extends Iterable<unknown> | AsyncIterable<unknown>>(
+  f: (a: IterableInfer<T>) => unknown,
+  iterable?: T,
+):
+  | IterableInfer<T>[]
+  | Promise<IterableInfer<T>[]>
+  | ((iterable: T) => ReturnValueType<T, IterableInfer<T>[]>) {
+  if (iterable === undefined) {
+    return (iterable: T) =>
+      sortBy(f, iterable as any) as ReturnValueType<T, IterableInfer<T>[]>;
+  }
+
+  const _sortBy = (a: IterableInfer<T>, b: IterableInfer<T>) => {
+    const aa = f(a) as number;
+    const bb = f(b) as number;
+    return aa < bb ? -1 : aa > bb ? 1 : 0;
+  };
+
+  if (isArray(iterable)) {
+    return iterable.sort(_sortBy);
+  }
+
+  if (isIterable(iterable)) {
+    return pipe1(toArray(iterable as Iterable<IterableInfer<T>>), (arr) =>
+      arr.sort(_sortBy),
+    );
+  }
+
+  if (isAsyncIterable(iterable)) {
+    return pipe1(toArray(iterable as AsyncIterable<IterableInfer<T>>), (arr) =>
+      arr.sort(_sortBy),
+    );
+  }
+
+  throw new TypeError("'iterable' must be type of Iterable or AsyncIterable");
+}
+
+export default sortBy;


Test patch:
diff --git a/test/Lazy/compress.spec.ts b/test/Lazy/compress.spec.ts
new file mode 100644
index 00000000..03213d85
--- /dev/null
+++ b/test/Lazy/compress.spec.ts
@@ -0,0 +1,87 @@
+import { compress, map, pipe, toArray, toAsync } from "../../src";
+import { Concurrent } from "../../src/Lazy/concurrent";
+import { generatorMock } from "../utils";
+
+describe("compress", function () {
+  describe("sync", function () {
+    it.each([
+      [
+        [false, true, false, false, true],
+        [1, 2, 3, 4, 5],
+        [2, 5],
+      ],
+      [
+        [false, true, false, false, true],
+        [1, 2, 3, 4, 5, 6],
+        [2, 5],
+      ],
+      [[false, true, false, false, true], [1, 2, 3, 4], [2]],
+      [[1, 0, 0, 1, 0], "abcde", ["a", "d"]],
+    ])(
+      "should filter elements that have a corresponding element in 'selectors'",
+      function (selectors, iterable, result) {
+        const res = compress(
+          selectors as Array<any>,
+          iterable as Iterable<any>,
+        );
+        expect(toArray(res)).toEqual(result);
+      },
+    );
+
+    it("should be able to be used as a curried function in the pipeline", function () {
+      const res = pipe(
+        [1, 2, 3, 4, 5],
+        compress([false, true, false, false, true]),
+        toArray,
+      );
+
+      expect(res).toEqual([2, 5]);
+    });
+  });
+
+  describe("async", function () {
+    it.each([
+      [
+        [false, true, false, false, true],
+        [1, 2, 3, 4, 5],
+        [2, 5],
+      ],
+      [
+        [false, true, false, false, true],
+        [1, 2, 3, 4, 5, 6],
+        [2, 5],
+      ],
+      [[false, true, false, false, true], [1, 2, 3, 4], [2]],
+      [[1, 0, 0, 1, 0], "abcde", ["a", "d"]],
+    ])(
+      "should filter elements that have a corresponding element in 'selectors'",
+      async function (selectors, iterable, result) {
+        const res = compress(
+          selectors as Array<any>,
+          iterable as Iterable<any>,
+        );
+        expect(await toArray(toAsync(res))).toEqual(result);
+      },
+    );
+
+    it("should be able to be used as a curried function in the pipeline", async function () {
+      const res = await pipe(
+        [1, 2, 3, 4, 5],
+        toAsync,
+        compress([false, true, false, false, true]),
+        toArray,
+      );
+
+      expect(res).toEqual([2, 5]);
+    });
+
+    it("should be passed concurrent object when job works concurrently", async function () {
+      const mock = generatorMock();
+      const iter = map((a) => a, mock);
+      const concurrent = Concurrent.of(2) as any;
+
+      await iter.next(concurrent);
+      expect((mock as any).getConcurrent()).toEqual(concurrent);
+    });
+  });
+});
diff --git a/test/Lazy/entries.spec.ts b/test/Lazy/entries.spec.ts
new file mode 100644
index 00000000..a44e0d5c
--- /dev/null
+++ b/test/Lazy/entries.spec.ts
@@ -0,0 +1,52 @@
+import { entries } from "../../src";
+
+describe("entries", function () {
+  it("should return an iterator that iterates entries of the given object", function () {
+    const obj = {
+      a: 1,
+      b: "2",
+      c: true,
+      d: null,
+      e: undefined,
+    };
+    const iter = entries(obj);
+    expect(iter.next().value).toEqual(["a", 1]);
+    const res = [...iter];
+    expect(res).toEqual([
+      ["b", "2"],
+      ["c", true],
+      ["d", null],
+      ["e", undefined],
+    ]);
+  });
+
+  it("should only deal with enumerable properties", function () {
+    const obj = { a: 1, b: 2, c: 3 };
+    Object.defineProperty(obj, "a", {
+      enumerable: false,
+    });
+    Object.defineProperty(obj, "c", {
+      enumerable: false,
+    });
+    const res = [...entries(obj)];
+    expect(res).toEqual([["b", 2]]);
+  });
+
+  it("should only deal with its own properties", function () {
+    class Parent {
+      a = 1;
+    }
+    class Child extends Parent {
+      b = 2;
+    }
+    const instance = new Child();
+    const proto = Object.getPrototypeOf(instance);
+    proto.__proto__.c = 3;
+    const res = [...entries(instance)];
+    expect((instance as any).c).toEqual(3);
+    expect(res).toEqual([
+      ["a", 1],
+      ["b", 2],
+    ]);
+  });
+});
diff --git a/test/Lazy/keys.spec.ts b/test/Lazy/keys.spec.ts
new file mode 100644
index 00000000..6991741a
--- /dev/null
+++ b/test/Lazy/keys.spec.ts
@@ -0,0 +1,48 @@
+import { keys } from "../../src";
+
+describe("keys", function () {
+  it("should return an iterator that iterates keys of the given object", function () {
+    const obj = {
+      a: 1,
+      b: "2",
+      c: true,
+      d: Symbol("a"),
+      e: function () {
+        return null;
+      },
+      f: null,
+      g: undefined,
+    };
+    const iter = keys(obj);
+    expect(iter.next().value).toEqual("a");
+    const res = [...iter];
+    expect(res).toEqual(["b", "c", "d", "e", "f", "g"]);
+  });
+
+  it("should only deal with enumerable properties", function () {
+    const obj = { a: 1, b: 2, c: 3 };
+    Object.defineProperty(obj, "a", {
+      enumerable: false,
+    });
+    Object.defineProperty(obj, "c", {
+      enumerable: false,
+    });
+    const res = [...keys(obj)];
+    expect(res).toEqual(["b"]);
+  });
+
+  it("should only deal with its own properties", function () {
+    class Parent {
+      a = 1;
+    }
+    class Child extends Parent {
+      b = 2;
+    }
+    const instance = new Child();
+    const proto = Object.getPrototypeOf(instance);
+    proto.__proto__.c = 3;
+    const res = [...keys(instance)];
+    expect((instance as any).c).toEqual(3);
+    expect(res).toEqual(["a", "b"]);
+  });
+});
diff --git a/test/Lazy/values.spec.ts b/test/Lazy/values.spec.ts
new file mode 100644
index 00000000..533c5ff3
--- /dev/null
+++ b/test/Lazy/values.spec.ts
@@ -0,0 +1,44 @@
+import { values } from "../../src";
+
+describe("values", function () {
+  it("should return an iterator that iterates values of the given object's property", function () {
+    const obj = {
+      a: 1,
+      b: "2",
+      c: true,
+      d: null,
+      e: undefined,
+    };
+    const iter = values(obj);
+    expect(iter.next().value).toEqual(1);
+    const res = [...iter];
+    expect(res).toEqual(["2", true, null, undefined]);
+  });
+
+  it("should only deal with enumerable properties", function () {
+    const obj = { a: 1, b: 2, c: 3 };
+    Object.defineProperty(obj, "a", {
+      enumerable: false,
+    });
+    Object.defineProperty(obj, "c", {
+      enumerable: false,
+    });
+    const res = [...values(obj)];
+    expect(res).toEqual([2]);
+  });
+
+  it("should only deal with its own properties", function () {
+    class Parent {
+      a = 1;
+    }
+    class Child extends Parent {
+      b = 2;
+    }
+    const instance = new Child();
+    const proto = Object.getPrototypeOf(instance);
+    proto.__proto__.c = 3;
+    const res = [...values(instance)];
+    expect((instance as any).c).toEqual(3);
+    expect(res).toEqual([1, 2]);
+  });
+});
diff --git a/test/average.spec.ts b/test/average.spec.ts
new file mode 100644
index 00000000..7a928a4a
--- /dev/null
+++ b/test/average.spec.ts
@@ -0,0 +1,37 @@
+import { average, pipe, toAsync } from "../src";
+
+describe("average", function () {
+  describe("sync", function () {
+    it.each([
+      [[1, 2, 3, 4, 5], 3],
+      [[], NaN],
+    ])(
+      "should return the average of the given elements",
+      function (nums, result) {
+        expect(average(nums)).toEqual(result);
+      },
+    );
+
+    it("should be able to be used as a curried function in the pipeline", function () {
+      const res = pipe([1, 2, 3, 4, 5], average);
+      expect(res).toEqual(3);
+    });
+  });
+
+  describe("async", function () {
+    it.each([
+      [[1, 2, 3, 4, 5], 3],
+      [[], NaN],
+    ])(
+      "should return the average of the given elements",
+      async function (nums, result) {
+        expect(await average(toAsync(nums))).toEqual(result);
+      },
+    );
+
+    it("should be able to be used as a curried function in the pipeline", async function () {
+      const res = await pipe([1, 2, 3, 4, 5], toAsync, average);
+      expect(res).toEqual(3);
+    });
+  });
+});
diff --git a/test/sort.spec.ts b/test/sort.spec.ts
index 8bd6f620..e8654422 100644
--- a/test/sort.spec.ts
+++ b/test/sort.spec.ts
@@ -12,10 +12,15 @@ describe("sort", function () {
   };
 
   describe("sync", function () {
-    it("should return 'false' if given iterable is an empty array", function () {
-      expect(sort(sortFn, [])).toEqual([]);
-      expect(sort(sortFn, [3, 4, 1, 2, 5, 2])).toEqual([1, 2, 2, 3, 4, 5]);
-      expect(sort(sortFn, "bcdaef")).toEqual(["a", "b", "c", "d", "e", "f"]);
+    it.each([
+      [[], []],
+      [
+        [3, 4, 1, 2, 5, 2],
+        [1, 2, 2, 3, 4, 5],
+      ],
+      ["bcdaef", ["a", "b", "c", "d", "e", "f"]],
+    ])("should sort the elements", function (iterable, result) {
+      expect(sort(sortFn, iterable as Iterable<any>)).toEqual(result);
     });
 
     it("should be able to be used as a curried function in the pipeline", function () {
@@ -29,29 +34,16 @@ describe("sort", function () {
   });
 
   describe("async", function () {
-    it("should return 'false' if given iterable is an empty array", async function () {
-      const sortFn = (a: number | string, b: number | string) => {
-        if (a === b) {
-          return 0;
-        }
-        if (a > b) {
-          return 1;
-        }
-        return -1;
-      };
-
-      expect(await sort(sortFn, toAsync([]))).toEqual([]);
-      expect(await sort(sortFn, toAsync([3, 4, 1, 2, 5, 2]))).toEqual([
-        1, 2, 2, 3, 4, 5,
-      ]);
-      expect(await sort(sortFn, toAsync("bcdaef"))).toEqual([
-        "a",
-        "b",
-        "c",
-        "d",
-        "e",
-        "f",
-      ]);
+    it.each([
+      [[], []],
+      [
+        [3, 4, 1, 2, 5, 2],
+        [1, 2, 2, 3, 4, 5],
+      ],
+      ["bcdaef", ["a", "b", "c", "d", "e", "f"]],
+    ])("should sort the elements", async function (iterable, result) {
+      const res = await sort(sortFn, toAsync(iterable as Iterable<any>));
+      expect(res).toEqual(result);
     });
 
     it("should be able to be used as a curried function in the pipeline", async function () {
diff --git a/test/sortBy.spec.ts b/test/sortBy.spec.ts
new file mode 100644
index 00000000..c3034781
--- /dev/null
+++ b/test/sortBy.spec.ts
@@ -0,0 +1,69 @@
+import { filter, identity, pipe, sortBy, toAsync } from "../src/index";
+
+describe("sortBy", function () {
+  describe("sync", function () {
+    it.each([
+      [identity, [], []],
+      [identity, [3, 4, 1, 2, 5, 2], [1, 2, 2, 3, 4, 5]],
+      [identity, "bcdae", ["a", "b", "c", "d", "e"]],
+      [
+        (a: any) => a.id,
+        [
+          { id: 4, name: "foo" },
+          { id: 2, name: "bar" },
+          { id: 3, name: "lee" },
+        ],
+        [
+          { id: 2, name: "bar" },
+          { id: 3, name: "lee" },
+          { id: 4, name: "foo" },
+        ],
+      ],
+    ])("should sort to elements by 'f'", function (f, iterable, result) {
+      expect(sortBy(f, iterable as Iterable<any>)).toEqual(result);
+    });
+
+    it("should be able to be used as a curried function in the pipeline", function () {
+      const res = pipe(
+        [3, 4, 1, 2, 5, 2],
+        filter((a) => a % 2 !== 0),
+        sortBy(identity),
+      );
+      expect(res).toEqual([1, 3, 5]);
+    });
+  });
+
+  describe("async", function () {
+    it.each([
+      [identity, [], []],
+      [identity, [3, 4, 1, 2, 5, 2], [1, 2, 2, 3, 4, 5]],
+      [identity, "bcdae", ["a", "b", "c", "d", "e"]],
+      [
+        (a: any) => a.id,
+        [
+          { id: 4, name: "foo" },
+          { id: 2, name: "bar" },
+          { id: 3, name: "lee" },
+        ],
+        [
+          { id: 2, name: "bar" },
+          { id: 3, name: "lee" },
+          { id: 4, name: "foo" },
+        ],
+      ],
+    ])("should sort to elements by 'f'", async function (f, iterable, result) {
+      const res = await sortBy(f, toAsync(iterable as Iterable<any>));
+      expect(res).toEqual(result);
+    });
+
+    it("should be able to be used as a curried function in the pipeline", async function () {
+      const res = await pipe(
+        [3, 4, 1, 2, 5, 2],
+        toAsync,
+        filter((a) => a % 2 !== 0),
+        sortBy(identity),
+      );
+      expect(res).toEqual([1, 3, 5]);
+    });
+  });
+});
diff --git a/type-check/Lazy/compress.test.ts b/type-check/Lazy/compress.test.ts
new file mode 100644
index 00000000..2ef34dbb
--- /dev/null
+++ b/type-check/Lazy/compress.test.ts
@@ -0,0 +1,24 @@
+import * as Test from "../../src/types/Test";
+import { toAsync, compress, pipe } from "../../src";
+
+const { checks, check } = Test;
+
+const res1 = compress([], []);
+const res2 = compress([true, false], [1, 0]);
+const res3 = compress([true, false], "ab");
+const res4 = compress([true, 0], [1, 0]);
+const res5 = compress([true, false], toAsync([1, 0]));
+const res6 = pipe([1, 0], compress([true, false]));
+const res7 = pipe(Promise.resolve([1, 2, 3]), compress([true, false]));
+const res8 = pipe(toAsync([1, 2, 3]), compress([true, false]));
+
+checks([
+  check<typeof res1, IterableIterator<never>, Test.Pass>(),
+  check<typeof res2, IterableIterator<number>, Test.Pass>(),
+  check<typeof res3, IterableIterator<string>, Test.Pass>(),
+  check<typeof res4, IterableIterator<number>, Test.Pass>(),
+  check<typeof res5, AsyncIterableIterator<number>, Test.Pass>(),
+  check<typeof res6, IterableIterator<number>, Test.Pass>(),
+  check<typeof res7, Promise<IterableIterator<number>>, Test.Pass>(),
+  check<typeof res8, AsyncIterableIterator<number>, Test.Pass>(),
+]);
diff --git a/type-check/Lazy/entries.test.ts b/type-check/Lazy/entries.test.ts
new file mode 100644
index 00000000..97560c3c
--- /dev/null
+++ b/type-check/Lazy/entries.test.ts
@@ -0,0 +1,45 @@
+import * as Test from "../../src/types/Test";
+import { entries, pipe } from "../../src";
+
+const { checks, check } = Test;
+
+const obj = {
+  a: 1,
+  b: "2",
+  c: true,
+  d: null,
+  e: undefined,
+  f: () => true,
+};
+
+const res1 = entries(obj);
+const res2 = pipe(obj, entries);
+
+checks([
+  check<
+    typeof res1,
+    Generator<
+      | ["a", number]
+      | ["b", string]
+      | ["c", boolean]
+      | ["d", null]
+      | ["e", undefined]
+      | ["f", () => boolean],
+      void
+    >,
+    Test.Pass
+  >(),
+  check<
+    typeof res2,
+    Generator<
+      | ["a", number]
+      | ["b", string]
+      | ["c", boolean]
+      | ["d", null]
+      | ["e", undefined]
+      | ["f", () => boolean],
+      void
+    >,
+    Test.Pass
+  >(),
+]);
diff --git a/type-check/Lazy/keys.test.ts b/type-check/Lazy/keys.test.ts
new file mode 100644
index 00000000..40416f6f
--- /dev/null
+++ b/type-check/Lazy/keys.test.ts
@@ -0,0 +1,24 @@
+import * as Test from "../../src/types/Test";
+import { keys, pipe } from "../../src";
+
+const { checks, check } = Test;
+
+const obj = {
+  a: 1,
+  b: "2",
+  c: true,
+  d: Symbol("a"),
+  e: function () {
+    return null;
+  },
+  f: null,
+  g: undefined,
+};
+
+const res1 = keys(obj);
+const res2 = pipe(obj, keys);
+
+checks([
+  check<typeof res1, Generator<keyof typeof obj, void>, Test.Pass>(),
+  check<typeof res2, Generator<keyof typeof obj, void>, Test.Pass>(),
+]);
diff --git a/type-check/Lazy/values.test.ts b/type-check/Lazy/values.test.ts
new file mode 100644
index 00000000..a4422be2
--- /dev/null
+++ b/type-check/Lazy/values.test.ts
@@ -0,0 +1,32 @@
+import * as Test from "../../src/types/Test";
+import { values, pipe } from "../../src";
+
+const { checks, check } = Test;
+
+const obj = {
+  a: 1,
+  b: "2",
+  c: true,
+  d: Symbol("a"),
+  e: function () {
+    return null;
+  },
+  f: null,
+  g: undefined,
+};
+
+const res1 = values(obj);
+const res2 = pipe(obj, values);
+
+checks([
+  check<
+    typeof res1,
+    Generator<typeof obj[keyof typeof obj], void>,
+    Test.Pass
+  >(),
+  check<
+    typeof res2,
+    Generator<typeof obj[keyof typeof obj], void>,
+    Test.Pass
+  >(),
+]);
diff --git a/type-check/average.test.ts b/type-check/average.test.ts
new file mode 100644
index 00000000..7a0176be
--- /dev/null
+++ b/type-check/average.test.ts
@@ -0,0 +1,16 @@
+import * as Test from "../src/types/Test";
+import { average, pipe, toAsync } from "../src";
+
+const { checks, check } = Test;
+
+const res1 = average([1, 2, 3]);
+const res2 = average(toAsync([1, 2, 3]));
+const res3 = pipe([1, 2, 3], average);
+const res4 = pipe([1, 2, 3], toAsync, average);
+
+checks([
+  check<typeof res1, number, Test.Pass>(),
+  check<typeof res2, Promise<number>, Test.Pass>(),
+  check<typeof res3, number, Test.Pass>(),
+  check<typeof res4, Promise<number>, Test.Pass>(),
+]);
diff --git a/type-check/sortBy.test.ts b/type-check/sortBy.test.ts
new file mode 100644
index 00000000..04092901
--- /dev/null
+++ b/type-check/sortBy.test.ts
@@ -0,0 +1,22 @@
+import * as Test from "../src/types/Test";
+import { identity, pipe, sortBy, toAsync } from "../src";
+
+const { checks, check } = Test;
+
+const res0 = sortBy(identity, []);
+const res1 = sortBy(identity, [1, 2, 3]);
+const res2 = sortBy(identity, "abc");
+const res3 = sortBy(identity, toAsync([1, 2, 3]));
+const res4 = sortBy(identity, toAsync("abc"));
+const res5 = pipe([1, 2, 3], sortBy(identity));
+const res6 = pipe([1, 2, 3], toAsync, sortBy(identity));
+
+checks([
+  check<typeof res0, any[], Test.Pass>(),
+  check<typeof res1, number[], Test.Pass>(),
+  check<typeof res2, string[], Test.Pass>(),
+  check<typeof res3, Promise<number[]>, Test.Pass>(),
+  check<typeof res4, Promise<string[]>, Test.Pass>(),
+  check<typeof res5, number[], Test.Pass>(),
+  check<typeof res6, Promise<number[]>, Test.Pass>(),
+]);