import { NoFailValueError, NoOkValueError } from "./errors"; enum ResultType { Fail, Ok, } type UnwrapResult | any> = T extends Result ? U : T; type UnwrapError> = T extends Result ? E : never; export type AsyncResult = Promise>; export type TCollectResult = Result< { [key in keyof T]: T[key] extends Result ? A : T[key] }, { [key in keyof T]: T[key] extends Result ? B : never }[keyof { [key in keyof T]: T[key] extends Result ? B : never; }] >; export class Result { private constructor(private _type: ResultType, private _data: S, private _error: E) {} /** * Produces a Failing Result instance * @param error * @returns */ static fail(error: E): Result { return new Result(ResultType.Fail, null, error); } /** * Produces a Ok Result Instance * @param data * @returns */ static ok(data: S): Result { return new Result(ResultType.Ok, data, null); } /** * Collects a array of Results into a Result of Array, * or outputs the first Failing Result. * Ignores non-result types from the generated array * @param results * @returns */ // static collect( // results: [...T], // ): Result< // ExcludeFromTuple, never>, // ToupleToUnion, never>> // > { static collect(results: Array>): Result { const values: T[] = []; for (const result of results) { if (result.isFail()) { return Result.fail(result._error); } values.push(result._data); } return Result.ok(values); } /** * Collects a object os key: Result into a Result, and * E being the first Failing result * If a object key contains something that's not a Result, the value is kept * in the final object Ok * @param obj * @returns */ public static collectObject | any>>(resultsObject: T): Result<{ [K in keyof T]: UnwrapResult }, UnwrapError> { const unwrappedObject: Partial<{ [K in keyof T]: UnwrapResult }> = {}; for (const key in resultsObject) { const result = resultsObject[key]; if (!Result.isResult(result)) { unwrappedObject[key] = result; continue; } if (result.isFail()) { return Result.fail(result._error); } unwrappedObject[key] = result._data; } return Result.ok(unwrappedObject as { [K in keyof T]: UnwrapResult }); } static isResult(result: any | Result): result is Result { return result instanceof Result; } /** * Returns True is Result is Ok * @returns boolean */ isOk(): boolean { return this._type === ResultType.Ok; } /** * Returns true is Result is Fail * @returns boolean */ isFail(): boolean { return this._type === ResultType.Fail; } /** * Unwraps the Ok value of a Result. If the Result is Fail, an error is thrown. * @returns The success value of the Result. * @throws {NoOkValueError} Will throw an error if the Result is Fail. */ unwrap(): S { if (!this.isOk()) throw new NoOkValueError(); return this._data; } /** * Unwraps the Fail value of a Result. If the Result is Ok, an error is thrown. * @returns The error value of the Result. * @throws {NoFailValueError} Will throw an error if the Result is Ok. */ unwrapFail(): E { if (!this.isFail()) throw new NoFailValueError(); return this._error; } /** * Applies a function into the Ok value of the result while propagating the Fail * @param transform * @returns */ map(transform: (data: S) => NewS): Result { if (!this.isOk()) return Result.fail(this._error); const newData = transform(this._data); return Result.ok(newData); } /** * Applies a function into the Fail value of the result while propagating the Ok * @param transform * @returns */ mapFail(transform: (error: E) => NewE): Result { if (!this.isFail()) return Result.ok(this._data); const newError = transform(this._error); return Result.fail(newError); } /** * Applies a function that returns a result into the Ok value of the result, if Failing, can return the original Fail or the new Fail * @param transform * @returns */ flatMap(transform: (data: S) => Result): Result { if (!this.isOk()) return Result.fail(this._error); const newData = transform(this._data); if (newData.isFail()) return Result.fail(newData.unwrapFail()); return Result.ok(newData.unwrap()); } /** * Applies a function that returns a result into the Fail value of the result, if Ok, can return the original Ok or the new Ok * @param transform * @returns */ flatMapFail(transform: (error: E) => Result): Result { if (!this.isFail()) return Result.ok(this._data); const newError = transform(this._error); if (newError.isOk()) return Result.ok(newError.unwrap()); return Result.fail(newError.unwrapFail()); } /** * Applies a async function into the Ok value of the result while propagating the Fail * Returns the Result as a Promise * @param transform * @returns */ async asyncMap(transform: (data: S) => Promise): Promise> { if (!this.isOk()) return Result.fail(this._error); const newData = await transform(this._data); return Result.ok(newData); } /** * Applies a async function into the Fail value of the result while propagating the Ok * Returns the Result as a Promise * @param transform * @returns */ async asyncMapFail(transform: (error: E) => Promise): Promise> { if (!this.isFail()) return Result.ok(this._data); const newError = await transform(this._error); return Result.fail(newError); } /** * Applies a async function that returns a result into the Ok value of the result, if Failing, can return the original Fail or the new Fail * Returns the Result as a Promise * @param transform * @returns */ async asyncFlatMap(transform: (data: S) => Promise>): Promise> { if (!this.isOk()) return Result.fail(this._error); const newData = await transform(this._data); if (newData.isFail()) return Result.fail(newData.unwrapFail()); return Result.ok(newData.unwrap()); } /** * Applies a async function that returns a result into the Fail value of the result, if Ok, can return the original Ok or the new Ok * Returns the Result as a Promise * @param transform * @returns */ async asyncFlatMapFail(transform: (error: E) => Promise>): Promise> { if (!this.isFail()) return Result.ok(this._data); const newError = await transform(this._error); if (newError.isOk()) return Result.ok(newError.unwrap()); return Result.fail(newError.unwrapFail()); } } export const Fail = Result.fail; export const Ok = Result.ok;