Result
27 exports from @ontologyio/receta/result.
Everything exported from @ontologyio/receta/result. New here? Start with the Result guide.
Functions
collect
Collects an array of Results into a single Result of an array.
If all Results are Ok, returns Ok with an array of all values. If any Result is Err, returns the first Err encountered.
This is useful for validating multiple independent operations and collecting all successes or short-circuiting on the first error.
In a pipe, write collect(). A bare collect runs correctly but loses the element type.
function collect<T>(): <E>(results: readonly Result<T, E>[]) => Result<T[], E>function collect<T, E>(results: readonly Result<T, E>[]): Result<T[], E>// All successful
collect([ok(1), ok(2), ok(3)])
// => Ok([1, 2, 3])
// First error short-circuits
collect([ok(1), err('fail'), ok(3)])
// => Err('fail')
// Practical use case
const validateUser = (data: UserInput) => pipe(
[
validateName(data.name),
validateEmail(data.email),
validateAge(data.age)
],
collect(),
map(([name, email, age]) => ({ name, email, age }))
)See also partition.
err
Creates a failed Result containing an error.
function err<E>(error: E): Err<E>const result = err('Something went wrong')
// => { _tag: 'Err', error: 'Something went wrong' }flatMap
Chains Result-returning operations (monadic bind).
If the Result is Ok, applies the function to its value and returns the resulting Result. If the Result is Err, returns the Err unchanged.
Use this when you have a sequence of operations that each return a Result.
This prevents nesting Results (Result<Result<T, E>, E>).
function flatMap<T, U, E, F>(result: Result<T, E>, fn: (value: T) => Result<U, F>): Result<U, E | F>function flatMap<T, U, F>(fn: (value: T) => Result<U, F>): <E>(result: Result<T, E>) => Result<U, F | E>// Data-first
const parseNumber = (str: string): Result<number, string> =>
str === '' ? err('Empty string') : ok(Number(str))
flatMap(ok('42'), parseNumber) // => Ok(42)
flatMap(ok(''), parseNumber) // => Err('Empty string')
flatMap(err('fail'), parseNumber) // => Err('fail')
// Data-last (in pipe) - chaining operations
pipe(
tryCatch(() => readFile('config.json')),
flatMap(str => tryCatch(() => JSON.parse(str))),
flatMap(config => validateConfig(config)),
map(config => config.port)
)See also map, flatten.
flatten
Flattens a nested Result one level.
Converts Result<Result<T, E>, E> to Result<T, E>.
In a pipe, write flatten(). A bare flatten runs correctly but loses the element type.
function flatten<T, E>(): <F>(result: Result<Result<T, E>, F>) => Result<T, E | F>function flatten<T, E, F>(result: Result<Result<T, E>, F>): Result<T, E | F>// Data-first
flatten(ok(ok(42))) // => Ok(42)
flatten(ok(err('inner'))) // => Err('inner')
flatten(err('outer')) // => Err('outer')
// Data-last (in pipe)
pipe(
ok(ok(42)),
flatten()
) // => Ok(42)See also flatMap.
fromNullable
Converts a nullable value to a Result.
If the value is not null or undefined, returns Ok with the value. Otherwise, returns Err with the provided error.
function fromNullable<T, E>(value: T | null | undefined, error: E): Result<T, E>function fromNullable<E>(error: E): <T>(value: T | null | undefined) => Result<T, E>// Data-first
fromNullable(42, 'No value') // => Ok(42)
fromNullable(null, 'No value') // => Err('No value')
fromNullable(undefined, 'No value') // => Err('No value')
// Data-last (in pipe)
pipe(
users.find(u => u.id === id),
fromNullable('User not found'),
map(user => user.name)
)
// With a config lookup
pipe(
config.get('apiKey'),
fromNullable('Missing API key in config')
)isErr
Type guard to check if a Result is Err.
Narrows the type to Err<E>, allowing safe access to the error property.
function isErr<T, E>(result: Result<T, E>): result is Err<E>const result: Result<number, string> = err('Failed')
if (isErr(result)) {
console.log(result.error) // TypeScript knows this is safe
}isOk
Type guard to check if a Result is Ok.
Narrows the type to Ok<T>, allowing safe access to the value property.
function isOk<T, E>(result: Result<T, E>): result is Ok<T>const result: Result<number, string> = ok(42)
if (isOk(result)) {
console.log(result.value) // TypeScript knows this is safe
}map
Maps over the Ok value of a Result.
If the Result is Ok, applies the function to its value and returns a new Ok. If the Result is Err, returns the Err unchanged.
This is the functor map operation for Result.
function map<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E>function map<T, U>(fn: (value: T) => U): <E>(result: Result<T, E>) => Result<U, E>// Data-first
map(ok(5), x => x * 2) // => Ok(10)
map(err('fail'), (x: number) => x * 2) // => Err('fail')
// Data-last (in pipe)
pipe(
ok(5),
map(x => x * 2),
map(x => x + 1)
) // => Ok(11)See also mapErr, flatMap.
mapErr
Maps over the Err value of a Result.
If the Result is Err, applies the function to its error and returns a new Err. If the Result is Ok, returns the Ok unchanged.
Useful for transforming or enriching error values.
function mapErr<T, E, F>(result: Result<T, E>, fn: (error: E) => F): Result<T, F>function mapErr<E, F>(fn: (error: E) => F): <T>(result: Result<T, E>) => Result<T, F>// Data-first
mapErr(err('fail'), e => `Error: ${e}`) // => Err('Error: fail')
mapErr(ok(5), e => `Error: ${e}`) // => Ok(5)
// Data-last (in pipe)
pipe(
parseJSON(str),
mapErr(e => ({
code: 'PARSE_ERROR',
message: e.message,
timestamp: Date.now()
}))
)See also map.
match
Pattern matches on a Result, providing handlers for both cases.
This is a more functional alternative to if/else or switch statements. Both handlers must return the same type.
function match<T, E, U>(result: Result<T, E>, handlers: { readonly onOk: (value: T) => U; readonly onErr: (error: E) => U; }): Ufunction match<T, E, U>(handlers: { readonly onOk: (value: T) => U; readonly onErr: (error: E) => U; }): (result: Result<T, E>) => U// Data-first
match(
parseNumber('42'),
{
onOk: n => `Success: ${n}`,
onErr: e => `Error: ${e}`
}
) // => 'Success: 42'
// Data-last (in pipe)
pipe(
fetchUser(id),
match({
onOk: user => renderUser(user),
onErr: error => renderError(error)
})
)
// With different handler types
const getStatus = match({
onOk: () => 'success' as const,
onErr: () => 'error' as const
})ok
Creates a successful Result containing a value.
function ok<T>(value: T): Ok<T>const result = ok(42)
// => { _tag: 'Ok', value: 42 }orThrow
Converts a Result-returning async function into a throwing variant.
This is a higher-order function that wraps any async function returning
Promise<Result<T, E>> to instead return Promise<T> and throw on errors.
Use this to create *OrThrow variants without duplicating logic.
Pass a function with a single signature: an overloaded function such as
mapAsync types as never, so pass its curried form instead.
function orThrow<F extends (...args: any[]) => Promise<Result<any, any>>>(fn: F): OrThrowVersion<F>import { mapAsync } from '@ontologyio/receta/async'
import { orThrow } from '@ontologyio/receta/result'
// Create throwing variant
const fetchAllOrThrow = orThrow(
mapAsync(async (url: string) => fetch(url))
)
// Use it
try {
const results = await fetchAllOrThrow(urls)
console.log(results)
} catch (error) {
console.error('Failed:', error)
}See also unwrap.
parseInt
Safely parses a string into an integer with optional radix.
Unlike the global parseInt() which can return NaN or parse partial strings, this function returns a Result with strict validation:
- Must be a valid integer
- No trailing non-numeric characters allowed
- Radix must be between 2 and 36
function parseInt(str: string, radix?: number): Result<number, ParseNumberError>Fails with ParseNumberError.
import { parseInt } from '@ontologyio/receta/result'
// Decimal (default radix 10)
parseInt('123')
// => Ok(123)
parseInt('-42')
// => Ok(-42)
parseInt('0')
// => Ok(0)
// Binary (radix 2)
parseInt('1010', 2)
// => Ok(10)
parseInt('11111111', 2)
// => Ok(255)
// Hexadecimal (radix 16)
parseInt('FF', 16)
// => Ok(255)
parseInt('0x1A', 16)
// => Ok(26)
// Octal (radix 8)
parseInt('77', 8)
// => Ok(63)
// Failed parsing - not a number
parseInt('abc')
// Err with reason 'not_a_number'
// Failed parsing - has decimal point
parseInt('123.45')
// Err with reason 'invalid_integer'
// Failed parsing - invalid radix
parseInt('123', 1)
// Err with reason 'out_of_radix_range'
parseInt('123', 37)
// Err with reason 'out_of_radix_range'
// Use with pattern matching
pipe(
parseInt('FF', 16),
match({
onOk: (n) => `Decimal: ${n}`,
onErr: (e) => `Error: ${e.message}`
})
)
// => 'Decimal: 255'See also parseNumber.
parseJSON
Safely parses a JSON string into a typed value.
Unlike JSON.parse which throws on invalid input, this function returns a Result that either contains the parsed value or a SyntaxError.
function parseJSON<T = unknown>(str: string): Result<T, SyntaxError>Fails with SyntaxError.
// Successful parsing
parseJSON('{"name":"John"}')
// => Ok({ name: 'John' })
parseJSON('[1, 2, 3]')
// => Ok([1, 2, 3])
// Failed parsing
parseJSON('invalid json')
// Err(SyntaxError)
parseJSON('{"incomplete":')
// Err(SyntaxError)
// With type annotation
interface User {
name: string
age: number
}
const result = parseJSON<User>('{"name":"Alice","age":30}')
// => Ok({ name: 'Alice', age: 30 })
// Use with pipe and map
pipe(
parseJSON<User>('{"name":"Bob","age":25}'),
map(user => user.name.toUpperCase())
)
// => Ok('BOB')parseNumber
Safely parses a string into a number.
Unlike Number() which can produce NaN or Infinity, this function returns a Result that either contains a valid finite number or a descriptive error.
function parseNumber(str: string): Result<number, ParseNumberError>Fails with ParseNumberError.
// Successful parsing
parseNumber('123')
// => Ok(123)
parseNumber('3.14')
// => Ok(3.14)
parseNumber('-42.5')
// => Ok(-42.5)
parseNumber('0')
// => Ok(0)
// Scientific notation
parseNumber('1.5e10')
// => Ok(15000000000)
// Failed parsing - not a number
parseNumber('abc')
// Err with reason 'not_a_number'
parseNumber('')
// Err with reason 'not_a_number'
// Failed parsing - infinite
parseNumber('Infinity')
// Err with reason 'infinite'
parseNumber('-Infinity')
// Err with reason 'infinite'
// Use in validation pipelines
pipe(
parseNumber('42'),
flatMap(n => n > 0 ? ok(n) : err('Must be positive'))
)
// => Ok(42)See also parseInt.
partition
Separates an array of Results into Ok values and Err values.
Returns a tuple of [okValues, errValues].
In a pipe, write partition(). A bare partition runs correctly but loses the element type.
function partition<T>(): <E>(results: readonly Result<T, E>[]) => [T[], E[]]function partition<T, E>(results: readonly Result<T, E>[]): [T[], E[]]const results = [
ok(1),
err('fail1'),
ok(2),
err('fail2'),
ok(3)
]
partition(results)
// => [[1, 2, 3], ['fail1', 'fail2']]
// Practical use case: bulk validation
const [validUsers, errors] = pipe(
rawUsers,
R.map(validateUser),
partition()
)
if (errors.length > 0) {
console.log('Validation errors:', errors)
}
console.log('Valid users:', validUsers)See also collect.
tap
Performs a side effect with the Ok value without modifying the Result.
Useful for logging, debugging, or performing side effects in a pipeline while keeping the Result unchanged.
function tap<T, E>(result: Result<T, E>, fn: (value: T) => void): Result<T, E>function tap<T>(fn: (value: T) => void): <E>(result: Result<T, E>) => Result<T, E>// Data-first
tap(ok(42), n => console.log('Got:', n)) // logs 'Got: 42', returns Ok(42)
tap(err('fail'), n => console.log(n)) // no log, returns Err('fail')
// Data-last (in pipe) - debugging
pipe(
fetchUser(id),
tap(user => console.log('Fetched user:', user.name)),
map(user => user.email),
tap(email => console.log('Email:', email))
)See also tapErr.
tapErr
Performs a side effect with the Err value without modifying the Result.
Useful for error logging or monitoring while keeping the error flowing through the pipeline.
function tapErr<T, E>(result: Result<T, E>, fn: (error: E) => void): Result<T, E>function tapErr<E>(fn: (error: E) => void): <T>(result: Result<T, E>) => Result<T, E>// Data-first
tapErr(ok(42), e => console.error(e)) // no log, returns Ok(42)
tapErr(err('fail'), e => console.error('Error:', e)) // logs, returns Err('fail')
// Data-last (in pipe) - error monitoring
pipe(
parseConfig(str),
tapErr(error => {
logError(error)
sendToMonitoring(error)
}),
mapErr(error => 'Using default config due to error'),
unwrapOr(defaultConfig)
)See also tap.
tryCatch
Wraps a potentially throwing function in a Result.
If the function executes successfully, returns Ok with the value. If it throws, returns Err with the error. Wraps a potentially throwing function in a Result with custom error mapping.
function tryCatch<T>(fn: () => T): Result<T, unknown>function tryCatch<T, E>(fn: () => T, mapError: (error: unknown) => E): Result<T, E>const parseJSON = (str: string) =>
tryCatch(() => JSON.parse(str))
parseJSON('{"name":"John"}')
// => Ok({ name: 'John' })
parseJSON('invalid json')
// Err(SyntaxError)const parseNumber = (str: string) =>
tryCatch(
() => {
const n = Number(str)
if (Number.isNaN(n)) throw new Error('Invalid number')
return n
},
(e) => `Parse error: ${e}`
)tryCatchAsync
Wraps an async function in a Result, catching any errors. Wraps an async function in a Result with custom error mapping.
function tryCatchAsync<T>(fn: () => Promise<T>): Promise<Result<T, unknown>>function tryCatchAsync<T, E>(fn: () => Promise<T>, mapError: (error: unknown) => E): Promise<Result<T, E>>const fetchUser = async (id: string) =>
tryCatchAsync(async () => {
const res = await fetch(`/api/users/${id}`)
return res.json()
})
const result = await fetchUser('123')
// Ok({ name: 'John' }) or Err(NetworkError)const fetchUser = async (id: string) =>
tryCatchAsync(
async () => {
const res = await fetch(`/api/users/${id}`)
if (!res.ok) throw new Error(`HTTP ${res.status}`)
return res.json()
},
(e) => ({ code: 'FETCH_ERROR', message: String(e) })
)unwrap
Extracts the value from an Ok Result or throws the error.
Warning: This function throws if the Result is Err.
Use unwrapOr or unwrapOrElse for safer alternatives.
function unwrap<T, E>(result: Result<T, E>): Tunwrap(ok(42)) // => 42
unwrap(err('fail')) // throws 'fail'See also unwrapOr, unwrapOrElse.
unwrapOr
Extracts the value from an Ok Result or returns a default.
function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): Tfunction unwrapOr<T>(defaultValue: T): <E>(result: Result<T, E>) => T// Data-first
unwrapOr(ok(42), 0) // => 42
unwrapOr(err('fail'), 0) // => 0
// Data-last (in pipe)
pipe(
parseNumber('abc'),
unwrapOr(0)
) // => 0See also unwrapOrElse.
unwrapOrElse
Extracts the value from an Ok Result or computes a fallback from the error.
Unlike unwrapOr, this allows you to compute the default value lazily
and access the error for decision-making.
function unwrapOrElse<T, E>(result: Result<T, E>, fn: (error: E) => T): Tfunction unwrapOrElse<T, E>(fn: (error: E) => T): (result: Result<T, E>) => T// Data-first
unwrapOrElse(ok(42), () => 0) // => 42
unwrapOrElse(err('fail'), e => {
console.error(e)
return 0
}) // logs 'fail', returns 0
// Data-last (in pipe)
pipe(
fetchUser(id),
unwrapOrElse(error => {
logError(error)
return guestUser
})
)See also unwrapOr.
Types
Err
Represents a failed computation containing an error.
interface Err<E> {
readonly _tag: 'Err'
readonly error: E
}Ok
Represents a successful computation containing a value.
interface Ok<T> {
readonly _tag: 'Ok'
readonly value: T
}ParseNumberError
Error returned when number parsing fails.
interface ParseNumberError {
readonly _tag: 'ParseNumberError'
readonly input: string
readonly reason: 'not_a_number' | 'infinite' | 'invalid_integer' | 'out_of_radix_range'
readonly message: string
}Result
Result type representing either success (Ok) or failure (Err).
type Result<T, E> = Ok<T> | Err<E>type ParseResult = Result<number, string>
const success: ParseResult = { _tag: 'Ok', value: 42 }
const failure: ParseResult = { _tag: 'Err', error: 'Invalid number' }