-
Notifications
You must be signed in to change notification settings - Fork 0
Getting Started
This guide covers the basics of using ripthrow to handle errors effectively.
bun add ripthrow
# or
npm install ripthrowIn ripthrow, every operation that can fail returns a Result<T, E>.
-
Ok<T>represents a success. -
Err<E>represents a failure.
import { Ok, Err } from "ripthrow";
const success = Ok("Hello");
const failure = Err(new Error("Broken"));Use safe to wrap synchronous code that might throw.
import { safe } from "ripthrow";
const res = safe(() => JSON.parse(input));Use safeAsync to handle asynchronous operations.
import { safeAsync } from "ripthrow";
const res = await safeAsync(fetch("https://api.example.com"));bail creates a Report — an Error with a help message and extra context.
import { Err, bail } from "ripthrow";
function getUser(id: string) {
if (!id) return Err(bail("Missing user ID", {
help: "Provide a valid user ID",
context: { id },
}));
return Ok({ id, name: "Alice" });
}The most robust way to extract data.
import { match } from "ripthrow";
const message = match(res, {
ok: (val) => `Data: ${val}`,
err: (err) => `Failed: ${err.message}`
});import { unwrapOr } from "ripthrow";
const val = unwrapOr(res, "fallback");If you are sure it won't fail (or want to crash if it does).
import { unwrap } from "ripthrow";
const val = unwrap(res);Wrap a Result with build() to chain methods:
import { Ok, build } from "ripthrow";
const result = build(Ok(1))
.map((n) => n + 1)
.andThen((n) => Ok(n.toString()))
.unwrapOr("0");Or use the handy namespace constructors for a more compact style:
import { ResultBuilder } from "ripthrow";
const result = ResultBuilder.safe(() => JSON.parse('{"a": 1}'))
.map((data: any) => data.a)
.unwrap();import { ResultBuilder } from "ripthrow";
const result = ResultBuilder.safe(() => JSON.parse(input))
.note("Invalid JSON")
.unwrapOr({});Notes accumulate when called multiple times:
const result = ResultBuilder.safe(() => fetch("/api/user"))
.note("Request failed")
.note("Check your network")
.unwrapOr(null);
// Report.notes → ["Request failed", "Check your network"]Combine multiple results easily:
const all = ResultBuilder.all([Ok(1), Ok(2)]).unwrap(); // [1, 2]
const any = ResultBuilder.any([Err("a"), Ok(1)]).unwrap(); // 1For async operations, use buildAsync() or AsyncResultBuilder:
import { AsyncResultBuilder } from "ripthrow";
const result = await AsyncResultBuilder.safeAsync(fetch("/api/user"))
.andThen(async (res) => {
const data = await res.json();
return data.id ? Ok(data) : Err("Missing ID");
})
.unwrapOr(null);The andThen and orElse callbacks in AsyncResultBuilder accept both sync Result and async Promise<Result>.
For a functional style without the builder, use pipe to compose transforms:
import { safe, pipe, map, unwrapOr } from "ripthrow";
const value = await pipe(
safe(() => JSON.parse('{"a":1}')),
(r) => map(r, (data: any) => data.a),
(r) => unwrapOr(r, 0),
); // 1pipe accepts both sync values and Promises — it awaits each step automatically, so you can mix sync and async transforms freely.
Define type-safe errors with automatic message interpolation:
import { createError, matchErr, type ErrFactory } from "ripthrow";
const NotFound = createError(
"NotFound",
(id: string) => `User "${id}" not found`,
(id: string) => `Double-check user ID "${id}"`,
);
const DbError = createError(
"DbError",
(code: number) => `Database error ${code}`,
);Use them in your Result-returning functions:
function getUser(id: string) {
if (!id) return Err(NotFound(id));
return Ok({ name: "Alice" });
}Use .exhaustive() when all variants are handled — TypeScript checks at compile time that no kind is missing:
const message = matchErr(getUser("123"))
.on(NotFound, (e) => `Missing user ${e.args[0]}`)
.on(DbError, (e) => `DB error code ${e.args[0]}`)
.exhaustive(); // ✅ TypeScript rejects if a variant is unhandledEvery TypedError carries a kind field with the error name as a literal type, letting TypeScript narrow discriminated unions.
Define all your app errors in one place, like thiserror:
const Errors = createErrors({
NotFound: { message: (id: string) => `User "${id}" not found` },
DbError: { message: (code: number) => `Database error ${code}` },
});
type AppError = typeof Errors._type;Use Errors.NotFound(id) directly — fully typed, with .kind as discriminant.
Each error can carry optional _metadata (e.g. an HTTP status code):
const Errors = createErrors({
NotFound: {
message: (id: string) => `User "${id}" not found`,
help: () => "Verify the user ID",
_metadata: { status: 404 },
},
});When enriched via createReport() or reportFrom(), the _metadata is merged into the resulting Report.context.
Match errors from third-party libraries:
import { wrapError } from "ripthrow";
import { PrismaClientKnownRequestError } from "@prisma/client";
const PrismaErr = wrapError(PrismaClientKnownRequestError);
matchErr(result)
.on(PrismaErr, (e) => `Prisma error ${e.code}`)
.otherwise((e) => `Other: ${e.message}`);Use kindOf to extract the .kind discriminant from any ripthrow error, including Report (traverses .cause):
import { kindOf } from "ripthrow";
app.use((err, _req, res, _next) => {
const kind = kindOf(err);
if (!kind) {
// Unknown error — don't leak details
return res.status(500).json({ error: "Internal server error" });
}
res.status(400).json({ error: { kind, message: err.message } });
});