Getting Started
Get started with @deessejs/errors, a TypeScript error handling library with exception chaining and hierarchical inheritance.
@deessejs/errors is a TypeScript library that reimagines error handling in JavaScript. Inspired by Python's exception system, it provides exception chaining, hierarchical inheritance, and rich error semantics through a function-based API.
This library is particularly useful when you need to:
- Preserve the full context of errors across async boundaries and service calls
- Organize errors in meaningful hierarchies that reflect your domain
- Attach structured data and metadata to errors for better debugging
Quick Start
The quickest way to understand how @deessejs/errors works is to see it in action. Here's a basic example that demonstrates creating typed errors and chaining them together.
import { error, raise } from '@deessejs/errors';
import { z } from 'zod';
// Define your error type. With a Standard Schema attached,
// every call validates the input and produces typed `fields`.
const ValidationError = error({
name: 'ValidationError',
fields: z.object({ field: z.string() }),
message: (d) => `Validation failed for ${d.field}`,
});
// Use the factory to create error instances.
const validationErr = ValidationError({ field: 'email' });
console.log(validationErr.message); // "Validation failed for email"
console.log(validationErr.fields.field); // "email"For catching and handling errors, you can use the standard try/catch syntax or the library's raise() function:
import { error, raise } from '@deessejs/errors';
import { z } from 'zod';
const ValidationError = error({
name: 'ValidationError',
fields: z.object({ field: z.string() }),
message: (d) => `Validation failed for ${d.field}`,
});
try {
raise(ValidationError({ field: 'email' }));
} catch (err) {
// `err` is `unknown` in strict mode; narrow before reading.
if (err instanceof Error) {
console.log(err.name); // "ValidationError"
console.log(err instanceof Error); // true
}
}Key Features
Exception Chaining
When an error occurs as a result of another error, you can preserve that relationship using the .from() method. This creates a chain that maintains the full history of what went wrong.
import { causes, error } from '@deessejs/errors';
import { z } from 'zod';
const ValidationError = error({
name: 'ValidationError',
fields: z.object({ field: z.string() }),
message: (d) => `Validation failed for ${d.field}`,
});
const AppError = error({ name: 'AppError' });
const validationErr = ValidationError({ field: 'email' });
const appErr = AppError();
// Chain the validation error as the cause.
appErr.from(validationErr);
console.log(appErr.cause === validationErr); // true
// The full chain is reachable via the causes() helper.
console.log(causes(appErr).length); // 1Hierarchical Inheritance
Errors can inherit from other errors, creating hierarchies that are useful for organization and type checking. A child error can check against its parent types.
import { error, is } from '@deessejs/errors';
const AppError = error({ name: 'AppError' });
const ValidationError = error({
name: 'ValidationError',
inherits: AppError,
});
// The no-schema factories have an empty input shape; the
// call takes no arguments.
const err = ValidationError();
// Check the error type.
console.log(is(err, ValidationError)); // true
console.log(is(err, AppError)); // true (through inheritance)Message Templates
You can define errors with function-form messages that read the schema's post-transform output. The message function receives the validated fields, so the rendered string is type-checked against the schema.
import { error } from '@deessejs/errors';
import { z } from 'zod';
const UserError = error({
name: 'UserError',
fields: z.object({
userId: z.string(),
reason: z.string(),
}),
message: (d) => `User "${d.userId}" failed: ${d.reason}`,
});
const err = UserError({ userId: 'usr_123', reason: 'not found' });
console.log(err.message); // 'User "usr_123" failed: not found'