Skip to content

dfdl

Minified138 BMinzipped85 B

Data-First / Data-Last oriented utilities.

Installation

sh
npm install @monstermann/dfdl
sh
pnpm add @monstermann/dfdl
sh
yarn add @monstermann/dfdl
sh
bun add @monstermann/dfdl

API

dfdl

  • dfdl(fn, arity?: boolean)
  • dfdl(fn, isDataFirst: (args: IArguments) => boolean)

Creates a function that can be used in both data-first and data-last styles. Types are automatically inferred from the input function.

ts
import { dfdl } from "@monstermann/dfdl";

const add = dfdl((a: number, b: number) => a + b);
ts
add(1, 2); // 3
ts
add(2)(1); // 3
pipe(1, add(2)); // 3

You can explicitly define the function arity if necessary, otherwise fn.length is used:

ts
const add = dfdl(
    (a: number, b: number) => a + b,
    2,
);

And for functions with optional parameters, you can use a custom predicate:

ts
import { dfdl } from "@monstermann/dfdl";

const slice = dfdl(
    (str: string, start: number, end?: number) => str.slice(start, end),
    (args) => typeof args[0] === "string",
);
ts
slice("Hello World!", 0, 5); // "Hello"
slice("Hello World!", 6); // "World!"
ts
slice(0, 5)("Hello World!"); // "Hello"
slice(6)("Hello World!"); // "World!"

dfdlT

  • dfdlT(fn, arity?: boolean)
  • dfdlT(fn, isDataFirst: (args: IArguments) => boolean)

Same as dfdl but allows explicit type definitions, which is in particularly useful if you want to use generics or additional overloads:

ts
import { dfdlT } from "@monstermann/dfdl";

const map = dfdlT<
    <T, U>(fn: (item: T) => U) => (arr: T[]) => U[],
    <T, U>(arr: T[], fn: (item: T) => U) => U[]
>((arr, fn) => arr.map(fn));
ts
import { dfdlT } from "@monstermann/dfdl";

const map: {
    <T, U>(fn: (item: T) => U): (arr: T[]) => U[];
    <T, U>(arr: T[], fn: (item: T) => U): U[];
} = dfdlT(<T, U>(arr: T[], fn: (item: T) => U): U[] => arr.map(fn));
ts
map([1, 2, 3], (x) => x * 2); // [2, 4, 6]
ts
map((x) => x * 2)([1, 2, 3]); // [2, 4, 6]

pipe(
    [1, 2, 3],
    map((x) => x * 2),
); // [2, 4, 6]

TIP

Typically when defining your functions in this case, you should experience better type inference when using NoInfer:

ts
const map = dfdlT<
    <T, U>(fn: (item: NoInfer<T>) => U) => (arr: T[]) => U[],
    <T, U>(arr: T[], fn: (item: NoInfer<T>) => U) => U[]
>((arr, fn) => arr.map(fn));

pipe

pipe(input, ...fns)

Pipes a value through a sequence of functions, passing the output of each function as input to the next.

ts
import { pipe } from "@monstermann/dfdl";

const increment = (x: number) => x + 1;
const double = (x: number) => x * 2;

pipe(5, increment, double); // 12

flow

flow(...fns)(input)

A data-last version of pipe for use with functions that accept a function parameter (like map, filter, etc.).

ts
import { flow, pipe } from "@monstermann/dfdl";

const increment = (x: number) => x + 1;
const double = (x: number) => x * 2;

flow(increment, double)(1); // 4

[1, 2, 3, 4].map(flow(increment, double)); // [4, 6, 8, 10]

// In comparison, `pipe` can feel too verbose:

[1, 2, 3, 4].map((num) => pipe(num, increment, double)); // [4, 6, 8, 10]

Credits