UNPKG

@pedromsilva/data-either

Version:

Simple TypeScript/ES2017 class to represent either values

302 lines (256 loc) 7.41 kB
export interface Mapper<T, U> { ( value : T ) : U; } export interface Consumer<T> { ( value : T ) : void; } export interface Predicate<T> { ( value : T ) : boolean; } export interface Supplier<T> { () : T; } export class WrongSideError extends Error {} export enum EitherSide { Left = 0, Right = 1 } export class Either<L, R> { protected side : EitherSide; protected value : L | R; protected get left () : L { return this.value as L; } protected get right () : R { return this.value as R; } /** * Instantiates an Either object with a left value. * * @static * @template L * @template R * @param {L} value * @returns {Either<L, R>} * @memberof Either */ static left<L, R> ( value : L ) : Either<L, R> { return new Either<L, R>( EitherSide.Left, value ); } /** * Instantiates an Either object, with a right value. * * @static * @template L * @template R * @param {R} value * @returns {Either<L, R>} * @memberof Either */ static right<L, R> ( value : R ) : Either<L, R> { return new Either<L, R>( EitherSide.Right, value ); } /** * Converts a function that might throw an error into an Either object. * Left represents the successful return value of the function. * Right represents the catched value in the try ... catch. * * @static * @template L * @template R * @param {Supplier<L>} supplier * @returns {Either<L, R>} * @memberof Either */ static ofThrowable<L, R> ( supplier : Supplier<L> ) : Either<L, R> { try { return Either.left( supplier() ); } catch ( error ) { return Either.right( error ); } } /** * Converts a NodeJS-style async callback that receives an error and a value. * If the error is different than null, calls the callback with the error on the right * side. If error is null, calls the callback with the value on the left side. * * @static * @template L * @template R * @param {Consumer<Either<L, R>>} cb * @returns {( err : R, value : L ) => void} * @memberof Either */ static ofCallback<L, R> ( cb : Consumer<Either<L, R>> ) : ( err : R, value : L ) => void { return ( err, value ) => { if ( err ) { cb( Either.right( err ) ); } cb( Either.left( value ) ); }; } /** * Converts a promise of a value that might return a rejection into a * promise that always resolves with an Either object. * * Left represents the resolve of the original promise, * while Right represents a rejection. * * @static * @template L * @template R * @param {Promise<L>} promise * @returns {Promise<Either<L, R>>} * @memberof Either */ static ofPromise<L, R> ( promise : Promise<L> ) : Promise<Either<L, R>> { return promise .then( value => Either.left<L, R>( value ) ) .catch( error => Either.right<L, R>( error ) ); } constructor ( side : EitherSide.Left, value : L ); constructor ( side : EitherSide.Right, value : R ); constructor ( side : EitherSide, value : L | R ) { this.side = side; this.value = value; } /** * Returns a boolean indicating if this object represents a left value. * * @returns {boolean} * @memberof Either */ isLeft () : boolean { return this.side === EitherSide.Left; } /** * Returns a boolean indicating if this object represents a right value. * * @returns {boolean} * @memberof Either */ isRight () : boolean { return this.side === EitherSide.Right; } /** * Returns the value. Can be either left or right. * * @returns {(L | R)} * @memberof Either */ get () : L | R { return this.value; } /** * Returns the value of the left side. If this object is the right side, * throws a WrongSideError. * * @throws {WrongSideError} * @returns {L} * @memberof Either */ getLeft () : L { if ( this.isLeft() ) { return this.left; } throw new WrongSideError(); } /** * Returns the value of the right side. If this object is the left side, * throws a WrongSideError. * * @throws {WrongSideError} * @returns {R} * @memberof Either */ getRight () : R { if ( this.isRight() ) { return this.right; } throw new WrongSideError(); } /** * Perform an action only if the left value is present. * * @param {Consumer<L>} consumer * @returns {this} * @memberof Either */ ifLeft ( consumer : Consumer<L> ) : this { if ( this.isLeft() ) { consumer( this.left ); } return this; } /** * Perform an action only if the right value is present. * * @param {Consumer<R>} consumer * @returns {this} * @memberof Either */ ifRight ( consumer : Consumer<R> ) : this { if ( this.isRight() ) { consumer( this.right ); } return this; } reduce<O> ( left : Mapper<L, O>, right : Mapper<R, O> ) : O { if ( this.isLeft() ) { return left( this.left ); } else if ( this.isRight() ) { return right( this.right ); } } flatMap<NL, NR> ( left : Mapper<L, Either<NL, NR>>, right : Mapper<R, Either<NL, NR>> ) : Either<NL, NR> { return this.reduce( left, right ); } flatMapLeft<NL> ( mapper : Mapper<L, Either<NL, R>> ) : Either<NL, R> { return this.flatMap( mapper, v => Either.right( v ) ); } flatMapRight<NR> ( mapper : Mapper<R, Either<L, NR>> ) : Either<L, NR> { return this.flatMap( v => Either.left( v ), mapper ); } map<NL, NR> ( left : Mapper<L, NL>, right : Mapper<R, NR> ) : Either<NL, NR> { return this.flatMap( v => Either.left( left( v ) ), v => Either.right( right( v ) ) ); } mapLeft<NL> ( mapper : Mapper<L, NL> ) : Either<NL, R> { return this.map( mapper, id => id ); } mapRight<NR> ( mapper : Mapper<R, NR> ) : Either<L, NR> { return this.map( id => id, mapper ); } leftOrElseGet ( getter : Supplier<L> ) : L { if ( this.isLeft() ) { return this.left; } return getter(); } leftOrElse ( other : L ) : L { return this.leftOrElseGet( () => other ); } leftOrElseThrow<E extends Error> ( supplier : Supplier<E> ) : L { if ( this.isLeft() ) { this.getLeft(); } throw supplier(); } rightOrElseGet ( getter : Supplier<R> ) : R { if ( this.isRight() ) { return this.right; } return getter(); } rightOrElse ( other : R ) : R { return this.rightOrElseGet( () => other ); } rightOrElseThrow<E extends Error> ( supplier : Supplier<E> ) : R { if ( this.isRight() ) { this.getRight(); } throw supplier(); } }