error-artisan
v0.1.0
Published
🎨 Type-safe, localizable error handling library for TypeScript. Craft user-friendly error messages with artisan care.
Maintainers
Readme
🎨 error-artisan
Type-safe, localizable error handling library for TypeScript. Craft user-friendly error messages with artisan care.
✨ Features
- 🔒 Type-Safe - Full TypeScript support with strict mode
- 🌍 Localizable - Built-in formatter system with Italian & English
- 🎯 User-Friendly - Separate technical errors from user messages
- 🔌 Framework Agnostic - Works with Next.js, Express, or vanilla Node.js
- 📦 Zero Dependencies - Only optional peer dependency on Zod
- 🎨 Extensible - Easy to add custom error types and formatters
- 🚀 Production Ready - Used in production applications
📦 Installation
npm install error-artisan
# Optional: Install Zod for validation error integration
npm install zod🚀 Quick Start
import { ValidationError, ApiError, ErrorContext, ItalianFormatter } from 'error-artisan';
// Configure globally (optional)
ErrorContext.getInstance().configure({
formatter: new ItalianFormatter(),
locale: 'it',
});
// Throw type-safe errors
throw ValidationError.missingField('email');
// User sees: "Il campo email è obbligatorio."
throw ApiError.notFound('user');
// User sees: "Impossibile recuperare l'utente."
// Custom errors
throw new ValidationError(
'Invalid email format',
ErrorCode.BAD_PARAMS,
{ field: 'email', value: 'invalid@' }
);📚 Documentation
Error Types
error-artisan provides multiple error types for different scenarios:
ValidationError
For input validation failures:
import { ValidationError } from 'error-artisan';
// Static factory methods
ValidationError.missingField('username');
ValidationError.invalidEmail('email', 'not-an-email');
ValidationError.tooShort('password', 8, 5);
ValidationError.tooLong('bio', 500, 750);
// Multiple field errors
ValidationError.fromFieldErrors([
{ field: 'email', message: 'Invalid format' },
{ field: 'password', message: 'Too short' },
]);
// Custom validation error
new ValidationError(
'Custom validation error',
ErrorCode.VALIDATION_FAILED,
{ field: 'customField', constraint: 'custom' }
);ApiError
For external API failures:
import { ApiError } from 'error-artisan';
// Static factory methods
ApiError.notFound('resource');
ApiError.badRequest('Invalid request parameters');
ApiError.unauthorized();
ApiError.forbidden();
ApiError.conflict('Resource already exists');
ApiError.tooManyRequests();
// Create from HTTP Response
const response = await fetch('/api/users');
if (!response.ok) {
const error = await ApiError.fromResponse(response, '/api/users', 'GET');
throw error;
}AuthError
For authentication/authorization failures:
import { AuthError } from 'error-artisan';
// Static factory methods
AuthError.invalidCredentials();
AuthError.tokenExpired('access');
AuthError.noRefreshToken();
AuthError.insufficientPermissions('admin_access');
AuthError.notAuthenticated('view dashboard');NetworkError
For network connectivity issues:
import { NetworkError } from 'error-artisan';
NetworkError.timeout(30000);
NetworkError.connectionRefused('https://api.example.com');
NetworkError.dnsLookupFailed('api.example.com');
// From fetch errors
try {
await fetch('https://api.example.com');
} catch (error) {
throw NetworkError.fromFetchError(error, 'https://api.example.com');
}DatabaseError
For database operation failures:
import { DatabaseError } from 'error-artisan';
DatabaseError.connectionFailed();
DatabaseError.queryFailed('SELECT * FROM users');
DatabaseError.uniqueConstraint('email');
DatabaseError.foreignKeyViolation('user_id');BusinessError
For business logic violations:
import { BusinessError } from 'error-artisan';
new BusinessError(
'User limit exceeded',
ErrorCode.LIMIT_EXCEEDED,
{ currentUsers: 100, maxUsers: 100 }
);🌍 Localization with Formatters
error-artisan includes a powerful formatter system for localization:
Using the Italian Formatter
import { ErrorContext, ItalianFormatter } from 'error-artisan';
// Configure globally
ErrorContext.getInstance().configure({
formatter: new ItalianFormatter(),
locale: 'it',
});
// Now all errors will use Italian messages
throw ValidationError.missingField('email');
// Output: "Il campo email è obbligatorio."
throw ApiError.notFound('user');
// Output: "Impossibile recuperare l'utente."Creating a Custom Formatter
import { MessageFormatter } from 'error-artisan';
class SpanishFormatter implements MessageFormatter {
translateEntity(entity: string): string {
const translations: Record<string, string> = {
user: 'El usuario',
product: 'El producto',
// ... more translations
};
return translations[entity.toLowerCase()] || entity;
}
translateField(field: string): string {
const translations: Record<string, string> = {
email: 'correo electrónico',
password: 'contraseña',
// ... more translations
};
return translations[field.toLowerCase()] || field;
}
getOperationMessage(operation: string, entity: string): string {
const entityName = this.translateEntity(entity);
switch (operation) {
case 'get':
return `No se pudo obtener ${entityName}.`;
case 'create':
return `No se pudo crear ${entityName}.`;
// ... more operations
default:
return `Operación falló para ${entityName}.`;
}
}
getValidationMessage(field: string, constraint: string): string {
const fieldName = this.translateField(field);
if (constraint === 'required') {
return `El campo ${fieldName} es obligatorio.`;
}
if (constraint === 'email') {
return `Ingrese un correo electrónico válido.`;
}
// ... more constraints
return `El campo ${fieldName} no es válido.`;
}
// Implement other required methods...
}
// Use your custom formatter
ErrorContext.getInstance().configure({
formatter: new SpanishFormatter(),
locale: 'es',
});🔌 Framework Integrations
Next.js Server Actions
error-artisan provides seamless integration with Next.js Server Actions:
// app/actions/users.ts
'use server';
import { handleServerActionError } from 'error-artisan/integrations/nextjs';
import { ValidationError } from 'error-artisan';
export async function createUser(data: FormData) {
try {
const email = data.get('email') as string;
if (!email) {
throw ValidationError.missingField('email');
}
// Your logic here
const user = await db.user.create({ data: { email } });
return { success: true, data: user };
} catch (error) {
// Automatically converts errors to user-friendly format
return handleServerActionError(error);
}
}Client-side handling:
'use client';
import { useFormState } from 'react-dom';
import { createUser } from './actions/users';
export function UserForm() {
const [state, formAction] = useFormState(createUser, null);
return (
<form action={formAction}>
<input name="email" type="email" />
{state?.error && (
<p className="error">{state.error.message}</p>
)}
<button type="submit">Create</button>
</form>
);
}🎨 Advanced Usage
Error Context Configuration
import { ErrorContext } from 'error-artisan';
ErrorContext.getInstance().configure({
formatter: new ItalianFormatter(),
locale: 'it',
logLevel: 'error', // 'debug' | 'info' | 'warn' | 'error'
includeStackInProduction: false,
fallbackToDefault: true,
});Custom Error Logger
import { ErrorLogger, BaseError } from 'error-artisan';
class CustomLogger implements ErrorLogger {
error(error: BaseError): void {
// Send to your logging service (Sentry, Datadog, etc.)
console.error('[CUSTOM LOGGER]', error.toLogObject());
}
warn(error: BaseError): void {
console.warn('[CUSTOM LOGGER]', error.toLogObject());
}
}
const logger = new CustomLogger();Error Serialization
import { ApiError } from 'error-artisan';
const error = ApiError.notFound('user');
// Serialize for API responses
const serialized = error.serialize();
console.log(serialized);
// {
// message: "User not found",
// type: "API_ERROR",
// code: 300,
// httpStatus: 404,
// timestamp: "2025-01-08T10:30:00.000Z",
// details: { resource: "user" }
// }
// Convert to JSON
const json = JSON.stringify(error);
// Get log-friendly object
const logObj = error.toLogObject();🧪 Testing
import { describe, it, expect } from 'vitest';
import { ValidationError, ErrorCode } from 'error-artisan';
describe('ValidationError', () => {
it('should create missing field error', () => {
const error = ValidationError.missingField('email');
expect(error.type).toBe('VALIDATION_ERROR');
expect(error.code).toBe(ErrorCode.MISSING_PARAMS);
expect(error.details?.field).toBe('email');
});
});📖 API Reference
Error Classes
BaseError- Abstract base class for all errorsValidationError- Input validation failuresApiError- External API errorsAuthError- Authentication/authorization errorsNetworkError- Network connectivity issuesDatabaseError- Database operation failuresBusinessError- Business logic violationsServiceError- Service layer errorsCacheError- Cache operation failuresPermissionError- Permission-related errors
Error Codes
import { ErrorCode, ErrorType, HttpStatus } from 'error-artisan';
// Application error codes (100-999)
ErrorCode.MISSING_PARAMS // 100
ErrorCode.BAD_PARAMS // 101
ErrorCode.VALIDATION_FAILED // 102
ErrorCode.UNAUTHORIZED // 200
// ... many more
// HTTP status codes
HttpStatus.BAD_REQUEST // 400
HttpStatus.UNAUTHORIZED // 401
HttpStatus.NOT_FOUND // 404
// ... all standard HTTP codes
// Error types
ErrorType.VALIDATION_ERROR
ErrorType.API_ERROR
ErrorType.AUTH_ERROR
// ... all error categories🤝 Contributing
Contributions are welcome! Please read our Contributing Guide for details.
Development Setup
git clone https://github.com/Vorshim92/error-artisan.git
cd error-artisan
npm install
npm run dev # Start development build
npm test # Run tests📄 License
MIT © Stefano Scalfari
🙏 Acknowledgments
- Inspired by best practices in error handling across multiple frameworks
- Built with TypeScript and modern tooling
- Designed for developer experience and user satisfaction
📞 Support
Made with ❤️ by Stefano Scalfari
