import { BiMapper, IOptional, type Mapper, Optional, Predicate, type Supplier, Tagged, isTagged, } from '@emprespresso/pengueno'; export const IEitherTag = 'IEither' as const; export type IEitherTag = typeof IEitherTag; export const isEither = (o: unknown): o is IEither => isTagged(o, IEitherTag); export interface IEither extends Tagged { readonly left: Supplier>; readonly right: Supplier>; readonly mapRight: <_T>(mapper: Mapper) => IEither; readonly filter: (mapper: Predicate) => IEither; readonly mapLeft: <_E>(mapper: Mapper) => IEither<_E, T>; readonly mapBoth: <_E, _T>(errBranch: Mapper, okBranch: Mapper) => IEither<_E, _T>; readonly flatMap: <_T>(mapper: Mapper>) => IEither; readonly flatMapAsync: <_T>(mapper: Mapper>>) => Promise>; readonly moveRight: <_T>(t: _T) => IEither; readonly fold: <_T>(leftFolder: Mapper, rightFolder: Mapper) => _T; readonly joinRight: (other: IEither, mapper: BiMapper) => IEither; readonly joinRightAsync: ( other: Supplier>> | Promise>, mapper: BiMapper, ) => Promise>; } const ELeftTag = 'E.Left' as const; type ELeftTag = typeof ELeftTag; export const isLeft = (o: unknown): o is Left => isTagged(o, ELeftTag); interface Left extends Tagged { err: E; } const ERightTag = 'E.Right' as const; type ERightTag = typeof ERightTag; export const isRight = (o: unknown): o is Right => isTagged(o, ERightTag); interface Right extends Tagged { ok: T; } class _Tagged implements Tagged { protected constructor(public readonly _tag = IEitherTag) {} } export class Either extends _Tagged implements IEither { protected constructor(private readonly self: Left | Right) { super(); } public moveRight<_T>(t: _T) { return this.mapRight(() => t); } public mapBoth<_E, _T>(errBranch: Mapper, okBranch: Mapper): IEither<_E, _T> { if (isLeft(this.self)) return Either.left(errBranch(this.self.err)); return Either.right(okBranch(this.self.ok)); } public mapRight<_T>(mapper: Mapper): IEither { if (isRight(this.self)) return Either.right(mapper(this.self.ok)); return Either.left(this.self.err); } public mapLeft<_E>(mapper: Mapper): IEither<_E, T> { if (isLeft(this.self)) return Either.left(mapper(this.self.err)); return Either.right(this.self.ok); } public flatMap<_T>(mapper: Mapper>): IEither { if (isRight(this.self)) return mapper(this.self.ok); return Either.left(this.self.err); } public filter(mapper: Predicate): IEither { if (isLeft(this.self)) return Either.left(this.self.err); return Either.fromFailable(() => this.right().filter(mapper).get()); } public async flatMapAsync<_T>(mapper: Mapper>>): Promise> { if (isLeft(this.self)) return Promise.resolve(Either.left(this.self.err)); return await mapper(this.self.ok).catch((err) => Either.left(err)); } public fold<_T>(leftFolder: Mapper, rightFolder: Mapper): _T { if (isLeft(this.self)) return leftFolder(this.self.err); return rightFolder(this.self.ok); } public left(): IOptional { if (isLeft(this.self)) return Optional.from(this.self.err) as IOptional; return Optional.none(); } public right(): IOptional { if (isRight(this.self)) return Optional.from(this.self.ok) as IOptional; return Optional.none(); } public joinRight(other: IEither, mapper: BiMapper) { return this.flatMap((t) => other.mapRight((o) => mapper(o, t))); } public joinRightAsync( other: Supplier>> | Promise>, mapper: BiMapper, ) { return this.flatMapAsync(async (t) => { const o = typeof other === 'function' ? other() : other; return o.then((other) => other.mapRight((o) => mapper(o, t))); }); } static left(e: E): IEither { return new Either({ err: e, _tag: ELeftTag }); } static right(t: T): IEither { return new Either({ ok: t, _tag: ERightTag }); } static fromFailable(s: Supplier): IEither { try { return Either.right(s()); } catch (e) { return Either.left(e as E); } } static async fromFailableAsync(s: Supplier> | Promise): Promise> { return await (typeof s === 'function' ? s() : s) .then((t: T) => Either.right(t)) .catch((e: E) => Either.left(e)); } }