<div align="center">
  <h3>Visulima error</h3>
  <p>
  Error with more than just a message, stacktrace parsing and sourcemap loading.
  </p>
</div>

<br />

<div align="center">

[![typescript-image]][typescript-url] [![npm-image]][npm-url] [![license-image]][license-url]

</div>

---

<div align="center">
    <p>
        <sup>
            Daniel Bannert's open source work is supported by the community on <a href="https://github.com/sponsors/prisis">GitHub Sponsors</a>
        </sup>
    </p>
</div>

---

## Install

```sh
npm install @visulima/error
```

```sh
yarn add @visulima/error
```

```sh
pnpm add @visulima/error
```

### Optional: AI integration

If you want to generate AI-powered solution hints, install these optional peer dependencies in your app:

```sh
# pnpm
pnpm add ai

# npm
npm i ai

# yarn
yarn add ai
```

## Usage

### Extend the VisulimaError

```ts
import { VisulimaError } from "@visulima/error";

class MyError extends VisulimaError {
    constructor(message: string) {
        super({
            name: "MyError",
            message,
        });
    }
}

throw new MyError("My error message");

// or

const error = new MyError("My error message");

error.hint = "My error hint";

throw error;
```

### Get all causes in a error

```ts
import { getErrorCauses } from "@visulima/error";

const error = new Error("My error message");
const error2 = new Error("Nested Error");

error.cause = error2;

// The getErrorCauses function will return an array of all causes in the error in the order they occurred.
const causes = getErrorCauses(error);

console.log(causes);

// [
//     {
//         message: "My error message",
//         name: "Error",
//         stack: "Error: My error message\n    at Object.<anonymous> (/visulima/packages/error/src/index.ts:2:16)",
//     },
//     {
//         message: "Nested Error",
//         name: "Error",
//         stack: "Error: Nested Error\n    at Object.<anonymous> (/visulima/packages/error/src/index.ts:3:16)",
//     },
// ];
```

## Pretty Code Frame

Display a pretty code frame with the error location.

> Note: Tabs can be used in the source code, codeFrame transforms them to spaces based on the tabWidth option.
> The default tabWidth is 4, to disable the transformation, set tabWidth to false.

```ts
import { codeFrame } from "@visulima/error";

const source = "const x = 10;\nconst error = x.y;\n";
const loc = { column: 16, line: 2 };

const frame = codeFrame(source, { start: loc });

console.log(frame);
//   1 | const x = 10;
// > 2 | const error = x.y;
//     |                ^
```

### API

#### source

Type: `string`

The source code to frame.

#### location

Type: `object`

The location of the error.

##### location.start

Type: `object`

The location of the start of the frame.

##### location.start.line

Type: `number`

The line number of the error.

##### location.start.column

Type: `number`

The column number of the error.

##### location.end

Type: `object`

The location of the end of the frame.

##### location.end.line

Type: `number`

The line number of the error.

##### location.end.column

Type: `number`

The column number of the error.

#### options

Type: `object`

##### options.linesAbove

Type: `number`

Default: `2`

The number of lines to show above the error.

##### options.linesBelow

Type: `number`

Default: `3`

The number of lines to show below the error.

##### options.tabWidth

Type: `number` | `false`

Default: `4`

## Stacktrace

> Browser older than 6 years are not supported.

Currently supported browsers/platforms:

- Firefox
- Chrome
- Webkit / Safari
- Edge
- Node / Node V8
- Opera (Chromium based)

```ts
import { parseStacktrace } from "@visulima/error";

const error = new Error("My error message");

const stack = parseStacktrace(error);

console.log(stack);

// [
//     {
//         column: 16,
//         file: "file:///Users/danielbannert/Projects/visulima/packages/error/src/index.ts",
//         line: 2,
//         methodName: "Object.<anonymous>",
//         raw: "    at Object.<anonymous> (/visulima/packages/error/src/index.ts:2:16)",
//         type: undefined, // optional property, can be undefined, "eval", "native", or "internal"
//         evalOrigin: undefined, // optional property only available if the stacktrace contains eval
//     },
//     ...and so on
// ];
```

### Format stacktrace to a string

