@kitiumai/error
v3.0.0
Published
Enterprise-grade error primitives for Kitium products: rich metadata, HTTP/Problem Details mapping, observability, and registry-driven error governance.
Maintainers
Keywords
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/errorQuick 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
