Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

@kitiumai/error

kitiumai0MIT2.0.1TypeScript support: included

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

error, errors, error-handling, error-management, error-tracking, exception, exceptions, exception-handling, observability, monitoring, logging, telemetry, problem-details, rfc7807, http-errors, api-errors, rest-errors, graphql-errors, error-codes, error-registry, error-classification, error-severity, error-context, error-metadata, resilience, retry, retry-logic, backoff, exponential-backoff, circuit-breaker, fault-tolerance, error-recovery, enterprise, production-ready, type-safe, typescript, typescript-errors, typed-errors, structured-errors, structured-logging, error-fingerprinting, error-grouping, error-metrics, nodejs, node, backend, api, microservices, distributed-systems, saas, kitium, esm, commonjs, tree-shakeable

readme

@kitiumai/error

Enterprise-grade error primitives for Kitium products. This library provides a single source of truth for error taxonomy, structured logging, HTTP/Problem Details mapping, and registry-driven governance – the kind of capabilities expected in large-scale products.

Usage & Tree-Shaking

This package is ESM-first and sideEffects: false. Import only what you need using subpath exports to keep bundles lean.

  • Core runtime:

    • import { KitiumError, ValidationError, problemDetailsFrom } from '@kitiumai/error'
  • Types-only:

    • import type { ErrorShape, ProblemDetails } from '@kitiumai/error/types'

Use @kitiumai/error/types when you only need type imports to avoid pulling runtime code.

Features

  • Typed Error Classes: KitiumError with rich metadata and typed subclasses for common error types
  • Retry Strategy Metadata: Detailed retry information (delay, max retries, backoff strategy)
  • Error Code Validation: Automatic validation of error code format
  • Error Registry: Centralized error code management with defaults
  • RFC 7807 Problem Details: Full compliance with HTTP Problem Details standard
  • Error Fingerprinting: Automatic error grouping for observability systems
  • Error Metrics: Built-in error metrics tracking and export
  • Structured Logging: Severity-aware logging with @kitiumai/logger
  • Context Enrichment: Safe context merging for distributed tracing
  • Error Normalization: Convert unknown errors to consistent shape
  • Lifecycle + Redaction: Registry-driven lifecycle states, schema versions, and PII redaction controls
  • Safe Messaging: Separate internal message from localized userMessage/i18nKey
  • Retry Execution Helper: runWithRetry executes retry policies consistently
  • Tracing Helper: recordException propagates error metadata into spans

Installation

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

Quick start

Basic Usage

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

// Register error definitions in your application startup
httpErrorRegistry.register({
  code: 'auth/forbidden',
  message: 'Access denied',
  statusCode: 403,
  severity: 'warning',
  kind: 'auth',
  retryable: false,
  lifecycle: 'active',
  schemaVersion: '2024-12-01',
  userMessage: 'You do not have permission to perform this action',
  redact: ['context.userId'],
  docs: 'https://docs.kitium.ai/errors/auth/forbidden',
});

// Create and use errors
const err = new KitiumError({
  code: 'auth/forbidden',
  message: 'You cannot access this resource',
  statusCode: 403,
  severity: 'warning',
  kind: 'auth',
  retryable: false,
  context: { correlationId: 'corr-123', userId: 'user-456' },
});

// Log the error
logError(err);

// Convert to Problem Details for HTTP responses
const problem = problemDetailsFrom(err);
// {
//   type: 'https://docs.kitium.ai/errors/auth/forbidden',
//   title: 'You do not have permission to perform this action',
//   status: 403,
//   instance: 'corr-123',
//   extensions: {
//     code: 'auth/forbidden',
//     severity: 'warning',
//     retryable: false,
//     kind: 'auth',
//     context: { correlationId: 'corr-123', userId: '[REDACTED]' }
//   }
// }

Express.js Integration Example

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

const app = express();

