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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@desarrollo-reino/errors

v1.0.3

Published

Paquete compartido para manejo consistente de errores en aplicaciones NestJS y React/NextJS

Readme

@reino/errors

Paquete compartido para manejo consistente de errores en aplicaciones NestJS y React/NextJS con arquitectura modular y separación de responsabilidades.

🎯 Ventajas

  • Consistencia: Mismo manejo de errores en backend y frontend
  • Escalabilidad: Fácil de extender con nuevos tipos de error
  • Multiidioma: Soporte para español e inglés (extensible)
  • TypeScript: Tipado completo para mejor DX
  • Principios SOLID: Código mantenible y extensible
  • Fácil mantenimiento: Diseñado para que juniors puedan trabajar sin problemas
  • Arquitectura modular: Separación clara de responsabilidades
  • Type Safety: Sin strings hardcodeados, solo constantes tipadas

📦 Instalación

npm install @reino/errors
# o
yarn add @reino/errors

🏗️ Nueva Estructura Modular

@reino/errors/
├── src/
│   ├── types/                    # 📁 Tipos TypeScript
│   │   ├── index.ts             # Exportación principal de tipos
│   │   ├── error-codes.types.ts # Tipos para códigos de error
│   │   ├── error-details.types.ts # Tipos para detalles de error
│   │   └── messages.types.ts    # Tipos para mensajes y localización
│   │
│   ├── constants/               # 📁 Constantes y configuraciones
│   │   ├── index.ts            # Exportación principal de constantes
│   │   ├── error-codes.constants.ts # Códigos de error organizados
│   │   └── messages.constants.ts # Mensajes localizados
│   │
│   ├── utils/                   # 📁 Utilidades y funciones puras
│   │   ├── index.ts            # Exportación principal de utils
│   │   └── message-utils.ts    # Utilidades para mensajes
│   │
│   ├── errors/                  # 📁 Clases de error especializadas
│   │   ├── index.ts            # Exportación principal de errores
│   │   ├── base/
│   │   │   └── app-error.ts    # Clase base AppError
│   │   ├── auth/
│   │   │   └── auth-error.ts   # Errores de autenticación
│   │   ├── validation/
│   │   │   └── validation-error.ts # Errores de validación
│   │   ├── business/
│   │   │   └── business-error.ts # Errores de negocio
│   │   └── server/
│   │       └── server-error.ts # Errores del servidor
│   │
│   ├── factories/               # 📁 Factories para creación de errores
│   │   ├── index.ts            # Exportación principal de factories
│   │   └── error-factory.ts    # Factory principal de errores
│   │
│   └── index.ts                # 🚀 Punto de entrada principal
│
├── examples/
│   └── error-examples.ts       # Ejemplos de uso prácticos
├── package.json
├── tsconfig.json
└── README.md

🔧 Uso Básico Actualizado

Importar constantes y clases (Recomendado)

import {
  // Clases de error
  AppError,
  ValidationError,
  AuthError,
  BusinessError,
  ServerError,

  // Factory
  ErrorFactory,

  // Constantes organizadas por categoría
  AUTH_ERROR_CODES,
  VALIDATION_ERROR_CODES,
  BUSINESS_ERROR_CODES,
  SERVER_ERROR_CODES,

  // Constante general (retrocompatibilidad)
  ERROR_CODES,

  // Utilidades
  getErrorMessage,
} from "@reino/errors";

✅ Crear errores usando constantes (Type Safe)

// ✅ RECOMENDADO: Usando constantes tipadas
const authError = new AuthError(AUTH_ERROR_CODES.UNAUTHORIZED);
const validationError = new ValidationError(
  VALIDATION_ERROR_CODES.REQUIRED_FIELD,
  { field: "email" }
);
const businessError = new BusinessError(
  BUSINESS_ERROR_CODES.PRODUCT_NOT_FOUND,
  404,
  { resourceId: "123" }
);

