UNPKG

true-myth

Version:

A library for safe functional programming in JavaScript, with first-class support for TypeScript

1,424 lines (1,184 loc) 71.1 kB
/** A {@linkcode Result Result<T, E>} is a type representing the value result of a synchronous operation which may fail, with a successful value of type `T` or an error of type `E`. If the result is a success, it is {@linkcode Ok Ok(value)}. If the result is a failure, it is {@linkcode Err Err(reason)}. For a deep dive on the type, see [the guide](/guide/understanding/result.md). @module */ import { curry1, identity, safeToString } from './-private/utils.js'; import type { AnyFunction } from './-private/utils.js'; import Unit from './unit.js'; /** Discriminant for {@linkcode Ok} and {@linkcode Err} variants of the {@linkcode Result} type. You can use the discriminant via the `variant` property of `Result` instances if you need to match explicitly on it. */ export const Variant = { Ok: 'Ok', Err: 'Err', } as const; export type Variant = keyof typeof Variant; /** Representation of an {@linkcode Ok} when serialized to JSON. */ export interface OkJSON<T> { variant: 'Ok'; value: T; } /** Representation of an {@linkcode Err} when serialized to JSON. */ export interface ErrJSON<E> { variant: 'Err'; error: E; } /** Representation of a {@linkcode Result} when serialized to JSON. */ export type ResultJSON<T, E> = OkJSON<T> | ErrJSON<E>; /** Representation of a possibly-nested {@linkcode Result} when serialized. */ export type Serialized<T, E> = ResultJSON<FlattenedJSON<T>, FlattenedJSON<E>>; /** Representation of a possibly-nested {@linkcode Result} when serialized. @internal */ export type FlattenedJSON<T> = T extends SomeResult<infer U, infer F> ? ResultJSON<FlattenedJSON<U>, FlattenedJSON<F>> : T; type Repr<T, E> = [tag: 'Ok', value: T] | [tag: 'Err', error: E]; declare const IsResult: unique symbol; /** A convenient way to name `Result<unknown, unknown>`. */ export type AnyResult = Result<unknown, unknown>; export type SomeResult<T, E> = { [IsResult]: [T, E] }; /** @internal */ export type TypesFor<R extends AnyResult> = R extends SomeResult<infer T, infer E> ? { ok: T; err: E } : never; /** @internal */ export type OkFor<R extends AnyResult> = TypesFor<R>['ok']; /** @internal */ export type ErrFor<R extends AnyResult> = TypesFor<R>['err']; // Defines the *implementation*, but not the *types*. See the exports below. class ResultImpl<T, E> { private constructor(private repr: Repr<T, E>) {} /** @internal */ declare readonly [IsResult]: [T, E]; /** Create an instance of {@linkcode Ok}. Note that you may explicitly pass {@linkcode Unit} to the {@linkcode ok} constructor to create a `Result<Unit, E>`. However, you may *not* call the `ok` constructor with `null` or `undefined` to get that result (the type system won't allow you to construct it that way). Instead, for convenience, you can simply call {@linkcode ok `Result.ok()`}, which will construct the type correctly. */ static ok(): Result<Unit, never>; /** @param value The value to wrap in an `Ok`. */ static ok<T, E = never>(value: T): Result<T, E>; static ok<T, E = never>(value?: T): Result<Unit, E> | Result<T, E> { // We produce `Unit` *only* in the case where no arguments are passed, so // that we can allow `undefined` in the cases where someone explicitly opts // into something like `Result<undefined, Blah>`. return arguments.length === 0 ? (new ResultImpl<Unit, E>(['Ok', Unit]) as Result<Unit, E>) : // SAFETY: TS does not understand that the arity check above accounts for // the case where the value is not passed. (new ResultImpl<T, E>(['Ok', value as T]) as Result<T, E>); } /** Create an instance of {@linkcode Err}. ```ts const anErr = Result.err('alas, failure'); ``` */ static err<T = never, E = unknown>(): Result<T, Unit>; /** Create an instance of {@linkcode Err}. ```ts const anErr = Result.err('alas, failure'); ``` @param error The value to wrap in an `Err`. */ static err<T = never, E = unknown>(error: E): Result<T, E>; static err<T = never, E = unknown>(error?: E): Result<T, Unit> | Result<T, E> { // We produce `Unit` *only* in the case where no arguments are passed, so // that we can allow `undefined` in the cases where someone explicitly opts // into something like `Result<undefined, Blah>`. return arguments.length === 0 ? (new ResultImpl<T, Unit>(['Err', Unit]) as Result<T, Unit>) : // SAFETY: TS does not understand that the arity check above accounts for // the case where the value is not passed. (new ResultImpl<T, E>(['Err', error as E]) as Result<T, E>); } /** Distinguish between the {@linkcode Variant.Ok} and {@linkcode Variant.Err} {@linkcode Variant variants}. */ get variant(): Variant { return this.repr[0]; } /** The wrapped value. @throws if you access when the {@linkcode Result} is not {@linkcode Ok} */ get value(): T | never { if (this.repr[0] === Variant.Err) { throw new Error('Cannot get the value of Err'); } return this.repr[1]; } /** The wrapped error value. @throws if you access when the {@linkcode Result} is not {@linkcode Err} */ get error(): E | never { if (this.repr[0] === Variant.Ok) { throw new Error('Cannot get the error of Ok'); } return this.repr[1]; } /** Is the {@linkcode Result} an {@linkcode Ok}? */ get isOk() { return this.repr[0] === Variant.Ok; } /** Is the `Result` an `Err`? */ get isErr() { return this.repr[0] === Variant.Err; } /** Method variant for {@linkcode map} */ map<U>(mapFn: (t: T) => U): Result<U, E> { return (this.repr[0] === 'Ok' ? Result.ok(mapFn(this.repr[1])) : this) as Result<U, E>; } /** Method variant for {@linkcode mapOr} */ mapOr<U>(orU: U, mapFn: (t: T) => U): U { return this.repr[0] === 'Ok' ? mapFn(this.repr[1]) : orU; } /** Method variant for {@linkcode mapOrElse} */ mapOrElse<U>(orElseFn: (err: E) => U, mapFn: (t: T) => U): U { return this.repr[0] === 'Ok' ? mapFn(this.repr[1]) : orElseFn(this.repr[1]); } /** Method variant for {@linkcode match} */ match<A>(matcher: Matcher<T, E, A>): A { return this.repr[0] === 'Ok' ? matcher.Ok(this.repr[1]) : matcher.Err(this.repr[1]); } /** Method variant for {@linkcode mapErr} */ mapErr<F>(mapErrFn: (e: E) => F): Result<T, F> { return (this.repr[0] === 'Ok' ? this : Result.err(mapErrFn(this.repr[1]))) as Result<T, F>; } /** Method variant for {@linkcode or} */ or<F>(orResult: Result<T, F>): Result<T, F> { return (this.repr[0] === 'Ok' ? this : orResult) as Result<T, F>; } /** Method variant for {@linkcode orElse} */ orElse<F>(orElseFn: (err: E) => Result<T, F>): Result<T, F>; orElse<R extends AnyResult>(orElseFn: (err: E) => R): Result<T | OkFor<R>, ErrFor<R>>; orElse<F>(orElseFn: (err: E) => Result<T, F>): Result<T, F> { return this.repr[0] === 'Ok' ? (this as Ok<T, E>).cast() : orElseFn(this.repr[1]); } /** Method variant for {@linkcode and} */ and<U>(mAnd: Result<U, E>): Result<U, E> { // (r.isOk ? andResult : err<U, E>(r.error)) return (this.repr[0] === 'Ok' ? mAnd : this) as Result<U, E>; } /** Method variant for {@linkcode andThen} */ andThen<U>(andThenFn: (t: T) => Result<U, E>): Result<U, E>; andThen<R extends AnyResult>(andThenFn: (t: T) => R): Result<OkFor<R>, E | ErrFor<R>>; andThen<U>(andThenFn: (t: T) => Result<U, E>): Result<U, E> { return this.repr[0] === 'Ok' ? andThenFn(this.repr[1]) : (this as Err<T, E>).cast(); } /** Method variant for {@linkcode unwrapOr} */ unwrapOr<U = T>(defaultValue: U): T | U { return this.repr[0] === 'Ok' ? this.repr[1] : defaultValue; } /** Method variant for {@linkcode unwrapOrElse} */ unwrapOrElse<U>(elseFn: (error: E) => U): T | U { return this.repr[0] === 'Ok' ? this.repr[1] : elseFn(this.repr[1]); } /** Run a side effect with the wrapped value without modifying the {@linkcode Result}. This is useful for performing actions like logging, debugging, or other “side effects” external to the wrapped value. (**Note:** You should *never* mutate the value in the callback. Doing so will be extremely surprising to callers.) The function is only called if the `Result` is {@linkcode Ok}, and the original `Result` is returned unchanged for further chaining. ```ts import * as result from 'true-myth/result'; const double = (n: number) => n * 2; const log = (value: unknown) => console.log(value); // Logs `42` then `84`, and returns `Ok(84)`. result.ok<number, string>(42).inspect(log).map(double).inspect(log); // Does not log anything, and returns `Err('error')`. result.err<number, string>('error').inspect(log).map(double).inspect(log); ``` @param fn The function to call with the wrapped value, only called for `Ok`. @returns The original `Result`, unchanged */ inspect(fn: (value: T) => void): Result<T, E> { if (this.repr[0] === 'Ok') { fn(this.repr[1]); } return this as Result<T, E>; } /** Run a side effect with a wrapped error value without modifying the {@linkcode Result}. This is useful for performing actions like logging, debugging, or other “side effects” external to the wrapped value. (**Note:** You should *never* mutate the value in the callback. Doing so will be extremely surprising to callers.) The function is only called if the `Result` is {@linkcode Err}, and the original `Result` is returned unchanged for further chaining. ```ts import * as result from 'true-myth/result'; const logError = (error: unknown) => console.logError('Got error:', error); result.err<number, string>('error') .inspectErr((error) => console.log('Got error:', error)) .mapErr((e) => e.toUpperCase()); // Logs: "Got error: error" // Returns: Err('ERROR') result.ok<number, string>(42) .inspectErr((error) => console.log('Got error:', error)) .mapErr((e) => e.toUpperCase()); // Logs nothing // Returns: Ok(42) ``` @param fn The function to call with the error value, only called for `Err`. @returns The original Result, unchanged */ inspectErr(fn: (error: E) => void): Result<T, E> { if (this.repr[0] === 'Err') { fn(this.repr[1]); } return this as Result<T, E>; } /** Method variant for {@linkcode toString} */ toString(): string { return `${this.repr[0]}(${safeToString(this.repr[1])})`; } /** Method variant for {@linkcode toJSON} */ toJSON(): Serialized<T, E> { const variant = this.repr[0]; if (variant === Variant.Ok) { const value = isInstance(this.repr[1]) ? this.repr[1].toJSON() : this.repr[1]; return { variant, value } as Serialized<T, E>; } else { const error = isInstance(this.repr[1]) ? this.repr[1].toJSON() : this.repr[1]; return { variant, error } as Serialized<T, E>; } } /** Method variant for {@linkcode equals} */ equals(comparison: Result<T, E>): boolean { // SAFETY: these casts are stripping away the `Ok`/`Err` distinction and // simply testing what `comparison` *actually* is, which is always an // instance of `ResultImpl` (the same as this method itself). return ( this.repr[0] === (comparison as ResultImpl<T, E>).repr[0] && this.repr[1] === (comparison as ResultImpl<T, E>).repr[1] ); } /** Method variant for {@linkcode ap} */ ap<A, B>(this: Result<(a: A) => B, E>, r: Result<A, E>): Result<B, E> { return r.andThen((val) => this.map((fn) => fn(val))); } /** Given a nested `Result`, remove one layer of nesting. For example, given a `Result<Result<string, E2>, E1>`, the resulting type after using this method will be `Result<string, E1 | E2>`. ## Note This method only works when the value wrapped in `Result` is another `Result`. If you have a `Result<string, E>` or `Result<number, E>`, this method won't work. If you have a `Result<Result<string, E2>, E1>`, then you can call `.flatten()` to get back a `Result<string, E1 | E2>`. ## Examples ```ts import * as result from 'true-myth/result'; const nested = result.ok(result.ok('hello')); const flattened = nested.flatten(); // Result<string, never> console.log(flattened); // Ok('hello') const nestedError = result.ok(result.err('inner error')); const flattenedError = nestedError.flatten(); // Result<never, string> console.log(flattenedError); // Err('inner error') const errorNested = result.err<Result<string, string>, string>('outer error'); const flattenedOuter = errorNested.flatten(); // Result<string, string> console.log(flattenedOuter); // Err('outer error') ``` */ // NOTE: it is necessary to express the type constraint on the `this` value // like this, rather than like `this: Result<Result<T, E2>, E1>`, to avoid // producing a `Result<Result<Result<T, E2>, E1>, E2>` at call sites with the // wrapped value. flatten<A, F>(this: Result<Result<A, F>, E>): Result<A, E | F> { return this.andThen(identity); } cast() { return this; } } /** An `Ok` instance is the *successful* variant instance of the {@linkcode Result} type, representing a successful outcome from an operation which may fail. For a full discussion, see the module docs. @template T The type wrapped in this `Ok` variant of `Result`. @template E The type which would be wrapped in an `Err` variant of `Result`. */ export interface Ok<T, E> extends Omit<ResultImpl<T, E>, 'error' | 'cast'> { /** `Ok` is always `Variant.Ok`. */ readonly variant: 'Ok'; isOk: true; isErr: false; /** The wrapped value */ value: T; cast<F>(): Result<T, F>; } /** An `Err` instance is the *failure* variant instance of the {@linkcode Result} type, representing a failure outcome from an operation which may fail. For a full discussion, see the module docs. @template T The type which would be wrapped in an `Ok` variant of `Result`. @template E The type wrapped in this `Err` variant of `Result`. */ export interface Err<T, E> extends Omit<ResultImpl<T, E>, 'value' | 'cast'> { /** `Err` is always `Variant.Err`. */ readonly variant: 'Err'; isOk: false; isErr: true; /** The wrapped error value. */ error: E; cast<U>(): Result<U, E>; } /** Execute the provided callback, wrapping the return value in {@linkcode Ok} or {@linkcode Err Err(error)} if there is an exception. ```ts const aSuccessfulOperation = () => 2 + 2; const anOkResult = Result.tryOr('Oh noes!!1', () => { aSuccessfulOperation() }); // => Ok(4) const thisOperationThrows = () => throw new Error('Bummer'); const anErrResult = Result.tryOr('Oh noes!!1', () => { thisOperationThrows(); }); // => Err('Oh noes!!1') ``` @param error The error value in case of an exception @param callback The callback to try executing */ export function tryOr<T, E>(error: E, callback: () => T): Result<T, E>; export function tryOr<T, E>(error: E): (callback: () => T) => Result<T, E>; export function tryOr<T, E>( error: E, callback?: () => T ): Result<T, E> | ((callback: () => T) => Result<T, E>) { const op = (cb: () => T) => { try { return ok<T, E>(cb()); } catch { return err<T, E>(error); } }; return curry1(op, callback); } /** Create an instance of {@linkcode Ok}. If you need to create an instance with a specific type (as you do whenever you are not constructing immediately for a function return or as an argument to a function), you can use a type parameter: ```ts const yayNumber = Result.ok<number, string>(12); ``` Note: passing nothing, or passing `null` or `undefined` explicitly, will produce a `Result<Unit, E>`, rather than producing the nonsensical and in practice quite annoying `Result<null, string>` etc. See {@linkcode Unit} for more. ```ts const normalResult = Result.ok<number, string>(42); const explicitUnit = Result.ok<Unit, string>(Unit); const implicitUnit = Result.ok<Unit, string>(); ``` In the context of an immediate function return, or an arrow function with a single expression value, you do not have to specify the types, so this can be quite convenient. ```ts type SomeData = { //... }; const isValid = (data: SomeData): boolean => { // true or false... } const arrowValidate = (data: SomeData): Result<Unit, string> => isValid(data) ? Result.ok() : Result.err('something was wrong!'); function fnValidate(data: someData): Result<Unit, string> { return isValid(data) ? Result.ok() : Result.err('something was wrong'); } ``` @template T The type of the item contained in the `Result`. @param value The value to wrap in a `Result.Ok`. */ export const ok = ResultImpl.ok; /** Is the {@linkcode Result} an {@linkcode Ok}? @template T The type of the item contained in the `Result`. @param result The `Result` to check. @returns A type guarded `Ok`. */ export function isOk<T, E>(result: Result<T, E>): result is Ok<T, E> { return result.isOk; } /** Is the {@linkcode Result} an {@linkcode Err}? @template T The type of the item contained in the `Result`. @param result The `Result` to check. @returns A type guarded `Err`. */ export function isErr<T, E>(result: Result<T, E>): result is Err<T, E> { return result.isErr; } /** Create an instance of {@linkcode Err}. If you need to create an instance with a specific type (as you do whenever you are not constructing immediately for a function return or as an argument to a function), you can use a type parameter: ```ts const notString = Result.err<number, string>('something went wrong'); ``` Note: passing nothing, or passing `null` or `undefined` explicitly, will produce a `Result<T, Unit>`, rather than producing the nonsensical and in practice quite annoying `Result<null, string>` etc. See {@linkcode Unit} for more. ```ts const normalResult = Result.err<number, string>('oh no'); const explicitUnit = Result.err<number, Unit>(Unit); const implicitUnit = Result.err<number, Unit>(); ``` In the context of an immediate function return, or an arrow function with a single expression value, you do not have to specify the types, so this can be quite convenient. ```ts type SomeData = { //... }; const isValid = (data: SomeData): boolean => { // true or false... } const arrowValidate = (data: SomeData): Result<number, Unit> => isValid(data) ? Result.ok(42) : Result.err(); function fnValidate(data: someData): Result<number, Unit> { return isValid(data) ? Result.ok(42) : Result.err(); } ``` @template T The type of the item contained in the `Result`. @param E The error value to wrap in a `Result.Err`. */ export const err = ResultImpl.err; /** Execute the provided callback, wrapping the return value in {@linkcode Ok}. If there is an exception, return a {@linkcode Err} of whatever the `onError` function returns. ```ts import { tryOrElse } from 'true-myth/result'; const aSuccessfulOperation = () => 2 + 2; const anOkResult = tryOrElse( (e) => e, aSuccessfulOperation ); // => Ok(4) const thisOperationThrows = () => throw 'Bummer' const anErrResult = tryOrElse( (e) => e, () => { thisOperationThrows(); } ); // => Err('Bummer') ``` @param onError A function that takes `e` exception and returns what will be wrapped in a `Result.Err` @param callback The callback to try executing */ export function tryOrElse<T, E>(onError: (e: unknown) => E, callback: () => T): Result<T, E>; export function tryOrElse<T, E>(onError: (e: unknown) => E): (callback: () => T) => Result<T, E>; export function tryOrElse<T, E>( onError: (e: unknown) => E, callback?: () => T ): Result<T, E> | ((callback: () => T) => Result<T, E>) { const op = (cb: () => T) => { try { return ok<T, E>(cb()); } catch (e) { return err<T, E>(onError(e)); } }; return curry1(op, callback); } /** Map over a {@linkcode Result} instance: apply the function to the wrapped value if the instance is {@linkcode Ok}, and return the wrapped error value wrapped as a new {@linkcode Err} of the correct type (`Result<U, E>`) if the instance is `Err`. `map` works a lot like `Array.prototype.map`, but with one important difference. Both `Result` and `Array` are containers for other kinds of items, but where `Array.prototype.map` has 0 to _n_ items, a `Result` always has exactly one item, which is *either* a success or an error instance. Where `Array.prototype.map` will apply the mapping function to every item in the array (if there are any), `Result.map` will only apply the mapping function to the (single) element if an `Ok` instance, if there is one. If you have no items in an array of numbers named `foo` and call `foo.map(x => x + 1)`, you'll still some have an array with nothing in it. But if you have any items in the array (`[2, 3]`), and you call `foo.map(x => x + 1)` on it, you'll get a new array with each of those items inside the array "container" transformed (`[3, 4]`). With this `map`, the `Err` variant is treated *by the `map` function* kind of the same way as the empty array case: it's just ignored, and you get back a new `Result` that is still just the same `Err` instance. But if you have an `Ok` variant, the map function is applied to it, and you get back a new `Result` with the value transformed, and still wrapped in an `Ok`. ```ts import { ok, err, map, toString } from 'true-myth/result'; const double = n => n * 2; const anOk = ok(12); const mappedOk = map(double, anOk); console.log(toString(mappedOk)); // Ok(24) const anErr = err("nothing here!"); const mappedErr = map(double, anErr); console.log(toString(mappedErr)); // Err(nothing here!) ``` @template T The type of the value wrapped in an `Ok` instance, and taken as the argument to the `mapFn`. @template U The type of the value wrapped in the new `Ok` instance after applying `mapFn`, that is, the type returned by `mapFn`. @template E The type of the value wrapped in an `Err` instance. @param mapFn The function to apply the value to if `result` is `Ok`. @param result The `Result` instance to map over. @returns A new `Result` with the result of applying `mapFn` to the value in an `Ok`, or else the original `Err` value wrapped in the new instance. */ export function map<T, U, E>(mapFn: (t: T) => U, result: Result<T, E>): Result<U, E>; export function map<T, U, E>(mapFn: (t: T) => U): (result: Result<T, E>) => Result<U, E>; export function map<T, U, E>( mapFn: (t: T) => U, result?: Result<T, E> ): Result<U, E> | ((result: Result<T, E>) => Result<U, E>) { const op = (r: Result<T, E>) => r.map(mapFn); return curry1(op, result); } /** Map over a {@linkcode Result} instance as in [`map`](#map) and get out the value if `result` is an {@linkcode Ok}, or return a default value if `result` is an {@linkcode Err}. ```ts import { ok, err, mapOr } from 'true-myth/result'; const length = (s: string) => s.length; const anOkString = ok('a string'); const theStringLength = mapOr(0, length, anOkString); console.log(theStringLength); // 8 const anErr = err('uh oh'); const anErrMapped = mapOr(0, length, anErr); console.log(anErrMapped); // 0 ``` @param orU The default value to use if `result` is an `Err`. @param mapFn The function to apply the value to if `result` is an `Ok`. @param result The `Result` instance to map over. */ export function mapOr<T, U, E>(orU: U, mapFn: (t: T) => U, result: Result<T, E>): U; export function mapOr<T, U, E>(orU: U, mapFn: (t: T) => U): (result: Result<T, E>) => U; export function mapOr<T, U, E>(orU: U): (mapFn: (t: T) => U) => (result: Result<T, E>) => U; export function mapOr<T, U, E>( orU: U, mapFn?: (t: T) => U, result?: Result<T, E> ): U | ((result: Result<T, E>) => U) | ((mapFn: (t: T) => U) => (result: Result<T, E>) => U) { function fullOp(fn: (t: T) => U, r: Result<T, E>): U { return r.mapOr(orU, fn); } function partialOp(fn: (t: T) => U): (maybe: Result<T, E>) => U; function partialOp(fn: (t: T) => U, curriedResult: Result<T, E>): U; function partialOp( fn: (t: T) => U, curriedResult?: Result<T, E> ): U | ((maybe: Result<T, E>) => U) { return curriedResult !== undefined ? fullOp(fn, curriedResult) : (extraCurriedResult: Result<T, E>) => fullOp(fn, extraCurriedResult); } return mapFn === undefined ? partialOp : result === undefined ? partialOp(mapFn) : partialOp(mapFn, result); } /** Map over a {@linkcode Result} instance as in {@linkcode map} and get out the value if `result` is {@linkcode Ok}, or apply a function (`orElseFn`) to the value wrapped in the {@linkcode Err} to get a default value. Like {@linkcode mapOr} but using a function to transform the error into a usable value instead of simply using a default value. ```ts import { ok, err, mapOrElse } from 'true-myth/result'; const summarize = (s: string) => `The response was: '${s}'`; const getReason = (err: { code: number, reason: string }) => err.reason; const okResponse = ok("Things are grand here."); const mappedOkAndUnwrapped = mapOrElse(getReason, summarize, okResponse); console.log(mappedOkAndUnwrapped); // The response was: 'Things are grand here.' const errResponse = err({ code: 500, reason: 'Nothing at this endpoint!' }); const mappedErrAndUnwrapped = mapOrElse(getReason, summarize, errResponse); console.log(mappedErrAndUnwrapped); // Nothing at this endpoint! ``` @template T The type of the wrapped `Ok` value. @template U The type of the resulting value from applying `mapFn` to the `Ok` value or `orElseFn` to the `Err` value. @template E The type of the wrapped `Err` value. @param orElseFn The function to apply to the wrapped `Err` value to get a usable value if `result` is an `Err`. @param mapFn The function to apply to the wrapped `Ok` value if `result` is an `Ok`. @param result The `Result` instance to map over. */ export function mapOrElse<T, U, E>( orElseFn: (err: E) => U, mapFn: (t: T) => U, result: Result<T, E> ): U; export function mapOrElse<T, U, E>( orElseFn: (err: E) => U, mapFn: (t: T) => U ): (result: Result<T, E>) => U; export function mapOrElse<T, U, E>( orElseFn: (err: E) => U ): (mapFn: (t: T) => U) => (result: Result<T, E>) => U; export function mapOrElse<T, U, E>( orElseFn: (err: E) => U, mapFn?: (t: T) => U, result?: Result<T, E> ): U | ((result: Result<T, E>) => U) | ((mapFn: (t: T) => U) => (result: Result<T, E>) => U) { function fullOp(fn: (t: T) => U, r: Result<T, E>) { return r.mapOrElse(orElseFn, fn); } function partialOp(fn: (t: T) => U): (result: Result<T, E>) => U; function partialOp(fn: (t: T) => U, curriedResult: Result<T, E>): U; function partialOp( fn: (t: T) => U, curriedResult?: Result<T, E> ): U | ((maybe: Result<T, E>) => U) { return curriedResult !== undefined ? fullOp(fn, curriedResult) : (extraCurriedResult: Result<T, E>) => fullOp(fn, extraCurriedResult); } return mapFn === undefined ? partialOp : result === undefined ? partialOp(mapFn) : partialOp(mapFn, result); } /** Map over a {@linkcode Ok}, exactly as in {@linkcode map}, but operating on the value wrapped in an {@linkcode Err} instead of the value wrapped in the {@linkcode Ok}. This is handy for when you need to line up a bunch of different types of errors, or if you need an error of one shape to be in a different shape to use somewhere else in your codebase. ```ts import { ok, err, mapErr, toString } from 'true-myth/result'; const reason = (err: { code: number, reason: string }) => err.reason; const anOk = ok(12); const mappedOk = mapErr(reason, anOk); console.log(toString(mappedOk)); // Ok(12) const anErr = err({ code: 101, reason: 'bad file' }); const mappedErr = mapErr(reason, anErr); console.log(toString(mappedErr)); // Err(bad file) ``` @template T The type of the value wrapped in the `Ok` of the `Result`. @template E The type of the value wrapped in the `Err` of the `Result`. @template F The type of the value wrapped in the `Err` of a new `Result`, returned by the `mapErrFn`. @param mapErrFn The function to apply to the value wrapped in `Err` if `result` is an `Err`. @param result The `Result` instance to map over an error case for. */ export function mapErr<T, E, F>(mapErrFn: (e: E) => F, result: Result<T, E>): Result<T, F>; export function mapErr<T, E, F>(mapErrFn: (e: E) => F): (result: Result<T, E>) => Result<T, F>; export function mapErr<T, E, F>( mapErrFn: (e: E) => F, result?: Result<T, E> ): Result<T, F> | ((result: Result<T, E>) => Result<T, F>) { const op = (r: Result<T, E>) => r.mapErr(mapErrFn); return curry1(op, result); } /** You can think of this like a short-circuiting logical "and" operation on a {@linkcode Result} type. If `result` is {@linkcode Ok}, then the result is the `andResult`. If `result` is {@linkcode Err}, the result is the `Err`. This is useful when you have another `Result` value you want to provide if and *only if* you have an `Ok` – that is, when you need to make sure that if you `Err`, whatever else you're handing a `Result` to *also* gets that `Err`. Notice that, unlike in [`map`](#map) or its variants, the original `result` is not involved in constructing the new `Result`. ```ts import { and, ok, err, toString } from 'true-myth/result'; const okA = ok('A'); const okB = ok('B'); const anErr = err({ so: 'bad' }); console.log(toString(and(okB, okA))); // Ok(B) console.log(toString(and(okB, anErr))); // Err([object Object]) console.log(toString(and(anErr, okA))); // Err([object Object]) console.log(toString(and(anErr, anErr))); // Err([object Object]) ``` @template T The type of the value wrapped in the `Ok` of the `Result`. @template U The type of the value wrapped in the `Ok` of the `andResult`, i.e. the success type of the `Result` present if the checked `Result` is `Ok`. @template E The type of the value wrapped in the `Err` of the `Result`. @param andResult The `Result` instance to return if `result` is `Err`. @param result The `Result` instance to check. */ export function and<T, U, E>(andResult: Result<U, E>, result: Result<T, E>): Result<U, E>; export function and<T, U, E>(andResult: Result<U, E>): (result: Result<T, E>) => Result<U, E>; export function and<T, U, E>( andResult: Result<U, E>, result?: Result<T, E> ): Result<U, E> | ((result: Result<T, E>) => Result<U, E>) { const op = (r: Result<T, E>) => r.and(andResult); return curry1(op, result); } /** Apply a function to the wrapped value if {@linkcode Ok} and return a new `Ok` containing the resulting value; or if it is {@linkcode Err} return it unmodified. This differs from `map` in that `thenFn` returns another {@linkcode Result}. You can use `andThen` to combine two functions which *both* create a `Result` from an unwrapped type. You may find the `.then` method on an ES6 `Promise` helpful for comparison: if you have a `Promise`, you can pass its `then` method a callback which returns another `Promise`, and the result will not be a *nested* promise, but a single `Promise`. The difference is that `Promise#then` unwraps *all* layers to only ever return a single `Promise` value, whereas `Result.andThen` will not unwrap nested `Result`s. > [!NOTE] This is is sometimes also known as `bind`, but *not* aliased as such > because [`bind` already means something in JavaScript][bind]. [bind]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind ```ts import { ok, err, andThen, toString } from 'true-myth/result'; const toLengthAsResult = (s: string) => ok(s.length); const anOk = ok('just a string'); const lengthAsResult = andThen(toLengthAsResult, anOk); console.log(toString(lengthAsResult)); // Ok(13) const anErr = err(['srsly', 'whatever']); const notLengthAsResult = andThen(toLengthAsResult, anErr); console.log(toString(notLengthAsResult)); // Err(srsly,whatever) ``` @template T The type of the value wrapped in the `Ok` of the `Result`. @template U The type of the value wrapped in the `Ok` of the `Result` returned by the `thenFn`. @template E The type of the value wrapped in the `Err` of the `Result`. @param thenFn The function to apply to the wrapped `T` if `maybe` is `Just`. @param result The `Maybe` to evaluate and possibly apply a function to. */ export function andThen<T, E, R extends AnyResult>( thenFn: (t: T) => R, result: Result<T, E> ): Result<OkFor<R>, E | ErrFor<R>>; export function andThen<T, E, R extends AnyResult>( thenFn: (t: T) => R ): (result: Result<T, E>) => Result<OkFor<R>, E | ErrFor<R>>; export function andThen<T, E, R extends AnyResult>( thenFn: (t: T) => R, result?: Result<T, E> ): Result<OkFor<R>, E | ErrFor<R>> | ((result: Result<T, E>) => Result<OkFor<R>, E | ErrFor<R>>) { const op = (r: Result<T, E>) => r.andThen(thenFn); return curry1(op, result); } /** Provide a fallback for a given {@linkcode Result}. Behaves like a logical `or`: if the `result` value is an {@linkcode Ok}, returns that `result`; otherwise, returns the `defaultResult` value. This is useful when you want to make sure that something which takes a `Result` always ends up getting an `Ok` variant, by supplying a default value for the case that you currently have an {@linkcode Err}. ```ts import { ok, err, Result, or } from 'true-utils/result'; const okA = ok<string, string>('a'); const okB = ok<string, string>('b'); const anErr = err<string, string>(':wat:'); const anotherErr = err<string, string>(':headdesk:'); console.log(or(okB, okA).toString()); // Ok(A) console.log(or(anErr, okA).toString()); // Ok(A) console.log(or(okB, anErr).toString()); // Ok(B) console.log(or(anotherErr, anErr).toString()); // Err(:headdesk:) ``` @template T The type wrapped in the `Ok` case of `result`. @template E The type wrapped in the `Err` case of `result`. @template F The type wrapped in the `Err` case of `defaultResult`. @param defaultResult The `Result` to use if `result` is an `Err`. @param result The `Result` instance to check. @returns `result` if it is an `Ok`, otherwise `defaultResult`. */ export function or<T, E, F>(defaultResult: Result<T, F>, result: Result<T, E>): Result<T, F>; export function or<T, E, F>(defaultResult: Result<T, F>): (result: Result<T, E>) => Result<T, F>; export function or<T, E, F>( defaultResult: Result<T, F>, result?: Result<T, E> ): Result<T, F> | ((result: Result<T, E>) => Result<T, F>) { const op = (r: Result<T, E>) => r.or(defaultResult); return curry1(op, result); } /** Like {@linkcode or}, but using a function to construct the alternative {@linkcode Result}. Sometimes you need to perform an operation using the `error` value (and possibly other data in the environment) to construct the fallback value. In these situations, you can pass a function (which may be a closure) as the `elseFn` to generate the fallback `Result<T>`. It can then transform the data in the `Err` to something usable as an {@linkcode Ok}, or generate a new {@linkcode Err} instance as appropriate. Useful for transforming failures to usable data. @param elseFn The function to apply to the contents of the `Err` if `result` is an `Err`, to create a new `Result`. @param result The `Result` to use if it is an `Ok`. @returns The `result` if it is `Ok`, or the `Result` returned by `elseFn` if `result` is an `Err. */ export function orElse<T, E, R extends AnyResult>( elseFn: (err: E) => R, result: Result<T, E> ): Result<T | OkFor<R>, ErrFor<R>>; export function orElse<T, E, R extends AnyResult>( elseFn: (err: E) => R ): (result: Result<T, E>) => Result<T | OkFor<R>, ErrFor<R>>; export function orElse<T, E, R extends AnyResult>( elseFn: (err: E) => R, result?: Result<T, E> ): Result<T | OkFor<R>, ErrFor<R>> | ((result: Result<T, E>) => Result<T | OkFor<R>, ErrFor<R>>) { const op = (r: Result<T, E>) => r.orElse(elseFn); return curry1(op, result); } /** Safely get the value out of the {@linkcode Ok} variant of a {@linkcode Result}. This is the recommended way to get a value out of a `Result` most of the time. ```ts import { ok, err, unwrapOr } from 'true-myth/result'; const anOk = ok<number, string>(12); console.log(unwrapOr(0, anOk)); // 12 const anErr = err<number, string>('nooooo'); console.log(unwrapOr(0, anErr)); // 0 ``` @template T The value wrapped in the `Ok`. @template E The value wrapped in the `Err`. @param defaultValue The value to use if `result` is an `Err`. @param result The `Result` instance to unwrap if it is an `Ok`. @returns The content of `result` if it is an `Ok`, otherwise `defaultValue`. */ export function unwrapOr<T, U, E>(defaultValue: U, result: Result<T, E>): U | T; export function unwrapOr<T, U, E>(defaultValue: U): (result: Result<T, E>) => U | T; export function unwrapOr<T, U, E>( defaultValue: U, result?: Result<T, E> ): (T | U) | ((result: Result<T, E>) => T | U) { const op = (r: Result<T, E>) => r.unwrapOr(defaultValue); return curry1(op, result); } /** Safely get the value out of a {@linkcode Result} by returning the wrapped value if it is {@linkcode Ok}, or by applying `orElseFn` to the value in the {@linkcode Err}. This is useful when you need to *generate* a value (e.g. by using current values in the environment – whether preloaded or by local closure) instead of having a single default value available (as in {@linkcode unwrapOr}). ```ts import { ok, err, unwrapOrElse } from 'true-myth/result'; // You can imagine that someOtherValue might be dynamic. const someOtherValue = 2; const handleErr = (errValue: string) => errValue.length + someOtherValue; const anOk = ok<number, string>(42); console.log(unwrapOrElse(handleErr, anOk)); // 42 const anErr = err<number, string>('oh teh noes'); console.log(unwrapOrElse(handleErr, anErr)); // 13 ``` @template T The value wrapped in the `Ok`. @template E The value wrapped in the `Err`. @param orElseFn A function applied to the value wrapped in `result` if it is an `Err`, to generate the final value. @param result The `result` to unwrap if it is an `Ok`. @returns The value wrapped in `result` if it is `Ok` or the value returned by `orElseFn` applied to the value in `Err`. */ export function unwrapOrElse<T, U, E>(orElseFn: (error: E) => U, result: Result<T, E>): T | U; export function unwrapOrElse<T, U, E>(orElseFn: (error: E) => U): (result: Result<T, E>) => T | U; export function unwrapOrElse<T, U, E>( orElseFn: (error: E) => U, result?: Result<T, E> ): (T | U) | ((result: Result<T, E>) => T | U) { const op = (r: Result<T, E>) => r.unwrapOrElse(orElseFn); return curry1(op, result); } /** Run a side effect with the wrapped value without modifying the {@linkcode Result}. This is useful for performing actions like logging, debugging, or other “side effects” external to the wrapped value. (**Note:** You should *never* mutate the value in the callback. Doing so will be extremely surprising to callers.) The function is only called if the `Result` is {@linkcode Ok}, and the original `Result` is returned unchanged for further chaining. ```ts import * as result from 'true-myth/result'; const double = (n: number) => n * 2; const log = (value: unknown) => console.log(value); // Logs `42` then `84`, and returns `Ok(84)`. const anOk = result.ok<number, string>(42); result.inspect( log, result.map( double, result.inspect( log, anOk ) ) ); // Does not log anything, and returns `Err('error')`. const anErr = result.err<number, string>('error'); result.inspect( log, result.map( double, result.inspect( log, anErr ) ) ); ``` @template T The type of the wrapped value @template E The type of the error value @param fn The function to call with the wrapped value (only called for Ok) @param result The Result to inspect @returns The original Result, unchanged */ export function inspect<T, E>(fn: (value: T) => void, result: Result<T, E>): Result<T, E>; export function inspect<T, E>(fn: (value: T) => void): (result: Result<T, E>) => Result<T, E>; export function inspect<T, E>( fn: (value: T) => void, result?: Result<T, E> ): Result<T, E> | ((result: Result<T, E>) => Result<T, E>) { const op = (r: Result<T, E>) => r.inspect(fn); return curry1(op, result); } /** Run a side effect with the error value without modifying the {@linkcode Result}. This is useful for performing actions like logging, debugging, or other “side effects” external to the wrapped value. (**Note:** You should *never* mutate the value in the callback. Doing so will be extremely surprising to callers.) The function is only called if the `Result` is {@linkcode Ok}, and the original `Result` is returned unchanged for further chaining. > [!NOTE] > TypeScript type inference is limited with the curried form. You will need to > provide explicit type parameters. Alternatively, use the non-curried form > to get consistent type inference. ```ts import * as result from 'true-myth/result'; const double = (n: number) => n * 2; const logError = (value: unknown) => console.log('Got error:', value); // Logs: "Got error: error" const anErr = result.err<number, string>('error'); result.inspectErr( logError, result.map( double, result.inspectErr( logError, anOk ) ) ); // Does not log anything, and returns `Ok(42)`. const anOk = result.ok<number, string>(42); result.inspectErr( logError, result.map( double, result.inspectErr( logError, anErr ) ) ); ``` @template T The type of the wrapped value @template E The type of the error value @param fn The function to call with the error value (only called for Err) @param result The Result to inspect @returns The original Result, unchanged */ export function inspectErr<T, E>(fn: (error: E) => void, result: Result<T, E>): Result<T, E>; /** Run a side effect with the error value without modifying the {@linkcode Result}. This is useful for performing actions like logging, debugging, or other “side effects” external to the wrapped value. (**Note:** You should *never* mutate the value in the callback. Doing so will be extremely surprising to callers.) The function is only called if the `Result` is {@linkcode Ok}, and the original `Result` is returned unchanged for further chaining. ```ts import * as result from 'true-myth/result'; const double = (n: number) => n * 2; const logError = (value: unknown) => console.log('Got error:', value); // Logs: "Got error: error" const anErr = result.err<number, string>('error'); result.inspectErr( logError, result.map( double, result.inspectErr( logError, anOk ) ) ); // Does not log anything, and returns `Ok(42)`. const anOk = result.ok<number, string>(42); result.inspectErr( logError, result.map( double, result.inspectErr( logError, anErr ) ) ); ``` @template T The type of the wrapped value @template E The type of the error value @param fn The function to call with the error value (only called for Err) @param result The Result to inspect @returns The original Result, unchanged */ export function inspectErr<T, E>(fn: (error: E) => void): (result: Result<T, E>) => Result<T, E>; export function inspectErr<T, E>( fn: (error: E) => void, result?: Result<T, E> ): Result<T, E> | ((result: Result<T, E>) => Result<T, E>) { const op = (r: Result<T, E>) => r.inspectErr(fn); return curry1(op, result); } /** Create a `String` representation of a {@linkcode Result} instance. An {@linkcode Ok} instance will be `Ok(<representation of the value>)`, and an {@linkcode Err} instance will be `Err(<representation of the error>)`, where the representation of the value or error is simply the value or error's own `toString` representation. For example: call | output --------------------------------- | ---------------------- `toString(ok(42))` | `Ok(42)` `toString(ok([1, 2, 3]))` | `Ok(1,2,3)` `toString(ok({ an: 'object' }))` | `Ok([object Object])`n `toString(err(42))` | `Err(42)` `toString(err([1, 2, 3]))` | `Err(1,2,3)` `toString(err({ an: 'object' }))` | `Err([object Object])` @template T The type of the wrapped value; its own `.toString` will be used to print the interior contents of the `Just` variant. @param result The value to convert to a string. @returns The string representation of the `Maybe`. */ export const toString = <T, E>(result: Result<T, E>): string => { return result.toString(); }; /** * Create an `Object` representation of a {@linkcode Result} instance. * * Useful for serialization. `JSON.stringify()` uses it. * * @param result The value to convert to JSON * @returns The JSON representation of the `Result` */ export function toJSON<T, E>(result: Result<T, E>): Serialized<T, E> { return result.toJSON(); } /** * Given a {@linkcode ResultJSON} object, convert it into a {@linkcode Result}. * * Note that this is not designed for parsing data off the wire, but requires * you to have *already* parsed it into the {@linkcode ResultJSON} format. * * @param {ResultJSON<T,E>} json The value to convert from a JSON object. * @returns {Result<T,E>} The converted `Result` type. */ export function fromJSON<T, E>(json: ResultJSON<T, E>): Result<T, E> { return json.variant === Variant.Ok ? ok(json.value) : err(json.error); } /** A lightweight object defining how to handle each variant of a {@linkcode Result}. @template T The success type @template E The error type @template A The type resulting from calling {@linkcode match} on a {@linkcode Result} */ export type Matcher<T, E, A> = { /** Transform a `T` into the resulting type `A`. */ Ok: (value: T) => A; /** Transform an `E` into the resulting type `A`. */ Err: (error: E) => A; }; /** Performs the same basic functionality as {@linkcode unwrapOrElse}, but instead of simply unwrapping the value if it is {@linkcode Ok} and applying a value to generate the same default type if it is {@linkcode Err}, lets you supply functions which may transform the wrapped type if it is `Ok` or get a default value for `Err`. This is kind of like a poor man's version of pattern matching, which JavaScript currently lacks. Instead of code like this: ```ts import Result, { isOk, match } from 'true-myth/result'; const logValue = (mightBeANumber: Result<number, string>) => { console.log( mightBeANumber.isOk ? mightBeANumber.value.toString() : `There was an error: ${unsafelyGetErr(mightBeANumber)}` ); }; ``` ...we can write code like this: ```ts import Result, { match } from 'true-myth/result'; const logValue = (mightBeANumber: Result<number, string>) => { const value = match( { Ok: n => n.toString(), Err: e => `There was an error: ${e}`, }, mightBeANumber ); console.log(value); }; ``` This is slightly longer to write, but clearer: the more complex the resulting expression, the hairer it is to understand the ternary. Thus, this is especially convenient for times when there is a complex result, e.g. when rendering part of a React component inline in JSX/TSX. @param matcher A lightweight object defining what to do in the case of each variant. @param result The `result` instance to check. */ export function match<T, E, A>(matcher: Matcher<T, E, A>, result: Result<T, E>): A; /** Performs the same basic functionality as {@linkcode unwrapOrElse}, but instead of simply unwrapping the value if it is {@linkcode Ok} and applying a value to generate the same default type if it is {@linkcode Err}, lets you supply functions which may transform the wrapped type if it is `Ok` or get a default value for `Err`. This is kind of like a poor man's version of pattern matching, which JavaScript currently lacks. Instead of code like this: ```ts import Result, { isOk, match } from 'true-myth/result'; const logValue = (mightBeANumber: Result<number, string>) => { console.log( mightBeANumber.isOk ? mightBeANumber.value.toString() : `There was an error: ${unsafelyGetErr(mightBeANumber)}` ); }; ``` ...we can write code like this: ```ts import Result, { match } from 'true-myth/result'; const logValue = (mightBeANumber: Result<number, string>) => { const value = match( { Ok: n => n.toString(), Err: e => `There was an error: ${e}`, }, migh