Add AsyncDoEither class

This commit is contained in:
themodernhakr 2025-06-09 01:03:09 -05:00
parent 07a74315e1
commit de0160fd9e

View File

@ -176,3 +176,137 @@ export class DoEither<T extends Record<string, unknown>, E> {
return new DoEither(right({})) return new DoEither(right({}))
} }
} }
/**
* A class for working with Either in a do-notation style with asynchronous computations
* @typeParam T - The type of the accumulated scope
* @typeParam E - The error type
*/
export class AsyncDoEither<T extends Record<string, unknown>, E> {
/**
* @param promise - The current Promise of an Either value
*/
constructor(private readonly promise: Promise<Either<T, E>>) {}
/**
* Binds a new value to the scope synchronously
* @typeParam K - The key to bind to
* @typeParam A - The type of the value to bind
* @param key - The property name to bind to
* @param fa - The Either value to bind
* @returns A new AsyncDoEither instance with the extended scope
*/
bind<K extends string, A>(
key: K,
fa: Either<A, E>,
): AsyncDoEither<T & Record<K, A>, E> {
return this.bindL(key, () => fa)
}
/**
* Binds a new value to the scope asynchronously
* @typeParam K - The key to bind to
* @typeParam A - The type of the value to bind
* @param key - The property name to bind to
* @param f - Function returning Either or Promise of Either
* @returns A new AsyncDoEither instance with the extended scope
*/
bindL<K extends string, A>(
key: K,
f: (scope: T) => Either<A, E> | Promise<Either<A, E>>,
): AsyncDoEither<T & Record<K, A>, E> {
const newPromise = this.promise.then(async (currentEither) => {
if (isLeft(currentEither)) {
return currentEither as unknown as Either<T & Record<K, A>, E>
}
const scope = currentEither.right
try {
const nextEither = await f(scope)
if (isLeft(nextEither)) {
return nextEither as unknown as Either<T & Record<K, A>, E>
}
return right({
...scope,
[key]: nextEither.right,
}) as Either<T & Record<K, A>, E>
} catch (err) {
return left(err as E) as Either<T & Record<K, A>, E>
}
})
return new AsyncDoEither(newPromise)
}
/**
* Returns the final result from the scope
* @typeParam B - The return type
* @param f - The function to transform the scope
* @returns A Promise of Either containing the result
*/
return<B>(f: (scope: T) => B): Promise<Either<B, E>> {
return this.promise.then(either => {
if (isLeft(either)) {
return either as unknown as Either<B, E>
}
return right(f(either.right))
})
}
/**
* Returns the current scope
* @returns A Promise of the current Either scope
*/
done(): Promise<Either<T, E>> {
return this.promise
}
/**
* Executes an effect without modifying the scope
* @param fa - The Either effect to execute
* @returns A new AsyncDoEither instance with the same scope
*/
do(fa: Either<unknown, E>): AsyncDoEither<T, E> {
return this.doL(() => fa)
}
/**
* Executes an effect that depends on the current scope
* @param f - Function returning Either or Promise of Either
* @returns A new AsyncDoEither instance with the same scope
*/
doL(f:
(scope: T) =>
| Either<unknown, E>
| Promise<Either<unknown, E>>): AsyncDoEither<T, E>
{
const newPromise = this.promise.then(async (currentEither) => {
if (isLeft(currentEither)) {
return currentEither
}
const scope = currentEither.right
try {
const effectEither = await f(scope)
if (isLeft(effectEither)) {
return effectEither as unknown as Either<T, E>
}
return currentEither
} catch (err) {
return left(err as E)
}
})
return new AsyncDoEither(newPromise)
}
/**
* Starts a new AsyncDoEither chain with an empty scope
* @returns A new AsyncDoEither instance with an empty scope
*/
static start<E>(): AsyncDoEither<{}, E> {
return new AsyncDoEither(Promise.resolve(right({})))
}
}