JSPM

@kitiumai/error

3.0.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 33
  • Score
    100M100P100Q50790F
  • License MIT

Enterprise-grade error primitives for Kitium products: rich metadata, HTTP/Problem Details mapping, observability, and registry-driven error governance.

Package Exports

  • @kitiumai/error
  • @kitiumai/error/types

Readme

@kitiumai/error

Enterprise-grade error primitives for modern applications

@kitiumai/error is a comprehensive TypeScript error handling library that provides structured error management, RFC 7807 Problem Details compliance, retry semantics, observability integration, and enterprise-grade error taxonomy. Designed for large-scale applications that require consistent error handling across distributed systems.

What is this package?

@kitiumai/error is a TypeScript-first error handling library that provides:

  • Structured Error Classes: Typed error classes with rich metadata for consistent error representation
  • Error Registry System: Centralized error code management with defaults and validation
  • RFC 7807 Compliance: Full Problem Details standard implementation for HTTP APIs
  • Retry Semantics: Built-in retry metadata and execution helpers with configurable backoff strategies
  • Observability Integration: Error fingerprinting, metrics collection, and tracing support
  • Internationalization: i18n support with parameterized error messages
  • Rate Limiting: Built-in rate limit error handling with retry metadata
  • PII Redaction: Automatic sensitive data redaction for logging and monitoring

Why we need this package

Modern applications face complex error handling challenges:

The Problem

  • Inconsistent Error Types: Different services use different error formats
  • Poor Observability: Errors lack context for debugging and monitoring
  • HTTP API Inconsistencies: No standardized error response format
  • Retry Logic Complexity: Manual retry implementation leads to bugs
  • PII Exposure: Sensitive data leaks through error logs
  • Internationalization Gaps: Error messages not localized for users
  • Rate Limiting Issues: No standardized way to handle rate limit errors

The Solution

@kitiumai/error provides a single source of truth for error handling that:

  • Standardizes Error Format: Consistent error structure across all services
  • Enhances Observability: Rich metadata for better monitoring and debugging
  • Ensures API Consistency: RFC 7807 compliance for predictable HTTP responses
  • Simplifies Retry Logic: Built-in retry helpers with proven strategies
  • Protects User Privacy: Automatic PII redaction
  • Supports Globalization: i18n-ready error messages
  • Handles Scale: Rate limiting and dependency error management

Competitor Comparison

Feature @kitiumai/error Google APIs AWS SDK Stripe API Apollo GraphQL
Error Taxonomy ✅ Typed classes ❌ Manual codes ❌ Inconsistent ❌ Manual codes ❌ Basic types
RFC 7807 Support ✅ Full compliance ❌ Partial ❌ None ❌ None ❌ None
Retry Semantics ✅ Built-in helpers ❌ Manual ❌ Manual ❌ Manual ❌ Manual
Observability ✅ Fingerprinting + metrics ❌ Basic ❌ Basic ❌ Basic ❌ Basic
PII Redaction ✅ Configurable ❌ None ❌ None ❌ None ❌ None
i18n Support ✅ Parameterized messages ❌ None ❌ None ❌ None ❌ None
Rate Limiting ✅ Built-in types ❌ Manual ❌ Manual ❌ Manual ❌ Manual
TypeScript First ✅ Strict types ❌ JavaScript ❌ JavaScript ❌ JavaScript ❌ JavaScript
Registry System ✅ Centralized ❌ None ❌ None ❌ None ❌ None
Tracing Integration ✅ OpenTelemetry ❌ None ❌ None ❌ None ❌ None

Unique Selling Proposition (USP)

Enterprise-Grade Error Handling

While other libraries provide basic error classes, @kitiumai/error delivers enterprise-grade error management that scales with your business:

  • Big Tech Standards: Implements patterns used by Google, AWS, and Stripe
  • Production Ready: Used in high-traffic production environments
  • Developer Experience: TypeScript-first with excellent DX
  • Compliance Ready: PII redaction and audit trails built-in
  • Future Proof: Extensible design for evolving requirements

Single Source of Truth