Turn parsed frames into a printable stack string (Node-style), optionally with a header line.

```ts
import { parseStacktrace, stringifyStackFrames, formatStackFrameLine } from "@visulima/error";

const error = new Error("Boom");

const frames = parseStacktrace(error);

// Full stack as a single string with a header ("Error: Boom")
const stackText = stringifyStackFrames(frames, {
    header: { name: error.name, message: error.message },
});
console.log(stackText);

// Format a single frame line (e.g., for custom rendering)
const firstLine = formatStackFrameLine(frames[0]);
```

#### API

- `stringifyStackFrames(frames, options?)`
    - `frames`: `Trace[]` — parsed frames from `parseStacktrace`
    - `options.header`: `{ name?: string; message?: string }` — optional first-line header
- `formatStackFrameLine(frame)`
    - `frame`: `Trace`
    - returns a single formatted line like `"    at myMethod (/path/file.ts:10:5)"`

## Solutions (finders)

Generate helpful hints to fix an error. Two finders ship out of the box:

- Rule-based finder: provides best-effort fixes for common errors (ESM/CJS mix, missing default export, DNS issues, hydration mismatch, etc.)
- Error-hint finder: reads `error.hint` you attach to an `Error` or `VisulimaError`

Both return a `Solution` (`{ header?: string; body: string }`) or `undefined`.

```ts
import { codeFrame } from "@visulima/error";
import { ruleBasedFinder, errorHintFinder } from "@visulima/error";

const error = new Error("Cannot read properties of undefined (reading 'x')");

const source = "const o = undefined\nconsole.log(o.x)\n";
const snippet = codeFrame(source, { start: { line: 2, column: 13 } });

const fileCtx = {
    file: "/path/to/file.ts",
    line: 2,
    language: "ts",
    snippet,
};

// 1) Rule-based finder
const ruleHint = await ruleBasedFinder.handle(error, fileCtx);
if (ruleHint) {
    console.log(ruleHint.header);
    console.log(ruleHint.body);
}

// 2) Error-hint finder (reads error.hint)
const custom = new Error("failed") as Error & { hint?: unknown };
custom.hint = ["Check the environment variables.", "Ensure the service is reachable."];
const hintHint = await errorHintFinder.handle(custom, { file: "", line: 0 });
```

### AI solution helper (optional)

You can build an AI prompt with rich context and render the LLM result into a nice HTML snippet.

```ts
import { aiPrompt, aiSolutionResponse } from "@visulima/error/solution/ai";
// or
// import aiPrompt from "@visulima/error/solution/ai/prompt";

const prompt = aiPrompt({
    applicationType: undefined,
    error,
    file: fileCtx,
});

// Send `prompt` to your LLM of choice.
// When you receive the model's text output, format it for display:
const html = aiSolutionResponse(modelText);
```

You may also use a convenience finder that calls an AI provider for you (requires optional peer dep `ai`):

```ts
import { aiFinder } from "@visulima/error/solution/ai";
import { createOpenAI } from "@ai-sdk/openai";

const client = createOpenAI(apiKey ? { apiKey } : undefined);

const findAI = aiFinder(client(modelId), {
    temperature: 0,
});

const aiHint = await findAI.handle(error, fileCtx);
```

### API

#### error

Type: `Error`

The error to parse.

#### options

Type: `object`

##### options.filter

Type: `Function`

A function to filter the stack frames.

##### options.frameLimit

Type: `number`

The maximum number of frames to parse.

## `serialize` an error object

- Ensures errors are safe to serialize with JSON
- Can be used as error.toJSON()
- Deep serialization, including transforming
- Custom serialization (e.g. YAML or process.send())
- Keeps both native (TypeError, etc.) and custom error classes
- Preserves errors' additional properties
- Can keep constructor's arguments
- Works recursively with error.cause and AggregateError
- Buffer properties are replaced with [object Buffer]
- Circular references are handled.
- If the input object has a .toJSON() method, then it's called instead of serializing the object's properties.
- It's up to .toJSON() implementation to handle circular references and enumerability of the properties.

