File size: 3,981 Bytes
d4b85c0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# `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.