@ebec/core
v1.0.1
Published
Extensible error class with automatic code derivation, message interpolation, error grouping, and JSON serialization.
Maintainers
Readme
@ebec/core 🧱
Core error class library for TypeScript. Provides BaseError with automatic code derivation, message interpolation, and JSON serialization. Zero runtime dependencies.
Table of Contents
- Installation
- Quick Start
- Code Derivation
- Message Interpolation
- Wrapping Errors
- Serialization
- Type Guards
- Error Grouping
- Error Catalog
- API Reference
- License
Installation
npm install @ebec/coreQuick Start
The constructor accepts a string or an ErrorOptions object:
import { BaseError } from '@ebec/core';
// String message
const error = new BaseError('something went wrong');
// Options object
const error = new BaseError({
message: 'something went wrong',
code: 'SOMETHING_WRONG',
});
// No arguments — defaults to message "An error occurred"
const error = new BaseError();Code Derivation
When no code is provided, it is derived from the class name by converting PascalCase to CONSTANT_CASE:
const error = new BaseError();
console.log(error.code);
// "BASE_ERROR"
class NotFoundError extends BaseError {}
const notFound = new NotFoundError();
console.log(notFound.code);
// "NOT_FOUND_ERROR"An explicit code always takes priority:
const error = new BaseError({ code: 'CUSTOM_CODE' });
console.log(error.code);
// "CUSTOM_CODE"Message Interpolation
Use messageData to fill {placeholder} tokens in the message. The data is used for interpolation only and is not stored on the error instance.
const error = new BaseError({
message: 'User {id} not found in {service}',
messageData: { id: 42, service: 'auth' },
});
console.log(error.message);
// "User 42 not found in auth"Missing keys are left as-is:
const error = new BaseError({
message: 'Missing {field}',
messageData: { other: 'value' },
});
console.log(error.message);
// "Missing {field}"Wrapping Errors
Use the cause option to preserve the original error:
try {
await db.query('...');
} catch (err) {
throw new BaseError({
message: 'query failed',
code: 'DB_ERROR',
cause: err,
});
}Serialization
toJSON() returns a plain object with name, message, code, and optionally cause. If cause is a BaseError, it is serialized recursively.
const cause = new BaseError({ message: 'inner', code: 'INNER' });
const error = new BaseError({ message: 'outer', code: 'OUTER', cause });
console.log(JSON.stringify(error, null, 2));
// {
// "name": "BaseError",
// "message": "outer",
// "code": "OUTER",
// "cause": {
// "name": "BaseError",
// "message": "inner",
// "code": "INNER"
// }
// }Type Guards
import { isBaseError, isErrorWithCode } from '@ebec/core';
// Check if any value is a BaseError-shaped object
if (isBaseError(error)) {
console.log(error.code);
}
// Narrow by specific code
if (isErrorWithCode(error, 'NOT_FOUND')) {
// error.code is narrowed to 'NOT_FOUND'
}
// Match against multiple codes
if (isErrorWithCode(error, ['NOT_FOUND', 'GONE'])) {
// error.code is 'NOT_FOUND' | 'GONE'
}Error Grouping
Use the errors option to collect multiple errors into a single error:
const errors = [
new BaseError({ message: 'field "name" is required', code: 'VALIDATION' }),
new BaseError({ message: 'field "email" is invalid', code: 'VALIDATION' }),
];
throw new BaseError({
message: 'validation failed',
errors,
});Plain Error instances work too — no wrapping needed:
const results = await Promise.allSettled([taskA(), taskB(), taskC()]);
const failures = results
.filter((r) => r.status === 'rejected')
.map((r) => r.reason);
if (failures.length > 0) {
throw new BaseError({ message: 'batch operation failed', errors: failures });
}Use isBaseErrorGroup to check if an error carries grouped errors:
import { isBaseErrorGroup } from '@ebec/core';
if (isBaseErrorGroup(error)) {
for (const child of error.errors) {
console.log(child.message);
}
}toJSON() includes errors when present, serializing each child via its toJSON() method if available:
const error = new BaseError({
message: 'batch failed',
errors: [
new BaseError({ message: 'step 1', code: 'STEP_1' }),
new Error('step 2'),
],
});
console.log(JSON.stringify(error, null, 2));
// {
// "name": "BaseError",
// "message": "batch failed",
// "code": "BASE_ERROR",
// "errors": [
// { "name": "BaseError", "message": "step 1", "code": "STEP_1" },
// { "message": "step 2" }
// ]
// }Error Catalog
Define a centralized catalog of error factories with interpolation support:
import { defineErrorCatalog } from '@ebec/core';
const errors = defineErrorCatalog({
USER_NOT_FOUND: { message: 'User {id} not found' },
INVALID_INPUT: { message: 'Invalid input: {reason}', code: 'VALIDATION_ERROR' },
});
// Create errors with interpolation data
throw errors.USER_NOT_FOUND({ id: 42 });
// ^ message: "User 42 not found", code: "USER_NOT_FOUND"
// Override via second argument
throw errors.INVALID_INPUT({ reason: 'email required' }, { code: 'MISSING_EMAIL' });
// ^ message: "Invalid input: email required", code: "MISSING_EMAIL"When code is not specified in the catalog entry, the key name is used as the code.
API Reference
BaseError
class BaseError extends Error {
readonly code: string;
readonly errors?: ReadonlyArray<Error>;
cause?: unknown;
constructor(input?: string | ErrorOptions);
toJSON(): { name: string; message: string; code: string; cause?: unknown; errors?: unknown[] };
}ErrorOptions
| Property | Type | Description |
|----------|------|-------------|
| message | string | Error message. Defaults to "An error occurred". |
| code | string | Error identifier. Derived from class name if not set. |
| messageData | Record<string, unknown> | Data for {placeholder} interpolation. Not stored. |
| cause | unknown | Underlying cause of the error. |
| errors | Error[] | Collection of errors for batch/group scenarios. |
| stack | string | Override the stack trace. |
Type Guards
| Function | Returns | Description |
|----------|---------|-------------|
| isBaseError(input) | input is IBaseError | Checks for Error with string code |
| isBaseErrorGroup(input) | input is IBaseErrorGroup | Checks for isBaseError + errors array |
| isErrorWithCode(input, code) | input is IBaseError & { code: C } | Narrows code to specific value(s) |
| isError(input) | input is Error | Duck-type check for Error-shaped objects |
| isErrorOptions(input) | input is ErrorOptions | Validates options shape |
Helpers
| Function | Description |
|----------|-------------|
| sanitizeErrorCode(input) | Converts PascalCase to CONSTANT_CASE |
| extractErrorOptions(input) | Normalizes string \| ErrorOptions to ErrorOptions |
| defineErrorCatalog(definitions) | Creates typed error factory functions |
| toSerializable(input) | Converts to JSON-safe form via toJSON() or { message } fallback |
License
Made with 💚
Published under MIT License.
