|
|
|
/**
|
|
|
|
* A closure that maps a given pipe item to a different type.
|
|
|
|
*/
|
|
|
|
import {Awaitable} from './types'
|
|
|
|
|
|
|
|
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))
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Like tap, but always returns the original pipe.
|
|
|
|
* @param op
|
|
|
|
*/
|
|
|
|
peek<T2>(op: PipeOperator<T, T2>): this {
|
|
|
|
op(this.subject)
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get an AsyncPipe with the current item in the pipe.
|
|
|
|
*/
|
|
|
|
async(): AsyncPipe<T> {
|
|
|
|
return AsyncPipe.wrap<T>(this.subject)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A subject function that yields the value in the AsyncPipe.
|
|
|
|
*/
|
|
|
|
export type AsyncPipeResolver<T> = () => Awaitable<T>
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A closure that maps a given pipe item to a different type.
|
|
|
|
*/
|
|
|
|
export type AsyncPipeOperator<T, T2> = (subject: T) => Awaitable<T2>
|
|
|
|
|
|
|
|
export type PromisePipeOperator<T, T2> = (subject: T, resolve: (val: T2) => unknown, reject: (err: Error) => unknown) => Awaitable<unknown>
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A closure that maps a given pipe item to an item of the same type.
|
|
|
|
*/
|
|
|
|
export type ReflexiveAsyncPipeOperator<T> = (subject: T) => Awaitable<T|void>
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A condition or condition-resolving function for pipe methods.
|
|
|
|
*/
|
|
|
|
export type AsyncPipeCondition<T> = boolean | ((subject: T) => Awaitable<boolean>)
|
|
|
|
|
|
|
|
/**
|
|
|
|
* An asynchronous version of the Pipe helper.
|
|
|
|
*/
|
|
|
|
export class AsyncPipe<T> {
|
|
|
|
/**
|
|
|
|
* Get an AsyncPipe with the given value in it.
|
|
|
|
* @param subject
|
|
|
|
*/
|
|
|
|
static wrap<subjectType>(subject: subjectType): AsyncPipe<subjectType> {
|
|
|
|
return new AsyncPipe<subjectType>(() => subject)
|
|
|
|
}
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
/** The current value resolver of the pipe. */
|
|
|
|
private subject: AsyncPipeResolver<T>,
|
|
|
|
) {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Apply a transformative operator to the pipe.
|
|
|
|
* @param op
|
|
|
|
*/
|
|
|
|
tap<T2>(op: AsyncPipeOperator<T, T2>): AsyncPipe<T2> {
|
|
|
|
return new AsyncPipe<T2>(async () => op(await this.subject()))
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Apply a transformative operator to the pipe, wrapping it
|
|
|
|
* in a Promise and passing the resolve/reject callbacks to the
|
|
|
|
* closure.
|
|
|
|
* @param op
|
|
|
|
*/
|
|
|
|
promise<T2>(op: PromisePipeOperator<T, T2>): AsyncPipe<T2> {
|
|
|
|
return new AsyncPipe<T2>(() => {
|
|
|
|
return new Promise<T2>((res, rej) => {
|
|
|
|
(async () => this.subject())()
|
|
|
|
.then(subject => {
|
|
|
|
op(subject, res, rej)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Apply an operator to the pipe, but return the reference
|
|
|
|
* to the current pipe. The operator is resolved when the
|
|
|
|
* overall pipe is resolved.
|
|
|
|
* @param op
|
|
|
|
*/
|
|
|
|
peek<T2>(op: AsyncPipeOperator<T, T2>): AsyncPipe<T> {
|
|
|
|
return new AsyncPipe<T>(async () => {
|
|
|
|
const subject = await this.subject()
|
|
|
|
await op(subject)
|
|
|
|
return subject
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Apply an operator to the pipe, if the check condition passes.
|
|
|
|
* @param check
|
|
|
|
* @param op
|
|
|
|
*/
|
|
|
|
when(check: AsyncPipeCondition<T>, op: ReflexiveAsyncPipeOperator<T>): AsyncPipe<T> {
|
|
|
|
return new AsyncPipe<T>(async () => {
|
|
|
|
let subject
|
|
|
|
|
|
|
|
if ( typeof check === 'function' ) {
|
|
|
|
check = await check(subject = await this.subject())
|
|
|
|
}
|
|
|
|
|
|
|
|
subject = subject ?? await this.subject()
|
|
|
|
if ( check ) {
|
|
|
|
return ((await op(subject)) ?? subject) as T
|
|
|
|
}
|
|
|
|
|
|
|
|
return subject as T
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Apply an operator to the pipe, if the check condition fails.
|
|
|
|
* @param check
|
|
|
|
* @param op
|
|
|
|
*/
|
|
|
|
unless(check: AsyncPipeCondition<T>, op: ReflexiveAsyncPipeOperator<T>): AsyncPipe<T> {
|
|
|
|
if ( typeof check === 'function' ) {
|
|
|
|
return this.when(async (subject: T) => !(await check(subject)), op)
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.when(!check, op)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Alias of `unless()`.
|
|
|
|
* @param check
|
|
|
|
* @param op
|
|
|
|
*/
|
|
|
|
whenNot(check: AsyncPipeCondition<T>, op: ReflexiveAsyncPipeOperator<T>): AsyncPipe<T> {
|
|
|
|
return this.unless(check, op)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the transformed value from the pipe.
|
|
|
|
*/
|
|
|
|
async resolve(): Promise<T> {
|
|
|
|
return this.subject()
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resolve the value and return it in a sync `Pipe` instance.
|
|
|
|
*/
|
|
|
|
async sync(): Promise<Pipe<T>> {
|
|
|
|
return Pipe.wrap<T>(await this.subject())
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Get the transformed value from the pipe. Allows awaiting the pipe directly. */
|
|
|
|
then(): Promise<T> {
|
|
|
|
return this.resolve()
|
|
|
|
}
|
|
|
|
}
|