// Error handling middleware
app.use((err: unknown, req: express.Request, res: express.Response, next: express.NextFunction) => {
  // Normalize unknown errors
  const kitiumError = toKitiumError(err, {
    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, {
    correlationId: req.headers['x-correlation-id'] as string,
    requestId: req.id,
    path: req.path,
    method: req.method,
  });

  // Log the error
  logError(enrichedError);

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

Running operations with built-in retry semantics

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

const result = await runWithRetry(
  async () => {
    const response = await fetch('https://example.com/api');
    if (!response.ok) {
      throw new DependencyError({
        code: 'dependency/upstream_unavailable',
        message: 'Partner API unavailable',
        retryDelay: 500,
        maxRetries: 3,
      });
    }
    return response.json();
  },
  {
    maxAttempts: 4,
    onAttempt: (attempt, error) => console.warn('retry attempt', attempt, error.code),
  }
);

if (result.error) {
  // Exhausted retries; result.error is a KitiumError
}

Recording enriched exceptions into tracing spans

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

const span = trace.getSpan(context.active());
try {
  await doWork();
} catch (err) {
  const kitium = toKitiumError(err);
  recordException(kitium, span);
  throw kitium;
}

Error Registry Pattern

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

// Create a custom registry for your service
const userServiceRegistry = createErrorRegistry({
  statusCode: 500,
  severity: 'error',
  kind: 'internal',
  retryable: false,
  docs: 'https://docs.kitium.ai/errors/user-service',
});

// Register service-specific errors
userServiceRegistry.register({
  code: 'user/not_found',
  message: 'User not found',
  statusCode: 404,
  severity: 'warning',
  kind: 'not_found',
  retryable: false,
});

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

// Use the registry
const error = new KitiumError({
  code: 'user/not_found',
  message: 'User with ID 123 not found',
  kind: 'not_found',
  context: { userId: '123' },
});

const problem = userServiceRegistry.toProblemDetails(error);

Using Typed Error Subclasses

import {
  ValidationError,
  AuthenticationError,
  AuthorizationError,
  NotFoundError,
  ConflictError,
  RateLimitError,
  DependencyError,
  BusinessError,
  InternalError,
} from '@kitiumai/error';

// Validation errors (default: 400, warning)
function validateEmail(email: string) {
  if (!email) {
    throw new ValidationError({
      code: 'validation/required_field',
      message: 'Email is required',
      context: { field: 'email' },
    });
  }
  if (!email.includes('@')) {
    throw new ValidationError({
      code: 'validation/invalid_format',
      message: 'Email must be a valid email address',
      context: { field: 'email', value: email },
    });
  }
}

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

// Authorization errors (default: 403, warning)
function authorize(userId: string, resource: string) {
  if (!hasPermission(userId, resource)) {
    throw new AuthorizationError({
      code: 'auth/insufficient_permissions',
      message: 'Insufficient permissions to access resource',
      context: { userId, resource },
    });
  }
}

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

// Conflict errors (default: 409, warning)
async function createUser(email: string) {
  const existing = await db.users.findByEmail(email);
  if (existing) {
    throw new ConflictError({
      code: 'user/email_taken',
      message: 'Email address already in use',
      context: { email },
    });
  }
}

// Rate limit errors (default: 429, warning, retryable)
async function checkRateLimit(userId: string) {
  const count = await rateLimiter.getCount(userId);
  if (count >= MAX_REQUESTS) {
    throw new RateLimitError({
      code: 'rate_limit/exceeded',
      message: 'Rate limit exceeded. Please try again later.',
      retryDelay: 2000, // 2 seconds
      maxRetries: 3,
      context: { userId, limit: MAX_REQUESTS },
    });
  }
}

// Dependency errors (default: 502, error, retryable)
async function callExternalService(url: string) {
  try {
    return await fetch(url);
  } catch (error) {
    throw new DependencyError({
      code: 'dependency/service_unavailable',
      message: 'External service unavailable',
      retryDelay: 1000,
      maxRetries: 5,
      backoff: 'exponential',
      context: { url },
      cause: error,
    });
  }
}

// Business logic errors (default: 400, error, non-retryable)
function processPayment(amount: number, balance: number) {
  if (amount > balance) {
    throw new BusinessError({
      code: 'payment/insufficient_funds',
      message: 'Insufficient funds for transaction',
      context: { amount, balance },
    });
  }
}

// Internal errors (default: 500, error, non-retryable)
function criticalOperation() {
  try {
    // Some critical operation
  } catch (error) {
    throw new InternalError({
      code: 'internal/unexpected_error',
      message: 'An unexpected error occurred',
      cause: error,
    });
  }
}

Retry Strategy Metadata

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

// Exponential backoff (recommended for transient errors)
const transientError = new KitiumError({
  code: 'dependency/timeout',
  message: 'Request timeout',
  statusCode: 504,
  severity: 'error',
  kind: 'dependency',
  retryable: true,
  retryDelay: 1000, // Initial delay: 1 second
  maxRetries: 3, // Maximum 3 retry attempts
  backoff: 'exponential', // Delays: 1s, 2s, 4s
});

// Linear backoff (constant delay between retries)
const linearRetryError = new KitiumError({
  code: 'dependency/rate_limited',
  message: 'Service rate limited',
  retryable: true,
  retryDelay: 2000, // 2 seconds between each retry
  maxRetries: 5,
  backoff: 'linear', // Delays: 2s, 2s, 2s, 2s, 2s
});

// Fixed delay (same as linear, but explicit)
const fixedRetryError = new KitiumError({
  code: 'dependency/temporary_unavailable',
  message: 'Service temporarily unavailable',
  retryable: true,
  retryDelay: 5000, // 5 seconds between retries
  maxRetries: 2,
  backoff: 'fixed', // Delays: 5s, 5s
});

// Implementing retry logic
async function retryWithBackoff<T>(fn: () => Promise<T>, error: KitiumError): Promise<T> {
  if (!error.retryable || !error.maxRetries) {
    throw error;
  }

  let delay = error.retryDelay || 1000;
  for (let attempt = 0; attempt < error.maxRetries; attempt++) {
    try {
      return await fn();
    } catch (err) {
      if (attempt === error.maxRetries - 1) throw err;

      await new Promise((resolve) => setTimeout(resolve, delay));

      // Calculate next delay based on backoff strategy
      if (error.backoff === 'exponential') {
        delay *= 2;
      } else if (error.backoff === 'linear' || error.backoff === 'fixed') {
        delay = error.retryDelay || 1000;
      }
    }
  }
  throw error;
}

Error Metrics

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

// Get current error metrics
const metrics = getErrorMetrics();
console.log(metrics);
// {
//   totalErrors: 150,
//   errorsByKind: {
//     validation: 50,
//     auth: 30,
//     rate_limit: 20,
//     not_found: 25,
//     conflict: 10,
//     dependency: 10,
//     business: 3,
//     internal: 2
//   },
//   errorsBySeverity: {
//     fatal: 0,
//     error: 100,
//     warning: 50,
//     info: 0,
//     debug: 0
//   },
//   retryableErrors: 75,
//   nonRetryableErrors: 75
// }

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

  // Send to Prometheus, Datadog, etc.
  prometheus.gauge('errors_total').set(metrics.totalErrors);
  prometheus.gauge('errors_retryable').set(metrics.retryableErrors);
  prometheus.gauge('errors_non_retryable').set(metrics.nonRetryableErrors);

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

  Object.entries(metrics.errorsBySeverity).forEach(([severity, count]) => {
    prometheus.gauge('errors_by_severity', { severity }).set(count);
  });
}

// Reset metrics (useful for testing)
resetErrorMetrics();

Error Fingerprinting

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

// Automatic fingerprint generation
const err = new KitiumError({
  code: 'validation/required_field',
  message: 'Field is required',
  kind: 'validation',
});

const fingerprint = getErrorFingerprint(err);
// Returns: "validation/required_field:validation"

// Register with custom fingerprint for better grouping
httpErrorRegistry.register({
  code: 'validation/required_field',
  message: 'Field is required',
  kind: 'validation',
  fingerprint: 'validation-required-field', // Custom fingerprint
});

// Use fingerprint for error tracking
function trackError(error: KitiumError) {
  const fingerprint = getErrorFingerprint(error);

  // Send to error tracking service (Sentry, Rollbar, etc.)
  errorTrackingService.captureError(error, {
    fingerprint: [fingerprint],
    tags: {
      code: error.code,
      kind: error.kind,
      severity: error.severity,
    },
  });
}

Error Normalization

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

// Normalize unknown errors
try {
  await someOperation();
} catch (error) {
  // Convert any error to KitiumError
  const kitiumError = toKitiumError(error, {
    code: 'operation/failed',
    message: 'Operation failed',
    statusCode: 500,
    severity: 'error',
    kind: 'internal',
    retryable: false,
  });

  logError(kitiumError);
  throw kitiumError;
}

// Normalize with fallback
function safeOperation() {
  try {
    return riskyOperation();
  } catch (error) {
    // If error is already KitiumError, it's returned as-is
    // Otherwise, uses fallback
    return toKitiumError(error, {
      code: 'operation/unknown_error',
      message: 'An unknown error occurred',
      severity: 'error',
      kind: 'internal',
      retryable: false,
    });
  }
}

Context Enrichment

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

// Add context to existing errors
function handleRequest(error: KitiumError, req: Request) {
  // Enrich with request context
  const enriched = enrichError(error, {
    correlationId: req.headers['x-correlation-id'],
    requestId: req.id,
    path: req.path,
    method: req.method,
    ip: req.ip,
    userAgent: req.headers['user-agent'],
  });

  return enriched;
}

// Chain enrichment
function processError(error: KitiumError) {
  let enriched = error;

  // Add service context
  enriched = enrichError(enriched, {
    service: 'user-service',
    version: '1.2.3',
  });

  // Add deployment context
  enriched = enrichError(enriched, {
    environment: process.env.NODE_ENV,
    region: process.env.AWS_REGION,
  });

  return enriched;
}

Error Code Conventions

Error codes must follow this pattern: ^[a-z0-9_]+(\/[a-z0-9_]+)*$

Examples

  • auth/forbidden
  • validation/required_field
  • internal/server_error
  • rate_limit/exceeded
  • Auth/Forbidden (uppercase not allowed)
  • auth.forbidden (dots not allowed, use slashes)
  • auth forbidden (spaces not allowed)

Best Practices

  • Use hierarchical structure: domain/error_type or service/action/error
  • Keep codes lowercase and descriptive
  • Use underscores for multi-word segments: required_field not requiredfield
  • Group related errors under the same domain prefix

API Reference

Core Classes

KitiumError

Base error class with rich metadata support. Extends native Error class.

Constructor:

constructor(shape: ErrorShape, validateCode?: boolean)

Properties:

class KitiumError extends Error {
  readonly code: string; // Error code (validated format)
  readonly message: string; // Human-readable message
  readonly statusCode?: number; // HTTP status code
  readonly severity: ErrorSeverity; // Error severity level
  readonly kind: ErrorKind; // Error category
  readonly retryable: boolean; // Whether error is retryable
  readonly retryDelay?: number; // Initial retry delay (ms)
  readonly maxRetries?: number; // Maximum retry attempts
  readonly backoff?: RetryBackoff; // Backoff strategy
  readonly help?: string; // Help text for users
  readonly docs?: string; // Documentation URL
  readonly source?: string; // Error source/service
  readonly context?: ErrorContext; // Additional context
  readonly cause?: unknown; // Original error cause
}

Methods:

toJSON(): ErrorShape  // Serialize error to JSON

Example:

const error = new KitiumError({
  code: 'auth/forbidden',
  message: 'Access denied',
  statusCode: 403,
  severity: 'warning',
  kind: 'auth',
  retryable: false,
  context: { userId: '123' },
});

const json = error.toJSON();
// { code: 'auth/forbidden', message: 'Access denied', ... }

Typed Error Subclasses

All subclasses extend KitiumError and provide sensible defaults:

ValidationError

  • Default: statusCode: 400, severity: 'warning', retryable: false
  • Use for: Input validation, schema validation, format errors

AuthenticationError

  • Default: statusCode: 401, severity: 'error', retryable: false
  • Use for: Invalid credentials, expired tokens, missing authentication

AuthorizationError

  • Default: statusCode: 403, severity: 'warning', retryable: false
  • Use for: Insufficient permissions, forbidden actions

NotFoundError

  • Default: statusCode: 404, severity: 'warning', retryable: false
  • Use for: Missing resources, invalid IDs

ConflictError

  • Default: statusCode: 409, severity: 'warning', retryable: false
  • Use for: Resource conflicts, duplicate entries, optimistic locking failures

RateLimitError

  • Default: statusCode: 429, severity: 'warning', retryable: true, backoff: 'exponential'
  • Use for: Rate limiting, throttling

DependencyError

  • Default: statusCode: 502, severity: 'error', retryable: true, backoff: 'exponential', maxRetries: 3
  • Use for: External service failures, timeouts, network errors

BusinessError

  • Default: statusCode: 400, severity: 'error', retryable: false
  • Use for: Business rule violations, domain logic errors

InternalError

  • Default: statusCode: 500, severity: 'error', retryable: false
  • Use for: Unexpected internal errors, system failures

Functions

createErrorRegistry(defaults?)

Creates a new error registry for centralized error management.

Signature:

function createErrorRegistry(defaults?: Partial<ErrorRegistryEntry>): ErrorRegistry;

Parameters:

  • defaults (optional): Default values for error registry entries

Returns: ErrorRegistry object with register(), resolve(), and toProblemDetails() methods

Example:

const registry = createErrorRegistry({
  statusCode: 500,
  severity: 'error',
  kind: 'internal',
  docs: 'https://docs.example.com/errors',
});

registry.register({
  code: 'user/not_found',
  message: 'User not found',
  statusCode: 404,
  kind: 'not_found',
});

const entry = registry.resolve('user/not_found');

toKitiumError(error, fallback?)

Normalizes unknown errors into KitiumError instances.

Signature:

function toKitiumError(error: unknown, fallback?: ErrorShape): KitiumError;

Parameters:

  • error: Any error value (Error, object, string, etc.)
  • fallback (optional): Default error shape if normalization fails

Returns: KitiumError instance

Example:

try {
  await riskyOperation();
} catch (error) {
  const kitiumError = toKitiumError(error, {
    code: 'operation/failed',
    message: 'Operation failed',
    severity: 'error',
    kind: 'internal',
    retryable: false,
  });
}

enrichError(error, context)

Safely merges additional context into an error, creating a new error instance.

Signature:

function enrichError(error: KitiumError, context: Record<string, unknown>): KitiumError;

Parameters:

  • error: KitiumError instance to enrich
  • context: Additional context to merge

Returns: New KitiumError instance with merged context

Example:

const error = new KitiumError({
  /* ... */
});
const enriched = enrichError(error, {
  correlationId: 'corr-123',
  requestId: 'req-456',
});

logError(error)

Logs error with structured logging, including fingerprint and retry metadata.

Signature:

function logError(error: KitiumError): void;

Parameters:

  • error: KitiumError instance to log

Example:

const error = new KitiumError({
  /* ... */
});
logError(error);
// Logs with appropriate severity level via @kitiumai/logger

problemDetailsFrom(error)

Converts error to RFC 7807 Problem Details format for HTTP responses.

Signature:

function problemDetailsFrom(error: KitiumError): ProblemDetails;

Parameters:

  • error: KitiumError instance

Returns: ProblemDetails object compliant with RFC 7807

Example:

const error = new KitiumError({
  /* ... */
});
const problem = problemDetailsFrom(error);
// {
//   type: 'https://docs.kitium.ai/errors/auth/forbidden',
//   title: 'Access denied',
//   status: 403,
//   instance: 'corr-123',
//   extensions: { code: 'auth/forbidden', ... }
// }

isValidErrorCode(code)

Validates error code format without throwing.

Signature:

function isValidErrorCode(code: string): boolean;

Parameters:

  • code: Error code to validate

Returns: true if valid, false otherwise

Example:

isValidErrorCode('auth/forbidden'); // true
isValidErrorCode('Auth/Forbidden'); // false
isValidErrorCode('auth.forbidden'); // false

validateErrorCode(code)

Validates error code format and throws if invalid.

Signature:

function validateErrorCode(code: string): void;

Parameters:

  • code: Error code to validate

Throws: Error if code format is invalid

Example:

try {
  validateErrorCode('invalid code');
} catch (error) {
  // Error: Invalid error code format: "invalid code"...
}

getErrorFingerprint(error)

Generates fingerprint for error grouping in observability systems.

Signature:

function getErrorFingerprint(error: KitiumError | ErrorShape): string;

Parameters:

  • error: KitiumError instance or ErrorShape object

Returns: Fingerprint string for error grouping

Example:

const error = new KitiumError({ code: 'auth/forbidden', kind: 'auth' });
const fingerprint = getErrorFingerprint(error);
// "auth/forbidden:auth"

getErrorMetrics()

Returns current error metrics snapshot.

Signature:

function getErrorMetrics(): ErrorMetrics;

Returns: ErrorMetrics object with error statistics

Example:

const metrics = getErrorMetrics();
console.log(metrics.totalErrors);
console.log(metrics.errorsByKind);

resetErrorMetrics()

Resets error metrics (useful for testing).

Signature:

function resetErrorMetrics(): void;

Example:

beforeEach(() => {
  resetErrorMetrics();
});

Types

ErrorSeverity

Error severity levels for logging and monitoring.

type ErrorSeverity = 'fatal' | 'error' | 'warning' | 'info' | 'debug';
  • fatal: Critical errors that cause system shutdown
  • error: Errors that require attention
  • warning: Warnings that may indicate issues
  • info: Informational messages
  • debug: Debug-level messages

ErrorKind

Error categories for classification and metrics.

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

RetryBackoff

Backoff strategies for retry logic.

type RetryBackoff = 'linear' | 'exponential' | 'fixed';
  • linear: Constant delay between retries
  • exponential: Exponential backoff (delay doubles each retry)
  • fixed: Same as linear (constant delay)

ErrorContext

Context object for error metadata and tracing.

interface ErrorContext {
  correlationId?: string; // Request correlation ID
  requestId?: string; // Unique request identifier
  spanId?: string; // Distributed tracing span ID
  tenantId?: string; // Multi-tenant tenant ID
  userId?: string; // User identifier
  [key: string]: unknown; // Additional custom fields
}

Example:

const context: ErrorContext = {
  correlationId: 'corr-123',
  requestId: 'req-456',
  spanId: 'span-789',
  tenantId: 'tenant-abc',
  userId: 'user-xyz',
  customField: 'custom-value',
};

ErrorShape

Complete error shape interface.

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 context?: ErrorContext;
  readonly cause?: unknown;
}

ProblemDetails

RFC 7807 Problem Details format.

interface ProblemDetails {
  readonly type?: string; // URI reference identifying problem type
  readonly title: string; // Short summary
  readonly status?: number; // HTTP status code
  readonly detail?: string; // Detailed explanation
  readonly instance?: string; // URI reference identifying specific occurrence
  readonly extensions?: Record<string, unknown>; // Additional properties
}

ErrorMetrics

Error metrics snapshot.

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

ErrorRegistry

Error registry interface.

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

ErrorRegistryEntry

Error registry entry with optional fingerprint and custom Problem Details renderer.

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

Advanced Usage

Custom Problem Details Rendering

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

const registry = createErrorRegistry();

registry.register({
  code: 'validation/field_error',
  message: '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 || [],
    },
  }),
});