Centralized error management eliminates inconsistencies across services:

  • Registry-Driven: All error definitions in one place
  • Validation: Automatic error code format validation
  • Documentation: Auto-generated documentation URLs
  • Metrics: Built-in error tracking and reporting
  • Governance: Lifecycle management for error evolution

Observability Excellence

Superior monitoring and debugging capabilities:

  • Error Fingerprinting: Automatic error grouping for alert reduction
  • Rich Context: Correlation IDs, request IDs, and custom metadata
  • Tracing Integration: OpenTelemetry span enrichment
  • Metrics Collection: Real-time error statistics
  • PII Protection: Safe logging without data leaks

Installation

npm install @kitiumai/error
# or
pnpm add @kitiumai/error
# or
yarn add @kitiumai/error

Quick Start

import {
  KitiumError,
  ValidationError,
  httpErrorRegistry,
  problemDetailsFrom,
  logError,
} from '@kitiumai/error';

// Register your error definitions
httpErrorRegistry.register({
  code: 'user/not_found',
  message: 'User not found',
  statusCode: 404,
  severity: 'warning',
  kind: 'not_found',
  retryable: false,
  docs: 'https://docs.kitium.ai/errors/user_not_found',
});

// Create and handle errors
try {
  throw new ValidationError({
    code: 'validation/invalid_email',
    message: 'Invalid email format',
    context: { email: 'invalid-email' },
  });
} catch (error) {
  logError(error);
  const problem = problemDetailsFrom(error);
  // Send RFC 7807 compliant response
}

Core Concepts

Error Taxonomy

Errors are categorized by kind and severity for consistent handling:

type ErrorKind =
  | 'business' // Business rule violations
  | 'validation' // Input validation errors
  | 'auth' // Authentication/authorization
  | 'rate_limit' // Rate limiting
  | 'not_found' // Resource not found
  | 'conflict' // Resource conflicts
  | 'dependency' // External service failures
  | 'internal'; // System errors

type ErrorSeverity = 'fatal' | 'error' | 'warning' | 'info' | 'debug';

Error Registry

Centralized error management with defaults and validation:

import { createErrorRegistry } from '@kitiumai/error';

const userRegistry = createErrorRegistry({
  statusCode: 500,
  severity: 'error',
  kind: 'internal',
  docs: 'https://docs.kitium.ai/errors/user-service',
});

userRegistry.register({
  code: 'user/email_taken',
  message: 'Email address already in use',
  statusCode: 409,
  kind: 'conflict',
});

RFC 7807 Problem Details

Standardized HTTP error responses:

const error = new KitiumError({
  code: 'auth/forbidden',
  message: 'Access denied',
  statusCode: 403,
});

const problem = problemDetailsFrom(error);
// {
//   "type": "https://docs.kitium.ai/errors/auth/forbidden",
//   "title": "Access denied",
//   "status": 403,
//   "instance": "req-123",
//   "extensions": {
//     "code": "auth/forbidden",
//     "severity": "warning",
//     "kind": "auth"
//   }
// }

API Reference

Core Classes

KitiumError

The base error class with rich metadata support.

class KitiumError extends Error {
  constructor(shape: ErrorShape, validateCode?: boolean);

  // Core properties
  readonly code: string;
  readonly message: string;
  readonly statusCode?: number;
  readonly severity: ErrorSeverity;
  readonly kind: ErrorKind;
  readonly retryable: boolean;

  // Retry metadata
  readonly retryDelay?: number;
  readonly maxRetries?: number;
  readonly backoff?: RetryBackoff;

  // Additional metadata
  readonly help?: string;
  readonly docs?: string;
  readonly source?: string;
  readonly context?: ErrorContext;
  readonly cause?: unknown;

  // New features
  readonly i18nKey?: string;
  readonly i18nParams?: Record<string, unknown>;
  readonly rateLimit?: RateLimitInfo;

  // Methods
  toJSON(): ErrorShape;
}

Typed Error Subclasses

ValidationError - Input validation errors (400, warning) AuthenticationError - Auth failures (401, error) AuthorizationError - Permission denied (403, warning) NotFoundError - Resource not found (404, warning) ConflictError - Resource conflicts (409, warning) RateLimitError - Rate limiting (429, warning, retryable) DependencyError - External service failures (502, error, retryable) BusinessError - Business logic violations (400, error) InternalError - System failures (500, error)

