Skip to content

Instantly share code, notes, and snippets.

@b4lk0n
Created October 1, 2025 17:24
Show Gist options
  • Select an option

  • Save b4lk0n/903fa38b92a2fd99bbe2de518c4398d7 to your computer and use it in GitHub Desktop.

Select an option

Save b4lk0n/903fa38b92a2fd99bbe2de518c4398d7 to your computer and use it in GitHub Desktop.
import { err, isErr, ok } from "./result";
function createError<const T extends readonly string[]>(_codes: T) {
type Code = T[number];
class AppError<C extends Code = Code> extends Error {
public readonly code: C;
constructor(code: C) {
super(`Error: ${code}`);
this.code = code;
}
}
return AppError as new <C extends Code = Code>(
code: C,
) => AppError<C>;
}
// users module
const UserError = createError(["already_exists", "banned"] as const);
function getUser() {
const rand = Math.random();
if (rand < 0.5) {
return err(new UserError("already_exists"));
}
return ok("string");
}
const res = getUser();
if (isErr(res)) {
switch (res.err.code) {
case "already_exists":
console.log("User already exists");
break;
// error, 'cause getUser does not return such error
// case "banned":
// console.log("You are banned");
// break;
}
}
// profiles module
const ProfileError = createError(["user_not_found", "account_banned"] as const);
function geProfile() {
const user = getUser();
if (isErr(user)) {
switch (user.err.code) {
case "already_exists":
break;
}
return err(new ProfileError("user_not_found"));
}
return ok({ id: 10 });
}
const profile = geProfile();
if (isErr(profile)) {
switch (profile.err.code) {
case "user_not_found":
console.log("User not found");
break;
// error, 'cause getProfile does not return such error
// case "account_banned":
// console.log("Account is banned");
// break;
}
}
export type Ok<T> = Readonly<{
tag: "Ok";
data: T;
}>;
export type Err<E> = Readonly<{
tag: "Err";
err: E;
}>;
export type Result<E, T> = Ok<T> | Err<E>;
export function ok<T>(data: T): Ok<T> {
return Object.freeze({ tag: "Ok", data });
}
export function err<E>(err: E): Err<E> {
return Object.freeze({ tag: "Err", err });
}
export function isOk<E, T>(result: Result<E, T>): result is Ok<T> {
return result.tag === "Ok";
}
export function isErr<E, T>(result: Result<E, T>): result is Err<E> {
return result.tag === "Err";
}
export function isResult<E, T>(result: unknown): result is Result<E, T> {
return (
result !== null &&
typeof result === "object" &&
"tag" in result &&
(result.tag === "Ok" || result.tag === "Err")
);
}
export function map<E, T, F>(
result: Result<E, T>,
fn: (data: T) => F,
): Result<E, F> {
return result.tag === "Ok" ? ok(fn(result.data)) : result;
}
export function mapErr<E, T, F>(
result: Result<E, T>,
fn: (err: E) => F,
): Result<F, T> {
return result.tag === "Err" ? err(fn(result.err)) : result;
}
export function unwrap<E, T>(result: Result<E, T>): T {
if (result.tag === "Ok") {
return result.data;
}
throw result.err;
}
export function unwrapOr<E, T>(result: Result<E, T>, defaultValue: T): T {
if (isErr(result)) {
return defaultValue;
}
return unwrap(result);
}
export function unwrapErr<E, T>(result: Result<E, T>): E {
if (result.tag === "Err") {
return result.err;
}
throw new Error("Result is ok");
}
export function match<E, T, F, U>(
result: Result<E, T>,
onErr: (err: E) => F,
onOk: (data: T) => U,
): F | U {
return result.tag === "Err" ? onErr(result.err) : onOk(result.data);
}
export function invariant<E, T>(
result: Result<E, T>,
message: string,
): asserts result is Ok<T> {
if (!isOk(result)) {
const cause = unwrapErr(result);
console.error(cause);
throw new Error(message, { cause });
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment