Skip to content

Instantly share code, notes, and snippets.

@b4lk0n
Created November 5, 2024 09:01
Show Gist options
  • Select an option

  • Save b4lk0n/60ee2fd309c8e70f8f57ae3cc2e2ce15 to your computer and use it in GitHub Desktop.

Select an option

Save b4lk0n/60ee2fd309c8e70f8f57ae3cc2e2ce15 to your computer and use it in GitHub Desktop.
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)
}
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,
}
}
export type Sink<T> = (v: T) => void
export type Thunk<T> = () => T
export type UnaryFn<T, U> = (v: T) => U
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 }
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
}
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