Functions

Error Creation & Normalization

// Generate IDs
generateCorrelationId(): string;
generateRequestId(): string;
generateIdempotencyKey(): string;
generateDocumentationUrl(code: string): string;

// Create i18n messages
createI18nMessage(key: string, params?: Record<string, unknown>, fallback?: string): I18nMessage;

// Add i18n to errors
withI18n(error: KitiumError, i18nKey: string, params?: Record<string, unknown>, fallback?: string): KitiumError;

// Normalize unknown errors
toKitiumError(error: unknown, fallback?: ErrorShape): KitiumError;

// Enrich errors with context
enrichError(error: KitiumError, context: Record<string, unknown>): KitiumError;

Validation & Registry

// Error code validation
isValidErrorCode(code: string): boolean;
validateErrorCode(code: string): void;

// Registry management
createErrorRegistry(defaults?: Partial<ErrorRegistryEntry>): ErrorRegistry;

Observability & Monitoring

// Logging
logError(error: KitiumError): void;

// Tracing
recordException(error: KitiumError, span?: TraceSpanLike): void;

// Metrics
getErrorMetrics(): ErrorMetrics;
resetErrorMetrics(): void;

// Fingerprinting
getErrorFingerprint(error: KitiumError | ErrorShape): string;

HTTP & Serialization

// RFC 7807 Problem Details
problemDetailsFrom(error: KitiumError): ProblemDetails;

Retry Execution

// Execute with retry semantics
runWithRetry<T>(
  operation: () => Promise<T>,
  options?: RetryOptions
): Promise<RetryOutcome<T>>;

Types

Core Types

type ErrorSeverity = 'fatal' | 'error' | 'warning' | 'info' | 'debug';

type ErrorKind =
  | 'business'
  | 'validation'
  | 'auth'
  | 'rate_limit'
  | 'not_found'
  | 'conflict'
  | 'dependency'
  | 'internal';

type RetryBackoff = 'linear' | 'exponential' | 'fixed';

interface ErrorContext {
  correlationId?: string;
  requestId?: string;
  idempotencyKey?: string;
  locale?: string;
  [key: string]: unknown;
}

interface ErrorShape {
  readonly code: string;
  readonly message: string;
  readonly statusCode?: number;
  readonly severity: ErrorSeverity;
  readonly kind: ErrorKind;
  readonly retryable: boolean;
  readonly retryDelay?: number;
  readonly maxRetries?: number;
  readonly backoff?: RetryBackoff;
  readonly help?: string;
  readonly docs?: string;
  readonly source?: string;
  readonly userMessage?: string;
  readonly i18nKey?: string;
  readonly i18nParams?: Record<string, unknown>;
  readonly redact?: string[];
  readonly context?: ErrorContext;
  readonly rateLimit?: RateLimitInfo;
  readonly cause?: unknown;
}

New Types (Latest Features)

interface I18nMessage {
  readonly key: string;
  readonly params?: Record<string, unknown>;
  readonly fallback?: string;
}

interface RateLimitInfo {
  readonly limit?: number;
  readonly remaining?: number;
  readonly resetTime?: number;
}

interface RetryOptions {
  readonly maxAttempts?: number;
  readonly baseDelayMs?: number;
  readonly backoff?: RetryBackoff;
  readonly idempotencyKey?: string;
  readonly onAttempt?: (attempt: number, error: KitiumError) => void;
}

interface RetryOutcome<T> {
  readonly attempts: number;
  readonly result?: T;
  readonly error?: unknown;
  readonly lastDelayMs?: number;
}

interface ProblemDetails {
  readonly type?: string;
  readonly title: string;
  readonly status?: number;
  readonly detail?: string;
  readonly instance?: string;
  readonly extensions?: Record<string, unknown>;
}

interface ErrorMetrics {
  readonly totalErrors: number;
  readonly errorsByKind: Record<ErrorKind, number>;
  readonly errorsBySeverity: Record<ErrorSeverity, number>;
  readonly retryableErrors: number;
  readonly nonRetryableErrors: number;
}

