# @jchip/error reference `@jchip/error` has helpers to print readable error stacks and an `AggregateError` subclass whose `stack` includes the stacks of the errors it holds. It has no runtime dependencies. ESM only (`"type": "module"`). Requires Node `^22.22.2 || ^24.15.0 || >=26.0.0`. ## Imports ```js import { cleanErrorStack, aggregateStack, aggregateErrorStack, AggregateError } from "@jchip/error"; import * as jerr from "@jchip/error"; ``` Everything is a named export from the package root. There is no default export and there are no subpaths (only `@jchip/error/package.json`). Runtime exports: `cleanErrorStack`, `aggregateStack`, `aggregateErrorStack`, `AggregateError`. There are no exported types besides the class `AggregateError`. The options type `CleanErrorStackOptions` is declared but not exported, so it cannot be imported by name. Use `Parameters[1]` to get it. ## `cleanErrorStack(error, options?)` ```ts type CleanErrorStackOptions = { replacePath?: false | string; ignorePathFilter?: (string | RegExp)[]; }; function cleanErrorStack( error: Error, options?: CleanErrorStackOptions ): string ``` Returns the text of `error.stack` with noisy stack frames removed and file paths shortened. The error is not modified. | option | type | default | behavior | | --- | --- | --- | --- | | `replacePath` | `false \| string` | `` `${process.cwd()}/` `` (evaluated on each call) | Substring removed from each kept frame path. Ignored when `false`, empty, or exactly 1 character long (so `"/"` does nothing). | | `ignorePathFilter` | `(string \| RegExp)[]` | `[]` | Extra filters. A frame whose path matches any filter is dropped. Added to the built-in filter. | Input selection: - The text cleaned is `error.stack`, or `error.message` when the stack is falsy. - When neither exists (or `error` is `null`/`undefined`), nothing is cleaned and the return value is `String(error && (error.stack || error.message))`. That gives `"null"` for `null` and `"undefined"` for `undefined`, `{}`, or an object with empty stack and no message. - A non-string, truthy `stack` (such as a number) throws `TypeError` (`stack.split is not a function`). Processing, applied to each line of the text split on `"\n"`: 1. A line that does not match `/ {4,}at/` (an `at` preceded by 4 or more spaces anywhere in the line) is kept unchanged. This covers the message line, `Require stack:` lines, and frames indented with fewer than 4 spaces. 2. A frame line is parsed with `/( {4,}at )(?:([^(]*\()(.+)(\).*)|(.+))/`. It has two forms. `at name (location)` takes the location from the first `(` to the last `)`, so a `)` inside a path does not cut it short. `at location` has no parentheses. Node uses it for anonymous functions and ES module top-level code. 3. The frame is dropped when parsing fails (such as a bare ` at`), or unless the location contains `scheme://` (regex `/[^:]+:\/\//`) or `path.isAbsolute(location)` is true. That removes `node:internal/...` frames in either form, `()`, `(index 0)`, and relative paths. `path.isAbsolute` is platform specific, so a Windows path like `C:\a\b.js` is dropped on POSIX. 4. Backslashes in the location are replaced with `/`. 5. The frame is dropped if the location matches the built-in filter or any `ignorePathFilter` entry. A `RegExp` entry uses `location.match(re)`. A string entry uses `location.includes(str)`. Falsy entries (such as `""`) are skipped. 6. Otherwise `replacePath` (when usable) is removed. A location that starts with `file://` + `replacePath` loses both, so an ES module URL under the current directory becomes a relative path. Any other location has the first occurrence of `replacePath` removed with `String.prototype.replace` (plain string, not a regex). It can match anywhere in the location, not only at the start. A `file://` URL outside `replacePath` therefore stays whole. 7. The kept frame is rebuilt as `at` + name + new location + the text after the last `)`. Text before the 4-space indent is not kept. 8. Dropped frames are removed and the remaining lines are joined with `"\n"`. Blank lines in the original text are kept. Real stacks from CommonJS and ES modules are covered by tests on Node 22, 24 and 26 (`test/spec/real-stack.spec.ts`). Built-in filter (always on, checked before `ignorePathFilter`): ``` /\/node_modules.*\/(pirates\/|isomorphic-loader\/lib\/extend-require)/ ``` It drops frames from the `pirates` and `isomorphic-loader` require hooks. It matches on the backslash-normalized path. Not removed: frames from other `node_modules` packages, and any absolute path outside `replacePath`. Only `node:` internals and other non-absolute locations go by rule 3. ```js cleanErrorStack(err); // Error: boom // at run (src/app.js:10:5) // at src/main.mjs:3:1 cleanErrorStack(err, { replacePath: false, ignorePathFilter: ["/node_modules/", /vitest/] }); ``` ## `aggregateStack(stack, errors)` ```ts function aggregateStack(stack: string, errors: any[]): string ``` Builds a text from a top stack and a list of errors. Output is `stack`, then one entry per error, all joined with `"\n"`. Each error entry is its text with every line indented by 2 spaces (`replace(/^/gm, " ")`, so blank lines become `" "`). The text of one error `e` is `e.stack`, else `e.message`, else `String(e)`. A falsy `e` gives `String(e)`. Examples: `1` gives `"1"`, `null` gives `"null"`, `{ message: "mm" }` gives `"mm"`. Nested aggregate stacks indent again, so each nesting level adds 2 spaces. Edge cases on `errors`: - An array (or any object with a `map` function) is mapped. `[]` returns `stack` alone with no trailing newline. - A falsy `errors` (`null`, `undefined`, `""`) or a value with no `map` function (such as a string or a plain object without `map`) is treated as no errors, but the result gets a trailing `"\n"`, for example `aggregateStack("t", null) === "t\n"`. - A truthy `errors.map` that is not a function throws `TypeError`. - `stack` is not checked. A non-string is joined as is. ## `aggregateErrorStack(error)` ```ts function aggregateErrorStack(error: AggregateError): string ``` Returns `aggregateStack(top, error.errors)`. The parameter type is this package's `AggregateError` class. - For an instance of this package's `AggregateError`, `top` is its saved original stack (`__stack`), which is the native stack text, including the header `AggregateError: ` and frames. - For any other object, such as a native `globalThis.AggregateError`, `top` is `error.stack`. The native header and frames are kept. - When that text is empty, `top` falls back to `error.message`, then `String(error)`. An instance of this class with an empty `__stack` never reads its own `stack`, since that getter calls this function. - The list used is `error.errors`. - The result is computed each time. It is cached only by the `stack` getter of this package's class. ```js console.log(aggregateErrorStack(new AggregateError([new Error("error 1")], "test"))); // AggregateError: test // at ... // Error: error 1 // at ... ``` ## `AggregateError` ```ts class AggregateError extends globalThis.AggregateError { readonly name: string; // "AggregateError" errors: any[]; stack: string; __stack: string; constructor(errors?: Iterable, msg?: string, options?: { cause?: unknown }); } ``` An `AggregateError` whose `stack` also prints the errors it wraps. It is a subclass, not a polyfill. See "Polyfill behavior" below. Constructor `new AggregateError(errors, msg?, options?)`: - `errors` must be truthy and have a callable `[Symbol.iterator]`. Otherwise it throws `TypeError` with the message `input errors must be iterable but it's ${typeof errors}`. Falsy values give `undefined`, `object` (for `null`), `number`, and so on. A truthy number (`5`) gives `number`. A string is accepted since strings are iterable. - `errors` is spread once with `Array.from`, and that list goes to both the parent constructor and `errors`. So a `Set` or a one-shot iterator such as a generator gives its elements, and a string `"ab"` gives `["a", "b"]`, as with the native class. - `msg` is the message. When omitted the message is `""`. - `options` is passed to the parent constructor, so `{ cause }` sets `cause` as the native class does. Instance properties (all defined with `Object.defineProperty`, so `Object.keys(err)` is `[]`): | property | attributes | value | | --- | --- | --- | | `name` | non-enumerable, non-writable, non-configurable own property | `"AggregateError"`. A subclass cannot override it by assignment. | | `errors` | non-enumerable, writable, configurable | `Array.from(errors)`: a new array, even when `errors` is an array. | | `__stack` | non-enumerable, non-writable, non-configurable | `this.stack` read right after `super()`: the native stack text of the error. | | `stack` | non-enumerable, configurable accessor | Getter returning `aggregateErrorStack(this)`, computed on first read and cached. | `stack` details: - The cache means `errors` changes made after the first read of `stack` are not reflected. Changes made before the first read are. - Only a getter is defined. The existing native setter of the engine remains, so assigning to `err.stack` does not throw and has no effect on the getter result (observed on Node 24). - `instanceof` works against the native class (`new AggregateError([]) instanceof globalThis.AggregateError` is `true`). A native instance is not an instance of this class. - `console.log(err)` and `err.stack` in a debugger show the aggregate form. `cleanErrorStack(err)` therefore also cleans the nested error stacks, and the nested lines keep their 2-space indent. ## Polyfill behavior - The package does not patch or define any global. Importing it has no side effects (`"sideEffects": false`). - `AggregateError` here is a different class from `globalThis.AggregateError` (`AggregateError !== globalThis.AggregateError`). Its prototype parent is the global one. - The module evaluates `class AggregateError extends globalThis.AggregateError` at import time. If the runtime lacks a global `AggregateError`, import throws. All supported Node versions (`engines`) have it, so there is no fallback implementation. - Code that wants the aggregate-stack behavior must import this class explicitly. Native `AggregateError` instances are unchanged, and `aggregateErrorStack(native)` is the way to format them.