type Error = { error: T value?: never } type Success = { error?: never value: U } export type Result = NonNullable | Success> export type UnwrapResult = (e: Result) => NonNullable export const unwrapResult: UnwrapResult = ({ error, value, }: Result) => { if (value !== undefined && error !== undefined) { throw new Error( `Received both left and right values at runtime when opening a Result\nSuccess: ${JSON.stringify( value, )}\nError: ${JSON.stringify(error)}`, ) /* We're throwing in this function because this can only occur at runtime if something happens that the TypeScript compiler couldn't anticipate. That means the application is in an unexpected state and we should terminate immediately. */ } if (error !== undefined) { return error as NonNullable // Typescript is getting confused and returning this type as `T | undefined` unless we add the type assertion } if (value !== undefined) { return value as NonNullable } throw new Error( 'Received no value or error values at runtime when opening Result', ) } export const isError = (e: Result): e is Error => { return e.error !== undefined } export const isSuccess = (e: Result): e is Success => { return e.value !== undefined } export const makeError = (value: T): Error => ({ error: value }) export const makeSuccess = (value: U): Success => ({ value: value })