Skip to main content

Errors Reference

Error types and handling in saga-bus.

Error Hierarchy​

Base Error Classes​

SagaBusError​

Base class for all saga-bus errors.

import { SagaBusError } from '@saga-bus/core';

class SagaBusError extends Error {
// Unique error code
readonly code: string;

// Whether this error is retriable
readonly retryable: boolean;

// Additional context
readonly context?: Record<string, any>;

constructor(message: string, options?: SagaBusErrorOptions);
}

interface SagaBusErrorOptions {
code?: string;
retryable?: boolean;
context?: Record<string, any>;
cause?: Error;
}

Usage:

throw new SagaBusError('Something went wrong', {
code: 'CUSTOM_ERROR',
retryable: false,
context: { orderId: '123' },
});

Transient Errors​

Transient errors are automatically retried according to the retry policy.

TransientError​

Base class for retriable errors.

import { TransientError } from '@saga-bus/core';

class TransientError extends SagaBusError {
readonly retryable = true;
}

// Usage
throw new TransientError('Service temporarily unavailable');

NetworkError​

Network-related failures.

import { NetworkError } from '@saga-bus/core';

class NetworkError extends TransientError {
readonly code = 'NETWORK_ERROR';
}

// Thrown automatically on connection failures
// Can also be thrown manually:
throw new NetworkError('Connection refused');

TimeoutError​

Operation timeout.

import { TimeoutError } from '@saga-bus/core';

class TimeoutError extends TransientError {
readonly code = 'TIMEOUT_ERROR';

// Time waited before timeout (ms)
readonly timeout: number;
}

throw new TimeoutError('Operation timed out', { timeout: 30000 });

RateLimitError​

Rate limit exceeded.

import { RateLimitError } from '@saga-bus/core';

class RateLimitError extends TransientError {
readonly code = 'RATE_LIMIT_ERROR';

// When to retry (ms from now)
readonly retryAfter: number;
}

throw new RateLimitError('Too many requests', { retryAfter: 60000 });

Permanent Errors​

Permanent errors are not retried and typically sent to a dead letter queue.

PermanentError​

Base class for non-retriable errors.

import { PermanentError } from '@saga-bus/core';

class PermanentError extends SagaBusError {
readonly retryable = false;
}

ValidationError​

Input validation failure.

import { ValidationError } from '@saga-bus/core';

class ValidationError extends PermanentError {
readonly code = 'VALIDATION_ERROR';

// Validation errors by field
readonly errors?: Record<string, string[]>;
}

throw new ValidationError('Invalid input', {
errors: {
orderId: ['orderId is required'],
total: ['total must be positive'],
},
});

AuthorizationError​

Authorization/permission failure.

import { AuthorizationError } from '@saga-bus/core';

class AuthorizationError extends PermanentError {
readonly code = 'AUTHORIZATION_ERROR';

// Required permission
readonly requiredPermission?: string;
}

throw new AuthorizationError('Access denied', {
requiredPermission: 'orders:create',
});

BusinessRuleError​

Business logic violation.

import { BusinessRuleError } from '@saga-bus/core';

class BusinessRuleError extends PermanentError {
readonly code = 'BUSINESS_RULE_ERROR';

// Rule that was violated
readonly rule?: string;
}

throw new BusinessRuleError('Cannot cancel shipped order', {
rule: 'order_cancellation_policy',
});

System Errors​

ConcurrencyError​

Optimistic concurrency conflict.

import { ConcurrencyError } from '@saga-bus/core';

class ConcurrencyError extends SagaBusError {
readonly code = 'CONCURRENCY_ERROR';
readonly retryable = true;

// Expected version
readonly expectedVersion: number;

// Actual version
readonly actualVersion: number;
}

// Thrown by stores when version mismatch
// Automatically retried by saga-bus

Handling:

try {
await store.update(sagaName, correlationId, state, version);
} catch (error) {
if (error instanceof ConcurrencyError) {
// Another process updated the saga - reload and retry
const current = await store.getByCorrelationId(sagaName, correlationId);
// Re-process with current state
}
}

SagaProcessingError​

Error during saga handler execution.

import { SagaProcessingError } from '@saga-bus/core';

class SagaProcessingError extends SagaBusError {
readonly code = 'SAGA_PROCESSING_ERROR';

// Original error
readonly cause: Error;

// Saga information
readonly sagaName: string;
readonly correlationId: string;
readonly messageType: string;
}