Error Handling in Async Functions

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

async function asyncOperation() {
  try {
    return await riskyOperation();
  } catch (error) {
    const kitiumError = toKitiumError(error, {
      code: 'operation/failed',
      message: 'Operation failed',
      severity: 'error',
      kind: 'internal',
      retryable: false,
    });

    logError(kitiumError);
    throw kitiumError;
  }
}

Error Handling in Express Routes

import express from 'express';
import { ValidationError, NotFoundError, problemDetailsFrom, logError } from '@kitiumai/error';

const router = express.Router();

router.get('/users/:id', async (req, res, next) => {
  try {
    const user = await getUserById(req.params.id);
    if (!user) {
      throw new NotFoundError({
        code: 'user/not_found',
        message: 'User not found',
        context: { userId: req.params.id },
      });
    }
    res.json(user);
  } catch (error) {
    next(error);
  }
});

// Error handler middleware
router.use(
  (error: unknown, req: express.Request, res: express.Response, next: express.NextFunction) => {
    if (error instanceof ValidationError || error instanceof NotFoundError) {
      logError(error);
      const problem = problemDetailsFrom(error);
      return res.status(problem.status || 500).json(problem);
    }
    next(error);
  }
);

Integration with Error Tracking Services

import * as Sentry from '@sentry/node';
import { getErrorFingerprint, logError } from '@kitiumai/error';

