npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@patina/core

v3.0.0

Published

Type-safe nothing-handling and error-handling library

Downloads

471

Readme

Patina

  • error- and nothing-handling library for TypeScript
  • inspired by Rust's Result and Option types
  • utilities for composing functions that return errors and interacting with code that throws errors
  • no dependencies

Table of contents

Installation

CommonJS and ESM modules are available.

npm install @patina/core

Usage

import {
	Result,
	Ok,
	Err,
	Option,
	Some,
	AsyncResult,
	None,
	asyncFn,
	Panic,
	ErrorWithCause,
} from "@patina/core";
import {db} from "./db";

function divide(a: number, b: number): Result<number, Error> {
	if (b === 0) {
		return Err(new Error("division by zero"));
	}
	return Ok(a / b);
}

// You do not have to use `namespace` pattern, but I find it useful to group errors and their mappers together.
namespace DatabaseError {
	export class Unreachable extends ErrorWithCause<Error> {
		readonly tag = "DatabaseError.Unreachable";
	}

	export class ValidationError extends ErrorWithCause<Error> {
		readonly tag = "DatabaseError.ValidationError";
	}

	export function from(error: Error): DatabaseError {
		if (error.message === "validation error") {
			return new ValidationError(error.message, {cause: error});
		}
		if (error.message === "unreachable") {
			return new Unreachable(error.message, {cause: error});
		}
		// Add more error variants here, for now we panic if we encounter an unknown error
		throw new Panic("unhandled database error", {cause: error});
	}
}
export type DatabaseError = DatabaseError.ValidationError | DatabaseError.Unreachable;

// Chain API example:
function findGradesByStudentId(id: string): AsyncResult<Option<number[]>, DatabaseError> {
	return Result.fromPromise(db.findGradesByStudentId(id))
		.map((grades) => (grades ? Some(grades) : None))
		.mapErr(DatabaseError.from);
}

// Or you can use `asyncFn` to wrap functions that return `Promise<Result<T, E>>` to convert return type to `AsyncResult<T, E>`
// Inferred type is `(studentId: string) => AsyncResult<number, Error>`
const getAverageGrade = asyncFn(async (studentId: string) => {
	const grades = await findGradesByStudentId(studentId)
		.andThen((maybeGrades) => {
			return maybeGrades.match({
				Some: (grades) => {
					if (grades.length === 0) {
						return Err(new Error("grades not found"));
					}
					return Ok(grades);
				},
				None: () => {
					// Map to error if grades not found
					return Err(new Error("grades not found"));
				},
			});
		})
		.mapErr(() => {
			// Map to generic error from database error, or handle each variant
			return new Error("failed to get grades");
		});

	// Check and return error
	if (grades.isErr()) {
		return Err(grades.unwrapErr());
	}

	// Safe to unwrap because we checked for error
	const value = grades.unwrap();
	return divide(
		value.reduce((a, b) => a + b, 0),
		value.length,
	);
});

Panic

Panic is a special error that extends native Error and represents a panic condition. It is used to indicate that a function has failed in an unrecoverable way. The throw statement should only be used to raise exceptions that represent a bug detected in your program.

On the other hand, Result can represent either the successful outcome of some operation, Ok<T>, or an expected runtime error, Err<E>. Result types are used alongside user-defined error types that represent the various anticipated runtime failure modes that the associated operation might encounter. Unlike exceptions, Result types must be explicitly handled and propagated by the developer.

The utilities of this library are designed to differentiate between recoverable errors and panics. For example thrown Panics will not be caught:

function panicOrError() {
	const rand = Math.random();
	if (rand > 0.5) {
		throw new Panic("random panic");
	}
	return rand;
}

// Will not catch the panic
const result = Result.from(() => {
	panicOrError();
});

Result

Result is a type that represents either success (Ok) or failure (Err).

Result<T, E> is the type used for returning errors. It is a discriminated union with the variants, Ok<T>, representing success and containing a value, and Err<E>, representing error and containing an error value.

Functions return Result whenever errors are expected and recoverable.

.fromThrowable(f: Function)

Tries to execute a function and returns the result as a Result.

const result = Result.from(() => {
	if (Math.random() > 0.5) {
		return 42;
	} else {
		throw new Error("random error");
	}
});

.fromPromise(promise: Promise)

Tries to resolve a promise and returns the result as a AsyncResult.

// AsyncResult<number, Error>
const result = Result.fromPromise(Promise.resolve(42));

.and(other: Result)

Returns other if the result is Ok, otherwise returns this (as Err).