ConfigurationError​

Invalid configuration.

import { ConfigurationError } from '@saga-bus/core';

class ConfigurationError extends SagaBusError {
readonly code = 'CONFIGURATION_ERROR';
readonly retryable = false;
}

throw new ConfigurationError('Missing required transport configuration');

TransportError​

Transport-level error.

import { TransportError } from '@saga-bus/core';

class TransportError extends SagaBusError {
readonly code = 'TRANSPORT_ERROR';

// Transport name
readonly transport: string;
}

Error Handling​

Custom Error Handler​

import { createBus, createErrorHandler } from '@saga-bus/core';

const errorHandler = createErrorHandler({
onTransientError: async (error, envelope) => {
logger.warn('Transient error, will retry', {
error: error.message,
messageType: envelope.message.type,
attempt: envelope.retryInfo.attempt,
});
},

onPermanentError: async (error, envelope) => {
logger.error('Permanent error, sending to DLQ', {
error: error.message,
messageType: envelope.message.type,
});

await alertService.notify({
severity: 'error',
title: 'Message processing failed',
details: { error, message: envelope.message },
});
},
});

const bus = createBus({
transport,
store,
sagas,
errorHandler,
});

Error Classification Middleware​

const errorClassifier = createMiddleware({
name: 'error-classifier',
onError: async ({ error }) => {
// Classify unknown errors
if (error.code === 'ECONNREFUSED') {
throw new NetworkError(error.message);
}

if (error.code === 'ETIMEDOUT') {
throw new TimeoutError(error.message);
}

if (error.response?.status === 429) {
throw new RateLimitError('Rate limited', {
retryAfter: error.response.headers['retry-after'] * 1000,
});
}

if (error.response?.status === 400) {
throw new ValidationError(error.message);
}

if (error.response?.status === 403) {
throw new AuthorizationError(error.message);
}

// Re-throw unclassified errors
throw error;
},
});

Try-Catch in Handlers​

const orderSaga = defineSaga({
handlers: {
ProcessPayment: async (ctx) => {
try {
const result = await paymentService.capture(ctx.message);

ctx.setState({
...ctx.state,
status: 'paid',
transactionId: result.transactionId,
});
} catch (error) {
if (error instanceof PaymentDeclinedError) {
// Business error - mark as failed, don't retry
ctx.setState({
...ctx.state,
status: 'payment_declined',
failureReason: error.message,
});
return;
}

// Re-throw for retry
throw error;
}
},
},
});

Creating Custom Errors​

import { SagaBusError } from '@saga-bus/core';

// Transient custom error
class ExternalServiceError extends SagaBusError {
constructor(service: string, message: string) {
super(message, {
code: 'EXTERNAL_SERVICE_ERROR',
retryable: true,
context: { service },
});
}
}

// Permanent custom error
class InsufficientFundsError extends SagaBusError {
constructor(available: number, required: number) {
super(`Insufficient funds: ${available} < ${required}`, {
code: 'INSUFFICIENT_FUNDS',
retryable: false,
context: { available, required },
});
}
}

// Usage
throw new ExternalServiceError('payment-gateway', 'Gateway timeout');
throw new InsufficientFundsError(50, 100);

Error Codes Reference​

CodeError ClassRetriableDescription
NETWORK_ERRORNetworkErrorYesNetwork connectivity issue
TIMEOUT_ERRORTimeoutErrorYesOperation timed out
RATE_LIMIT_ERRORRateLimitErrorYesRate limit exceeded
VALIDATION_ERRORValidationErrorNoInput validation failed
AUTHORIZATION_ERRORAuthorizationErrorNoPermission denied
BUSINESS_RULE_ERRORBusinessRuleErrorNoBusiness logic violation
CONCURRENCY_ERRORConcurrencyErrorYesOptimistic locking conflict
SAGA_PROCESSING_ERRORSagaProcessingErrorVariesHandler execution failed
CONFIGURATION_ERRORConfigurationErrorNoInvalid configuration
TRANSPORT_ERRORTransportErrorVariesTransport-level failure

Best Practices​

  1. Classify errors correctly - Use appropriate error types for proper retry behavior
  2. Include context - Add relevant information to error context
  3. Preserve error chains - Use cause option to preserve original errors
  4. Log at boundaries - Log errors at the middleware level
  5. Alert on permanent errors - These require manual attention
  6. Monitor retry rates - High retry rates indicate systemic issues

See Also​