function trackError(error: KitiumError) {
  const fingerprint = getErrorFingerprint(error);

  Sentry.withScope((scope) => {
    scope.setFingerprint([fingerprint]);
    scope.setTag('error_code', error.code);
    scope.setTag('error_kind', error.kind);
    scope.setTag('error_severity', error.severity);
    scope.setContext('error', {
      code: error.code,
      kind: error.kind,
      retryable: error.retryable,
      context: error.context,
    });

    Sentry.captureException(error);
  });

  logError(error);
}

Best Practices

  1. Use Typed Subclasses: Prefer typed error subclasses over base KitiumError for better type safety
  2. Register Errors Early: Register error definitions in application startup
  3. Enrich Context: Add correlation IDs, request IDs, and other tracing context
  4. Log Before Throwing: Always log errors before throwing or returning
  5. Use Problem Details: Convert errors to Problem Details format for HTTP responses
  6. Validate Error Codes: Follow error code conventions for consistency
  7. Track Metrics: Export error metrics to monitoring systems
  8. Handle Retries: Use retry metadata for implementing retry logic
  9. Document Errors: Provide documentation URLs for each error code
  10. Normalize Unknown Errors: Use toKitiumError() to handle unexpected errors

Publishing

The package is configured for public npm publishing via publishConfig.access = public.