interface ErrorRegistry {
  register(entry: ErrorRegistryEntry): void;
  resolve(code: string): ErrorRegistryEntry | undefined;
  toProblemDetails(error: ErrorShape): ProblemDetails;
}

interface ErrorRegistryEntry extends ErrorShape {
  readonly fingerprint?: string;
  readonly toProblem?: (error: ErrorShape) => ProblemDetails;
}

interface TraceSpanLike {
  setAttribute(key: string, value: unknown): void;
  recordException(exception: unknown): void;
}

Usage Examples

Basic Error Handling

import { KitiumError, logError, problemDetailsFrom } from '@kitiumai/error';

// Create typed error
const error = new KitiumError({
  code: 'auth/forbidden',
  message: 'Access denied to resource',
  statusCode: 403,
  severity: 'warning',
  kind: 'auth',
  retryable: false,
  context: {
    correlationId: 'corr-123',
    requestId: 'req-456',
    userId: 'user-789',
    resource: '/api/users',
  },
});

// Log with structured data
logError(error);

// Convert to HTTP response
const problem = problemDetailsFrom(error);
res.status(problem.status).json(problem);

Typed Subclass Usage

import {
  ValidationError,
  AuthenticationError,
  NotFoundError,
  RateLimitError,
} from '@kitiumai/error';

// Validation errors
function validateUser(userData: any) {
  if (!userData.email) {
    throw new ValidationError({
      code: 'validation/required_field',
      message: 'Email is required',
      context: { field: 'email' },
    });
  }
}

// Auth errors
function authenticate(token: string) {
  if (!isValidToken(token)) {
    throw new AuthenticationError({
      code: 'auth/invalid_token',
      message: 'Invalid authentication token',
      context: { tokenLength: token.length },
    });
  }
}

// Not found errors
async function getUser(id: string) {
  const user = await db.users.findById(id);
  if (!user) {
    throw new NotFoundError({
      code: 'user/not_found',
      message: 'User not found',
      context: { userId: id },
    });
  }
  return user;
}

// Rate limiting
function checkRateLimit(userId: string) {
  const remaining = getRemainingRequests(userId);
  if (remaining <= 0) {
    throw new RateLimitError({
      code: 'rate_limit/exceeded',
      message: 'Rate limit exceeded',
      retryDelay: 60000, // 1 minute
      context: { userId, limit: 100 },
      rateLimit: {
        limit: 100,
        remaining: 0,
        resetTime: Date.now() + 60000,
      },
    });
  }
}

Retry with Built-in Semantics

import { runWithRetry, DependencyError } from '@kitiumai/error';

const result = await runWithRetry(
  async () => {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) {
      throw new DependencyError({
        code: 'dependency/api_error',
        message: 'External API error',
        statusCode: response.status,
        retryDelay: 1000,
        maxRetries: 3,
        backoff: 'exponential',
      });
    }
    return response.json();
  },
  {
    maxAttempts: 4,
    baseDelayMs: 500,
    backoff: 'exponential',
    idempotencyKey: 'retry-123',
    onAttempt: (attempt, error) => {
      console.log(`Retry attempt ${attempt} failed:`, error.message);
    },
  }
);

if (result.error) {
  console.error('All retry attempts failed:', result.error);
} else {
  console.log('Success:', result.result);
}

Error Registry Pattern

import { createErrorRegistry } from '@kitiumai/error';

const apiRegistry = createErrorRegistry({
  statusCode: 500,
  severity: 'error',
  kind: 'internal',
  retryable: false,
  docs: 'https://docs.kitium.ai/errors/api',
});

// Register domain-specific errors
apiRegistry.register({
  code: 'api/validation_failed',
  message: 'Request validation failed',
  statusCode: 400,
  kind: 'validation',
  help: 'Check your request parameters',
});

apiRegistry.register({
  code: 'api/unauthorized',
  message: 'Authentication required',
  statusCode: 401,
  kind: 'auth',
  help: 'Provide valid authentication credentials',
});

// Use in error handling
try {
  validateRequest(req.body);
} catch (error) {
  const kitiumError = toKitiumError(error, {
    code: 'api/validation_failed',
    message: 'Invalid request data',
  });

  const problem = apiRegistry.toProblemDetails(kitiumError);
  res.status(problem.status).json(problem);
}

