@kitiumai/error
Version:
Enterprise-grade error primitives for Kitium products: rich metadata, HTTP/Problem Details mapping, observability, and registry-driven error governance.
1,282 lines (1,013 loc) • 32.4 kB
Markdown
# @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
```bash
npm install @kitiumai/error
# or
pnpm add @kitiumai/error
# or
yarn add @kitiumai/error
```
## Quick start
### Basic Usage
```ts
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
```ts
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
```ts
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
```ts
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
```ts
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
```ts
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
```ts
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
```ts
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
```ts
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
```ts
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
```ts
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:**
```ts
constructor(shape: ErrorShape, validateCode?: boolean)
```
**Properties:**
```ts
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:**
```ts
toJSON(): ErrorShape // Serialize error to JSON
```
**Example:**
```ts
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:**
```ts
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:**
```ts
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:**
```ts
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:**
```ts
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:**
```ts
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:**
```ts
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:**
```ts
function logError(error: KitiumError): void;
```
**Parameters:**
- `error`: `KitiumError` instance to log
**Example:**
```ts
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:**
```ts
function problemDetailsFrom(error: KitiumError): ProblemDetails;
```
**Parameters:**
- `error`: `KitiumError` instance
**Returns:** `ProblemDetails` object compliant with RFC 7807
**Example:**
```ts
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:**
```ts
function isValidErrorCode(code: string): boolean;
```
**Parameters:**
- `code`: Error code to validate
**Returns:** `true` if valid, `false` otherwise
**Example:**
```ts
isValidErrorCode('auth/forbidden'); // true
isValidErrorCode('Auth/Forbidden'); // false
isValidErrorCode('auth.forbidden'); // false
```
#### `validateErrorCode(code)`
Validates error code format and throws if invalid.
**Signature:**
```ts
function validateErrorCode(code: string): void;
```
**Parameters:**
- `code`: Error code to validate
**Throws:** `Error` if code format is invalid
**Example:**
```ts
try {
validateErrorCode('invalid code');
} catch (error) {
// Error: Invalid error code format: "invalid code"...
}
```
#### `getErrorFingerprint(error)`
Generates fingerprint for error grouping in observability systems.
**Signature:**
```ts
function getErrorFingerprint(error: KitiumError | ErrorShape): string;
```
**Parameters:**
- `error`: `KitiumError` instance or `ErrorShape` object
**Returns:** Fingerprint string for error grouping
**Example:**
```ts
const error = new KitiumError({ code: 'auth/forbidden', kind: 'auth' });
const fingerprint = getErrorFingerprint(error);
// "auth/forbidden:auth"
```
#### `getErrorMetrics()`
Returns current error metrics snapshot.
**Signature:**
```ts
function getErrorMetrics(): ErrorMetrics;
```
**Returns:** `ErrorMetrics` object with error statistics
**Example:**
```ts
const metrics = getErrorMetrics();
console.log(metrics.totalErrors);
console.log(metrics.errorsByKind);
```
#### `resetErrorMetrics()`
Resets error metrics (useful for testing).
**Signature:**
```ts
function resetErrorMetrics(): void;
```
**Example:**
```ts
beforeEach(() => {
resetErrorMetrics();
});
```
### Types
#### `ErrorSeverity`
Error severity levels for logging and monitoring.
```ts
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.
```ts
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.
```ts
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.
```ts
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:**
```ts
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.
```ts
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.
```ts
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.
```ts
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.
```ts
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.
```ts
interface ErrorRegistryEntry extends ErrorShape {
readonly fingerprint?: string;
readonly toProblem?: (error: ErrorShape) => ProblemDetails;
}
```
## Advanced Usage
### Custom Problem Details Rendering
```ts
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
```ts
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
```ts
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
```ts
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`.