changelog

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[v2.0.0] - 2025-11-28

Added

  • Lifecycle metadata validation and schema version tags on errors and registry entries to support governed evolution.
  • Redaction-aware context handling and safe user messaging in JSON serialization and RFC 7807 Problem Details rendering.
  • Tracing (recordException) and retry (runWithRetry) helpers that propagate retry metadata, backoff choices, and attempt counts.

Changed

  • Default Problem Details output now prioritizes registry-provided docs, lifecycle, and schema version metadata when present.

1.0.0 - 2024-12-19

Added

Core Features

  • KitiumError class: Enterprise-grade error class with rich metadata support
    • Error code, message, HTTP status code
    • Severity levels: fatal, error, warning, info, debug
    • Error kinds: business, validation, auth, rate_limit, not_found, conflict, dependency, internal
    • Retryability flag with detailed retry strategy metadata
    • Context support for correlation IDs, request IDs, span IDs, tenant/user IDs
    • Source tracking and documentation URLs
    • Error cause chain support
    • Full JSON serialization via toJSON() method

Error Registry System

  • Error Registry API: Centralized error code management
    • createErrorRegistry() - Create custom error registries with defaults
    • httpErrorRegistry - Pre-configured HTTP error registry
    • register() - Register error definitions with defaults
    • resolve() - Resolve error definitions by code
    • Custom Problem Details rendering via toProblem hooks
    • Error fingerprinting support for observability grouping

