JS/TS lightweight value-multimethod util

Overview

fp-multik ๐Ÿคน๐Ÿผโ€โ™‚๏ธ

npm version

Small functional utility for control flow and conditional operator for functions. Multik is value-based multimethod for Javascript/Typescript programs.

It's simple:

  1. import fp-multik
  2. create in first argument of multik - selector from initial arguments
  3. write in the rest of the arguments what you want to process in the predicates
  4. enjoi!

Installation

NPM

npm install fp-multik

Yarn

yarn add fp-multik

Features

  • ๐Ÿฃ small API and size
  • ๐ŸŒŠ pipable
  • ๐Ÿ™Œ๐Ÿป usefull access to data/selector in predicates
  • ๐Ÿ”— better types than in analogues

Usage

multik is a single function:

multik(
  selectorFunction(...initialArgs) => selector,
  [pricidateValue, actionFunction(selector, ...initialArgs) => result],
  [predicateFunction(selector, ...initialArgs), actionFunction(selector, ...initialArg) => result],
  [defaultFunction?(selector, ...initialArgs) => result]
): (initialArgs) => result;
  • pricidateValue can be primitive value or Array / Object
  • predicateFunction is a classic predicate function that return boolean
  • defaultFunction is optional function for specify default result

Simple predicate as value

Matching Number values

import multik from 'fp-multik';

const nominalDegreesOfThousand = multik(
  (n: number) => n,
  [1000, () => 'thousand'],
  [1000000, () => 'million'],
  [1000000000, () => 'billion'],
  [1000000000000, () => 'trillion'],
  [1000000000000000, () => 'quadrillion'],
);

nominalDegreesOfThousand(1000); // 'thousand'
nominalDegreesOfThousand(1000000); // 'million'
nominalDegreesOfThousand(1000000000); // 'billion'
nominalDegreesOfThousand(1000000000000); // 'trillion'
nominalDegreesOfThousand(1000000000000000); // 'quadrillion'

Matching String values

import multik from 'fp-multik';

const greet = multik(
  (data) => data.lang,
  ["english", () => "Hello"),
  ["french", () => "Bonjour")
);

greet({ id: 1, lang: "french" }); // "Bonjour"

Matching Array values

import multik from 'fp-multik';

const shot = multik(
  (data) => data.coord,
  [[30, 40], () => 'hitted!'],
  [[90, 40], () => 'hitted your building!'],
);

shot({ coord: [30, 40] }); // "hitted!"
shot({ coord: [90, 40] }); // "hitted your building!"
shot({ coord: [0, 0] }); // undefined

Matching Object values

import multik from 'fp-multik';

interface Response {
  code: number;
}

const getResult = multik(
  (data: Response) => data,
  [{ code: 200 }, () => 'complete'],
  [{ code: 500 }, () => 'error'],
);

getResult({ code: 200 }); // "complete"
getResult({ code: 500 }); // "error"

Custom predicate

import multik from 'fp-multik';

const fizzBuzz = multik(
  (n: number) => n,
  [(n) => n % 3 === 0 && n % 5 === 0, () => 'FizzBuzz'],
  [(n) => n % 3 === 0, () => 'Fizz'],
  [(n) => n % 5 === 0, () => 'Buzz'],
);

fizzBuzz(3); // "Fizz"
fizzBuzz(5); // "Buzz"
fizzBuzz(15); // "FizzBuzz"

OR predicate

import multik from 'fp-multik';

enum UserRole {
  Admin = 'admin',
  Guest = 'guest',
  Editor = 'editor',
}
type User = { fullname: string; age: number; role: UserRole };

const adminUser: User = { fullname: 'John Smith', age: 20, role: UserRole.Admin };
const guestUser: User = { fullname: 'Evan Martinez', age: 24, role: UserRole.Guest };
const editorUser: User = { fullname: 'Tod Parker', age: 17, role: UserRole.Editor };

const getInformation = multik(
  (data: User) => data.role,
  [[UserRole.Admin, UserRole.Editor], () => 'secret information'],
  [UserRole.Guest, () => 'no access'],
);

getInformation(adminUser); // "secret information"
getInformation(editorUser); // "secret information"
getInformation(guestUser); // "no access"

Default predicate

import multik from 'fp-multik';

const greet = multik(
  (data) => data.lang,
  ["english", () => "Hello"),
  ["french", () => "Bonjour"),
  [() => 'not matched'] // default method
);

greet({ id: 1, lang: "germany" }); // "not matched"

Access to initial arg and selector

import multik from 'fp-multik';

