A Rust-inspired Result type for TypeScript that makes error handling explicit and unavoidable.
npm install @alexismora/result-typeimport { Result, ok, err } from '@alexismora/result-type'
function divide(a: number, b: number): Result<number, string> {
if (b === 0) {
return err("Division by zero")
}
return ok(a / b)
}
// Force explicit error handling
const result = divide(10, 2)
const value = result.expect("Division should succeed") // 5
// Safe handling with defaults
const safe = divide(10, 0).unwrapOr(0) // 0Traditional Typescript error handling with try/catch is easy to ignore. The Result type forces you to handle errors explicitly, making your code more reliable and predictable.
- Rust-inspired API - Familiar to Rust developers
- Type-safe - Full TypeScript support with strict inference
- Zero dependencies - Lightweight and fast
- Explicit errors - No more forgotten error handling
- Functional - Chain operations with
map,andThen, etc. - Dual packages - ESM and CommonJS support
The Result type represents either success (Ok) or failure (Err). It forces you to handle errors explicitly, preventing the common JavaScript pattern of ignoring errors.
import { Result, ok, err } from '@alexismora/result-type'
type MyResult = Result<number, string>
const success: MyResult = ok(42)
const failure: MyResult = err("something went wrong")A type that is either Ok<T> (success with value of type T) or Err<E> (failure with error of type E).
Represents a successful result containing a value of type T.
Represents a failed result containing an error of type E.
Creates a successful Result containing the given value.
const result = ok(42)
// Result<number, never>Creates a failed Result containing the given error.
const result = err("File not found")
// Result<never, string>You can also use the class constructors directly:
import { Ok, Err } from '@alexismora/result-type'
const success = new Ok(42)
const failure = new Err("error message")Returns true if the result is Ok. Acts as a type guard in TypeScript.
const result = ok(42)
if (result.isOk()) {
// TypeScript knows this is Ok<number>
console.log(result.value) // 42
}Returns true if the result is Err. Acts as a type guard in TypeScript.
const result = err("failed")
if (result.isErr()) {
// TypeScript knows this is Err<string>
console.log(result.error) // "failed"
}Returns the contained Ok value. If the result is Err, throws an error with your custom message.
Use this when you have a good reason to expect success and want to fail fast with a clear message.
const config = loadConfig().expect("Config file must exist")
// If loadConfig() returns Err, throws: "Config file must exist: [error details]"Returns the contained Ok value. If the result is Err, throws with a default message.
Use sparingly - prefer expect() for better error messages or safe methods like unwrapOr().
const value = ok(42).unwrap() // 42
const value = err("failed").unwrap() // Throws: "Called unwrap on an Err value: failed"Returns the contained Ok value, or the provided default if Err.
Use this when you have a sensible fallback value.
const port = getPort().unwrapOr(3000)
// If getPort() fails, uses 3000 as defaultReturns the contained Ok value, or computes a value from the error using the provided function.
Use this when you need to compute a fallback based on the error.
const value = result.unwrapOrElse((error) => {
console.error("Operation failed:", error)
return getDefaultValue()
})Transforms the Ok value by applying a function. If Err, returns the error unchanged.
Use for transforming successful values while preserving errors.
const result = ok(5)
.map(x => x * 2)
.map(x => x.toString())
// Result<string, never> = Ok("10")
const failed = err("oops").map(x => x * 2)
// Result<number, string> = Err("oops")Transforms the Err value by applying a function. If Ok, returns the value unchanged.
Use for transforming or enriching error information.
const result = err(404)
.mapErr(code => `HTTP Error ${code}`)
// Result<never, string> = Err("HTTP Error 404")
const success = ok(42).mapErr(e => `Error: ${e}`)
// Result<number, string> = Ok(42)Chains operations that return Results. Also known as flatMap.
Use for sequential operations that can each fail.
const result = readFile("config.json")
.andThen(content => parseJSON(content))
.andThen(config => validateConfig(config))
// If any step fails, the error propagates
// If all succeed, you get the final valueReturns this result if Ok, otherwise returns the alternative.
Use for providing fallback operations.
const result = readFromCache()
.or(readFromDatabase())
.or(readFromAPI())
// Uses the first successful resultPattern matching for Results. Calls one function or the other based on the variant.
Use for handling both cases explicitly and transforming to a common type.
const message = result.match({
ok: (value) => `Success: ${value}`,
err: (error) => `Failed: ${error}`
})
const status = apiCall().match({
ok: (data) => ({ success: true, data }),
err: (error) => ({ success: false, error })
})Wraps a function that might throw an exception into a Result.
Use to convert exception-based code to Result-based code.
const result = tryCatch(
() => JSON.parse(jsonString),
(error) => `Parse error: ${error}`
)
if (result.isOk()) {
console.log("Parsed:", result.value)
} else {
console.error("Failed:", result.error)
}Without error handler, the caught error is used directly:
const result = tryCatch(() => riskyOperation())
// Result<ReturnType, unknown>tryCatchAsync<T, E>(fn: () => Promise<T>, errorHandler?: (error: unknown) => E): Promise<Result<T, E>>
Async version of tryCatch. Wraps an async function that might throw.
Use to convert promise rejections into Results.
const result = await tryCatchAsync(
async () => await fetch(url).then(r => r.json()),
(error) => `Network error: ${error}`
)
result.match({
ok: (data) => console.log("Data:", data),
err: (error) => console.error("Error:", error)
})function divide(a: number, b: number): Result<number, string> {
if (b === 0) {
return err("Division by zero")
}
return ok(a / b)
}
const result = divide(10, 2)
const value = result.expect("Division should succeed") // 5
const bad = divide(10, 0)
const safe = bad.unwrapOr(0) // 0function readConfig(): Result<Config, string> {
return tryCatch(
() => {
const content = fs.readFileSync("config.json", "utf8")
return JSON.parse(content)
},
(error) => `Failed to read config: ${error}`
)
}
const config = readConfig()
.map(c => ({ ...c, loaded: true }))
.unwrapOr(getDefaultConfig())async function fetchUser(id: string): Promise<Result<User, ApiError>> {
return tryCatchAsync(
async () => {
const response = await fetch(`/api/users/${id}`)
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
return response.json()
},
(error) => ({
code: "FETCH_ERROR",
message: String(error)
})
)
}
// Usage
const result = await fetchUser("123")
const user = result.match({
ok: (user) => user,
err: (error) => {
console.error("Failed to fetch user:", error)
return null
}
})function processUserData(userId: string): Result<ProcessedData, string> {
return fetchUserFromDB(userId)
.andThen(user => validateUser(user))
.andThen(user => enrichUserData(user))
.andThen(data => processData(data))
.mapErr(error => `User processing failed: ${error}`)
}
// If any step fails, the chain short-circuits and returns the error
// If all succeed, you get the final ProcessedDatatype AppError =
| { type: "NotFound"; resource: string }
| { type: "Unauthorized"; reason: string }
| { type: "Validation"; errors: string[] }
function getResource(id: string): Result<Resource, AppError> {
if (!isAuthenticated()) {
return err({ type: "Unauthorized", reason: "Not logged in" })
}
const resource = db.find(id)
if (!resource) {
return err({ type: "NotFound", resource: id })
}
return ok(resource)
}
const result = getResource("123")
result.match({
ok: (resource) => displayResource(resource),
err: (error) => {
switch (error.type) {
case "NotFound":
show404(error.resource)
break
case "Unauthorized":
redirectToLogin(error.reason)
break
case "Validation":
showErrors(error.errors)
break
}
}
})function loadAppData(): Result<AppData, string> {
const config = loadConfig()
const user = loadUser()
const settings = loadSettings()
if (config.isErr()) return config
if (user.isErr()) return user
if (settings.isErr()) return settings
return ok({
config: config.value,
user: user.value,
settings: settings.value
})
}const data = fetchFromCache()
.or(fetchFromDatabase())
.or(fetchFromAPI())
.unwrapOr(getDefaultData())
// Tries cache first, then database, then API
// Falls back to default data if all fail-
Use
expect()with descriptive messages when you have a good reason to believe the operation should succeed. -
Prefer
unwrapOr()overunwrap()when you have a sensible default value. -
Use
andThen()for chaining operations that can each fail - it's cleaner than nested if/else. -
Use
match()for exhaustive handling when you need to handle both cases explicitly. -
Use typed errors (discriminated unions) for better error handling and IDE support.
-
Wrap external APIs with
tryCatchortryCatchAsyncto convert exceptions to Results. -
Don't mix paradigms - once you start using Result, avoid mixing it with try/catch in the same code path.
MIT
Contributions are welcome! Please feel free to submit a Pull Request.