DEV Community

Matthias Hryniszak
Matthias Hryniszak

Posted on

Pipes! Pipes everywhere!

I love small bits that do great things. This time it's about adding the pipe() method to any object, e.g.

const items = [ { price: 1 }, { price: 2 } ]
const sumPrice = (acc, item) => acc + item.price
const sum = pipeable(items)
  .pipe(items => items.reduce(sumPrice, 0))
  .pipe(sum => sum++)
Enter fullscreen mode Exit fullscreen mode

And on and on, you can pipe the result of one callback to another, with the first callback receiving the pipeable value.

Now, how do we type this so that it makes sense in TypeScript?

export type Pipeable<I> = {
  pipe<O>(transform: (input: I) => O): O & Pipeable<O>
}
Enter fullscreen mode Exit fullscreen mode

Having the type definition of the Pipeable type let's try to implement it:

function pipeable<I>(input: I) {
  const result = clone(input)
  if ((result as any).pipe) {
    throw new Error('Error: object is already pipeable')
  }

  const pipeableResult = result as Pipeable<I>
  pipeableResult.pipe = <O>(transform: (input: I) => O) => pipeable<O>(transform(result))

  return result as I & Pipeable<I>
}
Enter fullscreen mode Exit fullscreen mode

Depending on your needs, clone can either have a trivial implementation like this one:

function clone<T>(input: T): T {
  return JSON.parse(JSON.stringify(input))
}
Enter fullscreen mode Exit fullscreen mode

or, you could go full on with packages such as clone-deep.

Top comments (0)