ts-results
    TypeScript icon, indicating that this package has built-in type declarations

    3.3.0 • Public • Published

    ts-results

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

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

    Contents

    Installation

    $ npm install ts-results

    or

    $ yarn add ts-results

    Example

    Result Example

    Convert this:

    import { existsSync, readFileSync } from 'fs';
    
    function readFile(path: string): string {
        if (existsSync(path)) {
            return readFileSync(path);
        } else {
            // Callers of readFile have no way of knowing the function can fail
            throw new Error('invalid path');
        }
    }
    
    // This line may fail unexpectedly without warnings from typescript
    const text = readFile('test.txt');

    To this:

    import { existsSync, readFileSync } from 'fs';
    import { Ok, Err, Result } from 'ts-results';
    
    function readFile(path: string): Result<string, 'invalid path'> {
        if (existsSync(path)) {
            return new Ok(readFileSync(path)); // new is optional here
        } else {
            return new Err('invalid path'); // new is optional here
        }
    }
    
    // Typescript now forces you to check whether you have a valid result at compile time.
    const result = readFile('test.txt');
    if (result.ok) {
        // text contains the file's content
        const text = result.val;
    } else {
        // err equals 'invalid path'
        const err = result.val;
    }

    Option Example

    Convert this:

    declare function getLoggedInUsername(): string | undefined;
    
    declare function getImageURLForUsername(username: string): string | undefined;
    
    function getLoggedInImageURL(): string | undefined {
        const username = getLoggedInUsername();
        if (!username) {
            return undefined;
        }
    
        return getImageURLForUsername(username);
    }
    
    const stringUrl = getLoggedInImageURL();
    const optionalUrl = stringUrl ? new URL(stringUrl) : undefined;
    console.log(optionalUrl);

    To this:

    import { Option, Some, None } from 'ts-results';
    
    declare function getLoggedInUsername(): Option<string>;
    
    declare function getImageForUsername(username: string): Option<string>;
    
    function getLoggedInImage(): Option<string> {
        return getLoggedInUsername().andThen(getImageForUsername);
    }
    
    const optionalUrl = getLoggedInImage().map((url) => new URL(stringUrl));
    console.log(optionalUrl); // Some(URL('...'))
    
    // To extract the value, do this:
    if (optionalUrl.some) {
        const url: URL = optionalUrl.val;
    }

    Usage

    import { Result, Err, Ok } from 'ts-results';

    Creation

    let okResult: Result<number, Error> = Ok(10);
    let errorResult: Result<number, Error> = Err(new Error('bad number!'));

    Type Safety

    let result: Result<number, Error> = Ok(1);
    if (result.ok) {
        // Typescript knows that result.val is a number because result.ok was true
        let number = result.val + 1;
    } else {
        // Typescript knows that result.val is an `Error` because result.ok was false
        console.error(result.val.message);
    }
    
    if (result.err) {
        // Typescript knows that result.val is an `Error` because result.err was true
        console.error(result.val.message);
    } else {
        // Typescript knows that result.val is a number because result.err was false
        let number = result.val + 1;
    }

    Stack Trace

    A stack trace is generated when an Err is created.

    let error = Err('Uh Oh');
    let stack = error.stack;

    Unwrap

    let goodResult = new Ok(1);
    let badResult = new Err(new Error('something went wrong'));
    
    goodResult.unwrap(); // 1
    badResult.unwrap(); // throws Error("something went wrong")

    Expect

    let goodResult = Ok(1);
    let badResult = Err(new Error('something went wrong'));
    
    goodResult.expect('goodResult should be a number'); // 1
    badResult.expect('badResult should be a number'); // throws Error("badResult should be a number - Error: something went wrong")

    Map and MapErr

    let goodResult = Ok(1);
    let badResult = Err(new Error('something went wrong'));
    
    goodResult.map((num) => num + 1).unwrap(); // 2
    badResult.map((num) => num + 1).unwrap(); // throws Error("something went wrong")
    
    goodResult
        .map((num) => num + 1)
        .mapErr((err) => new Error('mapped'))
        .unwrap(); // 2
    badResult
        .map((num) => num + 1)
        .mapErr((err) => new Error('mapped'))
        .unwrap(); // throws Error("mapped")

    Else

    Deprecated in favor of unwrapOr

    UnwrapOr

    let goodResult = Ok(1);
    let badResult = Err(new Error('something went wrong'));
    
    goodResult.unwrapOr(5); // 1
    badResult.unwrapOr(5); // 5

    Empty

    function checkIsValid(isValid: boolean): Result<void, Error> {
        if (isValid) {
            return Ok.EMPTY;
        } else {
            return new Err(new Error('Not valid'));
        }
    }

    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

    let pizzaResult: Result<Pizza, GetPizzaError> = getPizzaSomehow();
    let toppingsResult: Result<Toppings, GetToppingsError> = getToppingsSomehow();
    
    let result = Result.all(pizzaResult, toppingsResult); // Result<[Pizza, Toppings], GetPizzaError | GetToppingsError>
    
    let [pizza, toppings] = result.unwrap(); // pizza is a Pizza, toppings is a Toppings.  Could throw GetPizzaError or GetToppingsError.
    Result.any

    Either returns the first Ok value, or all Err values

    let url1: Result<string, Error1> = attempt1();
    let url2: Result<string, Error2> = attempt2();
    let url3: Result<string, Error3> = attempt3();
    
    let result = Result.any(url1, url2, url3); // Result<string, Error1 | Error2 | Error3>
    
    let url = result.unwrap(); // At least one attempt gave us a successful url

    Usage with rxjs

    resultMap

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

    import { of, Observable } from 'rxjs';
    import { Ok, Err, Result } from 'ts-results';
    import { resultMap } from 'ts-results/rxjs-operators';
    
    const obs$: Observable<Result<number, Error>> = of(Ok(5), Err('uh oh'));
    
    const greaterThanZero = obs$.pipe(
        resultMap((number) => number > 0), // Doubles the value
    ); // Has type Observable<Result<boolean, 'uh oh'>>
    
    greaterThanZero.subscribe((result) => {
        if (result.ok) {
            console.log('Was greater than zero: ' + result.val);
        } else {
            console.log('Got Error Message: ' + result.val);
        }
    });
    
    // Logs the following:
    // Got number: 10
    // Got Error Message: uh oh

    resultMapErr

    import { resultMapErr } from 'ts-results/rxjs-operators';

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

    resultMapTo

    import { resultMapTo } from 'ts-results/rxjs-operators';

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

    resultMapErrTo

    import { resultMapErrTo } from 'ts-results/rxjs-operators';

    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.

    import { of, Observable } from 'rxjs';
    import { Ok, Err, Result } from 'ts-results';
    import { elseMap } from 'ts-results/rxjs-operators';
    
    const obs$: Observable<Result<number, Error>> = of(Ok(5), Err(new Error('uh oh')));
    
    const doubled = obs$.pipe(
        elseMap((err) => {
            console.log('Got error: ' + err.message);
    
            return -1;
        }),
    ); // Has type Observable<number>
    
    doubled.subscribe((number) => {
        console.log('Got number: ' + number);
    });
    
    // Logs the following:
    // Got number: 5
    // Got error: uh oh
    // Got number: -1

    elseMapTo

    import { elseMapTo } from 'ts-results/rxjs-operators';

    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 normal rxjs switchMap and rxjs switchMap operator on a stream of Result objects.

    Merging or switching from a stream of Result<T, E> objects onto a stream of <T2> objects turns the stream into a stream of Result<T2, E> objects.

    Merging or switching from a stream of Result<T, E> objects onto a stream of Result<T2, E2> objects turn the stream into a stream of Result<T2, E | T2> objects.

    import { of, Observable } from 'rxjs';
    import { Ok, Err, Result } from 'ts-results';
    import { resultMergeMap } from 'ts-results/rxjs-operators';
    
    const obs$: Observable<Result<number, Error>> = of(new Ok(5), new Err(new Error('uh oh')));
    
    const obs2$: Observable<Result<string, CustomError>> = of(new Ok('hi'), new Err(new CustomError('custom error')));
    
    const test$ = obs$.pipe(
        resultMergeMap((number) => {
            console.log('Got number: ' + number);
    
            return obs2$;
        }),
    ); // Has type Observable<Result<string, CustomError | Error>>
    
    test$.subscribe((result) => {
        if (result.ok) {
            console.log('Got string: ' + result.val);
        } else {
            console.log('Got error: ' + result.val.message);
        }
    });
    
    // Logs the following:
    // Got number: 5
    // Got string: hi
    // Got error: custom error
    // Got error: uh oh

    filterResultOk

    Converts an Observable<Result<T, E>> to an Observble<T> by filtering out the Errs and mapping to the Ok values.

    import { of, Observable } from 'rxjs';
    import { Ok, Err, Result } from 'ts-results';
    import { filterResultOk } from 'ts-results/rxjs-operators';
    
    const obs$: Observable<Result<number, Error>> = of(new Ok(5), new Err(new Error('uh oh')));
    
    const test$ = obs$.pipe(filterResultOk()); // Has type Observable<number>
    
    test$.subscribe((result) => {
        console.log('Got number: ' + result);
    });
    
    // Logs the following:
    // Got number: 5

    filterResultErr

    Converts an Observable<Result<T, E>> to an Observble<T> by filtering out the Oks and mapping to the error values.

    import { of, Observable } from 'rxjs';
    import { Ok, Err, Result } from 'ts-results';
    import { filterResultOk } from 'ts-results/rxjs-operators';
    
    const obs$: Observable<Result<number, Error>> = of(new Ok(5), new Err(new Error('uh oh')));
    
    const test$ = obs$.pipe(filterResultOk()); // Has type Observable<number>
    
    test$.subscribe((result) => {
        console.log('Got number: ' + result);
    });
    
    // Logs the following:
    // Got number: 5

    Install

    npm i ts-results

    DownloadsWeekly Downloads

    2,501

    Version

    3.3.0

    License

    MIT

    Unpacked Size

    97.3 kB

    Total Files

    34

    Last publish

    Collaborators

    • avatar