Error Catalog
This catalog provides comprehensive documentation of errors encountered in MBC CQRS Serverless, including their causes, solutions, and recovery strategies.
Quick Reference
Use this table to quickly identify errors and jump to solutions.
Command & Data Errors
| Code | Error Message | Severity | Quick Fix |
|---|---|---|---|
| MBC-CMD-001 | version not match | High | Fetch latest version or use version: -1 |
| MBC-CMD-002 | item not found | Medium | Check if item exists before update |
| MBC-CMD-003 | Invalid input version | Medium | Use latest version from getItem() |
Tenant Errors
| Code | Error Message | Severity | Quick Fix |
|---|---|---|---|
| MBC-TNT-001 | Tenant not found | High | Verify tenant exists with listTenants() |
| MBC-TNT-002 | Tenant already exist | Low | Check existence before creating |
Sequence & Task Errors
| Code | Error Message | Severity | Quick Fix |
|---|---|---|---|
| MBC-SEQ-001 | Sequence not found | Medium | Sequence auto-initializes on first use |
| MBC-TSK-001 | Task not found | Medium | Verify task exists with NotFoundException |
Validation Errors
| Code | Error Message | Severity | Quick Fix |
|---|---|---|---|
| MBC-VAL-001 | Validation failed | Medium | Check DTO constraints and input data |
DynamoDB Errors
| Code | Error Message | Severity | Quick Fix |
|---|---|---|---|
| MBC-DDB-001 | ProvisionedThroughputExceededException | High | Implement exponential backoff retry |
| MBC-DDB-002 | ConditionalCheckFailedException | High | Refresh item and retry with new version |
| MBC-DDB-003 | ResourceNotFoundException | Critical | Verify table exists and check env vars |
| MBC-DDB-004 | ValidationException | Medium | Avoid empty strings, escape reserved words |
Authentication Errors
| Code | Error Message | Severity | Quick Fix |
|---|---|---|---|
| MBC-COG-001 | NotAuthorizedException | High | Refresh token or re-authenticate |
| MBC-COG-002 | UserNotFoundException | Medium | Check user exists in pool |
| MBC-COG-003 | UserNotConfirmedException | Medium | Resend confirmation code |
Import Module Errors
| Code | Error Message | Severity | Quick Fix |
|---|---|---|---|
| MBC-IMP-001 | Step Functions Timeout | Critical | Upgrade to v1.0.18+ for proper failure handling |
| MBC-IMP-002 | No import strategy found | High | Register ImportStrategy in module config |
| MBC-IMP-003 | Import stuck in PROCESSING | High | Check DynamoDB streams and SNS topics |
Step Functions Errors
| Code | Error Message | Severity | Quick Fix |
|---|---|---|---|
| MBC-SFN-001 | TaskTimedOut | High | Increase Lambda timeout or chunk processing |
| MBC-SFN-002 | TaskFailed | High | Add proper error handling with sendTaskFailure |
AWS Service Errors
| Code | Error Message | Severity | Quick Fix |
|---|---|---|---|
| MBC-S3-001 | NoSuchKey | Medium | Check object exists with headObject |
| MBC-S3-002 | AccessDenied | High | Add required IAM permissions |
| MBC-SQS-001 | MessageNotInflight | Medium | Process within visibility timeout |
Command Service Errors
BadRequestException: "The input is not a valid, item not found or version not match"
Location: packages/core/src/commands/command.service.ts
Cause: Optimistic locking failure. The version number in the request does not match the current version in the database.
Solution:
// Option 1: Fetch latest version before update
const latest = await dataService.getItem({ pk, sk });
await commandService.publishPartialUpdateSync({
pk,
sk,
version: latest.version,
name: 'Updated Name',
}, options);
// Option 2: Use version: -1 for auto-fetch (async mode only)
await commandService.publishPartialUpdateAsync({
pk,
sk,
version: -1,
name: 'Updated Name',
}, options);
// Option 3: Implement retry logic
async function updateWithRetry(data, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const latest = await dataService.getItem({ pk: data.pk, sk: data.sk });
return await commandService.publishPartialUpdateSync({
...data,
version: latest.version,
}, options);
} catch (error) {
if (error.message.includes('version not match') && i < maxRetries - 1) {
await new Promise(r => setTimeout(r, 100 * (i + 1)));
continue;
}
throw error;
}
}
}
BadRequestException: "The input key is not a valid, item not found"
Location: packages/core/src/commands/command.service.ts
Cause: Attempting to update an item that does not exist in the database.
Solution:
// Check if item exists first
const existing = await dataService.getItem({ pk, sk });
if (!existing) {
// Create new item
await commandService.publishAsync(newItem, options);
} else {
// Update existing item
await commandService.publishPartialUpdateAsync({
pk,
sk,
version: existing.version,
...updates,
}, options);
}
BadRequestException: "Invalid input version"
Location: packages/core/src/commands/command.service.ts
Cause: Using a version in publishSync that does not match the latest saved version.
Solution: Fetch the latest item and use its version, or use version: -1 with async methods.
Tenant Errors
BadRequestException: "Tenant not found"
Location: packages/tenant/src/services/tenant.service.ts
Cause: The specified tenant does not exist or has been deleted.
Solution:
// Verify tenant exists
try {
const tenant = await tenantService.getTenant(tenantCode);
} catch (error) {
if (error.message === 'Tenant not found') {
// List available tenants
const tenants = await tenantService.listTenants();
console.log('Available tenants:', tenants.items.map(t => t.code));
}
}
BadRequestException: "Tenant already exist"
Location: packages/tenant/src/services/tenant.service.ts
Cause: Attempting to create a tenant with an existing code.
Solution:
// Check if tenant exists before creating
const existing = await tenantService.getTenant(tenantCode).catch(() => null);
if (existing) {
console.log('Tenant already exists, using existing tenant');
} else {
await tenantService.createTenant({ code: tenantCode, name: tenantName });
}
Sequence Errors
BadRequestException: "Sequence not found"
Location: packages/sequence/src/services/sequence.service.ts
Cause: The requested sequence key does not exist.
Solution:
// Generate sequence - auto-initializes on first use
try {
const result = await sequencesService.generateSequenceItem(
{
tenantCode,
typeCode: 'ORDER',
},
{ invokeContext },
);
} catch (error) {
// If error persists, check DynamoDB table permissions
}
Task Errors
NotFoundException: "Task not found"
Location: packages/task/src/task.controller.ts
Cause: The specified task does not exist or has been completed/deleted.
Solution:
// Verify task status before operations
const task = await taskService.getTask({ pk, sk });
if (!task) {
throw new NotFoundException('Task not found');
}
if (task.status === 'completed') {
throw new BadRequestException('Task already completed');
}
Validation Errors
BadRequestException: "Validation failed"
Location: packages/core/src/pipe/class.validation.pipe.ts
Cause: The request DTO failed class-validator validation.
Common Validation Errors:
// Example DTO with validation
export class CreateOrderDto {
@IsNotEmpty({ message: 'Name is required' })
@IsString()
@MaxLength(100)
name: string;
@IsNotEmpty({ message: 'Code is required' })
@Matches(/^[A-Z0-9-]+$/, { message: 'Code must be uppercase alphanumeric' })
code: string;
@IsOptional()
@IsNumber()
@Min(0)
amount?: number;
}
// Common validation errors and fixes:
// - "name must be a string" -> Ensure name is string type
// - "code should not be empty" -> Provide code value
// - "amount must not be less than 0" -> Use positive number
Solution:
// Validate before sending
import { validate } from 'class-validator';
import { plainToInstance } from 'class-transformer';
const dto = plainToInstance(CreateOrderDto, requestBody);
const errors = await validate(dto);
if (errors.length > 0) {
console.log('Validation errors:', errors.map(e => e.constraints));
}
DynamoDB Errors
ProvisionedThroughputExceededException
Location: AWS DynamoDB
Cause: Read or write capacity has been exceeded on on-demand or provisioned tables.
Solution:
// Implement exponential backoff retry
async function withRetry<T>(
fn: () => Promise<T>,
maxRetries = 5,
baseDelay = 100
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (error.name === 'ProvisionedThroughputExceededException') {
const delay = baseDelay * Math.pow(2, i) + Math.random() * 100;
console.log(`Throughput exceeded, retrying in ${delay}ms...`);
await new Promise(r => setTimeout(r, delay));
continue;
}
throw error;
}
}
throw new Error('Max retries exceeded');
}
Prevention:
- Use on-demand capacity mode for unpredictable workloads
- Implement request batching to reduce write operations
- Use DAX for read-heavy workloads
ConditionalCheckFailedException
Location: AWS DynamoDB
Cause: Optimistic locking condition failed (version mismatch) or unique constraint violation.
Solution:
// Handle conditional check failure
try {
await commandService.publishSync(item, options);
} catch (error) {
if (error.name === 'ConditionalCheckFailedException') {
// Refresh and retry
const latest = await dataService.getItem({ pk, sk });
await commandService.publishSync({
...item,
version: latest.version,
}, options);
}
}
ResourceNotFoundException
Location: AWS DynamoDB
Cause: The specified table or index does not exist.
Solution:
# Verify table exists
aws dynamodb describe-table --table-name your-table-name
# Check environment variable
echo $DYNAMODB_TABLE_NAME
ValidationException: "One or more parameter values were invalid"
Location: AWS DynamoDB
Cause: Invalid key structure, empty string for non-key attribute, or reserved word conflict.
Solution:
// Avoid empty strings
const item = {
pk: 'ORDER#tenant001',
sk: 'ORDER#ORD001',
name: value || null, // Use null instead of empty string
};
// Use expression attribute names for reserved words
const params = {
ExpressionAttributeNames: {
'#name': 'name',
'#status': 'status',
},
};
Cognito Authentication Errors
NotAuthorizedException
Location: AWS Cognito
Cause: Invalid credentials or token expired.
Solution:
// Frontend: Refresh token
try {
await Auth.currentSession(); // Auto-refreshes if needed
} catch (error) {
if (error.name === 'NotAuthorizedException') {
// Redirect to login
await Auth.signOut();
window.location.href = '/login';
}
}
UserNotFoundException
Location: AWS Cognito
Cause: User does not exist in the user pool.
Solution:
// Check user exists before operations
try {
const user = await adminGetUser({ Username: email });
} catch (error) {
if (error.name === 'UserNotFoundException') {
// Create new user or show registration form
}
}
UserNotConfirmedException
Location: AWS Cognito
Cause: User has not confirmed their email/phone.
Solution:
try {
await Auth.signIn(email, password);
} catch (error) {
if (error.name === 'UserNotConfirmedException') {
// Resend confirmation code
await Auth.resendSignUp(email);
// Redirect to confirmation page
}
}
Import Module Errors
For API details and usage patterns, see ImportStatusHandler API. For version history, see Changelog v1.0.18.
Step Functions Timeout (Import Job)
Location: packages/import/src/event/import-status.queue.event.handler.ts
Symptom: Step Functions execution stays in RUNNING state indefinitely for import jobs.
Cause: Prior to version 1.0.18, the ImportStatusHandler only sent SendTaskSuccessCommand for completed jobs. When an import job failed, no callback was sent to Step Functions, causing it to wait indefinitely for the waitForTaskToken callback.
Solution (Fixed in 1.0.18+):
The handler now properly sends SendTaskFailureCommand when import jobs fail:
// Internal behavior (automatic, no user action needed):
// - COMPLETED status → SendTaskSuccessCommand
// - FAILED status → SendTaskFailureCommand
If you're on an older version:
- Upgrade to
@mbc-cqrs-serverless/import@^1.0.18 - For stuck executions, manually stop them via AWS Console or CLI:
aws stepfunctions stop-execution --execution-arn <execution-arn>
BadRequestException: "No import strategy found for table: {tableName}"
Location: packages/import/src/import.service.ts
Cause: No import strategy is registered for the specified table name.
Solution: Register an import strategy when configuring the ImportModule:
ImportModule.register({
profiles: [
{
tableName: 'your-table-name', // Must match the tableName in your import request
importStrategy: YourImportStrategy,
processStrategy: YourProcessStrategy,
},
],
})
Import Job Stuck in PROCESSING Status
Location: packages/import/src/event/import.queue.event.handler.ts
Cause: An error occurred during import processing but the job status wasn't properly updated, or DynamoDB streams failed to trigger the next step.
Solution:
- Check CloudWatch logs for Lambda errors
- Verify DynamoDB streams are enabled on the import_tmp table
- Check if the SNS topic for import status notifications exists
- Clean up stuck records:
await importService.updateStatus(
{ pk: 'CSV_IMPORT#tenant', sk: 'table#taskCode' },
ImportStatusEnum.FAILED,
{ error: 'Manual cleanup' }
);
Step Functions Errors
TaskTimedOut
Location: AWS Step Functions
Cause: Lambda function did not respond within the configured timeout.
Solution:
// Increase Lambda timeout in serverless.yml
functions:
processTask:
handler: handler.process
timeout: 900 # 15 minutes max
// Or break into smaller chunks
async function processInChunks(items, chunkSize = 100) {
for (let i = 0; i < items.length; i += chunkSize) {
const chunk = items.slice(i, i + chunkSize);
await processChunk(chunk);
}
}
TaskFailed
Location: AWS Step Functions
Cause: Lambda function threw an unhandled error.
Solution:
// Proper error handling with Step Functions
export async function handler(event: StepFunctionEvent) {
try {
const result = await processTask(event.input);
// Send success callback
await sfn.sendTaskSuccess({
taskToken: event.taskToken,
output: JSON.stringify(result),
}).promise();
} catch (error) {
// Send failure callback
await sfn.sendTaskFailure({
taskToken: event.taskToken,
error: error.name,
cause: error.message,
}).promise();
}
}
S3 Errors
NoSuchKey
Location: AWS S3
Cause: The specified object does not exist in the bucket.
Solution:
// Check if object exists before downloading
try {
await s3.headObject({ Bucket: bucket, Key: key }).promise();
const data = await s3.getObject({ Bucket: bucket, Key: key }).promise();
} catch (error) {
if (error.code === 'NoSuchKey' || error.code === 'NotFound') {
console.log('File does not exist:', key);
return null;
}
throw error;
}
AccessDenied
Location: AWS S3
Cause: IAM policy does not allow the requested operation.
Solution:
# Add required permissions in serverless.yml
provider:
iam:
role:
statements:
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
- s3:DeleteObject
Resource:
- arn:aws:s3:::${self:custom.bucketName}/*
SQS Errors
MessageNotInflight
Location: AWS SQS
Cause: Attempting to delete or change visibility of a message that is no longer in flight.
Solution:
// Process messages within visibility timeout
export async function handler(event: SQSEvent) {
for (const record of event.Records) {
try {
await processMessage(record);
// Message auto-deleted on successful return
} catch (error) {
// Throw to keep message in queue for retry
throw error;
}
}
}
HTTP Status Code Reference
| Status | Exception | Meaning | Recovery Strategy |
|---|---|---|---|
| 400 | BadRequestException | Invalid input or business rule violation | Fix request data |
| 401 | UnauthorizedException | Authentication missing or invalid | Refresh token or re-login |
| 403 | ForbiddenException | Authenticated but not authorized | Check user permissions |
| 404 | NotFoundException | Resource not found | Verify resource exists |
| 409 | ConflictException | Version conflict (optimistic locking) | Refresh and retry |
| 422 | UnprocessableEntityException | Validation failed | Fix validation errors |
| 429 | TooManyRequestsException | Rate limit exceeded | Implement backoff retry |
| 500 | InternalServerErrorException | Unexpected server error | Check logs, report bug |
| 502 | BadGatewayException | Upstream service error | Retry with backoff |
| 503 | ServiceUnavailableException | Service temporarily unavailable | Retry later |
| 504 | GatewayTimeoutException | Upstream service timeout | Increase timeout or optimize |
Error Handling Best Practices
1. Centralized Error Handler
// Create a global exception filter
@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status = exception instanceof HttpException
? exception.getStatus()
: 500;
const message = exception instanceof HttpException
? exception.message
: 'Internal server error';
// Log error with context
console.error({
timestamp: new Date().toISOString(),
path: request.url,
method: request.method,
status,
message,
stack: exception instanceof Error ? exception.stack : undefined,
});
response.status(status).json({
statusCode: status,
message,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
2. Retry with Exponential Backoff
async function retryWithBackoff<T>(
operation: () => Promise<T>,
options: {
maxRetries?: number;
baseDelay?: number;
maxDelay?: number;
retryableErrors?: string[];
} = {}
): Promise<T> {
const {
maxRetries = 3,
baseDelay = 100,
maxDelay = 10000,
retryableErrors = [
'ProvisionedThroughputExceededException',
'ThrottlingException',
'ServiceUnavailable',
],
} = options;
let lastError: Error;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;
if (!retryableErrors.includes(error.name) || attempt === maxRetries) {
throw error;
}
const delay = Math.min(
baseDelay * Math.pow(2, attempt) + Math.random() * 100,
maxDelay
);
console.log(`Retry attempt ${attempt + 1} after ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
3. Circuit Breaker Pattern
class CircuitBreaker {
private failures = 0;
private lastFailure: number = 0;
private state: 'closed' | 'open' | 'half-open' = 'closed';
constructor(
private threshold: number = 5,
private timeout: number = 60000
) {}
async execute<T>(operation: () => Promise<T>): Promise<T> {
if (this.state === 'open') {
if (Date.now() - this.lastFailure > this.timeout) {
this.state = 'half-open';
} else {
throw new Error('Circuit breaker is open');
}
}
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.lastFailure = Date.now();
if (this.failures >= this.threshold) {
this.state = 'open';
}
}
}
Debugging Tips
-
Enable debug logging:
DEBUG=* npm run offline -
Check CloudWatch logs for Lambda errors:
aws logs tail /aws/lambda/your-function-name --follow -
Use request IDs for tracing:
console.log('RequestId:', context.awsRequestId); -
Verify environment variables:
console.log('Config:', {
table: process.env.DYNAMODB_TABLE_NAME,
region: process.env.AWS_REGION,
}); -
Test locally with serverless-offline:
npm run offline -- --stage dev
See Also
- Debugging Guide - Detailed debugging procedures
- Common Issues - Frequently encountered problems
- Monitoring and Logging - Production monitoring setup