@cardellini/ts-result
v0.3.2
Published
Useful type to model success and failure
Downloads
34
Maintainers
Readme
Result
Useful type to model success and failure, implemented with focus on type safety, developer experience and preserving flat learning curve.
Installation
npm install @cardellini/ts-result
Usage
Creating a Result
import { Result, ok, err } from '@cardellini/ts-result';
type JsonObject = Record<string, unknown>;
const okIfObject = (value: unknown): Result<JsonObject, 'ERR_NOT_AN_OBJECT'> =>
typeof value === 'object' && value !== null && !Array.isArray(value)
? ok(value as JsonObject)
: err('ERR_NOT_AN_OBJECT');
const okIfInt = (value: unknown): Result<number, 'ERR_NOT_AN_INT'> =>
Number.isInteger(value)
? ok(value as number)
: err('ERR_NOT_AN_INT');
const okIfString = (value: unknown): Result<string, 'ERR_NOT_A_STRING'> =>
typeof value === 'string'
? ok(value)
: err('ERR_NOT_A_STRING');
Composing with Do-notation
type Person = {
name: string;
age: number;
}
const okIfPerson = (value: unknown): Result<Person, 'ERR_NOT_A_PERSON'> =>
Do(function*() {
const obj = yield* okIfObject(value);
const name = yield* okIfString(obj.name);
const age = yield* okIfInt(obj.age);
return { name, age };
}).mapErr(() => 'ERR_NOT_A_PERSON');
const person: Person = okIfPerson({ name: 'John', age: 42 }).unwrap();
Composing with chain
const okIfPerson =
(value: unknown) => okIfObject(value).chain(
(obj) => okIfString(obj.name).chain(
(name) => okIfInt(obj.age).chain(
(age) => ok({ name, age })
)));
or the same with map
on the last step:
const okIfPerson =
(value: unknown) => okIfObject(value).chain(
(obj) => okIfString(obj.name).chain(
(name) => okIfInt(obj.age).map(
(age) => ({ name, age })
)));
Note: from the performance perspective, using
chain
is preferable toDo
-notation, becausechain
doesn't create and run generators. However,Do
-notation is more readable and easier to use. Additionally, the formatting of the code in this section requires specific linters and formatters configuration.
Collecting Ok-s from a Result Array
const lordOfTheRingsAuthors = collect([
ok({ id, name: 'J. R. R. Tolkien' }),
ok({ id, name: 'Christopher Tolkien' }),
]);
const silmarillionAuthors = collect([
ok({ id, name: 'J. R. R. Tolkien' }),
err('ERR_PERSON_NOT_FOUND' as const),
]);
console.log(lordOfTheRingsAuthors.unwrap());
// Prints to console:
// [
// { id, name: 'J. R. R. Tolkien' },
// { id, name: 'Christopher Tolkien' }
// ]
console.log(silmarillionAuthors.unwrapErr());
// Prints to console: ERR_PERSON_NOT_FOUND
Working with Async Results
import { asyncDo, collect, err, ok } from '@cardellini/ts-result';
const getBookWithAuthors = (bookId: string) =>
asyncDo(async function* () {
const book = yield* await fetchBook(bookId);
const authors = yield* await fetchPersons(book.authorIds);
return { ...book, authors };
});
const fetchBook = async (id: string) => (
id === '1' ? ok({ id, title: 'The Lord of the Rings', authorIds: ['1', '2'] }) :
id === '2' ? ok({ id, title: 'The Silmarillion', authorIds: ['1', '3'] }) :
err('ERR_BOOK_NOT_FOUND' as const)
);
const fetchPersons = async (ids: string[]) => collect(
ids.map(id => (
id === '1' ? ok({ id, name: 'J. R. R. Tolkien' }) :
id === '2' ? ok({ id, name: 'Christopher Tolkien' }) :
err("ERR_PERSON_NOT_FOUND" as const)
))
);
async function run() {
const LordOfTheRings = await getBookWithAuthors('1');
console.log(LordOfTheRings.unwrap());
// Prints to console book with authors populated
const Silmarillion = await getBookWithAuthors('2');
console.log(Silmarillion.unwrapErr());
// Prints to console: ERR_PERSON_NOT_FOUND
const TheHobbit = await getBookWithAuthors('3');
console.log(TheHobbit.unwrapErr());
// Prints to console: ERR_BOOK_NOT_FOUND
}
run().catch(console.error);
Documentation
[TODO: insert link to documentation]
Result Type
Result<T, E>
is a generic type that represents either success or failure, where:
T
is the type of value that represents success and wrapped withOk<T>
.E
is the type of error that represents failure and wrapped withErr<E>
.
The Result<T, E>
is not a union type, it is an interface with the methods described below,
which is implemented by Ok<T>
and Err<E>
classes.
Methods of Result<T, E>
Instances
isOk()
Returns true
if Result is Ok<T>
, false
otherwise. Narrows the Result<T, E>
to Ok<T>
.
Method Signature:
interface Result<T, E> {
isOk(): this is Ok<T>
}
Function Signature:
const isOk: <T, E>(result: Result<T, E>) => result is Ok<T>
isErr()
Returns true
if Result is Err<E>
, false
otherwise. Narrows the Result<T, E>
to Err<E>
.
Method Signature:
interface Result<T, E> {
isErr(): this is Err<E>
}
Function Signature:
const isErr: <T, E>(result: Result<T, E>): result is Err<E>
map(fn)
Applies fn
to the value of Ok<T>
and returns the value wrapped in Ok<S>
. If Result<T, E>
is Err<E>
returns itself without applying fn
.
Method Signature:
interface Result<T, E> {
map<S>(fn: (data: T) => S): Result<S, E>
}
Curried Function Signature:
const map:
<T, S>(fn: (data: T) => S) =>
<E>(result: Result<T, E>) => Result<S, E>
mapErr(fn)
Applies fn
to the value of Err<E>
and returns the value wrapped in Err<F>
. If Result<T, E>
is Ok<T>
returns itself without applying fn
.
Method Signature:
interface Result<T, E> {
mapErr<F>(fn: (error: E) => F): Result<T, F>
}
Curried Function Signature:
const mapErr:
<E, F>(fn: (error: E) => F) =>
<T>(result: Result<T, E>) => Result<T, F>
chain(next)
Applies next
to the value of Ok<T>
and returns the result of next
. If the Result<T, E>
is Err<E>
, returns itself without applying next
.
Method Signature:
interface Result<T, E> {
chain<S, F>(next: (data: T) => Result<S, F>): Result<T | S, E | F>
}
Curried Function Signature:
const chain:
<T, S, E, F>(next: (data: T) => Result<S, F>) =>
(result: Result<T, E>) => Result<T | S, E | F>
chainErr(next)
Applies next
to the value of Err<E>
and returns the result of next
. If the Result<T, E>
is Ok<T>
, returns itself without applying next
.
Method Signature:
interface Result<T, E> {
chainErr<S, F>(next: (error: E) => Result<S, F>): Result<T | S, E | F>
}
Curried Function Signature:
const chainErr:
<S, E, F>(next: (error: E) => Result<S, F>) =>
<T>(result: Result<T, E>) => Result<T | S, E | F>
unwrap()
Returns the value of Ok<T>
. If the Result<T, E>
is Err<E>
throws a TypeError
where cause
is the Err<E>
.
Method Signature:
interface Result<T, E> {
unwrap(): T
}
Function Signature:
const unwrap: <T>(result: Result<T, unknown>) => T
unwrapGen()
Returns a generator function that yields the value of Err<E>
or returns the value of Ok<T>
.
Method Signature:
interface Result<T, E> {
unwrapGen(): Generator<E, T, unknown>
}
Function Signature:
const unwrapGen: <T, E>(result: Result<T, E>) => Generator<E, T, unknown>
unwrapOr(fallback)
Returns the value of Ok<T>
. If the Result<T, E>
is Err<E>
returns fallback
.
Method Signature:
interface Result<T, E> {
unwrapOr<S>(fallback: S): T | S
}
Curried Function Signature:
const unwrapOr:
<T, S>(fallback: S) =>
(result: Result<T, unknown>) => T | S
unwrapOrThrow()
Returns the value of Ok<T>
. If the Result<T, E>
is Err<E>
throws a value of
type E
.
unwrapOrThrow
doesn't check if E
is an instance of Error
or not, so it is
possible to throw a non-error literal.
Method Signature:
interface Result<T, E> {
unwrapOrThrow(): T
}
Function Signature:
const unwrapOrThrow: <T>(result: Result<T, unknown>) => T
unwrapOrElse
Returns the value of Ok<T>
. If the Result<T, E>
is Err<E>
returns the result of fallbackFn
.
Method Signature:
interface Result<T, E> {
unwrapOrElse<S>(fallbackFn: (error: E) => S): T | S
}
Curried Function Signature:
const unwrapOrElse:
<T, S>(fallbackFn: (error: unknown) => S) =>
(result: Result<T, unknown>) => T | S
unwrapErr
Returns the value of Err<E>
. If the Result<T, E>
is Ok<T>
throws a TypeError
where cause
is the Ok<T>
.
Method Signature:
interface Result<T, E> {
unwrapErr(): E
}
Function Signature:
const unwrapErr: <E>(result: Result<unknown, E>) => E
unwrapErrOr(fallback)
Returns the value of Err<E>
. If the Result<T, E>
is Ok<T>
returns fallback
.
Method Signature:
interface Result<T, E> {
unwrapErrOr<F>(fallback: F): E | F
}
Curried Function Signature:
const unwrapErrOr:
<F>(fallback: F) =>
<T, E>(result: Result<T, E>) => E | F
unwrapErrOrElse(fallbackFn)
Returns the value of Err<E>
. If the Result<T, E>
is Ok<T>
returns the result of fallback
.
Method Signature:
interface Result<T, E> {
unwrapErrOrElse<F>(fallbackFn: (data: T) => F): E | F
}
Curried Function Signature:
const unwrapErrOrElse:
<F, T>(fallbackFn: (data: T) => F) =>
<E>(result: Result<T, E>) => E | F
unpack()
Returns the value of Ok<T>
or Err<E>
.
Method Signature:
interface Result<T, E> {
unpack(): T | E
}
Function Signature:
const unpack: <T, E>(result: Result<T, E>) => T | E
match(okMatcher, errMatcher)
Applies okMatcher
to the value of Ok<T>
and returns the result. Applies errMatcher
to the value of Err<E>
and returns the result.
Method Signature:
interface Result<T, E> {
match<S, F>(okMatcher: (data: T) => S, errMatcher: (error: E) => F): S | F
}
Curried Function Signature:
const match:
<T, S, E, F>(okMatcher: (data: T) => S, errMatcher: (error: E) => F) =>
(result: Result<T, E>) => S | F
tap(fn)
Applies fn
to the value of Ok<T>
and returns the original result. If the Result<T, E>
is Err<E>
doesn't apply fn
.
Method Signature:
interface Result<T, E> {
tap(fn: (data: T) => void): Result<T, E>
}
Curried Function Signature:
const tap:
<T>(fn: (data: T) => void) =>
<E>(result: Result<T, E>) => Result<T, E>
tapErr(fn)
Applies fn
to the value of Err<E>
and returns the original result. If the Result<T, E>
is Ok<T>
doesn't apply fn
.
Method Signature:
interface Result<T, E> {
tapErr(fn: (error: E) => void): Result<T, E>
}
Curried Function Signature:
const tapErr:
<E>(fn: (error: E) => void) =>
<T>(result: Result<T, E>) => Result<T, E>
apply(fnResult)
Applies the function wrapped in Ok<(data: T) => S>
of argument fnResult
to the value of Ok<T>
and returns the result of the application wrapped into Ok<S>
. If the Result<T, E>
is Err<E>
or fnResult
is Err<F>
, returns itself without applying the function.
Method Signature:
interface Result<T, E> {
apply<S, F>(fnResult: Result<(data: T) => S, F>): Result<S, E | F>
}
Curried Function Signature:
const apply:
<T, S, F>(fnResult: Result<(data: T) => S, F>) =>
<E>(result: Result<T, E>) => Result<S, E | F>
Operating on Multiple Results
collect(results)
Collects Ok<T>
values from an array of Result<T, E>
and returns a Result<T[], E>
.
Function Signature:
const collect:
<R extends readonly Result<any, any>[]>(results: R) => Result<Collected<R>, ErrTypeOf<R[number]>>