A Rust inspired results types library for Typescript with static type inference
by @fvdessen
Rizzly lets you handle your application errors with results types instead of exceptions. It is similar to other popular results type libraries like neverthrow and oxide.ts but with much better static type inference. Rizzly is tiny and has zero dependencies.
Let's have a look at an example
import { ok, error } from 'rizzly'
function makeUsername(name: string) {
if (!name) {
return error("empty")
} else if (name.length >= 10) {
return error("too-long", {max: 10})
} else {
return ok(name)
}
}
let res = makeUsername("John Smith")
if (res.ok) {
console.log(res.value)
} else if (res.error === "empty") {
console.error("It's empty :(")
} else if (res.error === "too-long") {
console.log(`Too long ! (max: ${res.cause.max})`)
}- We did not write any type, yet everything is type checked.
- We cannot get the
valueout ofreswithout checkingok(or using a convenience method, let's see later) res.errorcan only be compared with actual, possible error types. Possible error types for a result are always known and can be auto completed by your editorres.causecan hold any data and is also type checked.- We do not need to explicitly tell the possible returned types on a function, they are correctly inferred. The inferance also works with function composition.
import { wrap } from "rizzly"
let res = wrap('parse-error', () => JSON.parse(value))wrap() lets you create results from functions that can throw errors. If the function throws an Error, the exception is put as the .cause
of the result. Otherwise, the returned value is put as the result value.
import { awrap } from "rizzly"
let res = await awrap('network-error', fetch("https://perdu.com"))awrap() lets you create results from functions that return promises. If the promise fails, the result is failed as well and the error is put as the cause. Note that awrap() returns a promise of the result.
let val = doSomething().unwrap()res.unwrap() lets you directly get the value of a result, but throws an Error if the result is failed. The cause of the result is set as the cause of the Error, and the error as its message. Mainly useful in tests where you expect things to go right.
let json = wrap('err', () => JSON.parse(value)).unwrapOr({ data: {} })res.unwrapOr() lets you directly get the value of a result, or a default if the result is failed.
let res = getUsername().map((name) => name.toLowerCase())res.map() lets you change the value of the result without needing to unwrap the value.
let res = getData().mapError((error) => i18n.translate(error))res.mapError() lets you change the value of the error in case of failure. The cause is left untouched.
let res = getData().mapCause((cause) => { errors: [cause] })res.mapCause() lets you alter the cause of the failure. The error name is left untouched.
let res = doManyThings().withError('something failed')res.withError() lets you set the error type of the result in case of failure. The cause is left untouched. This is useful when a
result has many possible errors which you do not care about in a specific context.
let res = doManyThings().withErrorAndCause('something failed', { code: `ERR48321` })res.withErrorAndCause() does the same thing as res.withError() but also sets the cause.
let userName = getUser().match({
ok: (user) => user.userName,
err: (error, cause) => "guest",
})res.match() takes an ok and an err callback that return the value of the match call.
- ok: true
- value: T
The type of a succesfull result
- ok: false
- error: E
- cause: C
The type of a failed result
Ok<T> | Err<E, undefined>
Basic result, without a cause. If you want to specify multiple possible errors, you can put them as a string union.
function doManyThings(): Result<number, "ERROR_A" | "ERROR_B" | "ERROR_C"> {
...
}Ok<T> | Err<E,C>
When you want to specify the cause.