npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

error-artisan

v0.1.0

Published

🎨 Type-safe, localizable error handling library for TypeScript. Craft user-friendly error messages with artisan care.

Readme

🎨 error-artisan

Type-safe, localizable error handling library for TypeScript. Craft user-friendly error messages with artisan care.

npm version npm downloads License: MIT TypeScript


✨ 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 errors
  • ValidationError - Input validation failures
  • ApiError - External API errors
  • AuthError - Authentication/authorization errors
  • NetworkError - Network connectivity issues
  • DatabaseError - Database operation failures
  • BusinessError - Business logic violations
  • ServiceError - Service layer errors
  • CacheError - Cache operation failures
  • PermissionError - 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