```ts
import { serializeError } from "@visulima/error";

const error = new TypeError("example");
const errorObject = serializeError(error);
// Plain object: { name: 'TypeError', message: 'example', stack: '...' }

const errorString = JSON.stringify(errorObject);
const newErrorObject = JSON.parse(errorString);
```

## `deserialize` an error object

Deserialize a previously serialized error back to an Error instance.

- Automatically detects error-like objects
- Reconstructs proper Error instances with correct constructors
- Handles custom error classes registered with `addKnownErrorConstructor`
- Supports AggregateError deserialization
- Preserves error properties and cause chains
- Wraps non-error-like objects in NonError

```ts
import { serializeError, deserializeError } from "@visulima/error";

const error = new TypeError("example");
const serialized = serializeError(error);

// Deserialize back to Error instance
const deserialized = deserializeError(serialized);

console.log(deserialized instanceof TypeError); // true
console.log(deserialized.message); // "example"
```

### Registering Custom Error Constructors

```ts
import { addKnownErrorConstructor, deserializeError } from "@visulima/error";

class CustomError extends Error {
    constructor(message: string, code: number) {
        super(message);
        this.name = "CustomError";
        this.code = code;
    }
}

// Register the custom error constructor
addKnownErrorConstructor(CustomError);

// Now it can be deserialized properly
const serialized = { name: "CustomError", message: "test", code: 42 };
const deserialized = deserializeError(serialized);

console.log(deserialized instanceof CustomError); // true
console.log(deserialized.code); // 42
```

### NonError for Non-Error Objects

When deserializing objects that don't look like errors, they're wrapped in a `NonError`:

```ts
import { deserializeError, NonError } from "@visulima/error";

const deserialized = deserializeError({ foo: "bar" });

console.log(deserialized instanceof NonError); // true
console.log(deserialized.message); // '{"foo":"bar"}'
```

## renderError - pretty print an error

```ts
import { renderError } from "@visulima/error";

const error = new Error("This is an error message");

console.log(renderError(error));

// Error: This is an error message
//
// at <unknown> file:///home/visulima/visulima/examples/error/node/render-error.js:5
//   1 | import { renderError } from "@visulima/error";
//   2 |
//   3 | const error = new Error("This is an error message");
//   4 |
// ❯ 5 | console.log(renderError(new Error("This is an error message")));
//     |                         ^
//   6 |
//
// at ModuleJob.run node:internal/modules/esm/module_job:195
// at async ModuleLoader.import node:internal/modules/esm/loader:336
// at async loadESM node:internal/process/esm_loader:34
// at async handleMainPromise node:internal/modules/run_main:106
```

#### colorized output

Use the `@visulima/colorize`, `chalk` or some other package to colorize the output.

![colorized output](./__assets__/pretty-error-render-with-cause-and-hint.png)

### API

#### error

Type: `AggregateError | Error | VisulimaError` \
The error to render.

#### options

Type: `object`

##### options.color

Type: `object` \
The color options.

##### options.cwd

Type: `string`

The current working directory.

##### options.displayShortPath

Type: `boolean` \
Default: `false`

Display the short path.

##### options.framesMaxLimit

Type: `number` \
Default: `Number.Infinity`

The maximum number of frames to display.

##### options.hideErrorCauseCodeView

Type: `boolean` \
Default: `false`

Hide the error cause code view.

##### options.hideErrorCodeView

Type: `boolean` \
Default: `false`

Hide the error code view.

##### options.hideErrorTitle

Type: `boolean` \
Default: `false`

Hide the error title.

##### options.hideMessage

Type: `boolean` \
Default: `false`

Hide the error message.

### `deserializeError`

Deserialize a value back to its original form. If the value looks like a serialized error, it will be reconstructed as an Error instance. Otherwise, it will be wrapped in a NonError.

```ts
import { deserializeError } from "@visulima/error";

const deserialized = deserializeError({ name: "TypeError", message: "example" });

console.log(deserialized instanceof TypeError); // true
```

#### value

Type: `unknown`

The value to deserialize.

#### options

Type: `object`

##### options.maxDepth

Type: `number` \
Default: `Number.POSITIVE_INFINITY`

The maximum depth to deserialize nested objects.

### `NonError`

A class for wrapping non-error-like objects during deserialization.

