ts-not-nil

A runtime assertion for preserving not-null and not-undefined invariants

Usage no npm install needed!

<script type="module">
  import tsNotNil from 'https://cdn.skypack.dev/ts-not-nil';
</script>

README

ts-not-nil

npm version Build Status TypeScript

A runtime assertion for preserving not-null and not-undefined invariants.

:zap: Rationale

This package helps you to prevent nasty "Cannot read property 'a' of undefined/null" runtime error messages with some compile-time sanity checks (thanks to TypeScript).

This library exports two functions:

/** 
 * @throws NotNilAssertionError
 */
export function assertNotNil<T>(val: T, reason?: string): asserts val is NonNullable<T>;

/**
 * @throws NotNilAssertionError
 */
export function unwrapNotNil<T>(val: T, reason?: string): NonNullable<T>;

If val happens to be null or undefined, these functions throw NotNilAssertionError at runtime. There is also a debugger statement that is hit when the assertion fails for debugging convenience.

If val is neither null nor undefined nothing happens.

The difference between assertNotNil(val) and unwrapNotNil(val) is that the former returns val back which is handy for inline assertions inside of expressions.

The second optional parameter reason will be embedded into the thrown error message in case of an assertion failure.

const map = new Map<string, number>();
map.set("key", 42);

const foo: number | undefined = map.get("key");

assertNotNil(foo, `I've added an entry with key "key" one line above!`);

foo; // `number` - its type was narrowed to just number after assertion

const bar: number = unwrapNotNil(map.get("key"), "Believe me!"); // inline assertion

// All the following invocations throw
assertNotNil(null);
assertNotNil(undefined);
unwrapNotNil(null);
unwrapNotNil(undefined);

:boom: Compile-time protection

Both of the exported functions help you to catch bugs at compile time. They explicitly prohibit calling them with T that is known to be neither null nor undefined at compile time. This is quite a hack with the type system, that may generate an error report that says you didn't pass the second parameter to *notNil() function or that the parameter is not assignable to NotNilAssertionCompileError, but as long as you see which line it points to and see assertNotNil(val) or unwrapNotNil(val) invocation you should rethink whether this assertion is really needed (because the type system already ensures that val is neither null nor undefined).

declare const unkown:     unknown;
declare const any:        unknown;
declare const maybeNull:  string | null;
declare const maybeUndef: string | undefined;
declare const maybeNil:   string | null | undefined;
declare const string:     string;
declare const obj:        { a: number };

assertNotNil(unkown);     // ok
assertNotNil(any);        // ok
assertNotNil(maybeNull);  // ok
assertNotNil(maybeUndef); // ok
assertNotNil(maybeNil);   // ok
assertNotNil(string);     // compile error: the value passed is known to never be nil
assertNotNil(obj);        // compile error: the value passed is known to never be nil

:warning: Caveats

You should have "strictNullChecks" enabled in your tsconfig.json and that's all.