Onion Compose
Simple function inspired by koa compose the difference is that this module accepts any number of arguments as opposed to original which accepts only one argument.
Install
npm i onion-compose
Motivation
Composing functions with onion
style execution is a very powerful concept, especially in the HTTP
framework for composing middleware functions, and I wanted the same middleware execution style as koa.js
but with a middleware signature like expressjs
, hence this little module has been created.
If you need a refresher on what is onion style
composition, check this out:
test('Run order', async () => {
const original: number[] = []
const fn1 = async (arr: number[], next: () => void) => {
arr.push(1)
await next()
arr.push(2)
return 'done'
}
const fn2 = async (arr: number[], next: () => void) => {
arr.push(3)
await next()
arr.push(4)
}
const result = await compose([fn1, fn2])([original])
expect(original).toEqual(expect.arrayContaining([1, 3, 4, 2]))
expect(result).toBe('done')
})
Usage
import compose from 'compose'
const fn1 = async (a: string, b: number, next: () => void) => {
await next()
}
const fn2 = async (a: string, b: number, next: () => void) => {
await next()
}
const fn3 = async (a: string, b: number, next: () => void) => {
await next()
}
await compose([fn1, fn2, fn3])(['foo', 1])
// or
await compose([fn1, fn2])(['foo', 1], fn3)
You can use compose
with any number of arguments.
const fn = (
a: string,
b: number,
c: boolean,
d: string[],
e: number[],
_next: () => void
) => undefined
await compose([fn])(['a', 1, true, ['a', 'b'], [1, 2]])
One difference with koa-compose
is that because of the arbitrary number of arguments you cannot nest compose
inside another compose
(which koa-compose can do), but it's very easy to achieve that with the help of a simple function.
//utility function to wrap compose before passing it to another compose
function nest(composeExec) {
return (...args) => {
const next = args[args.length - 1]
args.pop()
return composeExec(args, next)
}
}
const fn1 = (a, next) => {
next()
}
const fn2 = (a, next) => {
next()
}
const fn3 = (a: string, next: () => void) => {
next()
}
const nestedCompose = nest(compose([fn1, fn2]))
// run order: fn3, fn, fn2
await compose([fn3, nestedCompose])(['foo'])
Keep in mind that Typescript types can stop working when nesting compose
inside another compose
if you know how to make the types work, please make a pull request.
All original koa-compose
tests are passing, except for the nested compose test (you can pass that one with the help of the utility function above).