```ts
import { NonError } from "@visulima/error";

const nonError = new NonError("some message");

console.log(nonError instanceof Error); // true
console.log(nonError.name); // "NonError"
```

### `addKnownErrorConstructor`

Add a known error constructor to the registry for proper deserialization.

```ts
import { addKnownErrorConstructor } from "@visulima/error";

class CustomError extends Error {
    constructor(message: string) {
        super(message);
        this.name = "CustomError";
    }
}

addKnownErrorConstructor(CustomError);
```

#### constructor

Type: `new (...args: unknown[]) => Error`

The error constructor to add to the registry.

### `isErrorLike`

Check if an object looks like a serialized error.

```ts
import { isErrorLike } from "@visulima/error";

const obj = { name: "TypeError", message: "example" };

console.log(isErrorLike(obj)); // true
```

#### value

Type: `unknown`

The value to check.

### captureRawStackTrace

Capture a raw stack trace.

```ts
import { captureRawStackTrace } from "@visulima/error";

const stack = captureRawStackTrace();

console.log(stack);
```

## Supported Node.js Versions

Libraries in this ecosystem make the best effort to track [Node.js’ release schedule](https://github.com/nodejs/release#release-schedule).
Here’s [a post on why we think this is important](https://medium.com/the-node-js-collection/maintainers-should-consider-following-node-js-release-schedule-ab08ed4de71a).

## Contributing

If you would like to help take a look at the [list of issues](https://github.com/visulima/visulima/issues) and check our [Contributing](.github/CONTRIBUTING.md) guild.

> **Note:** please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.

## Credits

- [Daniel Bannert](https://github.com/prisis)
- [All Contributors](https://github.com/visulima/visulima/graphs/contributors)

## About

### Related Projects

- [baseerr](https://github.com/tjmehta/baseerr): merge another error with additional properties.
- [callsites](https://github.com/sindresorhus/callsites): get callsites from the V8 stack trace API.
- [explain-error](https://github.com/dominictarr/explain-error): wrap an error with additional explanation.
- [error-wrapper](https://github.com/spudly/error-wrapper): merges the stack of another error to its own.
- [errwischt/stacktrace-parser](https://github.com/errwischt/stacktrace-parser)
- [trace](https://github.com/AndreasMadsen/trace): create super long stack traces.
- [clarify](https://github.com/AndreasMadsen/clarify): remove node related stack trace noise.
- [piotr-szewczyk/stacktrace-parser-node](https://github.com/piotr-szewczyk/stacktrace-parser-node)
- [pretty-error](https://github.com/AriaMinaei/pretty-error): make the call stacks clear.
- [node-pretty-exceptions](https://github.com/ahmadnassri/node-pretty-exceptions) - Pretty and more helpful uncaught exceptions, automatically
- [youch-terminal](https://github.com/poppinss/youch-terminal/tree/develop) - Display youch error message on terminal
- [ono](https://github.com/bigstickcarpet/ono): allow different types of error to be thrown.
- [stacktracejs/error-stack-parser](https://github.com/stacktracejs/error-stack-parser)
- [marvinhagemeister/errorstacks](https://github.com/marvinhagemeister/errorstacks) Tiny library to parse error stack traces
- [getsentry/sentry-javascript](https://github.com/getsentry/sentry-javascript)
- [serialize-error](https://github.com/sindresorhus/serialize-error) - Serialize/deserialize an error into a plain object
- [baseerr](https://github.com/tjmehta/baseerr): merge another error with additional properties.
- [callsite-record](https://github.com/inikulin/callsite-record) - Create fancy log entries for errors and function call sites

## License

The visulima error is open-sourced software licensed under the [MIT][license-url]

[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript

[typescript-url]: https://www.typescriptlang.org/ "TypeScript" "typescript"
[license-image]: https://img.shields.io/npm/l/@visulima/error?color=blueviolet&style=for-the-badge
[license-url]: LICENSE.md "license"
[npm-image]: https://img.shields.io/npm/v/@visulima/error/latest.svg?style=for-the-badge&logo=npm
[npm-url]: https://www.npmjs.com/package/@visulima/error/v/latest "npm"
