Start routing and pipeline rewrite
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2022-01-17 15:57:40 -06:00
parent 9b8333295f
commit 8cf19792a6
34 changed files with 470 additions and 1654 deletions

View File

@@ -7,7 +7,7 @@ import {
} from './Collection'
import {Iterable, StopIteration} from './Iterable'
import {applyWhere, WhereOperator} from './where'
import {AsyncPipe, Pipe} from '../support/Pipe'
import {AsyncPipe, Pipeline} from '../support/Pipe'
type AsyncCollectionComparable<T> = CollectionItem<T>[] | Collection<T> | AsyncCollection<T>
type AsyncKeyFunction<T, T2> = (item: CollectionItem<T>, index: number) => CollectionItem<T2> | Promise<CollectionItem<T2>>
type AsyncCollectionFunction<T, T2> = (items: AsyncCollection<T>) => T2
@@ -798,19 +798,16 @@ export class AsyncCollection<T> {
return this.storedItems.range(start, end)
}
/**
* Return the value of the function, passing this collection to it.
* @param {AsyncCollectionFunction} func
*/
pipeTo<T2>(func: AsyncCollectionFunction<T, T2>): any {
return func(this)
}
/**
* Return a new Pipe of this collection.
*/
pipe(): Pipe<AsyncCollection<T>> {
return Pipe.wrap(this)
pipeTo<TOut>(pipeline: Pipeline<this, TOut>): TOut {
return pipeline.apply(this)
}
/** Build and apply a pipeline. */
pipe<TOut>(builder: (pipeline: Pipeline<this, this>) => Pipeline<this, TOut>): TOut {
return builder(Pipeline.id()).apply(this)
}
/**

View File

@@ -1,4 +1,4 @@
import {AsyncPipe, Pipe} from '../support/Pipe'
import {AsyncPipe, Pipeline} from '../support/Pipe'
type CollectionItem<T> = T
type MaybeCollectionItem<T> = CollectionItem<T> | undefined
@@ -822,8 +822,13 @@ class Collection<T> {
/**
* Return a new Pipe of this collection.
*/
pipe(): Pipe<Collection<T>> {
return Pipe.wrap(this)
pipeTo<TOut>(pipeline: Pipeline<this, TOut>): TOut {
return pipeline.apply(this)
}
/** Build and apply a pipeline. */
pipe<TOut>(builder: (pipeline: Pipeline<this, this>) => Pipeline<this, TOut>): TOut {
return builder(Pipeline.id()).apply(this)
}
/**

View File

@@ -1,6 +1,6 @@
import {collect} from '../collection/Collection'
import {InvalidJSONStateError, JSONState, Rehydratable} from './Rehydratable'
import {Pipe} from './Pipe'
import {Pipeline} from './Pipe'
/**
* A class for building and working with messages grouped by keys.
@@ -131,9 +131,14 @@ export class Messages implements Rehydratable {
}
/**
* Get a new Pipe object wrapping this instance.
* Return a new Pipe of this collection.
*/
pipe(): Pipe<Messages> {
return Pipe.wrap<Messages>(this)
pipeTo<TOut>(pipeline: Pipeline<this, TOut>): TOut {
return pipeline.apply(this)
}
/** Build and apply a pipeline. */
pipe<TOut>(builder: (pipeline: Pipeline<this, this>) => Pipeline<this, TOut>): TOut {
return builder(Pipeline.id()).apply(this)
}
}

View File

@@ -1,14 +1,14 @@
/**
* A closure that maps a given pipe item to a different type.
*/
import {Awaitable} from './types'
import {Awaitable, Maybe} 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
export type ReflexivePipeOperator<T> = (subject: T) => Maybe<T>
/**
* A condition or condition-resolving function for pipe methods.
@@ -19,48 +19,14 @@ 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)
export class Pipeline<TIn, TOut> {
static id<T>(): Pipeline<T, T> {
return new Pipeline(x => x)
}
constructor(
/**
* The item being operated on.
*/
private subject: T,
protected readonly factory: (TIn: TIn) => TOut,
) {}
/**
@@ -75,17 +41,22 @@ export class Pipe<T> {
*
* @param op
*/
tap<T2>(op: PipeOperator<T, T2>): Pipe<T2> {
return new Pipe(op(this.subject))
tap<T2>(op: PipeOperator<TOut, T2>): Pipeline<TIn, T2> {
return new Pipeline((val: TIn) => {
return op(this.factory(val))
})
}
/**
* Like tap, but always returns the original pipe.
* Like tap, but always returns the original pipe type.
* @param op
*/
peek<T2>(op: PipeOperator<T, T2>): this {
op(this.subject)
return this
peek<T2>(op: PipeOperator<TOut, T2>): Pipeline<TIn, TOut> {
return new Pipeline((val: TIn) => {
const nextVal = this.factory(val)
op(nextVal)
return nextVal
})
}
/**
@@ -95,14 +66,20 @@ export class Pipe<T> {
* @param check
* @param op
*/
when(check: PipeCondition<T>, op: ReflexivePipeOperator<T>): Pipe<T> {
if (
(typeof check === 'function' && check(this.subject))
|| (typeof check !== 'function' && check) ) {
return Pipe.wrap(op(this.subject))
}
when(check: PipeCondition<TOut>, op: ReflexivePipeOperator<TOut>): Pipeline<TIn, TOut> {
return new Pipeline((val: TIn) => {
const nextVal = this.factory(val)
if ( this.checkCondition(check, nextVal) ) {
const appliedVal = op(nextVal)
if ( typeof appliedVal === 'undefined' ) {
return nextVal
}
return this
return appliedVal
}
return nextVal
})
}
/**
@@ -112,42 +89,32 @@ export class Pipe<T> {
* @param check
* @param op
*/
unless(check: PipeCondition<T>, op: ReflexivePipeOperator<T>): Pipe<T> {
if (
(typeof check === 'function' && check(this.subject))
|| (typeof check !== 'function' && check) ) {
return this
}
unless(check: PipeCondition<TOut>, op: ReflexivePipeOperator<TOut>): Pipeline<TIn, TOut> {
return new Pipeline((val: TIn) => {
const nextVal = this.factory(val)
if ( !this.checkCondition(check, nextVal) ) {
const appliedVal = op(nextVal)
if ( typeof appliedVal === 'undefined' ) {
return nextVal
}
return Pipe.wrap(op(this.subject))
return appliedVal
}
return nextVal
})
}
/**
* Alias of `unless()`.
* @param check
* @param op
* Apply the pipeline to an input.
*/
whenNot(check: PipeCondition<T>, op: ReflexivePipeOperator<T>): Pipe<T> {
return this.unless(check, op)
apply(input: TIn): TOut {
return this.factory(input)
}
/**
* 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)
protected checkCondition(check: PipeCondition<TOut>, val: TOut): boolean {
return (typeof check === 'function' && check(val))
|| (typeof check !== 'function' && check)
}
}
@@ -280,13 +247,6 @@ export class AsyncPipe<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()

View File

@@ -5,7 +5,7 @@ import * as mime from 'mime-types'
import {FileNotFoundError, Filesystem} from './path/Filesystem'
import {Collection} from '../collection/Collection'
import {Readable, Writable} from 'stream'
import {Pipe} from './Pipe'
import {Pipeline} from './Pipe'
/**
* An item that could represent a path.
@@ -533,8 +533,15 @@ export class UniversalPath {
return false
}
/** Get a new Pipe instance wrapping this. */
toPipe(): Pipe<UniversalPath> {
return Pipe.wrap(this)
/**
* Return a new Pipe of this collection.
*/
pipeTo<TOut>(pipeline: Pipeline<this, TOut>): TOut {
return pipeline.apply(this)
}
/** Build and apply a pipeline. */
pipe<TOut>(builder: (pipeline: Pipeline<this, this>) => Pipeline<this, TOut>): TOut {
return builder(Pipeline.id()).apply(this)
}
}

View File

@@ -4,6 +4,28 @@ export type Awaitable<T> = T | Promise<T>
/** Type alias for something that may be undefined. */
export type Maybe<T> = T | undefined
export type Either<T1, T2> = Left<T1> | Right<T2>
export type Left<T> = [T, undefined]
export type Right<T> = [undefined, T]
export function isLeft<T1, T2>(what: Either<T1, T2>): what is Left<T1> {
return typeof what[1] === 'undefined'
}
export function isRight<T1, T2>(what: Either<T1, T2>): what is Right<T2> {
return typeof what[0] === 'undefined'
}
export function left<T>(what: T): Left<T> {
return [what, undefined]
}
export function right<T>(what: T): Right<T> {
return [undefined, what]
}
/** Type alias for a callback that accepts a typed argument. */
export type ParameterizedCallback<T> = ((arg: T) => any)
@@ -30,3 +52,7 @@ export function hasOwnProperty<X extends {}, Y extends PropertyKey>(obj: X, prop
export interface TypeTag<S extends string> {
readonly __typeTag: S
}
export type PrefixTypeArray<T, TArr extends unknown[]> = [T, ...TArr]
export type SuffixTypeArray<TArr extends unknown[], T> = [...TArr, T]
export type TypeArraySignature<TArr extends unknown[], TReturn> = (...params: TArr) => TReturn