Simple function inspired by koa compose the difference is that this one accepts any number of arguments.
MIT License
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.
npm i onion-compose
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')
})
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).