.andThen(f: Function)

Calls f if the result is Ok, otherwise returns this (as Err).

let x: Result<number, string> = Ok(2);
assert.deepStrictEqual(x.andThen((n) => Ok(n * 2)).unwrap(), 4);

let y: Result<string, string> = Err("late error");
assert.deepStrictEqual(y.andThen((n) => Ok(n * 2)).unwrapErr(), "late error");

.err()

Returns None if the result is Ok, otherwise returns Some containing the error.

.expect(message: string)

Returns the contained Ok value.

Throws Panic if the value is an Err, with a message containing message and content of the Err as cause.

const x = Err("emergency failure");
x.expect("Testing expect"); // throws Panic: Testing expect

.expectErr(message: string)

Returns the contained Err value.

Throws Panic if the value is an Ok, with a message containing message and content of the Ok as cause.

const x = Ok(2);
x.expectErr("Testing expectErr"); // throws Panic: Testing expectErr

.flatten()

Converts from Result<Result<U, F>, E> to Result<U, E | F>.

.inspect(f: Function)

Calls the provided function with the contained value (if Ok).

const x = Ok(2);
x.inspect((v) => console.log(v));

.inspectErr(f: Function)

Calls the provided function with the contained error (if Err).

const x = Err("error");
x.inspectErr((e) => console.error(e));

.isErr()

Returns true if the result is an Err, and narrows the type to Err.

.isOk()

Returns true if the result is an Ok, and narrows the type to Ok.

.map(f: Function)

Maps a Result<T, E> to Result<U, E> by applying a function to a contained Ok value, leaving an Err value untouched.

const x = Ok(10);
const mapped = x.map((n) => `number ${n}`);
assert.strictEqual(mapped.unwrap(), "number 10");

.mapErr(f: Function)

Maps a Result<T, E> to Result<T, F> by applying a function to a contained Err value, leaving an Ok value untouched.

const x = Err("error");
const mapped = x.mapErr((s) => s.length);
assert.strictEqual(mapped.unwrapErr(), 5);

.mapOr(defaultValue: T, f: Function)

Returns the provided default (if Err), or applies a function to the contained value (if Ok).

const x: Result<string, string> = Ok("foo");
assert.strictEqual(
	x.mapOr(42, (v) => v.length),
	3,
);

const y: Result<string, string> = Err("bar");
assert.strictEqual(
	y.mapOr(42, (v) => v.length),
	42,
);

.mapOrElse(defaultValue: Function, f: Function)

Maps a Result<T, E> to A | B by applying fallback function defaultValue to a contained Err value, or function f to a contained Ok value.

const k = 21;

let x: Result<string, string> = Ok("foo");
assert.strictEqual(
	x.mapOrElse(
		() => k * 2,
		(v) => v.length,
	),
	3,
);

x = Err("bar");
assert.strictEqual(
	x.mapOrElse(
		() => k * 2,
		(v) => v.length,
	),
	42,
);

.ok()

Returns Some if the result is Ok, otherwise returns None.

.or(other: Result)

Returns other if the result is Err, otherwise returns this (as Ok).

let x: Result<number, string> = Ok(2);
let y: Result<number, string> = Err("late error");
assert.deepStrictEqual(x.or(y).unwrap(), 2);
assert.deepStrictEqual(y.or(x).unwrap(), 2);

.orElse(f: Function)

Calls f if the result is Err, otherwise returns this (as Ok).

let x: Result<number, string> = Ok(2);
assert.deepStrictEqual(x.orElse((e) => Err(e + "bar")).unwrap(), 2);

let y: Result<number, string> = Err("foo");
assert.deepStrictEqual(y.orElse((e) => Err(e + "bar")).unwrapErr(), "foobar");

.unwrap()

Returns the contained Ok value or undefined.

This works differently from Rust's unwrap method, which panics if the value is an Err. Since TypeScript compiler is not able to enforce that a value is checked for error before unwrapping, this method is safer to use.

let x: Result<number, string> = Ok(2);
let value = x.unwrap(); // `value` type is `2 | undefined`
if (x.isOk()) {
	value = x.unwrap(); // `value` type is `2`
} else {
	value = x.unwrap(); // `value` type is `undefined`
}

.unwrapErr()

Returns the contained Err value or undefined.

This works differently from Rust's unwrap_err method, which panics if the value is an Ok. Since TypeScript compiler is not able to enforce that a value is checked for error before unwrapping, this method is safer to use.

