export type Result = OkResult | ErrResult type OkResult = { readonly kind: 'ok', value: T } type ErrResult = { readonly kind: 'err', value: E } export function ok(value: T): Result { return { kind: 'ok', value } } export function err(value: E): Result { return { kind: 'err', value } } export function isOk(result: Result): result is OkResult { return result.kind === 'ok' } export function isErr(result: Result): result is ErrResult { return result.kind === 'err' } export function map(result: Result, fn: (value: T1) => T2): Result { if (isOk(result)) { return ok(fn(result.value)) } else { return result } } export function mapErr(result: Result, fn: (value: E1) => E2): Result { if (isErr(result)) { return err(fn(result.value)) } else { return result } } // The above functions are the most efficient way to work with results, but when we expose our // result externally we want to make it more convenient to use and not have to expose the above. // Thus, we use this to attach useful prototype methods to the Result, which is less efficient // than direct method calls but nicer to work with. export function toOutputResult(result: Result): OutputResult { const res = Object.create(resultMethods()) res.kind = result.kind res.value = result.value return res } /** A result, which can be in an 'ok' or 'err' state. */ export type OutputResult = Result & ResultMethods interface ResultMethods { /** Is the result an ok value (ie not an error)? */ isOk(this: Result): this is OkResult & ResultMethods /** Is the result an error? */ isErr(this: Result): this is ErrResult & ResultMethods } const resultMethods = (): ResultMethods => ({ isOk() { return this.kind === 'ok' }, isErr() { return this.kind === 'err' } })