# AWS Amplify Shared Utilities

A TypeScript package providing standardized utilities for AWS Amplify projects with error handling, structured logging, type-safe database operations, and Lambda middleware.

## 🎯 Features

- **Error Handling**: Standardized error throwing with structured logging and context
- **Logging**: Environment-aware logging with AWS CloudWatch integration and Lambda detection
- **Query Factory**: Type-safe CRUD operations for Amplify Data models
- **Middleware System**: Composable middleware chain for Lambda functions and GraphQL resolvers
- **REST APIs**: Complete REST API middleware with validation and error handling
- **WebSocket Support**: Complete WebSocket API middleware with error handling and validation
- **Request Validation**: Yup-based validation with built-in patterns

## 📦 Installation

```bash
npm install @your-org/amplify-shared-utilities
```

## 🏗️ Architecture Overview

This package is organized into four core modules, each with specific responsibilities:

### 1. Error Module (`/error`)

**Purpose**: Centralized error handling with structured context and WebSocket-specific error types.

**Key Components**:

- `error.ts`: Core error utilities (`throwError`, `extractErrorMessage`, `createErrorContext`)
- `websocket.ts`: WebSocket-specific error handling with predefined error codes
- `index.ts`: Main exports for both REST and WebSocket error handling

**Error Library Integration**: The middleware system automatically detects errors thrown by `throwError()` and preserves their context through the middleware chain.

### 2. Logging Module (`/log`)

**Purpose**: Environment-aware structured logging with CloudWatch integration.

**Key Features**:

- Automatic environment detection (development, production, aws-lambda)
- Structured JSON logging for CloudWatch
- Context-aware logging with request IDs and user context
- Lambda context capture (request ID, function name, X-Ray trace)

### 3. Query Factory (`/queries`)

**Purpose**: Type-safe CRUD operations for Amplify Data models.

**Key Components**:

- `QueryFactory.ts`: Main factory for creating type-safe model operations
- `ClientManager.ts`: Singleton client management with connection pooling
- `initialize.ts`: One-time setup for Amplify client configuration
- `types.ts`: Comprehensive TypeScript types for all operations

### 4. Middleware System (`/middleware`)

**Purpose**: Composable middleware chains for different AWS services.

**Sub-modules**:

- **Core**: `middlewareChain.ts` - Generic middleware chain implementation
- **REST**: Complete REST API middleware with validation and error handling
- **GraphQL**: GraphQL resolver middleware with error handling
- **WebSocket**: Complete WebSocket API middleware system
- **Utils**: Validation patterns and sanitization utilities

## 🚨 Error Handling

### Core Error Utilities

```typescript
import {
  throwError,
  extractErrorMessage,
  createErrorContext,
} from '@your-org/amplify-shared-utilities';

// Simple error with automatic context
throwError('User not found');

// Error with structured context
throwError(
  'Database operation failed',
  createErrorContext({
    userId: '123',
    operation: 'create',
    database: 'users',
  }),
);

// Wrap existing error with context
try {
  await databaseOperation();
} catch (error) {
  throwError('Database operation failed', error);
}

// Extract consistent error messages
const message = extractErrorMessage(error);
```

### WebSocket Error Handling

```typescript
import {
  WebSocketErrors,
  WebSocketErrorCodes,
  throwWebSocketError,
  isWebSocketError,
} from '@your-org/amplify-shared-utilities';

// Use predefined WebSocket error codes
throwWebSocketError(WebSocketErrorCodes.INVALID_MESSAGE_FORMAT, {
  message: 'Invalid JSON format',
  receivedData: rawMessage,
});

// Check if error is WebSocket-specific
if (isWebSocketError(error)) {
  const { code, message, context } = extractWebSocketErrorInfo(error);
  // Handle WebSocket error specifically
}
```

### Error Context in Middleware

The middleware system automatically preserves error context:

```typescript
// Error thrown in middleware preserves all context
const middleware = async (input, next) => {
  try {
    return await next();
  } catch (error) {
    // Error automatically includes:
    // - middlewareName: 'auth'
    // - middlewareChain: ['auth', 'validation', 'handler']
    // - All original error context from throwError()
    throw error;
  }
};
```