Internationalization Support

import { createI18nMessage, withI18n, KitiumError } from '@kitiumai/error';

// Create i18n message
const i18nMsg = createI18nMessage(
  'validation.required_field',
  { field: 'email' },
  'The email field is required'
);

// Add i18n to error
const error = new KitiumError({
  code: 'validation/required_field',
  message: 'Email is required',
  kind: 'validation',
});

const localizedError = withI18n(
  error,
  'validation.required_field',
  { field: 'email' },
  'The email field is required'
);

// Error now has i18n metadata for frontend localization
console.log(localizedError.i18nKey); // 'validation.required_field'
console.log(localizedError.i18nParams); // { field: 'email' }

Express.js Integration

import express from 'express';
import {
  KitiumError,
  toKitiumError,
  enrichError,
  problemDetailsFrom,
  logError,
} from '@kitiumai/error';

const app = express();

// Request context middleware
app.use((req, res, next) => {
  req.context = {
    correlationId: req.headers['x-correlation-id'] || generateCorrelationId(),
    requestId: generateRequestId(),
    path: req.path,
    method: req.method,
  };
  next();
});

// Error handling middleware
app.use(
  (error: unknown, req: express.Request, res: express.Response, next: express.NextFunction) => {
    // Normalize error
    const kitiumError = toKitiumError(error, {
      code: 'internal/server_error',
      message: 'An unexpected error occurred',
      statusCode: 500,
      severity: 'error',
      kind: 'internal',
      retryable: false,
    });

    // Enrich with request context
    const enrichedError = enrichError(kitiumError, req.context);

    // Log error
    logError(enrichedError);

    // Send Problem Details response
    const problem = problemDetailsFrom(enrichedError);
    res.status(problem.status || 500).json(problem);
  }
);

Tracing Integration

import { context, trace } from '@opentelemetry/api';
import { recordException, KitiumError } from '@kitiumai/error';

const tracer = trace.getTracer('my-service');

async function tracedOperation() {
  const span = tracer.startSpan('operation');
  try {
    await riskyOperation();
    span.setStatus({ code: trace.SpanStatusCode.OK });
  } catch (error) {
    const kitiumError = toKitiumError(error);
    recordException(kitiumError, span);
    span.setStatus({ code: trace.SpanStatusCode.ERROR, message: kitiumError.message });
    throw kitiumError;
  } finally {
    span.end();
  }
}

Error Metrics & Monitoring

import { getErrorMetrics, resetErrorMetrics } from '@kitiumai/error';

// Get current metrics
const metrics = getErrorMetrics();
console.log('Total errors:', metrics.totalErrors);
console.log('Errors by kind:', metrics.errorsByKind);
console.log('Errors by severity:', metrics.errorsBySeverity);

// Export to monitoring system
function exportMetrics() {
  const metrics = getErrorMetrics();

  // Send to Prometheus/DataDog
  prometheus.gauge('errors_total').set(metrics.totalErrors);
  prometheus.gauge('errors_retryable').set(metrics.retryableErrors);

  Object.entries(metrics.errorsByKind).forEach(([kind, count]) => {
    prometheus.gauge('errors_by_kind', { kind }).set(count);
  });
}

// Reset for testing
resetErrorMetrics();

Error Fingerprinting

import { getErrorFingerprint, KitiumError } from '@kitiumai/error';

const error = new KitiumError({
  code: 'validation/email_invalid',
  message: 'Invalid email format',
  kind: 'validation',
});

const fingerprint = getErrorFingerprint(error);
// Returns: "validation/email_invalid:validation"

// Use for error grouping in monitoring
Sentry.withScope((scope) => {
  scope.setFingerprint([fingerprint]);
  Sentry.captureException(error);
});

Advanced Patterns

Custom Error Registry with Overrides

import { createErrorRegistry } from '@kitiumai/error';

const registry = createErrorRegistry({
  statusCode: 500,
  severity: 'error',
  kind: 'internal',
});

