Introduction

GitHubnpm
Install @vinicunca/perkakas, a typed utility library with data-first and data-last APIs, pipes, and lazy evaluation.

@vinicunca/perkakas is a TypeScript utility library. Every helper is written in TypeScript, so types are part of the implementation rather than a later add-on. You can call the same function data-first (data as the first argument) or data-last (to drop into a pipe).

It is a fork of Remeda. The runtime API is the Remeda surface plus a few Perkakas-only helpers. The one rename to know: Remeda's purry is curry here.

Installation

pnpm add @vinicunca/perkakas

Import the functions you need. Tree-shaking drops the rest.

import { filter, map, pipe } from '@vinicunca/perkakas';

Data-first and data-last

Libraries like Lodash put data first (_.pick(obj, keys)). Ramda puts data last (R.pick(keys, obj)). Perkakas ships both on the same import. Data-first is the usual call. Data-last omits the data argument and returns a function — that is what you pass to pipe.

import { pick, pipe } from '@vinicunca/perkakas';

pick(obj, ['firstName', 'lastName']); // data-first
pipe(obj, pick(['firstName', 'lastName'])); // data-last

Data-first is kinder to autocomplete: your editor already knows the object's keys when you type the second argument.

import { filter, groupBy, pipe } from '@vinicunca/perkakas';

const DATA = [
  { name: 'john', age: 20, gender: 'm' },
  { name: 'marry', age: 22, gender: 'f' },
  { name: 'samara', age: 24, gender: 'f' },
  { name: 'bill', age: 33, gender: 'm' }
];

pipe(
  DATA,
  filter((x) => x.gender === 'f'),
  groupBy((x) => x.age)
);

Pipe

pipe runs functions left to right. You read the transformation in the order it runs, without naming every intermediate value.

import { filter, map, pipe } from '@vinicunca/perkakas';

pipe(
  [1, 2, 3, 4],
  filter((n) => n % 2 === 0),
  map((n) => n * 2)
); // => [4, 8]

Any function that takes one argument works in a pipe, not only Perkakas helpers. Wrap other calls in an arrow if they are not data-last: ($) => validate($, CONFIG).

A headless variant, piped, builds a reusable pipeline without the initial data.

Lazy evaluation

When consecutive lazy helpers sit in a pipe in their data-last form (map, filter, take, unique, and others tagged Lazy on the Functions page), Perkakas walks items one at a time. Later steps can stop early so the rest of the input is never visited.

import { map, pipe, take, unique } from '@vinicunca/perkakas';

pipe(
  [1, 2, 2, 3, 3, 4, 5, 6],
  map((x) => {
    console.log('iterate', x);
    return x;
  }),
  unique(),
  take(3)
); // => [1, 2, 3]

// Console:
// iterate 1
// iterate 2
// iterate 2
// iterate 3

To force eager evaluation of a lazy helper, call it data-first inside an arrow: ($) => map($, callback) instead of map(callback).

Type guards

Functions in the Guard category are type predicates. After isString(value) (or filter(isString)), TypeScript narrows the value to string.

import { filter, isString } from '@vinicunca/perkakas';

isString('ok'); // => true
isString(1); // => false

filter(['a', 1, 'b'], isString); // => ['a', 'b']

Types that stay strict

Helpers are written so mapped tuples stay tuples, and filters keep the narrowed type. You often do not need extra libraries just to get the type you already expected.

import { map } from '@vinicunca/perkakas';

const DATA = [1, 2, 3] as const;

DATA.map((x) => x.toString());
// => string[]

map(DATA, (x) => x.toString());
// => [string, string, string]

Next

  • Browse every export on the Functions page (search and jump by category).
  • Switching from Remeda, Lodash, Ramda, or Just? Start at Migrate.