
Security News
Axios Maintainer Confirms Social Engineering Attack Behind npm Compromise
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.
Fun functional programming with pipelinable functions.
Effepi is a functional way to enqueue and use different functions. You can put your functions in a pipeline and resolve them both asynchronously or synchronously, creating new async functions or sync functions.
npm i effepi
yarn add effepi
npm i pipe
# or
yarn add pipe
import { pipe, math, misc } from 'effepi';
export const calculateVat = (vatPercentage: number) =>
pipe<number, number>(misc.useCallValue())
.pipe(math.divideBy(100))
.pipe(math.multiplyBy(vatPercentage))
.toSyncFunction();
vatCalculator(22)(100); // 22
vatCalculator(22)(1285); // 282.7
To create a pipeline, use the pipe function. This function can be used with any function, althought is recommended if you are building a function from a pipeline to use the useCallValue function.
const p = pipe(useCallValue()).toSyncFunction();
p(10) // returns 10
p('hello world') // returns 'hello world'
A pipeline can be memoized, it is useful when you have to call a pipeline a lot of times.
const test = pipe(useCallValue(), true)
.pipe(add(10))
.pipe(multiplyBy(2))
.toSyncFunction();
test(1) // returns 22
test(1) // returns the cached previous result
test(2) // returns 24
test(2) // returns the cached previous result
test(2) // returns the cached previous result
You can iterate a pipeline.
Each iteration returns an immutable pipeline result.
const pipeline = pipe(useCallValue())
.pipe(pow(2))
.pipe(add(10))
.pipe(multiplyBy(4));
const iterpipe = pipeline.iter(10) // 10 is the call value
const first = iterpipe.next();
const second = first.next();
const third = second.next();
const last = third.next();
first.value() // 10
second.value() // 100
third.value() // 110
last.value() // 440
Each function passed in a pipeline can use the previous value and can access to the current pipeline context. A passed context can be enriched with a mutation function, which is the only way to mutate the pipeline from the inside of a function.
const aFunctionUsingContext = (previousValue, context) => {
// mutations are always executed after the current function
context.mutate = () => {
const pipeline = [
(arg: number) => {
const isEven = arg % 2 === 0;
console.log(isEven);
return isEven;
},
];
return { pipeline };
};
console.log(previousValue);
return previousValue;
};
const isEven = pipe(useCallValue())
.pipe(aFunctionUsingContext) // uses console.log for context callValue
.toSyncFunction(); // returns true if the number is even
isEven(10) // logs 10, logs true, returns true
A context has also the call method, which can be used to invoke a function with
the previous value as argument.
pipe(useCallValue())
.pipe(
(value, context) => context.call(add(10))
)
).resolveSync(0); // returns 10
Array functions are under the array module.
import { array } from 'effepi';
Applies the result of a pipeline to each element in an array of another pipeline.
Note: invoking a pipeline using this function with a sync method will throw an error.
const doMath = pipe(useCallValue())
.pipe(sum(2))
.pipe(multiplyBy(3));
pipe(useCallValue())
.pipe(applyEach(doMath))
.resolve([10, 20, 30]) // Promise([36, 66, 96])
Is the same of applyEach, except it does not work with async functions
Note: invoking a pipeline using this function with an async method will throw an error.
const doMath = pipe(useCallValue())
.pipe(sum(2))
.pipe(multiplyBy(3));
pipe(useCallValue())
.pipe(applyEach(doMath))
.resolveSync([10, 20, 30]) // [36, 66, 96]
Concatenates previous value with another array.
If the previous value is not an array an error will be thrown
pipe(useCallValue())
.pipe(concat([4,5,6]))
.resolveSync([1,2,3]) // [1,2,3,4,5,6]
Filters the previous value with a given callback. The callback must return a boolean value.
If the previous value is not an array an error will be thrown
pipe(useCallValue())
.pipe(filter(a => a > 2))
.resolveSync([1,2,3]) // [3]
Filters the previous value with a given value.
If the previous value is not an array an error will be thrown
pipe(useCallValue())
.pipe(filterWith(2))
.resolveSync([1,2,3,2,3,2]) // [2,2,2]
Finds a value as specified by a find callback. The callback must return true.
If the value is not found, the pipe will return an undefined
If the previous value is not an array an error will be thrown
pipe(useCallValue())
.pipe(find((arg: number) => arg === 1))
.resolveSync([1,2,3,2,3,2]) // 1
Finds an exacts value.
If the value is not found, the pipe will return an undefined
If the previous value is not an array an error will be thrown
pipe(useCallValue())
.pipe(find(1))
.resolveSync([1,2,3,2,3,2]) // 1
Joins the previous value with a given char.
If the previous value is not an array an error will be thrown
pipe(useCallValue())
.pipe(join('*'))
.resolveSync([1,2,3]) // '1*2*3'
Returns the length of the previous value.
If the previous value is not an array an error will be thrown
pipe(useCallValue())
.pipe(length())
.resolveSync([1,2,3]) // 3
Returns the nth element in the previous value.
If the previous value is not an array an error will be thrown
pipe(useCallValue())
.pipe(nth(3))
.resolveSync([0, 2, 5, 12, 24]) // 12
Reverses the previous value.
If the previous value is not an array an error will be thrown
pipe(useCallValue())
.pipe(reverse())
.resolveSync([1,2,3]) // [3,2,1]
Boolean functions are under the boolean module.
import { boolean } from 'effepi';
Puts a false value.
pipe(F()).resolveSync(undefined) // false
Puts a true value.
pipe(T()).resolveSync(undefined) // true
Inverts previous value.
Previous value must be boolean.
pipe(useCallValue()).pipe(inverse()).resolveSync(true) // false
pipe(useCallValue()).pipe(inverse()).resolveSync(false) // true
Math functions are under the math module.
import { math } from 'effepi';
Sums a value to the previous one.
pipe(useCallValue()).pipe(add(1)).resolveSync(10) // 11
Changes previous value sign, from positive to negative and vice-versa.
pipe(put(-123)).pipe(changeSign()) // 123
Decrements the previous value by one.
pipe(useCallValue()).pipe(decrement()).resolve(44) // 41
Divides the previous value by the passed one.
pipe(useCallValue()).pipe(divideBy(2)).resolve(44) // 22
Increments the previous value by one.
pipe(useCallValue()).pipe(increment()).resolve(44) // 45
Checks if previous value is negative.
pipe(useCallValue()).pipe(isNegative()).resolveSync(1) // false
pipe(useCallValue()).pipe(isNegative()).resolveSync(-1) // true
Checks if previous value is positive.
pipe(useCallValue()).pipe(isPositive()).resolveSync(1) // true
pipe(useCallValue()).pipe(isPositive()).resolveSync(-1) // false
Multiplies the previous value by the given one
pipe(useCallValue()).pipe(multiplyBy(2)).resolve(44) // 88
Converts previous' value from positive sign to negative unless is already negative.
pipe(useCallValue()).pipe(negative()).resolve(44) // -44
pipe(useCallValue()).pipe(negative()).resolve(-12) // -12
Converts previous' value from negative sign to positive unless is already positive.
pipe(useCallValue()).pipe(positive()).resolve(44) // 44
pipe(useCallValue()).pipe(positive()).resolve(-12) // 12
Raises to power the previous value by a given exponent
pipe(useCallValue()).pipe(pow(4)).resolve(2) // 16
pipe(useCallValue()).pipe(pow(2)).resolve(-2) // 4
Extracts the root of the previous value by a given exponent
pipe(useCallValue()).pipe(root(2)).resolve(4) // 2
pipe(useCallValue()).pipe(root(2)).resolve(9) // 3
Subtracts the previous value by the given one
pipe(useCallValue()).pipe(subtract(2)).resolve(9) // 7
Takes all numbers in a number array between two values (inclusive)
pipe(useCallValue()).pipe(takeBetween(5, 7)).resolve([4, 5, 6, 7, 8]) // [5, 6, 7]
Returns the greater number in a number array
pipe(useCallValue()).pipe(takeGreater()).resolve([4, 5, 44, 6, 7, 8]) // 44
Takes all numbers in a number array greater than a given value.
pipe(useCallValue()).pipe(takeGreaterThan(8)).resolve([4, 5, 44, 6, 7, 8]) // [44]
This function accepts a second parameter (boolean) to include also the same value
pipe(useCallValue()).pipe(takeGreaterThan(8, true)).resolve([4, 5, 44, 6, 7, 8]) // [8, 44]
Returns the lower number in a number array
pipe(takeLower()).pipe(takeGreater()).resolve([4, 5, 44, 6, 7, 8]) // 4
Takes all numbers in a number array lower than a given value.
pipe(useCallValue()).pipe(takeLowerThan(5)).resolve([4, 5, 44, 6, 7, 8]) // [4]
This function accepts a second parameter (boolean) to include also the same value
pipe(useCallValue()).pipe(takeLowerThan(8, true)).resolve([4, 5, 44, 6, 7, 8]) // [4, 5, 6, 7, 8]
Takes all numbers in a number array lower than the first passed value or greater than the second passed value. Matching elements are discarded.
pipe(useCallValue()).pipe(takeOuter(5, 10)).resolveSync([3,4,5,10,11]) // [3, 4, 11]
Logical operators functions are under the logical module.
import { logical } from 'effepi';
Is the same of the switch construct.
// silly example: checking if a string is inside an array of strings
const cities = pipe(useCallValue())
.pipe(
createSwitch(
createSwitchDefault('City not found'),
createSwitchOption('Munich', 'Beerfest!'),
createSwitchOption('Rome', 'We love carbonara'),
createSwitchOption('London', 'God save the queen'),
)
)
.toSyncFunction();
cities('Munich') // 'Beerfest!'
cities('Rome') // 'We love carbonara'
cities('London') // 'God save the queen'
cities('Casalpusterlengo') // 'City not found'
To create the default, you can use the createSwitchDefault method, whilst using createSwitchOption you can add a switch case.
This function is the same of the if/else statement.
It takes two arguments, the left and the right part. The left part is intended as the false comparison result, while the right is the true.
const smsLengthCheck = pipe(useCallValue())
.pipe(isGreaterThan(144))
.pipe(fold('', 'Maximum character'))
.toSyncFunction();
smsLengthCheck('lorem') // ''
smsLengthCheck('lorem'.repeat(2000)) // 'Maximum character'
This function works like the if/else statement.
It requires three arguments:
function which returns a boolean)pipe. If is a pipe, it will be resolved using the context's async/sync flowpipe. If is a pipe, it will be resolved using the context's async/sync flowconst simple = pipe(useCallValue())
.pipe(
logical.ifElse(
(arg: number) => arg > 5,
'lower than 5',
'greater than 5'
)
)
.toSyncFunction();
const complex = pipe(useCallValue())
.pipe(
logical.ifElse(
(arg: number) => arg > 5,
pipe(useCallValue()).pipe(math.pow(2)),
pipe(useCallValue()).pipe(math.divideBy(2)),
)
).toSyncFunction();
simple(4) // lower than 5
simple(10) // greater than 5
complex(4) // 14
complex(10) // 5
Object functions are under the object module.
import { object } from 'effepi';
Returns previous value except for the given keys. This applies only to objects.
pipe(useCallValue())
.pipe(exclude('foo'))
.resolveSync({ foo: 123, bar: 'baz' }); // { bar: 'baz' }
Returns if an object has a owned property
pipe(useCallValue())
.pipe(hasProperty('foo'))
.resolveSync({ foo: new Date() }) // true
Returns previous's value keys. Works only for objects
pipe(useCallValue())
.pipe(keys())
.resolveSync({ bar: 123, foo: new Date() }) // ['bar', 'foo']
Returns a property key value by a given path. This applies only to objects.
pipe(useCallValue())
.pipe(maybe('foo.bar'))
.resolveSync({ foo: { bar: 100 } }) // 100
You can provide a fallback value or a fallback pipeline if the path does not match the object schema.
If you start you pipeline with the useCallValue function, the monad will be invoked with an undefined value.
pipe(useCallValue())
.pipe(maybe('foo.bar.baz', 123))
.resolveSync({ foo: { bar: 100 } }) // 123
// or
const fallback = pipe(useCallValue());
pipe(useCallValue())
.pipe(maybe('foo.bar.baz', fallback))
.resolveSync({ foo: { bar: 100 } }) // undefined
// or
const fallback = pipe(put(10));
pipe(useCallValue())
.pipe(maybe('foo.bar.baz', fallback))
.resolveSync({ foo: { bar: 100 } }) // 10
Merges the previous object with the given one
pipe(useCallValue())
.pipe(merge({ foo: 'bar' }))
.resolveSync({ bar: 'baz' }) // { foo: 'bar', bar: 'baz' }
Returns a new object (previous value) with the given keys. This applies only to objects.
pipe(useCallValue())
.pipe(pick('foo'))
.resolveSync({ foo: 123, bar: 'baz' }); // { foo: 123 }
Misc functions are under the misc module.
import { misc } from 'effepi';
A simple currying function which adapts a function
with two arguments in order to be used with the pipe method.
Can adapt both a sync or an async function.
function myFn(name: string, surname: string): string {
return [name, surname].join(' ');
}
async function myFnAsync(name: string, surname: string): string {
return [name, surname].join(' ');
}
const adaptedMyFn = adapt(myFn);
const adaptedMyFnAsync = adapt(myFnAsync);
pipe(useCallValue())
.pipe(adaptedMyFn('john'))
.resolveSync('snow'); // 'john snow'
pipe(useCallValue())
.pipe(adaptedMyFnAsync('john'))
.resolve('snow'); // Promise('john snow')
Applies a pipeline using the async resolve method.
Note: invoking a pipeline using this function with a sync method will throw an error.
const injectedPipeline = pipe(functions.useCallValue())
.pipe(functions.add(10));
const testPipeline = pipe(functions.useCallValue())
.pipe(functions.apply(injectedPipeline))
.pipe(functions.multiplyBy(2));
testPipeline.resolve(2) // Promise(24)
Applies a pipeline using the sync resolveSync method.
Note: invoking a pipeline using this function with an async method will throw an error.
const injectedPipeline = pipe(functions.useCallValue())
.pipe(functions.add(10));
const testPipeline = pipe(functions.useCallValue())
.pipe(functions.applySync(injectedPipeline))
.pipe(functions.multiplyBy(2));
testPipeline.resolve(2) // 24
Calls previous value with a specific argument.
Works both with async and sync flows, and the argument can be both a value or a pipeline.
It will throw a TypeError if the previous value is not a function.
const p1 = pipe(put(2));
pipe(useCallValue())
.pipe(callWith(2))
.resolveSync((arg: number) => arg * 2) // 4
pipe(useCallValue())
.pipe(callWith(2))
.resolve(async (arg: number) => arg * 2) // Promise(4)
Use this function to put a value at the beginning of the pipeline
pipe(put(10)).resolveSync(0) // 10
Use this function to perform a safe function call (will not throw) with the previous value as argument
pipe(useCallValue())
.pipe(safeCall(() => throw new Error('hello world')))
.resolveSync(100) // will not throw, instead it will return 100
Use this function at the beginning of your pipeline to use the passed value to resolve/resolveSync and to function invokation
const p = pipe(useCallValue()).add(100);
p.resolve(200) // Promise(300)
p.resolveSync(10) // 110
p.toFunction()(123) // Promise(223)
p.toSyncFunction()(1000) // 1100
Use this function to return the previous pipeline value
pipe(useCallValue())
.pipe(add(10))
.pipe(useValue())
.resolveSync(10) // 20
String functions are under the string module.
import { string } from 'effepi';
Returns previous value in camel-case. Previous value must be a string.
pipe(useCallValue())
.pipe(camelCase())
.resolveSync('hello world') // 'helloWorld'
Returns previous value as an array of chars. Previous value must be a string.
pipe(useCallValue())
.pipe(chars())
.resolveSync('hello') // returns ['h', 'e', 'l', 'l', 'o']
Concatenate previous value with another string. Previous value must be a string.
pipe(useCallValue())
.pipe(concat('world'))
.resolveSync('hello') // 'helloworld'
Returns if the previous value contains a portion of text. Previous value must be a string.
pipe(useCallValue())
.pipe(includes('llo'))
.resolveSync('hello') // true
Returns previous value length. Previous value must be a string.
pipe(useCallValue())
.pipe(length())
.resolveSync('hello') // 5
Returns previous value in lower case. Previous value must be a string.
pipe(useCallValue())
.pipe(lowercase())
.resolveSync('HELLO') // 'hello'
Returns previous value in pascal-case. Previous value must be a string.
pipe(useCallValue())
.pipe(pascalCase())
.resolveSync('hello world') // 'HelloWorld'
Repeats previous value a number of times. Previous value must be a string.
pipe(useCallValue())
.pipe(repeat())
.resolveSync('hello') // hellohello
pipe(useCallValue())
.pipe(repeat(2))
.resolveSync('hello') // hellohellohello
Replaces all occurencies from the previous value. Previous value must be a string.
pipe(useCallValue())
.pipe(replaceAll('l', '1'))
.resolveSync('hello') // he110
Returns previous value in a binary representation. Previous value must be a string.
pipe(useCallValue())
.pipe(toBinary())
.resolveSync('hello world') // [ '1101000', '1100101', '1101100', '1101100', '1101111', '100000', '1110111', '1101111', '1110010', '1101100', '1100100' ]
Returns previous value in upper case. Previous value must be a string.
pipe(useCallValue())
.pipe(uppercase())
.resolveSync('hello') // 'HELLO'
Type functions are under the type module.
import { type } from 'effepi';
Throws if the previous value is not of the same type expected.
TypeName is the second portion of a Object.prototype.toString call in lowerCase:
[object TypeName] -->
typename
pipe(useCallValue())
.pipe(exactTypeOf('date'))
.resolveSync(123) // throws!
pipe(useCallValue())
.pipe(exactTypeOf('date'))
.resolveSync(new Date()) // 2019-03-26T02:17:000Z
Throws if the previous value is not of the same type expected.
Internally uses the typeof operator.
pipe(useCallValue())
.pipe(exactTypeOf('number'))
.resolveSync(123) // 123
pipe(useCallValue())
.pipe(exactTypeOf('number'))
.resolveSync(`hello world!`) // throws!
Converts previous value to an array.
pipe(useCallValue()).pipe(toArray()).resolveSync(10) // [10]
Converts previous value to a boolean value.
pipe(useCallValue()).pipe(toBoolean()).resolveSync(10) // true
pipe(useCallValue()).pipe(toBoolean()).resolveSync(0) // false
pipe(useCallValue()).pipe(toBoolean()).resolveSync(null) // false
pipe(useCallValue()).pipe(toBoolean()).resolveSync(undefined) // false
pipe(useCallValue()).pipe(toBoolean()).resolveSync('') // false
pipe(useCallValue()).pipe(toBoolean()).resolveSync('123') // true
Converts previous value to a Date instance.
pipe(useCallValue())
.pipe(toDate())
.resolveSync(`2019-01-01T00:00:000Z`) // Date(2019, 0, 1, 0, 0, 0)
Converts previous value to a number.
pipe(useCallValue())
.pipe(toNumber())
.resolveSync('12000') // 12000
Converts previous value to a set. Previous value must be an array.
Converts previous value to a string.
pipe(useCallValue())
.pipe(toString())
.resolveSync([1,2,3]) // "1,2,3"
Every contribution is welcome! Before creating pull-request or opening issues, ensure you have read the contribution guidelines
This library is released under the MIT licence
FAQs
Fun functional programming with pipelinable functions
We found that effepi demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.