import { Err, FlattenResult, Ok, type Result, type UnwrapOption, } 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< Fn extends (...args: readonly any[]) => Promise, Em extends ErrorMapper, >( fn: Fn, errorMapper?: Em, ): ( ...args: Parameters ) => ResultAsync< UnwrapPromise>, ExtractErrorFromMapper > { return ( ...args: Parameters ): ResultAsync< UnwrapPromise>, ExtractErrorFromMapper > => { return ResultAsync.fromPromise( fn(args), (e) => errorMapper ? errorMapper(e) : e, ); }; } static from< T = void, E = void, >( executor: ( resolve: (value: T | PromiseLike) => void, reject: (reason?: E) => void, ) => void, ): ResultAsync { return ResultAsync.fromPromise(new Promise(executor), (e) => e as 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) => Result): ResultAsync { return new ResultAsync( this._promise.then( (result: Result): ResultAsync => { if (result.isErr()) { return errAsync(result.error); } return fn(result.value).toAsync() as ResultAsync; }, ), ); } andThenAsync( fn: (value: T) => ResultAsync | Promise>, ): 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))); } type FlattenResultAsync = R extends ResultAsync ? Inner extends ResultAsync ? ResultAsync : R : R; type UnwrapPromise> = Pr extends Promise ? U : never; type ErrorMapper = ((e: unknown) => E) | undefined; type ExtractErrorFromMapper> = Em extends (e: unknown) => infer E ? E : unknown;