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

@beethovn/errors

v1.0.0

Published

Standardized error types for the Beethovn platform

Readme

@beethovn/errors

Standardized error types for the Beethovn platform. Provides structured, typed errors with rich metadata for better debugging, logging, and monitoring.

Installation

pnpm add @beethovn/errors

Quick Start

import { ValidationError, ErrorFactory, isDomainError } from '@beethovn/errors';

// Throw typed errors
if (!email.includes('@')) {
  throw new ValidationError('Invalid email format', 'email');
}

// Handle unknown errors
try {
  await riskyOperation();
} catch (error: unknown) {
  const beethovnError = ErrorFactory.fromUnknown(error, 'riskyOperation');
  logger.error('Operation failed', beethovnError.toJSON());
  
  if (isDomainError(error)) {
    // User-fixable error
    return { error: beethovnError.toClient() };
  }
}

Error Hierarchy

BeethovnError (base)
├── DomainError (business logic)
│   ├── ValidationError
│   ├── BusinessRuleError
│   ├── NotFoundError
│   ├── ConflictError
│   └── InvalidStateError
├── InfrastructureError (technical)
│   ├── DatabaseError
│   ├── NetworkError
│   ├── ExternalServiceError
│   ├── TimeoutError
│   ├── RateLimitError
│   └── UnknownError
├── AuthError (authentication/authorization)
│   ├── InvalidCredentialsError
│   ├── TokenExpiredError
│   ├── InvalidTokenError
│   ├── PermissionDeniedError
│   ├── ForbiddenError
│   └── UnauthenticatedError
└── IntegrationError (inter-service communication)
    ├── EventPublishError
    ├── MessageQueueError
    ├── WebSocketError
    └── EventSubscriptionError

Usage Examples

Domain Errors

import {
  ValidationError,
  BusinessRuleError,
  NotFoundError,
  ConflictError,
} from '@beethovn/errors';

// Validation error
if (!email.includes('@')) {
  throw new ValidationError('Invalid email format', 'email', {
    value: email,
  });
}

// Business rule violation
if (balance < amount) {
  throw new BusinessRuleError(
    'Insufficient balance',
    'MINIMUM_BALANCE',
    { balance, required: amount }
  );
}

// Not found
const user = await db.getUser(id);
if (!user) {
  throw new NotFoundError('User', id, { tenantId });
}

// Conflict
if (existingUser) {
  throw new ConflictError(
    'User with this email already exists',
    { email }
  );
}

Infrastructure Errors

import {
  DatabaseError,
  NetworkError,
  ExternalServiceError,
  TimeoutError,
} from '@beethovn/errors';

// Database error
try {
  await db.query(sql, params);
} catch (error: unknown) {
  throw new DatabaseError('saveUser', error, { userId });
}

// Network error
try {
  const response = await fetch(url);
} catch (error: unknown) {
  throw new NetworkError('Failed to fetch user data', { url, method: 'GET' });
}

// External service error
try {
  await stripeClient.charge(amount);
} catch (error: unknown) {
  throw new ExternalServiceError(
    'Stripe',
    'createCharge',
    error,
    { amount, currency }
  );
}

// Timeout error
throw new TimeoutError('Database query', 5000, { query: sql });

Auth Errors

import {
  InvalidCredentialsError,
  TokenExpiredError,
  PermissionDeniedError,
} from '@beethovn/errors';

// Invalid credentials
const user = await verifyCredentials(email, password);
if (!user) {
  throw new InvalidCredentialsError({ email });
}

// Token expired
if (token.exp < Date.now()) {
  throw new TokenExpiredError({ userId: token.sub });
}

// Permission denied
if (!user.permissions.includes('delete:product')) {
  throw new PermissionDeniedError('Product', 'delete', {
    userId: user.id,
    requiredPermission: 'delete:product',
  });
}

Error Factory

import { ErrorFactory } from '@beethovn/errors';

// Convert unknown errors
try {
  await operation();
} catch (error: unknown) {
  throw ErrorFactory.fromUnknown(error, 'operationContext');
}

// Create database error
throw ErrorFactory.database('saveUser', error, { userId });

// Create validation error
throw ErrorFactory.validation('Invalid email', 'email');

// Aggregate multiple errors
const errors = [
  new ValidationError('Invalid email', 'email'),
  new ValidationError('Invalid age', 'age'),
];
throw ErrorFactory.aggregate(errors, 'User validation');