## 📝 Logging

### Environment-Aware Logging

```typescript
import { logger, LogLevel } from '@your-org/amplify-shared-utilities';

// Set context for all subsequent logs
logger.setContext({
  userId: '123',
  operation: 'create',
  requestId: 'req-456',
});

// Log levels with automatic environment detection
logger.error('Critical error', { errorCode: 500 });
logger.warn('Deprecated API usage');
logger.info('Operation completed', { result: data });
logger.debug('Debug info', { state: complexObject });

// Environment detection
logger.getEnvironment(); // 'production', 'development', 'aws-lambda'
logger.isStructuredLoggingEnabled(); // true in production/Lambda

// Clear context when done
logger.clearContext();
```

### Lambda Integration

Automatically captures Lambda context in structured logs:

```typescript
// In Lambda environment, logs include:
{
  "timestamp": "2024-01-01T00:00:00.000Z",
  "level": "info",
  "message": "Operation completed",
  "awsRequestId": "req-123",
  "functionName": "myFunction",
  "xrayTraceId": "1-abc123-def456",
  "environment": "aws-lambda"
}
```

## 🗃️ Query Factory

### Initialization

```typescript
import {
  QueryFactory,
  initializeQueries,
} from '@your-org/amplify-shared-utilities';
import { MainTypes } from './schema';
import outputs from './amplify_outputs.json';

// Initialize once at startup
await initializeQueries<MainTypes>(outputs);

// Create type-safe QueryFactory for any model
const UserQueries = await QueryFactory<'User', MainTypes>({
  name: 'User',
});
```

### CRUD Operations

```typescript
// Create with type safety
const user = await UserQueries.create({
  input: {
    username: 'john',
    email: 'john@example.com',
    validated: false,
  },
});

// Get with automatic error handling
const foundUser = await UserQueries.get({
  input: { userId: user.id },
});

// List with filtering and pagination
const users = await UserQueries.list({
  filter: { validated: { eq: true } },
  limit: 10,
  nextToken: 'next-page-token',
});

// Update with partial data
await UserQueries.update({
  input: {
    userId: user.id,
    validated: true,
    lastLoginAt: new Date().toISOString(),
  },
});

// Delete with validation
await UserQueries.delete({
  input: { userId: user.id },
});
```

### Client Management

```typescript
import { ClientManager } from '@your-org/amplify-shared-utilities';

// Get singleton client manager
const manager = ClientManager.getInstance('default');

// Get initialized client
const client = await manager.getClient<YourTypes>();

// Client is automatically configured with:
// - Amplify Data client
// - Authentication
// - Environment-specific settings
```

## 🔧 Middleware System

### Core Middleware Chain

```typescript
import { MiddlewareChain } from '@your-org/amplify-shared-utilities';

// Create generic middleware chain
const chain = new MiddlewareChain<MyInput, MyOutput>({
  enableDebugLogging: true,
  onError: (error, middlewareName) => {
    console.error(`Middleware ${middlewareName} failed:`, error);
  },
});

// Add middleware in execution order
chain
  .use('auth', authMiddleware)
  .use('logging', loggingMiddleware)
  .use('validation', validationMiddleware);

// Execute with final handler
const result = await chain.execute(input, async input => {
  return await businessLogic(input);
});
```

### REST API Gateway Lambda Functions

