@exodus/errors 2.0.2 β 2.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/CHANGELOG.md +12 -0
- package/README.md +21 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/stack.d.ts +12 -0
- package/lib/stack.js +51 -18
- package/package.json +3 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [2.1.1](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/errors@2.1.0...@exodus/errors@2.1.1) (2025-05-08)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- fix: remove `void` keyword (#12267)
|
|
11
|
+
|
|
12
|
+
## [2.1.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/errors@2.0.2...@exodus/errors@2.1.0) (2025-04-28)
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
- feat: introduce `captureStackTrace` util (#12042)
|
|
17
|
+
|
|
6
18
|
## [2.0.2](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/errors@2.0.1...@exodus/errors@2.0.2) (2025-02-27)
|
|
7
19
|
|
|
8
20
|
### Bug Fixes
|
package/README.md
CHANGED
|
@@ -12,3 +12,24 @@ try {
|
|
|
12
12
|
sendToErrorTrackingServer(parseStackTrace(e))
|
|
13
13
|
}
|
|
14
14
|
```
|
|
15
|
+
|
|
16
|
+
## Troubleshooting
|
|
17
|
+
|
|
18
|
+
### `parseStackTrace` returns undefined stack trace?
|
|
19
|
+
|
|
20
|
+
That likely means that something accesses `error.stack` _before_ the `parseStackTrace` had a chance to apply the custom handler. This could be React, a high-level error handler, or any other framework. `error.stack` is computed only on the first access, so the custom handler (`prepareStackTrace`) wonβt be called on subsequent attempts (see the `stack.test.js` for a quick demo).
|
|
21
|
+
|
|
22
|
+
If you can identify the exact place where `.stack` is accessed, consider capturing the stack trace explicitly like this:
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
import { parseStackTrace, captureStackTrace } from '@exodus/errors'
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
// the devil's work
|
|
29
|
+
} catch (e) {
|
|
30
|
+
captureStackTrace(e)
|
|
31
|
+
void e.stack // Intentionally access the property to "break" it here.
|
|
32
|
+
sendToErrorTrackingServer(parseStackTrace(e))
|
|
33
|
+
// π Congrats β you just (hopefully) saved hours of debugging! `parseStackTrace` now works because the stack was captured explicitly above.
|
|
34
|
+
}
|
|
35
|
+
```
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
package/lib/stack.d.ts
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
1
|
import type { Frame } from './types.js';
|
|
2
|
+
export declare function captureStackTrace(err: Error): void;
|
|
2
3
|
export declare function stackFramesToString(frames?: Frame[]): string | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* If this function returns undefined, that likely means `error.stack` has been already accessed.
|
|
6
|
+
* Consider calling `captureStackTrace` first to capture the customized stack trace, e.g.,
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* captureStackTrace(error)
|
|
10
|
+
* console.log(error.stack)
|
|
11
|
+
* ...
|
|
12
|
+
* const safeError = SafeError.from(error) // Works even if `error.stack` has been accessed.
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
3
15
|
export default function parseStackTraceNatively(err: Error): Frame[] | undefined;
|
package/lib/stack.js
CHANGED
|
@@ -1,21 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
const stackCache = new WeakMap();
|
|
2
|
+
export function captureStackTrace(err) {
|
|
3
|
+
if (stackCache.has(err)) {
|
|
3
4
|
return;
|
|
4
5
|
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const { function: fn, file, line, column } = frame;
|
|
8
|
-
return ` at ${fn || 'unknownFn'}${file ? ` (${file}${line === null ? '' : `:${line}:${column}`})` : ''}`;
|
|
9
|
-
})
|
|
10
|
-
.join('\n');
|
|
11
|
-
}
|
|
12
|
-
export default function parseStackTraceNatively(err) {
|
|
13
|
-
const { prepareStackTrace } = Error;
|
|
14
|
-
let stack;
|
|
6
|
+
const { prepareStackTrace: originalPrepareStackTrace } = Error;
|
|
7
|
+
let structuredStack;
|
|
15
8
|
let calledOn;
|
|
16
|
-
Error.prepareStackTrace = (
|
|
17
|
-
calledOn =
|
|
18
|
-
|
|
9
|
+
Error.prepareStackTrace = (err, callSites) => {
|
|
10
|
+
calledOn = err;
|
|
11
|
+
structuredStack = callSites.map((trace) => ({
|
|
19
12
|
function: trace.getFunctionName(),
|
|
20
13
|
method: trace.getMethodName(),
|
|
21
14
|
file: trace.getFileName(),
|
|
@@ -25,15 +18,55 @@ export default function parseStackTraceNatively(err) {
|
|
|
25
18
|
async: trace.isAsync?.(),
|
|
26
19
|
toplevel: trace.isToplevel(),
|
|
27
20
|
}));
|
|
21
|
+
// Let V8 continue to build the default `.stack` string
|
|
22
|
+
if (originalPrepareStackTrace)
|
|
23
|
+
return originalPrepareStackTrace(err, callSites);
|
|
24
|
+
// Non V8 case (e.g. Hermes).
|
|
25
|
+
return err.stack;
|
|
28
26
|
};
|
|
29
27
|
try {
|
|
28
|
+
// do NOT add "void", it will remove the whole line in a tragic transpilation accident
|
|
30
29
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
31
30
|
err.stack; // trigger prepareStackTrace
|
|
32
31
|
}
|
|
33
32
|
finally {
|
|
34
|
-
Error.prepareStackTrace =
|
|
33
|
+
Error.prepareStackTrace = originalPrepareStackTrace;
|
|
35
34
|
}
|
|
36
35
|
// @ts-expect-error calledOn gets assigned in prepareStackTrace
|
|
37
|
-
if (calledOn === err && Array.isArray(
|
|
38
|
-
|
|
36
|
+
if (calledOn === err && Array.isArray(structuredStack)) {
|
|
37
|
+
stackCache.set(err, structuredStack);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
// This helps to avoid calling `captureStackTrace` multiple times on the same error.
|
|
41
|
+
stackCache.set(err, undefined);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export function stackFramesToString(frames) {
|
|
45
|
+
if (frames === undefined) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
return frames
|
|
49
|
+
.map((frame) => {
|
|
50
|
+
const { function: fn, file, line, column } = frame;
|
|
51
|
+
return ` at ${fn || 'unknownFn'}${file ? ` (${file}${line === null ? '' : `:${line}:${column}`})` : ''}`;
|
|
52
|
+
})
|
|
53
|
+
.join('\n');
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* If this function returns undefined, that likely means `error.stack` has been already accessed.
|
|
57
|
+
* Consider calling `captureStackTrace` first to capture the customized stack trace, e.g.,
|
|
58
|
+
*
|
|
59
|
+
* ```ts
|
|
60
|
+
* captureStackTrace(error)
|
|
61
|
+
* console.log(error.stack)
|
|
62
|
+
* ...
|
|
63
|
+
* const safeError = SafeError.from(error) // Works even if `error.stack` has been accessed.
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export default function parseStackTraceNatively(err) {
|
|
67
|
+
if (stackCache.has(err)) {
|
|
68
|
+
return stackCache.get(err);
|
|
69
|
+
}
|
|
70
|
+
captureStackTrace(err);
|
|
71
|
+
return stackCache.get(err);
|
|
39
72
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/errors",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.1.1",
|
|
5
5
|
"description": "Utilities for error handling in client code, such as sanitization",
|
|
6
6
|
"author": "Exodus Movement, Inc.",
|
|
7
7
|
"repository": {
|
|
@@ -38,11 +38,10 @@
|
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@exodus/errors-fixture": "^1.0.0",
|
|
41
|
-
"@types/minimalistic-assert": "^1.0.1"
|
|
42
|
-
"hermes-engine-cli": "^0.12.0"
|
|
41
|
+
"@types/minimalistic-assert": "^1.0.1"
|
|
43
42
|
},
|
|
44
43
|
"publishConfig": {
|
|
45
44
|
"access": "public"
|
|
46
45
|
},
|
|
47
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "5d915aed4f43c8b9377e87c902347aac778391d2"
|
|
48
47
|
}
|