Created
November 5, 2024 09:01
-
-
Save b4lk0n/60ee2fd309c8e70f8f57ae3cc2e2ce15 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import type { Thunk, UnaryFn } from "./func" | |
| import { type Result, err, isErr, ok } from "./result" | |
| export type AsyncResult<E, T> = Promise<Result<E, T>> | |
| export const fromResult = <E, T>(result: Result<E, T>): AsyncResult<E, T> => | |
| Promise.resolve(result) | |
| export const fromThrowable = async <E, T>( | |
| f: Thunk<Promise<T>>, | |
| onReject: UnaryFn<unknown, E>, | |
| ): AsyncResult<E, T> => { | |
| try { | |
| const data = await f() | |
| return ok(data) as Result<E, T> | |
| } catch (e) { | |
| return err(onReject(e)) as Result<E, T> | |
| } | |
| } | |
| export const map = async <E, T, U>( | |
| result: AsyncResult<E, T>, | |
| f: UnaryFn<T, U>, | |
| ): AsyncResult<E, U> => { | |
| const res = await result | |
| return isErr(res) ? res : ok(f(res.data)) | |
| } | |
| export const flatMap = async <E, T, F, U>( | |
| result: AsyncResult<E, T>, | |
| f: UnaryFn<T, AsyncResult<F, U>>, | |
| ): AsyncResult<E | F, U> => { | |
| const res = await result | |
| return isErr(res) ? res : f(res.data) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { isPlainObject, prop } from "remeda" | |
| export type AppErr<C = string, M = string> = { | |
| tag: "AppErr" | |
| code: C | |
| msg: M | |
| cause?: unknown | |
| } | |
| export const isAppErr = <C, M>(err: unknown): err is AppErr<C, M> => | |
| isPlainObject(err) && prop(err, "tag") === "AppErr" | |
| type ErrMapping<T extends string> = { | |
| [key in T]: string | |
| } | |
| type ErrFn<T extends ErrMapping<string>> = { | |
| <C extends keyof T>(code: C, cause?: unknown): AppErr<C, T[C]> | |
| wrap: <C extends keyof T>(code: C) => (cause: unknown) => AppErr<C, T[C]> | |
| coerce: <C extends keyof T>(code: C) => (err: unknown) => AppErr<C, T[C]> | |
| } & { | |
| [K in keyof T]: K | |
| } | |
| export function createErrors<T extends ErrMapping<string>>( | |
| mapping: T, | |
| ): ErrFn<T> { | |
| const errFn = <C extends keyof T>( | |
| code: C, | |
| cause?: unknown, | |
| ): AppErr<C, T[C]> => { | |
| const msg = mapping[code] | |
| if (!msg) { | |
| throw new Error(`Unknown error code: ${String(code)}`) | |
| } | |
| return { | |
| tag: "AppErr", | |
| code, | |
| msg, | |
| cause, | |
| } | |
| } | |
| errFn.wrap = <C extends keyof T>(code: C) => { | |
| return (cause: unknown): AppErr<C, T[C]> => { | |
| const msg = mapping[code] | |
| if (!msg) { | |
| throw new Error(`Unknown error code: ${String(code)}`) | |
| } | |
| return { | |
| tag: "AppErr", | |
| code, | |
| msg, | |
| cause, | |
| } | |
| } | |
| } | |
| errFn.coerce = <C extends keyof T>(code: C) => { | |
| return (err: unknown): AppErr<C, T[C]> => { | |
| if (isAppErr<C, T[C]>(err)) { | |
| return err | |
| } | |
| const msg = mapping[code] | |
| if (!msg) { | |
| throw new Error(`Unknown error code: ${String(code)}`) | |
| } | |
| return { | |
| tag: "AppErr", | |
| code, | |
| msg, | |
| cause: err, | |
| } | |
| } | |
| } | |
| for (const [code] of Object.entries(mapping)) { | |
| // biome-ignore lint/suspicious/noExplicitAny: | |
| ;(errFn as any)[code] = code | |
| } | |
| return errFn as ErrFn<T> | |
| } | |
| export function toAppErr(err: unknown): AppErr { | |
| if (isAppErr<string, string>(err)) { | |
| return err | |
| } | |
| if (err instanceof Error) { | |
| return { | |
| tag: "AppErr", | |
| code: "Unknown", | |
| msg: err.message, | |
| cause: err.cause, | |
| } | |
| } | |
| const msg = String(err) | |
| return { | |
| tag: "AppErr", | |
| code: "Unknown", | |
| msg, | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| export type Sink<T> = (v: T) => void | |
| export type Thunk<T> = () => T | |
| export type UnaryFn<T, U> = (v: T) => U |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { isNullish } from "remeda" | |
| import { type AppErr, isAppErr } from "./errors" | |
| import { type Err, type Ok, type Result, isErr, isOk, isResult } from "./result" | |
| type MaybeValue<T> = T | null | undefined | |
| type Nullish = null | undefined | |
| function must<E, T>(result: Result<E, T>): asserts result is Ok<T> | |
| function must<E, T>(result: Result<E, T>, err: string): asserts result is Ok<T> | |
| function must<E, T>(result: Result<E, T>, err: Error): asserts result is Ok<T> | |
| function must<E, T, C, M>( | |
| result: Result<E, T>, | |
| err: AppErr<C, M>, | |
| ): asserts result is Ok<T> | |
| function must<T>(value: MaybeValue<T>): asserts value is T | |
| function must<T>(value: MaybeValue<T>, err: string): asserts value is T | |
| function must<T>(value: MaybeValue<T>, err: Error): asserts value is T | |
| function must<T, C, M>( | |
| value: MaybeValue<T>, | |
| err: AppErr<C, M>, | |
| ): asserts value is T | |
| function must<E, T, C, M>( | |
| value: Result<E, T> | MaybeValue<T>, | |
| err?: AppErr<C, M> | Error | string, | |
| ): asserts value is Ok<T> | T { | |
| if (isResult(value)) { | |
| if (isErr(value)) { | |
| if (!err) { | |
| throw value.err | |
| } | |
| const error = isAppErr(err) || err instanceof Error ? err : new Error(err) | |
| error.cause = value.err | |
| throw error | |
| } | |
| } | |
| if (isNullish(value)) { | |
| if (!err) { | |
| throw new Error("Invariant failed") | |
| } | |
| const error = isAppErr(err) || err instanceof Error ? err : new Error(err) | |
| throw error | |
| } | |
| } | |
| export { must } | |
| function mustNot<E, T>(value: Result<E, T>): asserts value is Err<E> | |
| function mustNot<E, T>( | |
| value: Result<E, T>, | |
| err: string, | |
| ): asserts value is Err<E> | |
| function mustNot<E, T>(value: Result<E, T>, err: Error): asserts value is Err<E> | |
| function mustNot<E, T, C, M>( | |
| value: Result<E, T>, | |
| err: AppErr<C, M>, | |
| ): asserts value is Err<E> | |
| function mustNot<T>(value: MaybeValue<T>): asserts value is Nullish | |
| function mustNot<T>(value: MaybeValue<T>, err: string): asserts value is Nullish | |
| function mustNot<T>(value: MaybeValue<T>, err: Error): asserts value is Nullish | |
| function mustNot<T, C, M>( | |
| value: MaybeValue<T>, | |
| err: AppErr<C, M>, | |
| ): asserts value is Nullish | |
| function mustNot<E, T, C, M>( | |
| value: Result<E, T> | MaybeValue<T>, | |
| err?: AppErr<C, M> | Error | string, | |
| ): asserts value is Err<E> | Nullish { | |
| if (isResult(value)) { | |
| if (isOk(value)) { | |
| if (!err) { | |
| throw new Error("Refute failed") | |
| } | |
| const error = isAppErr(err) || err instanceof Error ? err : new Error(err) | |
| throw error | |
| } | |
| } | |
| if (!isNullish(value)) { | |
| if (!err) { | |
| throw new Error("Invariant failed") | |
| } | |
| const error = isAppErr(err) || err instanceof Error ? err : new Error(err) | |
| throw error | |
| } | |
| } | |
| export { mustNot } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { isNullish } from "remeda" | |
| type None = Readonly<{ | |
| tag: "None" | |
| }> | |
| type Some<T> = Readonly<{ | |
| tag: "Some" | |
| data: T | |
| }> | |
| export type Option<T> = None | Some<T> | |
| export const none = (): Option<never> => ({ tag: "None" }) | |
| export const isNone = <T>(option: Option<T>): option is None => | |
| option.tag === "None" | |
| export const some = <T>(data: T): Option<T> => ({ tag: "Some", data }) | |
| export const isSome = <T>(option: Option<T>): option is Some<T> => | |
| option.tag === "Some" | |
| export const fromNullish = <T>(data: T | null | undefined): Option<T> => | |
| isNullish(data) ? none() : some(data as Exclude<T, null | undefined>) | |
| export const unwrap = <T>(option: Option<T>): T => { | |
| if (isSome(option)) { | |
| return option.data | |
| } | |
| throw new Error("Cannot unwrap a value of None") | |
| } | |
| export const unwrapOr = <T>(option: Option<T>, def: T): T => { | |
| if (isSome(option)) { | |
| return option.data | |
| } | |
| return def | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { isPlainObject } from "remeda" | |
| import type { Thunk, UnaryFn } from "./func" | |
| export type Err<E> = Readonly<{ | |
| tag: "Err" | |
| err: E | |
| }> | |
| export type Ok<T> = Readonly<{ | |
| tag: "Ok" | |
| data: T | |
| }> | |
| export type Result<E, T> = Err<E> | Ok<T> | |
| export const err = <E, T>(err: E): Result<E, T> => ({ | |
| tag: "Err", | |
| err, | |
| }) | |
| export const ok = <E, T>(data: T): Result<E, T> => ({ | |
| tag: "Ok", | |
| data, | |
| }) | |
| export const isErr = <E, T>(result: Result<E, T>): result is Err<E> => | |
| result.tag === "Err" | |
| export const isOk = <E, T>(result: Result<E, T>): result is Ok<T> => | |
| result.tag === "Ok" | |
| export const isResult = <E, T>(data: unknown): data is Result<E, T> => | |
| isPlainObject(data) && | |
| "tag" in data && | |
| (data.tag === "Ok" || data.tag === "Err") | |
| export const fromThrowable = <E, T>( | |
| f: Thunk<T>, | |
| onThrow: UnaryFn<unknown, E>, | |
| ): Result<E, T> => { | |
| try { | |
| return ok(f()) as Result<E, T> | |
| } catch (e: unknown) { | |
| return err(onThrow(e)) as Result<E, T> | |
| } | |
| } | |
| export const match = <E, T, F, U>( | |
| result: Result<E, T>, | |
| onErr: UnaryFn<E, U | F>, | |
| onOk: UnaryFn<T, U>, | |
| ): U | F => (isOk(result) ? onOk(result.data) : onErr(result.err)) | |
| export const map = <E, T, U>(result: Result<E, T>, f: UnaryFn<T, U>) => | |
| isOk(result) ? ok(f(result.data)) : result | |
| export const mapErr = <E, T, F>( | |
| result: Result<E, T>, | |
| f: UnaryFn<E, F>, | |
| ): Result<F, T> => (isErr(result) ? err(f(result.err)) : result) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment