Repository: remeda/remeda
Pull request #571 (2024-03-15 08:20:16)

feat(v2): deprecate and rename functions ahead of v2 release.
I am marking functions as deprecated in v1 ahead of removal in v2. This includes functions that were sunset and functions that were just renamed. For renamed toPairs and fromPairs this also includes adding the dataLast impl back from pr #531 as the rename allows people to migrate cleanly.

Notice that this PR is for v1, not v2!

codesandbox-ci[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/remeda/remeda/pr/571/builds/483890) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit f4db1351fc47248389c173ab8d056142744f17a6:

|Sandbox| Source |
|--|--|
|[remeda-example-vanilla](https://codesandbox.io/s/remeda-example-vanilla-8nppz9)| Configuration |

eranhirsch: The main goal for this PR is to give as much heads up as possible to Remeda users about some of the breaking changes that are coming with V2. Once we actually delete the files we'll fix any residuals as we find the issues.

Patch:
diff --git a/README.md b/README.md
index 51dc65b..adf4bfd 100644
--- a/README.md
+++ b/README.md
@@ -97,7 +97,7 @@ R.pick(["firstName", "lastName"])(obj); // this will work but the types cannot b
 
 ## Lazy evaluation
 
-Many functions support lazy evaluation when using `pipe` or `createPipe`. These functions have a `pipeable` tag in the documentation.
+Many functions support lazy evaluation when using `pipe` or `piped`. These functions have a `pipeable` tag in the documentation.
 Lazy evaluation is not supported in Ramda and only partially supported in lodash.
 
 ```js
@@ -145,7 +145,7 @@ Please check function mapping in [mapping.md](./mapping.md).
 ## Remeda Design Goals
 
 1. The usage must be programmer friendly, and that's more important than following XYZ paradigm strictly.
-2. Manual annotation should never be required, and proper typings should infer everything. The only exception is the first function in `createPipe`.
+2. Manual annotation should never be required, and proper typings should infer everything. The only exception is the first function in `piped`.
 3. ES6 polyfill is required. Core methods are reused, and data structure (like Map/Set) are not re-implemented.
 4. The implementation of each function should be as minimal as possible. Tree-shaking is supported by default. (Do you know that `lodash.keyBy` has 14KB after minification?)
 5. All functions are immutable, and there are no side-effects.
diff --git a/docs/src/lib/navbar-categories.ts b/docs/src/lib/navbar-categories.ts
index adbab96..00a0af5 100644
--- a/docs/src/lib/navbar-categories.ts
+++ b/docs/src/lib/navbar-categories.ts
@@ -1,4 +1,4 @@
-import { map, pipe, toPairs } from "remeda";
+import { map, pipe, sortBy, toPairs } from "remeda";
 import { CATEGORIZED } from "./categorized";
 import { getTags } from "./get-tags";
 
@@ -12,4 +12,8 @@ export const NAVBAR_ENTRIES = pipe(
         map(funcs, (func) => ({ name: func.name, tags: getTags(func) })),
       ] as const,
   ),
+  sortBy(
+    ([category]) => category === "Deprecated",
+    ([category]) => category,
+  ),
 );
diff --git a/docs/src/pages/index.astro b/docs/src/pages/index.astro
index e659f16..9ed84d9 100644
--- a/docs/src/pages/index.astro
+++ b/docs/src/pages/index.astro
@@ -128,8 +128,8 @@ R.pick(['firstName', 'lastName'])(obj); // this will work but the types cannot b
 
         <Typography.P>
           Many functions support lazy evaluation when using <code>pipe</code> or
-          <code>createPipe</code>. These functions have a <code>pipeable</code> tag
-          in the documentation.
+          <code>piped</code>. These functions have a <code>pipeable</code> tag in
+          the documentation.
         </Typography.P>
 
         <Typography.P>
diff --git a/mapping.md b/mapping.md
index ac9ff6c..b849f8c 100644
--- a/mapping.md
+++ b/mapping.md
@@ -19,15 +19,14 @@ documentation when migrating._
 | [`concat`](https://remedajs.com/docs/#concat)                       | [`concat`](https://lodash.com/docs/4.17.15#concat)                       | [`concat`](https://ramdajs.com/docs/#concat)                       |
 | [`conditional`](https://remedajs.com/docs/#conditional)             | [`cond`](https://lodash.com/docs/4.17.15#cond)                           | [`cond`](https://ramdajs.com/docs/#cond)                           |
 | [`constant`](https://remedajs.com/docs/#constant)                   | [`constant`](https://lodash.com/docs/4.17.15#constant)                   | [`always`](https://ramdajs.com/docs/#always)                       |
-| [`createPipe`](https://remedajs.com/docs/#createPipe)               |                                                                          |                                                                    |
 | [`debounce`](https://remedajs.com/docs/#debounce)                   | [`debounce`](https://lodash.com/docs/4.17.15#debounce)                   |                                                                    |
-| [`difference`](https://remedajs.com/docs/#difference)               | [`difference`](https://lodash.com/docs/4.17.15#difference)               | [`difference`](https://ramdajs.com/docs/#difference)               |
 | [`differenceWith`](https://remedajs.com/docs/#differenceWith)       | [`differenceWith`](https://lodash.com/docs/4.17.15#differenceWith)       | [`differenceWith`](https://ramdajs.com/docs/#differenceWith)       |
 | [`divide`](https://remedajs.com/docs/#divide)                       | [`divide`](https://lodash.com/docs/4.17.15#divide)                       | [`divide`](https://ramdajs.com/docs/#divide)                       |
 | [`drop`](https://remedajs.com/docs/#drop)                           | [`drop`](https://lodash.com/docs/4.17.15#drop)                           | [`drop`](https://ramdajs.com/docs/#drop)                           |
 | [`dropLast`](https://remedajs.com/docs/#dropLast)                   | [`dropRight`](https://lodash.com/docs/4.17.15#dropRight)                 | [`dropLast`](https://ramdajs.com/docs/#dropLast)                   |
 | [`dropLastWhile`](https://remedajs.com/docs/#dropLastWhile)         | [`dropRightWhile`](https://lodash.com/docs/4.17.15#dropRightWhile)       | [`dropLastWhile`](https://ramdajs.com/docs/#dropLastWhile)         |
 | [`dropWhile`](https://remedajs.com/docs/#dropWhile)                 | [`dropWhile`](https://lodash.com/docs/4.17.15#dropWhile)                 | [`dropWhile`](https://ramdajs.com/docs/#dropWhile)                 |
+| [`entries`](https://remedajs.com/docs/#entries)                     | [`toPairs`](https://lodash.com/docs/4.17.15#toPairs)                     | [`toPairs`](https://ramdajs.com/docs/#toPairs)                     |
 | [`evolve`](https://remedajs.com/docs/#evolve)                       |                                                                          | [`evolve`](https://ramdajs.com/docs/#evolve)                       |
 | [`filter`](https://remedajs.com/docs/#filter)                       | [`filter`](https://lodash.com/docs/4.17.15#filter)                       | [`filter`](https://ramdajs.com/docs/#filter)                       |
 | [`find`](https://remedajs.com/docs/#find)                           | [`find`](https://lodash.com/docs/4.17.15#find)                           | [`find`](https://ramdajs.com/docs/#find)                           |
@@ -45,13 +44,12 @@ documentation when migrating._
 | [`hasSubobject`](https://remedajs.com/docs/#hasSubobject)           | [`isMatch`](https://lodash.com/docs/4.17.15#isMatch)                     | [`where`](https://ramdajs.com/docs/#where)                         |
 | [`identity`](https://remedajs.com/docs/#identity)                   | [`identity`](https://lodash.com/docs/4.17.15#identity)                   | [`identity`](https://ramdajs.com/docs/#identity)                   |
 | [`indexBy`](https://remedajs.com/docs/#indexBy)                     | [`keyBy`](https://lodash.com/docs/4.17.15#keyBy)                         | [`indexBy`](https://ramdajs.com/docs/#indexBy)                     |
-| [`intersection`](https://remedajs.com/docs/#intersection)           | [`intersection`](https://lodash.com/docs/4.17.15#intersection)           | [`intersection`](https://ramdajs.com/docs/#intersection)           |
 | [`intersectionWith`](https://remedajs.com/docs/#intersectionWith)   | [`intersectionWith`](https://lodash.com/docs/4.17.15#intersectionWith)   | [`innerJoin`](https://ramdajs.com/docs/#innerJoin)                 |
 | [`invert`](https://remedajs.com/docs/#invert)                       | [`invert`](https://lodash.com/docs/4.17.15#invert)                       | [`invertObj`](https://ramdajs.com/docs/#invertObj)                 |
 | [`isDeepEqual`](https://remedajs.com/docs/#isDeepEqual)             | [`isEqual`](https://lodash.com/docs/4.17.15#isEqual)                     | [`equals`](https://ramdajs.com/docs/#equals)                       |
-| [`isDefined`](https://remedajs.com/docs/#isDefined)                 |                                                                          | [`isNotNil`](https://ramdajs.com/docs/#isNotNil)                   |
 | [`isEmpty`](https://remedajs.com/docs/#isEmpty)                     | [`isEmpty`](https://lodash.com/docs/4.17.15#isEmpty)                     | [`isEmpty`](https://ramdajs.com/docs/#isEmpty)                     |
-| [`isNil`](https://remedajs.com/docs/#isNil)                         | [`isNil`](https://lodash.com/docs/4.17.15#isNil)                         | [`isNil`](https://ramdajs.com/docs/#isNil)                         |
+| [`isNonNullish`](https://remedajs.com/docs/#isNonNullish)           |                                                                          | [`isNotNil`](https://ramdajs.com/docs/#isNotNil)                   |
+| [`isNullish`](https://remedajs.com/docs/#isNullish)                 | [`isNil`](https://lodash.com/docs/4.17.15#isNil)                         | [`isNil`](https://ramdajs.com/docs/#isNil)                         |
 | [`isObjectType`](https://remedajs.com/docs/#isObjectType)           | [`isObjectLike`](https://lodash.com/docs/4.17.15#isObjectLike)           |                                                                    |
 | [`isPlainObject`](https://remedajs.com/docs/#isPlainObject)         | [`isPlainObject`](https://lodash.com/docs/4.17.15#isPlainObject)         |                                                                    |
 | [`isSymbol`](https://remedajs.com/docs/#isSymbol)                   | [`isSymbol`](https://lodash.com/docs/4.17.15#isSymbol)                   |                                                                    |
@@ -77,11 +75,9 @@ documentation when migrating._
 | [`pickBy`](https://remedajs.com/docs/#pickBy)                       | [`pickBy`](https://lodash.com/docs/4.17.15#pickBy)                       | [`pickBy`](https://ramdajs.com/docs/#pickBy)                       |
 | [`pipe`](https://remedajs.com/docs/#pipe)                           | [`flow`](https://lodash.com/docs/4.17.15#flow)                           | [`pipe`](https://ramdajs.com/docs/#pipe)                           |
 | [`prop`](https://remedajs.com/docs/#prop)                           | [`get`](https://lodash.com/docs/4.17.15#get)                             | [`prop`](https://ramdajs.com/docs/#prop)                           |
-| [`purry`](https://remedajs.com/docs/#purry)                         |                                                                          |                                                                    |
 | [`randomString`](https://remedajs.com/docs/#randomString)           |                                                                          |                                                                    |
 | [`range`](https://remedajs.com/docs/#range)                         | [`range`](https://lodash.com/docs/4.17.15#range)                         | [`range`](https://ramdajs.com/docs/#range)                         |
 | [`reduce`](https://remedajs.com/docs/#reduce)                       | [`reduce`](https://lodash.com/docs/4.17.15#reduce)                       | [`reduce`](https://ramdajs.com/docs/#reduce)                       |
-| [`reject`](https://remedajs.com/docs/#reject)                       | [`reject`](https://lodash.com/docs/4.17.15#reject)                       | [`reject`](https://ramdajs.com/docs/#reject)                       |
 | [`reverse`](https://remedajs.com/docs/#reverse)                     | [`reverse`](https://lodash.com/docs/4.17.15#reverse)                     | [`reverse`](https://ramdajs.com/docs/#reverse)                     |
 | [`round`](https://remedajs.com/docs/#round)                         | [`round`](https://lodash.com/docs/4.17.15#round)                         |                                                                    |
 | [`sample`](https://remedajs.com/docs/#sample)                       | [`sampleSize`](https://lodash.com/docs/4.17.15#)\*                       |                                                                    |
@@ -108,12 +104,9 @@ documentation when migrating._
 | [`takeWhile`](https://remedajs.com/docs/#takeWhile)                 | [`takeWhile`](https://lodash.com/docs/4.17.15#takeWhile)                 | [`takeWhile`](https://ramdajs.com/docs/#takeWhile)                 |
 | [`tap`](https://remedajs.com/docs/#tap)                             | [`tap`](https://lodash.com/docs/4.17.15#tap)                             | [`tap`](https://ramdajs.com/docs/#tap)                             |
 | [`times`](https://remedajs.com/docs/#times)                         | [`times`](https://lodash.com/docs/4.17.15#times)                         | [`times`](https://ramdajs.com/docs/#times)                         |
-| [`toPairs`](https://remedajs.com/docs/#toPairs)                     | [`toPairs`](https://lodash.com/docs/4.17.15#toPairs)                     | [`toPairs`](https://ramdajs.com/docs/#toPairs)                     |
-| [`type`](https://remedajs.com/docs/#type)                           |                                                                          | [`type`](https://ramdajs.com/docs/#type)                           |
-| [`uniq`](https://remedajs.com/docs/#uniq)                           | [`uniq`](https://lodash.com/docs/4.17.15#uniq)                           | [`uniq`](https://ramdajs.com/docs/#uniq)                           |
-| [`uniqBy`](https://remedajs.com/docs/#uniqBy)                       | [`uniqBy`](https://lodash.com/docs/4.17.15#uniqBy)                       | [`uniqBy`](https://ramdajs.com/docs/#uniqBy)                       |
-| [`uniqWith`](https://remedajs.com/docs/#uniqWith)                   | [`uniqWith`](https://lodash.com/docs/4.17.15#uniqWith)                   | [`uniqWith`](https://ramdajs.com/docs/#uniqWith)                   |
-| [`zipObj`](https://remedajs.com/docs/#zipObj)                       | [`zipObj`](https://lodash.com/docs/4.17.15#zipObj)                       | [`zipObj`](https://ramdajs.com/docs/#zipObj)                       |
+| [`unique`](https://remedajs.com/docs/#unique)                       | [`uniq`](https://lodash.com/docs/4.17.15#uniq)                           | [`uniq`](https://ramdajs.com/docs/#uniq)                           |
+| [`uniqueBy`](https://remedajs.com/docs/#uniqueBy)                   | [`uniqBy`](https://lodash.com/docs/4.17.15#uniqBy)                       | [`uniqBy`](https://ramdajs.com/docs/#uniqBy)                       |
+| [`uniqueWith`](https://remedajs.com/docs/#uniqueWith)               | [`uniqWith`](https://lodash.com/docs/4.17.15#uniqWith)                   | [`uniqWith`](https://ramdajs.com/docs/#uniqWith)                   |
 
 ## Helpful one-liners
 
@@ -126,17 +119,21 @@ provide the exact functionality of the listed Lodash and Ramda functions.
 Be sure to consult each library's documentation and to check what features
 you are relying on when migrating._
 
-| Lodash    | Ramda    | TypeScript                        |
-| --------- | -------- | --------------------------------- |
-|           | `append` | `(arr, val) => [...arr, val]`     |
-| `nth`     | `nth`    | `n => a[n]`                       |
-|           | `path`   | `a?.b?.c`                         |
-|           | `propEq` | `x => x.a === value`              |
-|           | `propOr` | `x => x.a ?? defaultValue`        |
-| `words`   |          | `str => str.split(/\s+/)`         |
-| `compact` |          | `R.filter(R.isTruthy)`            |
-| `max`     | `max`    | `R.firstBy([R.identity, "desc"])` |
-| `maxBy`   | `maxBy`  | `R.firstBy([fn, "desc"])`         |
-| `min`     | `min`    | `R.firstBy(R.identity)`           |
-| `minBy`   | `minBy`  | `R.firstBy(fn)`                   |
-| `noop`    |          | `R.constant(undefined)`           |
+| Remeda                                      | Lodash         | Ramda          |
+| ------------------------------------------- | -------------- | -------------- |
+| `(arr, val) => [...arr, val]`               |                | `append`       |
+| `a?.b?.c`                                   | `path`         |                |
+| `n => a[n]`                                 | `nth`          | `nth`          |
+| `R.constant(undefined)`                     | `noop`         |                |
+| `R.filter(R.isIncludedIn(other))`           | `intersection` | `intersection` |
+| `R.filter(R.isNot(fn))`                     | `reject`       | `reject`       |
+| `R.filter(R.isNot(R.isIncludedIn(other)))`  | `difference`   | `difference`   |
+| `R.filter(R.isTruthy)`                      | `compact`      |                |
+| `R.firstBy([fn, "desc"])`                   | `maxBy`        | `maxBy`        |
+| `R.firstBy([R.identity, "desc"])`           | `max`          | `max`          |
+| `R.firstBy(fn)`                             | `minBy`        | `minBy`        |
+| `R.firstBy(R.identity)`                     | `min`          | `min`          |
+| `R.fromEntries.strict(R.zip(keys, values))` | `zipObj`       | `zipObj`       |
+| `str => str.split(/\s+/)`                   | `words`        |                |
+| `x => x.a ?? defaultValue`                  | `propOr`       |                |
+| `x => x.a === value`                        | `propEq`       |                |
diff --git a/src/compact.ts b/src/compact.ts
index 7d6e25a..06705c3 100644
--- a/src/compact.ts
+++ b/src/compact.ts
@@ -4,16 +4,15 @@ import { isTruthy } from "./isTruthy";
  * Filter out all falsy values. The values `false`, `null`, `0`, `""`,
  * `undefined`, and `NaN` are falsy.
  *
- * **DEPRECATED: equivalent to `R.filter(R.isTruthy)` and so will be removed in
- * v2**.
+ * ! **DEPRECATED**: Use `R.filter(items, R.isTruthy)`. Will be removed in V2!
  *
  * @param items - The array to compact.
  * @signature
  *    R.compact(array)
  * @example
  *    R.compact([0, 1, false, 2, '', 3]) // => [1, 2, 3]
- * @category Array
- * @deprecated Equivalent to `R.filter(R.isTruthy)` and so will be removed in v2.
+ * @category Deprecated
+ * @deprecated Use `R.filter(items, R.isTruthy)`. Will be removed in V2!
  */
 export function compact<T>(
   items: ReadonlyArray<T | "" | 0 | false | null | undefined>,
diff --git a/src/countBy.ts b/src/countBy.ts
index b2cda80..3623cd8 100644
--- a/src/countBy.ts
+++ b/src/countBy.ts
@@ -1,7 +1,7 @@
-/* eslint-disable jsdoc/check-param-names, jsdoc/require-param -- ignore for deprecated files */
+/* eslint-disable jsdoc/require-param -- ignore for deprecated files */
 
-import { purry } from "./purry";
 import type { Pred, PredIndexed, PredIndexedOptional } from "./_types";
+import { purry } from "./purry";
 
 const _countBy =
   (indexed: boolean) =>
@@ -19,7 +19,7 @@ const _countBy =
 /**
  * Counts how many values of the collection pass the specified predicate.
  *
- * **DEPRECATED: equivalent to `R.filter(fn).length` and so will be removed in v2**.
+ * ! **DEPRECATED**: Use `R.filter(items, fn).length`. Will be removed in v2!
  *
  * @param items - The input data.
  * @param fn - The predicate.
@@ -29,22 +29,18 @@ const _countBy =
  *    R.countBy([1, 2, 3, 4, 5], x => x % 2 === 0) // => 2
  * @dataFirst
  * @indexed
- * @category Array
- * @deprecated Equivalent to `R.filter(fn).length` and so will be removed in v2.
+ * @category Deprecated
+ * @deprecated Use `R.filter(items, fn).length`. Will be removed in v2.
  */
 export function countBy<T>(
   items: ReadonlyArray<T>,
   fn: Pred<T, boolean>,
 ): number;
 
-export function countBy<T>(
-  fn: Pred<T, boolean>,
-): (array: ReadonlyArray<T>) => number;
-
 /**
  * Counts how many values of the collection pass the specified predicate.
  *
- * **DEPRECATED: equivalent to `R.filter(fn).length` and so will be removed in v2**.
+ * ! **DEPRECATED**: Use `<T>(items: ReadonlyArray<T>) => R.filter(items, fn).length` or if in a pipe: `R.pipe(..., R.filter(fn), R.length(), ...)`. Will be removed in v2!
  *
  * @param fn - The predicate.
  * @signature
@@ -53,9 +49,13 @@ export function countBy<T>(
  *    R.pipe([1, 2, 3, 4, 5], R.countBy(x => x % 2 === 0)) // => 2
  * @dataLast
  * @indexed
- * @category Array
- * @deprecated Equivalent to `R.filter(fn).length` and so will be removed in v2.
+ * @category Deprecated
+ * @deprecated Use `<T>(items: ReadonlyArray<T>) => R.filter(items, fn).length` or if in a pipe: `R.pipe(..., R.filter(fn), R.length(), ...)`. Will be removed in v2.
  */
+export function countBy<T>(
+  fn: Pred<T, boolean>,
+): (array: ReadonlyArray<T>) => number;
+
 export function countBy(): unknown {
   return purry(_countBy(false), arguments);
 }
@@ -64,9 +64,11 @@ export namespace countBy {
   /**
    * Counts how many values of the collection pass the specified predicate.
    *
+   * ! **DEPRECATED**: Use `R.filter.indexed(items, fn).length`. Will be removed in v2!
+   *
    * @example
    *    R.pipe([1, 2, 3, 4, 5], R.countBy(x => x % 2 === 0)) // => 2
-   * @deprecated Equivalent to `R.filter.indexed(fn).length` and so will be removed in v2.
+   * @deprecated Use `R.filter.indexed(items, fn).length`. Will be removed in v2.
    */
   export function indexed<T>(
     array: ReadonlyArray<T>,
@@ -75,9 +77,11 @@ export namespace countBy {
   /**
    * Counts how many values of the collection pass the specified predicate.
    *
+   * ! **DEPRECATED**: Use `<T>(items: ReadonlyArray<T>) => R.filter.indexed(items, fn).length` or if in a pipe: `R.pipe(..., R.filter.indexed(fn), R.length(), ...)`. Will be removed in v2!
+   *
    * @example
    *    R.pipe([1, 2, 3, 4, 5], R.countBy(x => x % 2 === 0)) // => 2
-   * @deprecated Equivalent to `R.filter.indexed(fn).length` and so will be removed in v2.
+   * @deprecated Use `<T>(items: ReadonlyArray<T>) => R.filter.indexed(items, fn).length` or if in a pipe: `R.pipe(..., R.filter.indexed(fn), R.length(), ...)`. Will be removed in v2.
    */
   export function indexed<T>(
     fn: PredIndexed<T, boolean>,
diff --git a/src/createPipe.ts b/src/createPipe.ts
index cae7583..cc89d1c 100644
--- a/src/createPipe.ts
+++ b/src/createPipe.ts
@@ -5,6 +5,8 @@ import { pipe } from "./pipe";
 /**
  * Creates a data-last pipe function. First function must be always annotated. Other functions are automatically inferred.
  *
+ * ! **DEPRECATED**: Use `R.piped(op1, op2, op3)`. Will be removed in V2!
+ *
  * @signature
  *    R.createPipe(op1, op2, op3)(data);
  * @example
@@ -12,7 +14,8 @@ import { pipe } from "./pipe";
  *      (x: number) => x * 2,
  *      x => x * 3
  *    )(1) // => 6
- * @category Function
+ * @category Deprecated
+ * @deprecated Use `R.piped(op1, op2, op3)`. Will be removed in V2!
  */
 export function createPipe<A, B>(op1: (input: A) => B): (value: A) => B;
 
diff --git a/src/difference.ts b/src/difference.ts
index 60c86cf..470ea3e 100644
--- a/src/difference.ts
+++ b/src/difference.ts
@@ -5,8 +5,7 @@ import { purry } from "./purry";
 /**
  * Excludes the values from `other` array.
  *
- * **DEPRECATED: equivalent to `R.filter(array, R.isNot(R.isIncludedIn(other)))`
- * and will be removed in v2.**.
+ * ! **DEPRECATED**: Use `R.filter(array, R.isNot(R.isIncludedIn(other)))`. Will be removed in v2!
  *
  * @param array - The source array.
  * @param other - The values to exclude.
@@ -16,8 +15,8 @@ import { purry } from "./purry";
  *    R.difference([1, 2, 3, 4], [2, 5, 3]) // => [1, 4]
  * @dataFirst
  * @pipeable
- * @category Array
- * @deprecated Equivalent to `R.filter(array, R.isNot(R.isIncludedIn(other)))` and will be removed in v2.
+ * @category Deprecated
+ * @deprecated Use `R.filter(array, R.isNot(R.isIncludedIn(other)))`. Will be removed in v2.
  */
 export function difference<T>(
   array: ReadonlyArray<T>,
@@ -27,8 +26,7 @@ export function difference<T>(
 /**
  * Excludes the values from `other` array.
  *
- * **DEPRECATED: equivalent to `R.filter(R.isNot(R.isIncludedIn(other)))` and
- * will be removed in v2.**.
+ * ! **DEPRECATED**: Use `R.filter(R.isNot(R.isIncludedIn(other)))`. Will be removed in v2!
  *
  * @param other - The values to exclude.
  * @signature
@@ -42,8 +40,8 @@ export function difference<T>(
  *    ) // => [1, 4]
  * @dataLast
  * @pipeable
- * @category Array
- * @deprecated Equivalent to `R.filter(R.isNot(R.isIncludedIn(other)))` and will be removed in v2.
+ * @category Deprecated
+ * @deprecated Use `R.filter(R.isNot(R.isIncludedIn(other)))`. Will be removed in v2.
  */
 export function difference<T, K>(
   other: ReadonlyArray<T>,
diff --git a/src/entries.ts b/src/entries.ts
new file mode 100644
index 0000000..9d6ad5a
--- /dev/null
+++ b/src/entries.ts
@@ -0,0 +1,59 @@
+import { purry } from "./purry";
+
+/**
+ * Returns an array of key/values of the enumerable properties of an object.
+ *
+ * @param object - Object to return keys and values of.
+ * @signature
+ *    R.entries(object)
+ *    R.entries.strict(object)
+ * @example
+ *    R.entries({ a: 1, b: 2, c: 3 }) // => [['a', 1], ['b', 2], ['c', 3]]
+ *    R.entries.strict({ a: 1 } as const) // => [['a', 1]] typed Array<['a', 1]>
+ * @dataFirst
+ * @strict
+ * @category Object
+ */
+export function entries<T>(
+  object: Readonly<Record<string, T>>,
+): Array<[string, T]>;
+
+/**
+ * Returns an array of key/values of the enumerable properties of an object.
+ *
+ * @signature
+ *    R.entries()(object)
+ *    R.entries.strict()(object)
+ * @example
+ *    R.pipe(
+ *      { a: 1, b: 2, c: 3 },
+ *      entries(),
+ *    ); // => [['a', 1], ['b', 2], ['c', 3]]
+ *    R.pipe(
+ *      { a: 1 } as const,
+ *      entries.strict(),
+ *    ); // => [['a', 1]] typed Array<['a', 1]>
+ * @dataLast
+ * @strict
+ * @category Object
+ */
+export function entries(): <T>(
+  object: Readonly<Record<string, T>>,
+) => Array<[string, T]>;
+
+export function entries(): unknown {
+  return purry(Object.entries, arguments);
+}
+
+type Entries<T> = Array<
+  { [K in keyof T]-?: [key: K, value: Required<T>[K]] }[keyof T]
+>;
+
+type Strict = {
+  <T extends NonNullable<unknown>>(object: T): Entries<T>;
+  (): <T extends NonNullable<unknown>>(object: T) => Entries<T>;
+};
+
+export namespace entries {
+  export const strict = entries as Strict;
+}
diff --git a/src/equals.ts b/src/equals.ts
index b01df82..1bb0400 100644
--- a/src/equals.ts
+++ b/src/equals.ts
@@ -6,7 +6,7 @@ import { purry } from "./purry";
  * Returns true if its arguments are equivalent, false otherwise.
  * NOTE: Doesn't handle cyclical data structures.
  *
- * **DEPRECATED: use R.isDeepEqual().**.
+ * ! **DEPRECATED**: Use `R.isDeepEqual(a, b)`. Will be removed in V2.
  *
  * @param a - The first object to compare.
  * @param b - The second object to compare.
@@ -17,8 +17,8 @@ import { purry } from "./purry";
  *    R.equals(1, '1') //=> false
  *    R.equals([1, 2, 3], [1, 2, 3]) //=> true
  * @dataFirst
- * @category Object
- * @deprecated Use `R.isDeepEqual`.
+ * @category Deprecated
+ * @deprecated Use `R.isDeepEqual(a, b)`. Will be removed in V2.
  */
 export function equals(a: unknown, b: unknown): boolean;
 
@@ -26,7 +26,7 @@ export function equals(a: unknown, b: unknown): boolean;
  * Returns true if its arguments are equivalent, false otherwise.
  * NOTE: Doesn't handle cyclical data structures.
  *
- * **DEPRECATED: use R.isDeepEqual().**.
+ * ! **DEPRECATED**: Use `R.isDeepEqual(b)`. Will be removed in V2.
  *
  * @param a - The first object to compare.
  * @param b - The second object to compare.
@@ -37,8 +37,8 @@ export function equals(a: unknown, b: unknown): boolean;
  *    R.equals('1')(1) //=> false
  *    R.equals([1, 2, 3])([1, 2, 3]) //=> true
  * @dataLast
- * @category Object
- * @deprecated Use `R.isDeepEqual`.
+ * @category Deprecated
+ * @deprecated Use `R.isDeepEqual(b)`. Will be removed in V2.
  */
 export function equals(a: unknown): (b: unknown) => boolean;
 
diff --git a/src/flatMapToObj.ts b/src/flatMapToObj.ts
index febaec8..2b2656f 100644
--- a/src/flatMapToObj.ts
+++ b/src/flatMapToObj.ts
@@ -1,9 +1,13 @@
+/* eslint-disable jsdoc/require-param, jsdoc/require-example, jsdoc/require-description -- Deprecated file */
+
 import type { PredIndexedOptional } from "./_types";
 import { purry } from "./purry";
 
 /**
  * Map each element of an array into an object using a defined callback function and flatten the result.
  *
+ * ! **DEPRECATED**: Use `R.fromEntries.strict(R.flatMap(array, fn))`. Will be removed in V2!
+ *
  * @param array - The array to map.
  * @param fn - The mapping function, which should return an Array of key-value pairs, similar to Object.fromEntries.
  * @returns The new mapped object.
@@ -20,7 +24,8 @@ import { purry } from "./purry";
  *  ]) // => {a: 0, aa: 0, b: 1, bb: 2}
  * @dataFirst
  * @indexed
- * @category Array
+ * @category Deprecated
+ * @deprecated Use `R.fromEntries.strict(R.flatMap(array, fn))`. Will be removed in V2!
  */
 export function flatMapToObj<T, K extends PropertyKey, V>(
   array: ReadonlyArray<T>,
@@ -30,6 +35,8 @@ export function flatMapToObj<T, K extends PropertyKey, V>(
 /**
  * Map each element of an array into an object using a defined callback function and flatten the result.
  *
+ * ! **DEPRECATED**: Use `(array: ReadonlyArray<T>) => R.fromEntries.strict(R.flatMap(array, fn))` or if used in a pipe: `pipe(..., R.flatMap(fn), R.fromEntries.strict(), ...)`. Will be removed in V2!
+ *
  * @param fn - The mapping function, which should return an Array of key-value pairs, similar to Object.fromEntries.
  * @returns The new mapped object.
  * @signature
@@ -49,7 +56,8 @@ export function flatMapToObj<T, K extends PropertyKey, V>(
  *    ) // => {a: 0, aa: 0, b: 1, bb: 2}
  * @dataLast
  * @indexed
- * @category Array
+ * @category Deprecated
+ * @deprecated Use `(array: ReadonlyArray<T>) => R.fromEntries.strict(R.flatMap(array, fn))` or if used in a pipe: `pipe(..., R.flatMap(fn), R.fromEntries.strict(), ...)`. Will be removed in V2!
  */
 export function flatMapToObj<T, K extends PropertyKey, V>(
   fn: (element: T) => Array<[K, V]>,
@@ -82,10 +90,17 @@ const _flatMapToObj =
   };
 
 export namespace flatMapToObj {
+  /**
+   * @deprecated Use `R.fromEntries.strict(R.flatten(R.map.indexed(array, fn)))`. Will be removed in V2!
+   */
   export function indexed<T, K extends PropertyKey, V>(
     array: ReadonlyArray<T>,
     fn: (element: T, index: number, array: ReadonlyArray<T>) => Array<[K, V]>,
   ): Record<K, V>;
+
+  /**
+   * @deprecated Use `(array: ReadonlyArray<T>) => R.fromEntries.strict(R.flatten(R.map.indexed(array, fn)))` or if used in a pipe: `pipe(..., R.map.indexed(fn), R.flatten(), R.fromEntries.strict(), ...)`. Will be removed in V2!
+   */
   export function indexed<T, K extends PropertyKey, V>(
     fn: (element: T, index: number, array: ReadonlyArray<T>) => Array<[K, V]>,
   ): (array: ReadonlyArray<T>) => Record<K, V>;
diff --git a/src/fromEntries.ts b/src/fromEntries.ts
new file mode 100644
index 0000000..a6cd47e
--- /dev/null
+++ b/src/fromEntries.ts
@@ -0,0 +1,163 @@
+import type { IterableContainer } from "./_types";
+import { purry } from "./purry";
+
+type Entry<Key extends PropertyKey = PropertyKey, Value = unknown> = readonly [
+  key: Key,
+  value: Value,
+];
+
+/**
+ * Creates a new object from an array of tuples by pairing up first and second elements as {[key]: value}.
+ * If a tuple is not supplied for any element in the array, the element will be ignored
+ * If duplicate keys exist, the tuple with the greatest index in the input array will be preferred.
+ *
+ * The strict option supports more sophisticated use-cases like those that would
+ * result when calling the strict `toPairs` function.
+ *
+ * @param entries - The list of input tuples.
+ * @signature
+ *   R.fromEntries(tuples)
+ *   R.fromEntries.strict(tuples)
+ * @example
+ *   R.fromEntries([['a', 'b'], ['c', 'd']]) // => {a: 'b', c: 'd'} (type: Record<string, string>)
+ *   R.fromEntries.strict(['a', 1] as const) // => {a: 1} (type: {a: 1})
+ * @dataFirst
+ * @strict
+ * @category Object
+ */
+export function fromEntries<V>(
+  entries: ReadonlyArray<Entry<number, V>>,
+): Record<number, V>;
+export function fromEntries<V>(
+  entries: ReadonlyArray<Entry<string, V>>,
+): Record<string, V>;
+
+/**
+ * Creates a new object from an array of tuples by pairing up first and second elements as {[key]: value}.
+ * If a tuple is not supplied for any element in the array, the element will be ignored
+ * If duplicate keys exist, the tuple with the greatest index in the input array will be preferred.
+ *
+ * The strict option supports more sophisticated use-cases like those that would
+ * result when calling the strict `toPairs` function.
+ *
+ * @signature
+ *   R.fromEntries()(tuples)
+ *   R.fromEntries.strict()(tuples)
+ * @example
+ *   R.pipe(
+ *     [['a', 'b'], ['c', 'd']],
+ *     R.fromEntries(),
+ *   ); // => {a: 'b', c: 'd'} (type: Record<string, string>)
+ *   R.pipe(
+ *     ['a', 1] as const,
+ *     R.fromEntries.strict(),
+ *   ); // => {a: 1} (type: {a: 1})
+ * @dataLast
+ * @strict
+ * @category Object
+ */
+export function fromEntries(): <K extends PropertyKey, V>(
+  entries: ReadonlyArray<Entry<K, V>>,
+) => Record<K extends string ? string : K extends number ? number : never, V>;
+
+export function fromEntries(): unknown {
+  // TODO: When we bump the typescript target beyond ES2019 we can use Object.fromEntries directly here instead of our user-space implementation.
+  return purry(fromEntriesImplementation, arguments);
+}
+
+function fromEntriesImplementation(
+  entries: ReadonlyArray<Entry>,
+): Record<string, unknown> {
+  const out: Record<PropertyKey, unknown> = {};
+  for (const [key, value] of entries) {
+    out[key] = value;
+  }
+  return out;
+}
+
+// Redefining the fromEntries function to allow stricter entries arrays and
+// fine-grained handling of partiality of the output.
+type Strict = {
+  <Entries extends IterableContainer<Entry>>(
+    entries: Entries,
+  ): StrictOut<Entries>;
+
+  (): <Entries extends IterableContainer<Entry>>(
+    entries: Entries,
+  ) => StrictOut<Entries>;
+};
+
+// The 2 kinds of arrays we accept result in different kinds of outputs:
+// 1. If the input is a *tuple*, we know exactly what entries it would hold,
+// and thus can type the result so that the keys are required. We will then run
+// recursively on the rest of the tuple.
+// 2. If the input is an *array* then any keys defined in the array might not
+// actually show up in runtime, and thus need to be optional. (e.g. if the input
+// is an empty array).
+type StrictOut<Entries> = Entries extends readonly [infer First, ...infer Tail]
+  ? fromEntriesTuple<First, Tail>
+  : Entries extends readonly [...infer Head, infer Last]
+    ? fromEntriesTuple<Last, Head>
+    : Entries extends IterableContainer<Entry>
+      ? fromEntriesArray<Entries>
+      : "ERROR: Entries array-like could not be inferred";
+
+// For strict tuples we build the result by intersecting each entry as a record
+// between it's key and value, recursively. The recursion goes through our main
+// type so that we support tuples which also contain rest parts.
+type fromEntriesTuple<E, Rest> = E extends Entry
+  ? Record<E[0], E[1]> & StrictOut<Rest>
+  : "ERROR: Array-like contains a non-entry element";
+
+// For the array case we also need to handle what kind of keys it defines:
+// 1. If it defines a *broad* key (one that has an infinite set of values, like
+// number or string) then the result is a simple record.
+// 2. If the keys are *literals* then we need to make the record partial
+// (because those props are explicit), and we need to match each key it's
+// specific possible value, as defined by the entries.
+//
+// Note that this destination between keys is the result of how typescript
+// considers Record<string, unknown> to be **implicitly** partial, whereas
+// Record<"a", unknown> is not.
+type fromEntriesArray<Entries extends IterableContainer<Entry>> =
+  string extends AllKeys<Entries>
+    ? Record<string, Entries[number][1]>
+    : number extends AllKeys<Entries>
+      ? Record<number, Entries[number][1]>
+      : symbol extends AllKeys<Entries>
+        ? Record<symbol, Entries[number][1]>
+        : fromEntriesArrayWithLiteralKeys<Entries>;
+
+// This type is largely copied from `objectFromEntries` in the repo:
+// *sindresorhus/ts-extras* but makes all properties of the output optional,
+// which is more correct because we can't assure that an entry will exist for
+// every possible prop/key of the input.
+// @see https://github.com/sindresorhus/ts-extras/blob/44f57392c5f027268330771996c4fdf9260b22d6/source/object-from-entries.ts)
+type fromEntriesArrayWithLiteralKeys<Entries extends IterableContainer<Entry>> =
+  {
+    [K in AllKeys<Entries>]?: ValueForKey<Entries, K>;
+  };
+
+type AllKeys<Entries extends IterableContainer<Entry>> = Extract<
+  Entries[number],
+  Entry
+>[0];
+
+type ValueForKey<
+  Entries extends IterableContainer<Entry>,
+  K extends PropertyKey,
+> =
+  // I tried and failed to simplify the type here! What the ternary does here is
+  // to support the cases where the entries are defined by a single type that
+  // defines all entries, but it defines the keys as a union of literals
+  // (`['a' | 'b', number]`); which is different from the output of toPairs
+  // which would define a separate tuple literal for each key (`['a', number] |
+  // ['b', number]`). We need to support both cases!
+  (Extract<Entries[number], Entry<K>> extends never
+    ? Entries[number]
+    : Extract<Entries[number], Entry<K>>)[1];
+
+export namespace fromEntries {
+  // Strict is simply a retyping of fromEntries, it runs the same runtime logic.
+  export const strict = fromEntries as Strict;
+}
diff --git a/src/fromPairs.ts b/src/fromPairs.ts
index cfb01d9..df2ea0b 100644
--- a/src/fromPairs.ts
+++ b/src/fromPairs.ts
@@ -1,5 +1,3 @@
-/* eslint-disable jsdoc/check-param-names -- ignore for deprecated files */
-
 import type { IterableContainer } from "./_types";
 import { purry } from "./purry";
 
@@ -16,6 +14,8 @@ type Entry<Key extends PropertyKey = PropertyKey, Value = unknown> = readonly [
  * The strict option supports more sophisticated use-cases like those that would
  * result when calling the strict `toPairs` function.
  *
+ * ! **DEPRECATED**: Use `R.fromEntries(pairs)`, for dataLast invocations use the functional form `R.fromEntries()`. Will be removed in V2!
+ *
  * @param pairs - The list of input tuples.
  * @signature
  *   R.fromPairs(tuples)
@@ -33,7 +33,8 @@ type Entry<Key extends PropertyKey = PropertyKey, Value = unknown> = readonly [
  *   ); // => {a: 1} (type: {a: 1})
  * @dataFirst
  * @strict
- * @category Object
+ * @category Deprecated
+ * @deprecated Use `R.fromEntries(pairs)`, for dataLast invocations use the functional form `R.fromEntries()`. Will be removed in V2!
  */
 export function fromPairs<V>(
   pairs: ReadonlyArray<Entry<number, V>>,
@@ -42,36 +43,6 @@ export function fromPairs<V>(
   pairs: ReadonlyArray<Entry<string, V>>,
 ): Record<string, V>;
 
-/**
- * Creates a new object from an array of tuples by pairing up first and second elements as {[key]: value}.
- * If a tuple is not supplied for any element in the array, the element will be ignored
- * If duplicate keys exist, the tuple with the greatest index in the input array will be preferred.
- *
- * The strict option supports more sophisticated use-cases like those that would
- * result when calling the strict `toPairs` function.
- *
- * @param pairs - The list of input tuples.
- * @signature
- *   R.fromPairs()(tuples)
- *   R.fromPairs.strict()(tuples)
- * @example
- *   R.pipe(
- *     [['a', 'b'], ['c', 'd']],
- *     R.fromPairs(),
- *   ); // => {a: 'b', c: 'd'} (type: Record<string, string>)
- *   R.pipe(
- *     ['a', 1] as const,
- *     R.fromPairs.strict(),
- *   ); // => {a: 1} (type: {a: 1})
- * @dataLast
- * @strict
- * @category Object
- */
-// TODO: Add this back when we deprecate headless calls in V2 of Remeda. Currently the dataLast overload breaks the typing for the headless version of the function, which is used widely in the wild.
-// export function fromPairs(): <K extends PropertyKey, V>(
-//   pairs: ReadonlyArray<Entry<K, V>>,
-// ) => Record<K extends string ? string : K extends number ? number : never, V>;
-
 export function fromPairs(): unknown {
   // TODO: When we bump the typescript target beyond ES2019 we can use Object.fromEntries directly here instead of our user-space implementation.
   return purry(fromPairsImplementation, arguments);
@@ -89,13 +60,9 @@ function fromPairsImplementation(
 
 // Redefining the fromPairs function to allow stricter pairs arrays and fine-
 // grained handling of partiality of the output.
-type Strict = // ) => StrictOut<Entries>;
-  //   entries: Entries,
-  // (): <Entries extends IterableContainer<Entry>>(
-  // TODO: Add this back when we deprecate headless calls in V2 of Remeda. Currently the dataLast overload breaks the typing for the headless version of the function, which is used widely in the wild.
-  <Entries extends IterableContainer<Entry>>(
-    entries: Entries,
-  ) => StrictOut<Entries>;
+type Strict = <Entries extends IterableContainer<Entry>>(
+  entries: Entries,
+) => StrictOut<Entries>;
 
 // The 2 kinds of arrays we accept result in different kinds of outputs:
 // 1. If the input is a *tuple*, we know exactly what pairs it would hold,
@@ -167,6 +134,8 @@ type ValueForKey<
     : Extract<Entries[number], Entry<K>>)[1];
 
 export namespace fromPairs {
-  // Strict is simply a retyping of fromPairs, it runs the same runtime logic.
+  /**
+   * @deprecated Use `R.fromEntries.strict(pairs)`, for dataLast invocations use the functional form `R.fromEntries.strict()`. Will be removed in V2!
+   */
   export const strict = fromPairs as Strict;
 }
diff --git a/src/index.ts b/src/index.ts
index b81da1e..e1469b4 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -22,6 +22,7 @@ export * from "./dropFirstBy";
 export * from "./dropLast";
 export * from "./dropLastWhile";
 export * from "./dropWhile";
+export * from "./entries";
 export * from "./equals";
 export * from "./evolve";
 export * from "./filter";
@@ -38,6 +39,7 @@ export * from "./flattenDeep";
 export * from "./floor";
 export * from "./forEach";
 export * from "./forEachObj";
+export * from "./fromEntries";
 export * from "./fromKeys";
 export * from "./fromPairs";
 export * from "./groupBy";
@@ -59,7 +61,9 @@ export * from "./isFunction";
 export * from "./isIncludedIn";
 export * from "./isNil";
 export * from "./isNonNull";
+export * from "./isNonNullish";
 export * from "./isNot";
+export * from "./isNullish";
 export * from "./isNumber";
 export * from "./isObject";
 export * from "./isObjectType";
@@ -96,6 +100,7 @@ export * from "./pathOr";
 export * from "./pick";
 export * from "./pickBy";
 export * from "./pipe";
+export * from "./piped";
 export * from "./prop";
 export * from "./purry";
 export * from "./randomString";
@@ -136,6 +141,9 @@ export * from "./type";
 export * from "./uniq";
 export * from "./uniqBy";
 export * from "./uniqWith";
+export * from "./unique";
+export * from "./uniqueBy";
+export * from "./uniqueWith";
 export * from "./values";
 export * from "./zip";
 export * from "./zipObj";
diff --git a/src/intersection.ts b/src/intersection.ts
index eaf2511..bb87b5a 100644
--- a/src/intersection.ts
+++ b/src/intersection.ts
@@ -7,8 +7,7 @@ import { purry } from "./purry";
 /**
  * Returns a list of elements that exist in both array.
  *
- * **DEPRECATED: equivalent to `R.filter(array, R.isIncludedIn(other))` and will
- * be removed in v2.**.
+ * ! **DEPRECATED**: Use `R.filter(array, R.isIncludedIn(other))`. Will be removed in v2!
  *
  * @param array - The source array.
  * @param other - The second array.
@@ -18,8 +17,8 @@ import { purry } from "./purry";
  *    R.intersection([1, 2, 3], [2, 3, 5]) // => [2, 3]
  * @dataFirst
  * @pipeable
- * @category Array
- * @deprecated Equivalent to `R.filter(array, R.isIncludedIn(other))` and will be removed in v2.
+ * @category Deprecated
+ * @deprecated Use `R.filter(array, R.isIncludedIn(other))`. Will be removed in v2.
  */
 export function intersection<T>(
   source: ReadonlyArray<T>,
@@ -29,8 +28,7 @@ export function intersection<T>(
 /**
  * Returns a list of elements that exist in both array.
  *
- * **DEPRECATED: equivalent to `R.filter(R.isIncludedIn(other))` and will be
- * removed in v2.**.
+ * ! **DEPRECATED**: Use `R.filter(R.isIncludedIn(other))`. Will be removed in v2!
  *
  * @param array - The source array.
  * @param other - The second array.
@@ -40,8 +38,8 @@ export function intersection<T>(
  *    R.intersection([2, 3, 5])([1, 2, 3]) // => [2, 3]
  * @dataLast
  * @pipeable
- * @category Array
- * @deprecated Equivalent to `R.filter(R.isIncludedIn(other))` and will be removed in v2.
+ * @category Deprecated
+ * @deprecated Use `R.filter(R.isIncludedIn(other))`. Will be removed in v2.
  */
 export function intersection<T, K>(
   other: ReadonlyArray<T>,
diff --git a/src/isDefined.ts b/src/isDefined.ts
index a973073..a322b7a 100644
--- a/src/isDefined.ts
+++ b/src/isDefined.ts
@@ -1,6 +1,9 @@
 /**
- * A function that checks if the passed parameter is defined and narrows its type accordingly.
- * To test specifically for `undefined` (and not `null`) use the strict variant of this function.
+ * A function that checks if the passed parameter is defined and narrows its
+ * type accordingly. To test specifically for `undefined` (and not `null`) use
+ * the strict variant of this function.
+ *
+ * ! **DEPRECATED**: If your type accepts `null` use `R.isNullish(data)`, otherwise prefer `R.isDefined.strict(data)`. The **non-strict** version will be removed in V2!
  *
  * @param data - The variable to check.
  * @returns True if the passed input is defined, false otherwise.
@@ -15,6 +18,7 @@
  *    R.isDefined.strict(undefined) //=> false
  * @strict
  * @category Guard
+ * @deprecated If your type accepts `null` use `R.isNullish(data)`, otherwise prefer `R.isDefined.strict(data)`. The **non-strict** version will be removed in V2!
  */
 export function isDefined<T>(data: T): data is NonNullable<T> {
   return data !== undefined && data !== null;
diff --git a/src/isNil.ts b/src/isNil.ts
index ccf1a61..c7a8084 100644
--- a/src/isNil.ts
+++ b/src/isNil.ts
@@ -1,6 +1,8 @@
 /**
  * A function that checks if the passed parameter is Nil (null or undefined) and narrows its type accordingly.
  *
+ * ! **DEPRECATED**: Use `R.isNullish(data)`. Will be removed in V2!
+ *
  * @param data - The variable to check.
  * @returns True if the passed input is Nil (null or undefined), false otherwise.
  * @signature
@@ -9,7 +11,8 @@
  *    R.isNil(undefined) //=> true
  *    R.isNil(null) //=> true
  *    R.isNil('somethingElse') //=> false
- * @category Guard
+ * @category Deprecated
+ * @deprecated Use `R.isNullish(data)`. Will be removed in V2!
  */
 export function isNil<T>(data: T): data is Extract<T, null | undefined> {
   return data === null || data === undefined;
diff --git a/src/isNonNullish.ts b/src/isNonNullish.ts
new file mode 100644
index 0000000..965f75e
--- /dev/null
+++ b/src/isNonNullish.ts
@@ -0,0 +1,19 @@
+/**
+ * A function that checks if the passed parameter is defined *AND* isn't `null`
+ * and narrows its type accordingly.
+ *
+ * @param data - The variable to check.
+ * @returns True if the passed input is defined and isn't `null`, false
+ * otherwise.
+ * @signature
+ *    R.isNonNullish(data)
+ * @example
+ *    R.isNonNullish('string') //=> true
+ *    R.isNonNullish(null) //=> false
+ *    R.isNonNullish(undefined) //=> false
+ * @strict
+ * @category Guard
+ */
+export function isNonNullish<T>(data: T): data is NonNullable<T> {
+  return data !== undefined && data !== null;
+}
diff --git a/src/isNullish.ts b/src/isNullish.ts
new file mode 100644
index 0000000..8e5861c
--- /dev/null
+++ b/src/isNullish.ts
@@ -0,0 +1,18 @@
+/**
+ * A function that checks if the passed parameter is either `null` or
+ * `undefined` and narrows its type accordingly.
+ *
+ * @param data - The variable to check.
+ * @returns True if the passed input is either `null` or `undefined`, false
+ * otherwise.
+ * @signature
+ *    R.isNullish(data)
+ * @example
+ *    R.isNullish(undefined) //=> true
+ *    R.isNullish(null) //=> true
+ *    R.isNullish('somethingElse') //=> false
+ * @category Guard
+ */
+export function isNullish<T>(data: T): data is Extract<T, null | undefined> {
+  return data === null || data === undefined;
+}
diff --git a/src/isObject.ts b/src/isObject.ts
index d9b8edd..6c4e58f 100644
--- a/src/isObject.ts
+++ b/src/isObject.ts
@@ -12,7 +12,7 @@ type DefinitelyObject<T> =
 /**
  * A function that checks if the passed parameter is of type Object and narrows its type accordingly.
  *
- * ! **DEPRECATED: The semantics of this guard are confusing. It excludes Arrays but does not exclude other complex objects like Classes or built-in objects (such as Date, Promise, Error, etc.). Instead, consider using `isObjectType` for a more inclusive validation encompassing any JavaScript "object" type, or `isPlainObject` for a more specific validation targeting simple struct/shape/record-like objects. To replicate the existing logic, use: `const isObject = (x) => isObjectType(x) && !Array.isArray(x)`.** !
+ * ! **DEPRECATED**: Use: `R.isObjectType(data) && R.isNonNull(data) && !R.isArray(data)` or `R.isPlainObject(data)`. Will be removed in V2!
  *
  * @param data - The variable to check.
  * @returns True if the passed input is an Object, Promise, Date or Error, false otherwise.
@@ -24,8 +24,8 @@ type DefinitelyObject<T> =
  *    R.isObject(new Date()) //=> true
  *    R.isObject(new Error("error")) //=> true
  *    R.isObject('somethingElse') //=> false
- * @category Guard
- * @deprecated - The semantics of this guard are confusing. It excludes Arrays but does not exclude other complex objects like Classes or built-in objects (such as Date, Promise, Error, etc.). Instead, consider using `isObjectType` for a more inclusive validation encompassing any JavaScript "object" type, or `isPlainObject` for a more specific validation targeting simple struct/shape/record-like objects. To replicate the existing logic, use: `const isObject = (x) => isObjectType(x) && !Array.isArray(x)`.
+ * @category Deprecated
+ * @deprecated Use: `R.isObjectType(data) && R.isNonNull(data) && !R.isArray(data)` or `R.isPlainObject(data)`. Will be removed in V2!
  */
 export function isObject<T>(data: T | object): data is DefinitelyObject<T> {
   return Boolean(data) && !Array.isArray(data) && typeof data === "object";
diff --git a/src/maxBy.ts b/src/maxBy.ts
index a9f3640..cc5429e 100644
--- a/src/maxBy.ts
+++ b/src/maxBy.ts
@@ -23,7 +23,7 @@ const _maxBy =
 /**
  * Returns the max element using the provided predicate.
  *
- * If you need more control over how "max" is defined, consider using `firstBy` instead. `maxBy` might be deprecated in the future!
+ * ! **DEPRECATED**: Use `R.firstBy([fn, "desc"])`. Will be removed in V2!
  *
  * @param fn - The predicate.
  * @signature
@@ -36,7 +36,8 @@ const _maxBy =
  *    ) // { a: 5 }
  * @dataLast
  * @indexed
- * @category Array
+ * @category Deprecated
+ * @deprecated Use `R.firstBy([fn, "desc"])`. Will be removed in V2!
  */
 export function maxBy<T>(
   fn: (item: T) => number,
@@ -45,6 +46,8 @@ export function maxBy<T>(
 /**
  * Returns the max element using the provided predicate.
  *
+ * ! **DEPRECATED**: Use `R.firstBy(items, fn)`. Will be removed in V2!
+ *
  * @param items - The array.
  * @param fn - The predicate.
  * @signature
@@ -57,7 +60,8 @@ export function maxBy<T>(
  *    ) // { a: 5 }
  * @dataFirst
  * @indexed
- * @category Array
+ * @category Deprecated
+ * @deprecated Use `R.firstBy(items, [fn, "desc"])`. Will be removed in V2!
  */
 export function maxBy<T>(
   items: ReadonlyArray<T>,
diff --git a/src/minBy.ts b/src/minBy.ts
index 5f3201a..06cba0b 100644
--- a/src/minBy.ts
+++ b/src/minBy.ts
@@ -23,7 +23,7 @@ const _minBy =
 /**
  * Returns the min element using the provided predicate.
  *
- * If you need more control over how "min" is defined, consider using `firstBy` instead. `minBy` might be deprecated in the future!
+ * ! **DEPRECATED**: Use `R.firstBy(fn)`. Will be removed in V2!
  *
  * @param fn - The predicate.
  * @signature
@@ -36,7 +36,8 @@ const _minBy =
  *    ) // { a: 1 }
  * @dataLast
  * @indexed
- * @category Array
+ * @category Deprecated
+ * @deprecated Use `R.firstBy(fn)`. Will be removed in V2!
  */
 export function minBy<T>(
   fn: (item: T) => number,
@@ -45,6 +46,8 @@ export function minBy<T>(
 /**
  * Returns the min element using the provided predicate.
  *
+ * ! **DEPRECATED**: Use `R.firstBy(items, fn)`. Will be removed in V2!
+ *
  * @param items - The array.
  * @param fn - The predicate.
  * @signature
@@ -57,7 +60,8 @@ export function minBy<T>(
  *    ) // { a: 1 }
  * @dataFirst
  * @indexed
- * @category Array
+ * @category Deprecated
+ * @deprecated Use `R.firstBy(items, fn)`. Will be removed in V2!
  */
 export function minBy<T>(
   items: ReadonlyArray<T>,
diff --git a/src/omit.ts b/src/omit.ts
index 18bf975..d6b78a1 100644
--- a/src/omit.ts
+++ b/src/omit.ts
@@ -1,13 +1,10 @@
-/* eslint-disable jsdoc/check-param-names -- ignore for deprecated files */
-
-import { fromPairs } from "./fromPairs";
+import { fromEntries } from "./fromEntries";
 import { hasAtLeast } from "./hasAtLeast";
 import { purry } from "./purry";
 
 /**
  * Returns a partial copy of an object omitting the keys specified.
  *
- * @param data - The object.
  * @param propNames - The property names.
  * @signature
  *    R.omit(names)(obj);
@@ -65,7 +62,7 @@ function _omit<T extends object, K extends keyof T>(
   }
 
   const asSet = new Set(propNames);
-  return fromPairs(
+  return fromEntries(
     Object.entries(data).filter(([key]) => !asSet.has(key as K)),
   ) as Omit<T, K>;
 }
diff --git a/src/piped.ts b/src/piped.ts
new file mode 100644
index 0000000..29c2bed
--- /dev/null
+++ b/src/piped.ts
@@ -0,0 +1,81 @@
+/* eslint-disable jsdoc/require-param -- We don't document the ops */
+
+import { pipe } from "./pipe";
+
+/**
+ * A dataLast version of `pipe` that could be used to provide more complex
+ * computations to functions that except a function as a param (like `map`,
+ * `filter`, `groupBy`, etc...).
+ *
+ * The first function must be always annotated. Other functions are
+ * automatically inferred.
+ *
+ * @signature
+ *    R.piped(...ops)(data);
+ * @example
+ *    R.filter(
+ *      [{ a: 1 }, { a: 2 }, { a: 3 }],
+ *      R.piped(
+ *        R.prop('a'),
+ *        (x) => x % 2 === 0,
+ *      ),
+ *    ); // => [{ a: 2 }]
+ * @category Function
+ */
+export function piped<A, B>(op1: (input: A) => B): (value: A) => B;
+
+export function piped<A, B, C>(
+  op1: (input: A) => B,
+  op2: (input: B) => C,
+): (value: A) => C;
+
+export function piped<A, B, C, D>(
+  op1: (input: A) => B,
+  op2: (input: B) => C,
+  op3: (input: C) => D,
+): (value: A) => D;
+
+export function piped<A, B, C, D, E>(
+  op1: (input: A) => B,
+  op2: (input: B) => C,
+  op3: (input: C) => D,
+  op4: (input: D) => E,
+): (value: A) => E;
+
+export function piped<A, B, C, D, E, F>(
+  op1: (input: A) => B,
+  op2: (input: B) => C,
+  op3: (input: C) => D,
+  op4: (input: D) => E,
+  op5: (input: E) => F,
+): (value: A) => F;
+
+export function piped<A, B, C, D, E, F, G>(
+  op1: (input: A) => B,
+  op2: (input: B) => C,
+  op3: (input: C) => D,
+  op4: (input: D) => E,
+  op5: (input: E) => F,
+  op6: (input: F) => G,
+): (value: A) => G;
+
+export function piped<A, B, C, D, E, F, G, H>(
+  op1: (input: A) => B,
+  op2: (input: B) => C,
+  op3: (input: C) => D,
+  op4: (input: D) => E,
+  op5: (input: E) => F,
+  op6: (input: F) => G,
+  op7: (input: G) => H,
+): (value: A) => H;
+
+export function piped(
+  ...operations: ReadonlyArray<(input: unknown) => unknown>
+) {
+  return (value: unknown): unknown =>
+    pipe(
+      value,
+      // @ts-expect-error [ts2556] - We can't avoid this error because pipe is typed for users and this is an internal function
+      ...operations,
+    );
+}
diff --git a/src/reject.ts b/src/reject.ts
index fa1a46f..ed3f871 100644
--- a/src/reject.ts
+++ b/src/reject.ts
@@ -1,4 +1,4 @@
-/* eslint-disable jsdoc/check-param-names -- ignore for deprecated files */
+/* eslint-disable jsdoc/require-param, jsdoc/require-example, jsdoc/require-description, jsdoc/check-param-names -- ignore for deprecated files */
 
 import { _reduceLazy } from "./_reduceLazy";
 import { _toLazyIndexed } from "./_toLazyIndexed";
@@ -9,6 +9,8 @@ import { purry } from "./purry";
 /**
  * Reject the elements of an array that meet the condition specified in a callback function.
  *
+ * ! **DEPRECATED**: Use `R.filter(items, R.isNot(fn))`. Will be removed in V2!
+ *
  * @param items - The array to reject.
  * @param fn - The callback function.
  * @signature
@@ -20,7 +22,8 @@ import { purry } from "./purry";
  * @dataFirst
  * @indexed
  * @pipeable
- * @category Array
+ * @category Deprecated
+ * @deprecated Use `R.filter(items, R.isNot(fn))`. Will be removed in V2!
  */
 export function reject<T>(
   items: ReadonlyArray<T>,
@@ -30,6 +33,8 @@ export function reject<T>(
 /**
  * Reject the elements of an array that meet the condition specified in a callback function.
  *
+ * ! **DEPRECATED**: Use `R.filter(R.isNot(fn))`. Will be removed in V2!
+ *
  * @param items - The array to reject.
  * @param fn - The callback function.
  * @signature
@@ -41,7 +46,8 @@ export function reject<T>(
  * @dataFirst
  * @indexed
  * @pipeable
- * @category Array
+ * @category Deprecated
+ * @deprecated Use `R.filter(R.isNot(fn))`. Will be removed in V2!
  */
 export function reject<T>(
   fn: Pred<T, boolean>,
@@ -69,13 +75,21 @@ const _lazy =
       : { done: false, hasNext: true, next: item };
 
 export namespace reject {
+  /**
+   * @deprecated Use `R.filter.indexed(items, (item, index, array) => !fn(item, index, array))`. Will be removed in V2!
+   */
   export function indexed<T, K>(
     array: ReadonlyArray<T>,
     fn: PredIndexed<T, boolean>,
   ): Array<K>;
+
+  /**
+   * @deprecated Use `R.filter.indexed((item, index, array) => !fn(item, index, array))`. Will be removed in V2!
+   */
   export function indexed<T, K>(
     fn: PredIndexed<T, boolean>,
   ): (array: ReadonlyArray<T>) => Array<K>;
+
   export function indexed(): unknown {
     return purry(_reject(true), arguments, reject.lazyIndexed);
   }
diff --git a/src/toPairs.ts b/src/toPairs.ts
index 05ca2d2..d6e3e03 100644
--- a/src/toPairs.ts
+++ b/src/toPairs.ts
@@ -1,10 +1,10 @@
-/* eslint-disable jsdoc/check-param-names -- ignore for deprecated files */
-
 import { purry } from "./purry";
 
 /**
  * Returns an array of key/values of the enumerable properties of an object.
  *
+ * ! **DEPRECATED** Use `R.entries(object)`, for dataLast invocations use the functional form `R.entries()`. Will be removed in V2!
+ *
  * @param object - Object to return keys and values of.
  * @signature
  *    R.toPairs(object)
@@ -22,35 +22,13 @@ import { purry } from "./purry";
  *    ); // => [['a', 1]] typed Array<['a', 1]>
  * @dataFirst
  * @strict
- * @category Object
+ * @category Deprecated
+ * @deprecated Use `R.entries(object)`, for dataLast invocations use the functional form `R.entries()`. Will be removed in V2!
  */
 export function toPairs<T>(
   object: Readonly<Record<string, T>>,
 ): Array<[string, T]>;
 
-/**
- * Returns an array of key/values of the enumerable properties of an object.
- *
- * @param object - Object to return keys and values of.
- * @signature
- *    R.toPairs()(object)
- *    R.toPairs.strict()(object)
- * @example
- *    R.pipe(
- *      { a: 1, b: 2, c: 3 },
- *      toPairs(),
- *    ); // => [['a', 1], ['b', 2], ['c', 3]]
- *    R.pipe(
- *      { a: 1 } as const,
- *      toPairs.strict(),
- *    ); // => [['a', 1]] typed Array<['a', 1]>
- * @dataLast
- * @strict
- * @category Object
- */
-// TODO: Add this back when we deprecate headless calls in V2 of Remeda. Currently the dataLast overload breaks the typing for the headless version of the function, which is used widely in the wild.
-// export function toPairs(): <T>(object: Record<string, T>) => Array<[string, T]>;
-
 export function toPairs(): unknown {
   return purry(Object.entries, arguments);
 }
@@ -59,10 +37,11 @@ type Pairs<T> = Array<
   { [K in keyof T]-?: [key: K, value: Required<T>[K]] }[keyof T]
 >;
 
-type Strict = // (): <T extends NonNullable<unknown>>(object: T) => Pairs<T>;
-  // TODO: Add this back when we deprecate headless calls in V2 of Remeda. Currently the dataLast overload breaks the typing for the headless version of the function, which is used widely in the wild.
-  <T extends NonNullable<unknown>>(object: T) => Pairs<T>;
+type Strict = <T extends NonNullable<unknown>>(object: T) => Pairs<T>;
 
 export namespace toPairs {
+  /**
+   * @deprecated Use `R.entries.strict(object)`, for dataLast invocations use the functional form `R.entries.strict(object)`. Will be removed in V2!
+   */
   export const strict = toPairs as Strict;
 }
diff --git a/src/type.ts b/src/type.ts
index 2bd4a43..2a56b10 100644
--- a/src/type.ts
+++ b/src/type.ts
@@ -1,7 +1,10 @@
 // from https://github.com/ramda/ramda/blob/master/source/type.js
+
 /**
  * Gives a single-word string description of the (native) type of a value, returning such answers as 'Object', 'Number', 'Array', or 'Null'. Does not attempt to distinguish user Object types any further, reporting them all as 'Object'.
  *
+ * ! **DEPRECATED**: Use `typeof val`, or one of the guards offered by this library. Will be removed in V2! We don't know what the use case for this function is. If you have a use case reach out via a GitHub issue so we can discuss this.
+ *
  * @param val - Value to return type of.
  * @signature
  *    R.type(obj)
@@ -15,7 +18,8 @@
  *    R.type(/[A-z]/); //=> "RegExp"
  *    R.type(() => {}); //=> "Function"
  *    R.type(undefined); //=> "Undefined"
- * @category Type
+ * @category Deprecated
+ * @deprecated Use `typeof val`, or one of the guards offered by this library. Will be removed in V2! We don't know what the use case for this function is. If you have a use case reach out via a GitHub issue so we can discuss this.
  */
 export function type(val: unknown): string {
   return val === null
diff --git a/src/uniq.ts b/src/uniq.ts
index 161a931..13fac9a 100644
--- a/src/uniq.ts
+++ b/src/uniq.ts
@@ -8,6 +8,8 @@ import { purry } from "./purry";
  * Returns a new array containing only one copy of each element in the original list.
  * Elements are compared by reference using Set.
  *
+ * ! **DEPRECATED**: Use `R.unique(array)`. Will be removed in V2.
+ *
  * @param array - The array to filter.
  * @signature
  *    R.uniq(array)
@@ -15,7 +17,8 @@ import { purry } from "./purry";
  *    R.uniq([1, 2, 2, 5, 1, 6, 7]) // => [1, 2, 5, 6, 7]
  * @dataFirst
  * @pipeable
- * @category Array
+ * @category Deprecated
+ * @deprecated Use `R.unique(array)`. Will be removed in V2.
  */
 export function uniq<T>(array: ReadonlyArray<T>): Array<T>;
 
@@ -23,6 +26,8 @@ export function uniq<T>(array: ReadonlyArray<T>): Array<T>;
  * Returns a new array containing only one copy of each element in the original list.
  * Elements are compared by reference using Set.
  *
+ * ! **DEPRECATED**: Use `R.unique()`. Will be removed in V2.
+ *
  * @param array - The array to filter.
  * @signature
  *    R.uniq()(array)
@@ -34,7 +39,8 @@ export function uniq<T>(array: ReadonlyArray<T>): Array<T>;
  *    ) // => [1, 2, 5]
  * @dataLast
  * @pipeable
- * @category Array
+ * @category Deprecated
+ * @deprecated Use `R.unique()`. Will be removed in V2.
  */
 export function uniq<T>(): (array: ReadonlyArray<T>) => Array<T>;
 
diff --git a/src/uniqBy.ts b/src/uniqBy.ts
index a132d1c..3f1c0f8 100644
--- a/src/uniqBy.ts
+++ b/src/uniqBy.ts
@@ -1,17 +1,17 @@
+/* eslint-disable jsdoc/require-param-description, jsdoc/check-param-names -- Deprecated file */
+
 import { _reduceLazy } from "./_reduceLazy";
 import type { LazyEvaluator } from "./pipe";
 import { purry } from "./purry";
 
-export function uniqBy<T, K>(
-  array: ReadonlyArray<T>,
-  transformer: (item: T) => K,
-): Array<T>;
-
 /**
  * Returns a new array containing only one copy of each element in the original list transformed by a function.
  * Elements are compared by reference using Set.
  *
- * @param array
+ * ! **DEPRECATED**: Use `R.uniqueBy(array, fn)`. Will be removed in V2!
+ *
+ * @param array - The array to filter.
+ * @param transformer
  * @signature
  *    R.uniqBy(array, fn)
  * @example
@@ -19,15 +19,37 @@ export function uniqBy<T, K>(
  *     [{ n: 1 }, { n: 2 }, { n: 2 }, { n: 5 }, { n: 1 }, { n: 6 }, { n: 7 }],
  *     (obj) => obj.n,
  *    ) // => [{n: 1}, {n: 2}, {n: 5}, {n: 6}, {n: 7}]
+ * @dataFirst
+ * @pipeable
+ * @category Deprecated
+ * @deprecated Use `R.uniqueBy(array, fn)`. Will be removed in V2!
+ */
+export function uniqBy<T, K>(
+  array: ReadonlyArray<T>,
+  transformer: (item: T) => K,
+): Array<T>;
+
+/**
+ * Returns a new array containing only one copy of each element in the original list transformed by a function.
+ * Elements are compared by reference using Set.
+ *
+ * ! **DEPRECATED**: Use `R.uniqueBy(fn)`. Will be removed in V2!
+ *
+ * @param array - The array to filter.
+ * @param transformer
+ * @signature
+ *    R.uniqBy(fn)(array)
+ * @example
  *    R.pipe(
  *      [{n: 1}, {n: 2}, {n: 2}, {n: 5}, {n: 1}, {n: 6}, {n: 7}], // only 4 iterations
  *      R.uniqBy(obj => obj.n),
  *      R.take(3)
  *    ) // => [{n: 1}, {n: 2}, {n: 5}]
+ * @dataLast
  * @pipeable
- * @category Array
+ * @category Deprecated
+ * @deprecated Use `R.uniqueBy(fn)`. Will be removed in V2!
  */
-
 export function uniqBy<T, K>(
   transformer: (item: T) => K,
 ): (array: ReadonlyArray<T>) => Array<T>;
diff --git a/src/uniqWith.ts b/src/uniqWith.ts
index 328d9b8..45c2941 100644
--- a/src/uniqWith.ts
+++ b/src/uniqWith.ts
@@ -9,6 +9,8 @@ type IsEquals<T> = (a: T, b: T) => boolean;
  * Returns a new array containing only one copy of each element in the original list.
  * Elements are compared by custom comparator isEquals.
  *
+ * ! **DEPRECATED**: Use `R.uniqueWith(array, isEquals)`. Will be removed in V2!
+ *
  * @param array - The array to filter.
  * @param isEquals - The comparator.
  * @signature
@@ -19,7 +21,8 @@ type IsEquals<T> = (a: T, b: T) => boolean;
  *      R.equals,
  *    ) // => [{a: 1}, {a: 2}, {a: 5}, {a: 6}, {a: 7}]
  * @dataFirst
- * @category Array
+ * @category Deprecated
+ * @deprecated Use `R.uniqueWith(array, isEquals)`. Will be removed in V2!
  */
 export function uniqWith<T>(
   array: ReadonlyArray<T>,
@@ -30,6 +33,8 @@ export function uniqWith<T>(
  * Returns a new array containing only one copy of each element in the original list.
  * Elements are compared by custom comparator isEquals.
  *
+ * ! **DEPRECATED**: Use `R.uniqueWith(isEquals)`. Will be removed in V2!
+ *
  * @param isEquals - The comparator.
  * @signature R.uniqWith(isEquals)(array)
  * @example
@@ -42,7 +47,8 @@ export function uniqWith<T>(
  *      R.take(3)
  *    ) // => [{a: 1}, {a: 2}, {a: 5}]
  * @dataLast
- * @category Object
+ * @category Deprecated
+ * @deprecated Use `R.uniqueWith(isEquals)`. Will be removed in V2!
  */
 export function uniqWith<T>(
   isEquals: IsEquals<T>,
diff --git a/src/unique.ts b/src/unique.ts
new file mode 100644
index 0000000..8f9fd35
--- /dev/null
+++ b/src/unique.ts
@@ -0,0 +1,57 @@
+import { _reduceLazy } from "./_reduceLazy";
+import type { LazyEvaluator } from "./pipe";
+import { purry } from "./purry";
+
+/**
+ * Returns a new array containing only one copy of each element in the original
+ * list. Elements are compared by reference using Set.
+ *
+ * @param array - The array to filter.
+ * @signature
+ *    R.unique(array)
+ * @example
+ *    R.unique([1, 2, 2, 5, 1, 6, 7]) // => [1, 2, 5, 6, 7]
+ * @dataFirst
+ * @pipeable
+ * @category Array
+ */
+export function unique<T>(array: ReadonlyArray<T>): Array<T>;
+
+/**
+ * Returns a new array containing only one copy of each element in the original
+ * list. Elements are compared by reference using Set.
+ *
+ * @signature
+ *    R.unique()(array)
+ * @example
+ *    R.pipe(
+ *      [1, 2, 2, 5, 1, 6, 7], // only 4 iterations
+ *      R.unique(),
+ *      R.take(3)
+ *    ) // => [1, 2, 5]
+ * @dataLast
+ * @pipeable
+ * @category Array
+ */
+export function unique<T>(): (array: ReadonlyArray<T>) => Array<T>;
+
+export function unique(): unknown {
+  return purry(uniqueImplementation, arguments, unique.lazy);
+}
+
+function uniqueImplementation<T>(array: ReadonlyArray<T>): Array<T> {
+  return _reduceLazy(array, unique.lazy());
+}
+
+export namespace unique {
+  export function lazy<T>(): LazyEvaluator<T> {
+    const set = new Set<T>();
+    return (value) => {
+      if (set.has(value)) {
+        return { done: false, hasNext: false };
+      }
+      set.add(value);
+      return { done: false, hasNext: true, next: value };
+    };
+  }
+}
diff --git a/src/uniqueBy.ts b/src/uniqueBy.ts
new file mode 100644
index 0000000..f8055ea
--- /dev/null
+++ b/src/uniqueBy.ts
@@ -0,0 +1,70 @@
+import { _reduceLazy } from "./_reduceLazy";
+import type { LazyEvaluator } from "./pipe";
+import { purry } from "./purry";
+
+/**
+ * Returns a new array containing only one copy of each element in the original
+ * list transformed by a function. Elements are compared by reference using Set.
+ *
+ * @param data - The array to filter.
+ * @param keyFunction - Extracts a value that would be used to compare elements.
+ * @signature
+ *    R.uniqueBy(array, fn)
+ * @example
+ *    R.uniqueBy(
+ *     [{ n: 1 }, { n: 2 }, { n: 2 }, { n: 5 }, { n: 1 }, { n: 6 }, { n: 7 }],
+ *     (obj) => obj.n,
+ *    ) // => [{n: 1}, {n: 2}, {n: 5}, {n: 6}, {n: 7}]
+ * @dataFirst
+ * @pipeable
+ * @category Array
+ */
+export function uniqueBy<T, K>(
+  data: ReadonlyArray<T>,
+  keyFunction: (item: T) => K,
+): Array<T>;
+
+/**
+ * Returns a new array containing only one copy of each element in the original
+ * list transformed by a function. Elements are compared by reference using Set.
+ *
+ * @param keyFunction - Extracts a value that would be used to compare elements.
+ * @signature
+ *    R.uniqueBy(fn)(array)
+ * @example
+ *    R.pipe(
+ *      [{n: 1}, {n: 2}, {n: 2}, {n: 5}, {n: 1}, {n: 6}, {n: 7}], // only 4 iterations
+ *      R.uniqueBy(obj => obj.n),
+ *      R.take(3)
+ *    ) // => [{n: 1}, {n: 2}, {n: 5}]
+ * @dataLast
+ * @pipeable
+ * @category Array
+ */
+export function uniqueBy<T, K>(
+  keyFunction: (item: T) => K,
+): (data: ReadonlyArray<T>) => Array<T>;
+
+export function uniqueBy(): unknown {
+  return purry(uniqueByImplementation, arguments, lazyUniqueBy);
+}
+
+function uniqueByImplementation<T, K>(
+  data: ReadonlyArray<T>,
+  keyFunction: (item: T) => K,
+): Array<T> {
+  return _reduceLazy(data, lazyUniqueBy(keyFunction));
+}
+
+function lazyUniqueBy<T, K>(keyFunction: (item: T) => K): LazyEvaluator<T> {
+  const set = new Set<K>();
+  return (value) => {
+    const key = keyFunction(value);
+    if (set.has(key)) {
+      return { done: false, hasNext: false };
+    }
+
+    set.add(key);
+    return { done: false, hasNext: true, next: value };
+  };
+}
diff --git a/src/uniqueWith.ts b/src/uniqueWith.ts
new file mode 100644
index 0000000..a7cd558
--- /dev/null
+++ b/src/uniqueWith.ts
@@ -0,0 +1,73 @@
+import { _reduceLazy } from "./_reduceLazy";
+import { _toLazyIndexed } from "./_toLazyIndexed";
+import type { LazyEvaluator } from "./pipe";
+import { purry } from "./purry";
+
+type IsEquals<T> = (a: T, b: T) => boolean;
+
+/**
+ * Returns a new array containing only one copy of each element in the original
+ * list. Elements are compared by custom comparator isEquals.
+ *
+ * @param array - The array to filter.
+ * @param isEquals - The comparator.
+ * @signature
+ *    R.uniqueWith(array, isEquals)
+ * @example
+ *    R.uniqueWith(
+ *      [{a: 1}, {a: 2}, {a: 2}, {a: 5}, {a: 1}, {a: 6}, {a: 7}],
+ *      R.equals,
+ *    ) // => [{a: 1}, {a: 2}, {a: 5}, {a: 6}, {a: 7}]
+ * @dataFirst
+ * @category Array
+ */
+export function uniqueWith<T>(
+  array: ReadonlyArray<T>,
+  isEquals: IsEquals<T>,
+): Array<T>;
+
+/**
+ * Returns a new array containing only one copy of each element in the original
+ * list. Elements are compared by custom comparator isEquals.
+ *
+ * @param isEquals - The comparator.
+ * @signature R.uniqueWith(isEquals)(array)
+ * @example
+ *    R.uniqueWith(R.equals)(
+ *      [{a: 1}, {a: 2}, {a: 2}, {a: 5}, {a: 1}, {a: 6}, {a: 7}],
+ *    ) // => [{a: 1}, {a: 2}, {a: 5}, {a: 6}, {a: 7}]
+ *    R.pipe(
+ *      [{a: 1}, {a: 2}, {a: 2}, {a: 5}, {a: 1}, {a: 6}, {a: 7}], // only 4 iterations
+ *      R.uniqueWith(R.equals),
+ *      R.take(3)
+ *    ) // => [{a: 1}, {a: 2}, {a: 5}]
+ * @dataLast
+ * @category Object
+ */
+export function uniqueWith<T>(
+  isEquals: IsEquals<T>,
+): (array: ReadonlyArray<T>) => Array<T>;
+
+export function uniqueWith(): unknown {
+  return purry(uniqueWithImplementation, arguments, uniqueWith.lazy);
+}
+
+function uniqueWithImplementation<T>(
+  array: ReadonlyArray<T>,
+  isEquals: IsEquals<T>,
+): Array<T> {
+  const lazy = uniqueWith.lazy(isEquals);
+  return _reduceLazy(array, lazy, true);
+}
+
+const _lazy =
+  <T>(isEquals: IsEquals<T>): LazyEvaluator<T> =>
+  (value, index, array) =>
+    array !== undefined &&
+    array.findIndex((otherValue) => isEquals(value, otherValue)) === index
+      ? { done: false, hasNext: true, next: value }
+      : { done: false, hasNext: false };
+
+export namespace uniqueWith {
+  export const lazy = _toLazyIndexed(_lazy);
+}
diff --git a/src/zipObj.ts b/src/zipObj.ts
index e15c482..84ebd10 100644
--- a/src/zipObj.ts
+++ b/src/zipObj.ts
@@ -4,6 +4,8 @@ import { purry } from "./purry";
  * Creates a new object from two supplied lists by pairing up equally-positioned items.
  * Key/value pairing is truncated to the length of the shorter of the two lists.
  *
+ * ! **DEPRECATED**: Use `R.fromEntries.strict(R.zip(first, second))`. Will be removed in V2!
+ *
  * @param first - The first input list.
  * @param second - The second input list.
  * @signature
@@ -11,7 +13,8 @@ import { purry } from "./purry";
  * @example
  *   R.zipObj(['a', 'b'], [1, 2]) // => {a: 1, b: 2}
  * @dataFirst
- * @category Array
+ * @category Deprecated
+ * @deprecated Use `R.fromEntries.strict(R.zip(first, second))`. Will be removed in V2!
  */
 export function zipObj<F extends PropertyKey, S>(
   first: ReadonlyArray<F>,
@@ -22,13 +25,16 @@ export function zipObj<F extends PropertyKey, S>(
  * Creates a new object from two supplied lists by pairing up equally-positioned items.
  * Key/value pairing is truncated to the length of the shorter of the two lists.
  *
+ * ! **DEPRECATED**: Use `<F extends PropertyKey>(first: ReadonlyArray<F>) => R.fromEntries.strict(R.zip(first, second))` or if as part of a pipe: `R.pipe(..., R.zip(second), R.fromEntries.strict(), ...)`. Will be removed in V2!
+ *
  * @param second - The second input list.
  * @signature
  *   R.zipObj(second)(first)
  * @example
  *   R.zipObj([1, 2])(['a', 'b']) // => {a: 1, b: 2}
  * @dataLast
- * @category Array
+ * @category Deprecated
+ * @deprecated Use `<F extends PropertyKey>(first: ReadonlyArray<F>) => R.fromEntries.strict(R.zip(first, second))` or if as part of a pipe: `R.pipe(..., R.zip(second), R.fromEntries.strict(), ...)`. Will be removed in V2!
  */
 export function zipObj<S>(
   second: ReadonlyArray<S>,


Test patch:
diff --git a/src/entries.test.ts b/src/entries.test.ts
new file mode 100644
index 0000000..1baf7f0
--- /dev/null
+++ b/src/entries.test.ts
@@ -0,0 +1,72 @@
+import { entries } from "./entries";
+
+describe("runtime", () => {
+  test("dataFirst", () => {
+    expect(entries({ a: 1, b: 2, c: 3 })).toEqual([
+      ["a", 1],
+      ["b", 2],
+      ["c", 3],
+    ]);
+  });
+
+  describe("typing", () => {
+    test("with known properties", () => {
+      const actual = entries({ a: 1, b: 2, c: 3 });
+      expectTypeOf(actual).toEqualTypeOf<Array<[string, number]>>();
+    });
+
+    test("with optional properties", () => {
+      const actual = entries({} as { a?: string });
+      expectTypeOf(actual).toEqualTypeOf<Array<[string, string]>>();
+    });
+
+    test("with undefined properties", () => {
+      const actual = entries({ a: undefined } as {
+        a: string | undefined;
+      });
+      expectTypeOf(actual).toEqualTypeOf<Array<[string, string | undefined]>>();
+    });
+
+    test("with unknown properties", () => {
+      const actual = entries({} as Record<string, unknown>);
+      expectTypeOf(actual).toEqualTypeOf<Array<[string, unknown]>>();
+    });
+  });
+});
+
+describe("entries.strict", () => {
+  test("should return pairs", () => {
+    const actual = entries.strict({ a: 1, b: 2, c: 3 });
+    expect(actual).toEqual([
+      ["a", 1],
+      ["b", 2],
+      ["c", 3],
+    ]);
+  });
+
+  describe("typing", () => {
+    test("with known properties", () => {
+      const actual = entries.strict({ a: 1, b: 2, c: 3 } as const);
+      expectTypeOf(actual).toEqualTypeOf<
+        Array<["a", 1] | ["b", 2] | ["c", 3]>
+      >();
+    });
+
+    test("with optional properties", () => {
+      const actual = entries.strict({} as { a?: string });
+      expectTypeOf(actual).toEqualTypeOf<Array<["a", string]>>();
+    });
+
+    test("with undefined properties", () => {
+      const actual = entries.strict({ a: undefined } as {
+        a: string | undefined;
+      });
+      expectTypeOf(actual).toEqualTypeOf<Array<["a", string | undefined]>>();
+    });
+
+    test("with unknown properties", () => {
+      const actual = entries.strict({} as Record<string, unknown>);
+      expectTypeOf(actual).toEqualTypeOf<Array<[string, unknown]>>();
+    });
+  });
+});
diff --git a/src/fromEntries.test.ts b/src/fromEntries.test.ts
new file mode 100644
index 0000000..2d7ae8d
--- /dev/null
+++ b/src/fromEntries.test.ts
@@ -0,0 +1,271 @@
+import { fromEntries } from "./fromEntries";
+
+const tuples: Array<[string, number]> = [
+  ["a", 1],
+  ["b", 2],
+  ["c", 3],
+];
+
+describe("runtime", () => {
+  test("dataFirst", () => {
+    expect(fromEntries(tuples)).toEqual({
+      a: 1,
+      b: 2,
+      c: 3,
+    });
+  });
+});
+
+describe("typings", () => {
+  test("arrays", () => {
+    const actual = fromEntries(tuples);
+    assertType<Record<string, number>>(actual);
+  });
+  test("arrays with mixed type value", () => {
+    const actual = fromEntries<number | string>([
+      ["a", 2],
+      ["b", "c"],
+    ]);
+    assertType<Record<string, number | string>>(actual);
+  });
+});
+
+describe("Strict (with readonly inputs)", () => {
+  test("trivial empty case", () => {
+    const result = fromEntries.strict([] as const);
+    expectTypeOf(result).toEqualTypeOf({} as const);
+    expect(result).toStrictEqual({});
+  });
+
+  test("trivial single entry const case", () => {
+    const result = fromEntries.strict([["a", 1]] as const);
+    expectTypeOf(result).toMatchTypeOf<{ a: 1 }>();
+    expect(result).toStrictEqual({ a: 1 });
+  });
+
+  test("trivial multi entry const case", () => {
+    const result = fromEntries.strict([
+      ["a", 1],
+      ["b", 2],
+      ["c", 3],
+    ] as const);
+    expectTypeOf(result).toMatchTypeOf<{ a: 1; b: 2; c: 3 }>();
+    expect(result).toStrictEqual({ a: 1, b: 2, c: 3 });
+  });
+
+  test("runtime empty well defined array", () => {
+    const arr: ReadonlyArray<["a", 1] | ["b", 2] | ["c", 3]> = [];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toEqualTypeOf<{ a?: 1; b?: 2; c?: 3 }>();
+    expect(result).toStrictEqual({});
+  });
+
+  test("runtime single value well defined array", () => {
+    const arr: ReadonlyArray<["a", 1] | ["b", 2] | ["c", 3]> = [["a", 1]];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toEqualTypeOf<{ a?: 1; b?: 2; c?: 3 }>();
+    expect(result).toStrictEqual({ a: 1 });
+  });
+
+  test("runtime multi-value well defined array", () => {
+    const arr: ReadonlyArray<["a", 1] | ["b", 2] | ["c", 3]> = [
+      ["a", 1],
+      ["b", 2],
+      ["c", 3],
+    ];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toEqualTypeOf<{ a?: 1; b?: 2; c?: 3 }>();
+    expect(result).toStrictEqual({ a: 1, b: 2, c: 3 });
+  });
+
+  test("runtime mixed tuple with rest (first)", () => {
+    const arr: readonly [["a", 1], ...ReadonlyArray<["b", 2] | ["c", 3]>] = [
+      ["a", 1],
+    ];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toMatchTypeOf<{ a: 1; b?: 2; c?: 3 }>();
+    expect(result).toStrictEqual({ a: 1 });
+  });
+
+  test("runtime mixed tuple with rest (last)", () => {
+    const arr: readonly [...ReadonlyArray<["b", 2] | ["c", 3]>, ["a", 1]] = [
+      ["a", 1],
+    ];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toMatchTypeOf<{ a: 1; b?: 2; c?: 3 }>();
+    expect(result).toStrictEqual({ a: 1 });
+  });
+
+  test("runtime empty generic type", () => {
+    const arr: ReadonlyArray<readonly [string, boolean]> = [];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toEqualTypeOf<Record<string, boolean>>();
+    expect(result).toStrictEqual({});
+  });
+
+  test("runtime single value generic type", () => {
+    const arr: ReadonlyArray<readonly [string, boolean]> = [["hello", true]];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toEqualTypeOf<Record<string, boolean>>();
+    expect(result).toStrictEqual({ hello: true });
+  });
+
+  test("runtime multi-value generic type", () => {
+    const arr: ReadonlyArray<readonly [string, boolean]> = [
+      ["hello", true],
+      ["world", false],
+    ];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toEqualTypeOf<Record<string, boolean>>();
+    expect(result).toStrictEqual({ hello: true, world: false });
+  });
+
+  test("mixed literals and generics", () => {
+    const arr: ReadonlyArray<
+      readonly ["a", 1] | readonly [`testing_${string}`, boolean]
+    > = [["a", 1]];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toMatchTypeOf<
+      Partial<Record<`testing_${string}`, boolean>> & { a?: 1 }
+    >();
+    expect(result).toStrictEqual({ a: 1 });
+  });
+
+  test("array with literal keys", () => {
+    const arr: ReadonlyArray<readonly ["a" | "b" | "c", "d"]> = [["a", "d"]];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toEqualTypeOf<Partial<Record<"a" | "b" | "c", "d">>>();
+    expect(result).toStrictEqual({ a: "d" });
+  });
+
+  test("backwards compatibility (number)", () => {
+    const arr: ReadonlyArray<readonly [number, 123]> = [[1, 123]];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toEqualTypeOf<Record<number, 123>>();
+    expect(result).toStrictEqual({ 1: 123 });
+  });
+
+  test("backwards compatibility (string)", () => {
+    const arr: ReadonlyArray<readonly [string, 123]> = [["a", 123]];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toEqualTypeOf<Record<string, 123>>();
+    expect(result).toStrictEqual({ a: 123 });
+  });
+});
+
+describe("Strict (with non-readonly inputs)", () => {
+  test("trivial empty case", () => {
+    const result = fromEntries.strict([]);
+    expectTypeOf(result).toEqualTypeOf({} as const);
+    expect(result).toStrictEqual({});
+  });
+
+  test("trivial single entry const case", () => {
+    const result = fromEntries.strict([["a", 1]]);
+    expectTypeOf(result).toMatchTypeOf<Record<string, number>>();
+    expect(result).toStrictEqual({ a: 1 });
+  });
+
+  test("trivial multi entry const case", () => {
+    const result = fromEntries.strict([
+      ["a", 1],
+      ["b", 2],
+      ["c", 3],
+    ]);
+    expectTypeOf(result).toMatchTypeOf<Record<string, number>>();
+    expect(result).toStrictEqual({ a: 1, b: 2, c: 3 });
+  });
+
+  test("runtime empty well defined array", () => {
+    const arr: Array<["a", 1] | ["b", 2] | ["c", 3]> = [];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toEqualTypeOf<{ a?: 1; b?: 2; c?: 3 }>();
+    expect(result).toStrictEqual({});
+  });
+
+  test("runtime single value well defined array", () => {
+    const arr: Array<["a", 1] | ["b", 2] | ["c", 3]> = [["a", 1]];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toEqualTypeOf<{ a?: 1; b?: 2; c?: 3 }>();
+    expect(result).toStrictEqual({ a: 1 });
+  });
+
+  test("runtime multi-value well defined array", () => {
+    const arr: Array<["a", 1] | ["b", 2] | ["c", 3]> = [
+      ["a", 1],
+      ["b", 2],
+      ["c", 3],
+    ];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toEqualTypeOf<{ a?: 1; b?: 2; c?: 3 }>();
+    expect(result).toStrictEqual({ a: 1, b: 2, c: 3 });
+  });
+
+  test("runtime mixed tuple with rest (first)", () => {
+    const arr: [["a", 1], ...Array<["b", 2] | ["c", 3]>] = [["a", 1]];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toMatchTypeOf<{ a: 1; b?: 2; c?: 3 }>();
+    expect(result).toStrictEqual({ a: 1 });
+  });
+
+  test("runtime mixed tuple with rest (last)", () => {
+    const arr: [...Array<["b", 2] | ["c", 3]>, ["a", 1]] = [["a", 1]];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toMatchTypeOf<{ a: 1; b?: 2; c?: 3 }>();
+    expect(result).toStrictEqual({ a: 1 });
+  });
+
+  test("runtime empty generic type", () => {
+    const arr: Array<[string, boolean]> = [];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toEqualTypeOf<Record<string, boolean>>();
+    expect(result).toStrictEqual({});
+  });
+
+  test("runtime single value generic type", () => {
+    const arr: Array<[string, boolean]> = [["hello", true]];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toEqualTypeOf<Record<string, boolean>>();
+    expect(result).toStrictEqual({ hello: true });
+  });
+
+  test("runtime multi-value generic type", () => {
+    const arr: Array<[string, boolean]> = [
+      ["hello", true],
+      ["world", false],
+    ];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toEqualTypeOf<Record<string, boolean>>();
+    expect(result).toStrictEqual({ hello: true, world: false });
+  });
+
+  test("mixed literals and generics", () => {
+    const arr: Array<["a", 1] | [`testing_${string}`, boolean]> = [["a", 1]];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toMatchTypeOf<
+      Partial<Record<`testing_${string}`, boolean>> & { a?: 1 }
+    >();
+    expect(result).toStrictEqual({ a: 1 });
+  });
+
+  test("array with literal keys", () => {
+    const arr: Array<readonly ["a" | "b" | "c", "d"]> = [["a", "d"]];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toEqualTypeOf<Partial<Record<"a" | "b" | "c", "d">>>();
+    expect(result).toStrictEqual({ a: "d" });
+  });
+
+  test("backwards compatibility (number)", () => {
+    const arr: Array<[number, 123]> = [[1, 123]];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toEqualTypeOf<Record<number, 123>>();
+    expect(result).toStrictEqual({ 1: 123 });
+  });
+
+  test("backwards compatibility (string)", () => {
+    const arr: Array<[string, 123]> = [["a", 123]];
+    const result = fromEntries.strict(arr);
+    expectTypeOf(result).toEqualTypeOf<Record<string, 123>>();
+    expect(result).toStrictEqual({ a: 123 });
+  });
+});
diff --git a/src/isNonNullish.test.ts b/src/isNonNullish.test.ts
new file mode 100644
index 0000000..60b45bc
--- /dev/null
+++ b/src/isNonNullish.test.ts
@@ -0,0 +1,60 @@
+import type {
+  AllTypesDataProviderTypes,
+  TestClass,
+} from "../test/types_data_provider";
+import {
+  ALL_TYPES_DATA_PROVIDER,
+  TYPES_DATA_PROVIDER,
+} from "../test/types_data_provider";
+import { isNonNullish } from "./isNonNullish";
+
+describe("isNonNullish", () => {
+  it("should work as type guard", () => {
+    const data = TYPES_DATA_PROVIDER.date as AllTypesDataProviderTypes;
+    if (isNonNullish(data)) {
+      expect(data instanceof Date).toEqual(true);
+      expectTypeOf(data).toEqualTypeOf<
+        | Array<number>
+        | Date
+        | Error
+        | Map<string, string>
+        | Promise<number>
+        | RegExp
+        | Set<string>
+        | TestClass
+        | Uint8Array
+        | boolean
+        | number
+        | string
+        | symbol
+        | (() => void)
+        | { readonly a: "asd" }
+        | [number, number, number]
+      >();
+    }
+  });
+  it("should work as type guard in filter", () => {
+    const data = ALL_TYPES_DATA_PROVIDER.filter(isNonNullish);
+    expect(data).toHaveLength(16);
+    expectTypeOf(data).toEqualTypeOf<
+      Array<
+        | Array<number>
+        | Date
+        | Error
+        | Map<string, string>
+        | Promise<number>
+        | RegExp
+        | Set<string>
+        | TestClass
+        | Uint8Array
+        | boolean
+        | number
+        | string
+        | symbol
+        | (() => void)
+        | { readonly a: "asd" }
+        | [number, number, number]
+      >
+    >();
+  });
+});
diff --git a/src/isNullish.test.ts b/src/isNullish.test.ts
new file mode 100644
index 0000000..86ecb2b
--- /dev/null
+++ b/src/isNullish.test.ts
@@ -0,0 +1,23 @@
+import type { AllTypesDataProviderTypes } from "../test/types_data_provider";
+import {
+  ALL_TYPES_DATA_PROVIDER,
+  TYPES_DATA_PROVIDER,
+} from "../test/types_data_provider";
+import { isNullish } from "./isNullish";
+
+describe("isNullish", () => {
+  it("should work as type guard", () => {
+    const data = TYPES_DATA_PROVIDER.null as AllTypesDataProviderTypes;
+    if (isNullish(data)) {
+      expect(data).toEqual(null);
+      expectTypeOf(data).toEqualTypeOf<null | undefined>();
+    }
+  });
+
+  it("should work as type guard in filter", () => {
+    const data = ALL_TYPES_DATA_PROVIDER.filter(isNullish);
+    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Intentional for the test.
+    expect(data.every((c) => c === null || c === undefined)).toEqual(true);
+    expectTypeOf(data).toEqualTypeOf<Array<null | undefined>>();
+  });
+});
diff --git a/src/piped.test.ts b/src/piped.test.ts
new file mode 100644
index 0000000..516dad3
--- /dev/null
+++ b/src/piped.test.ts
@@ -0,0 +1,14 @@
+import { piped } from "./piped";
+
+it("should pipe a single operation", () => {
+  const fn = piped((x: number) => x * 2);
+  expect(fn(1)).toEqual(2);
+});
+
+it("should pipe operations", () => {
+  const fn = piped(
+    (x: number) => x * 2,
+    (x) => x * 3,
+  );
+  expect(fn(1)).toEqual(6);
+});
diff --git a/src/unique.test.ts b/src/unique.test.ts
new file mode 100644
index 0000000..8174f2a
--- /dev/null
+++ b/src/unique.test.ts
@@ -0,0 +1,35 @@
+import { createLazyInvocationCounter } from "../test/lazy_invocation_counter";
+import { pipe } from "./pipe";
+import { take } from "./take";
+import { unique } from "./unique";
+
+it("unique", () => {
+  expect(unique([1, 2, 2, 5, 1, 6, 7] as const)).toEqual([1, 2, 5, 6, 7]);
+});
+
+describe("pipe", () => {
+  it("unique", () => {
+    const counter = createLazyInvocationCounter();
+    const result = pipe(
+      [1, 2, 2, 5, 1, 6, 7] as const,
+      counter.fn(),
+      unique(),
+      take(3),
+    );
+    expect(counter.count).toHaveBeenCalledTimes(4);
+    expect(result).toEqual([1, 2, 5]);
+  });
+
+  it("take before unique", () => {
+    // bug from https://github.com/remeda/remeda/issues/14
+    const counter = createLazyInvocationCounter();
+    const result = pipe(
+      [1, 2, 2, 5, 1, 6, 7] as const,
+      counter.fn(),
+      take(3),
+      unique(),
+    );
+    expect(counter.count).toHaveBeenCalledTimes(3);
+    expect(result).toEqual([1, 2]);
+  });
+});
diff --git a/src/uniqueBy.test.ts b/src/uniqueBy.test.ts
new file mode 100644
index 0000000..71aec27
--- /dev/null
+++ b/src/uniqueBy.test.ts
@@ -0,0 +1,78 @@
+import { createLazyInvocationCounter } from "../test/lazy_invocation_counter";
+import { identity } from "./identity";
+import { pipe } from "./pipe";
+import { take } from "./take";
+import { uniqueBy } from "./uniqueBy";
+
+describe("uniqueBy", () => {
+  const people = [
+    { name: "John", age: 42 },
+    { name: "Jörn", age: 30 },
+    { name: "Sarah", age: 33 },
+    { name: "Kim", age: 22 },
+    { name: "Sarah", age: 38 },
+    { name: "John", age: 33 },
+    { name: "Emily", age: 42 },
+  ] as const;
+
+  it("handles uniq by identity", () => {
+    expect(uniqueBy([1, 2, 2, 5, 1, 6, 7], identity)).toEqual([1, 2, 5, 6, 7]);
+  });
+
+  it("returns people with uniq names", () => {
+    expect(uniqueBy(people, (p) => p.name)).toEqual([
+      { name: "John", age: 42 },
+      { name: "Jörn", age: 30 },
+      { name: "Sarah", age: 33 },
+      { name: "Kim", age: 22 },
+      { name: "Emily", age: 42 },
+    ]);
+  });
+
+  it("returns people with uniq ages", () => {
+    expect(uniqueBy(people, (p) => p.age)).toEqual([
+      { name: "John", age: 42 },
+      { name: "Jörn", age: 30 },
+      { name: "Sarah", age: 33 },
+      { name: "Kim", age: 22 },
+      { name: "Sarah", age: 38 },
+    ]);
+  });
+
+  it("returns people with uniq first letter of name", () => {
+    expect(uniqueBy(people, (p) => p.name.slice(0, 1))).toEqual([
+      { name: "John", age: 42 },
+      { name: "Sarah", age: 33 },
+      { name: "Kim", age: 22 },
+      { name: "Emily", age: 42 },
+    ]);
+  });
+
+  describe("pipe", () => {
+    it("gets executed until target length is reached", () => {
+      const counter = createLazyInvocationCounter();
+      const result = pipe(
+        [1, 2, 2, 5, 1, 6, 7],
+        counter.fn(),
+        uniqueBy(identity),
+        take(3),
+      );
+
+      expect(counter.count).toHaveBeenCalledTimes(4);
+      expect(result).toEqual([1, 2, 5]);
+    });
+
+    it("get executed 3 times when take before uniqueBy", () => {
+      const counter = createLazyInvocationCounter();
+      const result = pipe(
+        [1, 2, 2, 5, 1, 6, 7],
+        counter.fn(),
+        take(3),
+        uniqueBy(identity),
+      );
+
+      expect(counter.count).toHaveBeenCalledTimes(3);
+      expect(result).toEqual([1, 2]);
+    });
+  });
+});
diff --git a/src/uniqueWith.test.ts b/src/uniqueWith.test.ts
new file mode 100644
index 0000000..8c29fbd
--- /dev/null
+++ b/src/uniqueWith.test.ts
@@ -0,0 +1,53 @@
+import { createLazyInvocationCounter } from "../test/lazy_invocation_counter";
+import { equals } from "./equals";
+import { pipe } from "./pipe";
+import { take } from "./take";
+import { uniqueWith } from "./uniqueWith";
+
+const source = [
+  { a: 1 },
+  { a: 2 },
+  { a: 2 },
+  { a: 5 },
+  { a: 1 },
+  { a: 6 },
+  { a: 7 },
+];
+const expected = [{ a: 1 }, { a: 2 }, { a: 5 }, { a: 6 }, { a: 7 }];
+
+describe("data_first", () => {
+  test("should return uniq", () => {
+    expect(uniqueWith(source, equals)).toEqual(expected);
+  });
+});
+
+describe("data_last", () => {
+  test("should return uniq", () => {
+    expect(uniqueWith(equals)(source)).toEqual(expected);
+  });
+
+  it("lazy", () => {
+    const counter = createLazyInvocationCounter();
+    const result = pipe(
+      [{ a: 1 }, { a: 2 }, { a: 2 }, { a: 5 }, { a: 1 }, { a: 6 }, { a: 7 }],
+      counter.fn(),
+      uniqueWith(equals),
+      take(3),
+    );
+    expect(counter.count).toHaveBeenCalledTimes(4);
+    expect(result).toEqual([{ a: 1 }, { a: 2 }, { a: 5 }]);
+  });
+
+  it("take before uniq", () => {
+    // bug from https://github.com/remeda/remeda/issues/14
+    const counter = createLazyInvocationCounter();
+    const result = pipe(
+      [{ a: 1 }, { a: 2 }, { a: 2 }, { a: 5 }, { a: 1 }, { a: 6 }, { a: 7 }],
+      counter.fn(),
+      take(3),
+      uniqueWith(equals),
+    );
+    expect(counter.count).toHaveBeenCalledTimes(3);
+    expect(result).toEqual([{ a: 1 }, { a: 2 }]);
+  });
+});