Handle success and failure explicitly with a type-safe, Rust-inspired Result
for TypeScript.
strict-result represents an operation as either Ok(value) or Err(error).
Checking the result narrows its type, so successful values and failures can be
handled without exceptions, unsafe casts, or optional properties.
As a project grows, functions can develop inconsistent ways of reporting
failure. One function might return a string or null, while another returns an
array or an object. Without a shared convention and clear type constraints,
callers must guess which values represent success and which represent failure.
Result gives these functions a consistent return type: Ok for success and
Err for failure. Because the two outcomes are represented explicitly, callers
must distinguish between them before TypeScript allows access to the successful
value. This encourages developers to handle expected failures where they occur,
rather than throwing exceptions or optimistically assuming an operation will
succeed.
- Discriminated
Result<Ok, Error>union with TypeScript type narrowing - Familiar helpers such as
map,unwrap,unwrapOr, andmapOrElse - String errors by default, with opt-in support for custom error types
- Utilities for normalizing unknown errors and unpacking results
- Small, dependency-free ESM package
npm install strict-resultimport { Err, Ok, type Result } from "strict-result"
async function safeFetch(
...args: Parameters<typeof fetch>
): Promise<Result<Response, string>> {
try {
return Ok(await fetch(...args))
} catch (error) {
return Err(error)
}
}
const result = await safeFetch("https://jsonplaceholder.typicode.com/todos/1")
if (result.isErr()) {
console.error(`Request failed: ${result.error}`)
} else {
// result is narrowed to OkResult<Response> here.
console.log(await result.value.json())
}By default, Err(error) converts an unknown error to a string. Pass true as
the second argument to preserve a structured error value.
import { Err, Ok, stringifyError, type Result } from "strict-result"
interface HttpRequestError {
message: string
status?: number
}
async function safeFetch(
...args: Parameters<typeof fetch>
): Promise<Result<Response, HttpRequestError>> {
try {
const response = await fetch(...args)
if (response.status >= 400) {
return Err(
{ message: response.statusText, status: response.status },
true,
)
}
return Ok(response)
} catch (error) {
return Err({ message: stringifyError(error) }, true)
}
}For repeated error construction, return an error-only result from a helper:
By convention, name custom error helpers in PascalCase and end their names with
Err, such as HttpErr. This makes them recognizable as specialized Err
constructors rather than Error classes or general-purpose functions.
import { Err, stringifyError, type Result } from "strict-result"
interface HttpRequestError {
message: string
status?: number
}
function HttpErr(
error: unknown,
status?: number,
): Result<never, HttpRequestError> {
return Err({ message: stringifyError(error), status }, true)
}The fetch wrapper can then replace its inline custom errors with HttpErr:
async function safeFetch(
...args: Parameters<typeof fetch>
): Promise<Result<Response, HttpRequestError>> {
try {
const response = await fetch(...args)
if (response.status >= 400) {
- return Err(
- { message: response.statusText, status: response.status },
- true,
- )
+ return HttpErr(response.statusText, response.status)
}
return Ok(response)
} catch (error) {
- return Err({ message: stringifyError(error) }, true)
+ return HttpErr(error)
}
}import { Err, Ok, type Result } from "strict-result"
const doubled = Ok(21).map((value) => value * 2)
doubled.unwrap() // 42
const unavailable: Result<string, string> = Err("not available")
unavailable.unwrapOr("fallback") // "fallback"
const message = Ok(3).mapOrElse(
(error) => `Failed: ${error}`,
(value) => `Received: ${value}`,
)
// "Received: 3"unwrap() returns an Ok value and throws when called on an Err. Prefer
isOk(), isErr(), unwrapOr(), or mapOrElse() when failure is expected.
Use unpack when code is easier to consume with a value and error available as
separate fields. The fallback guarantees that value is non-nullish.
import { Err, unpack, type Result } from "strict-result"
const result: Result<string, { status: number }> = Err({ status: 503 }, true)
const { value, error } = unpack(result, "No data available")
console.log(value) // "No data available"
console.log(error) // { status: 503 }For an Ok result, value contains the successful value and error is
null. A nullish successful value is replaced by the provided fallback.
A result can also model the state returned by a hook:
import { useEffect, useState } from "react"
import { Err, Ok, type Result } from "strict-result"
export function useSafeFetch(url: string): Result<Response | null, string> {
const [result, setResult] = useState<Result<Response | null, string>>(Ok(null))
useEffect(() => {
let active = true
void fetch(url)
.then((response) => Ok<Response | null>(response))
.catch((error) => Err(error))
.then((nextResult) => {
if (active) {
setResult(nextResult)
}
})
return () => {
active = false
}
}, [url])
return result
}In a production hook, consider representing loading as a separate state rather
than treating Ok(null) as both the initial and successful empty state.
A union of OkResult<O, E> and ErrResult<O, E>. Every Result has a type
discriminant ("ok" or "err") and the methods listed below.
The successful branch of a Result. Its payload is available as value.
The failed branch of a Result. Its payload is available as error.
An object returned by unpack, containing a non-nullish value and either an
error or null.
Creates a successful Result containing value.
Creates a failed Result. Without raw, the error is normalized to a string.
Pass true to preserve the original error value and type.
Creates a string error prefixed with a name, such as
NamedErr("parse", error).
Converts an unknown thrown value to a useful string. It handles strings,
Error instances, Zod-like errors, plain objects, and circular references.
Converts a Result into { value, error }. An Err uses defaultValue; an
Ok uses its contained value, or defaultValue when that value is nullish.
| Method | Description |
|---|---|
isOk() |
Returns true for an Ok and narrows the Result type. |
isErr() |
Returns true for an Err and narrows the Result type. |
unwrap() |
Returns the successful value or throws the error. |
unwrapOr(defaultValue) |
Returns the successful value or a fallback. |
map(fn) |
Transforms an Ok value and leaves an Err unchanged. |
mapOrElse(defaultFn, mapFn) |
Maps either branch into a plain value. |
toJSON() |
Converts the contained value or error to a JSON-style string. |
strict-result is published as an ECMAScript module and includes TypeScript
declarations. Its output targets ES2022-compatible runtimes.
npm install
npm test
npm run build