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 🙏

© 2024 – Pkg Stats / Ryan Hefner

jafl

v3.1.0

Published

Just Antoher Functional Library

Downloads

256

Readme

Just Another Functional Library (jafl)

As the title said, it is just another functional programming library. I always end up writing the same "utils" file with a couple of functions that helps me write the code in a more declarative-first way. Using mainly but not only function composition.

Notes:

I'm working on a mayor (v4) update, it probably includes a pipe refactor, and a couple of new features like sleep function and some initial work for Immutable objects. Also improved documentation and tests are a personal goal. Thanks for being patience.

Introduction

This library allows you to write functional and declarative code having the KISS principle in mind.

It provide flow-control utils like, pipe, conditional, or tap. Functions wraping helpers, that could be used as adapters, like curry, pick or applier.

Install

You are good to go with:

yarn add jafl

or:

npm i jafl

Usage

pipe

It takes N functions as parameter, and return a single function that when is called will invoke the first function with the input, and then passthrough the output to the next function input, creating a pipe.

tap

It takes a function (fn), and return and async function that when is invoked with and input will excecute the original function over the input (fn(input)) but instead of returning the function invocation output, returns the original input

conditional

It takes two functions, a conditionalCheckFn, and a conditionalAppliedFn, and return a new function that takes and input and check if the resulto of apply conditionalCheckFn to input is true, then return conditionalAppliedFn(input), and if it is false, it will return the input without applying any function

pick

It pick a key that could be a string like 'name', or a chained access like: 'address.number' and return a function that takes an object and return the value of that key.

In addition it can take multiple of this keys as multiple arguments, and in that case it will return an array with every value in the same order as requested i.e:

const obj = { name: 'Some', address: { number: 33, isReal: false }};

pick('name')(obj); // -> 'Some'
pick('address.number')(obj) // -> 33
pick('address.number', 'id', 'name')(obj) // -> [33, undefined, 'Some']

curry

This curry implementation works thanks to @cbroeren code (thank you!)

Currying is a bit hard to understand but sometime when you are using a pipe you most likely will need to "trick" some function to be prefilled with a first argument and have a function that only receive the rest of the parameters, well that when curry kicks in.

const sendEmail = ; // we have this implemented and has the following signature: (to: string, topic: string, message: string)
const sendEmailWithCurry = curry(sendEmail);
const sendElonWithCurry = sendEmailWithCurry(_, _, 'You are fired!'); // now this function is prefilled with the third argument, message.

const sendElonMsg = (users: [User]) => {
  const process = pipe(
    conditional(IHaveTheRightMood, pipe(
      tap(logEmailSendtTriggered),
      pick('emailAddress'),
      sendElonWithCurry(_, 'I Have a lovely message for you') // Now is filled with message, and topic, so it will return a function that receive a "to" and excecute the original function
    ))
  );

  await Promise.all(users.map(process));
}

applier

It takes a function (fn) and return a function that receive a list of arguments in an array-like format and call fn with those args as it was a non array receiving function i.e:

const greeter = (greetMsg: string, userName: string) => `Hey, ${greetMsg} ${userName}!`;

applier(greeter)(['hello there', 'fellow friend']); // -> 'Hey, hello there fellow friend!' 

Integration Sample

So let asume you have something like:

const findUser = async () => User //ie: { name: 'John', mail: { address: [email protected], foo: true } }
const checkOnboarding = async () => //...
const trackEventToCloud = async () => //...
const isFirstTimeCompletingOnboarding = (user) => true; // implement a real one
const sendEmail = curry(_sendEmail);// where _sendEmail = (to, topic, msg) => {...};

You could write your composition like this:

const process = pipe(
  findUser, // we get the User from DB
  checkOnboarding, //We check onboarding status
  conditional(isFirstTimeCompletingOnboarding, pipe( //as it is the first time, so this pipe wil run
    tap(trackEventToCloud), // we track the event but return the same input using tap
    pick(['mail.adress', 'name']),// we only need the mail adress and the name for the user
    applier(sendEmail(_, _, "Welcome!")) // applier will give sendEmail the two remains parameters from the input array (the pick's output)
  ))
);

await process(user);

And what we are doing in here is telling in a declarative way:

  1. Check the onboarding process
  2. Check if is the first time completing onboarding (conditional)
  3. If yes, then:
    • track the event in some logs but return the input user as output instead of the traking result (tap)
    • send an email
  4. If no, then return the same input.

This whole process happens in a pipe, where input flow from left to right. So if we got: A, B and C, and then we define pipe as P where P = P(A, B, C), P will be a function that given and input it will excecute A, then the result will be the input to execute B, and finally the result will be the input of C, so the finall result would be C(B(A(input))) equivalent.

Changelog

Latest Release:

v3.1.0

  • Updated dev dependencies
  • moved from yarn to npm package manager
  • move CI from node v18 to v20
  • updated some docs typos
  • deprecated old versions ( < 3.0.1 )

You can view full changelog here: -> CHANGELOG <-