@alexismora/result-type
v1.0.0
Published
A Rust-inspired Result type for TypeScript that makes error handling explicit and unavoidable
Maintainers
Readme
@alexismora/result-type
A Rust-inspired Result type for TypeScript that makes error handling explicit and unavoidable.
Installation
npm install @alexismora/result-typeQuick Start
import { 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) // 0Why implement Result?
Traditional 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.
Key Features
- 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
Usage
Table of Contents
Overview
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")Basic Types
Result<T, E>
A type that is either Ok<T> (success with value of type T) or Err<E> (failure with error of type E).
Ok<T, E>
Represents a successful result containing a value of type T.
Err<T, E>
Represents a failed result containing an error of type E.
Creating Results
ok<T, E>(value: T): Result<T, E>
Creates a successful Result containing the given value.
const result = ok(42)
// Result<number, never>err<T, E>(error: E): Result<T, E>
Creates a failed Result containing the given error.
const result = err("File not found")
// Result<never, string>Constructor Usage
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")Core Methods
isOk(): boolean
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
}isErr(): boolean
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"
}expect(message: string): T
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]"unwrap(): T
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"unwrapOr(defaultValue: T): T
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 defaultunwrapOrElse(fn: (error: E) => T): T
Returns 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()
})Functional Methods
map<U>(fn: (value: T) => U): Result<U, E>
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")mapErr<F>(fn: (error: E) => F): Result<T, F>
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)andThen<U>(fn: (value: T) => Result<U, E>): Result<U, E>
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 valueor(other: Result<T, E>): Result<T, E>
Returns this result if Ok, otherwise returns the alternative.
Use for providing fallback operations.
const result = readFromCache()
.or(readFromDatabase())
.or(readFromAPI())
// Uses the first successful resultmatch<U>(patterns: { ok: (value: T) => U, err: (error: E) => U }): U
Pattern 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 })
})Helper Functions
tryCatch<T, E>(fn: () => T, errorHandler?: (error: unknown) => E): Result<T, E>
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)
})Examples
Basic Error Handling
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) // 0File Operations
function 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())API Calls
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
}
})Chaining Operations
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-Safe Error Handling
type 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
}
}
})Combining Multiple Results
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
})
}Graceful Degradation
const data = fetchFromCache()
.or(fetchFromDatabase())
.or(fetchFromAPI())
.unwrapOr(getDefaultData())
// Tries cache first, then database, then API
// Falls back to default data if all failBest Practices
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.
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Repository
https://github.com/AlexisMora/result-type