// ❌ EVITAR: Strings hardcodeados
const badError = new AppError("AUTH_UNAUTHORIZED", 401); // No type safe!

Usar métodos estáticos (aún más fácil)

// Validación
const emailRequired = ValidationError.fieldRequired("email");
const invalidFormat = ValidationError.invalidFormat("phone", "+123");
const weakPassword = ValidationError.weakPassword();

// Autenticación
const unauthorized = AuthError.unauthorized();
const tokenExpired = AuthError.tokenExpired();
const forbidden = AuthError.forbidden("delete_user");

// Negocio
const productNotFound = BusinessError.productNotFound("PROD-123");
const userNotFound = BusinessError.userNotFound("USER-456");
const stockError = BusinessError.insufficientStock("PROD-123", 5, 2);

// Servidor
const internalError = ServerError.internal();
const dbError = ServerError.database("user_query", "users", "SELECT");
const networkError = ServerError.network("https://api.example.com");

ErrorFactory con nuevas funcionalidades

// Crear automáticamente el tipo correcto
const error = ErrorFactory.create(BUSINESS_ERROR_CODES.PRODUCT_NOT_FOUND, {
  resourceId: "PROD-123",
});

// Crear desde respuesta de API
const apiError = ErrorFactory.fromApiResponse({
  error: {
    code: "BUSINESS_PRODUCT_NOT_FOUND",
    message: "Product not found",
    statusCode: 404,
  },
});

// Crear múltiples errores de validación
const fieldErrors = ErrorFactory.createValidationErrors({
  email: VALIDATION_ERROR_CODES.INVALID_EMAIL,
  password: VALIDATION_ERROR_CODES.PASSWORD_TOO_WEAK,
});

// Agrupar errores por categoría
const groupedErrors = ErrorFactory.groupErrorsByCategory([
  authError,
  validationError,
  businessError,
]);
console.log(groupedErrors);
// {
//   auth: [authError],
//   validation: [validationError],
//   business: [businessError]
// }

🎯 Uso en NestJS (Backend) - Actualizado

Exception Filter Mejorado

// common/filters/app-exception.filter.ts
import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  Logger,
} from "@nestjs/common";
import { Request, Response } from "express";
import { AppError, ErrorFactory } from "@reino/errors";

@Catch()
export class AppExceptionFilter implements ExceptionFilter {
  private readonly logger = new Logger(AppExceptionFilter.name);

  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();

    let appError: AppError;

    if (exception instanceof AppError) {
      appError = exception;
    } else if (exception instanceof HttpException) {
      appError = ErrorFactory.fromUnknown(exception);
    } else {
      appError = ErrorFactory.fromUnknown(exception);
    }

    // Logging estructurado
    this.logger.error("Error occurred", {
      error: appError.toJSON(),
      url: request.url,
      method: request.method,
      ip: request.ip,
      userAgent: request.get("User-Agent"),
    });

    // Respuesta consistente usando factory
    const errorResponse = ErrorFactory.toApiResponse(appError);

    response.status(appError.statusCode).json({
      ...errorResponse.error,
      path: request.url,
      method: request.method,
      timestamp: new Date().toISOString(),
    });
  }
}

En un Service con constantes

// products/products.service.ts
import { Injectable, Logger } from "@nestjs/common";
import {
  BusinessError,
  ValidationError,
  ServerError,
  BUSINESS_ERROR_CODES,
  VALIDATION_ERROR_CODES,
} from "@reino/errors";

@Injectable()
export class ProductsService {
  private readonly logger = new Logger(ProductsService.name);

  async findById(id: string) {
    if (!id) {
      // ✅ Usando constante tipada
      throw ValidationError.fieldRequired("id");
    }

    if (id.length < 3) {
      // ✅ Usando constante tipada
      throw new ValidationError(VALIDATION_ERROR_CODES.FIELD_TOO_SHORT, {
        field: "id",
        minLength: 3,
        currentLength: id.length,
      });
    }

    try {
      const product = await this.productRepository.findById(id);

      if (!product) {
        // ✅ Usando método estático
        throw BusinessError.productNotFound(id);
      }

      return product;
    } catch (error) {
      if (error instanceof AppError) {
        throw error; // Re-lanzar errores de aplicación
      }

      // Convertir errores de BD a errores consistentes
      this.logger.error("Database error in findById", { error, productId: id });
      throw ServerError.database("findById", "products", "SELECT");
    }
  }

