You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lib/src/util/support/Pipe.ts

131 lines
3.4 KiB

/**
* A closure that maps a given pipe item to a different type.
*/
export type PipeOperator<T, T2> = (subject: T) => T2
/**
* A closure that maps a given pipe item to an item of the same type.
*/
export type ReflexivePipeOperator<T> = (subject: T) => T
/**
* A condition or condition-resolving function for pipe methods.
*/
export type PipeCondition<T> = 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<T> {
/**
* Return a new Pipe containing the given subject.
* @param subject
*/
static wrap<subjectType>(subject: subjectType): Pipe<subjectType> {
return new Pipe<subjectType>(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<T2>(op: PipeOperator<T, T2>): Pipe<T2> {
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<T>, op: ReflexivePipeOperator<T>): Pipe<T> {
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<T>, op: ReflexivePipeOperator<T>): Pipe<T> {
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<T>, op: ReflexivePipeOperator<T>): Pipe<T> {
return this.unless(check, op)
}
/**
* Get the item in the pipe.
*
* @example
* ```typescript
* Pipe.wrap(4).get() // => 4
* ```
*/
get(): T {
return this.subject
}
}