```typescript
import {
  createRestChain,
  wrapRestHandler,
  createRestErrorHandler,
  createRestRequestLogger,
  createRestRequestValidator,
  createRestModelInitializer,
  getValidatedBody,
  getModelsFromInput,
  getModelFromInput,
  ValidationPatterns,
} from '@your-org/amplify-shared-utilities';
import * as yup from 'yup';

// Create REST middleware chain
const chain = createRestChain<MainTypes>({
  enableDebugLogging: true,
});

// Add REST-specific middleware
chain.use(
  'errorHandler',
  createRestErrorHandler({
    includeStackTrace: process.env.NODE_ENV === 'development',
    defaultContext: { service: 'message-api', version: '1.0.0' },
  }),
);

chain.use(
  'requestLogger',
  createRestRequestLogger({
    logEvent: true,
    logResponse: true,
    logTiming: true,
    logRequestBody: true,
  }),
);

chain.use(
  'modelInitializer',
  createRestModelInitializer({
    schema: MainSchema,
    amplifyOutputs: outputs,
    entities: ['Message'],
    timeout: 5000,
  }),
);

// Add validation middleware
chain.use(
  'validator',
  createRestRequestValidator({
    bodySchema: yup.object({
      content: yup.string().required(),
      type: yup.string().oneOf(['info', 'warning', 'error']).required(),
      targetId: yup.string().required(),
    }),
    stripUnknown: true,
    abortEarly: false,
  }),
);

// Business logic handler
const messageHandler = async input => {
  const MessageModel = getModelFromInput(input, 'Message');
  const validatedBody = getValidatedBody(input.event);

  const message = await MessageModel.create({
    input: validatedBody,
  });

  return {
    statusCode: 201,
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      success: true,
      data: message,
    }),
  };
};

// Wrap handler - single endpoint pattern
export const handler = wrapRestHandler(chain, messageHandler);
```

### REST Middleware Components

#### Error Handler (`createRestErrorHandler`)

```typescript
// Environment-aware error responses
const errorHandler = createRestErrorHandler({
  includeStackTrace: process.env.NODE_ENV === 'development',
  defaultContext: { service: 'api' },
  forceStructuredLogging: true,
});

// Automatically handles:
// - Development: Full error details with stack traces
// - Production: Sanitized error messages
// - Request ID tracking
// - Structured error context
```

#### Request Validator (`createRestRequestValidator`)

```typescript
// Validate request components
const validator = createRestRequestValidator({
  bodySchema: yup.object({
    name: yup.string().required(),
    email: yup.string().email().required(),
  }),
  queryParametersSchema: yup.object({
    page: yup.number().positive().optional(),
    limit: yup.number().positive().max(100).optional(),
  }),
  pathParametersSchema: yup.object({
    id: yup.string().uuid().required(),
  }),
  headersSchema: yup.object({
    authorization: yup.string().required(),
  }),
  stripUnknown: true,
  abortEarly: false,
  validateOnlyOnMethods: ['POST', 'PUT', 'PATCH'],
});

// Access validated data in handlers
const validatedBody = getValidatedBody(event);
const validatedQuery = getValidatedQuery(event);
const validatedPath = getValidatedPath(event);
const validatedHeaders = getValidatedHeaders(event);
```

#### Model Initializer (`createRestModelInitializer`)

```typescript
// Initialize Amplify Data models
const modelInitializer = createRestModelInitializer({
  schema: MainSchema,
  amplifyOutputs: outputs,
  entities: ['User', 'Post', 'Comment'],
  clientKey: 'default',
  timeout: 5000,
});

// Access models in handlers
const models = getModelsFromInput(input);
const UserModel = getModelFromInput(input, 'User');
const PostModel = getModelFromInput(input, 'Post');
```

#### Request Logger (`createRestRequestLogger`)

```typescript
// Structured request/response logging
const logger = createRestRequestLogger({
  logEvent: true,
  logResponse: true,
  logTiming: true,
  maxDepth: 3,
  excludeEventFields: ['body', 'headers'],
  excludeResponseFields: ['body'],
  defaultContext: { service: 'api' },
  logLevel: 'info',
  logRequestBody: true,
  forceStructuredLogging: true,
});
```

### Single Endpoint Pattern

The REST middleware is optimized for AWS API Gateway's single Lambda per resource pattern:

```typescript
// Perfect for CDK REST API deployment
// One Lambda function per resource endpoint
// Each function handles one HTTP method (typically POST)
// API Gateway handles CORS and method routing

const chain = createRestChain<MessageTypes>();
chain.use('errorHandler', createRestErrorHandler());
chain.use('logger', createRestRequestLogger());
chain.use('validator', createRestRequestValidator({ bodySchema }));

const handler = wrapRestHandler(chain, businessLogicHandler);
```

### GraphQL Resolvers

