Skip to content

Latest commit

 

History

History
853 lines (607 loc) · 21.6 KB

README.md

File metadata and controls

853 lines (607 loc) · 21.6 KB

this-is-ok

An ergonomic way to work with fallible operations in TypeScript.

API Reference · Report Bug · Request Feature · Road map


Why this library?

  • ✨ Fully type-safe, and ergonomic API that uses popular conventions.
  • 🎉 Comes with a clean and easy to use way to simulate the do-notation.
  • ⚡️ Tree-shakable, works with esm, cjs, and doesn't force you to use nodenext/node16 module resolution.

Getting started

Installation

npm install this-is-ok

yarn add this-is-ok

pnpm add this-is-ok

Basic usage

// an example of adapting a standard library function to use Option
const safeParseInt = (value: string): Option<number> => {
  const parsed = parseInt(value);
  if (isNaN(parsed)) {
    return none;
  }
  return some(parsed);
};

// `of` from the option module creates an Option from a nullable value
of(localStorage.getItem("counter"))
  // map the inner value to another Option
  .flatMap((d) => safeParseInt(d))
  // map the inner value or use a default value if it's none
  .mapOr(0, (d) => d + 1)
  // perform any side effect with the value
  .tap((d) => localStorage.setItem("counter", d.toString()));

do notation

Do notation lets us write the "happy path" of a function without having to worry about the error handling. We access the inner values of options by calling bind on them, and if any point, inside the do block, bind is called on none then we short-circuit the execution and none is returned.

some(1)
  .do((value) => {
    const a = some(2).bind();
    const b = some("hello").bind();
    return some(b.repeat(value + a));
  })
  .tap(console, log); // "hellohellohello"

some(1).do((value) => {
  const a = value; // 1

  const b = some(2).bind(); // 2

  const c = (none as Option<number>).bind();
  // the execution stops here and none is returned
  // so the following lines are not executed

  const d = some(3).bind();
  return some(a + b + c + d);
}).isNone; // true

Api Reference

Option

Creation

of

Converts a nullable value into an option.

Example:

of(1); // some(1)
of(null); // none
of(undefined); // none

from

Calls a function and converts its result into an option. If the function is null or undefined or it throws an exception, none is returned.

Example:

from(() => 1); // some(1)
from(() => null); // none
from(() => {
  throw new Error("oopsie hehe");
}); // none

Methods

isSome: boolean

Property that is true if the option is a Some variant and false otherwise.

Example:

some(3).isSome; // true
none.isSome; // false

isSomeAnd: (predicate: (value: T) => boolean) => boolean

Returns true if the option is a Some variant and the value inside of it matches a predicate.

Example:

expect(some(42).isSomeAnd((v) => v % 2)).toBe(true);
expect(some(42).isSomeAnd((v) => v > 100)).toBe(true);

isNone: boolean

Property that is true if the option is a None variant and false otherwise.

Example:

expect(none.isNone).toBe(true);
expect(some(42).isNone).toBe(false);

expect: (message: string) => T

Returns the inner value. Useful for situations where if the option is a None then there is no tap to continue running the program. The message can be used for debugging purposes.

Parameters:

  • message - The message to throw if the option is a None variant.

Throws:

Throws the given message if the option is a None variant.

Example:

expect(some(42).expect("should be a number")).toBe(42);
expect(none.expect("this will throw")).toThrow();

unwrap: () => T

Returns the inner value or throws an Error if the option is a None variant.

Throws:

Throws if the option is a None variant.

Example:

expect(some(42).unwrap()).toBe(42);
expect(none.unwrap()).toThrow();

unwrapOr: (defaultValue: T) => T

Returns the inner value or the provided default value.

Example:

expect(some(42).unwrapOr(1)).toBe(42);
expect(none.unwrapOr(1)).toBe(1);

unwrapOrElse: (defaultValueFun: () => T) => T

Returns the inner value or computes it from a function.

Example:

expect(some(42).unwrapOrElse(() => 1)).toBe(42);
expect(none.unwrapOrElse(() => 1)).toBe(1);

map: <U>(f: (value: T) => U) => Option<U>

Maps an Option<T> to Option<U> by applying a function to a contained value (if Some) or returns None (if None).

Example:

expect(
  some(42)
    .map((x) => x + 1)
    .unwrap()
).toBe(43);
expect(none.map((x) => x + 1).isNone()).toBe(true);

mapOr: <U>(defaultValue: U, f: (value: T) => U) => U

Returns the provided default result (if none), or applies a function to the contained value (if any).

Example:

expect(
  some(42)
    .mapOr(10, (x) => x + 1)
    .unwrap()
).toBe(43);
expect(none.mapOr(10, (x) => x + 1).unwrap()).toBe(11);

mapOrElse: <U>(defaultValueFun: () => U, f: (arg: T) => U) => U

Computes a default function result (if none), or applies a different function to the contained value (if any).

Example:

expect(
  some(42)
    .mapOrElse(
      () => 10,
      (x) => x + 1
    )
    .unwrap()
).toBe(43);
expect(
  none
    .mapOrElse(
      () => 10,
      (x) => x + 1
    )
    .unwrap()
).toBe(11);

okOr: <E>(err: E) => Result<T, E>

Given an error: E converts Option<T> to Result<T, E>.

Example:

some(42).okOr("error"); // { variant: "ok", value: 42, ... }
none.okOr("error"); // { variant: "error", error: "error", ... }

okOrElse: <E>(f: () => E) => Result<T, E>

Same as okOr but the error is computed from a function.

and: <U>(b: Option<U>) => Option<U>

Returns None if the option is None, otherwise returns b.

Example:

expect(some(42).and(some(1)).unwrap()).toBe(1);
expect(none.and(some(1)).is

None()).toBe(true);

or: (b: Option<T>) => Option<T>

Returns None if the option is None, otherwise returns b.

Example:

expect(some(42).or(some(1)).unwrap()).toBe(43);
expect(none.or(some(1)).unwrap()).toBe(1);

orElse: (f: () => Option<T>) => Option<T>

Returns the option if it contains a value, otherwise calls f and returns the result.

Example:

expect(
  some(42)
    .orElse(() => some(1))
    .unwrap()
).toBe(42);
expect(none.orElse(() => some(1)).unwrap()).toBe(1);

flatMap: <U>(f: (value: T) => Option<U>) => Option<U>

Returns None if the option is None, otherwise calls f with the wrapped value and returns the result.

Example:

expect(
  some(42)
    .flatMap((x) => some(x + 1))
    .unwrap()
).toBe(43);
expect(
  some(42)
    .flatMap((x) => none)
    .isNone()
).toBe(true);

andThen: <U>(f: (value: T) => Option<U>) => Option<U>

Returns None if the option is None, otherwise calls f with the wrapped value and returns the result.

Example:

expect(
  some(42)
    .andThen((x) => some(x + 1))
    .unwrap()
).toBe(43);
expect(
  some(42)
    .andThen((x) => none)
    .isNone()
).toBe(true);

tap: <F extends void | Promise<void>>(f: (value: T) => F) => F

Runs the given void function with the inner value if the option is a Some variant or does nothing if the option is a None variant.

Example:

expect(
  some(42)
    .tap((x) => some(x + 1))
    .unwrap()
).toBe(43);
expect(
  some(42)
    .tap((x) => none)
    .isNone()
).toBe(true);

filter: (predicate: (arg: T) => boolean) => Option<T>

Returns None if the option is None, otherwise returns None if the predicate predicate returns false when applied to the contained value, otherwise returns the Option<T>.

Example:

expect(
  some(42)
    .filter((x) => x > 10)
    .unwrap()
).toBe(42);
expect(
  some(42)
    .filter((x) => x < 10)
    .isNone()
).toBe(true);

match: <U>(pattern: { some: (value: T) => U; none: () => U }) => U

Allows you to run different functions depending on the variant of the option.

Example:

expect(
  some(3).match({
    some: (d) => d + 1,
    none: () => 0,
  })
).toBe(4);

expect(
  none.match({
    some: (d) => d + 1,
    none: () => 0,
  })
).toBe(0);

do: <U>(f: (value: T) => Option<U>) => Option<U>

Lets you simulate a do-notation known from functional languages with the Option monad.

Example:

expect(
  some(1)
    .do((value) => {
      const a = value;
      const b = some(2).bind();
      const c = some(3).bind();
      return some(a + b + c);
    })
    .unwrap()
).toBe(6);