  async updateStock(productId: string, quantity: number) {
    const product = await this.findById(productId);

    if (product.stock < quantity) {
      // ✅ Usando método estático con detalles específicos
      throw BusinessError.insufficientStock(productId, quantity, product.stock);
    }

    // Lógica de actualización...
    return this.productRepository.updateStock(productId, quantity);
  }
}

⚛️ Uso en React/NextJS (Frontend) - Actualizado

Hook mejorado para manejo de errores

// hooks/useErrorHandler.ts
import { useCallback } from "react";
import { useNavigate } from "react-router-dom";
import {
  AppError,
  ErrorFactory,
  BusinessError,
  AuthError,
  ValidationError,
  // ✅ Importar constantes para comparaciones type-safe
  AUTH_ERROR_CODES,
  BUSINESS_ERROR_CODES,
  VALIDATION_ERROR_CODES,
  SERVER_ERROR_CODES,
} from "@reino/errors";
import { toast } from "react-hot-toast";

export const useErrorHandler = () => {
  const navigate = useNavigate();

  const handleError = useCallback(
    (error: unknown) => {
      const appError = ErrorFactory.isAppError(error)
        ? error
        : ErrorFactory.fromUnknown(error);

      // ✅ Manejo específico usando constantes tipadas
      switch (appError.code) {
        case AUTH_ERROR_CODES.UNAUTHORIZED:
        case AUTH_ERROR_CODES.TOKEN_EXPIRED:
          toast.error("Sesión expirada. Redirigiendo...");
          navigate("/login");
          break;

        case AUTH_ERROR_CODES.FORBIDDEN:
          toast.error("No tienes permisos para esta acción");
          break;

        case BUSINESS_ERROR_CODES.PRODUCT_NOT_FOUND:
          toast.error("Producto no encontrado");
          if (location.pathname.includes("/products/")) {
            navigate("/products");
          }
          break;

        case BUSINESS_ERROR_CODES.INSUFFICIENT_STOCK:
          const stockError = appError as BusinessError;
          toast.error(
            `Stock insuficiente. Disponible: ${stockError.details?.availableQuantity}`
          );
          break;

        case VALIDATION_ERROR_CODES.REQUIRED_FIELD:
          toast.error(
            `Campo requerido: ${(appError as ValidationError).getFieldName()}`
          );
          break;

        case SERVER_ERROR_CODES.NETWORK_ERROR:
          toast.error("Error de conexión. Verifica tu internet.");
          break;

        default:
          toast.error(appError.message);
      }

      // Log para debugging (solo en desarrollo)
      if (process.env.NODE_ENV === "development") {
        console.group("🐛 Error Details");
        console.error("Error:", appError.toJSON());
        console.groupEnd();
      }

      return appError;
    },
    [navigate]
  );

  return { handleError };
};

Componente con manejo type-safe

// components/ProductDetail.tsx
import React, { useEffect, useState } from "react";
import { useErrorHandler } from "../hooks/useErrorHandler";
import {
  BusinessError,
  BUSINESS_ERROR_CODES, // ✅ Importar constantes
  SERVER_ERROR_CODES,
} from "@reino/errors";
import api from "../services/api";

interface Product {
  id: string;
  name: string;
  stock: number;
  price: number;
}