const adultInformation = multik(
  (user) => user.age,
  [(age) => age >= 18, (user, age) => `hey ${user.name}, your age (`${age}`) is right, access success!`),
  [(_user, age) => `your age (${age}) less 18, access denied`]
);

adultInformation({ name: 'Greg', age: 17 }); // "your age (17) less 18, access denied"
adultInformation({ name: 'John', age: 27 }); // "hey John, your age (27) is right, access success!"

Placeholder convection for unused params

If you want use only concrete arguments in predicate or action and ignore other you can name param start underscore:

import multik from 'fp-multik';

const calc = multik(
  (_n1: number, op: string, _n2: number) => op,
  ['+', (_selector, n1, _op, n2) => n1 + n2],
  ['-', (_selector, n1, _op, n2) => n1 - n2],
);

calc(1, '+', 2); // 3
calc(4, '-', 2); // 2

that changes show unused params in callback and exclude some conflicts with names

Use-cases

Control flow

import multik from 'fp-multik';
import process from 'process';

const app = multik(
  (args: string[]) => args[2],
  ['--help', () => console.log('Show help information')],
  ['--run', () => console.log('Run job')],
  [() => console.log('Command not found')],
);

app(process.argv);
user % ts-node app.ts --help
Show help information
user % ts-node app.ts --run
Run job
user % ts-node app.ts
Command not found

Handling business scenarios

import multik from 'fp-multik';

const convertFile = multik(
  (filepath: string, format: string) => format,
  ['json', (format, filepath) => console.log(`Convert ${filepath} as JSON...`)],
  ['html', (format, filepath) => console.log(`Convert ${filepath} as HTML...`)],
  ['csv', (format, filepath) => console.log(`Convert ${filepath} as CSV...`)],
  [(format, filepath) => console.log(`Convert ${filepath} by default as TXT...`)],
);

convertFile('/Users/file1.data', 'json');
convertFile('/Users/file1.data', 'html');
convertFile('/Users/file1.data', 'csv');
convertFile('/Users/file1.data', 'unknown');
user % ts-node app.ts
Convert /Users/file1.data as JSON...
Convert /Users/file1.data as HTML...
Convert /Users/file1.data as CSV...
Convert /Users/file1.data by default as TXT...

Handling error

import multik from 'fp-multik';

const handleFetchError = multik(
  (clientError: HttpClientError) => clientError.code,
  [
    404,
    () => {
      /* ... handle 404 code */
    },
  ],
  [
    500,
    () => {
      /* ... handle 500 code */
    },
  ],
);

try {
  const response = await fetch('http://mysite.ru', {
    method: 'POST',
    body: JSON.stringify(data),
  });
  return await response.json();
} catch (e: HttpClientError) {
  handleFetchError(e);
}

Handling state

import multik from 'fp-multik';

type Action = {
  type: string;
  id?: number;
  text?: string;
};

type Store = {
  add: (text: string) => void;
  remove: (id: number) => void;
  toggle: (id: number) => void;
};

const store: Store = {
  add(text: string) {
    console.log(`todo with ${text} added`);
  },
  remove(id: number) {
    console.log(`${id} todo removed`);
  },
  toggle(id: number) {
    console.log(`#${id} todo toggled`);
  },
};

const handleAction = multik(
  (action: Action, store: Store) => action.type, // custom dispatch
  ['ADD_TODO', (_type_, action, store) => store.add(action.text!)],
  ['REMOVE_TODO', (_type, action, store) => store.remove(action.id!)],
  ['TOGGLE_TODO', (_type, action, store) => store.toggle(action.id!)],
);

handleAction({ type: 'ADD_TODO', text: 'Eat banana' }, store); // log "todo with Eat banana added"
handleAction({ type: 'TOGGLE_TODO', id: 1 }, store); // log "#1 todo toggled"

Alternatives

Let's overview simple code with multik:

import multik from "fp-multik";

const greet = multik(
  (data) => data.lang,
  ["english", () => "Hello"),
  ["french", () => "Bonjour")
);

greet({ id: 1, lang: "french" }); // "Bonjour"

you can also consider alternatives

Lodash (Ramda like libs)

If you love lodash and you dont want install multik - you can implement DIY multik yourself. ๐Ÿ˜‰

import _ from 'lodash';

const multik = (dispatcher, predicates) =>
  _.flow([
    (data) => dispatcher(data),
    _.cond([...predicates, [_.stubTrue, _.constant('no match')]]),
  ]);

const greet = multik(
  (data) => data.lang,
  [
    [(lang) => lang === 'english', () => 'Hello'],
    [(lang) => lang === 'french', () => 'Bonjour'],
  ],
);

