ripthrow
v3.2.4
Published
Zero-overhead, type-safe error handling for TypeScript.
Downloads
722
Maintainers
Readme
ripthrow
Zero-dependency, type-safe error handling for TypeScript.
ripthrow is a lightweight library inspired by Rust's Result type and the proposed ECMAScript ?= operator. It allows you to handle success and failure in a structured way, avoiding try/catch blocks and making error states explicit in your types.

Installation
bun add ripthrow
# or
npm install ripthrowQuick Start
Explicit errors with Ok and Err
Functions return Result, making failure paths visible in the type signature:
import { Ok, Err, match, type Result } from "ripthrow";
function getUser(id: string): Result<{ id: string; name: string }, string> {
if (!id) {
return Err("ID is required");
}
return Ok({ id, name: "Alice" });
}
const result = getUser("123");
match(result, {
ok: (user) => console.log(user.name),
err: (msg) => console.error(`getUser function failed: ${msg}`),
});Wrap throwing code with safe
safe catches exceptions and returns a Result — no try/catch:
import { safe, build } from "ripthrow";
const raw = '{"valid": true}';
const isValid = build(safe(() => JSON.parse(raw)))
.map((data: any) => data.valid)
.unwrapOr(false);
console.log(isValid); // trueWhy ripthrow?
Handling errors in TypeScript with native exceptions is inherently unpredictable. Because JavaScript allows you to throw anything—from a string to a new Date()—TypeScript is forced to treat all caught errors as unknown. This forces a type-erasure effect where failure states become invisible to the compiler and uncontracted in your function signatures.
Handling errors with exceptions can lead to "hidden" control flows. ripthrow forces you to acknowledge potential failures, leading to more resilient applications.
While there are other libraries, ripthrow is designed to be a lightweight "missing operator" for modern TypeScript, focusing on minimal overhead and native safety.
The image presents an usage of
matchErrutility. It's failing because there's a missing handler forAuthError.
Comparison: ripthrow vs neverthrow
| Feature | ripthrow | neverthrow |
|---------|----------|------------|
| Overhead | POJO / Object literals | Class-based allocation |
| Namespaces | None (ESM-only) | Uses Namespaces |
| Size (min+gzip) | ~1.6 KB | ~2.0 KB |
| Error Unions | Fluent matchErr().exhaustive() | Manual pattern matching |
Features
Minimal Overhead:
ripthrowuses simple JavaScript objects ({ ok: true, value }) instead of class instances. This ensures faster allocation and zero overhead when chaining operations, while remaining 100% type-safe.Dedicated Exhaustive Matching: While most libraries require writing manual
switchstatements withneverchecks to handle error unions,ripthrowprovides a built-in fluent API.matchErr().exhaustive()ensures at compile-time that every defined error variant is handled.Collision-Free Errors: Unique Symbols are used to identify error types, ensuring that error matching is precise and safe from name collisions across different packages or versions.
Modern ESM Design: Built without legacy patterns like namespaces or internal classes, ensuring the best possible compatibility with modern bundlers and tree-shaking.
Benchmarks
Benchmark Environment
Runtime: Bun 1.3.
CPU: Intel® Core™ i7-7700 × 8
RAM: 16gb ddr4.
OS: Fedora Linux 44 (Forty Four)
Latest Run:
[email protected]
All benchmarks run on Bun 1.3 via tinybench (bun run bench).
Higher ops/s = faster. Latency is per-operation (lower is better).
Construction
| Pattern | ops/s | Latency | vs native |
|---------|------:|--------:|----------:|
| Ok() | 26,013,752 | 40.1 ns | — |
| Err() | 25,357,831 | 41.6 ns | — |
| throw | 1,182,517 | 1047.0 ns | 22× slower |
| { ok: true } manual | 24,998,347 | 41.7 ns | identical |
Ok/Errare object literals — zero overhead vs writing the union manually.throwis the expensive one (Error object creation), not ripthrow.
Wrapping (safe vs try/catch)
| Pattern | ops/s | Latency |
|---------|------:|--------:|
| safe(success) | 21,958,738 | 50.6 ns |
| try/catch (no throw) | 26,350,123 | 38.9 ns |
| safe(throws) | 872,088 | 1373.5 ns |
| try/catch (throw) | 1,194,288 | 1073.8 ns |
| safeAsync(success) | 3,285,471 | 322.8 ns |
| try/catch async (success) | 3,276,500 | 323.7 ns |
safeadds <15 ns vs raw try/catch on the success path. On throws, both are bottlenecked by Error object creation — ripthrow is not the overhead. Async paths are identical (bottleneck is Promise scheduling).
Mapping & Chaining
| Pattern | ops/s | Latency |
|---------|------:|--------:|
| map x5 | 14,677,208 | 78.9 ns |
| if/else x5 (native) | 19,481,783 | 58.4 ns |
| andThen chain | 21,421,520 | 50.1 ns |
| orElse fallback | 23,304,137 | 46.9 ns |
| builder chain (5 ops) | 1,711,691 | 677.4 ns |
Functional overhead is ~3-5 ns per function call. The builder (fluent API) adds roughly 1-2 ns per method call — effectively zero.
Matching vs try/catch
| Pattern | ops/s | Latency |
|---------|------:|--------:|
| match(Ok) | 24,680,590 | 43.6 ns |
| match(Err) | 24,835,832 | 42.5 ns |
| try/catch (no throw) | 26,350,123 | 38.9 ns |
| try/catch (throw) | 1,194,288 | 1073.8 ns |
matchis within 15% of raw try/catch on the success path. On error paths,matchis 20× faster than try/catch with a thrown error.
Collections
| Pattern | ops/s | Latency |
|---------|------:|--------:|
| all(5 ok) | 10,445,477 | 110.5 ns |
| all(5, last err) | 14,598,523 | 78.8 ns |
| any(5 ok) | 15,921,334 | 72.8 ns |
Pattern Matching (matchErr)
| Pattern | ops/s | Latency |
|---------|------:|--------:|
| matchErr (hit — creates Error) | 1,188,146 | 1031.3 ns |
| matchErr (miss — instanceof only) | 10,155,206 | 122.7 ns |
The "hit" path creates the matched
TypedError, which is the same cost asnew Error(). The "miss" path is just aninstanceofcheck — 10M ops/s.
Context
| Pattern | ops/s | Latency |
|---------|------:|--------:|
| note() (plain Err) | 1,524,382 | 799.9 ns |
| note() (existing Report) | 733,049 | 1629.7 ns |
Creating structured
Reportobjects has the same cost asnew Error(~1 µs).
Real-World Patterns
| Pattern | ops/s | Latency | |---------|------:|--------:| | parse JSON (ripthrow) | 2,562,169 | 442.3 ns | | parse JSON (try/catch) | 8,885,156 | 117.9 ns | | deep access (ripthrow) | 3,244,627 | 359.9 ns | | deep access (try/catch) | 26,484,065 | 38.9 ns | | legacy throw fn (ripthrow) | 20,946,403 | 54.1 ns | | legacy throw fn (try/catch) | 27,033,874 | 38.5 ns |
In real-world usage, ripthrow adds 15-20 ns per operation — well within the "zero overhead" claim for all practical purposes. The bottleneck is never the Result type, it's whatever you're doing inside
map/andThen.
Summary
Ok/Err= native object literal speed. Same representation, same performance.map/andThen= 1-5 ns overhead per function call.match= faster than try/catch when errors are actually thrown.safe()= same speed as manual try/catch on success paths.matchErr"miss" path = 10M ops/s, "hit" path = Error construction speed.- Cost center is always
new Error(), not ripthrow.
Licence
This project is licensed under MIT License.
