Repository: supermacro/neverthrow Branch: master Commit: 5ef3a018bda7 Files: 24 Total size: 232.0 KB Directory structure: gitextract_7c6yg_7y/ ├── .babelrc ├── .changeset/ │ └── config.json ├── .eslintrc.js ├── .github/ │ └── workflows/ │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .prettierrc.js ├── CHANGELOG.md ├── CODEOWNERS ├── LICENSE ├── README.md ├── jest.config.js ├── package.json ├── rollup.config.mjs ├── src/ │ ├── _internals/ │ │ ├── error.ts │ │ └── utils.ts │ ├── index.ts │ ├── result-async.ts │ └── result.ts ├── tests/ │ ├── index.test.ts │ ├── safe-try.test.ts │ ├── tsconfig.tests.json │ └── typecheck-tests.ts └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": ["@babel/preset-env", "@babel/preset-typescript"] } ================================================ FILE: .changeset/config.json ================================================ { "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", "changelog": [ "@changesets/changelog-github", { "repo": "supermacro/neverthrow" } ], "commit": false, "fixed": [], "linked": [], "access": "public", "baseBranch": "master", "updateInternalDependencies": "patch", "ignore": [] } ================================================ FILE: .eslintrc.js ================================================ module.exports = { root: true, parser: '@typescript-eslint/parser', env: { node: true, }, ignorePatterns: ['dist/', 'tests/'], plugins: [ '@typescript-eslint', ], extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/eslint-recommended', 'plugin:@typescript-eslint/recommended', 'prettier/@typescript-eslint', 'plugin:prettier/recommended' ], rules: { semi: 'off', '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/semi': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/member-delimiter-style': ['error', { multiline: { delimiter: 'none', }, singleline: { delimiter: 'semi', requireLast: false } }] }, }; ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: pull_request: push: branches: - master concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} cancel-in-progress: true env: RUNNER_NODE_VERSION: 22 jobs: install_deps: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ env.RUNNER_NODE_VERSION }} uses: actions/setup-node@v4 with: node-version: ${{ env.RUNNER_NODE_VERSION }} - name: cache dependencies uses: actions/cache@v4 id: cache-dependencies with: path: "node_modules" key: depends-node${{ env.RUNNER_NODE_VERSION }}-${{ hashFiles('package-lock.json') }} - name: install dependencies if: steps.cache-dependencies.outputs.cache-hit != 'true' run: npm i typecheck: runs-on: ubuntu-latest needs: install_deps steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ env.RUNNER_NODE_VERSION }} uses: actions/setup-node@v4 with: node-version: ${{ env.RUNNER_NODE_VERSION }} - name: restore dependencies cache uses: actions/cache/restore@v4 with: path: "node_modules" key: depends-node${{ env.RUNNER_NODE_VERSION }}-${{ hashFiles('package-lock.json') }} - name: typecheck run: npm run typecheck lint: runs-on: ubuntu-latest needs: install_deps steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ env.RUNNER_NODE_VERSION }} uses: actions/setup-node@v4 with: node-version: ${{ env.RUNNER_NODE_VERSION }} - name: restore dependencies cache uses: actions/cache/restore@v4 with: path: "node_modules" key: depends-node${{ env.RUNNER_NODE_VERSION }}-${{ hashFiles('package-lock.json') }} - name: lint run: npm run lint build: runs-on: ubuntu-latest needs: install_deps steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ env.RUNNER_NODE_VERSION }} uses: actions/setup-node@v4 with: node-version: ${{ env.RUNNER_NODE_VERSION }} - name: restore dependencies cache uses: actions/cache/restore@v4 with: path: "node_modules" key: depends-node${{ env.RUNNER_NODE_VERSION }}-${{ hashFiles('package-lock.json') }} - name: build run: npm run build test: runs-on: ubuntu-latest needs: install_deps strategy: matrix: node-version: [18, 20, 22] steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - name: restore dependencies cache uses: actions/cache/restore@v4 with: path: "node_modules" key: depends-node${{ env.RUNNER_NODE_VERSION }}-${{ hashFiles('package-lock.json') }} - name: test run: npm run test test_result: runs-on: ubuntu-latest needs: test if: ${{ always() }} steps: - run: exit 1 if: ${{ needs.test.result != 'success' }} ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: push: branches: - master concurrency: ${{ github.workflow }}-${{ github.ref }} jobs: create_pr: name: Release runs-on: ubuntu-latest outputs: hasChangesets: ${{ steps.changesets.outputs.hasChangesets }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 cache: 'npm' - run: npm i - name: Create Release Pull Request id: changesets uses: changesets/action@v1 with: version: npm run version env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} release: needs: create_pr if: needs.create_pr.outputs.hasChangesets == 'false' runs-on: ubuntu-latest environment: deploy steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 cache: 'npm' - run: npm i - name: release id: changesets uses: changesets/action@v1 with: publish: npm run release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} ================================================ FILE: .gitignore ================================================ dist/ tmp/ node_modules/ publish.sh ================================================ FILE: .prettierrc.js ================================================ module.exports = { semi: false, trailingComma: "all", singleQuote: true, printWidth: 100, tabWidth: 2 }; ================================================ FILE: CHANGELOG.md ================================================ # neverthrow ## 8.2.0 ### Minor Changes - [#615](https://github.com/supermacro/neverthrow/pull/615) [`85ed7fd`](https://github.com/supermacro/neverthrow/commit/85ed7fd3a1247e4c0e83bba13f5e874282243d75) Thanks [@konker](https://github.com/konker)! - Add orTee, which is the equivalent of andTee but for the error track. - [#584](https://github.com/supermacro/neverthrow/pull/584) [`acea44a`](https://github.com/supermacro/neverthrow/commit/acea44adb98dda2ca32fe4e882879461cc7cedc2) Thanks [@macksal](https://github.com/macksal)! - Allow ok/err/okAsync/errAsync to accept zero arguments when returning void ## 8.1.1 ### Patch Changes - [#600](https://github.com/supermacro/neverthrow/pull/600) [`3aee20a`](https://github.com/supermacro/neverthrow/commit/3aee20a1c429062d26f440fde32a3f26ef05533a) Thanks [@m-shaka](https://github.com/m-shaka)! - docs: updated README.md about `safeTry` and added @deprecated tag to safeUnwrap ## 8.1.0 ### Minor Changes - [#589](https://github.com/supermacro/neverthrow/pull/589) [`609b398`](https://github.com/supermacro/neverthrow/commit/609b398aa1fd258a1fede974707d54eb4c230f3c) Thanks [@dmmulroy](https://github.com/dmmulroy)! - safeTry should not require .safeUnwrap() ## 8.0.0 ### Major Changes - [#484](https://github.com/supermacro/neverthrow/pull/484) [`09faf35`](https://github.com/supermacro/neverthrow/commit/09faf35a5ce701ed55b13b82074da9e50050526d) Thanks [@braxtonhall](https://github.com/braxtonhall)! - Allow orElse method to change ok types. This makes the orElse types match the implementation. This is a breaking change for the orElse type argument list, as the ok type must now be provided before the err type. ```diff - result.orElse(foo) + result.orElse(foo) ``` This only applies if type arguments were explicitly provided at an orElse callsite. If the type arguments were inferred, no updates are needed during the upgrade. ## 7.2.0 ### Minor Changes - [#562](https://github.com/supermacro/neverthrow/pull/562) [`547352f`](https://github.com/supermacro/neverthrow/commit/547352f326206b2c5b403bde4ddc88825172f25c) Thanks [@sharno](https://github.com/sharno)! - change the return type of `safeTry` to be `ResultAsync` instead of `Promise>` for better composability ## 7.1.0 ### Minor Changes - [#467](https://github.com/supermacro/neverthrow/pull/467) [`4b9d2fd`](https://github.com/supermacro/neverthrow/commit/4b9d2fdaf03223945068509f948b57194732aa03) Thanks [@untidy-hair ](https://github.com/untidy-hair)! - feat: add `andTee` and `andThrough` to handle side-effect ### Patch Changes - [#483](https://github.com/supermacro/neverthrow/pull/483) [`96f7f66`](https://github.com/supermacro/neverthrow/commit/96f7f669ac83be705a389d47ed804e9d44a13932) Thanks [@braxtonhall](https://github.com/braxtonhall)! - Fix `combineWithAllErrors` types - [#563](https://github.com/supermacro/neverthrow/pull/563) [`eadf50c`](https://github.com/supermacro/neverthrow/commit/eadf50c695db896b8841c0ee301ae5eeba994b90) Thanks [@mattpocock](https://github.com/mattpocock)! - Made err() infer strings narrowly for easier error tagging. ## 7.0.1 ### Patch Changes - [#527](https://github.com/supermacro/neverthrow/pull/527) [`2e1f198`](https://github.com/supermacro/neverthrow/commit/2e1f19899800ce5e1164412c6a693cf2f1c40b20) Thanks [@3846masa](https://github.com/3846masa)! - fix: change type definitions to make inferring types of safeTry more strict - [#497](https://github.com/supermacro/neverthrow/pull/497) [`e06203e`](https://github.com/supermacro/neverthrow/commit/e06203e90b2b64edaa42707cbca8383c9f4765e8) Thanks [@braxtonhall](https://github.com/braxtonhall)! - enhance type inferrence of `match` ## 7.0.0 ### Major Changes - [#553](https://github.com/supermacro/neverthrow/pull/553) [`5a3af0a`](https://github.com/supermacro/neverthrow/commit/5a3af0a55d0c440dfd50bfbbe021c6e4b973184b) Thanks [@m-shaka](https://github.com/m-shaka)! - Declare the minimum supported Node.js version `Neverthrow` does not depend on any Node.js version-specific features, so it should work with any version of Node.js that supports ES6 and other runtimes like Browser, Deno, etc. However, for the sake of maintaining a consistent development environment, we should declare the minimum supported version of Node.js in the `engines` field of the `package.json` file. ================================================ FILE: CODEOWNERS ================================================ * @m-shaka @supermacro ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019 Giorgio Delgado Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # NeverThrow 🙅 [![GitHub Workflow Status](https://github.com/supermacro/neverthrow/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/supermacro/neverthrow/actions) ## Description Encode failure into your program. ![Demo](assets/demo.png) This package contains a `Result` type that represents either success (`Ok`) or failure (`Err`). For asynchronous tasks, `neverthrow` offers a `ResultAsync` class which wraps a `Promise>` and gives you the same level of expressivity and control as a regular `Result`. `ResultAsync` is `thenable` meaning it **behaves exactly like a native `Promise`** ... except you have access to the same methods that `Result` provides without having to `await` or `.then` the promise! Check out [the wiki](https://github.com/supermacro/neverthrow/wiki/Basic-Usage-Examples#asynchronous-api) for examples and best practices. > Need to see real-life examples of how to leverage this package for error handling? See this repo: https://github.com/parlez-vous/server
## Table Of Contents * [Installation](#installation) * [Recommended: Use `eslint-plugin-neverthrow`](#recommended-use-eslint-plugin-neverthrow) * [Top-Level API](#top-level-api) * [API Documentation](#api-documentation) + [Synchronous API (`Result`)](#synchronous-api-result) - [`ok`](#ok) - [`err`](#err) - [`Result.isOk` (method)](#resultisok-method) - [`Result.isErr` (method)](#resultiserr-method) - [`Result.map` (method)](#resultmap-method) - [`Result.mapErr` (method)](#resultmaperr-method) - [`Result.unwrapOr` (method)](#resultunwrapor-method) - [`Result.andThen` (method)](#resultandthen-method) - [`Result.asyncAndThen` (method)](#resultasyncandthen-method) - [`Result.orElse` (method)](#resultorelse-method) - [`Result.match` (method)](#resultmatch-method) - [`Result.asyncMap` (method)](#resultasyncmap-method) - [`Result.andTee` (method)](#resultandtee-method) - [`Result.orTee` (method)](#resultortee-method) - [`Result.andThrough` (method)](#resultandthrough-method) - [`Result.asyncAndThrough` (method)](#resultasyncandthrough-method) - [`Result.fromThrowable` (static class method)](#resultfromthrowable-static-class-method) - [`Result.combine` (static class method)](#resultcombine-static-class-method) - [`Result.combineWithAllErrors` (static class method)](#resultcombinewithallerrors-static-class-method) - [`Result.safeUnwrap()`](#resultsafeunwrap) + [Asynchronous API (`ResultAsync`)](#asynchronous-api-resultasync) - [`okAsync`](#okasync) - [`errAsync`](#errasync) - [`ResultAsync.fromThrowable` (static class method)](#resultasyncfromthrowable-static-class-method) - [`ResultAsync.fromPromise` (static class method)](#resultasyncfrompromise-static-class-method) - [`ResultAsync.fromSafePromise` (static class method)](#resultasyncfromsafepromise-static-class-method) - [`ResultAsync.map` (method)](#resultasyncmap-method) - [`ResultAsync.mapErr` (method)](#resultasyncmaperr-method) - [`ResultAsync.unwrapOr` (method)](#resultasyncunwrapor-method) - [`ResultAsync.andThen` (method)](#resultasyncandthen-method) - [`ResultAsync.orElse` (method)](#resultasyncorelse-method) - [`ResultAsync.match` (method)](#resultasyncmatch-method) - [`ResultAsync.andTee` (method)](#resultasyncandtee-method) - [`ResultAsync.orTee` (method)](#resultasyncortee-method) - [`ResultAsync.andThrough` (method)](#resultasyncandthrough-method) - [`ResultAsync.combine` (static class method)](#resultasynccombine-static-class-method) - [`ResultAsync.combineWithAllErrors` (static class method)](#resultasynccombinewithallerrors-static-class-method) - [`ResultAsync.safeUnwrap()`](#resultasyncsafeunwrap) + [Utilities](#utilities) - [`fromThrowable`](#fromthrowable) - [`fromAsyncThrowable`](#fromasyncthrowable) - [`fromPromise`](#frompromise) - [`fromSafePromise`](#fromsafepromise) - [`safeTry`](#safetry) + [Testing](#testing) * [A note on the Package Name](#a-note-on-the-package-name) ## Installation ```sh > npm install neverthrow ``` ## Recommended: Use `eslint-plugin-neverthrow` As part of `neverthrow`s [bounty program](https://github.com/supermacro/neverthrow/issues/314), user [mdbetancourt](https://github.com/mdbetancourt) created [`eslint-plugin-neverthrow`](https://github.com/mdbetancourt/eslint-plugin-neverthrow) to ensure that errors are not gone unhandled. Install by running: ```sh > npm install eslint-plugin-neverthrow ``` With `eslint-plugin-neverthrow`, you are forced to consume the result in one of the following three ways: - Calling `.match` - Calling `.unwrapOr` - Calling `._unsafeUnwrap` This ensures that you're explicitly handling the error of your `Result`. This plugin is essentially a porting of Rust's [`must-use`](https://doc.rust-lang.org/std/result/#results-must-be-used) attribute. ## Top-Level API `neverthrow` exposes the following: - `ok` convenience function to create an `Ok` variant of `Result` - `err` convenience function to create an `Err` variant of `Result` - `Ok` class and type - `Err` class and type - `Result` Type as well as namespace / object from which to call [`Result.fromThrowable`](#resultfromthrowable-static-class-method), [Result.combine](#resultcombine-static-class-method). - `ResultAsync` class - `okAsync` convenience function to create a `ResultAsync` containing an `Ok` type `Result` - `errAsync` convenience function to create a `ResultAsync` containing an `Err` type `Result` ```typescript import { ok, Ok, err, Err, Result, okAsync, errAsync, ResultAsync, fromAsyncThrowable, fromThrowable, fromPromise, fromSafePromise, safeTry, } from 'neverthrow' ``` --- **Check out the [wiki](https://github.com/supermacro/neverthrow/wiki) for help on how to make the most of `neverthrow`.** If you find this package useful, please consider [sponsoring me](https://github.com/sponsors/supermacro/) or simply [buying me a coffee](https://ko-fi.com/gdelgado)! --- ## API Documentation ### Synchronous API (`Result`) #### `ok` Constructs an `Ok` variant of `Result` **Signature:** ```typescript ok(value: T): Ok { ... } ``` **Example:** ```typescript import { ok } from 'neverthrow' const myResult = ok({ myData: 'test' }) // instance of `Ok` myResult.isOk() // true myResult.isErr() // false ``` [⬆️ Back to top](#toc) --- #### `err` Constructs an `Err` variant of `Result` **Signature:** ```typescript err(error: E): Err { ... } ``` **Example:** ```typescript import { err } from 'neverthrow' const myResult = err('Oh noooo') // instance of `Err` myResult.isOk() // false myResult.isErr() // true ``` [⬆️ Back to top](#toc) --- #### `Result.isOk` (method) Returns `true` if the result is an `Ok` variant **Signature:** ```typescript isOk(): boolean { ... } ``` [⬆️ Back to top](#toc) --- #### `Result.isErr` (method) Returns `true` if the result is an `Err` variant **Signature**: ```typescript isErr(): boolean { ... } ``` [⬆️ Back to top](#toc) --- #### `Result.map` (method) Maps a `Result` to `Result` by applying a function to a contained `Ok` value, leaving an `Err` value untouched. This function can be used to compose the results of two functions. **Signature:** ```typescript class Result { map(callback: (value: T) => U): Result { ... } } ``` **Example**: ```typescript import { getLines } from 'imaginary-parser' // ^ assume getLines has the following signature: // getLines(str: string): Result, Error> // since the formatting is deemed correct by `getLines` // then it means that `linesResult` is an Ok // containing an Array of strings for each line of code const linesResult = getLines('1\n2\n3\n4\n') // this Result now has a Array inside it const newResult = linesResult.map( (arr: Array) => arr.map(parseInt) ) newResult.isOk() // true ``` [⬆️ Back to top](#toc) --- #### `Result.mapErr` (method) Maps a `Result` to `Result` by applying a function to a contained `Err` value, leaving an `Ok` value untouched. This function can be used to pass through a successful result while handling an error. **Signature:** ```typescript class Result { mapErr(callback: (error: E) => F): Result { ... } } ``` **Example**: ```typescript import { parseHeaders } from 'imaginary-http-parser' // imagine that parseHeaders has the following signature: // parseHeaders(raw: string): Result const rawHeaders = 'nonsensical gibberish and badly formatted stuff' const parseResult = parseHeaders(rawHeaders) parseResult.mapErr(parseError => { res.status(400).json({ error: parseError }) }) parseResult.isErr() // true ``` [⬆️ Back to top](#toc) --- #### `Result.unwrapOr` (method) Unwrap the `Ok` value, or return the default if there is an `Err` **Signature:** ```typescript class Result { unwrapOr(value: T): T { ... } } ``` **Example**: ```typescript const myResult = err('Oh noooo') const multiply = (value: number): number => value * 2 const unwrapped: number = myResult.map(multiply).unwrapOr(10) ``` [⬆️ Back to top](#toc) --- #### `Result.andThen` (method) Same idea as `map` above. Except you must return a new `Result`. The returned value will be a `Result`. As of `v4.1.0-beta`, you are able to return distinct error types (see signature below). Prior to `v4.1.0-beta`, the error type could not be distinct. This is useful for when you need to do a subsequent computation using the inner `T` value, but that computation might fail. Additionally, `andThen` is really useful as a tool to flatten a `Result, E1>` into a `Result` (see example below). **Signature:** ```typescript class Result { // Note that the latest version lets you return distinct errors as well. // If the error types (E and F) are the same (like `string | string`) // then they will be merged into one type (`string`) andThen( callback: (value: T) => Result ): Result { ... } } ``` **Example 1: Chaining Results** ```typescript import { err, ok } from 'neverthrow' const sq = (n: number): Result => ok(n ** 2) ok(2) .andThen(sq) .andThen(sq) // Ok(16) ok(2) .andThen(sq) .andThen(err) // Err(4) ok(2) .andThen(err) .andThen(sq) // Err(2) err(3) .andThen(sq) .andThen(sq) // Err(3) ``` **Example 2: Flattening Nested Results** ```typescript // It's common to have nested Results const nested = ok(ok(1234)) // notNested is a Ok(1234) const notNested = nested.andThen((innerResult) => innerResult) ``` [⬆️ Back to top](#toc) --- #### `Result.asyncAndThen` (method) Same idea as [`andThen` above](#resultandthen-method), except you must return a new `ResultAsync`. The returned value will be a `ResultAsync`. **Signature:** ```typescript class Result { asyncAndThen( callback: (value: T) => ResultAsync ): ResultAsync { ... } } ``` [⬆️ Back to top](#toc) --- #### `Result.orElse` (method) Takes an `Err` value and maps it to a `Result`. This is useful for error recovery. **Signature:** ```typescript class Result { orElse( callback: (error: E) => Result ): Result { ... } } ``` **Example:** ```typescript enum DatabaseError { PoolExhausted = 'PoolExhausted', NotFound = 'NotFound', } const dbQueryResult: Result = err(DatabaseError.NotFound) const updatedQueryResult = dbQueryResult.orElse((dbError) => dbError === DatabaseError.NotFound ? ok('User does not exist') // error recovery branch: ok() must be called with a value of type string // // // err() can be called with a value of any new type that you want // it could also be called with the same error value // // err(dbError) : err(500) ) ``` [⬆️ Back to top](#toc) --- #### `Result.match` (method) Given 2 functions (one for the `Ok` variant and one for the `Err` variant) execute the function that matches the `Result` variant. Match callbacks do not necessitate to return a `Result`, however you can return a `Result` if you want to. **Signature:** ```typescript class Result { match( okCallback: (value: T) => A, errorCallback: (error: E) => B ): A | B => { ... } } ``` `match` is like chaining `map` and `mapErr`, with the distinction that with `match` both functions must have the same return type. The differences between `match` and chaining `map` and `mapErr` are that: - with `match` both functions must have the same return type `A` - `match` unwraps the `Result` into an `A` (the match functions' return type) - This makes no difference if you are performing side effects only **Example:** ```typescript // map/mapErr api // note that you DON'T have to append mapErr // after map which means that you are not required to do // error handling computationThatMightFail().map(console.log).mapErr(console.error) // match api // works exactly the same as above since both callbacks // only perform side effects, // except, now you HAVE to do error handling :) computationThatMightFail().match(console.log, console.error) // Returning values const attempt = computationThatMightFail() .map((str) => str.toUpperCase()) .mapErr((err) => `Error: ${err}`) // `attempt` is of type `Result` const answer = computationThatMightFail().match( (str) => str.toUpperCase(), (err) => `Error: ${err}` ) // `answer` is of type `string` ``` If you don't use the error parameter in your match callback then `match` is equivalent to chaining `map` with `unwrapOr`: ```ts const answer = computationThatMightFail().match( (str) => str.toUpperCase(), () => 'ComputationError' ) // `answer` is of type `string` const answer = computationThatMightFail() .map((str) => str.toUpperCase()) .unwrapOr('ComputationError') ``` [⬆️ Back to top](#toc) --- #### `Result.asyncMap` (method) Similar to `map` except for two things: - the mapping function must return a `Promise` - asyncMap returns a `ResultAsync` You can then chain the result of `asyncMap` using the `ResultAsync` apis (like `map`, `mapErr`, `andThen`, etc.) **Signature:** ```typescript class Result { asyncMap( callback: (value: T) => Promise ): ResultAsync { ... } } ``` **Example:** ```typescript import { parseHeaders } from 'imaginary-http-parser' // imagine that parseHeaders has the following signature: // parseHeaders(raw: string): Result const asyncRes = parseHeaders(rawHeader) .map(headerKvMap => headerKvMap.Authorization) .asyncMap(findUserInDatabase) ``` Note that in the above example if `parseHeaders` returns an `Err` then `.map` and `.asyncMap` will not be invoked, and `asyncRes` variable will resolve to an `Err` when turned into a `Result` using `await` or `.then()`. [⬆️ Back to top](#toc) --- #### `Result.andTee` (method) Takes a `Result` and lets the original `Result` pass through regardless the result of the passed-in function. This is a handy way to handle side effects whose failure or success should not affect your main logics such as logging. **Signature:** ```typescript class Result { andTee( callback: (value: T) => unknown ): Result { ... } } ``` **Example:** ```typescript import { parseUserInput } from 'imaginary-parser' import { logUser } from 'imaginary-logger' import { insertUser } from 'imaginary-database' // ^ assume parseUserInput, logUser and insertUser have the following signatures: // parseUserInput(input: RequestData): Result // logUser(user: User): Result // insertUser(user: User): ResultAsync // Note logUser returns void upon success but insertUser takes User type. const resAsync = parseUserInput(userInput) .andTee(logUser) .asyncAndThen(insertUser) // Note no LogError shows up in the Result type resAsync.then((res: Result) => { if(res.isErr()){ console.log("Oops, at least one step failed", res.error) } else{ console.log("User input has been parsed and inserted successfully.") } }) ``` [⬆️ Back to top](#toc) --- #### `Result.orTee` (method) Like `andTee` for the error track. Takes a `Result` and lets the `Err` value pass through regardless the result of the passed-in function. This is a handy way to handle side effects whose failure or success should not affect your main logics such as logging. **Signature:** ```typescript class Result { orTee( callback: (value: E) => unknown ): Result { ... } } ``` **Example:** ```typescript import { parseUserInput } from 'imaginary-parser' import { logParseError } from 'imaginary-logger' import { insertUser } from 'imaginary-database' // ^ assume parseUserInput, logParseError and insertUser have the following signatures: // parseUserInput(input: RequestData): Result // logParseError(parseError: ParseError): Result // insertUser(user: User): ResultAsync // Note logParseError returns void upon success but insertUser takes User type. const resAsync = parseUserInput(userInput) .orTee(logParseError) .asyncAndThen(insertUser) // Note no LogError shows up in the Result type resAsync.then((res: Result) => { if(res.isErr()){ console.log("Oops, at least one step failed", res.error) } else{ console.log("User input has been parsed and inserted successfully.") } }) ``` [⬆️ Back to top](#toc) --- #### `Result.andThrough` (method) Similar to `andTee` except for: - when there is an error from the passed-in function, that error will be passed along. **Signature:** ```typescript class Result { andThrough( callback: (value: T) => Result ): Result { ... } } ``` **Example:** ```typescript import { parseUserInput } from 'imaginary-parser' import { validateUser } from 'imaginary-validator' import { insertUser } from 'imaginary-database' // ^ assume parseUseInput, validateUser and insertUser have the following signatures: // parseUserInput(input: RequestData): Result // validateUser(user: User): Result // insertUser(user: User): ResultAsync // Note validateUser returns void upon success but insertUser takes User type. const resAsync = parseUserInput(userInput) .andThrough(validateUser) .asyncAndThen(insertUser) resAsync.then((res: Result) => { if(res.isErr()){ console.log("Oops, at least one step failed", res.error) } else{ console.log("User input has been parsed, validated, inserted successfully.") } }) ``` [⬆️ Back to top](#toc) --- #### `Result.asyncAndThrough` (method) Similar to `andThrough` except you must return a ResultAsync. You can then chain the result of `asyncAndThrough` using the `ResultAsync` apis (like `map`, `mapErr`, `andThen`, etc.) **Signature:** ```typescript import { parseUserInput } from 'imaginary-parser' import { insertUser } from 'imaginary-database' import { sendNotification } from 'imaginary-service' // ^ assume parseUserInput, insertUser and sendNotification have the following signatures: // parseUserInput(input: RequestData): Result // insertUser(user: User): ResultAsync // sendNotification(user: User): ResultAsync // Note insertUser returns void upon success but sendNotification takes User type. const resAsync = parseUserInput(userInput) .asyncAndThrough(insertUser) .andThen(sendNotification) resAsync.then((res: Result) => { if(res.isErr()){ console.log("Oops, at least one step failed", res.error) } else{ console.log("User has been parsed, inserted and notified successfully.") } }) ``` [⬆️ Back to top](#toc) --- #### `Result.fromThrowable` (static class method) > Although Result is not an actual JS class, the way that `fromThrowable` has been implemented requires that you call `fromThrowable` as though it were a static method on `Result`. See examples below. The JavaScript community has agreed on the convention of throwing exceptions. As such, when interfacing with third party libraries it's imperative that you wrap third-party code in try / catch blocks. This function will create a new function that returns an `Err` when the original function throws. It is not possible to know the types of the errors thrown in the original function, therefore it is recommended to use the second argument `errorFn` to map what is thrown to a known type. **Example**: ```typescript import { Result } from 'neverthrow' type ParseError = { message: string } const toParseError = (): ParseError => ({ message: "Parse Error" }) const safeJsonParse = Result.fromThrowable(JSON.parse, toParseError) // the function can now be used safely, if the function throws, the result will be an Err const res = safeJsonParse("{"); ``` [⬆️ Back to top](#toc) --- #### `Result.combine` (static class method) > Although Result is not an actual JS class, the way that `combine` has been implemented requires that you call `combine` as though it were a static method on `Result`. See examples below. Combine lists of `Result`s. If you're familiar with `Promise.all`, the combine function works conceptually the same. **`combine` works on both heterogeneous and homogeneous lists**. This means that you can have lists that contain different kinds of `Result`s and still be able to combine them. Note that you cannot combine lists that contain both `Result`s **and** `ResultAsync`s. The combine function takes a list of results and returns a single result. If all the results in the list are `Ok`, then the return value will be a `Ok` containing a list of all the individual `Ok` values. If just one of the results in the list is an `Err` then the combine function returns that Err value (it short circuits and returns the first Err that it finds). Formally speaking: ```typescript // homogeneous lists function combine(resultList: Result[]): Result // heterogeneous lists function combine(resultList: [ Result, Result ]): Result<[ T1, T2 ], E1 | E2> function combine => Result<[ T1, T2, T3 ], E1 | E2 | E3> function combine => Result<[ T1, T2, T3, T4 ], E1 | E2 | E3 | E4> // ... etc etc ad infinitum ``` Example: ```typescript const resultList: Result[] = [ok(1), ok(2)] const combinedList: Result = Result.combine(resultList) ``` Example with tuples: ```typescript /** @example tuple(1, 2, 3) === [1, 2, 3] // with type [number, number, number] */ const tuple = (...args: T): T => args const resultTuple: [Result, Result] = tuple(ok('a'), ok('b')) const combinedTuple: Result<[string, string], unknown> = Result.combine(resultTuple) ``` [⬆️ Back to top](#toc) --- #### `Result.combineWithAllErrors` (static class method) > Although Result is not an actual JS class, the way that `combineWithAllErrors` has been implemented requires that you call `combineWithAllErrors` as though it were a static method on `Result`. See examples below. Like `combine` but without short-circuiting. Instead of just the first error value, you get a list of all error values of the input result list. If only some results fail, the new combined error list will only contain the error value of the failed results, meaning that there is no guarantee of the length of the new error list. Function signature: ```typescript // homogeneous lists function combineWithAllErrors(resultList: Result[]): Result // heterogeneous lists function combineWithAllErrors(resultList: [ Result, Result ]): Result<[ T1, T2 ], (E1 | E2)[]> function combineWithAllErrors => Result<[ T1, T2, T3 ], (E1 | E2 | E3)[]> function combineWithAllErrors => Result<[ T1, T2, T3, T4 ], (E1 | E2 | E3 | E4)[]> // ... etc etc ad infinitum ``` Example usage: ```typescript const resultList: Result[] = [ ok(123), err('boooom!'), ok(456), err('ahhhhh!'), ] const result = Result.combineWithAllErrors(resultList) // result is Err(['boooom!', 'ahhhhh!']) ``` [⬆️ Back to top](#toc) #### `Result.safeUnwrap()` **Deprecated**. You don't need to use this method anymore. Allows for unwrapping a `Result` or returning an `Err` implicitly, thereby reducing boilerplate. [⬆️ Back to top](#toc) --- ### Asynchronous API (`ResultAsync`) #### `okAsync` Constructs an `Ok` variant of `ResultAsync` **Signature:** ```typescript okAsync(value: T): ResultAsync ``` **Example:** ```typescript import { okAsync } from 'neverthrow' const myResultAsync = okAsync({ myData: 'test' }) // instance of `ResultAsync` const myResult = await myResultAsync // instance of `Ok` myResult.isOk() // true myResult.isErr() // false ``` [⬆️ Back to top](#toc) --- #### `errAsync` Constructs an `Err` variant of `ResultAsync` **Signature:** ```typescript errAsync(error: E): ResultAsync ``` **Example:** ```typescript import { errAsync } from 'neverthrow' const myResultAsync = errAsync('Oh nooo') // instance of `ResultAsync` const myResult = await myResultAsync // instance of `Err` myResult.isOk() // false myResult.isErr() // true ``` [⬆️ Back to top](#toc) --- #### `ResultAsync.fromThrowable` (static class method) Similar to [Result.fromThrowable](#resultfromthrowable-static-class-method), but for functions that return a `Promise`. **Example**: ```typescript import { ResultAsync } from 'neverthrow' import { insertIntoDb } from 'imaginary-database' // insertIntoDb(user: User): Promise const insertUser = ResultAsync.fromThrowable(insertIntoDb, () => new Error('Database error')) // `res` has a type of (user: User) => ResultAsync ``` Note that this can be safer than using [ResultAsync.fromPromise](#resultasyncfrompromise-static-class-method) with the result of a function call, because not all functions that return a `Promise` are `async`, and thus they can throw errors synchronously rather than returning a rejected `Promise`. For example: ```typescript // NOT SAFE !! import { ResultAsync } from 'neverthrow' import { db } from 'imaginary-database' // db.insert(table: string, value: T): Promise const insertUser = (user: User): Promise => { if (!user.id) { // this throws synchronously! throw new TypeError('missing user id') } return db.insert('users', user) } // this will throw, NOT return a `ResultAsync` const res = ResultAsync.fromPromise(insertIntoDb(myUser), () => new Error('Database error')) ``` [⬆️ Back to top](#toc) --- #### `ResultAsync.fromPromise` (static class method) Transforms a `PromiseLike` (that may throw) into a `ResultAsync`. The second argument handles the rejection case of the promise and maps the error from `unknown` into some type `E`. **Signature:** ```typescript // fromPromise is a static class method // also available as a standalone function // import { fromPromise } from 'neverthrow' ResultAsync.fromPromise( promise: PromiseLike, errorHandler: (unknownError: unknown) => E) ): ResultAsync { ... } ``` If you are working with `PromiseLike` objects that you **know for a fact** will not throw, then use `fromSafePromise` in order to avoid having to pass a redundant `errorHandler` argument. **Example**: ```typescript import { ResultAsync } from 'neverthrow' import { insertIntoDb } from 'imaginary-database' // insertIntoDb(user: User): Promise const res = ResultAsync.fromPromise(insertIntoDb(myUser), () => new Error('Database error')) // `res` has a type of ResultAsync ``` [⬆️ Back to top](#toc) --- #### `ResultAsync.fromSafePromise` (static class method) Same as `ResultAsync.fromPromise` except that it does not handle the rejection of the promise. **Ensure you know what you're doing, otherwise a thrown exception within this promise will cause ResultAsync to reject, instead of resolve to a Result.** **Signature:** ```typescript // fromPromise is a static class method // also available as a standalone function // import { fromPromise } from 'neverthrow' ResultAsync.fromSafePromise( promise: PromiseLike ): ResultAsync { ... } ``` **Example**: ```typescript import { RouteError } from 'routes/error' // simulate slow routes in an http server that works in a Result / ResultAsync context // Adopted from https://github.com/parlez-vous/server/blob/2496bacf55a2acbebc30631b5562f34272794d76/src/routes/common/signup.ts export const slowDown = (ms: number) => (value: T) => ResultAsync.fromSafePromise( new Promise((resolve) => { setTimeout(() => { resolve(value) }, ms) }) ) export const signupHandler = route((req, sessionManager) => decode(userSignupDecoder, req.body, 'Invalid request body').map((parsed) => { return createUser(parsed) .andThen(slowDown(3000)) // slowdown by 3 seconds .andThen(sessionManager.createSession) .map(({ sessionToken, admin }) => AppData.init(admin, sessionToken)) }) ) ``` [⬆️ Back to top](#toc) --- #### `ResultAsync.map` (method) Maps a `ResultAsync` to `ResultAsync` by applying a function to a contained `Ok` value, leaving an `Err` value untouched. The applied function can be synchronous or asynchronous (returning a `Promise`) with no impact to the return type. This function can be used to compose the results of two functions. **Signature:** ```typescript class ResultAsync { map( callback: (value: T) => U | Promise ): ResultAsync { ... } } ``` **Example**: ```typescript import { findUsersIn } from 'imaginary-database' // ^ assume findUsersIn has the following signature: // findUsersIn(country: string): ResultAsync, Error> const usersInCanada = findUsersIn("Canada") // Let's assume we only need their names const namesInCanada = usersInCanada.map((users: Array) => users.map(user => user.name)) // namesInCanada is of type ResultAsync, Error> // We can extract the Result using .then() or await namesInCanada.then((namesResult: Result, Error>) => { if(namesResult.isErr()){ console.log("Couldn't get the users from the database", namesResult.error) } else{ console.log("Users in Canada are named: " + namesResult.value.join(',')) } }) ``` [⬆️ Back to top](#toc) --- #### `ResultAsync.mapErr` (method) Maps a `ResultAsync` to `ResultAsync` by applying a function to a contained `Err` value, leaving an `Ok` value untouched. The applied function can be synchronous or asynchronous (returning a `Promise`) with no impact to the return type. This function can be used to pass through a successful result while handling an error. **Signature:** ```typescript class ResultAsync { mapErr( callback: (error: E) => F | Promise ): ResultAsync { ... } } ``` **Example**: ```typescript import { findUsersIn } from 'imaginary-database' // ^ assume findUsersIn has the following signature: // findUsersIn(country: string): ResultAsync, Error> // Let's say we need to low-level errors from findUsersIn to be more readable const usersInCanada = findUsersIn("Canada").mapErr((error: Error) => { // The only error we want to pass to the user is "Unknown country" if(error.message === "Unknown country"){ return error.message } // All other errors will be labelled as a system error return "System error, please contact an administrator." }) // usersInCanada is of type ResultAsync, string> usersInCanada.then((usersResult: Result, string>) => { if(usersResult.isErr()){ res.status(400).json({ error: usersResult.error }) } else{ res.status(200).json({ users: usersResult.value }) } }) ``` [⬆️ Back to top](#toc) --- #### `ResultAsync.unwrapOr` (method) Unwrap the `Ok` value, or return the default if there is an `Err`. Works just like `Result.unwrapOr` but returns a `Promise` instead of `T`. **Signature:** ```typescript class ResultAsync { unwrapOr(value: T): Promise { ... } } ``` **Example**: ```typescript const unwrapped: number = await errAsync(0).unwrapOr(10) // unwrapped = 10 ``` [⬆️ Back to top](#toc) --- #### `ResultAsync.andThen` (method) Same idea as `map` above. Except the applied function must return a `Result` or `ResultAsync`. `ResultAsync.andThen` always returns a `ResultAsync` no matter the return type of the applied function. This is useful for when you need to do a subsequent computation using the inner `T` value, but that computation might fail. `andThen` is really useful as a tool to flatten a `ResultAsync, E1>` into a `ResultAsync` (see example below). **Signature:** ```typescript // Note that the latest version (v4.1.0-beta) lets you return distinct errors as well. // If the error types (E and F) are the same (like `string | string`) // then they will be merged into one type (`string`) class ResultAsync { andThen( callback: (value: T) => Result | ResultAsync ): ResultAsync { ... } } ``` **Example** ```typescript import { validateUser } from 'imaginary-validator' import { insertUser } from 'imaginary-database' import { sendNotification } from 'imaginary-service' // ^ assume validateUser, insertUser and sendNotification have the following signatures: // validateUser(user: User): Result // insertUser(user): ResultAsync // sendNotification(user): ResultAsync const resAsync = validateUser(user) .andThen(insertUser) .andThen(sendNotification) // resAsync is a ResultAsync resAsync.then((res: Result) => { if(res.isErr()){ console.log("Oops, at least one step failed", res.error) } else{ console.log("User has been validated, inserted and notified successfully.") } }) ``` [⬆️ Back to top](#toc) --- #### `ResultAsync.orElse` (method) Takes an `Err` value and maps it to a `ResultAsync`. This is useful for error recovery. **Signature:** ```typescript class ResultAsync { orElse( callback: (error: E) => Result | ResultAsync ): ResultAsync { ... } } ``` [⬆️ Back to top](#toc) --- #### `ResultAsync.match` (method) Given 2 functions (one for the `Ok` variant and one for the `Err` variant) execute the function that matches the `ResultAsync` variant. The difference with `Result.match` is that it always returns a `Promise` because of the asynchronous nature of the `ResultAsync`. **Signature:** ```typescript class ResultAsync { match( okCallback: (value: T) => A, errorCallback: (error: E) => B ): Promise => { ... } } ``` **Example:** ```typescript import { validateUser } from 'imaginary-validator' import { insertUser } from 'imaginary-database' // ^ assume validateUser and insertUser have the following signatures: // validateUser(user: User): Result // insertUser(user): ResultAsync // Handle both cases at the end of the chain using match const resultMessage = await validateUser(user) .andThen(insertUser) .match( (user: User) => `User ${user.name} has been successfully created`, (error: Error) => `User could not be created because ${error.message}` ) // resultMessage is a string ``` [⬆️ Back to top](#toc) --- #### `ResultAsync.andTee` (method) Takes a `ResultAsync` and lets the original `ResultAsync` pass through regardless the result of the passed-in function. This is a handy way to handle side effects whose failure or success should not affect your main logics such as logging. **Signature:** ```typescript class ResultAsync { andTee( callback: (value: T) => unknown ): ResultAsync => { ... } } ``` **Example:** ```typescript import { insertUser } from 'imaginary-database' import { logUser } from 'imaginary-logger' import { sendNotification } from 'imaginary-service' // ^ assume insertUser, logUser and sendNotification have the following signatures: // insertUser(user: User): ResultAsync // logUser(user: User): Result // sendNotification(user: User): ResultAsync // Note logUser returns void on success but sendNotification takes User type. const resAsync = insertUser(user) .andTee(logUser) .andThen(sendNotification) // Note there is no LogError in the types below resAsync.then((res: Result) => { if(res.isErr()){ console.log("Oops, at least one step failed", res.error) } else{ console.log("User has been inserted and notified successfully.") } }) ``` [⬆️ Back to top](#toc) --- #### `ResultAsync.orTee` (method) Like `andTee` for the error track. Takes a `ResultAsync` and lets the original `Err` value pass through regardless the result of the passed-in function. This is a handy way to handle side effects whose failure or success should not affect your main logics such as logging. **Signature:** ```typescript class ResultAsync { orTee( callback: (value: E) => unknown ): ResultAsync => { ... } } ``` **Example:** ```typescript import { insertUser } from 'imaginary-database' import { logInsertError } from 'imaginary-logger' import { sendNotification } from 'imaginary-service' // ^ assume insertUser, logInsertError and sendNotification have the following signatures: // insertUser(user: User): ResultAsync // logInsertError(insertError: InsertError): Result // sendNotification(user: User): ResultAsync // Note logInsertError returns void on success but sendNotification takes User type. const resAsync = insertUser(user) .orTee(logInsertError) .andThen(sendNotification) // Note there is no LogError in the types below resAsync.then((res: Result) => { if(res.isErr()){ console.log("Oops, at least one step failed", res.error) } else{ console.log("User has been inserted and notified successfully.") } }) ``` [⬆️ Back to top](#toc) --- #### `ResultAsync.andThrough` (method) Similar to `andTee` except for: - when there is an error from the passed-in function, that error will be passed along. **Signature:** ```typescript class ResultAsync { andThrough( callback: (value: T) => Result | ResultAsync, ): ResultAsync => { ... } } ``` **Example:** ```typescript import { buildUser } from 'imaginary-builder' import { insertUser } from 'imaginary-database' import { sendNotification } from 'imaginary-service' // ^ assume buildUser, insertUser and sendNotification have the following signatures: // buildUser(userRaw: UserRaw): ResultAsync // insertUser(user: User): ResultAsync // sendNotification(user: User): ResultAsync // Note insertUser returns void upon success but sendNotification takes User type. const resAsync = buildUser(userRaw) .andThrough(insertUser) .andThen(sendNotification) resAsync.then((res: Result) => { if(res.isErr()){ console.log("Oops, at least one step failed", res.error) } else{ console.log("User data has been built, inserted and notified successfully.") } }) ``` [⬆️ Back to top](#toc) --- #### `ResultAsync.combine` (static class method) Combine lists of `ResultAsync`s. If you're familiar with `Promise.all`, the combine function works conceptually the same. **`combine` works on both heterogeneous and homogeneous lists**. This means that you can have lists that contain different kinds of `ResultAsync`s and still be able to combine them. Note that you cannot combine lists that contain both `Result`s **and** `ResultAsync`s. The combine function takes a list of results and returns a single result. If all the results in the list are `Ok`, then the return value will be a `Ok` containing a list of all the individual `Ok` values. If just one of the results in the list is an `Err` then the combine function returns that Err value (it short circuits and returns the first Err that it finds). Formally speaking: ```typescript // homogeneous lists function combine(resultList: ResultAsync[]): ResultAsync // heterogeneous lists function combine(resultList: [ ResultAsync, ResultAsync ]): ResultAsync<[ T1, T2 ], E1 | E2> function combine => ResultAsync<[ T1, T2, T3 ], E1 | E2 | E3> function combine => ResultAsync<[ T1, T2, T3, T4 ], E1 | E2 | E3 | E4> // ... etc etc ad infinitum ``` Example: ```typescript const resultList: ResultAsync[] = [okAsync(1), okAsync(2)] const combinedList: ResultAsync = ResultAsync.combine(resultList) ``` Example with tuples: ```typescript /** @example tuple(1, 2, 3) === [1, 2, 3] // with type [number, number, number] */ const tuple = (...args: T): T => args const resultTuple: [ResultAsync, ResultAsync] = tuple(okAsync('a'), okAsync('b')) const combinedTuple: ResultAsync<[string, string], unknown> = ResultAsync.combine(resultTuple) ``` [⬆️ Back to top](#toc) --- #### `ResultAsync.combineWithAllErrors` (static class method) Like `combine` but without short-circuiting. Instead of just the first error value, you get a list of all error values of the input result list. If only some results fail, the new combined error list will only contain the error value of the failed results, meaning that there is no guarantee of the length of the new error list. Function signature: ```typescript // homogeneous lists function combineWithAllErrors(resultList: ResultAsync[]): ResultAsync // heterogeneous lists function combineWithAllErrors(resultList: [ ResultAsync, ResultAsync ]): ResultAsync<[ T1, T2 ], (E1 | E2)[]> function combineWithAllErrors => ResultAsync<[ T1, T2, T3 ], (E1 | E2 | E3)[]> function combineWithAllErrors => ResultAsync<[ T1, T2, T3, T4 ], (E1 | E2 | E3 | E4)[]> // ... etc etc ad infinitum ``` Example usage: ```typescript const resultList: ResultAsync[] = [ okAsync(123), errAsync('boooom!'), okAsync(456), errAsync('ahhhhh!'), ] const result = ResultAsync.combineWithAllErrors(resultList) // result is Err(['boooom!', 'ahhhhh!']) ``` #### `ResultAsync.safeUnwrap()` **Deprecated**. You don't need to use this method anymore. Allows for unwrapping a `Result` or returning an `Err` implicitly, thereby reducing boilerplate. [⬆️ Back to top](#toc) --- ### Utilities #### `fromThrowable` Top level export of `Result.fromThrowable`. Please find documentation at [Result.fromThrowable](#resultfromthrowable-static-class-method) [⬆️ Back to top](#toc) #### `fromAsyncThrowable` Top level export of `ResultAsync.fromThrowable`. Please find documentation at [ResultAsync.fromThrowable](#resultasyncfromthrowable-static-class-method) [⬆️ Back to top](#toc) #### `fromPromise` Top level export of `ResultAsync.fromPromise`. Please find documentation at [ResultAsync.fromPromise](#resultasyncfrompromise-static-class-method) [⬆️ Back to top](#toc) #### `fromSafePromise` Top level export of `ResultAsync.fromSafePromise`. Please find documentation at [ResultAsync.fromSafePromise](#resultasyncfromsafepromise-static-class-method) [⬆️ Back to top](#toc) #### `safeTry` Used to implicitly return errors and reduce boilerplate. Let's say we are writing a function that returns a `Result`, and in that function we call some functions which also return `Result`s and we check those results to see whether we should keep going or abort. Usually, we will write like the following. ```typescript declare function mayFail1(): Result; declare function mayFail2(): Result; function myFunc(): Result { // We have to define a constant to hold the result to check and unwrap its value. const result1 = mayFail1(); if (result1.isErr()) { return err(`aborted by an error from 1st function, ${result1.error}`); } const value1 = result1.value // Again, we need to define a constant and then check and unwrap. const result2 = mayFail2(); if (result2.isErr()) { return err(`aborted by an error from 2nd function, ${result2.error}`); } const value2 = result2.value // And finally we return what we want to calculate return ok(value1 + value2); } ``` Basically, we need to define a constant for each result to check whether it's a `Ok` and read its `.value` or `.error`. With safeTry, we can state 'Return here if its an `Err`, otherwise unwrap it here and keep going.' in just one expression. ```typescript declare function mayFail1(): Result; declare function mayFail2(): Result; function myFunc(): Result { return safeTry(function*() { return ok( // If the result of mayFail1().mapErr() is an `Err`, the evaluation is // aborted here and the enclosing `safeTry` block is evaluated to that `Err`. // Otherwise, this `(yield* ...)` is evaluated to its `.value`. (yield* mayFail1() .mapErr(e => `aborted by an error from 1st function, ${e}`)) + // The same as above. (yield* mayFail2() .mapErr(e => `aborted by an error from 2nd function, ${e}`)) ) }) } ``` To use `safeTry`, the points are as follows. * Wrap the entire block in a [generator function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*) * In that block, you can use `yield* ` to state 'Return `` if it's an `Err`, otherwise evaluate to its `.value`' * Pass the generator function to `safeTry` You can also use [async generator function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function*) to pass an async block to `safeTry`. ```typescript // You can use either Promise or ResultAsync. declare function mayFail1(): Promise>; declare function mayFail2(): ResultAsync; function myFunc(): Promise> { return safeTry(async function*() { return ok( // You have to await if the expression is Promise (yield* (await mayFail1()) .mapErr(e => `aborted by an error from 1st function, ${e}`)) + // You can call `safeUnwrap` directly if its ResultAsync (yield* mayFail2() .mapErr(e => `aborted by an error from 2nd function, ${e}`)) ) }) } ``` For more information, see https://github.com/supermacro/neverthrow/pull/448 and https://github.com/supermacro/neverthrow/issues/444 [⬆️ Back to top](#toc) --- ### Testing `Result` instances have two unsafe methods, aptly called `_unsafeUnwrap` and `_unsafeUnwrapErr` which **should only be used in a test environment**. `_unsafeUnwrap` takes a `Result` and returns a `T` when the result is an `Ok`, otherwise it throws a custom object. `_unsafeUnwrapErr` takes a `Result` and returns a `E` when the result is an `Err`, otherwise it throws a custom object. That way you can do something like: ```typescript expect(myResult._unsafeUnwrap()).toBe(someExpectation) ``` However, do note that `Result` instances are comparable. So you don't necessarily need to unwrap them in order to assert expectations in your tests. So you could also do something like this: ```typescript import { ok } from 'neverthrow' // ... expect(callSomeFunctionThatReturnsAResult("with", "some", "args")).toEqual(ok(someExpectation)); ``` By default, the thrown value does not contain a stack trace. This is because stack trace generation [makes error messages in Jest harder to understand](https://github.com/supermacro/neverthrow/pull/215). If you want stack traces to be generated, call `_unsafeUnwrap` and / or `_unsafeUnwrapErr` with a config object: ```typescript _unsafeUnwrapErr({ withStackTrace: true, }) // ^ Now the error object will have a `.stack` property containing the current stack ``` --- If you find this package useful, please consider [sponsoring me](https://github.com/sponsors/supermacro/) or simply [buying me a coffee](https://ko-fi.com/gdelgado)! --- ## A note on the Package Name Although the package is called `neverthrow`, please don't take this literally. I am simply encouraging the developer to think a bit more about the ergonomics and usage of whatever software they are writing. `Throw`ing and `catching` is very similar to using `goto` statements - in other words; it makes reasoning about your programs harder. Secondly, by using `throw` you make the assumption that the caller of your function is implementing `catch`. This is a known source of errors. Example: One dev `throw`s and another dev uses the function without prior knowledge that the function will throw. Thus, and edge case has been left unhandled and now you have unhappy users, bosses, cats, etc. With all that said, there are definitely good use cases for throwing in your program. But much less than you might think. ### License The neverthrow project is available as open source under the terms of the [MIT license](https://github.com/supermacro/neverthrow/blob/master/LICENSE). ================================================ FILE: jest.config.js ================================================ module.exports = { preset: 'ts-jest', testEnvironment: 'node', }; ================================================ FILE: package.json ================================================ { "name": "neverthrow", "version": "8.2.0", "description": "Stop throwing errors, and instead return Results!", "main": "dist/index.cjs.js", "module": "dist/index.es.js", "types": "dist/index.d.ts", "files": [ "dist" ], "scripts": { "local-ci": "npm run typecheck && npm run lint && npm run test && npm run format && npm run build", "test": "vitest run && npm run test-types", "test-types": "tsc --noEmit -p ./tests/tsconfig.tests.json", "lint": "eslint ./src --ext .ts", "format": "prettier --write 'src/**/*.ts?(x)' && npm run lint -- --fix", "typecheck": "tsc --noEmit", "clean": "rm -rf ./dist ./tmp", "build": "npm run clean && rollup --config && mv tmp/*.js dist && attw --pack .", "prepublishOnly": "npm run build", "release": "changeset publish", "version": "changeset version && npm i --lockfile-only" }, "repository": { "type": "git", "url": "git+https://github.com/supermacro/neverthrow.git" }, "author": "Giorgio Delgado", "license": "MIT", "bugs": { "url": "https://github.com/supermacro/neverthrow/issues" }, "homepage": "https://github.com/supermacro/neverthrow#readme", "devDependencies": { "@arethetypeswrong/cli": "^0.17.3", "@changesets/changelog-github": "^0.5.0", "@changesets/cli": "^2.27.7", "@types/node": "^18.19.39", "@typescript-eslint/eslint-plugin": "4.28.1", "@typescript-eslint/parser": "4.28.1", "eslint": "7.30.0", "eslint-config-prettier": "7.1.0", "eslint-plugin-prettier": "3.4.0", "prettier": "2.2.1", "rollup": "^4.18.0", "rollup-plugin-dts": "^6.1.1", "rollup-plugin-typescript2": "^0.32.1", "testdouble": "3.20.2", "ts-toolbelt": "9.6.0", "typescript": "4.7.2", "vitest": "^2.1.3" }, "keywords": [ "typescript", "functional", "fp", "error" ], "engines": { "node": ">=18", "npm": ">=11" }, "optionalDependencies": { "@rollup/rollup-linux-x64-gnu": "^4.24.0" } } ================================================ FILE: rollup.config.mjs ================================================ import typescript from "rollup-plugin-typescript2"; import dts from "rollup-plugin-dts"; /** * Just a few details about this build implementation: * - Uses `rollup` along with `rollup-plugin-typescript2` to build to two target files * (`index.es.js` and `index.cjs.js`). This means that there is only a *single* file now for each * target rather than 4 files (ie. `index.js`, `chain.js`, etc.). * - Rollup has their own official @rollup/plugin-typescript plugin but it does not have the best * TS declarations support atm (see https://github.com/rollup/plugins/issues/394, * https://github.com/rollup/plugins/issues/254, https://github.com/rollup/plugins/issues/243). * Until these are resolved, I choose to just use `rollup-plugin-typescript2`. * - `rollup-plugin-typescript2` generates `index.d.ts`, `chain.d.ts`, `result.d.ts` and * `result-async.d.ts` but this is inconsistent with the generated JavaScript files. This isn't a * huge issues unless someone tries to import `neverthrow/dist/chain` which would error * because the underlying `.js` doesn't exist. To remedy this issue, I used `rollup-plugin-dts` to * merge `*.d.ts` files into a single `index.d.ts`. * - It's unfortunately a bit complicated to generate two build outputs but once some of the * issues I've linked to above are resolved this process will become much easier :) * - Because the build process is kinda two steps (first using `rollup-plugin-typescript2` and then * using `rollup-plugin-dts`), I first write to `tmp/`, then write the merged `index.d.ts` to * `dist/` and then moved `index.es.js` and `index.cjs.js` to `dist/`. */ export default [ { input: "src/index.ts", output: { file: "tmp/index.es.js", format: "es", }, plugins: [typescript()], }, { input: "src/index.ts", output: { file: "tmp/index.cjs.js", format: "cjs", }, plugins: [typescript()], }, { input: "tmp/index.d.ts", output: [{ file: "dist/index.d.ts", format: "es" }], plugins: [dts()], }, ]; ================================================ FILE: src/_internals/error.ts ================================================ import { Result } from '../result' export interface ErrorConfig { withStackTrace: boolean } const defaultErrorConfig: ErrorConfig = { withStackTrace: false, } interface NeverThrowError { data: | { type: string value: T } | { type: string value: E } message: string stack: string | undefined } // Custom error object // Context / discussion: https://github.com/supermacro/neverthrow/pull/215 export const createNeverThrowError = ( message: string, result: Result, config: ErrorConfig = defaultErrorConfig, ): NeverThrowError => { const data = result.isOk() ? { type: 'Ok', value: result.value } : { type: 'Err', value: result.error } const maybeStack = config.withStackTrace ? new Error().stack : undefined return { data, message, stack: maybeStack, } } ================================================ FILE: src/_internals/utils.ts ================================================ import { Result, ok, err } from '../result' import { ResultAsync } from '../result-async' // Given a list of Results, this extracts all the different `T` types from that list export type ExtractOkTypes[]> = { [idx in keyof T]: T[idx] extends Result ? U : never } // Given a list of ResultAsyncs, this extracts all the different `T` types from that list export type ExtractOkAsyncTypes[]> = { [idx in keyof T]: T[idx] extends ResultAsync ? U : never } // Given a list of Results, this extracts all the different `E` types from that list export type ExtractErrTypes[]> = { [idx in keyof T]: T[idx] extends Result ? E : never } // Given a list of ResultAsyncs, this extracts all the different `E` types from that list export type ExtractErrAsyncTypes[]> = { [idx in keyof T]: T[idx] extends ResultAsync ? E : never } export type InferOkTypes = R extends Result ? T : never export type InferErrTypes = R extends Result ? E : never export type InferAsyncOkTypes = R extends ResultAsync ? T : never export type InferAsyncErrTypes = R extends ResultAsync ? E : never /** * Short circuits on the FIRST Err value that we find */ export const combineResultList = ( resultList: readonly Result[], ): Result => { let acc = ok([]) as Result for (const result of resultList) { if (result.isErr()) { acc = err(result.error) break } else { acc.map((list) => list.push(result.value)) } } return acc } /* This is the typesafe version of Promise.all * * Takes a list of ResultAsync and success if all inner results are Ok values * or fails if one (or more) of the inner results are Err values */ export const combineResultAsyncList = ( asyncResultList: readonly ResultAsync[], ): ResultAsync => ResultAsync.fromSafePromise(Promise.all(asyncResultList)).andThen( combineResultList, ) as ResultAsync /** * Give a list of all the errors we find */ export const combineResultListWithAllErrors = ( resultList: readonly Result[], ): Result => { let acc = ok([]) as Result for (const result of resultList) { if (result.isErr() && acc.isErr()) { acc.error.push(result.error) } else if (result.isErr() && acc.isOk()) { acc = err([result.error]) } else if (result.isOk() && acc.isOk()) { acc.value.push(result.value) } // do nothing when result.isOk() && acc.isErr() } return acc } export const combineResultAsyncListWithAllErrors = ( asyncResultList: readonly ResultAsync[], ): ResultAsync => ResultAsync.fromSafePromise(Promise.all(asyncResultList)).andThen( combineResultListWithAllErrors, ) as ResultAsync ================================================ FILE: src/index.ts ================================================ export { Result, ok, Ok, err, Err, fromThrowable, safeTry } from './result' export { ResultAsync, okAsync, errAsync, fromAsyncThrowable, fromPromise, fromSafePromise, } from './result-async' ================================================ FILE: src/result-async.ts ================================================ import type { Combine, Dedup, EmptyArrayToNever, IsLiteralArray, MemberListOf, MembersToUnion, } from './result' import { Err, Ok, Result } from './' import { combineResultAsyncList, combineResultAsyncListWithAllErrors, ExtractErrAsyncTypes, ExtractOkAsyncTypes, InferAsyncErrTypes, InferAsyncOkTypes, InferErrTypes, InferOkTypes, } from './_internals/utils' export class ResultAsync implements PromiseLike> { private _promise: Promise> constructor(res: Promise>) { this._promise = res } static fromSafePromise(promise: PromiseLike): ResultAsync static fromSafePromise(promise: Promise): ResultAsync { const newPromise = promise.then((value: T) => new Ok(value)) return new ResultAsync(newPromise) } static fromPromise(promise: PromiseLike, errorFn: (e: unknown) => E): ResultAsync static fromPromise(promise: Promise, errorFn: (e: unknown) => E): ResultAsync { const newPromise = promise .then((value: T) => new Ok(value)) .catch((e) => new Err(errorFn(e))) return new ResultAsync(newPromise) } // eslint-disable-next-line @typescript-eslint/no-explicit-any static fromThrowable( fn: (...args: A) => Promise, errorFn?: (err: unknown) => E, ): (...args: A) => ResultAsync { return (...args) => { return new ResultAsync( (async () => { try { return new Ok(await fn(...args)) } catch (error) { return new Err(errorFn ? errorFn(error) : error) } })(), ) } } static combine< T extends readonly [ResultAsync, ...ResultAsync[]] >(asyncResultList: T): CombineResultAsyncs static combine[]>( asyncResultList: T, ): CombineResultAsyncs static combine[]>( asyncResultList: T, ): CombineResultAsyncs { return (combineResultAsyncList(asyncResultList) as unknown) as CombineResultAsyncs } static combineWithAllErrors< T extends readonly [ResultAsync, ...ResultAsync[]] >(asyncResultList: T): CombineResultsWithAllErrorsArrayAsync static combineWithAllErrors[]>( asyncResultList: T, ): CombineResultsWithAllErrorsArrayAsync static combineWithAllErrors[]>( asyncResultList: T, ): CombineResultsWithAllErrorsArrayAsync { return combineResultAsyncListWithAllErrors( asyncResultList, ) as CombineResultsWithAllErrorsArrayAsync } map(f: (t: T) => A | Promise): ResultAsync { return new ResultAsync( this._promise.then(async (res: Result) => { if (res.isErr()) { return new Err(res.error) } return new Ok(await f(res.value)) }), ) } andThrough(f: (t: T) => Result | ResultAsync): ResultAsync { return new ResultAsync( this._promise.then(async (res: Result) => { if (res.isErr()) { return new Err(res.error) } const newRes = await f(res.value) if (newRes.isErr()) { return new Err(newRes.error) } return new Ok(res.value) }), ) } andTee(f: (t: T) => unknown): ResultAsync { return new ResultAsync( this._promise.then(async (res: Result) => { if (res.isErr()) { return new Err(res.error) } try { await f(res.value) } catch (e) { // Tee does not care about the error } return new Ok(res.value) }), ) } orTee(f: (t: E) => unknown): ResultAsync { return new ResultAsync( this._promise.then(async (res: Result) => { if (res.isOk()) { return new Ok(res.value) } try { await f(res.error) } catch (e) { // Tee does not care about the error } return new Err(res.error) }), ) } mapErr(f: (e: E) => U | Promise): ResultAsync { return new ResultAsync( this._promise.then(async (res: Result) => { if (res.isOk()) { return new Ok(res.value) } return new Err(await f(res.error)) }), ) } andThen>( f: (t: T) => R, ): ResultAsync, InferErrTypes | E> andThen>( f: (t: T) => R, ): ResultAsync, InferAsyncErrTypes | E> andThen(f: (t: T) => Result | ResultAsync): ResultAsync // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types andThen(f: any): any { return new ResultAsync( this._promise.then((res) => { if (res.isErr()) { return new Err(res.error) } const newValue = f(res.value) return newValue instanceof ResultAsync ? newValue._promise : newValue }), ) } orElse>( f: (e: E) => R, ): ResultAsync | T, InferErrTypes> orElse>( f: (e: E) => R, ): ResultAsync | T, InferAsyncErrTypes> orElse(f: (e: E) => Result | ResultAsync): ResultAsync // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types orElse(f: any): any { return new ResultAsync( this._promise.then(async (res: Result) => { if (res.isErr()) { return f(res.error) } return new Ok(res.value) }), ) } match(ok: (t: T) => A, _err: (e: E) => B): Promise { return this._promise.then((res) => res.match(ok, _err)) } unwrapOr(t: A): Promise { return this._promise.then((res) => res.unwrapOr(t)) } /** * @deprecated will be removed in 9.0.0. * * You can use `safeTry` without this method. * @example * ```typescript * safeTry(async function* () { * const okValue = yield* yourResult * }) * ``` * Emulates Rust's `?` operator in `safeTry`'s body. See also `safeTry`. */ async *safeUnwrap(): AsyncGenerator, T> { return yield* await this._promise.then((res) => res.safeUnwrap()) } // Makes ResultAsync implement PromiseLike then( successCallback?: (res: Result) => A | PromiseLike, failureCallback?: (reason: unknown) => B | PromiseLike, ): PromiseLike { return this._promise.then(successCallback, failureCallback) } async *[Symbol.asyncIterator](): AsyncGenerator, T> { const result = await this._promise if (result.isErr()) { // @ts-expect-error -- This is structurally equivalent and safe yield errAsync(result.error) } // @ts-expect-error -- This is structurally equivalent and safe return result.value } } export function okAsync(value: T): ResultAsync export function okAsync(value: void): ResultAsync export function okAsync(value: T): ResultAsync { return new ResultAsync(Promise.resolve(new Ok(value))) } export function errAsync(err: E): ResultAsync export function errAsync(err: void): ResultAsync export function errAsync(err: E): ResultAsync { return new ResultAsync(Promise.resolve(new Err(err))) } export const fromPromise = ResultAsync.fromPromise export const fromSafePromise = ResultAsync.fromSafePromise export const fromAsyncThrowable = ResultAsync.fromThrowable // Combines the array of async results into one result. export type CombineResultAsyncs< T extends readonly ResultAsync[] > = IsLiteralArray extends 1 ? TraverseAsync> : ResultAsync, ExtractErrAsyncTypes[number]> // Combines the array of async results into one result with all errors. export type CombineResultsWithAllErrorsArrayAsync< T extends readonly ResultAsync[] > = IsLiteralArray extends 1 ? TraverseWithAllErrorsAsync> : ResultAsync, ExtractErrAsyncTypes[number][]> // Unwraps the inner `Result` from a `ResultAsync` for all elements. type UnwrapAsync = IsLiteralArray extends 1 ? Writable extends [infer H, ...infer Rest] ? H extends PromiseLike ? HI extends Result ? [Dedup, ...UnwrapAsync] : never : never : [] : // If we got something too general such as ResultAsync[] then we // simply need to map it to ResultAsync. Yet `ResultAsync` // itself is a union therefore it would be enough to cast it to Ok. T extends Array ? A extends PromiseLike ? HI extends Result ? Ok[] : never : never : never // Traverse through the tuples of the async results and create one // `ResultAsync` where the collected tuples are merged. type TraverseAsync = IsLiteralArray extends 1 ? Combine extends [infer Oks, infer Errs] ? ResultAsync, MembersToUnion> : never : // The following check is important if we somehow reach to the point of // checking something similar to ResultAsync[]. In this case we don't // know the length of the elements, therefore we need to traverse the X and Y // in a way that the result should contain X[] and Y[]. T extends Array ? // The MemberListOf here is to include all possible types. Therefore // if we face (ResultAsync | ResultAsync)[] this type should // handle the case. Combine, Depth> extends [infer Oks, infer Errs] ? // The following `extends unknown[]` checks are just to satisfy the TS. // we already expect them to be an array. Oks extends unknown[] ? Errs extends unknown[] ? ResultAsync, MembersToUnion> : ResultAsync, Errs> : // The rest of the conditions are to satisfy the TS and support // the edge cases which are not really expected to happen. Errs extends unknown[] ? ResultAsync> : ResultAsync : never : never // This type is similar to the `TraverseAsync` while the errors are also // collected in a list. For the checks/conditions made here, see that type // for the documentation. type TraverseWithAllErrorsAsync = TraverseAsync< T, Depth > extends ResultAsync ? ResultAsync : never // Converts a reaodnly array into a writable array type Writable = T extends ReadonlyArray ? [...T] : T ================================================ FILE: src/result.ts ================================================ import { errAsync, ResultAsync } from './' import { createNeverThrowError, ErrorConfig } from './_internals/error' import { combineResultList, combineResultListWithAllErrors, ExtractErrTypes, ExtractOkTypes, InferAsyncErrTypes, InferErrTypes, InferOkTypes, } from './_internals/utils' // eslint-disable-next-line @typescript-eslint/no-namespace export namespace Result { /** * Wraps a function with a try catch, creating a new function with the same * arguments but returning `Ok` if successful, `Err` if the function throws * * @param fn function to wrap with ok on success or err on failure * @param errorFn when an error is thrown, this will wrap the error result if provided */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export function fromThrowable any, E>( fn: Fn, errorFn?: (e: unknown) => E, ): (...args: Parameters) => Result, E> { return (...args) => { try { const result = fn(...args) return ok(result) } catch (e) { return err(errorFn ? errorFn(e) : e) } } } export function combine< T extends readonly [Result, ...Result[]] >(resultList: T): CombineResults export function combine[]>( resultList: T, ): CombineResults export function combine< T extends readonly [Result, ...Result[]] >(resultList: T): CombineResults { return combineResultList(resultList) as CombineResults } export function combineWithAllErrors< T extends readonly [Result, ...Result[]] >(resultList: T): CombineResultsWithAllErrorsArray export function combineWithAllErrors[]>( resultList: T, ): CombineResultsWithAllErrorsArray export function combineWithAllErrors[]>( resultList: T, ): CombineResultsWithAllErrorsArray { return combineResultListWithAllErrors(resultList) as CombineResultsWithAllErrorsArray } } export type Result = Ok | Err export function ok(value: T): Ok export function ok(value: void): Ok export function ok(value: T): Ok { return new Ok(value) } export function err(err: E): Err export function err(err: E): Err export function err(err: void): Err export function err(err: E): Err { return new Err(err) } /** * Evaluates the given generator to a Result returned or an Err yielded from it, * whichever comes first. * * This function is intended to emulate Rust's ? operator. * See `/tests/safeTry.test.ts` for examples. * * @param body - What is evaluated. In body, `yield* result` works as * Rust's `result?` expression. * @returns The first occurrence of either an yielded Err or a returned Result. */ export function safeTry(body: () => Generator, Result>): Result export function safeTry< YieldErr extends Err, GeneratorReturnResult extends Result >( body: () => Generator, ): Result< InferOkTypes, InferErrTypes | InferErrTypes > /** * Evaluates the given generator to a Result returned or an Err yielded from it, * whichever comes first. * * This function is intended to emulate Rust's ? operator. * See `/tests/safeTry.test.ts` for examples. * * @param body - What is evaluated. In body, `yield* result` and * `yield* resultAsync` work as Rust's `result?` expression. * @returns The first occurrence of either an yielded Err or a returned Result. */ export function safeTry( body: () => AsyncGenerator, Result>, ): ResultAsync export function safeTry< YieldErr extends Err, GeneratorReturnResult extends Result >( body: () => AsyncGenerator, ): ResultAsync< InferOkTypes, InferErrTypes | InferErrTypes > export function safeTry( body: | (() => Generator, Result>) | (() => AsyncGenerator, Result>), ): Result | ResultAsync { const n = body().next() if (n instanceof Promise) { return new ResultAsync(n.then((r) => r.value)) } return n.value } interface IResult { /** * Used to check if a `Result` is an `OK` * * @returns `true` if the result is an `OK` variant of Result */ isOk(): this is Ok /** * Used to check if a `Result` is an `Err` * * @returns `true` if the result is an `Err` variant of Result */ isErr(): this is Err /** * Maps a `Result` to `Result` * by applying a function to a contained `Ok` value, leaving an `Err` value * untouched. * * @param f The function to apply an `OK` value * @returns the result of applying `f` or an `Err` untouched */ map(f: (t: T) => A): Result /** * Maps a `Result` to `Result` by applying a function to a * contained `Err` value, leaving an `Ok` value untouched. * * This function can be used to pass through a successful result while * handling an error. * * @param f a function to apply to the error `Err` value */ mapErr(f: (e: E) => U): Result /** * Similar to `map` Except you must return a new `Result`. * * This is useful for when you need to do a subsequent computation using the * inner `T` value, but that computation might fail. * Additionally, `andThen` is really useful as a tool to flatten a * `Result, E1>` into a `Result` (see example below). * * @param f The function to apply to the current value */ andThen>( f: (t: T) => R, ): Result, InferErrTypes | E> andThen(f: (t: T) => Result): Result /** * This "tee"s the current value to an passed-in computation such as side * effect functions but still returns the same current value as the result. * * This is useful when you want to pass the current result to your side-track * work such as logging but want to continue main-track work after that. * This method does not care about the result of the passed in computation. * * @param f The function to apply to the current value */ andTee(f: (t: T) => unknown): Result /** * This "tee"s the current `Err` value to an passed-in computation such as side * effect functions but still returns the same `Err` value as the result. * * This is useful when you want to pass the current `Err` value to your side-track * work such as logging but want to continue error-track work after that. * This method does not care about the result of the passed in computation. * * @param f The function to apply to the current `Err` value */ orTee(f: (t: E) => unknown): Result /** * Similar to `andTee` except error result of the computation will be passed * to the downstream in case of an error. * * This version is useful when you want to make side-effects but in case of an * error, you want to pass the error to the downstream. * * @param f The function to apply to the current value */ andThrough>(f: (t: T) => R): Result | E> andThrough(f: (t: T) => Result): Result /** * Takes an `Err` value and maps it to a `Result`. * * This is useful for error recovery. * * * @param f A function to apply to an `Err` value, leaving `Ok` values * untouched. */ orElse>( f: (e: E) => R, ): Result | T, InferErrTypes> orElse(f: (e: E) => Result): Result /** * Similar to `map` Except you must return a new `Result`. * * This is useful for when you need to do a subsequent async computation using * the inner `T` value, but that computation might fail. Must return a ResultAsync * * @param f The function that returns a `ResultAsync` to apply to the current * value */ asyncAndThen(f: (t: T) => ResultAsync): ResultAsync /** * Maps a `Result` to `ResultAsync` * by applying an async function to a contained `Ok` value, leaving an `Err` * value untouched. * * @param f An async function to apply an `OK` value */ asyncMap(f: (t: T) => Promise): ResultAsync /** * Unwrap the `Ok` value, or return the default if there is an `Err` * * @param v the default value to return if there is an `Err` */ unwrapOr(v: A): T | A /** * * Given 2 functions (one for the `Ok` variant and one for the `Err` variant) * execute the function that matches the `Result` variant. * * Match callbacks do not necessitate to return a `Result`, however you can * return a `Result` if you want to. * * `match` is like chaining `map` and `mapErr`, with the distinction that * with `match` both functions must have the same return type. * * @param ok * @param err */ match(ok: (t: T) => A, err: (e: E) => B): A | B /** * @deprecated will be removed in 9.0.0. * * You can use `safeTry` without this method. * @example * ```typescript * safeTry(function* () { * const okValue = yield* yourResult * }) * ``` * Emulates Rust's `?` operator in `safeTry`'s body. See also `safeTry`. */ safeUnwrap(): Generator, T> /** * **This method is unsafe, and should only be used in a test environments** * * Takes a `Result` and returns a `T` when the result is an `Ok`, otherwise it throws a custom object. * * @param config */ _unsafeUnwrap(config?: ErrorConfig): T /** * **This method is unsafe, and should only be used in a test environments** * * takes a `Result` and returns a `E` when the result is an `Err`, * otherwise it throws a custom object. * * @param config */ _unsafeUnwrapErr(config?: ErrorConfig): E } export class Ok implements IResult { constructor(readonly value: T) {} isOk(): this is Ok { return true } isErr(): this is Err { return !this.isOk() } map(f: (t: T) => A): Result { return ok(f(this.value)) } // eslint-disable-next-line @typescript-eslint/no-unused-vars mapErr(_f: (e: E) => U): Result { return ok(this.value) } andThen>( f: (t: T) => R, ): Result, InferErrTypes | E> andThen(f: (t: T) => Result): Result // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types andThen(f: any): any { return f(this.value) } andThrough>(f: (t: T) => R): Result | E> andThrough(f: (t: T) => Result): Result // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types andThrough(f: any): any { return f(this.value).map((_value: unknown) => this.value) } andTee(f: (t: T) => unknown): Result { try { f(this.value) } catch (e) { // Tee doesn't care about the error } return ok(this.value) } orTee(_f: (t: E) => unknown): Result { return ok(this.value) } orElse>( _f: (e: E) => R, ): Result | T, InferErrTypes> orElse(_f: (e: E) => Result): Result // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types orElse(_f: any): any { return ok(this.value) } asyncAndThen(f: (t: T) => ResultAsync): ResultAsync { return f(this.value) } asyncAndThrough>( f: (t: T) => R, ): ResultAsync | E> asyncAndThrough(f: (t: T) => ResultAsync): ResultAsync // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types asyncAndThrough(f: (t: T) => ResultAsync): any { return f(this.value).map(() => this.value) } asyncMap(f: (t: T) => Promise): ResultAsync { return ResultAsync.fromSafePromise(f(this.value)) } // eslint-disable-next-line @typescript-eslint/no-unused-vars unwrapOr(_v: A): T | A { return this.value } // eslint-disable-next-line @typescript-eslint/no-unused-vars match(ok: (t: T) => A, _err: (e: E) => B): A | B { return ok(this.value) } safeUnwrap(): Generator, T> { const value = this.value /* eslint-disable-next-line require-yield */ return (function* () { return value })() } _unsafeUnwrap(_?: ErrorConfig): T { return this.value } _unsafeUnwrapErr(config?: ErrorConfig): E { throw createNeverThrowError('Called `_unsafeUnwrapErr` on an Ok', this, config) } // eslint-disable-next-line @typescript-eslint/no-this-alias, require-yield *[Symbol.iterator](): Generator, T> { return this.value } } export class Err implements IResult { constructor(readonly error: E) {} isOk(): this is Ok { return false } isErr(): this is Err { return !this.isOk() } // eslint-disable-next-line @typescript-eslint/no-unused-vars map(_f: (t: T) => A): Result { return err(this.error) } mapErr(f: (e: E) => U): Result { return err(f(this.error)) } andThrough(_f: (t: T) => Result): Result { return err(this.error) } andTee(_f: (t: T) => unknown): Result { return err(this.error) } orTee(f: (t: E) => unknown): Result { try { f(this.error) } catch (e) { // Tee doesn't care about the error } return err(this.error) } andThen>( _f: (t: T) => R, ): Result, InferErrTypes | E> andThen(_f: (t: T) => Result): Result // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types andThen(_f: any): any { return err(this.error) } orElse>( f: (e: E) => R, ): Result | T, InferErrTypes> orElse(f: (e: E) => Result): Result // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types orElse(f: any): any { return f(this.error) } // eslint-disable-next-line @typescript-eslint/no-unused-vars asyncAndThen(_f: (t: T) => ResultAsync): ResultAsync { return errAsync(this.error) } asyncAndThrough(_f: (t: T) => ResultAsync): ResultAsync { return errAsync(this.error) } // eslint-disable-next-line @typescript-eslint/no-unused-vars asyncMap(_f: (t: T) => Promise): ResultAsync { return errAsync(this.error) } unwrapOr(v: A): T | A { return v } match(_ok: (t: T) => A, err: (e: E) => B): A | B { return err(this.error) } safeUnwrap(): Generator, T> { const error = this.error return (function* () { yield err(error) throw new Error('Do not use this generator out of `safeTry`') })() } _unsafeUnwrap(config?: ErrorConfig): T { throw createNeverThrowError('Called `_unsafeUnwrap` on an Err', this, config) } _unsafeUnwrapErr(_?: ErrorConfig): E { return this.error } *[Symbol.iterator](): Generator, T> { // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this // @ts-expect-error -- This is structurally equivalent and safe yield self // @ts-expect-error -- This is structurally equivalent and safe return self } } export const fromThrowable = Result.fromThrowable //#region Combine - Types // This is a helper type to prevent infinite recursion in typing rules. // // Use this with your `depth` variable in your types. type Prev = [ never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ...0[] ] // Collects the results array into separate tuple array. // // T - The array of the results // Collected - The collected tuples. // Depth - The maximum depth. type CollectResults = [ Depth, ] extends [never] ? [] : T extends [infer H, ...infer Rest] ? // And test whether the head of the list is a result H extends Result ? // Continue collecting... CollectResults< // the rest of the elements Rest, // The collected [...Collected, [L, R]], // and one less of the current depth Prev[Depth] > : never // Impossible : Collected // Transposes an array // // A - The array source // Transposed - The collected transposed array // Depth - The maximum depth. export type Transpose< A, Transposed extends unknown[][] = [], Depth extends number = 10 > = A extends [infer T, ...infer Rest] ? T extends [infer L, infer R] ? Transposed extends [infer PL, infer PR] ? PL extends unknown[] ? PR extends unknown[] ? Transpose : never : never : Transpose : Transposed : Transposed // Combines the both sides of the array of the results into a tuple of the // union of the ok types and the union of the err types. // // T - The array of the results // Depth - The maximum depth. export type Combine = Transpose, [], Depth> extends [ infer L, infer R, ] ? [UnknownMembersToNever, UnknownMembersToNever] : Transpose, [], Depth> extends [] ? [[], []] : never // Deduplicates the result, as the result type is a union of Err and Ok types. export type Dedup = T extends Result ? [unknown] extends [RL] ? Err : Ok : T // Given a union, this gives the array of the union members. export type MemberListOf = ( (T extends unknown ? (t: T) => T : never) extends infer U ? (U extends unknown ? (u: U) => unknown : never) extends (v: infer V) => unknown ? V : never : never ) extends (_: unknown) => infer W ? [...MemberListOf>, W] : [] // Converts an empty array to never. // // The second type parameter here will affect how to behave to `never[]`s. // If a precise type is required, pass `1` here so that it will resolve // a literal array such as `[ never, never ]`. Otherwise, set `0` or the default // type value will cause this to resolve the arrays containing only `never` // items as `never` only. export type EmptyArrayToNever = T extends [] ? never : NeverArrayToNever extends 1 ? T extends [never, ...infer Rest] ? [EmptyArrayToNever] extends [never] ? never : T : T : T // Converts the `unknown` items of an array to `never`s. type UnknownMembersToNever = T extends [infer H, ...infer R] ? [[unknown] extends [H] ? never : H, ...UnknownMembersToNever] : T // Gets the member type of the array or never. export type MembersToUnion = T extends unknown[] ? T[number] : never // Checks if the given type is a literal array. export type IsLiteralArray = T extends { length: infer L } ? L extends number ? number extends L ? 0 : 1 : 0 : 0 // Traverses an array of results and returns a single result containing // the oks and errs union-ed/combined. type Traverse = Combine extends [infer Oks, infer Errs] ? Result, MembersToUnion> : never // Traverses an array of results and returns a single result containing // the oks combined and the array of errors combined. type TraverseWithAllErrors = Traverse extends Result< infer Oks, infer Errs > ? Result : never // Combines the array of results into one result. export type CombineResults< T extends readonly Result[] > = IsLiteralArray extends 1 ? Traverse : Result, ExtractErrTypes[number]> // Combines the array of results into one result with all errors. export type CombineResultsWithAllErrorsArray< T extends readonly Result[] > = IsLiteralArray extends 1 ? TraverseWithAllErrors : Result, ExtractErrTypes[number][]> //#endregion ================================================ FILE: tests/index.test.ts ================================================ import * as td from 'testdouble' import { err, Err, errAsync, fromAsyncThrowable, fromPromise, fromSafePromise, fromThrowable, ok, Ok, okAsync, Result, ResultAsync, } from '../src' import { vitest, describe, expect, it } from 'vitest' describe('Result.Ok', () => { it('Creates an Ok value', () => { const okVal = ok(12) expect(okVal.isOk()).toBe(true) expect(okVal.isErr()).toBe(false) expect(okVal).toBeInstanceOf(Ok) }) it('Creates an Ok value with null', () => { const okVal = ok(null) expect(okVal.isOk()).toBe(true) expect(okVal.isErr()).toBe(false) expect(okVal._unsafeUnwrap()).toBe(null) }) it('Creates an Ok value with undefined', () => { const okVal = ok(undefined) expect(okVal.isOk()).toBe(true) expect(okVal.isErr()).toBe(false) expect(okVal._unsafeUnwrap()).toBeUndefined() }) it('Is comparable', () => { expect(ok(42)).toEqual(ok(42)) expect(ok(42)).not.toEqual(ok(43)) }) it('Maps over an Ok value', () => { const okVal = ok(12) const mapFn = vitest.fn((number) => number.toString()) const mapped = okVal.map(mapFn) expect(mapped.isOk()).toBe(true) expect(mapped._unsafeUnwrap()).toBe('12') expect(mapFn).toHaveBeenCalledTimes(1) }) it('Skips `mapErr`', () => { const mapErrorFunc = vitest.fn((_error) => 'mapped error value') const notMapped = ok(12).mapErr(mapErrorFunc) expect(notMapped.isOk()).toBe(true) expect(mapErrorFunc).not.toHaveBeenCalledTimes(1) }) describe('andThen', () => { it('Maps to an Ok', () => { const okVal = ok(12) const flattened = okVal.andThen((_number) => { // ... // complex logic // ... return ok({ data: 'why not' }) }) expect(flattened.isOk()).toBe(true) expect(flattened._unsafeUnwrap()).toStrictEqual({ data: 'why not' }) }) it('Maps to an Err', () => { const okval = ok(12) const flattened = okval.andThen((_number) => { // ... // complex logic // ... return err('Whoopsies!') }) expect(flattened.isOk()).toBe(false) const nextFn = vitest.fn((_val) => ok('noop')) flattened.andThen(nextFn) expect(nextFn).not.toHaveBeenCalled() }) }) describe('andThrough', () => { it('Calls the passed function but returns an original ok', () => { const okVal = ok(12) const passedFn = vitest.fn((_number) => ok(undefined)) const thrued = okVal.andThrough(passedFn) expect(thrued.isOk()).toBe(true) expect(passedFn).toHaveBeenCalledTimes(1) expect(thrued._unsafeUnwrap()).toStrictEqual(12) }) it('Maps to an Err', () => { const okval = ok(12) const thrued = okval.andThen((_number) => { // ... // complex logic // ... return err('Whoopsies!') }) expect(thrued.isOk()).toBe(false) expect(thrued._unsafeUnwrapErr()).toStrictEqual('Whoopsies!') const nextFn = vitest.fn((_val) => ok('noop')) thrued.andThen(nextFn) expect(nextFn).not.toHaveBeenCalled() }) }) describe('andTee', () => { it('Calls the passed function but returns an original ok', () => { const okVal = ok(12) const passedFn = vitest.fn((_number) => {}) const teed = okVal.andTee(passedFn) expect(teed.isOk()).toBe(true) expect(passedFn).toHaveBeenCalledTimes(1) expect(teed._unsafeUnwrap()).toStrictEqual(12) }) it('returns an original ok even when the passed function fails', () => { const okVal = ok(12) const passedFn = vitest.fn((_number) => { throw new Error('OMG!') }) const teed = okVal.andTee(passedFn) expect(teed.isOk()).toBe(true) expect(passedFn).toHaveBeenCalledTimes(1) expect(teed._unsafeUnwrap()).toStrictEqual(12) }) }) describe('orTee', () => { it('Calls the passed function but returns an original err', () => { const errVal = err(12) const passedFn = vitest.fn((_number) => {}) const teed = errVal.orTee(passedFn) expect(teed.isErr()).toBe(true) expect(passedFn).toHaveBeenCalledTimes(1) expect(teed._unsafeUnwrapErr()).toStrictEqual(12) }) it('returns an original err even when the passed function fails', () => { const errVal = err(12) const passedFn = vitest.fn((_number) => { throw new Error('OMG!') }) const teed = errVal.orTee(passedFn) expect(teed.isErr()).toBe(true) expect(passedFn).toHaveBeenCalledTimes(1) expect(teed._unsafeUnwrapErr()).toStrictEqual(12) }) }) describe('asyncAndThrough', () => { it('Calls the passed function but returns an original ok as Async', async () => { const okVal = ok(12) const passedFn = vitest.fn((_number) => okAsync(undefined)) const teedAsync = okVal.asyncAndThrough(passedFn) expect(teedAsync).toBeInstanceOf(ResultAsync) const teed = await teedAsync expect(teed.isOk()).toBe(true) expect(passedFn).toHaveBeenCalledTimes(1) expect(teed._unsafeUnwrap()).toStrictEqual(12) }) it('Maps to an Err', async () => { const okval = ok(12) const teedAsync = okval.asyncAndThen((_number) => { // ... // complex logic // ... return errAsync('Whoopsies!') }) expect(teedAsync).toBeInstanceOf(ResultAsync) const teed = await teedAsync expect(teed.isOk()).toBe(false) expect(teed._unsafeUnwrapErr()).toStrictEqual('Whoopsies!') const nextFn = vitest.fn((_val) => ok('noop')) teed.andThen(nextFn) expect(nextFn).not.toHaveBeenCalled() }) }) describe('orElse', () => { it('Skips orElse on an Ok value', () => { const okVal = ok(12) const errorCallback = vitest.fn((_errVal) => err('It is now a string')) expect(okVal.orElse(errorCallback)).toEqual(ok(12)) expect(errorCallback).not.toHaveBeenCalled() }) }) it('unwrapOr and return the Ok value', () => { const okVal = ok(12) expect(okVal.unwrapOr(1)).toEqual(12) }) it('Maps to a ResultAsync', async () => { const okVal = ok(12) const flattened = okVal.asyncAndThen((_number) => { // ... // complex async logic // ... return okAsync({ data: 'why not' }) }) expect(flattened).toBeInstanceOf(ResultAsync) const newResult = await flattened expect(newResult.isOk()).toBe(true) expect(newResult._unsafeUnwrap()).toStrictEqual({ data: 'why not' }) }) it('Maps to a promise', async () => { const asyncMapper = vitest.fn((_val) => { // ... // complex logic // .. // db queries // network calls // disk io // etc ... return Promise.resolve('Very Nice!') }) const okVal = ok(12) const promise = okVal.asyncMap(asyncMapper) expect(promise).toBeInstanceOf(ResultAsync) const newResult = await promise expect(newResult.isOk()).toBe(true) expect(asyncMapper).toHaveBeenCalledTimes(1) expect(newResult._unsafeUnwrap()).toStrictEqual('Very Nice!') }) it('Matches on an Ok', () => { const okMapper = vitest.fn((_val) => 'weeeeee') const errMapper = vitest.fn((_val) => 'wooooo') const matched = ok(12).match(okMapper, errMapper) expect(matched).toBe('weeeeee') expect(okMapper).toHaveBeenCalledTimes(1) expect(errMapper).not.toHaveBeenCalled() }) it('Unwraps without issue', () => { const okVal = ok(12) expect(okVal._unsafeUnwrap()).toBe(12) }) it('Can read the value after narrowing', () => { const fallible: () => Result = () => ok('safe to read') const val = fallible() // After this check we val is narrowed to Ok. Without this // line TypeScript will not allow accessing val.value. if (val.isErr()) return expect(val.value).toBe('safe to read') }) }) describe('Result.Err', () => { it('Creates an Err value', () => { const errVal = err('I have you now.') expect(errVal.isOk()).toBe(false) expect(errVal.isErr()).toBe(true) expect(errVal).toBeInstanceOf(Err) }) it('Is comparable', () => { expect(err(42)).toEqual(err(42)) expect(err(42)).not.toEqual(err(43)) }) it('Skips `map`', () => { const errVal = err('I am your father') const mapper = vitest.fn((_value) => 'noooo') const hopefullyNotMapped = errVal.map(mapper) expect(hopefullyNotMapped.isErr()).toBe(true) expect(mapper).not.toHaveBeenCalled() expect(hopefullyNotMapped._unsafeUnwrapErr()).toEqual(errVal._unsafeUnwrapErr()) }) it('Maps over an Err', () => { const errVal = err('Round 1, Fight!') const mapper = vitest.fn((error: string) => error.replace('1', '2')) const mapped = errVal.mapErr(mapper) expect(mapped.isErr()).toBe(true) expect(mapper).toHaveBeenCalledTimes(1) expect(mapped._unsafeUnwrapErr()).not.toEqual(errVal._unsafeUnwrapErr()) }) it('unwrapOr and return the default value', () => { const okVal = err('Oh nooo') expect(okVal.unwrapOr(1)).toEqual(1) }) it('Skips over andThen', () => { const errVal = err('Yolo') const mapper = vitest.fn((_val) => ok('yooyo')) const hopefullyNotFlattened = errVal.andThen(mapper) expect(hopefullyNotFlattened.isErr()).toBe(true) expect(mapper).not.toHaveBeenCalled() expect(errVal._unsafeUnwrapErr()).toEqual('Yolo') }) it('Skips over andThrough', () => { const errVal = err('Yolo') const mapper = vitest.fn((_val) => ok(undefined)) const hopefullyNotFlattened = errVal.andThrough(mapper) expect(hopefullyNotFlattened.isErr()).toBe(true) expect(mapper).not.toHaveBeenCalled() expect(errVal._unsafeUnwrapErr()).toEqual('Yolo') }) it('Skips over andTee', () => { const errVal = err('Yolo') const mapper = vitest.fn((_val) => {}) const hopefullyNotFlattened = errVal.andTee(mapper) expect(hopefullyNotFlattened.isErr()).toBe(true) expect(mapper).not.toHaveBeenCalled() expect(errVal._unsafeUnwrapErr()).toEqual('Yolo') }) it('Skips over asyncAndThrough but returns ResultAsync instead', async () => { const errVal = err('Yolo') const mapper = vitest.fn((_val) => okAsync('Async')) const hopefullyNotFlattened = errVal.asyncAndThrough(mapper) expect(hopefullyNotFlattened).toBeInstanceOf(ResultAsync) const result = await hopefullyNotFlattened expect(result.isErr()).toBe(true) expect(mapper).not.toHaveBeenCalled() expect(result._unsafeUnwrapErr()).toEqual('Yolo') }) it('Transforms error into ResultAsync within `asyncAndThen`', async () => { const errVal = err('Yolo') const asyncMapper = vitest.fn((_val) => okAsync('yooyo')) const hopefullyNotFlattened = errVal.asyncAndThen(asyncMapper) expect(hopefullyNotFlattened).toBeInstanceOf(ResultAsync) expect(asyncMapper).not.toHaveBeenCalled() const syncResult = await hopefullyNotFlattened expect(syncResult._unsafeUnwrapErr()).toEqual('Yolo') }) it('Does not invoke callback within `asyncMap`', async () => { const asyncMapper = vitest.fn((_val) => { // ... // complex logic // .. // db queries // network calls // disk io // etc ... return Promise.resolve('Very Nice!') }) const errVal = err('nooooooo') const promise = errVal.asyncMap(asyncMapper) expect(promise).toBeInstanceOf(ResultAsync) const sameResult = await promise expect(sameResult.isErr()).toBe(true) expect(asyncMapper).not.toHaveBeenCalled() expect(sameResult._unsafeUnwrapErr()).toEqual(errVal._unsafeUnwrapErr()) }) it('Matches on an Err', () => { const okMapper = vitest.fn((_val) => 'weeeeee') const errMapper = vitest.fn((_val) => 'wooooo') const matched = err(12).match(okMapper, errMapper) expect(matched).toBe('wooooo') expect(okMapper).not.toHaveBeenCalled() expect(errMapper).toHaveBeenCalledTimes(1) }) it('Throws when you unwrap an Err', () => { const errVal = err('woopsies') expect(() => { errVal._unsafeUnwrap() }).toThrowError() }) it('Unwraps without issue', () => { const okVal = err(12) expect(okVal._unsafeUnwrapErr()).toBe(12) }) describe('orElse', () => { it('invokes the orElse callback on an Err value', () => { const okVal = err('BOOOM!') const errorCallback = vitest.fn((_errVal) => err(true)) expect(okVal.orElse(errorCallback)).toEqual(err(true)) expect(errorCallback).toHaveBeenCalledTimes(1) }) }) }) describe('Result.fromThrowable', () => { it('Creates a function that returns an OK result when the inner function does not throw', () => { const hello = (): string => 'hello' const safeHello = Result.fromThrowable(hello) const result = hello() const safeResult = safeHello() expect(safeResult).toBeInstanceOf(Ok) expect(result).toEqual(safeResult._unsafeUnwrap()) }) // Added for issue #300 -- the test here is not so much that expectations are met as that the test compiles. it('Accepts an inner function which takes arguments', () => { const hello = (fname: string): string => `hello, ${fname}` const safeHello = Result.fromThrowable(hello) const result = hello('Dikembe') const safeResult = safeHello('Dikembe') expect(safeResult).toBeInstanceOf(Ok) expect(result).toEqual(safeResult._unsafeUnwrap()) }) it('Creates a function that returns an err when the inner function throws', () => { const thrower = (): string => { throw new Error() } // type: () => Result // received types from thrower fn, no errorFn is provides therefore Err type is unknown const safeThrower = Result.fromThrowable(thrower) const result = safeThrower() expect(result).toBeInstanceOf(Err) expect(result._unsafeUnwrapErr()).toBeInstanceOf(Error) }) it('Accepts an error handler as a second argument', () => { const thrower = (): string => { throw new Error() } type MessageObject = { message: string } const toMessageObject = (): MessageObject => ({ message: 'error' }) // type: () => Result // received types from thrower fn and errorFn return type const safeThrower = Result.fromThrowable(thrower, toMessageObject) const result = safeThrower() expect(result.isOk()).toBe(false) expect(result.isErr()).toBe(true) expect(result).toBeInstanceOf(Err) expect(result._unsafeUnwrapErr()).toEqual({ message: 'error' }) }) it('has a top level export', () => { expect(fromThrowable).toBe(Result.fromThrowable) }) }) describe('Utils', () => { describe('`Result.combine`', () => { describe('Synchronous `combine`', () => { it('Combines a list of results into an Ok value', () => { const resultList = [ok(123), ok(456), ok(789)] const result = Result.combine(resultList) expect(result.isOk()).toBe(true) expect(result._unsafeUnwrap()).toEqual([123, 456, 789]) }) it('Combines a list of results into an Err value', () => { const resultList: Result[] = [ ok(123), err('boooom!'), ok(456), err('ahhhhh!'), ] const result = Result.combine(resultList) expect(result.isErr()).toBe(true) expect(result._unsafeUnwrapErr()).toBe('boooom!') }) it('Combines heterogeneous lists', () => { type HeterogenousList = [ Result, Result, Result, ] const heterogenousList: HeterogenousList = [ok('Yooooo'), ok(123), ok(true)] type ExpecteResult = Result<[string, number, boolean], string | number | boolean> const result: ExpecteResult = Result.combine(heterogenousList) expect(result._unsafeUnwrap()).toEqual(['Yooooo', 123, true]) }) it('Does not destructure / concatenate arrays', () => { type HomogenousList = [Result, Result] const homogenousList: HomogenousList = [ok(['hello', 'world']), ok([1, 2, 3])] type ExpectedResult = Result<[string[], number[]], boolean | string> const result: ExpectedResult = Result.combine(homogenousList) expect(result._unsafeUnwrap()).toEqual([ ['hello', 'world'], [1, 2, 3], ]) }) }) describe('`ResultAsync.combine`', () => { it('Combines a list of async results into an Ok value', async () => { const asyncResultList = [okAsync(123), okAsync(456), okAsync(789)] const resultAsync: ResultAsync = ResultAsync.combine(asyncResultList) expect(resultAsync).toBeInstanceOf(ResultAsync) const result = await ResultAsync.combine(asyncResultList) expect(result.isOk()).toBe(true) expect(result._unsafeUnwrap()).toEqual([123, 456, 789]) }) it('Combines a list of results into an Err value', async () => { const resultList: ResultAsync[] = [ okAsync(123), errAsync('boooom!'), okAsync(456), errAsync('ahhhhh!'), ] const result = await ResultAsync.combine(resultList) expect(result.isErr()).toBe(true) expect(result._unsafeUnwrapErr()).toBe('boooom!') }) it('Combines heterogeneous lists', async () => { type HeterogenousList = [ ResultAsync, ResultAsync, ResultAsync, ResultAsync, ] const heterogenousList: HeterogenousList = [ okAsync('Yooooo'), okAsync(123), okAsync(true), okAsync([1, 2, 3]), ] type ExpecteResult = Result<[string, number, boolean, number[]], string | number | boolean> const result: ExpecteResult = await ResultAsync.combine(heterogenousList) expect(result._unsafeUnwrap()).toEqual(['Yooooo', 123, true, [1, 2, 3]]) }) }) }) describe('`Result.combineWithAllErrors`', () => { describe('Synchronous `combineWithAllErrors`', () => { it('Combines a list of results into an Ok value', () => { const resultList = [ok(123), ok(456), ok(789)] const result = Result.combineWithAllErrors(resultList) expect(result.isOk()).toBe(true) expect(result._unsafeUnwrap()).toEqual([123, 456, 789]) }) it('Combines a list of results into an Err value', () => { const resultList: Result[] = [ ok(123), err('boooom!'), ok(456), err('ahhhhh!'), ] const result = Result.combineWithAllErrors(resultList) expect(result.isErr()).toBe(true) expect(result._unsafeUnwrapErr()).toEqual(['boooom!', 'ahhhhh!']) }) it('Combines heterogeneous lists', () => { type HeterogenousList = [ Result, Result, Result, ] const heterogenousList: HeterogenousList = [ok('Yooooo'), ok(123), ok(true)] type ExpecteResult = Result<[string, number, boolean], (string | number | boolean)[]> const result: ExpecteResult = Result.combineWithAllErrors(heterogenousList) expect(result._unsafeUnwrap()).toEqual(['Yooooo', 123, true]) }) it('Does not destructure / concatenate arrays', () => { type HomogenousList = [Result, Result] const homogenousList: HomogenousList = [ok(['hello', 'world']), ok([1, 2, 3])] type ExpectedResult = Result<[string[], number[]], (boolean | string)[]> const result: ExpectedResult = Result.combineWithAllErrors(homogenousList) expect(result._unsafeUnwrap()).toEqual([ ['hello', 'world'], [1, 2, 3], ]) }) }) describe('`ResultAsync.combineWithAllErrors`', () => { it('Combines a list of async results into an Ok value', async () => { const asyncResultList = [okAsync(123), okAsync(456), okAsync(789)] const result = await ResultAsync.combineWithAllErrors(asyncResultList) expect(result.isOk()).toBe(true) expect(result._unsafeUnwrap()).toEqual([123, 456, 789]) }) it('Combines a list of results into an Err value', async () => { const asyncResultList: ResultAsync[] = [ okAsync(123), errAsync('boooom!'), okAsync(456), errAsync('ahhhhh!'), ] const result = await ResultAsync.combineWithAllErrors(asyncResultList) expect(result.isErr()).toBe(true) expect(result._unsafeUnwrapErr()).toEqual(['boooom!', 'ahhhhh!']) }) it('Combines heterogeneous lists', async () => { type HeterogenousList = [ ResultAsync, ResultAsync, ResultAsync, ] const heterogenousList: HeterogenousList = [okAsync('Yooooo'), okAsync(123), okAsync(true)] type ExpecteResult = Result<[string, number, boolean], (string | number | boolean)[]> const result: ExpecteResult = await ResultAsync.combineWithAllErrors(heterogenousList) expect(result._unsafeUnwrap()).toEqual(['Yooooo', 123, true]) }) }) describe('testdouble `ResultAsync.combine`', () => { interface ITestInterface { getName(): string setName(name: string): void getAsyncResult(): ResultAsync } it('Combines `testdouble` proxies from mocks generated via interfaces', async () => { const mock = td.object() const result = await ResultAsync.combine([okAsync(mock)] as const) expect(result).toBeDefined() expect(result.isErr()).toBeFalsy() const unwrappedResult = result._unsafeUnwrap() expect(unwrappedResult.length).toBe(1) expect(unwrappedResult[0]).toBe(mock) }) }) }) }) describe('ResultAsync', () => { it('Is awaitable to a Result', async () => { // For a success value const asyncVal = okAsync(12) expect(asyncVal).toBeInstanceOf(ResultAsync) const val = await asyncVal expect(val).toBeInstanceOf(Ok) expect(val._unsafeUnwrap()).toEqual(12) // For an error const asyncErr = errAsync('Wrong format') expect(asyncErr).toBeInstanceOf(ResultAsync) const err = await asyncErr expect(err).toBeInstanceOf(Err) expect(err._unsafeUnwrapErr()).toEqual('Wrong format') }) describe('acting as a Promise', () => { it('Is chainable like any Promise', async () => { // For a success value const asyncValChained = okAsync(12).then((res) => { if (res.isOk()) { return res.value + 2 } }) expect(asyncValChained).toBeInstanceOf(Promise) const val = await asyncValChained expect(val).toEqual(14) // For an error const asyncErrChained = errAsync('Oops').then((res) => { if (res.isErr()) { return res.error + '!' } }) expect(asyncErrChained).toBeInstanceOf(Promise) const err = await asyncErrChained expect(err).toEqual('Oops!') }) it('Can be used with Promise.all', async () => { const allResult = await Promise.all([okAsync('1')]) expect(allResult).toHaveLength(1) expect(allResult[0]).toBeInstanceOf(Ok) if (!(allResult[0] instanceof Ok)) return expect(allResult[0].isOk()).toBe(true) expect(allResult[0]._unsafeUnwrap()).toEqual('1') }) it('rejects if the underlying promise is rejected', () => { const asyncResult = new ResultAsync(Promise.reject('oops')) expect(asyncResult).rejects.toBe('oops') }) }) describe('map', () => { it('Maps a value using a synchronous function', async () => { const asyncVal = okAsync(12) const mapSyncFn = vitest.fn((number) => number.toString()) const mapped = asyncVal.map(mapSyncFn) expect(mapped).toBeInstanceOf(ResultAsync) const newVal = await mapped expect(newVal.isOk()).toBe(true) expect(newVal._unsafeUnwrap()).toBe('12') expect(mapSyncFn).toHaveBeenCalledTimes(1) }) it('Maps a value using an asynchronous function', async () => { const asyncVal = okAsync(12) const mapAsyncFn = vitest.fn((number) => Promise.resolve(number.toString())) const mapped = asyncVal.map(mapAsyncFn) expect(mapped).toBeInstanceOf(ResultAsync) const newVal = await mapped expect(newVal.isOk()).toBe(true) expect(newVal._unsafeUnwrap()).toBe('12') expect(mapAsyncFn).toHaveBeenCalledTimes(1) }) it('Skips an error', async () => { const asyncErr = errAsync('Wrong format') const mapSyncFn = vitest.fn((number) => number.toString()) const notMapped = asyncErr.map(mapSyncFn) expect(notMapped).toBeInstanceOf(ResultAsync) const newVal = await notMapped expect(newVal.isErr()).toBe(true) expect(newVal._unsafeUnwrapErr()).toBe('Wrong format') expect(mapSyncFn).toHaveBeenCalledTimes(0) }) }) describe('mapErr', () => { it('Maps an error using a synchronous function', async () => { const asyncErr = errAsync('Wrong format') const mapErrSyncFn = vitest.fn((str) => 'Error: ' + str) const mappedErr = asyncErr.mapErr(mapErrSyncFn) expect(mappedErr).toBeInstanceOf(ResultAsync) const newVal = await mappedErr expect(newVal.isErr()).toBe(true) expect(newVal._unsafeUnwrapErr()).toBe('Error: Wrong format') expect(mapErrSyncFn).toHaveBeenCalledTimes(1) }) it('Maps an error using an asynchronous function', async () => { const asyncErr = errAsync('Wrong format') const mapErrAsyncFn = vitest.fn((str) => Promise.resolve('Error: ' + str)) const mappedErr = asyncErr.mapErr(mapErrAsyncFn) expect(mappedErr).toBeInstanceOf(ResultAsync) const newVal = await mappedErr expect(newVal.isErr()).toBe(true) expect(newVal._unsafeUnwrapErr()).toBe('Error: Wrong format') expect(mapErrAsyncFn).toHaveBeenCalledTimes(1) }) it('Skips a value', async () => { const asyncVal = okAsync(12) const mapErrSyncFn = vitest.fn((str) => 'Error: ' + str) const notMapped = asyncVal.mapErr(mapErrSyncFn) expect(notMapped).toBeInstanceOf(ResultAsync) const newVal = await notMapped expect(newVal.isOk()).toBe(true) expect(newVal._unsafeUnwrap()).toBe(12) expect(mapErrSyncFn).toHaveBeenCalledTimes(0) }) }) describe('andThen', () => { it('Maps a value using a function returning a ResultAsync', async () => { const asyncVal = okAsync(12) const andThenResultAsyncFn = vitest.fn(() => okAsync('good')) const mapped = asyncVal.andThen(andThenResultAsyncFn) expect(mapped).toBeInstanceOf(ResultAsync) const newVal = await mapped expect(newVal.isOk()).toBe(true) expect(newVal._unsafeUnwrap()).toBe('good') expect(andThenResultAsyncFn).toHaveBeenCalledTimes(1) }) it('Maps a value using a function returning a Result', async () => { const asyncVal = okAsync(12) const andThenResultFn = vitest.fn(() => ok('good')) const mapped = asyncVal.andThen(andThenResultFn) expect(mapped).toBeInstanceOf(ResultAsync) const newVal = await mapped expect(newVal.isOk()).toBe(true) expect(newVal._unsafeUnwrap()).toBe('good') expect(andThenResultFn).toHaveBeenCalledTimes(1) }) it('Skips an Error', async () => { const asyncVal = errAsync('Wrong format') const andThenResultFn = vitest.fn(() => ok('good')) const notMapped = asyncVal.andThen(andThenResultFn) expect(notMapped).toBeInstanceOf(ResultAsync) const newVal = await notMapped expect(newVal.isErr()).toBe(true) expect(newVal._unsafeUnwrapErr()).toBe('Wrong format') expect(andThenResultFn).toHaveBeenCalledTimes(0) }) }) describe('andThrough', () => { it('Returns the original value when map function returning ResultAsync succeeds', async () => { const asyncVal = okAsync(12) /* A couple examples of this function DB persistence (create or update) API calls (create or update) */ const andThroughResultAsyncFn = vitest.fn(() => okAsync('good')) const thrued = asyncVal.andThrough(andThroughResultAsyncFn) expect(thrued).toBeInstanceOf(ResultAsync) const result = await thrued expect(result.isOk()).toBe(true) expect(result._unsafeUnwrap()).toBe(12) expect(andThroughResultAsyncFn).toHaveBeenCalledTimes(1) }) it('Maps to an error when map function returning ResultAsync fails', async () => { const asyncVal = okAsync(12) const andThroughResultAsyncFn = vitest.fn(() => errAsync('oh no!')) const thrued = asyncVal.andThrough(andThroughResultAsyncFn) expect(thrued).toBeInstanceOf(ResultAsync) const result = await thrued expect(result.isErr()).toBe(true) expect(result._unsafeUnwrapErr()).toBe('oh no!') expect(andThroughResultAsyncFn).toHaveBeenCalledTimes(1) }) it('Returns the original value when map function returning Result succeeds', async () => { const asyncVal = okAsync(12) const andThroughResultFn = vitest.fn(() => ok('good')) const thrued = asyncVal.andThrough(andThroughResultFn) expect(thrued).toBeInstanceOf(ResultAsync) const newVal = await thrued expect(newVal.isOk()).toBe(true) expect(newVal._unsafeUnwrap()).toBe(12) expect(andThroughResultFn).toHaveBeenCalledTimes(1) }) it('Maps to an error when map function returning Result fails', async () => { const asyncVal = okAsync(12) const andThroughResultFn = vitest.fn(() => err('oh no!')) const thrued = asyncVal.andThrough(andThroughResultFn) expect(thrued).toBeInstanceOf(ResultAsync) const newVal = await thrued expect(newVal.isErr()).toBe(true) expect(newVal._unsafeUnwrapErr()).toBe('oh no!') expect(andThroughResultFn).toHaveBeenCalledTimes(1) }) it('Skips an Error', async () => { const asyncVal = errAsync('Wrong format') const andThroughResultFn = vitest.fn(() => ok('good')) const notMapped = asyncVal.andThrough(andThroughResultFn) expect(notMapped).toBeInstanceOf(ResultAsync) const newVal = await notMapped expect(newVal.isErr()).toBe(true) expect(newVal._unsafeUnwrapErr()).toBe('Wrong format') expect(andThroughResultFn).toHaveBeenCalledTimes(0) }) }) describe('andTee', () => { it('Calls the passed function but returns an original ok', async () => { const okVal = okAsync(12) const passedFn = vitest.fn((_number) => {}) const teed = await okVal.andTee(passedFn) expect(teed.isOk()).toBe(true) expect(passedFn).toHaveBeenCalledTimes(1) expect(teed._unsafeUnwrap()).toStrictEqual(12) }) it('returns an original ok even when the passed function fails', async () => { const okVal = okAsync(12) const passedFn = vitest.fn((_number) => { throw new Error('OMG!') }) const teed = await okVal.andTee(passedFn) expect(teed.isOk()).toBe(true) expect(passedFn).toHaveBeenCalledTimes(1) expect(teed._unsafeUnwrap()).toStrictEqual(12) }) }) describe('orTee', () => { it('Calls the passed function but returns an original err', async () => { const errVal = errAsync(12) const passedFn = vitest.fn((_number) => {}) const teed = await errVal.orTee(passedFn) expect(teed.isErr()).toBe(true) expect(passedFn).toHaveBeenCalledTimes(1) expect(teed._unsafeUnwrapErr()).toStrictEqual(12) }) it('returns an original err even when the passed function fails', async () => { const errVal = errAsync(12) const passedFn = vitest.fn((_number) => { throw new Error('OMG!') }) const teed = await errVal.orTee(passedFn) expect(teed.isErr()).toBe(true) expect(passedFn).toHaveBeenCalledTimes(1) expect(teed._unsafeUnwrapErr()).toStrictEqual(12) }) }) describe('orElse', () => { it('Skips orElse on an Ok value', async () => { const okVal = okAsync(12) const errorCallback = vitest.fn((_errVal) => errAsync('It is now a string')) const result = await okVal.orElse(errorCallback) expect(result).toEqual(ok(12)) expect(errorCallback).not.toHaveBeenCalled() }) it('Invokes the orElse callback on an Err value', async () => { const myResult = errAsync('BOOOM!') const errorCallback = vitest.fn((_errVal) => errAsync(true)) const result = await myResult.orElse(errorCallback) expect(result).toEqual(err(true)) expect(errorCallback).toHaveBeenCalledTimes(1) }) it('Accepts a regular Result in the callback', async () => { const myResult = errAsync('BOOOM!') const errorCallback = vitest.fn((_errVal) => err(true)) const result = await myResult.orElse(errorCallback) expect(result).toEqual(err(true)) expect(errorCallback).toHaveBeenCalledTimes(1) }) }) describe('match', () => { it('Matches on an Ok', async () => { const okMapper = vitest.fn((_val) => 'weeeeee') const errMapper = vitest.fn((_val) => 'wooooo') const matched = await okAsync(12).match(okMapper, errMapper) expect(matched).toBe('weeeeee') expect(okMapper).toHaveBeenCalledTimes(1) expect(errMapper).not.toHaveBeenCalled() }) it('Matches on an Error', async () => { const okMapper = vitest.fn((_val) => 'weeeeee') const errMapper = vitest.fn((_val) => 'wooooo') const matched = await errAsync('bad').match(okMapper, errMapper) expect(matched).toBe('wooooo') expect(okMapper).not.toHaveBeenCalled() expect(errMapper).toHaveBeenCalledTimes(1) }) }) describe('unwrapOr', () => { it('returns a promise to the result value on an Ok', async () => { const unwrapped = await okAsync(12).unwrapOr(10) expect(unwrapped).toBe(12) }) it('returns a promise to the provided default value on an Error', async () => { const unwrapped = await errAsync(12).unwrapOr(10) expect(unwrapped).toBe(10) }) }) describe('fromSafePromise', () => { it('Creates a ResultAsync from a Promise', async () => { const res = ResultAsync.fromSafePromise(Promise.resolve(12)) expect(res).toBeInstanceOf(ResultAsync) const val = await res expect(val.isOk()).toBe(true) expect(val._unsafeUnwrap()).toEqual(12) }) it('has a top level export', () => { expect(fromSafePromise).toBe(ResultAsync.fromSafePromise) }) }) describe('fromPromise', () => { it('Accepts an error handler as a second argument', async () => { const res = ResultAsync.fromPromise(Promise.reject('No!'), (e) => new Error('Oops: ' + e)) expect(res).toBeInstanceOf(ResultAsync) const val = await res expect(val.isErr()).toBe(true) expect(val._unsafeUnwrapErr()).toEqual(Error('Oops: No!')) }) it('has a top level export', () => { expect(fromPromise).toBe(ResultAsync.fromPromise) }) }) describe('ResultAsync.fromThrowable', () => { it('creates a new function that returns a ResultAsync', async () => { const example = ResultAsync.fromThrowable(async (a: number, b: number) => a + b) const res = example(4, 8) expect(res).toBeInstanceOf(ResultAsync) const val = await res expect(val.isOk()).toBe(true) expect(val._unsafeUnwrap()).toEqual(12) }) it('handles synchronous errors', async () => { const example = ResultAsync.fromThrowable(() => { if (1 > 0) throw new Error('Oops: No!') return Promise.resolve(12) }) const val = await example() expect(val.isErr()).toBe(true) expect(val._unsafeUnwrapErr()).toEqual(Error('Oops: No!')) }) it('handles asynchronous errors', async () => { const example = ResultAsync.fromThrowable(async () => { if (1 > 0) throw new Error('Oops: No!') return 12 }) const val = await example() expect(val.isErr()).toBe(true) expect(val._unsafeUnwrapErr()).toEqual(Error('Oops: No!')) }) it('Accepts an error handler as a second argument', async () => { const example = ResultAsync.fromThrowable( () => Promise.reject('No!'), (e) => new Error('Oops: ' + e), ) const val = await example() expect(val.isErr()).toBe(true) expect(val._unsafeUnwrapErr()).toEqual(TypeError('Oops: No!')) }) it('has a top level export', () => { expect(fromAsyncThrowable).toBe(ResultAsync.fromThrowable) }) }) describe('okAsync', () => { it('Creates a ResultAsync that resolves to an Ok', async () => { const val = okAsync(12) expect(val).toBeInstanceOf(ResultAsync) const res = await val expect(res.isOk()).toBe(true) expect(res._unsafeUnwrap()).toEqual(12) }) }) describe('errAsync', () => { it('Creates a ResultAsync that resolves to an Err', async () => { const err = errAsync('bad') expect(err).toBeInstanceOf(ResultAsync) const res = await err expect(res.isErr()).toBe(true) expect(res._unsafeUnwrapErr()).toEqual('bad') }) }) }) ================================================ FILE: tests/safe-try.test.ts ================================================ import { safeTry, ok, okAsync, err, errAsync, Ok, Err, Result, ResultAsync, } from "../src" import { describe, expect, test } from 'vitest' describe('Returns what is returned from the generator function', () => { const val = "value" test("With synchronous Ok", () => { const res = safeTry(function*() { return ok(val) }) expect(res).toBeInstanceOf(Ok) expect(res._unsafeUnwrap()).toBe(val) }) test("With synchronous Err", () => { const res = safeTry(function*() { return err(val) }) expect(res).toBeInstanceOf(Err) expect(res._unsafeUnwrapErr()).toBe(val) }) test("With async Ok", async () => { const res = await safeTry(async function*() { return await okAsync(val) }) expect(res).toBeInstanceOf(Ok) expect(res._unsafeUnwrap()).toBe(val) }) test("With async Err", async () => { const res = await safeTry(async function*() { return await errAsync(val) }) expect(res).toBeInstanceOf(Err) expect(res._unsafeUnwrapErr()).toBe(val) }) }) describe("Returns the first occurence of Err instance as yiled*'s operand", () => { test("With synchronous results", () => { const errVal = "err" const okValues = Array() const result = safeTry(function*() { const okFoo = yield* ok("foo").safeUnwrap() okValues.push(okFoo) const okBar = yield* ok("bar").safeUnwrap() okValues.push(okBar) yield* err(errVal).safeUnwrap() throw new Error("This line should not be executed") }) expect(okValues).toMatchObject(["foo", "bar"]) expect(result).toBeInstanceOf(Err) expect(result._unsafeUnwrapErr()).toBe(errVal) }) test("With async results", async () => { const errVal = "err" const okValues = Array() const result = await safeTry(async function*() { const okFoo = yield* okAsync("foo").safeUnwrap() okValues.push(okFoo) const okBar = yield* okAsync("bar").safeUnwrap() okValues.push(okBar) yield* errAsync(errVal).safeUnwrap() throw new Error("This line should not be executed") }) expect(okValues).toMatchObject(["foo", "bar"]) expect(result).toBeInstanceOf(Err) expect(result._unsafeUnwrapErr()).toBe(errVal) }) test("Mix results of synchronous and async in AsyncGenerator", async () => { const errVal = "err" const okValues = Array() const result = await safeTry(async function*() { const okFoo = yield* okAsync("foo").safeUnwrap() okValues.push(okFoo) const okBar = yield* ok("bar").safeUnwrap() okValues.push(okBar) yield* err(errVal).safeUnwrap() throw new Error("This line should not be executed") }) expect(okValues).toMatchObject(["foo", "bar"]) expect(result).toBeInstanceOf(Err) expect(result._unsafeUnwrapErr()).toBe(errVal) }) }) describe("Tests if README's examples work", () => { const okValue = 3 const errValue = "err!" function good(): Result { return ok(okValue) } function bad(): Result { return err(errValue) } function promiseGood(): Promise> { return Promise.resolve(ok(okValue)) } function promiseBad(): Promise> { return Promise.resolve(err(errValue)) } function asyncGood(): ResultAsync { return okAsync(okValue) } function asyncBad(): ResultAsync { return errAsync(errValue) } test("mayFail2 error", () => { function myFunc(): Result { return safeTry(function*() { return ok( (yield* good() .mapErr(e => `1st, ${e}`) .safeUnwrap()) + (yield* bad() .mapErr(e => `2nd, ${e}`) .safeUnwrap()) ) }) } const result = myFunc() expect(result.isErr()).toBe(true) expect(result._unsafeUnwrapErr()).toBe(`2nd, ${errValue}`) }) test("all ok", () => { function myFunc(): Result { return safeTry(function*() { return ok( (yield* good() .mapErr(e => `1st, ${e}`) .safeUnwrap()) + (yield* good() .mapErr(e => `2nd, ${e}`) .safeUnwrap()) ) }) } const result = myFunc() expect(result.isOk()).toBe(true) expect(result._unsafeUnwrap()).toBe(okValue + okValue) }) test("async mayFail1 error", async () => { function myFunc(): ResultAsync { return safeTry(async function*() { return ok( (yield* (await promiseBad()) .mapErr(e => `1st, ${e}`) .safeUnwrap()) + (yield* asyncGood() .mapErr(e => `2nd, ${e}`) .safeUnwrap()) ) }) } const result = await myFunc() expect(result.isErr()).toBe(true) expect(result._unsafeUnwrapErr()).toBe(`1st, ${errValue}`) }) test("async mayFail2 error", async () => { function myFunc(): ResultAsync { return safeTry(async function*() { return ok( (yield* (await promiseGood()) .mapErr(e => `1st, ${e}`) .safeUnwrap()) + (yield* asyncBad() .mapErr(e => `2nd, ${e}`) .safeUnwrap()) ) }) } const result = await myFunc() expect(result.isErr()).toBe(true) expect(result._unsafeUnwrapErr()).toBe(`2nd, ${errValue}`) }) test("promise async all ok", async () => { function myFunc(): ResultAsync { return safeTry(async function*() { return ok( (yield* (await promiseGood()) .mapErr(e => `1st, ${e}`) .safeUnwrap()) + (yield* asyncGood() .mapErr(e => `2nd, ${e}`) .safeUnwrap()) ) }) } const result = await myFunc() expect(result.isOk()).toBe(true) expect(result._unsafeUnwrap()).toBe(okValue + okValue) }) }) describe("it yields and works without safeUnwrap", () => { test("With synchronous Ok", () => { const res: Result = ok("ok"); const actual = safeTry(function* () { const x = yield* res; return ok(x); }); expect(actual).toBeInstanceOf(Ok); expect(actual._unsafeUnwrap()).toBe("ok"); }); test("With synchronous Err", () => { const res: Result = err("error"); const actual = safeTry(function* () { const x = yield* res; return ok(x); }); expect(actual).toBeInstanceOf(Err); expect(actual._unsafeUnwrapErr()).toBe("error"); }); const okValue = 3; const errValue = "err!"; function good(): Result { return ok(okValue); } function bad(): Result { return err(errValue); } function promiseGood(): Promise> { return Promise.resolve(ok(okValue)); } function promiseBad(): Promise> { return Promise.resolve(err(errValue)); } function asyncGood(): ResultAsync { return okAsync(okValue); } function asyncBad(): ResultAsync { return errAsync(errValue); } test("mayFail2 error", () => { function fn(): Result { return safeTry(function* () { const first = yield* good().mapErr((e) => `1st, ${e}`); const second = yield* bad().mapErr((e) => `2nd, ${e}`); return ok(first + second); }); } const result = fn(); expect(result.isErr()).toBe(true); expect(result._unsafeUnwrapErr()).toBe(`2nd, ${errValue}`); }); test("all ok", () => { function myFunc(): Result { return safeTry(function* () { const first = yield* good().mapErr((e) => `1st, ${e}`); const second = yield* good().mapErr((e) => `2nd, ${e}`); return ok(first + second); }); } const result = myFunc(); expect(result.isOk()).toBe(true); expect(result._unsafeUnwrap()).toBe(okValue + okValue); }); test("async mayFail1 error", async () => { function myFunc(): ResultAsync { return safeTry(async function* () { const first = yield* (await promiseBad()).mapErr((e) => `1st, ${e}`); const second = yield* asyncGood().mapErr((e) => `2nd, ${e}`); return ok(first + second); }); } const result = await myFunc(); expect(result.isErr()).toBe(true); expect(result._unsafeUnwrapErr()).toBe(`1st, ${errValue}`); }); test("async mayFail2 error", async () => { function myFunc(): ResultAsync { return safeTry(async function* () { const goodResult = await promiseGood(); const value = yield* goodResult.mapErr((e) => `1st, ${e}`); const value2 = yield* asyncBad().mapErr((e) => `2nd, ${e}`); return okAsync(value + value2); }); } const result = await myFunc(); expect(result.isErr()).toBe(true); expect(result._unsafeUnwrapErr()).toBe(`2nd, ${errValue}`); }); test("promise async all ok", async () => { function myFunc(): ResultAsync { return safeTry(async function* () { const first = yield* (await promiseGood()).mapErr((e) => `1st, ${e}`); const second = yield* asyncGood().mapErr((e) => `2nd, ${e}`); return ok(first + second); }); } const result = await myFunc(); expect(result.isOk()).toBe(true); expect(result._unsafeUnwrap()).toBe(okValue + okValue); }); }) ================================================ FILE: tests/tsconfig.tests.json ================================================ { "compilerOptions": { "target": "es2016", "module": "ES2015", "noImplicitAny": true, "sourceMap": false, "downlevelIteration": true, "noUnusedLocals": false, "noUnusedParameters": false, "strictNullChecks": true, "strictFunctionTypes": true, "declaration": true, "moduleResolution": "Node", "baseUrl": "./src", "lib": [ "dom", "es2016", "es2017.object" ], "outDir": "dist", "skipLibCheck": true }, "include": [ "./index.test.ts", "./typecheck-tests.ts" ] } ================================================ FILE: tests/typecheck-tests.ts ================================================ /* * Type Tests * * This file is ran during CI to ensure that there aren't breaking changes with types */ import { err, errAsync, fromSafePromise, ok, okAsync, Result, ResultAsync, } from '../src' import { safeTry, Transpose } from '../src/result' import { type N, Test } from 'ts-toolbelt' type CreateTuple = // Length must always be a number L extends number ? N.IsNegative extends 1 // Length must always be non-negative ? never // base case : L extends 0 ? [] // recursion depth check // typescript has a limit. : N.Lower extends 1 ? [V, ...CreateTuple, V>] : never : never; (function describe(_ = 'Result') { (function describe(_ = 'andThen') { (function it(_ = 'Combines two equal error types (native scalar types)') { type Expectation = Result const result: Expectation = ok(123) .andThen((val) => err('yoooooo dude' + val)) }); (function it(_ = 'Combines two equal error types (custom types)') { interface MyError { stack: string code: number } type Expectation = Result const result: Expectation = ok(123) .andThen((val) => err({ stack: '/blah', code: 500 })) }); (function it(_ = 'Creates a union of error types for disjoint types') { interface MyError { stack: string code: number } type Expectation = Result const result: Expectation = ok(123) .andThen((val) => err(['oh nooooo'])) }); (function it(_ = 'Infers error type when returning disjoint types (native scalar types)') { type Expectation = Result const result: Expectation = ok(123) .andThen((val) => { switch (val) { case 1: return err('yoooooo dude' + val) case 2: return err(123) default: return err(false) } }) }); (function it(_ = 'Infers error type when returning disjoint types (custom types)') { interface MyError { stack: string code: number } type Expectation = Result const result: Expectation = ok(123) .andThen((val) => { switch (val) { case 1: return err('yoooooo dude' + val) case 2: return err(123) default: return err({ stack: '/blah', code: 500 }) } }) }); (function it(_ = 'Infers new ok type when returning both Ok and Err (same as initial)') { type Expectation = Result const result: Expectation = ok(123) .andThen((val) => { switch (val) { case 1: return err('yoooooo dude' + val) default: return ok(val + 456) } }) }); (function it(_ = 'Infers new ok type when returning both Ok and Err (different from initial)') { const initial = ok(123) type Expectation = Result const result: Expectation = initial .andThen((val) => { switch (val) { case 1: return err('yoooooo dude' + val) default: return ok(val + ' string') } }) }); (function it(_ = 'Infers new err type when returning both Ok and Err') { interface MyError { stack: string code: number } type Expectation = Result const result: Expectation = ok(123) .andThen((val) => { switch (val) { case 1: return err('yoooooo dude' + val) case 2: return ok(123) default: return err({ stack: '/blah', code: 500 }) } }) }); (function it(_ = 'allows specifying the E and T types explicitly') { type Expectation = Result<'yo', number> const result: Expectation = ok(123).andThen<'yo', number>(val => { return ok('yo') }) }); }); (function describe(_ = 'andThrough') { (function it(_ = 'Combines two equal error types (native scalar types)') { type Expectation = Result const result: Expectation = ok(123) .andThrough((val) => err('yoooooo dude' + val)) }); (function it(_ = 'Combines two equal error types (custom types)') { interface MyError { stack: string code: number } type Expectation = Result const result: Expectation = ok(123) .andThrough((val) => err({ stack: '/blah', code: 500 })) }); (function it(_ = 'Creates a union of error types for disjoint types') { interface MyError { stack: string code: number } type Expectation = Result const result: Expectation = ok(123) .andThrough((val) => err(['oh nooooo'])) }); (function it(_ = 'Infers error type when returning disjoint types (native scalar types)') { type Expectation = Result const result: Expectation = ok(123) .andThrough((val) => { switch (val) { case 1: return err('yoooooo dude' + val) case 2: return err(123) default: return err(false) } }) }); (function it(_ = 'Infers error type when returning disjoint types (custom types)') { interface MyError { stack: string code: number } type Expectation = Result const result: Expectation = ok(123) .andThrough((val) => { switch (val) { case 1: return err('yoooooo dude' + val) case 2: return err(123) default: return err({ stack: '/blah', code: 500 }) } }) }); (function it(_ = 'Returns the original ok type when returning both Ok and Err (same as initial)') { type Expectation = Result const result: Expectation = ok(123) .andThrough((val) => { switch (val) { case 1: return err('yoooooo dude' + val) default: return ok(val + 456) } }) }); (function it(_ = 'Returns the original ok type when returning both Ok and Err (different from initial)') { const initial = ok(123) type Expectation = Result const result: Expectation = initial .andThrough((val) => { switch (val) { case 1: return err('yoooooo dude' + val) default: return ok("Hi" + val) } }) }); (function it(_ = 'Infers new err type when returning both Ok and Err') { interface MyError { stack: string code: number } type Expectation = Result const result: Expectation = ok(123) .andThrough((val) => { switch (val) { case 1: return err('yoooooo dude' + val) case 2: return ok(123) default: return err({ stack: '/blah', code: 500 }) } }) }); (function it(_ = 'allows specifying the E type explicitly') { type Expectation = Result const result: Expectation = ok(123).andThrough(val => { return ok('yo') }) }); }); (function describe(_ = 'orElse') { (function it(_ = 'the type of the argument is the error type of the result') { type Expectation = string const result = ok(123) .orElse((val: Expectation) => { switch (val) { case '2': return err(1) default: return err(1) } }) }); (function it(_ = 'infers the err return type with multiple returns (same type) ') { type Expectation = Result const result: Expectation = ok(123) .orElse((val) => { switch (val) { case '2': return err(1) default: return err(1) } }) }); (function it(_ = 'infers the err return type with multiple returns (different type) ') { type Expectation = Result const result: Expectation = ok(123) .orElse((val) => { switch (val) { case '2': return err(1) default: return err('1') } }) }); (function it(_ = 'infers ok and err return types with multiple returns ') { type Expectation = Result const result: Expectation = ok(123) .orElse((val) => { switch (val) { case '1': return ok(1) case '2': return err(1) default: return err('1') } }) }); (function it(_ = 'allows specifying the E and T types explicitly') { type Expectation = Result<'yo', string> const result: Expectation = ok<'yo', number>('yo').orElse<'yo', string>(val => { return err('yo') }) }); (function it(_ = 'Creates a union of ok types for disjoint types') { type Expectation = Result const result: Expectation = err([true]) .orElse((val) => ok('recovered!')) }); (function it(_ = 'Infers ok type when returning disjoint types') { type Expectation = Result const result: Expectation = err(123) .orElse((val) => { switch (val) { case 1: return ok('yoooooo dude' + val) case 2: return ok(123) default: return ok(false) } }) }); (function it(_ = 'Infers new type when returning both Ok and Err') { const initial = err(123) type Expectation = Result const result: Expectation = initial .orElse((val) => { switch (val) { case 1: return err(false as const) default: return ok(true as const) } }) }); }); (function describe(_ = 'match') { (function it(_ = 'the type of the arguments match the types of the result') { type OKExpectation = number type ErrExpectation = string ok(123) .match( (val: OKExpectation): void => void val, (val: ErrExpectation): void => void val, ); err("123") .match( (val: OKExpectation): void => void val, (val: ErrExpectation): void => void val, ); }); (function it(_ = 'infers the resulting value from match callbacks (same type)') { type Expectation = boolean const okResult: Expectation = ok(123) .match( (val) => !!val, (val) => !!val, ); const errResult: Expectation = err('123') .match( (val) => !!val, (val) => !!val, ); }); (function it(_ = 'infers the resulting value from match callbacks (different type)') { type Expectation = boolean | bigint const okResult: Expectation = ok('123') .match( (val) => !!val, (val) => BigInt(val), ); const errResult: Expectation = err(123) .match( (val) => !!val, (val) => BigInt(val), ); }); }); (function describe(_ = 'asyncAndThen') { (function it(_ = 'Combines two equal error types (native scalar types)') { type Expectation = ResultAsync const result: Expectation = ok(123) .asyncAndThen((val) => errAsync('yoooooo dude' + val)) }); (function it(_ = 'Combines two equal error types (custom types)') { interface MyError { stack: string code: number } type Expectation = ResultAsync const result: Expectation = ok(123) .asyncAndThen((val) => errAsync({ stack: '/blah', code: 500 })) }); (function it(_ = 'Creates a union of error types for disjoint types') { interface MyError { stack: string code: number } type Expectation = ResultAsync const result: Expectation = ok(123) .asyncAndThen((val) => errAsync(['oh nooooo'])) }); }); (function describe(_ = 'asyncAndThrough') { (function it(_ = 'Combines two equal error types (native scalar types)') { type Expectation = ResultAsync const result: Expectation = ok(123) .asyncAndThrough((val) => errAsync('yoooooo dude' + val)) }); (function it(_ = 'Combines two equal error types (custom types)') { interface MyError { stack: string code: number } type Expectation = ResultAsync const result: Expectation = ok(123) .asyncAndThrough((val) => errAsync({ stack: '/blah', code: 500 })) }); (function it(_ = 'Creates a union of error types for disjoint types') { interface MyError { stack: string code: number } type Expectation = ResultAsync const result: Expectation = ok(123) .asyncAndThrough((val) => errAsync(['oh nooooo'])) }); (function it(_ = 'Infers error type when returning disjoint types (native scalar types)') { type Expectation = ResultAsync const result: Expectation = ok(123) .asyncAndThrough((val) => { switch (val) { case 1: return errAsync('yoooooo dude' + val) case 2: return errAsync(123) default: return errAsync(false) } }) }); (function it(_ = 'Infers error type when returning disjoint types (custom types)') { interface MyError { stack: string code: number } type Expectation = Result const result: Expectation = ok(123) .andThrough((val) => { switch (val) { case 1: return err('yoooooo dude' + val) case 2: return err(123) default: return err({ stack: '/blah', code: 500 }) } }) }); (function it(_ = 'Returns the original ok type when returning both Ok and Err (same as initial)') { type Expectation = Result const result: Expectation = ok(123) .andThrough((val) => { switch (val) { case 1: return err('yoooooo dude' + val) default: return ok(val + 456) } }) }); (function it(_ = 'Returns the original ok type when returning both Ok and Err (different from initial)') { const initial = ok(123) type Expectation = Result const result: Expectation = initial .andThrough((val) => { switch (val) { case 1: return err('yoooooo dude' + val) default: return ok("Hi" + val) } }) }); (function it(_ = 'Infers new err type when returning both Ok and Err') { interface MyError { stack: string code: number } type Expectation = Result const result: Expectation = ok(123) .andThrough((val) => { switch (val) { case 1: return err('yoooooo dude' + val) case 2: return ok(123) default: return err({ stack: '/blah', code: 500 }) } }) }); (function it(_ = 'allows specifying the E type explicitly') { type Expectation = Result const result: Expectation = ok(123).andThrough(val => { return ok('yo') }) }); (function it(_ = 'Infers new err type when returning both Ok and Err') { interface MyError { stack: string code: number } type Expectation = ResultAsync const result: Expectation = ok(123) .asyncAndThrough((val) => { switch (val) { case 1: return errAsync('yoooooo dude' + val) case 2: return okAsync(123) default: return errAsync({ stack: '/blah', code: 500 }) } }) }); }); (function describe(_ = 'combine') { (function it(_ = 'combines different results into one') { type Expectation = Result<[ number, string, boolean, boolean ], Error | string | string[]>; const result = Result.combine([ ok(1), ok('string'), err([ 'string', 'string2' ]), err(new Error('error content')), ]) const assignableToCheck: Expectation = result; const assignablefromCheck: typeof result = assignableToCheck; }); (function it(_ = 'combines only ok results into one') { type Expectation = Result<[ number, string ], never>; const result = Result.combine([ ok(1), ok('string'), ]); const assignableToCheck: Expectation = result; const assignablefromCheck: typeof result = assignableToCheck; }); (function it(_ = 'combines only err results into one') { type Expectation = Result<[ never, never ], number | 'abc'>; const result = Result.combine([ err(1), err('abc'), ]); const assignableToCheck: Expectation = result; const assignablefromCheck: typeof result = assignableToCheck; }); (function it(_ = 'combines empty list results into one') { type Expectation = Result; const results: [] = []; const result = Result.combine(results); const assignableToCheck: Expectation = result; const assignablefromCheck: typeof result = assignableToCheck; }); (function it(_ = 'combines arrays of results to a result of an array') { type Expectation = Result; const results: Result[] = []; const result = Result.combine(results); const assignableToCheck: Expectation = result; const assignablefromCheck: typeof result = assignableToCheck; }); (function describe(_ = 'inference on large tuples') { (function it(_ = 'Should correctly infer the type on tuples with 6 elements') { type Input = CreateTuple<6, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 7 elements') { type Input = CreateTuple<7, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 8 elements') { type Input = CreateTuple<8, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 9 elements') { type Input = CreateTuple<9, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 10 elements') { type Input = CreateTuple<10, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 11 elements') { type Input = CreateTuple<11, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 12 elements') { type Input = CreateTuple<12, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 13 elements') { type Input = CreateTuple<13, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 14 elements') { type Input = CreateTuple<14, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 15 elements') { type Input = CreateTuple<15, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 16 elements') { type Input = CreateTuple<16, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 17 elements') { type Input = CreateTuple<17, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 18 elements') { type Input = CreateTuple<18, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 19 elements') { type Input = CreateTuple<19, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 20 elements') { type Input = CreateTuple<20, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 21 elements') { type Input = CreateTuple<21, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 22 elements') { type Input = CreateTuple<22, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 23 elements') { type Input = CreateTuple<23, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 24 elements') { type Input = CreateTuple<24, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 25 elements') { type Input = CreateTuple<25, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 26 elements') { type Input = CreateTuple<26, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 27 elements') { type Input = CreateTuple<27, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 28 elements') { type Input = CreateTuple<28, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 29 elements') { type Input = CreateTuple<29, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 30 elements') { type Input = CreateTuple<30, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 31 elements') { type Input = CreateTuple<31, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 32 elements') { type Input = CreateTuple<32, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 33 elements') { type Input = CreateTuple<33, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 34 elements') { type Input = CreateTuple<34, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 35 elements') { type Input = CreateTuple<35, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 36 elements') { type Input = CreateTuple<36, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 37 elements') { type Input = CreateTuple<37, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 38 elements') { type Input = CreateTuple<38, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 39 elements') { type Input = CreateTuple<39, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 40 elements') { type Input = CreateTuple<40, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 41 elements') { type Input = CreateTuple<41, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 42 elements') { type Input = CreateTuple<42, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 43 elements') { type Input = CreateTuple<43, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 44 elements') { type Input = CreateTuple<44, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 45 elements') { type Input = CreateTuple<45, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 46 elements') { type Input = CreateTuple<46, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 47 elements') { type Input = CreateTuple<47, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 48 elements') { type Input = CreateTuple<48, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 49 elements') { type Input = CreateTuple<49, Result> type Expectation = Result, never> const inputValues = input() const result = Result.combine(inputValues) Test.checks([ Test.check(), ]) }); }); }); (function describe(_ = 'combineWithAllErrors') { (function it(_ = 'combines different results into one') { type Expectation = Result<[ number, string, never, never ], (string[] | Error)[]>; const result = Result.combineWithAllErrors([ ok(1), ok('string'), err([ 'string', 'string2' ]), err(new Error('error content')), ]); const assignableToCheck: Expectation = result; const assignablefromCheck: typeof result = assignableToCheck; }); (function it(_ = 'combines only ok results into one') { type Expectation = Result<[ number, string ], never[]>; const result = Result.combineWithAllErrors([ ok(1), ok('string'), ]); const assignableToCheck: Expectation = result; const assignablefromCheck: typeof result = assignableToCheck; }); (function it(_ = 'combines only err results into one') { type Expectation = Result<[ never, never ], (number | 'string')[]>; const result = Result.combineWithAllErrors([ err(1), err('string'), ]); const assignableToCheck: Expectation = result; const assignablefromCheck: typeof result = assignableToCheck; }); (function it(_ = 'combines arrays of results to a result of an array') { type Expectation = Result; const results: Result[] = []; const result = Result.combineWithAllErrors(results); const assignableToCheck: Expectation = result; const assignablefromCheck: typeof result = assignableToCheck; }); (function it(_ = 'combines arrays of different results to a result of an array') { type Expectation = Result<(string | boolean)[], (number | string)[]>; const results: (Result | Result)[] = []; const result = Result.combineWithAllErrors(results); const assignableToCheck: Expectation = result; const assignablefromCheck: typeof result = assignableToCheck; }); (function describe(_ = 'inference on large tuples') { (function it(_ = 'Should correctly infer the type on tuples with 6 elements') { type Input = CreateTuple<6, Result> type Expectation = Result, number[]> const inputValues = input() const result = Result.combineWithAllErrors(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 15 elements') { type Input = CreateTuple<15, Result> type Expectation = Result, number[]> const inputValues = input() const result = Result.combineWithAllErrors(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 30 elements') { type Input = CreateTuple<30, Result> type Expectation = Result, number[]> const inputValues = input() const result = Result.combineWithAllErrors(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 49 elements') { type Input = CreateTuple<49 , Result> type Expectation = Result, number[]> const inputValues = input() const result = Result.combineWithAllErrors(inputValues) Test.checks([ Test.check(), ]) }); }); }); (function describe(_ = 'err') { (function it(_ = 'infers the error type narrowly when it is a string') { type Expectation = Result const result = err('error') const assignableToCheck: Expectation = result; }); (function it(_ = 'infers the error type widely when it is not a string') { type Expectation = Result const result = err({ abc: 123 }) const assignableToCheck: Expectation = result; }); }) }); (function describe(_ = 'ResultAsync') { (function describe(_ = 'andThen') { (function it(_ = 'Combines two equal error types (native scalar types)') { type Expectation = ResultAsync const result: Expectation = okAsync(123) .andThen((val) => err('yoooooo dude' + val)) }); (function it(_ = 'Combines two equal error types (custom types)') { interface MyError { stack: string code: number } type Expectation = ResultAsync const result: Expectation = okAsync(123) .andThen((val) => err({ stack: '/blah', code: 500 })) }); (function it(_ = 'Creates a union of error types for disjoint types') { interface MyError { stack: string code: number } type Expectation = ResultAsync const result: Expectation = okAsync(123) .andThen((val) => err(['oh nooooo'])) }); (function describe(_ = 'when returning Result types') { (function it(_ = 'Infers error type when returning disjoint types (native scalar types)') { type Expectation = ResultAsync const result: Expectation = okAsync(123) .andThen((val) => { switch (val) { case 1: return err('yoooooo dude' + val) case 2: return err(123) default: return err(false) } }) }); (function it(_ = 'Infers error type when returning disjoint types (custom types)') { interface MyError { stack: string code: number } type Expectation = ResultAsync const result: Expectation = okAsync(123) .andThen((val) => { switch (val) { case 1: return err('yoooooo dude' + val) case 2: return err(123) default: return err({ stack: '/blah', code: 500 }) } }) }); (function it(_ = 'Infers new ok type when returning both Ok and Err (same as initial)') { type Expectation = ResultAsync const result: Expectation = okAsync(123) .andThen((val) => { switch (val) { case 1: return err('yoooooo dude' + val) default: return ok(val + 456) } }) }); (function it(_ = 'Infers new ok type when returning both Ok and Err (different from initial)') { const initial = okAsync(123) type Expectation = ResultAsync const result: Expectation = initial .andThen((val) => { switch (val) { case 1: return err('yoooooo dude' + val) default: return ok(val + ' string') } }) }); (function it(_ = 'Infers new err type when returning both Ok and Err') { interface MyError { stack: string code: number } type Expectation = ResultAsync const result: Expectation = okAsync(123) .andThen((val) => { switch (val) { case 1: return err('yoooooo dude' + val) case 2: return ok(123) default: return err({ stack: '/blah', code: 500 }) } }) }); }); (function describe(_ = 'when returning ResultAsync types') { (function it(_ = 'Infers error type when returning disjoint types (native scalar types)') { type Expectation = ResultAsync const result: Expectation = okAsync(123) .andThen((val) => { switch (val) { case 1: return errAsync('yoooooo dude' + val) case 2: return errAsync(123) default: return errAsync(false) } }) }); (function it(_ = 'Infers error type when returning disjoint types (custom types)') { interface MyError { stack: string code: number } type Expectation = ResultAsync const result: Expectation = okAsync(123) .andThen((val) => { switch (val) { case 1: return errAsync('yoooooo dude' + val) case 2: return errAsync(123) default: return errAsync({ stack: '/blah', code: 500 }) } }) }); (function it(_ = 'Infers new ok type when returning both Ok and Err (same as initial)') { type Expectation = ResultAsync const result: Expectation = okAsync(123) .andThen((val) => { switch (val) { case 1: return errAsync('yoooooo dude' + val) default: return okAsync(val + 456) } }) }); (function it(_ = 'Infers new ok type when returning both Ok and Err (different from initial)') { const initial = okAsync(123) type Expectation = ResultAsync const result: Expectation = initial .andThen((val) => { switch (val) { case 1: return errAsync('yoooooo dude' + val) default: return okAsync(val + ' string') } }) }); (function it(_ = 'Infers new err type when returning both Ok and Err') { interface MyError { stack: string code: number } type Expectation = ResultAsync const result: Expectation = okAsync(123) .andThen((val) => { switch (val) { case 1: return errAsync('yoooooo dude' + val) case 2: return okAsync(123) default: return errAsync({ stack: '/blah', code: 500 }) } }) }); }); (function describe(_ = 'when returning a mix of Result and ResultAsync types') { (function it(_ = 'allows for explicitly specifying the Ok and Err types when inference fails') { type Expectation = ResultAsync const result: Expectation = okAsync(123) .andThen((val) => { switch (val) { case 1: return errAsync('yoooooo dude' + val) case 2: return err(123) default: return okAsync(false) } }) }); }); (function describe(_ = 'fromSafePromise') { (function it(_ = 'infers err type from usage') { type Expectation = ResultAsync const result: Expectation = fromSafePromise(new Promise((resolve) => resolve(123))) .map((val) => val) }); }); }); (function describe(_ = 'orElse') { (function it(_ = 'the type of the argument is the error type of the result') { type Expectation = string const result = okAsync(123) .orElse((val: Expectation) => { switch (val) { case '2': return errAsync(1) default: return errAsync(1) } }) }); (function it(_ = 'infers the err return type with multiple returns (same type) ') { type Expectation = ResultAsync const result: Expectation = okAsync(123) .orElse((val) => { switch (val) { case '2': return errAsync(1) default: return errAsync(1) } }) }); (function it(_ = 'infers the err return type with multiple returns (different type) ') { type Expectation = ResultAsync const result: Expectation = okAsync(123) .orElse((val) => { switch (val) { case '2': return errAsync(1) default: return errAsync('1') } }) }); (function it(_ = 'infers ok and err return types with multiple returns ') { type Expectation = ResultAsync const result: Expectation = okAsync(123) .orElse((val) => { switch (val) { case '1': return okAsync(1) case '2': return errAsync(1) default: return errAsync('1') } }) }); (function it(_ = 'allows specifying ok and err return types when mixing Result and ResultAsync in returns ') { type Expectation = ResultAsync const result: Expectation = okAsync(123) .orElse((val) => { switch (val) { case '1': return ok(1) case '2': return errAsync(1) default: return errAsync('1') } }) }); (function it(_ = 'Creates a union of ok types for disjoint types') { type Expectation = ResultAsync const result: Expectation = errAsync([true]) .orElse((val) => ok('recovered!')) }); (function it(_ = 'Infers ok type when returning disjoint types') { type Expectation = ResultAsync const result: Expectation = errAsync(123) .orElse((val) => { switch (val) { case 1: return okAsync('yoooooo dude' + val) case 2: return okAsync(123) default: return okAsync(false) } }) }); (function it(_ = 'Infers new type when returning both Ok and Err') { const initial = errAsync(123) type Expectation = ResultAsync const result: Expectation = initial .orElse((val) => { switch (val) { case 1: return err(false as const) default: return okAsync(true as const) } }) }); }); (function describe(_ = 'combine') { (function it(_ = 'combines different result asyncs into one') { type Expectation = ResultAsync<[ number, string, boolean, boolean ], Error | string | string[]>; const result = ResultAsync.combine([ okAsync(1), okAsync('string'), errAsync([ 'string', 'string2' ]), errAsync(new Error('error content')), ]) const assignableToCheck: Expectation = result; const assignablefromCheck: typeof result = assignableToCheck; }); (function it(_ = 'combines only ok result asyncs into one') { type Expectation = ResultAsync<[ number, string ], never>; const result = ResultAsync.combine([ okAsync(1), okAsync('string'), ]); const assignableToCheck: Expectation = result; const assignablefromCheck: typeof result = assignableToCheck; }); (function it(_ = 'combines only err results into one') { type Expectation = ResultAsync<[ never, never ], number | string>; const result = ResultAsync.combine([ errAsync(1), errAsync('string'), ]); const assignableToCheck: Expectation = result; const assignablefromCheck: typeof result = assignableToCheck; }); (function it(_ = 'combines empty list result asyncs into one') { type Expectation = ResultAsync; const results: [] = []; const result = ResultAsync.combine(results); const assignableToCheck: Expectation = result; const assignablefromCheck: typeof result = assignableToCheck; }); (function it(_ = 'combines arrays of result asyncs to a result async of an array') { type Expectation = ResultAsync; const results: ResultAsync[] = []; const result = ResultAsync.combine(results); const assignableToCheck: Expectation = result; const assignablefromCheck: typeof result = assignableToCheck; }); (function describe(_ = 'inference on large tuples') { (function it(_ = 'Should correctly infer the type on tuples with 6 elements') { type Input = CreateTuple<6, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 7 elements') { type Input = CreateTuple<7, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 8 elements') { type Input = CreateTuple<8, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 9 elements') { type Input = CreateTuple<9, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 10 elements') { type Input = CreateTuple<10, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 11 elements') { type Input = CreateTuple<11, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 12 elements') { type Input = CreateTuple<12, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 13 elements') { type Input = CreateTuple<13, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 14 elements') { type Input = CreateTuple<14, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 15 elements') { type Input = CreateTuple<15, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 16 elements') { type Input = CreateTuple<16, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 17 elements') { type Input = CreateTuple<17, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 18 elements') { type Input = CreateTuple<18, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 19 elements') { type Input = CreateTuple<19, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 20 elements') { type Input = CreateTuple<20, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 21 elements') { type Input = CreateTuple<21, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 22 elements') { type Input = CreateTuple<22, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 23 elements') { type Input = CreateTuple<23, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 24 elements') { type Input = CreateTuple<24, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 25 elements') { type Input = CreateTuple<25, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 26 elements') { type Input = CreateTuple<26, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 27 elements') { type Input = CreateTuple<27, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 28 elements') { type Input = CreateTuple<28, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 29 elements') { type Input = CreateTuple<29, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 30 elements') { type Input = CreateTuple<30, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 31 elements') { type Input = CreateTuple<31, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 32 elements') { type Input = CreateTuple<32, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 33 elements') { type Input = CreateTuple<33, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 34 elements') { type Input = CreateTuple<34, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 35 elements') { type Input = CreateTuple<35, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 36 elements') { type Input = CreateTuple<36, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 37 elements') { type Input = CreateTuple<37, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 38 elements') { type Input = CreateTuple<38, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 39 elements') { type Input = CreateTuple<39, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 40 elements') { type Input = CreateTuple<40, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 41 elements') { type Input = CreateTuple<41, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 42 elements') { type Input = CreateTuple<42, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 43 elements') { type Input = CreateTuple<43, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 44 elements') { type Input = CreateTuple<44, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 45 elements') { type Input = CreateTuple<45, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 46 elements') { type Input = CreateTuple<46, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 47 elements') { type Input = CreateTuple<47, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 48 elements') { type Input = CreateTuple<48, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 49 elements') { type Input = CreateTuple<49, ResultAsync> type Expectation = ResultAsync, never> const inputValues = input() const result = ResultAsync.combine(inputValues) Test.checks([ Test.check(), ]) }); }); }); (function describe(_ = 'combineWithAllErrors') { (function it(_ = 'combines different result asyncs into one') { type Expectation = ResultAsync<[ number, string, never, never ], (string[] | Error)[]>; const result = ResultAsync.combineWithAllErrors([ okAsync(1), okAsync('string'), errAsync([ 'string', 'string2' ]), errAsync(new Error('error content')), ]); const assignableToCheck: Expectation = result; const assignablefromCheck: typeof result = assignableToCheck; }); (function it(_ = 'combines only ok result asyncs into one') { type Expectation = ResultAsync<[ number, string ], never[]>; const result = ResultAsync.combineWithAllErrors([ okAsync(1), okAsync('string'), ]); const assignableToCheck: Expectation = result; const assignablefromCheck: typeof result = assignableToCheck; }); (function it(_ = 'combines only err result asyncs into one') { type Expectation = ResultAsync<[ never, never ], (number | string)[]>; const result = ResultAsync.combineWithAllErrors([ errAsync(1), errAsync('string'), ]); const assignableToCheck: Expectation = result; const assignablefromCheck: typeof result = assignableToCheck; }); (function it(_ = 'combines arrays of result asyncs to a result of an array') { type Expectation = ResultAsync; const results: ResultAsync[] = []; const result = ResultAsync.combineWithAllErrors(results); const assignableToCheck: Expectation = result; const assignablefromCheck: typeof result = assignableToCheck; }); (function it(_ = 'combines arrays of different result asyncs to a result of an array') { type Expectation = ResultAsync<(string | boolean)[], (number | string)[]>; const results: (ResultAsync | ResultAsync)[] = []; const result = ResultAsync.combineWithAllErrors(results); const assignableToCheck: Expectation = result; const assignablefromCheck: typeof result = assignableToCheck; }); (function describe(_ = 'inference on large tuples') { (function it(_ = 'Should correctly infer the type on tuples with 6 elements') { type Input = CreateTuple<6, ResultAsync> type Expectation = ResultAsync, number[]> const inputValues = input() const result = ResultAsync.combineWithAllErrors(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 15 elements') { type Input = CreateTuple<15, ResultAsync> type Expectation = ResultAsync, number[]> const inputValues = input() const result = ResultAsync.combineWithAllErrors(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 30 elements') { type Input = CreateTuple<30, ResultAsync> type Expectation = ResultAsync, number[]> const inputValues = input() const result = ResultAsync.combineWithAllErrors(inputValues) Test.checks([ Test.check(), ]) }); (function it(_ = 'Should correctly infer the type on tuples with 49 elements') { type Input = CreateTuple<49 , ResultAsync> type Expectation = ResultAsync, number[]> const inputValues = input() const result = ResultAsync.combineWithAllErrors(inputValues) Test.checks([ Test.check(), ]) }); }); }); }); (function describe(_ = 'Utility types') { (function describe(_ = 'safeTry') { (function describe(_ = 'sync generator') { (function it(_ = 'should correctly infer the result type when generator returns Ok') { interface ReturnMyError { name: 'ReturnMyError' } type Expectation = Result const result = safeTry(function *() { return ok('string'); }) Test.checks([ Test.check(), ]) }); (function it(_ = 'should correctly infer the result type when generator returns Err') { interface ReturnMyError { name: 'ReturnMyError'; } type Expectation = Result const result = safeTry(function *() { return err({ name: 'ReturnMyError' }); }) Test.checks([ Test.check(), ]) }); (function it(_ = 'infers the value type when calling "yield*"') { interface YieldMyError { name: 'YieldMyError'; } interface ReturnMyError { name: 'ReturnMyError'; } safeTry(function *() { type Expectation = number const unwrapped = yield* ok(123).safeUnwrap(); Test.checks([ Test.check(), ]) return ok('string'); }) }); (function it(_ = 'should correctly infer the result type with multiple "yield*"') { interface FirstYieldMyError { name: 'FirstYieldMyError'; } interface SecondYieldMyError { name: 'SecondYieldMyError'; } interface ReturnMyError { name: 'ReturnMyError'; } type Expectation = Result const result = safeTry(function *() { yield* ok(123).safeUnwrap(); yield* err({ name: 'SecondYieldMyError' }).safeUnwrap(); return ok('string'); }) Test.checks([ Test.check(), ]) }); }); (function describe(_ = 'async generator') { (function it(_ = 'should correctly infer the result type when generator returns OkAsync') { interface ReturnMyError { name: 'ReturnMyError' } type Expectation = ResultAsync const result = safeTry(async function *() { return okAsync('string'); }) Test.checks([ Test.check(), ]) }); (function it(_ = 'should correctly infer the result type when generator returns ErrAsync') { interface ReturnMyError { name: 'ReturnMyError'; } type Expectation = ResultAsync const result = safeTry(async function *() { return errAsync({ name: 'ReturnMyError' }); }) Test.checks([ Test.check(), ]) }); (function it(_ = 'infers the value type when calling "yield*"') { interface YieldMyError { name: 'YieldMyError'; } interface ReturnMyError { name: 'ReturnMyError'; } safeTry(async function *() { type Expectation = number const unwrapped = yield* okAsync(123).safeUnwrap(); Test.checks([ Test.check(), ]) return ok('string'); }) }); (function it(_ = 'should correctly infer the result type with multiple "yield*"') { interface FirstYieldMyError { name: 'FirstYieldMyError'; } interface SecondYieldMyError { name: 'SecondYieldMyError'; } interface ReturnMyError { name: 'ReturnMyError'; } type Expectation = ResultAsync const result = safeTry(async function *() { yield* okAsync(123).safeUnwrap(); yield* errAsync({ name: 'SecondYieldMyError' }).safeUnwrap(); return okAsync('string'); }) Test.checks([ Test.check(), ]) }); }); }); (function describe(_ = 'Transpose') { (function it(_ = 'should transpose an array') { const input: [ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ] = [ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ] type Expectation = [ [ 1, 3, 5 ], [ 2, 4, 6 ] ] const transposed: Expectation = transpose(input) }); (function it(_ = 'should transpose an empty array') { const input: [] = [] type Expectation = [] const transposed: Expectation = transpose(input) }); (function it(_ = 'should transpose incomplete array') { const input: [ [ 1, 3 ], [ 2, ] ] = [ [ 1, 3 ], [ 2, ] ] type Expectation = [[1], [3]] const transposed: Expectation = transpose(input) }); }); })(); //#region Utility function declarations for type testing // Transpose method converts [x, y] pairs into [xs, ys] array. declare function transpose< A extends unknown[][] >(input: A): Transpose<[ ...A ]>; //#endregion // create dummy values with a desired type const input = (): T => 123 as any ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "target": "es2015", "module": "ESNext", "moduleResolution": "Node", "strict": false, "noImplicitAny": true, "sourceMap": false, "noUnusedLocals": true, "noUnusedParameters": true, "strictNullChecks": true, "strictFunctionTypes": true, "declaration": true, "baseUrl": "./src", "lib": [ "dom", "es2016", "es2017.object" ], "outDir": "dist", "skipLibCheck": true, "esModuleInterop": true }, "include": [ "src/**/*.ts" ], "exclude": [ "node_modules", "**/*.spec.ts" ] }