RFC 7807 Problem Details Support

  • Full compliance with RFC 7807 Problem Details for HTTP APIs
  • problemDetailsFrom() - Convert errors to Problem Details format
  • Automatic type URL generation from documentation URLs
  • Extensible extensions object with error metadata
  • Instance field support via correlation/request IDs

Retry Strategy Metadata

  • Retry Information: Detailed retry strategy support
    • retryable - Boolean flag for retry eligibility
    • retryDelay - Initial retry delay in milliseconds
    • maxRetries - Maximum number of retry attempts
    • backoff - Backoff strategy: linear, exponential, or fixed
    • Included in Problem Details extensions
    • Logged in structured error logs

Error Code Validation

  • Automatic Validation: Error code format validation
    • Pattern: ^[a-z0-9_]+(\/[a-z0-9_]+)*$
    • isValidErrorCode() - Check if error code format is valid
    • validateErrorCode() - Validate and throw on invalid format
    • Automatic validation in KitiumError constructor
    • Validation in registry registration
    • Clear error messages for invalid codes

Typed Error Subclasses

  • Type-Safe Error Classes: Pre-configured error subclasses with sensible defaults
    • ValidationError - Validation errors (400, warning, non-retryable)
    • AuthenticationError - Authentication failures (401, error, non-retryable)
    • AuthorizationError - Authorization failures (403, warning, non-retryable)
    • NotFoundError - Resource not found (404, warning, non-retryable)
    • ConflictError - Resource conflicts (409, warning, non-retryable)
    • RateLimitError - Rate limiting (429, warning, retryable with exponential backoff)
    • DependencyError - External dependency failures (502, error, retryable with exponential backoff)
    • BusinessError - Business logic errors (400, error, non-retryable)
    • InternalError - Internal server errors (500, error, non-retryable)

