{"id":13596831,"url":"https://github.com/vultix/ts-results","last_synced_at":"2025-05-14T16:02:11.713Z","repository":{"id":42221183,"uuid":"170350244","full_name":"vultix/ts-results","owner":"vultix","description":"A typescript implementation of Rust's Result object.","archived":false,"fork":false,"pushed_at":"2024-04-15T19:33:23.000Z","size":286,"stargazers_count":1281,"open_issues_count":40,"forks_count":70,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-12T15:16:39.246Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vultix.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-02-12T16:20:03.000Z","updated_at":"2025-05-11T17:41:02.000Z","dependencies_parsed_at":"2024-06-18T12:31:02.604Z","dependency_job_id":"723dd30c-aaa2-43d2-a8b6-78efb71b9eca","html_url":"https://github.com/vultix/ts-results","commit_stats":{"total_commits":66,"total_committers":11,"mean_commits":6.0,"dds":"0.43939393939393945","last_synced_commit":"910789d42f2cc8469f5fad2b80b1844e0c063269"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vultix%2Fts-results","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vultix%2Fts-results/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vultix%2Fts-results/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vultix%2Fts-results/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vultix","download_url":"https://codeload.github.com/vultix/ts-results/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254110401,"owners_count":22016392,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T16:02:50.721Z","updated_at":"2025-05-14T16:02:11.232Z","avatar_url":"https://github.com/vultix.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# ts-results\n\nA typescript implementation of Rust's [Result](https://doc.rust-lang.org/std/result/)\nand [Option](https://doc.rust-lang.org/std/option/) objects.\n\nBrings compile-time error checking and optional values to typescript.\n\n## Contents\n\n-   [Installation](#installation)\n-   [Example](#example)\n    -   [Result Example](#result-example)\n    -   [Option Example](#option-example)\n-   [Usage](#usage)\n    -   [Creation](#creation)\n    -   [Type Safety](#type-safety)\n    -   [Unwrap](#unwrap)\n    -   [Expect](#expect)\n    -   [ExpectErr](#expecterr)\n    -   [Map, MapErr](#map-and-maperr)\n    -   [andThen](#andthen)\n    -   [Else](#else)\n    -   [UnwrapOr](#unwrapor)\n    -   [Empty](#empty)\n    -   [Combining Results](#combining-results)\n        -   [Result.all](#result-all)\n        -   [Result.any](#result-any)\n-   [Usage with rxjs](#usage-with-rxjs)\n    -   [resultMap](#resultmap)\n    -   [resultMapErr](#resultmaperr)\n    -   [resultMapTo](#resultmapto)\n    -   [resultMapErrTo](#resultmapto)\n    -   [elseMap](#elsemap)\n    -   [elseMapTo](#elsemapto)\n    -   [resultSwitchMap, resultMergeMap](#resultswitchmap-and-resultmergemap)\n    -   [filterResultOk](#filterresultok)\n    -   [filterResultErr](#filterresulterr)\n\n## Installation\n\n```bash\n$ npm install ts-results\n```\n\nor\n\n```bash\n$ yarn add ts-results\n```\n\n## Example\n\n### Result Example\n\nConvert this:\n\n```typescript\nimport { existsSync, readFileSync } from 'fs';\n\nfunction readFile(path: string): string {\n    if (existsSync(path)) {\n        return readFileSync(path);\n    } else {\n        // Callers of readFile have no way of knowing the function can fail\n        throw new Error('invalid path');\n    }\n}\n\n// This line may fail unexpectedly without warnings from typescript\nconst text = readFile('test.txt');\n```\n\nTo this:\n\n```typescript\nimport { existsSync, readFileSync } from 'fs';\nimport { Ok, Err, Result } from 'ts-results';\n\nfunction readFile(path: string): Result\u003cstring, 'invalid path'\u003e {\n    if (existsSync(path)) {\n        return new Ok(readFileSync(path)); // new is optional here\n    } else {\n        return new Err('invalid path'); // new is optional here\n    }\n}\n\n// Typescript now forces you to check whether you have a valid result at compile time.\nconst result = readFile('test.txt');\nif (result.ok) {\n    // text contains the file's content\n    const text = result.val;\n} else {\n    // err equals 'invalid path'\n    const err = result.val;\n}\n```\n\n### Option Example\n\nConvert this:\n\n```typescript\ndeclare function getLoggedInUsername(): string | undefined;\n\ndeclare function getImageURLForUsername(username: string): string | undefined;\n\nfunction getLoggedInImageURL(): string | undefined {\n    const username = getLoggedInUsername();\n    if (!username) {\n        return undefined;\n    }\n\n    return getImageURLForUsername(username);\n}\n\nconst stringUrl = getLoggedInImageURL();\nconst optionalUrl = stringUrl ? new URL(stringUrl) : undefined;\nconsole.log(optionalUrl);\n```\n\nTo this:\n\n```typescript\nimport { Option, Some, None } from 'ts-results';\n\ndeclare function getLoggedInUsername(): Option\u003cstring\u003e;\n\ndeclare function getImageForUsername(username: string): Option\u003cstring\u003e;\n\nfunction getLoggedInImage(): Option\u003cstring\u003e {\n    return getLoggedInUsername().andThen(getImageForUsername);\n}\n\nconst optionalUrl = getLoggedInImage().map((url) =\u003e new URL(stringUrl));\nconsole.log(optionalUrl); // Some(URL('...'))\n\n// To extract the value, do this:\nif (optionalUrl.some) {\n    const url: URL = optionalUrl.val;\n}\n```\n\n## Usage\n\n```typescript\nimport { Result, Err, Ok } from 'ts-results';\n```\n\n#### Creation\n\n```typescript\nlet okResult: Result\u003cnumber, Error\u003e = Ok(10);\nlet errorResult: Result\u003cnumber, Error\u003e = Err(new Error('bad number!'));\n```\n\n#### Type Safety\n_Note: Typescript currently has a [bug](https://github.com/microsoft/TypeScript/issues/10564), making this type narrowing only work when `strictNullChecks` is turned on._\n```typescript\nlet result: Result\u003cnumber, Error\u003e = Ok(1);\nif (result.ok) {\n    // Typescript knows that result.val is a number because result.ok was true\n    let number = result.val + 1;\n} else {\n    // Typescript knows that result.val is an `Error` because result.ok was false\n    console.error(result.val.message);\n}\n\nif (result.err) {\n    // Typescript knows that result.val is an `Error` because result.err was true\n    console.error(result.val.message);\n} else {\n    // Typescript knows that result.val is a number because result.err was false\n    let number = result.val + 1;\n}\n```\n\n### Stack Trace\n\nA stack trace is generated when an `Err` is created.\n\n```typescript\nlet error = Err('Uh Oh');\nlet stack = error.stack;\n```\n\n#### Unwrap\n\n```typescript\nlet goodResult = new Ok(1);\nlet badResult = new Err(new Error('something went wrong'));\n\ngoodResult.unwrap(); // 1\nbadResult.unwrap(); // throws Error(\"something went wrong\")\n```\n\n#### Expect\n\n```typescript\nlet goodResult = Ok(1);\nlet badResult = Err(new Error('something went wrong'));\n\ngoodResult.expect('goodResult should be a number'); // 1\nbadResult.expect('badResult should be a number'); // throws Error(\"badResult should be a number - Error: something went wrong\")\n```\n\n#### ExpectErr\n\n```typescript\nlet goodResult = Ok(1);\nlet badResult = Err(new Error('something went wrong'));\n\ngoodResult.expect('goodResult should not be a number'); // throws Error(\"goodResult should not be a number\")\nbadResult.expect('badResult should not be a number'); // new Error('something went wrong')\n```\n\n\n#### Map and MapErr\n\n```typescript\nlet goodResult = Ok(1);\nlet badResult = Err(new Error('something went wrong'));\n\ngoodResult.map((num) =\u003e num + 1).unwrap(); // 2\nbadResult.map((num) =\u003e num + 1).unwrap(); // throws Error(\"something went wrong\")\n\ngoodResult\n    .map((num) =\u003e num + 1)\n    .mapErr((err) =\u003e new Error('mapped'))\n    .unwrap(); // 2\nbadResult\n    .map((num) =\u003e num + 1)\n    .mapErr((err) =\u003e new Error('mapped'))\n    .unwrap(); // throws Error(\"mapped\")\n```\n\n#### andThen\n\n```typescript\nlet goodResult = Ok(1);\nlet badResult = Err(new Error('something went wrong'));\n\ngoodResult.andThen((num) =\u003e new Ok(num + 1)).unwrap(); // 2\nbadResult.andThen((num) =\u003e new Err(new Error('2nd error'))).unwrap(); // throws Error('something went wrong')\ngoodResult.andThen((num) =\u003e new Err(new Error('2nd error'))).unwrap(); // throws Error('2nd error')\n\ngoodResult\n    .andThen((num) =\u003e new Ok(num + 1))\n    .mapErr((err) =\u003e new Error('mapped'))\n    .unwrap(); // 2\nbadResult\n    .andThen((num) =\u003e new Err(new Error('2nd error')))\n    .mapErr((err) =\u003e new Error('mapped'))\n    .unwrap(); // throws Error('mapped')\ngoodResult\n    .andThen((num) =\u003e new Err(new Error('2nd error')))\n    .mapErr((err) =\u003e new Error('mapped'))\n    .unwrap(); // thros Error('mapped')\n```\n\n#### Else\n\nDeprecated in favor of unwrapOr\n\n#### UnwrapOr\n\n```typescript\nlet goodResult = Ok(1);\nlet badResult = Err(new Error('something went wrong'));\n\ngoodResult.unwrapOr(5); // 1\nbadResult.unwrapOr(5); // 5\n```\n\n#### Empty\n\n```typescript\nfunction checkIsValid(isValid: boolean): Result\u003cvoid, Error\u003e {\n    if (isValid) {\n        return Ok.EMPTY;\n    } else {\n        return new Err(new Error('Not valid'));\n    }\n}\n```\n\n#### Combining Results\n\n`ts-results` has two helper functions for operating over n `Result` objects.\n\n##### Result.all\n\nEither returns all of the `Ok` values, or the first `Err` value\n\n```typescript\nlet pizzaResult: Result\u003cPizza, GetPizzaError\u003e = getPizzaSomehow();\nlet toppingsResult: Result\u003cToppings, GetToppingsError\u003e = getToppingsSomehow();\n\nlet result = Result.all(pizzaResult, toppingsResult); // Result\u003c[Pizza, Toppings], GetPizzaError | GetToppingsError\u003e\n\nlet [pizza, toppings] = result.unwrap(); // pizza is a Pizza, toppings is a Toppings.  Could throw GetPizzaError or GetToppingsError.\n```\n\n##### Result.any\n\nEither returns the first `Ok` value, or all `Err` values\n\n```typescript\nlet url1: Result\u003cstring, Error1\u003e = attempt1();\nlet url2: Result\u003cstring, Error2\u003e = attempt2();\nlet url3: Result\u003cstring, Error3\u003e = attempt3();\n\nlet result = Result.any(url1, url2, url3); // Result\u003cstring, Error1 | Error2 | Error3\u003e\n\nlet url = result.unwrap(); // At least one attempt gave us a successful url\n```\n\n## Usage with rxjs\n\n#### resultMap\n\nAllows you to do the same actions as the normal [rxjs map](http://reactivex.io/documentation/operators/map.html)\noperator on a stream of Result objects.\n\n```typescript\nimport { of, Observable } from 'rxjs';\nimport { Ok, Err, Result } from 'ts-results';\nimport { resultMap } from 'ts-results/rxjs-operators';\n\nconst obs$: Observable\u003cResult\u003cnumber, Error\u003e\u003e = of(Ok(5), Err('uh oh'));\n\nconst greaterThanZero = obs$.pipe(\n    resultMap((number) =\u003e number \u003e 0), // Doubles the value\n); // Has type Observable\u003cResult\u003cboolean, 'uh oh'\u003e\u003e\n\ngreaterThanZero.subscribe((result) =\u003e {\n    if (result.ok) {\n        console.log('Was greater than zero: ' + result.val);\n    } else {\n        console.log('Got Error Message: ' + result.val);\n    }\n});\n\n// Logs the following:\n// Got number: 10\n// Got Error Message: uh oh\n```\n\n#### resultMapErr\n\n```typescript\nimport { resultMapErr } from 'ts-results/rxjs-operators';\n```\n\nBehaves exactly the same as [resultMap](#resultmap), but maps the error value.\n\n#### resultMapTo\n\n```typescript\nimport { resultMapTo } from 'ts-results/rxjs-operators';\n```\n\nBehaves the same as [resultMap](#resultmap), but takes a value instead of a function.\n\n#### resultMapErrTo\n\n```typescript\nimport { resultMapErrTo } from 'ts-results/rxjs-operators';\n```\n\nBehaves the same as [resultMapErr](#resultmaperr), but takes a value instead of a function.\n\n#### elseMap\n\nAllows you to turn a stream of Result objects into a stream of values, transforming any errors into a value.\n\nSimilar to calling the [else](#else) function, but works on a stream of Result objects.\n\n```typescript\nimport { of, Observable } from 'rxjs';\nimport { Ok, Err, Result } from 'ts-results';\nimport { elseMap } from 'ts-results/rxjs-operators';\n\nconst obs$: Observable\u003cResult\u003cnumber, Error\u003e\u003e = of(Ok(5), Err(new Error('uh oh')));\n\nconst doubled = obs$.pipe(\n    elseMap((err) =\u003e {\n        console.log('Got error: ' + err.message);\n\n        return -1;\n    }),\n); // Has type Observable\u003cnumber\u003e\n\ndoubled.subscribe((number) =\u003e {\n    console.log('Got number: ' + number);\n});\n\n// Logs the following:\n// Got number: 5\n// Got error: uh oh\n// Got number: -1\n```\n\n#### elseMapTo\n\n```typescript\nimport { elseMapTo } from 'ts-results/rxjs-operators';\n```\n\nBehaves the same as [elseMap](#elsemap), but takes a value instead of a function.\n\n#### resultSwitchMap and resultMergeMap\n\nAllows you to do the same actions as the\nnormal [rxjs switchMap](https://www.learnrxjs.io/operators/transformation/switchmap.html)\nand [rxjs switchMap](https://www.learnrxjs.io/operators/transformation/mergemap.html) operator on a stream of Result\nobjects.\n\nMerging or switching from a stream of `Result\u003cT, E\u003e` objects onto a stream of `\u003cT2\u003e` objects turns the stream into a\nstream of `Result\u003cT2, E\u003e` objects.\n\nMerging or switching from a stream of `Result\u003cT, E\u003e` objects onto a stream of `Result\u003cT2, E2\u003e` objects turn the stream\ninto a stream of `Result\u003cT2, E | T2\u003e` objects.\n\n```typescript\nimport { of, Observable } from 'rxjs';\nimport { Ok, Err, Result } from 'ts-results';\nimport { resultMergeMap } from 'ts-results/rxjs-operators';\n\nconst obs$: Observable\u003cResult\u003cnumber, Error\u003e\u003e = of(new Ok(5), new Err(new Error('uh oh')));\n\nconst obs2$: Observable\u003cResult\u003cstring, CustomError\u003e\u003e = of(new Ok('hi'), new Err(new CustomError('custom error')));\n\nconst test$ = obs$.pipe(\n    resultMergeMap((number) =\u003e {\n        console.log('Got number: ' + number);\n\n        return obs2$;\n    }),\n); // Has type Observable\u003cResult\u003cstring, CustomError | Error\u003e\u003e\n\ntest$.subscribe((result) =\u003e {\n    if (result.ok) {\n        console.log('Got string: ' + result.val);\n    } else {\n        console.log('Got error: ' + result.val.message);\n    }\n});\n\n// Logs the following:\n// Got number: 5\n// Got string: hi\n// Got error: custom error\n// Got error: uh oh\n```\n\n#### filterResultOk\n\nConverts an `Observable\u003cResult\u003cT, E\u003e\u003e` to an `Observble\u003cT\u003e` by filtering out the Errs and mapping to the Ok values.\n\n```typescript\nimport { of, Observable } from 'rxjs';\nimport { Ok, Err, Result } from 'ts-results';\nimport { filterResultOk } from 'ts-results/rxjs-operators';\n\nconst obs$: Observable\u003cResult\u003cnumber, Error\u003e\u003e = of(new Ok(5), new Err(new Error('uh oh')));\n\nconst test$ = obs$.pipe(filterResultOk()); // Has type Observable\u003cnumber\u003e\n\ntest$.subscribe((result) =\u003e {\n    console.log('Got number: ' + result);\n});\n\n// Logs the following:\n// Got number: 5\n```\n\n#### filterResultErr\n\nConverts an `Observable\u003cResult\u003cT, E\u003e\u003e` to an `Observble\u003cT\u003e` by filtering out the Oks and mapping to the error values.\n\n```typescript\nimport { of, Observable } from 'rxjs';\nimport { Ok, Err, Result } from 'ts-results';\nimport { filterResultOk } from 'ts-results/rxjs-operators';\n\nconst obs$: Observable\u003cResult\u003cnumber, Error\u003e\u003e = of(new Ok(5), new Err(new Error('uh oh')));\n\nconst test$ = obs$.pipe(filterResultOk()); // Has type Observable\u003cnumber\u003e\n\ntest$.subscribe((result) =\u003e {\n    console.log('Got number: ' + result);\n});\n\n// Logs the following:\n// Got number: 5\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvultix%2Fts-results","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvultix%2Fts-results","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvultix%2Fts-results/lists"}