// deno-lint-ignore-file no-explicit-any import type { TaggedError } from "../error/tagged-error.ts"; import { Result } from "../result/result.ts"; import type { MaybePromise } from "../types/maybe-promise.ts"; import type { MergeTypes } from "../types/merge-types.ts"; export class Effect< TDeps = void, TValue = any, TError extends TaggedError = never, > { static from( fn: (deps: TDeps) => MaybePromise>, ): Effect { return new Effect(fn); } static tryFrom( fn: () => MaybePromise, errorMapper: (error: any) => TError, ): Effect { return new Effect( async () => { try { const value = await fn(); return Result.ok(value); } catch (error) { return Result.failWith(errorMapper(error)); } }, ); } static ok(value: TValue): Effect { return new Effect(() => Result.ok(value)); } static failWith( error: TError, ): Effect { return new Effect(() => Result.failWith(error)); } constructor( private readonly fn: ( deps: TDeps, ) => MaybePromise>, ) { } map( fn: (value: TValue) => MaybePromise, ): Effect { return new Effect(async (deps: TDeps) => { const result = await this.fn(deps); if (result.isError()) { return result; } return Result.ok(await fn(result.value as TValue)); }); } flatMap( fn: ( value: TValue, ) => Effect, ): Effect, TNewValue, TError | TNewError> { return new Effect(async (deps: TDeps & TNewDeps) => { const result = await this.fn(deps); if (result.isError()) { return result as Result; } return await fn(result.value as TValue).fn(deps); }) as Effect< MergeTypes, TNewValue, TError | TNewError >; } async run(deps: TDeps): Promise> { return await this.fn(deps); } } export type ExtractEffectDependencies = T extends Effect ? TDeps : never;