Error Fingerprinting

  • Observability Support: Error grouping for monitoring systems
    • getErrorFingerprint() - Generate fingerprint for error grouping
    • Registry-based fingerprint support
    • Automatic fingerprint generation from code and kind
    • Included in structured logging payload

Error Metrics

  • Built-in Metrics: Error tracking and analytics
    • getErrorMetrics() - Get current error metrics snapshot
    • resetErrorMetrics() - Reset metrics (useful for testing)
    • Tracks: total errors, errors by kind, errors by severity
    • Tracks: retryable vs non-retryable error counts
    • Automatic metrics collection on error creation

Error Utilities

  • Helper Functions: Utility functions for error handling
    • toKitiumError() - Normalize unknown errors to KitiumError
    • enrichError() - Safely merge additional context into errors
    • logError() - Structured logging with severity-aware routing
    • Integration with @kitiumai/logger for observability

Structured Logging

  • Severity-Aware Logging: Automatic log routing by severity
    • fatal/errorlog.error()
    • warninglog.warn()
    • infolog.info()
    • debuglog.debug()
    • Includes error code, message, severity, retry info, context, source, fingerprint

TypeScript Support

  • Full Type Safety: Comprehensive TypeScript definitions
    • All types exported from ./types
    • Strict type checking for error shapes
    • Type-safe error registry entries
    • Type-safe error context
    • Type-safe retry metadata