```typescript
import {
  MiddlewareChain,
  wrapGraphQLResolver,
  createGraphQLErrorHandler,
} from '@your-org/amplify-shared-utilities';

// Create GraphQL-specific middleware chain
const chain = MiddlewareChain.createGraphQLChain<
  { id: string },
  unknown,
  { id: string; name: string }
>();

chain.use(
  'graphQLErrorHandler',
  createGraphQLErrorHandler({
    includeStackTrace: process.env.NODE_ENV === 'development',
    defaultContext: { service: 'product-api', version: '1.0.0' },
  }),
);

const mainHandler = async (source: any, args: any, context: any, info: any) => {
  const { productId } = args;
  return await getProductById(productId);
};

export const handler = wrapGraphQLResolver(chain, mainHandler);
```

### WebSocket APIs

```typescript
import {
  createWebSocketChain,
  wrapWebSocketHandler,
  createWebSocketErrorHandler,
  createWebSocketRequestLogger,
  createWebSocketRequestValidator,
  createWebSocketModelInitializer,
  getModelFromInput,
  WebSocketErrorCodes,
} from '@your-org/amplify-shared-utilities';
import * as yup from 'yup';

// Create WebSocket middleware chain
const chain = createWebSocketChain<MainTypes>({
  enableDebugLogging: true,
});

// Add WebSocket-specific middleware
chain.use(
  'errorHandler',
  createWebSocketErrorHandler({
    includeStackTrace: process.env.NODE_ENV === 'development',
  }),
);

chain.use(
  'requestLogger',
  createWebSocketRequestLogger({
    logLevel: 'info',
    includeBody: true,
  }),
);

chain.use(
  'modelInitializer',
  createWebSocketModelInitializer({
    models: ['User', 'Message'],
  }),
);

chain.use(
  'validator',
  createWebSocketRequestValidator({
    messageSchema: yup.object({
      action: yup.string().required(),
      data: yup.object().required(),
    }),
  }),
);

// WebSocket handler with model access
const webSocketHandler = async input => {
  const { event, models } = input;

  // Access type-safe models
  const UserModel = getModelFromInput(input, 'User');
  const MessageModel = getModelFromInput(input, 'Message');

  // Handle different WebSocket events
  switch (event.requestContext.routeKey) {
    case '$connect':
      return { statusCode: 200 };

    case '$disconnect':
      return { statusCode: 200 };

    case 'sendMessage':
      const message = await MessageModel.create({
        input: {
          content: event.body.data.content,
          userId: event.requestContext.authorizer.userId,
        },
      });
      return { statusCode: 200, body: JSON.stringify(message) };

    default:
      throw new Error(`Unknown route: ${event.requestContext.routeKey}`);
  }
};

export const handler = wrapWebSocketHandler(chain, webSocketHandler);
```

### Built-in Validation Patterns

```typescript
import { ValidationPatterns } from '@your-org/amplify-shared-utilities';

// Common validation patterns
ValidationPatterns.idParam(); // UUID path parameter
ValidationPatterns.email(); // Email validation
ValidationPatterns.pagination(); // Page/limit query params
ValidationPatterns.dateRange(); // Start/end date validation
```

## 🧪 Testing Patterns

### Middleware Testing

```typescript
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { MiddlewareChain } from '@your-org/amplify-shared-utilities';

// Mock dependencies
vi.mock('../log', () => ({
  logger: {
    debug: vi.fn(),
    error: vi.fn(),
    info: vi.fn(),
    warn: vi.fn(),
  },
}));

describe('Middleware Chain', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('should execute middleware in order', async () => {
    const chain = new MiddlewareChain();
    const executionOrder: string[] = [];

    const middleware1 = vi.fn().mockImplementation(async (input, next) => {
      executionOrder.push('before-1');
      const result = await next();
      executionOrder.push('after-1');
      return result;
    });

    const middleware2 = vi.fn().mockImplementation(async (input, next) => {
      executionOrder.push('before-2');
      const result = await next();
      executionOrder.push('after-2');
      return result;
    });

    chain.use('first', middleware1);
    chain.use('second', middleware2);

    const result = await chain.execute('input', async () => {
      executionOrder.push('handler');
      return 'success';
    });

    expect(executionOrder).toEqual([
      'before-1',
      'before-2',
      'handler',
      'after-2',
      'after-1',
    ]);
    expect(result).toBe('success');
  });

  it('should enhance errors with middleware context', async () => {
    const chain = new MiddlewareChain();

    const middleware = vi.fn().mockRejectedValue(new Error('Test error'));
    chain.use('test', middleware);

    try {
      await chain.execute('input', async () => 'success');
      expect.fail('Should have thrown');
    } catch (error: any) {
      expect(error.middlewareName).toBe('test');
      expect(error.middlewareChain).toEqual(['test']);
      expect(error.message).toBe('Test error');
    }
  });
});
```