export const ProductDetail: React.FC<{ productId: string }> = ({
  productId,
}) => {
  const [product, setProduct] = useState<Product | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const { handleError } = useErrorHandler();

  useEffect(() => {
    const fetchProduct = async () => {
      try {
        setLoading(true);
        setError(null);

        const response = await api.get(`/products/${productId}`);
        setProduct(response.data);
      } catch (error) {
        const appError = handleError(error);

        // ✅ Manejo específico usando constantes (Type Safe!)
        if (appError instanceof BusinessError) {
          switch (appError.code) {
            case BUSINESS_ERROR_CODES.PRODUCT_NOT_FOUND:
              setError("Producto no encontrado");
              setProduct(null);
              break;

            case BUSINESS_ERROR_CODES.INSUFFICIENT_STOCK:
              setError("Producto sin stock");
              break;

            default:
              setError("Error de negocio desconocido");
          }
        } else if (appError.code === SERVER_ERROR_CODES.NETWORK_ERROR) {
          setError("Error de conexión. Intenta nuevamente.");
        } else {
          setError("Ha ocurrido un error inesperado");
        }
      } finally {
        setLoading(false);
      }
    };

    if (productId) {
      fetchProduct();
    }
  }, [productId, handleError]);

  if (loading) return <div className="loading">Cargando producto...</div>;

  if (error) return <div className="error">{error}</div>;

  if (!product) return <div className="not-found">Producto no encontrado</div>;

  return (
    <div className="product-detail">
      <h1>{product.name}</h1>
      <p>Precio: ${product.price}</p>
      <p>Stock: {product.stock}</p>
    </div>
  );
};

API Client mejorado

// services/api.ts
import axios from "axios";
import {
  ErrorFactory,
  SERVER_ERROR_CODES,
  AUTH_ERROR_CODES,
} from "@reino/errors";

const api = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_URL,
  timeout: 10000,
});

// Request interceptor
api.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem("auth_token");
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => Promise.reject(ErrorFactory.fromUnknown(error))
);

// Response interceptor mejorado
api.interceptors.response.use(
  (response) => response,
  (error) => {
    // Si el backend devuelve errores en formato estándar
    if (error.response?.data?.error) {
      const appError = ErrorFactory.fromApiResponse(error.response.data);

      // Manejo automático de tokens expirados
      if (appError.code === AUTH_ERROR_CODES.TOKEN_EXPIRED) {
        localStorage.removeItem("auth_token");
        window.location.href = "/login";
      }

      return Promise.reject(appError);
    }

    // Error de red o timeout
    if (error.code === "NETWORK_ERROR" || error.code === "ECONNABORTED") {
      const networkError = ErrorFactory.create(
        SERVER_ERROR_CODES.NETWORK_ERROR
      );
      return Promise.reject(networkError);
    }

    // Cualquier otro error
    return Promise.reject(ErrorFactory.fromUnknown(error));
  }
);

export default api;

🌍 Soporte Multiidioma Mejorado

import {
  getErrorMessage,
  normalizeLanguage,
  hasErrorMessage,
  getAllMessages,
  getLocalizationInfo,
  BUSINESS_ERROR_CODES,
} from "@reino/errors";

// Funciones avanzadas de localización
const userLanguage = "en-US";
const normalizedLang = normalizeLanguage(userLanguage); // 'en'

// Verificar si existe mensaje
if (hasErrorMessage(BUSINESS_ERROR_CODES.PRODUCT_NOT_FOUND, "fr")) {
  // Solo mostrar si existe traducción
}

// Obtener todos los mensajes para un idioma
const allSpanishMessages = getAllMessages("es");

// Información sobre idiomas soportados
const localizationInfo = getLocalizationInfo();
console.log(localizationInfo);
// {
//   defaultLanguage: 'es',
//   fallbackLanguage: 'es',
//   supportedLanguages: ['es', 'en'],
//   availableLanguages: ['es', 'en']
// }

// Error con idioma específico
const englishError = new BusinessError(
  BUSINESS_ERROR_CODES.PRODUCT_NOT_FOUND,
  404,
  { resourceId: "PROD-123" },
  "en"
);

// Cambiar idioma dinámicamente
const spanishVersion = englishError.withLanguage("es");