Result

Creation

of = <T, E>(value: T, error: E): Result<T, E>

Creates a new Result<T, E> from a possibly nullable value and an error.

Example:

expect(of(42, "error").unwrap()).toBe(42);
expect(of({}, "error").unwrap()).toEqual({});
expect(of(NaN, "error").unwrap()).toEqual(NaN);
expect(of("", "error").unwrap()).toEqual("");

expect(of(null, "error").isErr).toBe(true);
expect(of(undefined, "error").isErr).toBe(true);

from = <T, E>(fn: () => T, error: E): Result<T, E>

Creates a new Result<T, E> from a function that can throw and an error.

Example:

expect(from(() => 42, "error").unwrap()).toBe(42);
expect(from(() => ({}), "error").unwrap()).toEqual({});
expect(from(() => NaN, "error").unwrap()).toEqual(NaN);
expect(from(() => "", "error").unwrap()).toEqual("");

expect(from(() => null, "error").isErr).toBe(true);
expect(from(() => undefined, "error").isErr).toBe(true);

expect(
  from(() => {
    throw "error";
  }, "error").isErr
).toBe(true);

<T, E extends Error>(fn: () => T): Result<T, E>

Creates a new Result<T, E> from a function that can throw. If the function returns any value (including null and undefined) it is wrapped in an Ok variant. If the function throws an error it is wrapped in an Err variant. Errors that are not of type Error are wrapped in an Error object.

Example:

expect(fromThrowable(() => 42).unwrap()).toBe(42);
expect(fromThrowable(() => ({})).unwrap()).toEqual({});
expect(fromThrowable(() => NaN).unwrap()).toEqual(NaN);
expect(fromThrowable(() => "").unwrap()).toEqual("");

expect(fromThrowable(() => null).unwrap()).toEqual(null);
expect(fromThrowable(() => undefined).unwrap()).toEqual(undefined);

const a = fromThrowable(() => {
  throw "error";
});

expect(a.unwrapErr()).toEqual(new Error("error"));

const b = fromThrowable(() => {
  throw new Error("error");
});

expect(b.unwrapErr()).toEqual(new Error("error"));

Methods

isOk: boolean

Property that is true if the result is Ok.

Example:

expect(ok(42).isOk).toBe(true);
expect(err("error").isOk).toBe(false);

isOkAnd: (predicate: (value: T) => boolean) => boolean

Returns true if the result is an Ok variant and the value inside of it matches a predicate.

Example:

expect(some(42).isSomeAnd((v) => v % 2)).toBe(true);
expect(some(42).isSomeAnd((v) => v > 100)).toBe(true);

isErr: boolean

Property that is true if the result is Err.

Example:

expect(ok(42).isErr).toBe(false);
expect(err("error").isErr).toBe(true);

isErrAnd: (predicate: (value: E) => boolean) => boolean

Returns true if the result is an Err variant and the value inside of it matches a predicate.

Example:

expect(some(42).isSomeAnd((v) => v % 2)).toBe(true);
expect(some(42).isSomeAnd((v) => v > 100)).toBe(true);

ok: () => Option<T>

Converts from Result<T, E> to Option discarding the error, if any.

Example:

expect(ok(42).ok()).toEqual(some(42));
expect(err("error").ok()).toEqual(none);

err: () => Option<T>

Converts from Result<T, E> to Option discarding the error, if any.

Example:

expect(ok(42).ok()).toEqual(some(42));
expect(err("error").ok()).toEqual(none);

expect: (message: string) => T

Returns the inner value. Useful for situations where if the result is an Err then there is no use to continue running the program. The message can be used for debugging purposes.

  • message: The message to throw if the option is an Err variant.

Throws:

  • Throws the given message if the option is an Err variant.

Example:

expect(ok(42).expect("should be a number")).toBe(42);
expect(err("error").expect("this will throw")).toThrow();

unwrap: () => T

Returns the Ok value or throws an Error if the result is an Err variant.

Throws:

  • Throws if the option is a None variant.

Example:

expect(ok(42).unwrap()).toBe(42)
expect(err("error").unwrap().toThrow()

expectErr: (message: string) => T

Returns the contained Err value or throws an error if the result is an Ok variant.

  • message: The message to throw if the option is an Ok variant.

Throws:

  • Throws the given message if the option is an Ok variant.

Example:

expect(err(42).expectErr("should be a number")).toBe(42);
expect(ok(42).expectErr("this will throw")).toThrow();

unwrapErr: () => E

Returns the contained Err value or throws an error if the result is an Ok variant.

Throws:

  • Throws if the option is a None variant.

**Example

:**

expect(err("error").unwrapErr()).toBe(42)
expect(ok(42).unwrapErr().toThrow()

unwrapOr: (defaultValue: T) => T

Returns the Ok value or the provided default value.

  • defaultValue: The default value to return if the result is an Err variant.

Example:

expect(ok(42).unwrapOr(1)).toBe(42)
expect(err("error").unwrapOr(1).toBe(1)

unwrapOrElse: (defaultValueFun: () => T) => T

Returns the Ok value or computes it from a function.

  • defaultValueFun: The function that computes the default value if the result is an Err variant.

Example:

expect(ok(42).unwrapOrElse(() => 1)).toBe(42)
expect(err("error").unwrapOrElse(() => 1).toBe(1)

map: <U>(f: (value: T) => U) => Result<U, E>

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

Example:

expect(
  ok(42)
    .map((x) => x + 1)
    .unwrap()
).toBe(43);
expect(
  err("error")
    .map((x) => x + 1)
    .isNone()
).toBe(true);

mapOr: <U>(defaultValue: U, f: (value: T) => U) => U

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

  • defaultValue: The default value to return if the result is an Err variant.
  • f: The function to apply to the contained value if the result is an Ok variant.

Example:

expect(ok(42).mapOr(10, (x) => x + 1)).toBe(43);
expect(error("error").mapOr(10, (x) => x + 1)).toBe(11);

mapOrElse: <U>(defaultValueFun: () => U, f: (arg: T) => U) => U

Maps a Result<T, E> to U by applying a fallback function default to a contained Err value, or function f to a contained Ok value.

  • defaultValueFun: The fallback function to apply to the contained Err value.
  • f: The function to apply to the contained Ok value.

Example:

expect(ok(42).mapOrElse(() => 10, (x) => x + 1))).toBe(43)
expect(error("error").mapOrElse(() => 10, (x) => x + 1))).toBe(11)

mapErr: <U>(f: (value: E) => U) => Result<T, U>

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

Example:

expect(
  ok(42)
    .map((x) => x + 1)
    .unwrap()
).toBe(43);
expect(
  err("error")
    .map((x) => x + 1)
    .isNone()
).toBe(true);

and: <U>(b: Result<U, E>) => Result<U, E>

Returns res if the result is Ok, otherwise returns the Err value of self.

  • b: The Result value to return if the current result is Ok.

or: <F>(b: Result<T, F>) => Result<T,F>

Returns res if the result is Err, otherwise returns the Ok value of self.

  • b: The Result value to return if the current result is Err.

orElse: <F>(f: () => Result<T, F>) => Result<T, F>

Returns the option if it contains a value, otherwise calls f and returns the result.

  • f: The function to call if the result is an Err variant.

flatMap: <U>(f: (value: T) => Result<U, E>) => Result<U, E>

Returns err if the Result is err, otherwise calls f with the wrapped value and returns the result.

Example:

// same as andThen
expect(okVariant.flatMap((v) => ok(v + 1)).unwrap()).toBe(43);
expect(okVariant.flatMap((v) => err("error")).unwrapErr()).toBe("error");
expect(errVariant.flatMap((v) => ok(v + 1)).unwrapErr()).toBe("error");
expect(errVariant.flatMap((v) => err("error1")).unwrapErr()).toBe("error");

andThen: <U>(f: (value: T) => Result<U, E>) => Result<U, E>

Alias for flatMap.

do: <U, F>(f: (value: T) => Result<U, F>) => Result<U, F>

Similar to do for Option

match: <U>(pattern: { ok: (value: T) => U; err: (value: E) => U }) => U

Similar to match for Option

Road map

  • Task monad for async operations
  • Use github releases
  • Add function syntax and make them curried (e.g. map(maybeNumber, x => x + 1)) apart from method syntax (e.g. maybeNumber.map(x => x + 1))
  • add adapters for common built-in APIs such as Map of fs module.