ts-results

A typescript implementation of Rust's Result object.

README

ts-results


A typescript implementation of Rust's Result
and Option objects.

Brings compile-time error checking and optional values to typescript.

Installation


  1. ```bash
  2. $ npm install ts-results
  3. ```

or

  1. ```bash
  2. $ yarn add ts-results
  3. ```

Example


Result Example


Convert this:

  1. ```typescript
  2. import { existsSync, readFileSync } from 'fs';

  3. function readFile(path: string): string {
  4.     if (existsSync(path)) {
  5.         return readFileSync(path);
  6.     } else {
  7.         // Callers of readFile have no way of knowing the function can fail
  8.         throw new Error('invalid path');
  9.     }
  10. }

  11. // This line may fail unexpectedly without warnings from typescript
  12. const text = readFile('test.txt');
  13. ```

To this:

  1. ```typescript
  2. import { existsSync, readFileSync } from 'fs';
  3. import { Ok, Err, Result } from 'ts-results';

  4. function readFile(path: string): Result<string, 'invalid path'> {
  5.     if (existsSync(path)) {
  6.         return new Ok(readFileSync(path)); // new is optional here
  7.     } else {
  8.         return new Err('invalid path'); // new is optional here
  9.     }
  10. }

  11. // Typescript now forces you to check whether you have a valid result at compile time.
  12. const result = readFile('test.txt');
  13. if (result.ok) {
  14.     // text contains the file's content
  15.     const text = result.val;
  16. } else {
  17.     // err equals 'invalid path'
  18.     const err = result.val;
  19. }
  20. ```

Option Example


Convert this:

  1. ```typescript
  2. declare function getLoggedInUsername(): string | undefined;

  3. declare function getImageURLForUsername(username: string): string | undefined;

  4. function getLoggedInImageURL(): string | undefined {
  5.     const username = getLoggedInUsername();
  6.     if (!username) {
  7.         return undefined;
  8.     }

  9.     return getImageURLForUsername(username);
  10. }

  11. const stringUrl = getLoggedInImageURL();
  12. const optionalUrl = stringUrl ? new URL(stringUrl) : undefined;
  13. console.log(optionalUrl);
  14. ```

To this:

  1. ```typescript
  2. import { Option, Some, None } from 'ts-results';

  3. declare function getLoggedInUsername(): Option<string>;

  4. declare function getImageForUsername(username: string): Option<string>;

  5. function getLoggedInImage(): Option<string> {
  6.     return getLoggedInUsername().andThen(getImageForUsername);
  7. }

  8. const optionalUrl = getLoggedInImage().map((url) => new URL(stringUrl));
  9. console.log(optionalUrl); // Some(URL('...'))

  10. // To extract the value, do this:
  11. if (optionalUrl.some) {
  12.     const url: URL = optionalUrl.val;
  13. }
  14. ```

Usage


  1. ```typescript
  2. import { Result, Err, Ok } from 'ts-results';
  3. ```

Creation


  1. ```typescript
  2. let okResult: Result<number, Error> = Ok(10);
  3. let errorResult: Result<number, Error> = Err(new Error('bad number!'));
  4. ```

Type Safety

_Note: Typescript currently has a bug, making this type narrowing only work whenstrictNullChecks is turned on._
  1. ```typescript
  2. let result: Result<number, Error> = Ok(1);
  3. if (result.ok) {
  4.     // Typescript knows that result.val is a number because result.ok was true
  5.     let number = result.val + 1;
  6. } else {
  7.     // Typescript knows that result.val is an `Error` because result.ok was false
  8.     console.error(result.val.message);
  9. }

  10. if (result.err) {
  11.     // Typescript knows that result.val is an `Error` because result.err was true
  12.     console.error(result.val.message);
  13. } else {
  14.     // Typescript knows that result.val is a number because result.err was false
  15.     let number = result.val + 1;
  16. }
  17. ```

Stack Trace


A stack trace is generated when an Err is created.

  1. ```typescript
  2. let error = Err('Uh Oh');
  3. let stack = error.stack;
  4. ```

Unwrap


  1. ```typescript
  2. let goodResult = new Ok(1);
  3. let badResult = new Err(new Error('something went wrong'));

  4. goodResult.unwrap(); // 1
  5. badResult.unwrap(); // throws Error("something went wrong")
  6. ```

Expect


  1. ```typescript
  2. let goodResult = Ok(1);
  3. let badResult = Err(new Error('something went wrong'));

  4. goodResult.expect('goodResult should be a number'); // 1
  5. badResult.expect('badResult should be a number'); // throws Error("badResult should be a number - Error: something went wrong")
  6. ```