greet({ id: 1, lang: 'french' }); // "Bonjour"

@arrows/multimethod

Powerful multimethod library. You can

import { multi, method } from '@arrows/multimethod';

const greet = multi(
  (data) => action.lang,
  method('english', () => 'Hello'),
  method('french', () => 'Bonjour'),
  method(() => 'no match'),
);

greet({ id: 1, lang: 'french' }); // "Bonjour"

Also you can discover next libraries:

Contributing

Your feedback and contributions are welcome. If you have a suggestion, please raise an issue. Prior to that, please search through the issues first in case your suggestion has been made already. If you decide to work on an issue, or feel like taking initiative and contributing anything at all, feel free to create a pull request and I will get back to you shortly.

You might also like...

Type predicate functions for checking if a value is of a specific type or asserting that it is.

As-Is Description As-Is contains two modules. Is - Type predicates for checking values are of certain types. As - Asserting values are of a certain ty

Feb 10, 2022

Combine type and value imports using Typescript 4.5 type modifier syntax

type-import-codemod Combines your type and value imports together into a single statement, using Typescript 4.5's type modifier syntax. Before: import

Sep 29, 2022

๐Ÿงฉ TypeScript utility type in order to ensure to return only properties (not methods) containing values in primitive types such as number or boolean (not Value Objects)

๐Ÿงฉ TypeScript utility type in order to ensure to return only properties (not methods) containing values in primitive types such as number or boolean (not Value Objects)

๐Ÿงฉ TypeScript Primitives type TypeScript utility type in order to ensure to return only properties (not methods) containing values in primitive types

Dec 7, 2022

It consists of a recreation of Twitter, to put into practice knowledge of both Front-end and Back-end implementing the MERN Stack along with other technologies to add more value to the project.

It consists of a recreation of Twitter, to put into practice knowledge of both Front-end and Back-end implementing the MERN Stack along with other technologies to add more value to the project.

Twitter-Clone_Front-end โœจ Demo. Login Home Profile Message Notifications Deployed in: https://twitter-clone-front-end.vercel.app/ ๐Ÿ’ป About the project

Jun 26, 2022

Tenzi is a dice game. The player needs to roll dice until they are all the same. Clicking on a dice, freezes it at its current value between rolls. Best scores are saved to local storage.

Tenzi is a dice game. The player needs to roll dice until they are all the same. Clicking on a dice, freezes it at its current value between rolls.  Best scores are saved to local storage.