Type Guards

import {
  isBeethovnError,
  isDomainError,
  isInfrastructureError,
  isRecoverableError,
  isClientSafeError,
} from '@beethovn/errors';

try {
  await operation();
} catch (error: unknown) {
  if (isBeethovnError(error)) {
    logger.error('Beethovn error', error.toJSON());
  }
  
  if (isDomainError(error)) {
    // User-fixable error - return helpful message
    return { error: error.toClient() };
  }
  
  if (isInfrastructureError(error) && error.recoverable) {
    // Retry recoverable infrastructure errors
    await retryWithBackoff(operation);
  }
  
  if (isClientSafeError(error)) {
    // Safe to send to client
    res.status(error.statusCode).json(error.toClient());
  } else {
    // Internal error - don't expose details
    res.status(500).json({ error: 'Internal server error' });
  }
}

Error Properties

All errors extend BeethovnError and include:

  • code - Unique error code (e.g., 'VALIDATION_ERROR')
  • statusCode - HTTP status code (e.g., 400, 404, 500)
  • message - Human-readable error message
  • metadata - Additional context for debugging
  • timestamp - When the error occurred
  • recoverable - Whether the error can be retried
  • stack - Stack trace for debugging

Error Methods

toJSON()

Serialize error for logging and monitoring (includes all details):

const error = new ValidationError('Invalid email', 'email');
logger.error('Validation failed', error.toJSON());
// {
//   name: 'ValidationError',
//   code: 'VALIDATION_ERROR',
//   message: 'Invalid email',
//   statusCode: 400,
//   metadata: { field: 'email' },
//   timestamp: '2025-01-03T10:00:00.000Z',
//   recoverable: false,
//   stack: '...'
// }

toClient()

Sanitized error for API responses (excludes stack trace and sensitive data):

const error = new ValidationError('Invalid email', 'email');
res.status(error.statusCode).json(error.toClient());
// {
//   code: 'VALIDATION_ERROR',
//   message: 'Invalid email',
//   statusCode: 400,
//   timestamp: '2025-01-03T10:00:00.000Z'
// }

Integration with DbResult

import { DbResult } from '@beethovn/types';
import { NotFoundError, ErrorFactory } from '@beethovn/errors';

async function getUser(id: string): Promise<DbResult<User>> {
  try {
    const user = await db.query('SELECT * FROM users WHERE id = $1', [id]);
    
    if (!user) {
      return {
        data: null,
        error: new NotFoundError('User', id),
      };
    }
    
    return { data: user, error: null };
  } catch (error: unknown) {
    return {
      data: null,
      error: ErrorFactory.database('getUser', error, { userId: id }),
    };
  }
}

Best Practices

  1. Always use typed errors - Never throw generic Error
  2. Include context - Add relevant metadata for debugging
  3. Use ErrorFactory.fromUnknown - For handling unknown errors
  4. Check recoverability - Retry recoverable errors
  5. Sanitize client responses - Use toClient() for API responses
  6. Log with toJSON() - Include full error details in logs
  7. Use type guards - Handle different error types appropriately

Error Decision Tree

Is this a business logic issue?
├─ YES → Use DomainError
│   ├─ Invalid input? → ValidationError
│   ├─ Rule violation? → BusinessRuleError
│   ├─ Not found? → NotFoundError
│   └─ Already exists? → ConflictError
│
└─ NO → Is it a technical issue?
    ├─ YES → Use InfrastructureError
    │   ├─ Database? → DatabaseError
    │   ├─ Network? → NetworkError
    │   └─ External service? → ExternalServiceError
    │
    └─ NO → Is it auth/authz?
        ├─ YES → Use AuthError
        └─ NO → Use ErrorFactory.fromUnknown()

TypeScript Support

Full TypeScript support with type inference:

import { BeethovnError, ValidationError } from '@beethovn/errors';

function handleError(error: BeethovnError) {
  // TypeScript knows all properties
  console.log(error.code);      // ✓ string
  console.log(error.statusCode); // ✓ number
  console.log(error.metadata);   // ✓ Record<string, unknown>
}

// Type narrowing with type guards
if (error instanceof ValidationError) {
  // TypeScript knows this is ValidationError
  console.log(error.metadata.field); // ✓ unknown
}

License

MIT

Contributing

See CONTRIBUTING.md