🔧 Extensión del Paquete (Actualizada)

1. Agregar nuevos tipos

// src/types/error-codes.types.ts
export type PaymentErrorCode =
  | "PAYMENT_FAILED"
  | "PAYMENT_DECLINED"
  | "PAYMENT_TIMEOUT";

// Actualizar el tipo principal
export type ErrorCode =
  | AuthErrorCode
  | ValidationErrorCode
  | BusinessErrorCode
  | ServerErrorCode
  | PaymentErrorCode; // ✅ Agregar nueva categoría

2. Agregar constantes

// src/constants/error-codes.constants.ts
export const PAYMENT_ERROR_CODES: Record<string, PaymentErrorCode> = {
  FAILED: "PAYMENT_FAILED",
  DECLINED: "PAYMENT_DECLINED",
  TIMEOUT: "PAYMENT_TIMEOUT",
} as const;

// Actualizar ERROR_CODES principal
export const ERROR_CODES = {
  ...AUTH_ERROR_CODES,
  ...VALIDATION_ERROR_CODES,
  ...BUSINESS_ERROR_CODES,
  ...SERVER_ERROR_CODES,
  ...PAYMENT_ERROR_CODES, // ✅ Incluir nuevos códigos
} as const;

// Actualizar mapeo de categorías
export const ERROR_CODE_CATEGORIES = {
  // ... códigos existentes
  [PAYMENT_ERROR_CODES.FAILED]: "payment",
  [PAYMENT_ERROR_CODES.DECLINED]: "payment",
  [PAYMENT_ERROR_CODES.TIMEOUT]: "payment",
} as const;

3. Agregar mensajes

// src/constants/messages.constants.ts
export const SPANISH_MESSAGES: ErrorMessageMap = {
  // ... mensajes existentes
  PAYMENT_FAILED: "El pago no pudo ser procesado.",
  PAYMENT_DECLINED: "El pago fue rechazado.",
  PAYMENT_TIMEOUT: "El pago expiró por tiempo de espera.",
};

export const ENGLISH_MESSAGES: ErrorMessageMap = {
  // ... mensajes existentes
  PAYMENT_FAILED: "Payment could not be processed.",
  PAYMENT_DECLINED: "Payment was declined.",
  PAYMENT_TIMEOUT: "Payment timed out.",
};

4. Crear nueva clase especializada

// src/errors/payment/payment-error.ts
import { AppError } from "../base/app-error";
import type { PaymentErrorCode } from "../../types/error-codes.types";
import type { ErrorDetails } from "../../types/error-details.types";
import type { SupportedLanguage } from "../../types/messages.types";
import { PAYMENT_ERROR_CODES } from "../../constants/error-codes.constants";

export class PaymentError extends AppError {
  constructor(
    code: PaymentErrorCode,
    details?: ErrorDetails,
    language: SupportedLanguage = "es"
  ) {
    super(code, 402, details, language);
    this.name = "PaymentError";
  }

  static failed(
    paymentMethod: string,
    amount: number,
    reason?: string,
    language: SupportedLanguage = "es"
  ): PaymentError {
    return new PaymentError(
      PAYMENT_ERROR_CODES.FAILED,
      {
        context: "Payment processing failed",
        paymentMethod,
        amount,
        reason,
      },
      language
    );
  }

  static declined(
    cardLast4: string,
    language: SupportedLanguage = "es"
  ): PaymentError {
    return new PaymentError(
      PAYMENT_ERROR_CODES.DECLINED,
      {
        context: "Payment declined by bank",
        cardLast4,
      },
      language
    );
  }
}

5. Actualizar el factory

// src/factories/error-factory.ts
import { PaymentError } from "../errors/payment/payment-error";

