UNPKG

@daisugi/anzen

Version:

Anzen helps write safe code without exceptions, taking roots from Rust's Result and Haskell's Either.

238 lines (204 loc) 5.12 kB
export type AnzenResultSuccess<T> = ResultSuccess<T>; export type AnzenResultFailure<E> = ResultFailure<E>; export type AnzenAnyResult<E, T> = | AnzenResultFailure<E> | AnzenResultSuccess<T>; type ExtractFailure<T extends readonly unknown[]> = Awaited< T[number] > extends infer R ? R extends AnzenResultFailure<infer U> ? U : never : never; type ExtractSuccess<T extends readonly unknown[]> = { [K in keyof T]: Awaited<T[K]> extends infer R ? R extends AnzenResultSuccess<infer U> ? U : never : never; }; export type AnzenResultFn<E, T> = ( ...args: any[] ) => AnzenAnyResult<E, T> | Promise<AnzenAnyResult<E, T>>; export class ResultSuccess<T> { readonly isSuccess = true; readonly isFailure = false; #value: T; constructor(value: T) { this.#value = value; } getValue(): T { return this.#value; } getOrElse<V>(_: V): T { return this.#value; } getError(): never { throw new Error('Cannot get the error of a success.'); } chain<V extends AnzenAnyResult<any, any>>( fn: (val: T) => V, ): V { return fn(this.#value); } elseChain(_: (val: T) => any): this { return this; } map<V>(fn: (val: T) => V): AnzenResultSuccess<V> { return new ResultSuccess(fn(this.#value)); } elseMap(_: (val: T) => any): this { return this; } unwrap(): [this, T] { return [this, this.#value]; } unsafeUnwrap(): T { return this.#value; } toJSON(): string { return JSON.stringify({ value: this.#value, isSuccess: this.isSuccess, }); } } export class ResultFailure<E> { readonly isSuccess = false; readonly isFailure = true; #error: E; constructor(err: E) { this.#error = err; } getValue(): never { throw new Error('Cannot get the value of a failure.'); } getOrElse<T>(defaultVal: T): T { return defaultVal; } getError(): E { return this.#error; } chain(_: (err: E) => any): this { return this; } elseChain<V extends AnzenAnyResult<any, any>>( fn: (err: E) => V, ): V { return fn(this.#error); } map(_: (err: E) => any): this { return this; } elseMap<V>(fn: (err: E) => V): AnzenResultSuccess<V> { return new ResultSuccess(fn(this.#error)); } unwrap<V = undefined>(defaultVal?: V): [this, V] { return [this, defaultVal as V]; } unsafeUnwrap(): E { return this.#error; } toJSON(): string { return JSON.stringify({ error: this.#error, isSuccess: this.isSuccess, }); } } async function handleResult<E, T>( whenRes: | Promise<AnzenAnyResult<E, T>> | AnzenAnyResult<E, T>, ) { const res = await whenRes; return res.isSuccess ? res.getValue() : Promise.reject(res.getError()); } export class Result { static success = <T>(val: T): AnzenResultSuccess<T> => new ResultSuccess(val); static failure = <E>(err: E): AnzenResultFailure<E> => new ResultFailure(err); static async promiseAll< const T extends ( | AnzenAnyResult<any, any> | Promise<AnzenAnyResult<any, any>> )[], >(whenRes: T) { try { const vals = await Promise.all( whenRes.map(handleResult), ); return Result.success(vals) as AnzenResultSuccess< ExtractSuccess<T> >; } catch (err) { return Result.failure(err) as AnzenResultFailure< ExtractFailure<T> >; } } static async unwrapPromiseAll< const T extends ( | AnzenAnyResult<any, any> | Promise<AnzenAnyResult<any, any>> )[], const D extends unknown[] = unknown[], >(args: [D, ...T]) { const [defaultsVals, ...whenRes] = args; try { const vals = await Promise.all( whenRes.map(handleResult), ); return [Result.success(vals), ...vals] as [ AnzenResultSuccess<ExtractSuccess<T>>, ...ExtractSuccess<T>, ]; } catch (err) { return [Result.failure(err), ...defaultsVals] as [ AnzenResultFailure<ExtractFailure<T>>, ...ExtractSuccess<T>, ]; } } static unwrap<T, E, D = undefined>(defaultVal?: D) { return ( res: AnzenAnyResult<E, T>, ): | [AnzenResultFailure<E>, D] | [AnzenResultSuccess<T>, T] => res.isSuccess ? [res, res.getValue()] : [res, defaultVal as D]; } static fromJSON<T = any, E = any>( json: string, ): AnzenAnyResult<E, T> { const obj = JSON.parse(json); return obj.isSuccess ? new ResultSuccess<T>(obj.value) : new ResultFailure<E>(obj.error); } static fromSyncThrowable<T, E = unknown>( fn: () => T, parseErr?: (err: unknown) => E, ): AnzenAnyResult<E, T> { try { return Result.success(fn()); } catch (err) { return Result.failure(parseErr?.(err) ?? (err as E)); } } static async fromThrowable<T, E = unknown>( fn: () => Promise<T>, parseErr?: (err: unknown) => E, ): Promise<AnzenAnyResult<E, T>> { try { return Result.success(await fn()); } catch (err) { return Result.failure(parseErr?.(err) ?? (err as E)); } } }