Remeda
Fully written in TypeScript for powerful, type-safe, and functional utilities.
npm install remeda
pnpm add remeda
yarn add remeda
bun install remeda
š§° Useful
Remeda offers a wide range of common everyday utility functions so that you
donāt have to deal with the minutiae of writing them yourself. We ensure that
our algorithms are efficient, optimized, and cover all edge cases properly. We
offer a wide range of functions, from simple utilities like
partition
and building objects with
pullObject
, to complex algorithms like
takeFirstBy
.
import * as R from "remeda";
// Or import the function directly:
// import { sortBy } from "remeda";
type User = {
firstName: string;
lastName: string;
lastLogin: number;
};
declare const USERS: User[];
R.sortBy(
USERS,
[R.prop("lastLogin"), "desc"],
R.prop("lastName"),
R.prop("firstName"),
);
š· Fully Typed
Libraries like Lodash and Ramda are written in JavaScript, with types added later. Remeda, on the other hand, is fully written in TypeScript! This means types are deeply integrated into the code, allowing us to shift the focus from runtime errors to compile-time errors, so you catch errors in your IDE and CI, not in your logs, and not in production.
We strive to provide the best possible real-life types for all functions, often going beyond what is offered by the TypeScript library, so you donāt need to use other libraries like ātype-festā and āts-resetā just to get the types you expected.
const DATA = [1, 2, 3] as const;
const builtin = DATA.map((x) => x.toString());
// ^? string[]
const withRemeda = R.map(DATA, (x) => x.toString());
// ^? [string, string, string]
š¦¾ Functional
Functional programming is beneficial, making code more readable. However, there are situations where you donāt need āpipesā and want to call just a single function. In Remeda, all functions have automatic currying built-in. This means the same imported function can be called either regularly (what we call data-first) or with the first parameter, data, omitted (what we call data-last). When a data-last invocation is detected, Remeda creates a curried version of that function on-the-fly, with all other parameters bound to it. This allows you to use the same function in both functional and imperative styles, without additional imports. It also means you can gradually adopt a functional style where it makes sense without compromising readability when it doesnāt.
// Lodash
_.pick(obj, ["firstName", "lastName"]);
// Ramda
R.pick(["firstName", "lastName"], obj);
// In Remeda you get both!
R.pick(obj, ["firstName", "lastName"]); // data-first
R.pipe(obj, R.pick(["firstName", "lastName"])); // data-last
In the above example, the ādata-firstā approach is more natural and programmer- friendly because when you type the second argument, you get auto-complete from the IDE. Itās not possible to get auto-complete in Ramda because the data argument is not provided.
The ādata-lastā approach is helpful when writing data transformations, aka pipes.
const DATA = [
{ name: "john", age: 20, gender: "m" },
{ name: "marry", age: 22, gender: "f" },
{ name: "samara", age: 24, gender: "f" },
{ name: "paula", age: 24, gender: "f" },
{ name: "bill", age: 33, gender: "m" },
];
// Remeda
R.pipe(
DATA,
R.filter((x) => x.gender === "f"),
R.groupBy((x) => x.age),
);
// Ramda
R.pipe(
R.filter((x) => x.gender === "f"),
// ^? š 'x' is of type 'unknown'
R.groupBy((x) => x.age),
// ^? š Property 'age' does not exist on type 'P'
)(DATA);
// Lodash
_(DATA)
.filter((x) => x.gender === "f")
.groupBy((x) => x.age)
.value();
// lodash/fp
_.flow(
_.filter((x) => x.gender === "f"),
// ^? š 'x' is of type 'unknown'
_.groupBy((x) => x.age),
// ^? š 'x' is of type 'unknown'
)(DATA);
š§ Efficient
You might have noticed the pipe
function in the above example.
The pipe function allows you to structure complex data transformations in the
order they will be computed, avoiding the need to name intermediate variables or
having confusingly deep nesting that needs to be read from the center outwards.
But the Remeda pipe function has an additional benefit. It automatically detects Remeda functions that could be computed lazily and optimizes those parts of the pipe without you having to do anything. This means that expensive computations can be deferred until they are actually needed.
// Get first 3 unique values
const DATA = [1, 2, 2, 3, 3, 4, 5, 6];
const result = R.pipe(
DATA,
R.map((x) => {
console.log("iterate", x);
return x;
}),
R.unique(),
R.take(3),
); // => [1, 2, 3]
// Console output:
// iterate 1
// iterate 2
// iterate 2
// iterate 3
š¬ Trustworthy!
All this wouldnāt be worth much if it didnāt work. Thatās why Remeda has an extensive test suite that offers 100% coverage. We also test all functions on all supported Node versions to make sure we never regress. Our tests cover not just runtime behavior but also types!
We use the strictest TypeScript settings and extensive linting to ensure that nothing slips through the cracks.
š„³ Collabrative
Remeda is provided with an MIT license and is open to contributions from the
community. We are always looking for new contributors and are happy to help you
get started. If you have any questions or want to contribute, feel free to reach
out to us over on GitHub
.