/** * A closure that maps a given pipe item to a different type. */ export type PipeOperator = (subject: T) => T2 /** * A closure that maps a given pipe item to an item of the same type. */ export type ReflexivePipeOperator = (subject: T) => T /** * A condition or condition-resolving function for pipe methods. */ export type PipeCondition = boolean | ((subject: T) => boolean) /** * A class for writing chained/conditional operations in a data-flow manner. * * This is useful when you need to do a series of operations on an object, perhaps conditionally. * * @example * Say we have a Collection of items, and want to apply some transformations and filtering based on arguments: * * ```typescript * const collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9]) * * function transform(collection, evensOnly = false, returnEntireCollection = false) { * return Pipe.wrap(collection) * .when(evensOnly, coll => { * return coll.filter(x => !(x % 2)) * }) * .unless(returnEntireCollection, coll => { * return coll.take(3) * }) * .tap(coll => { * return coll.map(x => x * 2)) * }) * .get() * } * * transform(collection) // => Collection[2, 4, 6] * * transform(collection, true) // => Collection[4, 8, 12] * * transform(collection, false, true) // => Collection[2, 4, 6, 8, 10, 12, 14, 16, 18] * ``` */ export class Pipe { /** * Return a new Pipe containing the given subject. * @param subject */ static wrap(subject: subjectType): Pipe { return new Pipe(subject) } constructor( /** * The item being operated on. */ private subject: T, ) {} /** * Apply the given operator to the item in the pipe, and return a new pipe with the result. * * @example * ```typescript * Pipe.wrap(2) * .tap(x => x * 4) * .get() // => 8 * ``` * * @param op */ tap(op: PipeOperator): Pipe { return new Pipe(op(this.subject)) } /** * If `check` is truthy, apply the given operator to the item in the pipe and return the result. * Otherwise, just return the current pipe unchanged. * * @param check * @param op */ when(check: PipeCondition, op: ReflexivePipeOperator): Pipe { if ( (typeof check === 'function' && check(this.subject)) || check ) { return Pipe.wrap(op(this.subject)) } return this } /** * If `check` is falsy, apply the given operator to the item in the pipe and return the result. * Otherwise, just return the current pipe unchanged. * * @param check * @param op */ unless(check: PipeCondition, op: ReflexivePipeOperator): Pipe { if ( (typeof check === 'function' && check(this.subject)) || check ) { return this } return Pipe.wrap(op(this.subject)) } /** * Alias of `unless()`. * @param check * @param op */ whenNot(check: PipeCondition, op: ReflexivePipeOperator): Pipe { return this.unless(check, op) } /** * Get the item in the pipe. * * @example * ```typescript * Pipe.wrap(4).get() // => 4 * ``` */ get(): T { return this.subject } }