ExpectErr


  1. ```typescript
  2. let goodResult = Ok(1);
  3. let badResult = Err(new Error('something went wrong'));

  4. goodResult.expect('goodResult should not be a number'); // throws Error("goodResult should not be a number")
  5. badResult.expect('badResult should not be a number'); // new Error('something went wrong')
  6. ```


Map and MapErr


  1. ```typescript
  2. let goodResult = Ok(1);
  3. let badResult = Err(new Error('something went wrong'));

  4. goodResult.map((num) => num + 1).unwrap(); // 2
  5. badResult.map((num) => num + 1).unwrap(); // throws Error("something went wrong")

  6. goodResult
  7.     .map((num) => num + 1)
  8.     .mapErr((err) => new Error('mapped'))
  9.     .unwrap(); // 2
  10. badResult
  11.     .map((num) => num + 1)
  12.     .mapErr((err) => new Error('mapped'))
  13.     .unwrap(); // throws Error("mapped")
  14. ```

andThen


  1. ```typescript
  2. let goodResult = Ok(1);
  3. let badResult = Err(new Error('something went wrong'));

  4. goodResult.andThen((num) => new Ok(num + 1)).unwrap(); // 2
  5. badResult.andThen((num) => new Err(new Error('2nd error'))).unwrap(); // throws Error('something went wrong')
  6. goodResult.andThen((num) => new Err(new Error('2nd error'))).unwrap(); // throws Error('2nd error')

  7. goodResult
  8.     .andThen((num) => new Ok(num + 1))
  9.     .mapErr((err) => new Error('mapped'))
  10.     .unwrap(); // 2
  11. badResult
  12.     .andThen((num) => new Err(new Error('2nd error')))
  13.     .mapErr((err) => new Error('mapped'))
  14.     .unwrap(); // throws Error('mapped')
  15. goodResult
  16.     .andThen((num) => new Err(new Error('2nd error')))
  17.     .mapErr((err) => new Error('mapped'))
  18.     .unwrap(); // thros Error('mapped')
  19. ```

Else


Deprecated in favor of unwrapOr

UnwrapOr


  1. ```typescript
  2. let goodResult = Ok(1);
  3. let badResult = Err(new Error('something went wrong'));

  4. goodResult.unwrapOr(5); // 1
  5. badResult.unwrapOr(5); // 5
  6. ```

Empty


  1. ```typescript
  2. function checkIsValid(isValid: boolean): Result<void, Error> {
  3.     if (isValid) {
  4.         return Ok.EMPTY;
  5.     } else {
  6.         return new Err(new Error('Not valid'));
  7.     }
  8. }
  9. ```

Combining Results


ts-results has two helper functions for operating over n Result objects.

Result.all

Either returns all of the Ok values, or the first Err value

  1. ```typescript
  2. let pizzaResult: Result<Pizza, GetPizzaError> = getPizzaSomehow();
  3. let toppingsResult: Result<Toppings, GetToppingsError> = getToppingsSomehow();

  4. let result = Result.all(pizzaResult, toppingsResult); // Result<[Pizza, Toppings], GetPizzaError | GetToppingsError>

  5. let [pizza, toppings] = result.unwrap(); // pizza is a Pizza, toppings is a Toppings.  Could throw GetPizzaError or GetToppingsError.
  6. ```

Result.any

Either returns the first Ok value, or all Err values

  1. ```typescript
  2. let url1: Result<string, Error1> = attempt1();
  3. let url2: Result<string, Error2> = attempt2();
  4. let url3: Result<string, Error3> = attempt3();

  5. let result = Result.any(url1, url2, url3); // Result

  6. let url = result.unwrap(); // At least one attempt gave us a successful url
  7. ```

Usage with rxjs


resultMap


Allows you to do the same actions as the normal rxjs map
operator on a stream of Result objects.

  1. ```typescript
  2. import { of, Observable } from 'rxjs';
  3. import { Ok, Err, Result } from 'ts-results';
  4. import { resultMap } from 'ts-results/rxjs-operators';

  5. const obs$: Observable<Result<number, Error>> = of(Ok(5), Err('uh oh'));

  6. const greaterThanZero = obs$.pipe(
  7.     resultMap((number) => number > 0), // Doubles the value
  8. ); // Has type Observable>

  9. greaterThanZero.subscribe((result) => {
  10.     if (result.ok) {
  11.         console.log('Was greater than zero: ' + result.val);
  12.     } else {
  13.         console.log('Got Error Message: ' + result.val);
  14.     }
  15. });

  16. // Logs the following:
  17. // Got number: 10
  18. // Got Error Message: uh oh
  19. ```