Roll until all dice are the same Try me! Technologies Used Description Tenzi is a dice game used to demonstrate the use of React Hooks (useState, useE

Nov 23, 2022

A common front-end/Service Worker-based Key/Value database based on CacheStorage

Cache-DB A common front-end/Service Worker-based Key/Value database based on CacheStorage const db = new CacheDB('ChenYFanDB') undefined await d

Sep 30, 2022

Tries to execute sync/async function, returns a specified default value if the function throws

good-try Tries to execute sync/async function, returns a specified default value if the function throws. Why Why not nice-try with it's 70+ million do

Dec 8, 2022

Persistent key/value data storage for your Browser and/or PWA, promisified, including file support and service worker support, all with IndexedDB. Perfectly suitable for your next (PWA) app.

Persistent key/value data storage for your Browser and/or PWA, promisified, including file support and service worker support, all with IndexedDB. Perfectly suitable for your next (PWA) app.

BrowstorJS ๐Ÿš€ ๐Ÿ’พ ๐Ÿ”’ Persistent key/value data storage for your Browser and/or PWA, promisified, including file support and service worker support, all

Aug 5, 2022

Tiny JavaScript library (1kB) by CurrencyRate.today, providing simple way and advanced number, money and currency formatting and removes all formatting/cruft and returns the raw float value.

Zero dependency tiny JavaScript library (1kB bytes) by CurrencyRate.today, providing simple way and advanced number, money and currency formatting and removes all formatting/cruft and returns the raw float value.

Nov 8, 2022
Comments
  • Placeholder for selector and predicate arguments

    Placeholder for selector and predicate arguments

    implement placeholder like https://github.com/ramda/ramda/blob/v0.28.0/source/__.js

    for skip unused variables.

    before:

    import multik from 'fp-multik';
    
    const convertFile = multik(
      (filepath: string, format: string) => format,
      ['json', (format, filepath) => console.log(`Convert ${filepath} as JSON...`)],
      ['html', (format, filepath) => console.log(`Convert ${filepath} as HTML...`)],
      ['csv', (format, filepath) => console.log(`Convert ${filepath} as CSV...`)],
      [(format, filepath) => console.log(`Convert ${filepath} by default as TXT...`)],
    );
    
    convertFile('/Users/file1.data', 'json');
    

    after:

    import multik, { __ } from 'fp-multik';
    
    const convertFile = multik(
      (filepath: string, format: string) => format,
      ['json', (__, filepath) => console.log(`Convert ${filepath} as JSON...`)],
      ['html', (__, filepath) => console.log(`Convert ${filepath} as HTML...`)],
      ['csv', (__, filepath) => console.log(`Convert ${filepath} as CSV...`)],
      [(__, filepath) => console.log(`Convert ${filepath} by default as TXT...`)],
    );
    
    convertFile('/Users/file1.data', 'json');
    
    enhancement 
    opened by lulldev 1
  • Predicate action as value

    Predicate action as value

    add user's choice possibility indicate value or function in predicate action

    before:

    import multik from 'fp-multik';
    
    const greet = multik(
      (data) => data.lang,
      ["english", () => "Hello"),
      ["french", () => "Bonjour")
    );
    
    greet({ id: 1, lang: "french" }); // "Bonjour"
    

    after:

    import multik from 'fp-multik';
    
    const greet = multik(
      (data) => data.lang,
      ["english", "Hello"),
      ["french",  "Bonjour")
    );
    
    greet({ id: 1, lang: "french" }); // "Bonjour"
    

    or (valid situation):

    import multik from 'fp-multik';
    
    const greet = multik(
      (data) => data.lang,
      ["english", "Hello"),
      ["french",  () => "Bonjour")
    );
    
    greet({ id: 1, lang: "french" }); // "Bonjour"
    

    please add tests for these cases

    enhancement help wanted good first issue 
    opened by lulldev 0
Releases(0.1.3)
A util for getting data and metadata for all markdown files in a given dir. Useful for building static site generators

extract-md-data A util for getting data and metadata for all markdown files in a given dir. Useful for building static site generators. Usage Given th

Claire Froelich 2 Jan 6, 2022
Util for kafkajs to buffer messages and send them in batches, inspired by node-rdkafka

kafkjajs-buffer Plugin for kafkajs to buffer messages and send them in batches, inspired by node-rdkafka Overview kafkajs-buffer adds queue/buffer cap

Alberto Juan 7 Sep 7, 2022
Util for calling Prisma middleware for nested write operations.

Prisma Nested Middleware Util for calling Prisma middleware for nested write operations. Existing Prisma middleware is called once for every operation

Olivier Wilkinson 6 Dec 7, 2022
A lightweight (~2kB) library to create range sliders that can capture a value or a range of values with one or two drag handles

range-slider-input A lightweight (~2kB) library to create range sliders that can capture a value or a range of values with one or two drag handles. Ex

Utkarsh Verma 42 Dec 24, 2022
Bookmate - Watch changes in Chrome bookmarks, and use bookmarks as an append-only key-value store via an fs-like API.

?? Bookmate An append-only key-value store built on Chrome bookmarks, plus an asychronous stream of Bookmark changes. For NodeJS Actual production exa

Cris 6 Nov 8, 2022
Byteroo - Key-value storage for your Node.js applications

Byteroo Byteroo is a key-value storage for your Node.js applications. Usage: const Byteroo = require('byteroo'); const storage = new Byteroo({ name:

JMax 1 Jan 3, 2022
Resolve parallel promises in key-value pairs whilst maintaining type information

async-kv Resolves promises in key-value pairs maintaining type information. Prerequisites NodeJS 12 or later Installation npm i async-kv yarn add asyn

Tony Tamplin 4 Feb 17, 2022
Sheetzapper imports your account value accross Zapper.fi supported wallets and dapps into a Google Sheet

Overview Sheetzapper imports your account value accross Zapper.fi supported wallets and dapps into a Google Sheet. This allows you to chart your net w

null 4 Nov 27, 2022
It consists of a recreation of Twitter, to put into practice both Front-end and Back-end knowledge by implementing the MERN Stack together with other technologies to add more value to the project.

Twitter-Clone_Back-end โœจ Demo. ?? About the project. ?? Descriptions. It consists of a recreation of Twitter, to put into practice knowledge of both F

Mario Quirรณs Luna 5 Apr 12, 2022
Cypress commands are asynchronous. It's a common pattern to use a then callback to get the value of a cypress command

cypress-thenify Rationale Cypress commands are asynchronous. It's a common pattern to use a then callback to get the value of a cypress command. Howev

Mikhail Bolotov 15 Oct 2, 2022