Skip to main content

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

CodeError MessageSeverityQuick Fix
MBC-CMD-001version not matchHighFetch latest version or use version: -1
MBC-CMD-002item not foundMediumCheck if item exists before update
MBC-CMD-003Invalid input versionMediumUse latest version from getItem()

Tenant Errors

CodeError MessageSeverityQuick Fix
MBC-TNT-001Tenant not foundHighVerify tenant exists with listTenants()
MBC-TNT-002Tenant already existLowCheck existence before creating

Sequence & Task Errors

CodeError MessageSeverityQuick Fix
MBC-SEQ-001Sequence not foundMediumSequence auto-initializes on first use
MBC-TSK-001Task not foundMediumVerify task exists with NotFoundException

Validation Errors

CodeError MessageSeverityQuick Fix
MBC-VAL-001Validation failedMediumCheck DTO constraints and input data

DynamoDB Errors

CodeError MessageSeverityQuick Fix
MBC-DDB-001ProvisionedThroughputExceededExceptionHighImplement exponential backoff retry
MBC-DDB-002ConditionalCheckFailedExceptionHighRefresh item and retry with new version
MBC-DDB-003ResourceNotFoundExceptionCriticalVerify table exists and check env vars
MBC-DDB-004ValidationExceptionMediumAvoid empty strings, escape reserved words

Authentication Errors

CodeError MessageSeverityQuick Fix
MBC-COG-001NotAuthorizedExceptionHighRefresh token or re-authenticate
MBC-COG-002UserNotFoundExceptionMediumCheck user exists in pool
MBC-COG-003UserNotConfirmedExceptionMediumResend confirmation code

Import Module Errors

CodeError MessageSeverityQuick Fix
MBC-IMP-001Step Functions TimeoutCriticalUpgrade to v1.0.18+ for proper failure handling
MBC-IMP-002No import strategy foundHighRegister ImportStrategy in module config
MBC-IMP-003Import stuck in PROCESSINGHighCheck DynamoDB streams and SNS topics

Step Functions Errors

CodeError MessageSeverityQuick Fix
MBC-SFN-001TaskTimedOutHighIncrease Lambda timeout or chunk processing
MBC-SFN-002TaskFailedHighAdd proper error handling with sendTaskFailure

AWS Service Errors

CodeError MessageSeverityQuick Fix
MBC-S3-001NoSuchKeyMediumCheck object exists with headObject
MBC-S3-002AccessDeniedHighAdd required IAM permissions
MBC-SQS-001MessageNotInflightMediumProcess 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

Related Documentation

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:

  1. Upgrade to @mbc-cqrs-serverless/import@^1.0.18
  2. 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:

  1. Check CloudWatch logs for Lambda errors
  2. Verify DynamoDB streams are enabled on the import_tmp table
  3. Check if the SNS topic for import status notifications exists
  4. 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

StatusExceptionMeaningRecovery Strategy
400BadRequestExceptionInvalid input or business rule violationFix request data
401UnauthorizedExceptionAuthentication missing or invalidRefresh token or re-login
403ForbiddenExceptionAuthenticated but not authorizedCheck user permissions
404NotFoundExceptionResource not foundVerify resource exists
409ConflictExceptionVersion conflict (optimistic locking)Refresh and retry
422UnprocessableEntityExceptionValidation failedFix validation errors
429TooManyRequestsExceptionRate limit exceededImplement backoff retry
500InternalServerErrorExceptionUnexpected server errorCheck logs, report bug
502BadGatewayExceptionUpstream service errorRetry with backoff
503ServiceUnavailableExceptionService temporarily unavailableRetry later
504GatewayTimeoutExceptionUpstream service timeoutIncrease 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

  1. Enable debug logging:

    DEBUG=* npm run offline
  2. Check CloudWatch logs for Lambda errors:

    aws logs tail /aws/lambda/your-function-name --follow
  3. Use request IDs for tracing:

    console.log('RequestId:', context.awsRequestId);
  4. Verify environment variables:

    console.log('Config:', {
    table: process.env.DYNAMODB_TABLE_NAME,
    region: process.env.AWS_REGION,
    });
  5. Test locally with serverless-offline:

    npm run offline -- --stage dev

See Also