resultMapErr


  1. ```typescript
  2. import { resultMapErr } from 'ts-results/rxjs-operators';
  3. ```

Behaves exactly the same as resultMap, but maps the error value.

resultMapTo


  1. ```typescript
  2. import { resultMapTo } from 'ts-results/rxjs-operators';
  3. ```

Behaves the same as resultMap, but takes a value instead of a function.

resultMapErrTo


  1. ```typescript
  2. import { resultMapErrTo } from 'ts-results/rxjs-operators';
  3. ```

Behaves the same as resultMapErr, but takes a value instead of a function.

elseMap


Allows you to turn a stream of Result objects into a stream of values, transforming any errors into a value.

Similar to calling the else function, but works on a stream of Result objects.

  1. ```typescript
  2. import { of, Observable } from 'rxjs';
  3. import { Ok, Err, Result } from 'ts-results';
  4. import { elseMap } from 'ts-results/rxjs-operators';

  5. const obs$: Observable<Result<number, Error>> = of(Ok(5), Err(new Error('uh oh')));

  6. const doubled = obs$.pipe(
  7.     elseMap((err) => {
  8.         console.log('Got error: ' + err.message);

  9.         return -1;
  10.     }),
  11. ); // Has type Observable

  12. doubled.subscribe((number) => {
  13.     console.log('Got number: ' + number);
  14. });

  15. // Logs the following:
  16. // Got number: 5
  17. // Got error: uh oh
  18. // Got number: -1
  19. ```

elseMapTo


  1. ```typescript
  2. import { elseMapTo } from 'ts-results/rxjs-operators';
  3. ```

Behaves the same as elseMap, but takes a value instead of a function.

resultSwitchMap and resultMergeMap


Allows you to do the same actions as the
and rxjs switchMap operator on a stream of Result
objects.

Merging or switching from a stream of `Result` objects onto a stream of `` objects turns the stream into astream of `Result` objects.

Merging or switching from a stream of `Result` objects onto a stream of `Result` objects turn the streaminto a stream of `Result` objects.

  1. ```typescript
  2. import { of, Observable } from 'rxjs';
  3. import { Ok, Err, Result } from 'ts-results';
  4. import { resultMergeMap } from 'ts-results/rxjs-operators';

  5. const obs$: Observable<Result<number, Error>> = of(new Ok(5), new Err(new Error('uh oh')));

  6. const obs2$: Observable<Result<string, CustomError>> = of(new Ok('hi'), new Err(new CustomError('custom error')));

  7. const test$ = obs$.pipe(
  8.     resultMergeMap((number) => {
  9.         console.log('Got number: ' + number);

  10.         return obs2$;
  11.     }),
  12. ); // Has type Observable>

  13. test$.subscribe((result) => {
  14.     if (result.ok) {
  15.         console.log('Got string: ' + result.val);
  16.     } else {
  17.         console.log('Got error: ' + result.val.message);
  18.     }
  19. });

  20. // Logs the following:
  21. // Got number: 5
  22. // Got string: hi
  23. // Got error: custom error
  24. // Got error: uh oh
  25. ```

filterResultOk


Converts an `Observable>` to an `Observble` by filtering out the Errs and mapping to the Ok values.

  1. ```typescript
  2. import { of, Observable } from 'rxjs';
  3. import { Ok, Err, Result } from 'ts-results';
  4. import { filterResultOk } from 'ts-results/rxjs-operators';

  5. const obs$: Observable<Result<number, Error>> = of(new Ok(5), new Err(new Error('uh oh')));

  6. const test$ = obs$.pipe(filterResultOk()); // Has type Observable

  7. test$.subscribe((result) => {
  8.     console.log('Got number: ' + result);
  9. });

  10. // Logs the following:
  11. // Got number: 5
  12. ```

filterResultErr


Converts an `Observable>` to an `Observble` by filtering out the Oks and mapping to the error values.

  1. ```typescript
  2. import { of, Observable } from 'rxjs';
  3. import { Ok, Err, Result } from 'ts-results';
  4. import { filterResultOk } from 'ts-results/rxjs-operators';

  5. const obs$: Observable<Result<number, Error>> = of(new Ok(5), new Err(new Error('uh oh')));

  6. const test$ = obs$.pipe(filterResultOk()); // Has type Observable

  7. test$.subscribe((result) => {
  8.     console.log('Got number: ' + result);
  9. });

  10. // Logs the following:
  11. // Got number: 5
  12. ```