unknown-error 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -13
- package/WHY-UNKNOWN-ERROR.md +2 -2
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -14,16 +14,7 @@ Turn any caught `unknown` into a real Error without throwing, even for hostile v
|
|
|
14
14
|
- **Whole error trees**: Walks `cause` chains and `AggregateError` entries without recursion, so circular or 100,000-deep chains end instead of overflowing the stack.
|
|
15
15
|
- **Doesn't modify your errors**: Errors are returned as they are, never patched in place.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|---|---|---|---|---|---|
|
|
19
|
-
| Never throws on hostile values | ✅ | ✅ | ❌ | ❌ | — |
|
|
20
|
-
| Errors from other realms | ✅ | ✅ | ❌ | ✅ | ❌ |
|
|
21
|
-
| Readable circular and BigInt values | ✅ | ❌ | ❌ | ❌ | — |
|
|
22
|
-
| Leaves your errors unmodified | ✅ | ❌ | ❌ | ✅ | — |
|
|
23
|
-
| Causes and `AggregateError` | ✅ | ✅ | ✅ | — | Causes only |
|
|
24
|
-
| 10,000-deep cause chains | ✅ | ❌ | ❌ | — | ❌ |
|
|
25
|
-
|
|
26
|
-
For the problems with hand-written conversions, the full comparison and design decisions, see [WHY-UNKNOWN-ERROR.md](https://github.com/JacobLey/leyman/blob/main/tools/unknown-error/WHY-UNKNOWN-ERROR.md).
|
|
17
|
+
Guides and the full reference are on the [docs site](https://jacobley.github.io/leyman/packages/unknown-error/). For why it exists and the design decisions behind it, see [WHY-UNKNOWN-ERROR.md](https://github.com/JacobLey/leyman/blob/main/tools/unknown-error/WHY-UNKNOWN-ERROR.md).
|
|
27
18
|
|
|
28
19
|
## Contents
|
|
29
20
|
- [Install](#install)
|
|
@@ -36,6 +27,7 @@ For the problems with hand-written conversions, the full comparison and design d
|
|
|
36
27
|
- [`NonError`](#nonerror)
|
|
37
28
|
- [`walkErrors(value, options?)`](#walkerrorsvalue-options)
|
|
38
29
|
- [`formatError(value, options?)`](#formaterrorvalue-options)
|
|
30
|
+
- [Compared to alternatives](#compared-to-alternatives)
|
|
39
31
|
|
|
40
32
|
## Install
|
|
41
33
|
|
|
@@ -94,7 +86,19 @@ toError('boom'); // NonError: boom
|
|
|
94
86
|
toError({ count: 1n }); // NonError: {"count":"1n"}
|
|
95
87
|
```
|
|
96
88
|
|
|
97
|
-
An error-like object without a `stack` gets a stack pointing at the `toError` call, since the original location is unknown.
|
|
89
|
+
An error-like object without a `stack` gets a stack pointing at the `toError` call, since the original location is unknown. Properties the new Error already has, own or inherited, are not copied, so a JSON-parsed `"__proto__"` key can't replace its prototype.
|
|
90
|
+
|
|
91
|
+
A [`NonError`](#nonerror) message describes the value as follows:
|
|
92
|
+
|
|
93
|
+
| Value | Message |
|
|
94
|
+
|-------|---------|
|
|
95
|
+
| String | The string itself |
|
|
96
|
+
| `null`, `undefined`, number, boolean, symbol | `String(value)`, like `'null'` or `'Symbol(id)'` |
|
|
97
|
+
| BigInt | `'10n'` |
|
|
98
|
+
| Function | `'[Function name]'`, never its source |
|
|
99
|
+
| Object or array | JSON, with circular references as `"[Circular]"` and BigInts as `"10n"` |
|
|
100
|
+
|
|
101
|
+
When an object's JSON is empty (`toJSON` returns `undefined`) or throws, the message falls back to `String(value)`, then to the object's tag (`[object Object]`), and finally to `[unprintable value]`.
|
|
98
102
|
|
|
99
103
|
### `getErrorMessage(value)`
|
|
100
104
|
|
|
@@ -143,14 +147,14 @@ Only real Errors match. To match error-like objects too, convert them first: `ha
|
|
|
143
147
|
|
|
144
148
|
Visits `value`, its `cause` chain and the `errors` of any `AggregateError`, depth first: an error, then its cause, then its `errors` in order.
|
|
145
149
|
|
|
146
|
-
Every value is converted with `toError`, so a cause that isn't an Error still appears. A value already visited is skipped, so circular chains end.
|
|
150
|
+
Every value is converted with `toError`, so a cause that isn't an Error still appears. A value already visited is skipped, so circular chains end. Primitives have no identity, so the same string can be visited more than once.
|
|
147
151
|
|
|
148
152
|
| Parameter | Type | Default | Description |
|
|
149
153
|
|-----------|------|---------|-------------|
|
|
150
154
|
| `value` | `unknown` | — | A caught value. |
|
|
151
155
|
| `options.maxDepth` | `number` | `32` | Deepest level to visit. The root is level `0`. Pass `Infinity` for no limit. |
|
|
152
156
|
|
|
153
|
-
**Returns** `Generator<ErrorNode>`.
|
|
157
|
+
**Returns** `Generator<ErrorNode, void, undefined>`. The option and node types are exported as `WalkOptions`, `ErrorNode` and `ErrorRelation`.
|
|
154
158
|
|
|
155
159
|
#### `ErrorNode`
|
|
156
160
|
|
|
@@ -188,3 +192,16 @@ console.log(formatError(new Error('request failed', { cause: new Error('ECONNREF
|
|
|
188
192
|
```
|
|
189
193
|
|
|
190
194
|
Causes are labelled `Caused by:`, and `AggregateError` entries `-`. Each error is printed as its `stack`, with a `Name: message` line added first when the stack doesn't start with one (as in Firefox and Safari). Where `maxDepth` cut the walk short, a `… more causes omitted` line says so.
|
|
195
|
+
|
|
196
|
+
## Compared to alternatives
|
|
197
|
+
|
|
198
|
+
| | unknown-error | [normalize-exception](https://www.npmjs.com/package/normalize-exception) | [ensure-error](https://www.npmjs.com/package/ensure-error) | [serialize-error](https://www.npmjs.com/package/serialize-error) | [pony-cause](https://www.npmjs.com/package/pony-cause) |
|
|
199
|
+
|---|---|---|---|---|---|
|
|
200
|
+
| Never throws on hostile values | ✅ | ✅ | ❌ | ❌ | — |
|
|
201
|
+
| Errors from other realms | ✅ | ✅ | ❌ | ✅ | ❌ |
|
|
202
|
+
| Readable circular and BigInt values | ✅ | ❌ | ❌ | ❌ | — |
|
|
203
|
+
| Leaves your errors unmodified | ✅ | ❌ | ❌ | ✅ | — |
|
|
204
|
+
| Causes and `AggregateError` | ✅ | ✅ | ✅ | — | Causes only |
|
|
205
|
+
| 10,000-deep cause chains | ✅ | ❌ | ❌ | — | ❌ |
|
|
206
|
+
|
|
207
|
+
Each cell is checked by a [comparison test suite](https://github.com/JacobLey/leyman/blob/main/e2e/unknown-error-comparison/test/integration/comparison.spec.ts). For every case, and where hand-written conversions fall short, see [WHY-UNKNOWN-ERROR.md](https://github.com/JacobLey/leyman/blob/main/tools/unknown-error/WHY-UNKNOWN-ERROR.md#comparison).
|
package/WHY-UNKNOWN-ERROR.md
CHANGED
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
## The Problem
|
|
8
8
|
|
|
9
|
-
In TypeScript's `strict` mode, a `catch` variable is `unknown`,
|
|
9
|
+
In TypeScript's `strict` mode, a `catch` variable is `unknown`, because JavaScript can throw anything. To log it, report it or wrap it, you need an Error, so nearly every codebase ends up with something like this:
|
|
10
10
|
|
|
11
11
|
```ts
|
|
12
12
|
const toError = (value: unknown): Error =>
|
|
13
13
|
value instanceof Error ? value : new Error(String(value));
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
It handles the cases that come to mind first: an Error, or a thrown string. It
|
|
16
|
+
It handles the cases that come to mind first: an Error, or a thrown string. It fails on anything else, and throws inside the `catch` block that was meant to handle the error.
|
|
17
17
|
|
|
18
18
|
### Errors from another realm aren't `instanceof Error`
|
|
19
19
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unknown-error",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Turn any caught `unknown` into a real Error without throwing, even for hostile values, other realms and deep or circular cause chains",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"url": "git+https://github.com/JacobLey/leyman.git",
|
|
14
14
|
"directory": "tools/unknown-error"
|
|
15
15
|
},
|
|
16
|
-
"homepage": "https://github.
|
|
16
|
+
"homepage": "https://jacobley.github.io/leyman/packages/unknown-error/",
|
|
17
17
|
"bugs": {
|
|
18
18
|
"url": "https://github.com/JacobLey/leyman/issues"
|
|
19
19
|
},
|
|
@@ -49,16 +49,16 @@
|
|
|
49
49
|
"@leyman/eslint-config": "^0.0.1",
|
|
50
50
|
"@leyman/expect": "^0.0.1",
|
|
51
51
|
"@swc/cli": "^0.8.1",
|
|
52
|
-
"@swc/core": "^1.16.
|
|
52
|
+
"@swc/core": "^1.16.18",
|
|
53
53
|
"@types/mocha": "^10.0.10",
|
|
54
54
|
"@types/node": "^24.19.0",
|
|
55
55
|
"@typescript/native": "npm:typescript@^7.0.2",
|
|
56
56
|
"c8": "^12.0.0",
|
|
57
|
-
"eslint": "^10.
|
|
57
|
+
"eslint": "^10.13.0",
|
|
58
58
|
"expect-type": "^1.4.0",
|
|
59
|
-
"mocha": "^12.0.
|
|
60
|
-
"mocha-chain": "^1.0.
|
|
61
|
-
"npm-barrelify": "npm:barrelify@^
|
|
59
|
+
"mocha": "^12.0.3",
|
|
60
|
+
"mocha-chain": "^1.0.2",
|
|
61
|
+
"npm-barrelify": "npm:barrelify@^2.2.1",
|
|
62
62
|
"typescript": "npm:@typescript/typescript6@^6.0.2"
|
|
63
63
|
}
|
|
64
64
|
}
|