import { Err, type UnwrapOption, Ok, type Result, FlattenResult, } from "@shared/utils/result.ts"; import { none, None, Option, some, Some } from "@shared/utils/option.ts"; export class ResultAsync implements PromiseLike> { constructor(private readonly _promise: Promise>) { this._promise = _promise; } static fromPromise( promise: Promise, errorMapper: (error: unknown) => E, ) { const promiseOfResult: Promise> = promise .then((value: T): Result => { return new Ok(value); }) .catch((error: unknown): Result => { return new Err(errorMapper(error)); }); return new ResultAsync(promiseOfResult); } static fromSafePromise(promise: Promise) { const promiseOfResult: Promise> = promise.then( (value: T): Result => { return new Ok(value); }, ); return new ResultAsync(promiseOfResult); } static fromThrowable any, E>( fn: Fn, errorMapper?: (e: unknown) => E, ): (...args: Parameters) => ResultAsync, E> { return (...args: Parameters): ResultAsync, E> => { try { return okAsync(fn(args)); } catch (e) { return errAsync(errorMapper ? errorMapper(e) : e); } }; } async unwrap(): Promise { const result = await this._promise; if (result.isErr()) { throw result.error; } return result.value; } async match( ok: (value: T) => A, err: (err: E) => B, ): Promise { const result = await this._promise; if (result.isErr()) { return err(result.error); } return ok(result.value); } map(fn: (value: T) => U): ResultAsync { return new ResultAsync( this._promise.then((result: Result): Result => { if (result.isErr()) { return new Err(result.error); } return new Ok(fn(result.value)); }), ); } mapAsync(fn: (value: T) => Promise): ResultAsync { return new ResultAsync( this._promise.then( async (result: Result): Promise> => { if (result.isErr()) { return errAsync(result.error); } return new Ok(await fn(result.value)); }, ), ); } mapErr(fn: (err: E) => U): ResultAsync { return new ResultAsync( this._promise.then((result: Result): Result => { if (result.isErr()) { return new Err(fn(result.error)); } return new Ok(result.value); }), ); } mapErrAsync(fn: (value: T) => Promise): ResultAsync { return new ResultAsync( this._promise.then( async (result: Result): Promise> => { if (result.isErr()) { return errAsync(await fn(result.error)); } return new Ok(result.value); }, ), ); } andThen(fn: (value: T) => ResultAsync): ResultAsync { return new ResultAsync( this._promise.then( (result: Result): ResultAsync => { if (result.isErr()) { return errAsync(result.error); } return fn(result.value) as ResultAsync; }, ), ); } nullableToOption(): ResultAsync>, E> { return this.map((v) => (v ? some(v) : none)); } flatten(): FlattenResultAsync> { return new ResultAsync( this._promise.then( (result: Result): FlattenResult> => { return result.flatten(); }, ), ) as FlattenResultAsync>; } flattenOption( errFn: () => U, ): ResultAsync, E | U> { return new ResultAsync( this._promise.then( (result: Result): Result, E | U> => { return result.flattenOption(errFn); }, ), ); } flattenOptionOrDefault>( defaultValue: D, ): ResultAsync | D, E> { return new ResultAsync( this._promise.then( (result: Result): Result | D, E> => { return result.flattenOptionOrDefault(defaultValue); }, ), ); } matchOption( some: (value: UnwrapOption) => A, none: () => B, ): ResultAsync { return new ResultAsync( this._promise.then((result: Result): Result => { return result.matchOption(some, none); }), ); } matchOptionAndFlatten( some: (value: UnwrapOption) => Result, none: () => Result, ): ResultAsync { return new ResultAsync( this._promise.then( (result: Result): Result => { return result.matchOptionAndFlatten(some, none); }, ), ); } then( onFulfilled?: (res: Result) => A | PromiseLike, onRejected?: (reason: unknown) => B | PromiseLike, ): PromiseLike { return this._promise.then(onFulfilled, onRejected); } } export function okAsync(value: T): ResultAsync; export function okAsync( value: void, ): ResultAsync; export function okAsync(value: T): ResultAsync { return new ResultAsync(Promise.resolve(new Ok(value))); } export function errAsync( err: E, ): ResultAsync; export function errAsync(err: E): ResultAsync; export function errAsync( err: void, ): ResultAsync; export function errAsync(err: E): ResultAsync { return new ResultAsync(Promise.resolve(new Err(err))); } export type FlattenResultAsync = R extends ResultAsync ? T extends ResultAsync ? FlattenResultAsync extends ResultAsync ? ResultAsync : never : R : never;