onion-compose

Simple function inspired by koa compose the difference is that this one accepts any number of arguments.

MIT License

Downloads
34
Stars
2
Committers
4

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).