/** * Fail is an Env-based abstraction for a try/catch style API * which is based on continuations to provide type-safe errors * with distinct channels to help separate errors that originate from different places. * @since 0.9.2 */ import { Disposable } from '@most/types' import { Either, left, right } from 'fp-ts/Either' import { pipe } from 'fp-ts/function' import { undisposable } from './Disposable' import { Env, map, of } from './Env' import { async, Resume, run } from './Resume' import { make } from './struct' /** * @since 0.9.2 * @category Model */ export type Fail = { readonly [_ in Key]: (e: E) => Resume } /** * @since 0.9.2 * @category Constructor */ export const throwError = (key: Key) => (err: E): Env, never> => (e) => e[key](err) const createFailEnv = ( key: Key, resume: (e: E) => Disposable, ): Fail => make(key, (e: E) => async(() => resume(e))) /** * @since 0.9.2 * @category Combinator */ export const catchErrorW = (key: Key) => (onError: (err: E) => Env) => (env: Env, B> | Env, B>): Env => (r) => async((resume) => pipe( { ...r, ...createFailEnv(key, (e: E) => pipe(r, onError(e), run(resume))) }, env, run(resume), ), ) /** * @since 0.9.2 * @category Combinator */ export const catchError = catchErrorW as ( key: Key, ) => { (onError: (err: E) => Env): { (env: Env, A> | Env, A>): Env (env: Env, A>): Env } } /** * @since 0.9.2 * @category Combinator */ export const attempt = (key: Key) => (env: Env, B> | Env, B>): Env> => pipe( env, map(right), catchErrorW(key)((e: E) => of(left(e))), ) /** * Creates a Provider for an Error which will throw an Exception. * Reserve this only for *critical* application errors * @since 0.13.4 * @category Environment */ export const criticalExpection = (key: K) => (f: (error: E) => string): Fail => createFailEnv( key, undisposable((e: E) => { throw new Error(f(e)) }), ) /** * @since 0.9.2 * @category Model */ export interface Failure { readonly throw: (err: E) => Env, never> readonly catchW: ( onError: (err: E) => Env, ) => (env: Env, B> | Env, B>) => Env readonly catch: ( onError: (err: E) => Env, ) => { (env: Env, A> | Env, A>): Env (env: Env, A>): Env } readonly attempt: (env: Env, B> | Env, B>) => Env> readonly criticalExpection: (f: (error: E) => string) => Fail } /** * @since 0.9.2 * @category Constructor */ export const named = () => (name: K): Failure => { return { throw: throwError(name), catchW: catchErrorW(name), catch: catchError(name), attempt: attempt(name) as Failure['attempt'], criticalExpection: criticalExpection(name), } } /** * @since 0.13.4 * @category Type-level */ export type ErrorOf = [A] extends [Failure] ? E : [A] extends [Fail] ? E : never /** * @since 0.13.4 * @category Type-level */ export type KeyOf = [A] extends [Failure] ? K : [A] extends [Fail] ? K : never /** * @since 0.13.4 * @category Type-level */ export type EnvOf = [A] extends [Failure] ? Fail : [A] extends [Fail] ? Fail : never