let x: Result<number, string> = Err("failure");
let value = x.unwrapErr(); // `value` type is `"failure" | undefined`
if (x.isErr()) {
	value = x.unwrapErr(); // `value` type is `"failure"`
} else {
	value = x.unwrapErr(); // `value` type is `undefined`
}

.unwrapOr(defaultValue: T)

Returns the contained Ok value or defaultValue.

let x: Result<number, string> = Ok(2);
assert.strictEqual(x.unwrapOr(0), 2);

let y: Result<number, string> = Err("error");
assert.strictEqual(y.unwrapOr(0), 0);

.unwrapOrElse(f: Function)

Returns the contained Ok value or the result of a function.

let x: Result<number, string> = Ok(2);
assert.strictEqual(
	x.unwrapOrElse(() => 0),
	2,
);

let y: Result<number, string> = Err("error");
assert.strictEqual(
	y.unwrapOrElse(() => 0),
	0,
);

.match(matcher: Matcher)

Matches a Result<T, E> to A | B by applying a matcher object. Similar to Rust's match statement.

const x: Result<number, string> = Ok(2);
assert.strictEqual(
	x.match({
		Ok: (v) => v * 2,
		Err: (e) => e.length,
	}),
	4,
);

const y: Result<number, string> = Err("error");
assert.strictEqual(
	y.match({
		Ok: (v) => v * 2,
		Err: (e) => e.length,
	}),
	5,
);

AsyncResult

AsyncResult is a type that represents either success (Ok) or failure (Err) of an asynchronous operation.

It implements the Promise interface, so you can use it as a drop-in replacement for promises.

The same methods are available on AsyncResult as on Result.

Methods on both Result and AsyncResult have async versions that accept a function that return a Promise, e.g. mapAsync, inspectAsync andThenAsync, etc. These methods are useful for chaining asynchronous operations and will turn a Result into an AsyncResult.

Option

Similar methods are available on Option as on Result.

.fromNullish(value: T)

Returns Some if the value is not null or undefined, otherwise returns None.

const x = Option.fromNullish(42);
assert.deepStrictEqual(x.unwrap(), 42);

const y = Option.fromNullish(null);
assert.deepStrictEqual(y, None);

const z = Option.fromNullish(undefined);
assert.deepStrictEqual(z, None);

AsyncOption

AsyncOption is a type that represents either a value (Some) or nothing (None) of an asynchronous operation.

Utilities

isResult(value: any): value is Result

Returns true if value is an instance of Result.

isAsyncResult(value: any): value is AsyncResult

Returns true if value is an instance of AsyncResult.

isOption(value: any): value is Option

Returns true if value is an instance of Option.

isAsyncOption(value: any): value is AsyncOption

Returns true if value is an instance of AsyncOption.

asyncFn

Wraps a function that returns any shape of Promise<Result<any, any>> and wraps the return value in a AsyncResult.

// (a: number, b: number) => Promise<Err<string> | Ok<number>>
const divide = async (a: number, b: number) => (b === 0 ? Err("division by zero") : Ok(a / b));

// (a: number, b: number) => AsyncResult<number, string>
const wrapped = asyncFn(divide);

// then you can await the result
const result = await wrapped(1, 2); // => Result<number, string>

tryBlock

Creates a scope where you can use yield* and try() together to unwrap or propagate errors from a Result. This is trying to emulate Rust's try_blocks and ? operator. Only works with synchronous blocks, if you need to use asynchronous operations, use tryBlockAsync instead.

const result = tryBlock(function* () {
	const x = yield* Ok(1).try();
	const y = yield* Ok(2).try();
	return Ok(x + y);
});
assert.equal(result.unwrap(), 3);

tryBlockAsync

Creates a scope where you can use yield* and try() together to unwrap or propagate errors from a Result or AsyncResult. This is trying to emulate Rust's try_blocks and ? operator.

const asyncNumber = new AsyncResult(Promise.resolve(Ok(2)));

const result = await tryBlockAsync(async function* () {
	const x = yield* Ok(1).try();
	const y = yield* asyncNumber.try();
	return Ok(x + y);
});
assert.equal(result.unwrap(), 3);

Similar Libraries

Unfortunately, there are no native TypeScript statements or expression that can help you deal with or propagate errors and you can only go so far with wrapping try and catch. Writing code with this library will look very different to what you're used to. Although it may seem verbose, it is more explicit and safer, but on the other hand it may not be straightforward to use and sometimes you can find yourself fighting the type system.

If you find this library lacking, you may want to check out similar libraries: