@billdaddy/safekit
v0.1.0
Published
Zero-dependency Try monad for TypeScript. Capture exceptions as values — Try.of(() => risky()).map().recover().getOrElse(). Port of Java Vavr Try / Scala util.Try.
Maintainers
Readme
safekit
Zero-dependency Try monad for TypeScript. Execute risky code and handle exceptions as values, not control flow.
Port of Java Vavr Try / Scala scala.util.Try. The existing try-monad npm package has been abandoned since 2017 (2 downloads/week).
Install
npm install @billdaddy/safekitThe problem Try solves
Result<T,E> (neverthrow, resultkit) wraps already-computed values where you know the error type ahead of time. Try<T> executes a computation and captures any thrown exception automatically — no need to know what might throw:
// Result: you write the error path manually
const result: Result<User, ApiError> = ok(user);
// Try: exception is captured automatically
const t = Try.of(() => JSON.parse(rawJson)); // SyntaxError captured if thrownQuick start
import { Try } from "@billdaddy/safekit";
const result = Try.of(() => JSON.parse(rawInput))
.map(obj => obj.name as string) // skipped if parse failed
.filter(name => name.length > 0) // skipped if map failed
.recover(e => "anonymous") // handles any prior failure
.get(); // never throws — recover caught everythingAPI
Try.of(fn)
Execute a function and capture the result or exception:
const t1 = Try.of(() => parseInt("42", 10)); // Success(42)
const t2 = Try.of(() => JSON.parse("bad")); // Failure(SyntaxError)
const t3 = Try.of(() => { throw "string err"; }); // Failure("string err")Try.ofAsync(fn) — async computations
const t = await Try.ofAsync(async () => fetch("/api/users").then(r => r.json()));
// Never rejects — always resolves to Success or Failure
if (t.isSuccess()) {
console.log(t.get()); // the parsed JSON
} else {
console.error(t.getCause()); // the fetch/parse error
}Transformations (fluent chaining)
Try.of(() => " hello ")
.map(s => s.trim()) // Success("hello")
.map(s => s.toUpperCase()) // Success("HELLO")
.filter(s => s.length > 3) // Success("HELLO") — passes
.flatMap(s => Try.of(() => s)) // Success("HELLO")
.get() // "HELLO"All transformations on a Failure are no-ops — the original failure propagates:
Try.of(() => { throw new Error("fail"); })
.map(x => x) // no-op
.filter(() => true) // no-op
.getOrElse("default") // "default"Recovery
// recover — provide a fallback value
const t = Try.of(() => riskyParse())
.recover(e => fallbackValue);
// recoverWith — provide a fallback Try computation
const t = Try.of(() => fetchPrimary())
.recoverWith(e => Try.of(() => fetchBackup()));Extracting values
const t = Try.of(() => compute());
t.get() // value or rethrows
t.getOrElse(defaultValue) // value or default
t.getOrElseGet(cause => handleErr()) // value or call fn(cause)
t.getOrElseThrow(e => new MyErr(e)) // value or throw custom error
t.toNullable() // value or null
t.toArray() // [value] or []
t.getCause() // cause (throws if Success)Fold
const message = Try.of(() => riskyOp()).fold(
value => `Success: ${value}`,
cause => `Error: ${(cause as Error).message}`,
);Side effects with tap
Try.of(() => loadConfig())
.tap(
config => logger.info("Loaded config", config),
err => logger.error("Config load failed", err),
)
.getOrElse(defaultConfig);Try.all — collect multiple results
const t = Try.all([
Try.of(() => parseA(rawA)),
Try.of(() => parseB(rawB)),
Try.of(() => parseC(rawC)),
]);
if (t.isSuccess()) {
const [a, b, c] = t.get();
} else {
console.error("First failure:", t.getCause());
}instanceof narrowing
import { Try, Success, Failure } from "@billdaddy/safekit";
const t = Try.of(() => 42);
if (t instanceof Success) {
t.get(); // TypeScript knows it's Success here
} else {
t.getCause(); // TypeScript knows it's Failure here
}Comparison with alternatives
| Package | Lazy (captures exceptions) | TypeScript | Active | Zero deps | |---|---|---|---|---| | safekit (Try) | ✅ | ✅ | ✅ | ✅ | | neverthrow | ❌ (wraps already-computed) | ✅ | ✅ | ✅ | | resultkit | ❌ (wraps already-computed) | ✅ | ✅ | ✅ | | try-monad | ✅ | ❌ | ❌ (abandoned 2017) | ✅ | | fp-ts | ✅ (TaskEither) | ✅ | ✅ | ❌ (heavy) | | Java Vavr Try | ✅ | n/a | ✅ | n/a | | Scala Try | ✅ | n/a | ✅ | n/a |
Contributors ✨
This project follows the all-contributors specification. Contributions of any kind are welcome — code, docs, bug reports, ideas, reviews! See the emoji key for how each contribution is recognized, and open a PR or issue to get involved.
Thanks goes to these wonderful people:
License
MIT © trananhtung