### Error Library Integration Testing

```typescript
import {
  throwError,
  createErrorContext,
} from '@your-org/amplify-shared-utilities';

it('should preserve error library context through middleware', async () => {
  const chain = new MiddlewareChain();

  const middleware = vi.fn().mockImplementation(async () => {
    throwError(
      'Database connection failed',
      createErrorContext({
        operation: 'getUser',
        userId: '123',
        database: 'users',
      }),
    );
  });

  chain.use('database', middleware);

  try {
    await chain.execute('input', async () => 'success');
    expect.fail('Should have thrown');
  } catch (error: any) {
    expect(error.__fromErrorLibrary).toBe(true);
    expect(error.operation).toBe('getUser');
    expect(error.userId).toBe('123');
    expect(error.database).toBe('users');
    expect(error.middlewareName).toBe('database');
  }
});
```

## ⚙️ Configuration

### Environment Variables

- `LOG_LEVEL`: Log level (0=NONE, 1=ERROR, 2=WARN, 3=INFO, 4=DEBUG)
- `STRUCTURED_LOGGING`: Force JSON logging ('true'/'false')
- `NODE_ENV`: Environment detection
- `environment`: Explicit environment setting ('prod' for production)

### AWS Lambda Auto-Detection

Automatically detects Lambda environment and:

- Enables structured JSON logging for CloudWatch
- Captures Lambda context (request ID, function name, X-Ray trace)
- Optimizes logging format for CloudWatch Logs Insights
- Detects production environment from function name patterns

## 📋 Requirements

- Node.js 22+
- TypeScript 5.6+
- AWS Amplify 6.0+
- Yup 1.6+ (for validation)

## 🔄 Development Workflow

### Adding New Features

1. **Follow the module structure**: Each module has its own directory with `index.ts` exports
2. **Add comprehensive tests**: Co-locate tests with source files, use Vitest
3. **Update exports**: Ensure all new functionality is exported from module `index.ts`
4. **Update main exports**: Add to root `index.ts` if needed
5. **Follow TypeScript standards**: No `any` types, use strict mode

### Testing Guidelines

- Use Vitest for all testing
- Mock external dependencies with `vi.mock`
- Test both success and error scenarios
- Verify error context preservation
- Test middleware chain execution order
- Use `expect().rejects.toThrow()` for error cases

### Code Quality

- Run `npm run lint:check` before committing
- Follow ESLint rules (no `any`, max complexity 15, etc.)
- Use Prettier for formatting
- Maintain backward compatibility
- Add JSDoc comments for public APIs

## 🚀 Performance Considerations

- **Middleware Chains**: Each execution creates a closure chain - reuse chains for high-frequency operations
- **Client Management**: ClientManager uses singleton pattern for connection reuse
- **Logging**: Debug logging adds overhead - disable in production
- **Error Context**: Large context objects are filtered to remove undefined values
- **Validation**: Use `stripUnknown: true` in validation to reduce payload size

## 🔧 Troubleshooting

### Common Issues

1. **"Model not found" errors**: Ensure `initializeQueries()` is called before using QueryFactory
2. **Middleware not executing**: Check middleware order and ensure `next()` is called
3. **Error context missing**: Use `throwError()` instead of `new Error()` for automatic context
4. **Logging not structured**: Check environment detection and `STRUCTURED_LOGGING` variable

### Debug Mode

Enable debug logging for middleware chains:

```typescript
const chain = new MiddlewareChain({
  enableDebugLogging: true,
  onError: (error, middlewareName) => {
    console.error(`Middleware ${middlewareName} failed:`, error);
  },
});
```

This comprehensive documentation provides detailed instructions for an AI to understand and work with this codebase, including architecture patterns, testing approaches, and development workflows.
