npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

rechainify

v0.8.1

Published

Lightweight utilities for composing and executing chained data validations and transformations.

Readme

Rechainify

Rechainify is a lightweight, dependency-free utility library for composing and executing chained data transformations. It allows you to define reusable validation and transformation steps, organize them into flexible pipelines, and apply them conditionally based on your use case.

Table of Contents

Quick Start

Install Rechainify in your project:

npm i rechainify

Create an instance of the builder and apply transformations to input values:

import Rechainify from 'rechainify'

const chain = Rechainify.map({
  category: (category) => (input) => ({ ...input, category }),
  required: (input) => ({ ...input, required: true }),
})

chain.required({ value: 5 }) // { required: true, value: 5 }
chain.required.category('author', { name: 'Lucy' })  // { category: 'author', name: 'Lucy', required: true }

[!NOTE] You don’t need to import the barrel file (i.e., the main entry file that re-exports everything) — all methods can be accessed directly, e.g., rechainify/map or rechainify/some.

[!WARNING] If you're using TypeScript and define steps as entries of type [string, Function][], it's important to make it readonly (e.g., using as const); otherwise, you'll lose helpful code intelligence features.

Handler

A handler is the core component that exposes a static method for each configured step, enabling the chaining of steps in different scenarios.

Internally, the handler records the order in which steps are applied. When executed, it passes the input value through each step in sequence. Executing the handler clears the recorded step sequence, allowing a new scenario to be built from scratch.

It’s important to note that the handler is invoked during chaining when:

  • a plain step is executed, or
  • a factory step is executed with two arguments (the configuration and the input).
import Rechainify from 'rechainify'

const chain = Rechainify.some({
  category: (category) => (input) => ({ ...input, category }),
  required: (input) => ({ ...input, required: true }),
})

chain.required(5) // the first argument is passed to the handler
chain.category('author', { name: 'Elfo' }) // the second argument is passed to the handler
chain.category('author')({ name: 'Elfo' }) // the same as above
chain.required.category('author', { name: 'Elfo' }) // applies the required, then the category steps
chain.required().category('author', { name: 'Elfo' }) // ReferenceError

[!WARNING] The handler should be invoked only at the end of the scenario; otherwise, you will get a ReferenceError.

Steps

A step is a unit of work added to the chain sequence during chaining. When the final handler is executed, the input value flows through each step in the chained order — each step receives the output of the previous step.

Each step consists of a name (i.e., string) and a function that implements the step behavior.

There are two types of step functions:

  • A plain function (input: any) => any.
  • A factory function (options: any) => (input: any) => any.

[!NOTE] Each function is executed once during step definition to determine the step type.
If the function returns another function, it is treated as a factory step; otherwise, it's a plain step. If the function throws an exception during this check, it is also treated as a plain step.

You can configure a list of steps in two ways:

  • As an array of tuples Array<[string, Function]>.
  • As an object Record<string, Function>.

Plain

A plain step is the simplest type of method in the chain. It accepts an input, processes and/or validates it, and returns a new value (or the original one, depending on the design):

import Rechainify from 'rechainify'

const chain = Rechainify.every({
  number: (input) => typeof input === 'number' ? input : null,
  required: (input) => input !== undefined ? input : null,
}) 

chain.required(undefined)   // null
chain.required(5)           // 5
chain.required.number(5)    // 5
chain.required().number(5)  // ReferenceError

[!WARNING] A plain method returns the final handler. This means that if you execute a step in the middle of the chain, you’ll get a ReferenceError because the chain has already been closed by calling the handler.

Factory

A factory step works the same way as a plain step, with one key difference: it can be configured during chaining.

import Rechainify from 'rechainify'

const chain = Rechainify.every({
  max: (boundary) => (input) => input < boundary ? input : null,
  min: (boundary) => (input) => input > boundary ? input : null,
})

chain.min(5, 6)             // 6
chain.min(5, 4)             // null
chain.min(5).max(10, 7)     // 7
chain.max(10).min(5, 7)     // 7
chain.max(10, 7).min(5, 7)  // ReferenceError

[!WARNING] At the moment, factory steps support only one configuration argument. The second argument is passed directly to the final chain handler and may cause a ReferenceError if the step is invoked in the middle of the chain.

Methods

every(steps, predicate)

The every method creates a handler that helps build a scenario — a sequence of steps — and applies it to an input. Each step is executed in order, and the output of one step is passed as the input to the next. If any step fails the predicate, the handler returns null immediately; otherwise, it returns the result of the final step.

import Rechainify from 'rechainify'

const chain = Rechainify.every(
  {
    number: (input) => {
      const possiblyNumber = typeof input === 'number' ? input : parseInt(input, 10)
      return Number.isNaN(possiblyNumber) ? null : possiblyNumber
    },
    min: (left) => (input) => input > left ? input : null,
  },
  (input) => input !== null
)

chain.number(5)           // 5
chain.number('5')         // 5
chain.number.min(5, 7)    // 7
chain.number.min(5, 3)    // null
chain.min(5).number(6)    // 6
chain.min(5).number('6')  // null

[!NOTE] The predicate argument is optional and defaults to (input: unknown): boolean => input !== null.

[!NOTE] If you need complex validation and transformation, it makes sense to look at zod.

map(steps)

The map method creates a handler that helps build a scenario — a sequence of transformation steps — and applies it to an input. Each step is executed in order, with the output of one step passed as the input to the next, until the final step returns the result.

import Rechainify from 'rechainify'

const chain = Rechainify.map({
  double: (input) => input * 2,
  divideBy: (divider) => (input) => input / divider,
})

chain.double.divideBy(3, 6) // 4 
chain.divideBy(3).double(9) // 6

some(steps, predicate)

The some method creates a handler that allows you to build a scenario — a sequence of steps — and apply it to an input. Each step is executed in order with the input value, until a result satisfies the given predicate. The handler returns null if no step produces a satisfying result.

import Rechainify from 'rechainify'

const chain = Rechainify.some(
  {
    px: (input) => input.endsWith('px') ? parseInt(input, 10) : null,
    em: (input) => input.endsWith('em') ? parseInt(input, 10) : null,
  },
  (input) => input !== null
)

chain.px.em('10px') // 10
chain.px.em('10em') // 10
chain.em.px('10px') // 10
chain.em.px('10em') // 10

[!NOTE] The predicate argument is optional and defaults to (input: unknown): boolean => input !== null.

Roadmap

  • [x] Avoid using the type property in step configuration. Instead, accept steps as a Record<name, function>.
  • [x] Migrate to Typescript 🙃
  • [ ] Add support for an arbitrary number of arguments in factory steps.

License

Licensed under MIT