// Custom Problem Details renderer
registry.register({
  code: 'validation/schema_error',
  message: 'Schema validation failed',
  kind: 'validation',
  toProblem: (error) => ({
    type: 'https://api.example.com/errors/validation',
    title: error.message,
    status: 400,
    detail: error.help,
    extensions: {
      code: error.code,
      fields: error.context?.fields || [],
      schema: error.context?.schema,
    },
  }),
});

Circuit Breaker Pattern

import { DependencyError, runWithRetry } from '@kitiumai/error';

class CircuitBreaker {
  private failures = 0;
  private lastFailureTime = 0;
  private state: 'closed' | 'open' | 'half-open' = 'closed';

  async call(operation: () => Promise<any>) {
    if (this.state === 'open') {
      if (Date.now() - this.lastFailureTime > 60000) {
        // 1 minute timeout
        this.state = 'half-open';
      } else {
        throw new DependencyError({
          code: 'dependency/circuit_open',
          message: 'Circuit breaker is open',
          retryable: true,
          retryDelay: 30000,
        });
      }
    }

    try {
      const result = await operation();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private onSuccess() {
    this.failures = 0;
    this.state = 'closed';
  }

  private onFailure() {
    this.failures++;
    this.lastFailureTime = Date.now();
    if (this.failures >= 5) {
      this.state = 'open';
    }
  }
}

Error Boundary Pattern

import React from 'react';
import { KitiumError, toKitiumError } from '@kitiumai/error';

class ErrorBoundary extends React.Component {
  state = { error: null };

  static getDerivedStateFromError(error: Error) {
    return { error };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    const kitiumError = toKitiumError(error, {
      code: 'ui/render_error',
      message: 'Component render failed',
      severity: 'error',
      kind: 'internal',
      context: {
        componentStack: errorInfo.componentStack,
        errorBoundary: this.constructor.name,
      },
    });

    logError(kitiumError);
  }

  render() {
    if (this.state.error) {
      return <div>Something went wrong. Please try again.</div>;
    }
    return this.props.children;
  }
}

Best Practices

1. Use Typed Error Subclasses

// ✅ Good
throw new ValidationError({ code: 'validation/invalid_email', ... });

// ❌ Avoid
throw new KitiumError({ code: 'validation/invalid_email', ... });

2. Register Errors Early

// ✅ Register at startup
app.on('ready', () => {
  httpErrorRegistry.register(userErrors);
  httpErrorRegistry.register(apiErrors);
});

3. Enrich Context Immediately

// ✅ Enrich when error occurs
try {
  await operation();
} catch (error) {
  const enriched = enrichError(error, {
    correlationId: req.correlationId,
    userId: req.user.id,
  });
  throw enriched;
}

4. Use Problem Details for APIs

// ✅ Standardized responses
const problem = problemDetailsFrom(error);
res.status(problem.status).json(problem);

5. Implement Retry Logic

// ✅ Use built-in retry
const result = await runWithRetry(operation, {
  maxAttempts: 3,
  backoff: 'exponential',
});

6. Protect Sensitive Data

// ✅ Automatic redaction
httpErrorRegistry.register({
  code: 'auth/login_failed',
  message: 'Login failed',
  redact: ['context.password', 'context.token'],
});

7. Monitor Error Metrics

// ✅ Track and alert
const metrics = getErrorMetrics();
if (metrics.errorsBySeverity.error > 100) {
  alert('High error rate detected');
}

Migration Guide

From Generic Errors

// Before
throw new Error('User not found');

// After
throw new NotFoundError({
  code: 'user/not_found',
  message: 'User not found',
  context: { userId },
});

From Manual HTTP Responses

// Before
res.status(404).json({ error: 'Not found' });

// After
const problem = problemDetailsFrom(error);
res.status(problem.status).json(problem);

From Basic Retry Logic

// Before
let attempts = 0;
while (attempts < 3) {
  try {
    return await operation();
  } catch (error) {
    attempts++;
    await delay(1000 * attempts);
  }
}

// After
return await runWithRetry(operation, {
  maxAttempts: 3,
  backoff: 'exponential',
});

Contributing

See CONTRIBUTING.md for development setup and contribution guidelines.

License

MIT