Documentation

  • Comprehensive Documentation: Complete API reference
    • README with quick start guide
    • Error code conventions and best practices
    • Usage examples for all features
    • Type definitions and API reference
    • Evaluation document comparing with big tech companies

Technical Details

Dependencies

  • @kitiumai/logger - Structured logging integration
  • @kitiumai/types - Shared type definitions
  • @kitiumai/utils-ts - TypeScript utility functions

Build Configuration

  • TypeScript 5.6+
  • ESM and CommonJS dual package support
  • Source maps for debugging
  • Type definitions included
  • ES2020 target

Package Exports

  • ESM: dist/index.mjs
  • CommonJS: dist/index.js
  • TypeScript: dist/index.d.ts

Standards Compliance

  • RFC 7807: Full Problem Details for HTTP APIs compliance
  • TypeScript: Strict type checking enabled
  • ESM/CommonJS: Dual package support
  • Semantic Versioning: Follows semver 2.0.0

Comparison with Industry Standards

This package matches or exceeds error handling capabilities of:

  • Microsoft Azure: RFC 7807 compliance, error registry pattern
  • Google Cloud: Error registry, request tracking, structured errors
  • AWS: Error codes, request IDs, retry information
  • Stripe: Typed error classes, rich metadata, documentation links
  • GitHub: Error codes, validation errors, rate limiting

Migration Notes

This is the initial release (v1.0.0). No migration needed.

Breaking Changes

None - this is the initial release.

Deprecations

None - this is the initial release.