Result types for TypeScript. Every failure is a Fault carrying tag, details, location, metadata and cause, and it serialises straight into Sentry or OTel.
import { ok, err, retry, Fault, ServiceError } from '@thourum/fault'
import { safeFetchJSON } from '@thourum/fault/fetch'
import { safeZodParse } from '@thourum/fault/zod'
Fault.onCapture = (f) => Sentry.captureException(f, { extra: f.toJSON() })
const me = () => safeFetchJSON('https://api.example.com/me')
const user = await retry(me, { times: 3, delayMs: 200 })
.andThen(safeZodParse(userSchema))
.andThen((u) => (u.active ? ok(u) : err(ServiceError('FORBIDDEN', 'inactive'))))
.orInspect((f) => f.capture())Build the failure once. toJSON() is what Sentry, OTel and your logger receive. location is filled in for you.
What you write
err(
new Fault('stripe declined')
.withTag('PAYMENT_FAILED')
.withDetails('card_declined: insufficient_funds')
.withMetadata({ intentId: 'pi_3Q…', amountCents: 4900 })
.withCause(stripeError),
)What your logger gets
{
"name": "Error",
"message": "stripe declined",
"tag": "PAYMENT_FAILED",
"details": "card_declined: insufficient_funds",
"statusCode": 402,
"location": "at chargeUser (src/billing.ts:41:12)",
"metadata": {
"intentId": "pi_3Q…",
"amountCents": 4900
},
"cause": {
"name": "StripeCardError",
"message": "Your card has insufficient funds.",
"stack": "StripeCardError: Your card has insufficient funds.\n at ..."
},
"stack": "Error: stripe declined\n at chargeUser (src/billing.ts:41:12)\n at ..."
}The core has no runtime dependencies. Each integration lives on its own import path and only asks for the peer it wraps.
| Import | Exports | Peer |
|---|---|---|
@thourum/fault | Result, ResultAsync, ok, err, Fault, ServiceError, retry | none |
@thourum/fault/fetch | safeFetch | none |
@thourum/fault/zod | safeZodParse, fromZodError | zod |
@thourum/fault/drizzle | safeDb, DatabaseError | drizzle-orm, pg |
@thourum/fault/pg | parsePgError, isPostgresError | pg |
@thourum/fault/std | safeJsonParse, safeJsonStringify, safeReadFile, safeWriteFile, safeEnv | none |