🚨❌
A smart, isomorphic, and type-safe error library for TypeScript applications. Provides excellent DX with intelligent error conversion, stack trace preservation, serialization support, error chaining, flexible custom error classes, and more.
- Built in TypeScript for full Type-safe error handling support and generics
- Isomorphic - works in Node.js and browsers
- Smart error conversion from different sources (API responses, strings, Error objects)
- Error chaining for full error sequence
- Error aggregation for batch operations with multiple failures
- Factory method
.create()for preset-based error creation - Custom metadata with type-safe generics for additional context
- Global configuration for stack cleaning and defaults
- Serialization/deserialization for network transfer and storage
- ErrorXResolver for i18n, documentation URLs, and custom presentation logic
- Observability - fingerprinting, structured logging, OpenTelemetry integration
- Custom ErrorX class examples:
HTTPErrorX- HTTP status code presets (400-511)DBErrorX- Database error presets (connection, query, constraints)ValidationErrorX- Validation errors with Zod integration
pnpm add @bombillazo/error-x
# or
npm install @bombillazo/error-x
# or
yarn add @bombillazo/error-x- Node.js: 18 or higher
- TypeScript: 5.0 or higher
- Target Environment: ES2022+
This library uses modern JavaScript features and ES2022 APIs. For browser compatibility, ensure your build tool (e.g., Vite, webpack, esbuild) is configured to target ES2022 or transpile accordingly.
Note
This library follows Semantic Versioning. The API is now stable - breaking changes will only occur in major version updates.
import { ErrorX, HTTPErrorX, DBErrorX } from "@bombillazo/error-x";
// Simple usage
throw new ErrorX("Something went wrong");
// A fully defined error
throw new ErrorX({
message: "User authentication failed",
name: "AuthError",
code: "AUTH_FAILED",
httpStatus: 401,
metadata: { userId: 123 },
});
// Using specialized error classes
throw HTTPErrorX.create(404);
throw DBErrorX.create("CONNECTION_FAILED");
// Convert unknown errors
const errorX = ErrorX.from(unknownError);The base error class that extends the native Error with enhanced capabilities.
| Property | Type | Description |
|---|---|---|
message |
string |
Technical error message |
name |
string |
Error type/name |
code |
string |
Error identifier code (auto-generated from name if not set) |
httpStatus |
number | undefined |
HTTP status code associated with this error |
metadata |
TMetadata | undefined |
Additional context (type-safe with generics) |
timestamp |
number |
Unix epoch timestamp (ms) when error was created |
stack |
string | undefined |
Stack trace (inherited from Error) |
chain |
readonly ErrorX[] |
Full error sequence: [this, parent, grandparent, ..., root] |
root |
ErrorX | undefined |
Error that started the whole error chain |
parent |
ErrorX | undefined |
Error that immediately precedes this error in the chain |
original |
ErrorXSnapshot | undefined |
Stores the original non-ErrorX error used to create this error |
| Method | Description |
|---|---|
from(value, opts?) |
Convert any value to ErrorX with intelligent property extraction |
fromJSON(json) |
Deserialize JSON back to ErrorX instance |
create(key?, opts?) |
Factory method for preset-based error creation (used by subclasses) |
aggregate(errors, opts?) |
Combine multiple errors into an AggregateErrorX instance |
isErrorX(value) |
Type guard to check if value is an ErrorX instance |
isErrorXOptions(v) |
Check if value is a valid ErrorXOptions object |
configure(config) |
Set global configuration (stack cleaning, defaults) |
getConfig() |
Get current global configuration |
resetConfig() |
Reset global configuration to defaults |
cleanStack(stack) |
Clean internal frames from stack trace |
| Method | Description |
|---|---|
withMetadata(meta) |
Create new ErrorX with additional metadata merged |
toJSON() |
Serialize to JSON-compatible object |
toString() |
Detailed string representation with metadata |
new ErrorX(input?: string | ErrorXOptions)Create a new ErrorX instance. All parameters are optional with sensible defaults.
// String message
new ErrorX("Database connection failed");
// Options object
new ErrorX({
message: "User not found",
name: "NotFoundError",
code: "USER_NOT_FOUND",
httpStatus: 404,
metadata: { userId: 123 },
});
// With type-safe metadata
type UserMeta = { userId: number; action: string };
new ErrorX<UserMeta>({
message: "Action failed",
metadata: { userId: 123, action: "delete" },
});| Property | Type | Default | Description |
|---|---|---|---|
| message | string |
'An error occurred' |
Technical error message |
| name | string |
'Error' |
Error type/name |
| code | string | number |
Auto-generated | Error identifier (UPPER_SNAKE_CASE) |
| httpStatus | number |
undefined |
HTTP status code |
| metadata | TMetadata |
undefined |
Additional context |
| cause | unknown |
undefined |
Error that caused this (builds the chain) |
Configure stack trace cleaning and other global settings.
import { ErrorX } from "@bombillazo/error-x";
// Enable stack cleaning with custom delimiter
ErrorX.configure({
cleanStack: true,
cleanStackDelimiter: "my-app-entry",
});
// Custom patterns to remove from stack traces
ErrorX.configure({
cleanStack: ["node_modules", "internal/"],
});
// Disable stack cleaning
ErrorX.configure({ cleanStack: false });
// Get current config
const config = ErrorX.getConfig();
// Reset to defaults
ErrorX.resetConfig();Error codes are automatically generated from names in UPPER_SNAKE_CASE when not provided:
new ErrorX({ name: "DatabaseError" });
// → code: 'DATABASE_ERROR'
new ErrorX({ name: "userAuthError" });
// → code: 'USER_AUTH_ERROR'
new ErrorX({ name: "API Timeout" });
// → code: 'API_TIMEOUT'ErrorX does NOT auto-format messages. Messages are passed through as-is:
new ErrorX({ message: "test error" });
// → message: 'test error'
new ErrorX({ message: "Test error." });
// → message: 'Test error.'Empty or whitespace-only messages default to 'An error occurred':
new ErrorX({ message: "" });
// → message: 'An error occurred'Preset messages in specialized classes (HTTPErrorX, DBErrorX) are properly formatted with sentence casing and periods.
Convert any value into an ErrorX instance with intelligent property extraction.
static from<T>(payload: unknown, overrides?: Partial<ErrorXOptions<T>>): ErrorX<T>Handles strings, Error objects, API responses, and unknown values. Extracts common properties like message, code, status, statusCode, and metadata.
// Convert string
ErrorX.from("Something went wrong");
// Convert Error
ErrorX.from(new Error("Connection failed"));
// Convert API response
ErrorX.from({
message: "User not found",
code: "USER_404",
status: 404,
metadata: { userId: 123 },
});
// With overrides (deep merged)
ErrorX.from(error, {
httpStatus: 500,
metadata: { context: "db-layer" },
});Type guard to check if a value is an ErrorX instance.
static isErrorX<T>(value: unknown): value is ErrorX<T>try {
await riskyOperation();
} catch (error) {
if (ErrorX.isErrorX(error)) {
console.log(error.code, error.metadata);
}
}Create a new ErrorX with additional metadata merged with existing metadata.
withMetadata<T>(additionalMetadata: T): ErrorX<TMetadata & T>const error = new ErrorX({
message: "Request failed",
metadata: { endpoint: "/api/users" },
});
const enriched = error.withMetadata({ retryCount: 3, userId: 123 });
// metadata: { endpoint: '/api/users', retryCount: 3, userId: 123 }Serialize ErrorX instances for network transfer or storage.
// Serialize
const json = error.toJSON();
// { name, message, code, stack, metadata, timestamp, httpStatus, original, chain }
// Deserialize
const restored = ErrorX.fromJSON(json);Build error timelines by passing cause to preserve the full error history.
// Build an error chain
const dbError = ErrorX.from(new Error("ECONNREFUSED"));
const repoError = new ErrorX({ message: "Query failed", cause: dbError });
const serviceError = new ErrorX({
message: "User fetch failed",
cause: repoError,
});
// Access chain information
serviceError.chain.length; // 3: [serviceError, repoError, dbError]
serviceError.parent; // repoError
serviceError.root; // dbError
dbError.original; // { message: 'ECONNREFUSED', name: 'Error', stack: '...' }// Practical example
try {
await database.query(sql);
} catch (dbError) {
throw DBErrorX.create("QUERY_FAILED", {
cause: dbError,
metadata: { query: sql, table: "users" },
});
}
// Later, inspect the chain
if (ErrorX.isErrorX(error)) {
console.log(
"Error chain:",
error.chain.map((e) => e.name),
);
console.log("Root cause:", error.root?.original);
}Combine multiple errors into a single AggregateErrorX instance. Useful for batch operations, parallel processing, or validation scenarios where multiple failures can occur.
import { ErrorX, AggregateErrorX } from "@bombillazo/error-x";
// Aggregate multiple validation errors
const validationErrors = [
new ErrorX({ message: "Email is required", code: "EMAIL_REQUIRED" }),
new ErrorX({ message: "Password too short", code: "PASSWORD_SHORT" }),
new ErrorX({ message: "Invalid phone format", code: "PHONE_INVALID" }),
];
const aggregate = ErrorX.aggregate(validationErrors);
// → message: 'Multiple errors occurred (3 errors)'
// → code: 'AGGREGATE_ERROR'
// → errors: [ErrorX, ErrorX, ErrorX]
// With custom options
const batchError = ErrorX.aggregate(errors, {
message: "Batch import failed",
code: "BATCH_IMPORT_FAILED",
httpStatus: 400,
metadata: { batchId: "batch_123", failedCount: 3 },
});
// Access individual errors
for (const error of aggregate.errors) {
console.log(error.code, error.message);
// Each error preserves its chain: error.chain, error.root, error.parent
}
// Type guard
if (AggregateErrorX.isAggregateErrorX(error)) {
console.log(`Found ${error.errors.length} errors`);
error.errors.forEach((e) => console.log(e.code));
}
// Serialization (preserves all aggregated errors)
const serialized = aggregate.toJSON();
const restored = AggregateErrorX.fromJSON(serialized);| Property | Type | Description |
|---|---|---|
errors |
readonly ErrorX[] |
Array of all aggregated errors |
| ...inherited | All ErrorX properties (message, code, metadata, etc.) |
| Method | Description |
|---|---|
ErrorX.aggregate(errors, opts?) |
Create an AggregateErrorX from an array of errors |
AggregateErrorX.isAggregateErrorX(value) |
Type guard to check if value is an AggregateErrorX |
AggregateErrorX.fromJSON(serialized) |
Deserialize back to AggregateErrorX instance |
error-x includes several custom error classes out of the box:
- Ready-to-use - Practical error classes for common scenarios (HTTP, database, validation)
- Educational - Demonstrate how to use presets, defaults, and transforms
- Extensible - Serve as templates and inspiration for your own domain-specific error classes
HTTP errors with presets for all standard status codes (400-511).
import { HTTPErrorX } from "@bombillazo/error-x";
// Create by status code
HTTPErrorX.create(404);
// → code: 'NOT_FOUND', name: 'NotFoundError', httpStatus: 404
HTTPErrorX.create(401);
// → code: 'UNAUTHORIZED', name: 'UnauthorizedError', httpStatus: 401
// With overrides
HTTPErrorX.create(404, {
message: "User not found",
metadata: { userId: 123 },
});
// With error chaining
HTTPErrorX.create(500, { cause: originalError });
// instanceof checks
if (error instanceof HTTPErrorX) {
console.log(error.httpStatus);
}Available Presets: All 4xx client errors (400-451) and 5xx server errors (500-511).
Database errors with presets for common database scenarios. All codes are automatically prefixed with DB_.
import { DBErrorX } from "@bombillazo/error-x";
// Connection errors
DBErrorX.create("CONNECTION_FAILED"); // → code: 'DB_CONNECTION_FAILED'
DBErrorX.create("CONNECTION_TIMEOUT");
DBErrorX.create("CONNECTION_REFUSED");
DBErrorX.create("CONNECTION_LOST");
// Query errors
DBErrorX.create("QUERY_FAILED");
DBErrorX.create("QUERY_TIMEOUT");
DBErrorX.create("SYNTAX_ERROR");
// Constraint errors (with appropriate httpStatus)
DBErrorX.create("UNIQUE_VIOLATION"); // httpStatus: 409
DBErrorX.create("FOREIGN_KEY_VIOLATION"); // httpStatus: 400
DBErrorX.create("NOT_NULL_VIOLATION"); // httpStatus: 400
DBErrorX.create("CHECK_VIOLATION"); // httpStatus: 400
// Transaction errors
DBErrorX.create("TRANSACTION_FAILED");
DBErrorX.create("DEADLOCK"); // httpStatus: 409
// Record errors
DBErrorX.create("NOT_FOUND"); // httpStatus: 404
// With metadata
DBErrorX.create("QUERY_FAILED", {
message: "Failed to fetch user",
metadata: {
query: "SELECT * FROM users WHERE id = ?",
table: "users",
operation: "SELECT",
},
});
// instanceof checks
if (error instanceof DBErrorX) {
console.log(error.metadata?.table);
}Validation errors with Zod integration. All codes are prefixed with VALIDATION_.
import { z } from "zod";
import { ValidationErrorX } from "@bombillazo/error-x";
// From Zod errors
const schema = z.object({
email: z.string().email(),
age: z.number().min(18),
});
try {
schema.parse({ email: "invalid", age: 15 });
} catch (err) {
if (err instanceof z.ZodError) {
throw ValidationErrorX.fromZodError(err);
// → code: 'VALIDATION_INVALID_STRING'
// → metadata.field: 'email'
// → metadata.zodCode: 'invalid_string'
// → httpStatus: 400
}
}
// With overrides
ValidationErrorX.fromZodError(zodError, {
httpStatus: 422,
});
// Field-specific errors (without Zod)
ValidationErrorX.forField("email", "Invalid email format");
ValidationErrorX.forField("age", "Must be 18 or older", { code: "TOO_YOUNG" });
// Direct creation
ValidationErrorX.create({
message: "Invalid input",
code: "INVALID_INPUT",
metadata: { field: "email" },
});
// instanceof checks
if (error instanceof ValidationErrorX) {
console.log(error.metadata?.field);
}Extend ErrorX to create domain-specific error classes with presets, defaults, and transforms.
import {
ErrorX,
type ErrorXOptions,
type ErrorXTransform,
} from "@bombillazo/error-x";
// 1. Define your metadata type
type PaymentMetadata = {
transactionId?: string;
amount?: number;
currency?: string;
};
// 2. Define presets outside the class for type inference
const paymentPresets = {
INSUFFICIENT_FUNDS: {
code: "INSUFFICIENT_FUNDS",
name: "PaymentError",
message: "Insufficient funds.",
httpStatus: 402,
},
CARD_DECLINED: {
code: "CARD_DECLINED",
name: "PaymentError",
message: "Card declined.",
httpStatus: 402,
},
EXPIRED_CARD: {
code: "EXPIRED_CARD",
name: "PaymentError",
message: "Card expired.",
httpStatus: 402,
},
} as const satisfies Record<string, ErrorXOptions>;
// Optional: Define user-friendly messages separately
export const paymentErrorUiMessages: Record<keyof typeof paymentPresets, string> = {
INSUFFICIENT_FUNDS: "Your payment method has insufficient funds.",
CARD_DECLINED: "Your card was declined. Please try another payment method.",
EXPIRED_CARD: "Your card has expired. Please update your payment method.",
};
// 3. Derive preset key type
type PaymentPresetKey = keyof typeof paymentPresets | (string & {});
// 4. Create the class
export class PaymentErrorX extends ErrorX<PaymentMetadata> {
static presets = paymentPresets;
static defaultPreset = "CARD_DECLINED";
static defaults = { httpStatus: 402 };
// Optional: transform to prefix codes
static transform: ErrorXTransform<PaymentMetadata> = (opts) => ({
...opts,
code: `PAYMENT_${opts.code}`,
});
// Override create for proper typing
static override create(
presetKey?: PaymentPresetKey,
overrides?: Partial<ErrorXOptions<PaymentMetadata>>,
): PaymentErrorX {
return ErrorX.create.call(
PaymentErrorX,
presetKey,
overrides,
) as PaymentErrorX;
}
}
// Usage
throw PaymentErrorX.create("INSUFFICIENT_FUNDS");
throw PaymentErrorX.create("CARD_DECLINED", {
metadata: { transactionId: "tx_123", amount: 99.99, currency: "USD" },
});
// instanceof works
if (error instanceof PaymentErrorX) {
console.log(error.metadata?.transactionId);
}The ErrorXResolver class resolves ErrorX instances to enhanced presentation objects with i18n support, documentation URLs, and custom properties.
import { ErrorXResolver, HTTPErrorX } from "@bombillazo/error-x";
const resolver = new ErrorXResolver({
// Required: determine error type from error instance
onResolveType: (error) => {
if (error instanceof HTTPErrorX) return "http";
return "general";
},
// Per-type configuration
configs: {
http: { namespace: "errors.http" },
general: { namespace: "errors" },
},
});
const error = HTTPErrorX.create(404);
const result = resolver.resolve(error);
// → { uiMessage: undefined, docsUrl: '', i18nKey: 'errors.http.NOT_FOUND', errorType: 'http', config: {...} }import i18next from "i18next";
const resolver = new ErrorXResolver({
i18n: {
resolver: (key, params) => i18next.t(key, params),
keyTemplate: "{namespace}.{code}", // default
},
docs: {
baseUrl: "https://docs.example.com/errors",
},
onResolveType: (error) => (error.code.startsWith("HTTP_") ? "http" : "general"),
configs: {
http: { namespace: "errors.http", docsPath: "/http" },
general: { namespace: "errors.general" },
},
});
const result = resolver.resolve(error);
// → { uiMessage: 'Not found', docsUrl: 'https://docs.example.com/errors/http#NOT_FOUND', ... }Extend the resolver with custom properties for your domain:
import { ErrorXResolver, type ErrorXResolverConfig } from "@bombillazo/error-x";
// Define custom config with additional properties
type MyConfig = ErrorXResolverConfig<{
severity: "error" | "warning" | "info";
retryable: boolean;
}>;
const resolver = new ErrorXResolver<MyConfig>({
onResolveType: (error) => "api",
defaults: {
namespace: "errors",
severity: "error",
retryable: false,
},
configs: {
api: {
namespace: "errors.api",
severity: "warning",
retryable: true,
// Per-code overrides
presets: {
NOT_FOUND: { severity: "info", retryable: false },
},
},
},
});Transform the resolve output to match your API:
type MyResult = { message: string; docs: string; canRetry: boolean };
const resolver = new ErrorXResolver<MyConfig, MyResult>({
onResolveType: (error) => "api",
onResolve: (error, context) => ({
message: context.uiMessage ?? error.message,
docs: context.docsUrl,
canRetry: context.config.retryable,
}),
configs: { api: { namespace: "errors.api", retryable: true } },
});ErrorX is designed to be fast enough for production use while providing rich error handling capabilities. Here are the key performance characteristics:
Run benchmarks locally with pnpm bench. Results from a typical run (Apple M2):
| Operation | ops/sec | Notes |
|---|---|---|
new Error() (native) |
~525k | Baseline comparison |
new ErrorX() |
~38k | ~14x slower than native Error |
new ErrorX(options) |
~40k | Similar to basic ErrorX |
ErrorX.from(ErrorX) |
~21M | Passthrough is extremely fast |
ErrorX.from(Error) |
~32k | Converts native errors |
toJSON() (simple) |
~5.4M | Very fast serialization |
toJSON() (with chain) |
~1.4M | Chain adds overhead |
fromJSON() (simple) |
~32k | Deserialization |
isErrorX() |
~21M | Near-instant type guard |
aggregate() (3 errors) |
~30k | Aggregation overhead |
Error Creation (~38k ops/sec)
- Creating an ErrorX is ~14x slower than native
Errordue to stack cleaning, timestamp generation, and chain management - Adding metadata or httpStatus has negligible impact
- Adding a cause (chaining) reduces performance by ~2x due to chain flattening
Serialization (toJSON)
- Simple errors: ~5.4M ops/sec (extremely fast)
- With metadata: ~346k ops/sec (JSON serialization overhead)
- With error chain: ~1.4M ops/sec (iterates chain)
Deserialization (fromJSON)
- ~32k ops/sec regardless of metadata
- Chain reconstruction adds ~3x overhead per chained error
Type Guards
isErrorX()andisAggregateErrorX(): ~21M ops/sec (instant)isErrorXOptions(): ~15M ops/sec (object key checking)
Memory Considerations
- Deep chains (50+ levels) process at ~343 ops/sec for full create/serialize/deserialize cycle
- Large aggregates (100 errors) process at ~135 ops/sec
- No memory leaks detected in chain or aggregate handling
ErrorX is suitable for:
- Application-level error handling (not hot loops)
- API error responses
- Error logging and monitoring
- Domain error modeling
For performance-critical code paths (>100k errors/sec), consider using native Error and converting to ErrorX at boundaries.
User-friendly messages are provided separately from error presets. This allows errors to remain technical while UI messages can be managed independently (e.g., for i18n).
import {
httpErrorUiMessages,
dbErrorUiMessages,
validationErrorUiMessage,
} from "@bombillazo/error-x";
// HTTP error messages keyed by status code
httpErrorUiMessages[404]; // "The requested resource could not be found."
httpErrorUiMessages[500]; // "An unexpected error occurred. Please try again later."
// Database error messages keyed by preset name
dbErrorUiMessages.CONNECTION_FAILED; // "Unable to connect to the database. Please try again later."
dbErrorUiMessages.UNIQUE_VIOLATION; // "This record already exists."
// Validation error default message
validationErrorUiMessage; // "The provided input is invalid. Please check your data."import { ErrorXResolver, HTTPErrorX, httpErrorUiMessages } from "@bombillazo/error-x";
const resolver = new ErrorXResolver({
onResolveType: () => "http",
configs: {
http: {
namespace: "errors.http",
presets: {
// Use provided UI messages as static fallbacks
NOT_FOUND: { uiMessage: httpErrorUiMessages[404] },
UNAUTHORIZED: { uiMessage: httpErrorUiMessages[401] },
},
},
},
});error-x provides built-in observability utilities for error fingerprinting, structured logging, and OpenTelemetry integration.
Generate stable fingerprints for error deduplication and grouping:
import { generateFingerprint } from "@bombillazo/error-x";
const error = new ErrorX({
message: "Database connection failed",
code: "DB_CONN_FAILED",
name: "DatabaseError",
});
const fingerprint = generateFingerprint(error);
// → "a1b2c3d4" (stable hash based on error properties)
// Same error type always produces the same fingerprint
const error2 = new ErrorX({
message: "Database connection failed",
code: "DB_CONN_FAILED",
name: "DatabaseError",
});
generateFingerprint(error2) === fingerprint; // true
// Customize what's included in the fingerprint
generateFingerprint(error, {
includeCode: true, // default: true
includeName: true, // default: true
includeMessage: true, // default: true
includeMetadataKeys: ["userId", "endpoint"], // specific metadata keys
hashFunction: customHashFn, // custom hash function
});Create structured log entries compatible with pino, winston, and other logging libraries:
import { toLogEntry } from "@bombillazo/error-x";
const error = new ErrorX({
message: "User not found",
code: "USER_NOT_FOUND",
httpStatus: 404,
metadata: { userId: 123 },
});
const logEntry = toLogEntry(error);
// {
// level: 'error',
// message: 'User not found',
// fingerprint: 'abc123',
// errorName: 'Error',
// errorCode: 'USER_NOT_FOUND',
// timestamp: 1704067200000,
// timestampIso: '2024-01-01T00:00:00.000Z',
// httpStatus: 404,
// metadata: { userId: 123 },
// chainDepth: 1,
// }
// With options
const debugEntry = toLogEntry(error, {
level: "warn", // 'error' | 'warn' | 'info'
includeStack: true, // include stack trace
includeFull: true, // include full serialized error
context: { requestId: "req-123" }, // merge additional context
});
// Use with pino
import pino from "pino";
const logger = pino();
logger.error(toLogEntry(error, { includeStack: true }));Create span attributes following OpenTelemetry semantic conventions:
import { toOtelAttributes, recordError } from "@bombillazo/error-x";
import { trace, SpanStatusCode } from "@opentelemetry/api";
const tracer = trace.getTracer("my-service");
const span = tracer.startSpan("operation");
try {
await riskyOperation();
} catch (err) {
const error = ErrorX.from(err);
// Get OTel-compatible attributes
const attributes = toOtelAttributes(error);
// {
// 'exception.type': 'DatabaseError',
// 'exception.message': 'Connection failed',
// 'exception.stacktrace': '...',
// 'error.code': 'DB_CONN_FAILED',
// 'error.fingerprint': 'abc123',
// 'error.chain_depth': 1,
// 'error.is_aggregate': false,
// 'error.timestamp': 1704067200000,
// 'http.status_code': 500,
// }
span.setAttributes(attributes);
span.recordException(error);
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
}
// Or use the helper function
const { attributes, applyToSpan } = recordError(error);
applyToSpan(span, { setStatus: true, recordException: true });
// Include metadata as span attributes
const attrs = toOtelAttributes(error, {
includeStack: true, // default: true
includeMetadata: true, // default: false
metadataPrefix: "app.error.", // default: 'error.metadata.'
});
// Includes: { 'app.error.userId': 123, ... }| Function | Description |
|---|---|
generateFingerprint(error, opts?) |
Generate stable hash for error deduplication |
toLogEntry(error, opts?) |
Create structured log entry for logging libraries |
toOtelAttributes(error, opts?) |
Create OpenTelemetry span attributes |
recordError(error, opts?) |
Helper to apply error info to OTel spans |
error-x includes example integrations with popular frameworks and libraries in the /examples directory:
Hono.js / Express.js - Error handling middleware with consistent JSON responses, request ID tracking, and structured logging.
// Hono.js
import { HTTPErrorX } from "@bombillazo/error-x";
app.get("/user/:id", async (c) => {
const user = await getUser(c.req.param("id"));
if (!user) {
throw HTTPErrorX.create(404, { message: "User not found" });
}
return c.json(user);
});
app.onError(errorMiddleware()); // Consistent JSON error responsesReact Error Boundaries - Error boundary components with ErrorX integration, user-friendly error display, and error tracking hooks.
<ErrorBoundary onError={(error, info) => trackError(error)}>
<App />
</ErrorBoundary>tRPC - Type-safe error handling with ErrorX-to-TRPCError conversion and custom error formatting.
GraphQL - Apollo Server and graphql-yoga integrations with consistent error extensions and user-friendly messages.
Pino / Winston - Structured error logging with fingerprinting, deduplication, and request context.
import { toLogEntry, generateFingerprint } from "@bombillazo/error-x";
const entry = toLogEntry(error, { includeStack: true });
logger.error(entry);Zod - Advanced validation patterns beyond the built-in ValidationErrorX.fromZodError().
See the /examples directory for complete integration examples with usage documentation.
MIT