# `outvariant` Type-safe implementation of invariant with positionals. ## Motivation ### Type-safely This implementation asserts the given predicate expression so it's treated as non-nullable after the `invariant` call: ```ts // Regular invariant: invariant(user, 'Failed to fetch') user?.firstName // "user" is possibly undefined // Outvariant: invariant(user, 'Failed to fetch') user.firstName // OK, "invariant" ensured the "user" exists ``` ### Positionals support This implementation uses [rest parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters) to support dynamic number of positionals: ```js invariant(predicate, 'Expected %s but got %s', 'one', false) ``` ## What is this for? Invariant is a shorthand function that asserts a given predicate and throws an error if that predicate is false. Compare these two pieces of code identical in behavior: ```js if (!token) { throw new Error(`Expected a token to be set but got ${typeof token}`) } ``` ```js import { invariant } from 'outvariant' invariant(token, 'Expected a token to be set but got %s', typeof token) ``` Using `invariant` reduces the visual nesting of the code and leads to cleaner error messages thanks to formatted positionals (i.e. the `%s` (string) positional above). ## Usage ### Install ```sh npm install outvariant # or yarn add outvariant ``` > You may want to install this library as a dev dependency (`-D`) based on your usage. ### Write an assertion ```js import { invariant } from 'outvariant' invariant(user, 'Failed to load: expected user, but got %o', user) ``` ## Positionals The following positional tokens are supported: | Token | Expected value type | | --------- | ------------------------------------------------------- | | `%s` | String | | `%d`/`%i` | Number | | `%j` | JSON (non-stringified) | | `%o` | Arbitrary object or object-like (i.e. a class instance) | Whenever present in the error message, a positional token will look up the value to insert in its place from the arguments given to `invariant`. ```js invariant( false, 'Expected the "%s" property but got %j', // Note that positionals are sensitive to order: // - "firstName" replaces "%s" because it's first. // - {"id":1} replaces "%j" because it's second. 'firstName', { id: 1, } ) ``` ## Polymorphic errors It is possible to throw a custom `Error` instance using `invariant.as`: ```js import { invariant } from 'outvariant' class NetworkError extends Error { constructor(message) { super(message) } } invariant.as(NetworkError, res.fulfilled, 'Failed to handle response') ``` Note that providing a custom error constructor as the argument to `invariant.as` requires the custom constructor's signature to be compatible with the `Error` class constructor. If your error constructor has a different signature, you can pass a function as the first argument to `invariant.as` that creates a new custom error instance. ```js import { invariant } from 'outvariant' class NetworkError extends Error { constructor(statusCode, message) { super(message) this.statusCode = statusCode } } invariant.as( (message) => new NetworkError(500, message), res.fulfilled, 'Failed to handle response' ) ``` Abstract the error into helper functions for flexibility: ```js function toNetworkError(statusCode) { return (message) => new NetworkError(statusCode, message) } invariant.as(toNetworkError(404), res?.user?.id, 'User Not Found') invariant.as(toNetworkError(500), res.fulfilled, 'Internal Server Error') ``` ## Contributing Please [open an issue](https://github.com/open-draft/outvariant/issues) or [submit a pull request](https://github.com/open-draft/outvariant/pulls) if you wish to contribute. Thank you.