0.1.1 • Published 10 months ago

@danwithabox/nullish v0.1.1

Weekly downloads
-
License
MIT
Repository
github
Last release
10 months ago

@danwithabox/nullish

Utilities that pair well with the lovely nullish coalescing (??) operator.

License

Install

$ npm install @danwithabox/nullish --save

Overview

The provided utilities are:

nullishMap(value, mapFn)

import { nullishMap } from "@danwithabox/nullish";

// Instead of expecting unsafe data,
type Data = { foo: number, bar: number, } /* | null | undefined */;

// define operations on safe data,
function processSafeData(data: Data): number {
    return data.foo + data.bar;
}
// then bridge the safety gap with `nullishMap()`
function handleUnsafeData(unsafeData: Data | null | undefined): number {
    return nullishMap(unsafeData, processSafeData) ?? 0;
}

// or use it in any other way to make more concise code
function greet(data?: { name: string, surname: string, } | null): string {
    return nullishMap(data, data => `Hello, ${data.name} ${data.surname}!`) ?? `Hi there!`;
    // Note the lack of branches and optional chaining (?.) operators, it's just one succinct line
}

console.log(greet({ name: `Daniel`, surname: `Withabox`, }));
// Hello, Daniel Withabox!

console.log(greet());
// Hi there!

Map a nullish value as if it were non-nullish.

The result retains either null, or undefined, or both, or neither, depending on what's inferred from the input value.

Practically, the possible mappings are:

  • T | null | undefined => R | null | undefined

    const val = 0 as number | null | undefined;
    const res = nullishMap(val, val => `${val}`);
    //    ^ string | null | undefined
  • T | undefined => R | undefined

    const val = 0 as number | undefined;
    const res = nullishMap(val, val => `${val}`);
    //    ^ string | undefined
  • T | null => R | null

    const val = 0 as number | null;
    const res = nullishMap(val, val => `${val}`);
    //    ^ string | null
  • T => R (not terribly useful, but it's allowed for simplicity's sake)

    const val = 0 as number;
    const res = nullishMap(val, val => `${val}`);
    //    ^ string

nullishOf(value)

import { nullishMap, nullishOf } from "@danwithabox/nullish";

// Optional value declaration: the type is inferred and made optional without any manual typing
let complicatedDeclaration = nullishOf({
    foo: 1,
    bar: { baz: 2, },
});

function calculate(): number {
    // Handle the value as if it weren't optional
    return nullishMap(complicatedDeclaration, _ => _.foo + _.bar.baz) ?? 0;
}

console.log(calculate());
// 3

complicatedDeclaration = null; // assignment does not cause a type error
console.log(calculate());
// 0

Augment a value's type with null and undefined.

Zero performance impact at runtime, as it is simply an identity function, and it most likely gets inlined.

Useful in a few common situations:

  • Making an inferred type optional at variable declaration, since something like https://github.com/microsoft/TypeScript/issues/13321 is not yet possible:

    let optional = nullishOf({ foo: 1, bar: 2, }) ?? void 0;
    //  ^ { foo: number; bar: number; } | undefined
  • Safely accessing arrays without enabling noUncheckedIndexedAccess in tsconfig.json:

    const myArray = [0, , 2].map(n => Boolean(n));
    
    // Without `noUncheckedIndexedAccess`:
    let element = myArray[1];
    //  ^ `boolean`
    //    this is incorrect, due to the empty element
    
    // With manual typing:
    let maybeElement1 = myArray[1] as undefined | (typeof myArray)[number];
    //  ^ `boolean | undefined`
    //    correct, but a hassle to type
    
    // With `nullishOf`:
    let maybeElement2 = nullishOf(myArray[1]);
    //  ^ `boolean | null | undefined`
    //    correct enough: it has an extraneous `null`, but that's fine in most   situations
    
    // And if you want to narrow to either `null` or `undefined`:
    let maybeElement3 = nullishOf(myArray[1]) ?? null;
    //  ^ `boolean | null`
    //     correct
    
    let maybeElement4 = nullishOf(myArray[1]) ?? void 0;
    //  ^ `boolean | undefined`
    //     correct

Acknowledgements

Motivation to release this, and a realization that not only I needed such a thing, came from:

0.1.1

10 months ago

0.1.0

10 months ago