// oxlint-disable-next-line typescript/no-explicit-any export type AnyEither = Either export type GetLeft = T extends Either ? Left : never export type GetRight = T extends Either ? Right : never export type GetRights = Eithers extends [] ? [] : Eithers extends [infer Item, ...infer xs] ? Item extends Either ? [Right, ...GetRights] : never : never export type GetLefts = Eithers extends [] ? [] : Eithers extends [infer Item, ...infer xs] ? Item extends Either ? [Left, ...GetLefts] : never : never type TupleToUnion = Tuple extends unknown[] ? Tuple[number] : never export abstract class Either { static isEither(target: unknown): target is Either { return target instanceof Either } static isLeft(target: unknown): target is Left { return target instanceof Left } static isRight(target: unknown): target is Right { return target instanceof Right } static left(value: L): Either static left(value: L): Either static left(value: L): Either { return new Left(value) } static right(value: R): Either static right(value: R): Either static right(value: R): Either { return new Right(value) } static pure(value: R): Either static pure(value: R): Either static pure(value: R): Either { return new Right(value) } static lift( eithers: [...Eithers], map: (eitherRights: GetRights) => NewR, ): Either>, NewR> { const firstLeft = eithers.find((either) => either.isLeft()) if (firstLeft !== undefined) { return firstLeft as Either>, NewR> } const rights = eithers.map((either) => { // oxlint-disable-next-line typescript/no-unsafe-return return either.assertRight().getRight() }) as GetRights const newRightValue = map(rights) return Either.pure(newRightValue) } abstract getValue(): L | R abstract getLeft(): L abstract getRight(): R abstract getRightOr(or: R): R abstract isLeft(): this is Left abstract isRight(): this is Right abstract assertLeft(): Left abstract assertRight(): Right abstract tap(effects: { left?: ((value: L) => void) | undefined right?: ((value: R) => void) | undefined }): Either abstract tapLeft(effect: (value: L) => void): Either abstract tapRight(effect: (value: R) => void): Either abstract mapLeft(map: (value: L) => NewL): Either abstract mapRight(map: (value: R) => NewR): Either abstract bimap(mappers: { left: (value: L) => NewL right: (value: R) => NewR }): Either abstract bind(bind: (value: R) => Either): Either abstract bindAsync( bind: (value: R) => Promise>, ): Promise> abstract fold(matchers: { left: (value: L) => X; right: (value: R) => X }): X abstract foldAsync(matchers: { left: (value: L) => Promise right: (value: R) => Promise }): Promise abstract match(matchers: { left: (value: L) => LX; right: (value: R) => RX }): LX | RX abstract matchAsync(matchers: { left: (value: L) => Promise right: (value: R) => Promise }): Promise abstract mplus(either: Either): Either abstract [Symbol.iterator](): Generator, R, R> // Async consumers can fall back to Symbol.iterator, but we still expose the // same semantics explicitly so Either remains self-descriptive as an async iterable. abstract [Symbol.asyncIterator](): AsyncGenerator, R, unknown> } export class Left extends Either { protected value: L constructor(value: L) { super() this.value = value } getValue(): L | R { return this.value } getLeft(): L { return this.value } getRight(): R { throw new Error("Cannot get right value from Left") } getRightOr(or: R): R { return or } isLeft(): this is Left { return true } isRight(): this is Right { return false } assertLeft(): this { return this } assertRight(): Right { throw new Error("Assert failed") } tap(effects: { left?: ((value: L) => void) | undefined right?: ((value: R) => void) | undefined }): Either { if (effects.left !== undefined) { effects.left(this.value) } return this } tapLeft(effect: (value: L) => void): Either { effect(this.value) return this } tapRight(_effect: (value: R) => void): Either { return this } mapLeft(map: (value: L) => NewL): Either { return new Left(map(this.value)) } mapRight(_map: (value: R) => NewR): Either { return new Left(this.value) } bimap(mappers: { left: (value: L) => NewL right: (value: R) => NewR }): Either { return new Left(mappers.left(this.value)) } bind(_map: (value: R) => Either): Either { return new Left(this.value) as Either } async bindAsync( _bind: (value: R) => Promise>, ): Promise> { return await Promise.resolve(new Left(this.value) as Either) } fold(matchers: { left: (value: L) => X; right: (value: R) => X }): X { return matchers.left(this.value) } async foldAsync(matchers: { left: (value: L) => Promise right: (value: R) => Promise }): Promise { return await matchers.left(this.value) } match(matchers: { left: (value: L) => LX; right: (value: R) => RX }): LX | RX { return matchers.left(this.value) } async matchAsync(matchers: { left: (value: L) => Promise right: (value: R) => Promise }): Promise { return await matchers.left(this.value) } mplus(either: Either): Either { return either } *[Symbol.iterator](): Generator, R, R> { yield this throw new Error("Cannot extract right value from Left") } // This mirrors the sync iterator exactly; it is explicit protocol support, // not a requirement for async yield* fallback. async *[Symbol.asyncIterator](): AsyncGenerator, R, unknown> { await Promise.resolve() yield this throw new Error("Cannot extract right value from Left") } } export class Right extends Either { protected value: R constructor(value: R) { super() this.value = value } getValue(): L | R { return this.value } getLeft(): L { throw new Error("Cannot get left value from Right") } getRight(): R { return this.value } getRightOr(_or: R): R { return this.value } isLeft(): this is Left { return false } isRight(): this is Right { return true } assertLeft(): Left { throw new Error("Assert failed") } assertRight(): this { return this } tap(effects: { left?: ((value: L) => void) | undefined right?: ((value: R) => void) | undefined }): Either { if (effects.right !== undefined) { effects.right(this.value) } return this } tapLeft(_effect: (value: L) => void): Either { return this } tapRight(effect: (value: R) => void): Either { effect(this.value) return this } mapLeft(_map: (value: L) => NewL): Either { return new Right(this.value) } mapRight(map: (value: R) => NewR): Either { return new Right(map(this.value)) } bimap(mappers: { left: (value: L) => NewL right: (value: R) => NewR }): Either { return new Right(mappers.right(this.value)) } bind(map: (value: R) => Either): Either { return map(this.value) as Either } async bindAsync( bind: (value: R) => Promise>, ): Promise> { return (await bind(this.value)) as Either } fold(matchers: { left: (value: L) => X; right: (value: R) => X }): X { return matchers.right(this.value) } async foldAsync(matchers: { left: (value: L) => Promise right: (value: R) => Promise }): Promise { return await matchers.right(this.value) } match(matchers: { left: (value: L) => LX; right: (value: R) => RX }): LX | RX { return matchers.right(this.value) } async matchAsync(matchers: { left: (value: L) => Promise right: (value: R) => Promise }): Promise { return await matchers.right(this.value) } mplus(_either: Either): Either { return this } *[Symbol.iterator](): Generator, R, R> { return this.value } // This mirrors the sync iterator exactly; async consumers could fall back to // Symbol.iterator, but the explicit async form keeps the contract obvious. async *[Symbol.asyncIterator](): AsyncGenerator, R, unknown> { await Promise.resolve() return this.value } } export const isEither = (target: unknown): target is Either => { return Either.isEither(target) } export const isLeft = (target: unknown): target is Left => { return Either.isLeft(target) } export const isRight = (target: unknown): target is Right => { return Either.isRight(target) } type LeftFunction = { (value: L): Either (value: L): Either } export const left = ((value: L): Either => { return Either.left(value) }) as LeftFunction type RightFunction = { (value: R): Either (value: R): Either } export const right = ((value: R): Either => { return Either.right(value) }) as RightFunction export type TupleOfEither = [undefined, Right] | [Left, undefined] export const eitherToTuple = (either: Either): TupleOfEither => { const left = either.isLeft() ? either.getLeft() : undefined const right = either.isRight() ? either.getRight() : undefined return [left, right] as TupleOfEither }