export class ErrorFactory {
  static create(
    code: ErrorCode,
    details?: ErrorDetails,
    language: SupportedLanguage = "es"
  ): AppError {
    const category = ERROR_CODE_CATEGORIES[code];

    switch (category) {
      case "auth":
        return new AuthError(code as any, details as any, language);
      case "validation":
        return new ValidationError(code as any, details as any, language);
      case "business":
        return new BusinessError(code as any, 422, details as any, language);
      case "server":
        return new ServerError(code as any, details as any, language);
      case "payment": // ✅ Nueva categoría
        return new PaymentError(code as any, details, language);
      default:
        return new AppError(code, 400, details, language);
    }
  }
}

📝 Ejemplos de Respuesta (Actualizados)

Respuesta exitosa con nueva estructura

{
  "success": false,
  "error": {
    "code": "BUSINESS_PRODUCT_NOT_FOUND",
    "message": "El producto solicitado no fue encontrado.",
    "statusCode": 404,
    "details": {
      "context": "product not found",
      "resourceType": "product",
      "resourceId": "PROD-123"
    },
    "timestamp": "2024-01-15T10:30:00.000Z",
    "path": "/products/PROD-123",
    "method": "GET"
  }
}

Error de validación múltiple

{
  "success": false,
  "error": {
    "code": "VALIDATION_REQUIRED_FIELD",
    "message": "Este campo es obligatorio.",
    "statusCode": 400,
    "details": {
      "field": "email",
      "context": "Field validation failed"
    },
    "timestamp": "2024-01-15T10:30:00.000Z",
    "additionalErrors": [
      {
        "code": "VALIDATION_PASSWORD_TOO_WEAK",
        "message": "La contraseña debe tener al menos 8 caracteres...",
        "statusCode": 400,
        "details": {
          "field": "password",
          "expectedFormat": "At least 8 characters..."
        }
      }
    ]
  }
}

🚀 Comandos Útiles

# Desarrollo
npm run build          # Compilar TypeScript
npm run dev            # Compilar en modo watch

# Publicación
npm run build          # Asegurar build limpio
npm publish --access restricted

# En tus proyectos
npm install @reino/errors@latest
npm update @reino/errors

🎯 Mejores Prácticas

Hacer

// ✅ Usar constantes tipadas
if (error.code === BUSINESS_ERROR_CODES.PRODUCT_NOT_FOUND) {
}

// ✅ Usar métodos estáticos cuando sea posible
throw BusinessError.productNotFound(productId);

// ✅ Usar ErrorFactory para casos genéricos
const error = ErrorFactory.create(code, details);

// ✅ Agrupar imports por categoría
import {
  BUSINESS_ERROR_CODES,
  BusinessError,
  ErrorFactory,
} from "@reino/errors";

Evitar

// ❌ Strings hardcodeados
if (error.code === "BUSINESS_PRODUCT_NOT_FOUND") {
}

// ❌ Crear errores manualmente sin Factory
const error = new AppError("SOME_CODE", 400);

// ❌ No manejar categorías específicas
// Siempre usar el tipo más específico disponible

📋 Principios SOLID Aplicados

  • S (Single Responsibility): Cada archivo tiene una responsabilidad específica

    • /types - Solo definiciones de tipos
    • /constants - Solo constantes y configuraciones
    • /utils - Solo funciones puras
    • /errors - Solo clases de error
    • /factories - Solo lógica de creación
  • O (Open/Closed): Fácil de extender sin modificar código existente

    • Agregar nuevas categorías sin tocar las existentes
    • Nuevas clases de error heredan de AppError
  • L (Liskov Substitution): Todas las clases de error son intercambiables

    • Cualquier clase de error puede usarse como AppError
  • I (Interface Segregation): Interfaces específicas y no sobrecargadas

    • Tipos separados por responsabilidad
    • Detalles específicos por tipo de error
  • D (Dependency Inversion): Depende de abstracciones, no implementaciones

    • ErrorFactory abstrae la creación de errores
    • Interfaces tipadas en lugar de implementaciones concretas

¡La estructura modular está lista! 🎉

Este paquete ahora proporciona una arquitectura robusta, type-safe y escalable para manejo de errores con separación clara de responsabilidades y principios SOLID.