diff --git a/lambda-s3-athena-cdk-ts/.gitignore b/lambda-s3-athena-cdk-ts/.gitignore new file mode 100644 index 000000000..cec1a33a0 --- /dev/null +++ b/lambda-s3-athena-cdk-ts/.gitignore @@ -0,0 +1,13 @@ +!jest.config.js +*.d.ts +node_modules + +# CDK asset staging directory +.cdk.staging +cdk.out + +# Parcel default cache directory +.parcel-cache + +# Mac files +.DS_Store diff --git a/lambda-s3-athena-cdk-ts/README.md b/lambda-s3-athena-cdk-ts/README.md new file mode 100644 index 000000000..32b0e9a04 --- /dev/null +++ b/lambda-s3-athena-cdk-ts/README.md @@ -0,0 +1,267 @@ +# AWS Lambda with Amazon S3 Failed-Event Destination and Amazon Athena Analytics + +This pattern demonstrates how to use Amazon S3 as a failed-event destination for AWS Lambda asynchronous invocations, with Amazon Athena for analytics on failed events. The pattern includes an AWS Lambda function with business logic that can succeed or fail, automatically capturing failed events to Amazon S3 for analysis. + +Learn more about this pattern at Serverless Land Patterns: [https://serverlessland.com/patterns/lambda-s3-athena-cdk-ts](https://serverlessland.com/patterns/lambda-s3-athena-cdk-ts) + +Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example. + +## Requirements + +* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. +* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured +* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +* [Node.js and npm](https://nodejs.org/) installed +* [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html) installed + +## Deployment Instructions + +1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository: + + ```bash + git clone https://github.com/aws-samples/serverless-patterns + ``` + +2. Change directory to the pattern directory: + + ```bash + cd lambda-s3-athena-cdk-ts + ``` + +3. Install dependencies: + + ```bash + npm install + ``` + +4. Deploy the CDK stack to your default AWS account and region: + + ```bash + cdk deploy + ``` + +5. Note the outputs from the CDK deployment process. These contain the resource names and URLs which are used for testing. + +## Deployment Outputs + +After deployment, CDK will display the following outputs. Save these values for configuration and testing: + +| Output Key | Description | Usage | +|------------|-------------|-------| +| `LambdaFunctionName` | AWS Lambda function name | Use this name to invoke the AWS Lambda function via AWS CLI | +| `FailedEventsBucketName` | Amazon S3 bucket storing failed AWS Lambda events | Check this bucket to verify failed events are being captured | +| `AthenaResultsBucketName` | Amazon S3 bucket for Amazon Athena query results | Amazon Athena stores query results here automatically | +| `GlueDatabaseName` | AWS Glue database name (typically `failed_events_db`) | Use in Amazon Athena queries: `FROM .` | +| `GlueTableName` | AWS Glue table name (`failed_events`) | Use in Amazon Athena queries: `FROM .
` | +| `AthenaWorkgroupName` | Amazon Athena workgroup name | Specify this workgroup when running Amazon Athena queries | + +**Example output:** +``` +Outputs: +LambdaS3AthenaCdkStack.LambdaFunctionName = processor-function-abc123 +LambdaS3AthenaCdkStack.FailedEventsBucketName = lambdas3athenac-failedeventsbucket12345-abcdef +LambdaS3AthenaCdkStack.AthenaResultsBucketName = lambdas3athenac-athenaresultsbucket67890-ghijkl +LambdaS3AthenaCdkStack.GlueDatabaseName = failed_events_db +LambdaS3AthenaCdkStack.GlueTableName = failed_events +LambdaS3AthenaCdkStack.AthenaWorkgroupName = failed-events-workgroup +``` + +## How it works + +![Architecture Diagram](./lambda-s3-athena-cdk-ts.png) + +This pattern creates an AWS Lambda function that implements simple business logic with three actions: `process`, `validate`, and `calculate`. When the AWS Lambda function fails (throws an error) during asynchronous invocation, the failed event is automatically sent to an Amazon S3 bucket configured as the failed-event destination. + +The pattern also sets up AWS Glue and Amazon Athena to enable SQL-based analytics on the failed events stored in Amazon S3. This allows you to query and analyze error patterns, identify common failure scenarios, and gain insights into your application's error behavior. + +Architecture flow: +1. Client invokes AWS Lambda function asynchronously via AWS CLI +2. AWS Lambda processes the request based on the action type +3. On success: AWS Lambda returns successful response +4. On failure: AWS Lambda throws error, and AWS automatically sends the failed event to Amazon S3 +5. Failed events are stored in Amazon S3 as JSON files +6. Amazon Athena queries the failed events using AWS Glue Data Catalog for analytics + +### AWS Lambda Destinations and Invocation Types + +**Important**: AWS Lambda destinations only work with **asynchronous invocations** and **stream-based invocations** (on-failure only). They do not trigger for synchronous invocations. + +| Invocation Type | Examples | Destinations Supported? | +|----------------|----------|------------------------| +| **Synchronous** | Amazon API Gateway, Application Load Balancer, SDK RequestResponse | ❌ No | +| **Asynchronous** | Amazon S3, Amazon SNS, Amazon EventBridge, SDK Event | ✅ Yes | +| **Stream/Poll-based** | Amazon Kinesis, Amazon DynamoDB Streams, Amazon SQS | ✅ On-failure only | + +This pattern uses **asynchronous invocation via AWS CLI** (`--invocation-type Event`) to demonstrate the Amazon S3 failed-event destination feature. + +The AWS Glue Data Catalog is required for Amazon Athena to query Amazon S3 data, providing the schema definition, data location, and SerDe configuration needed to parse the JSON files. This pattern uses a simplified configuration without a predefined schema, allowing Amazon Athena to perform full scans of all failed event files for learning and low-volume workloads. + +## Testing + +### Test Successful Requests + +1. Get the AWS Lambda function name from the stack outputs: + + ```bash + FUNCTION_NAME=$(aws cloudformation describe-stacks --stack-name LambdaS3AthenaCdkStack --query 'Stacks[0].Outputs[?OutputKey==`LambdaFunctionName`].OutputValue' --output text) + ``` + +2. Test the `process` action (success) with asynchronous invocation: + + ```bash + aws lambda invoke \ + --function-name $FUNCTION_NAME \ + --invocation-type Event \ + --cli-binary-format raw-in-base64-out \ + --payload '{"action": "process", "value": 10}' \ + response.json + ``` + + Expected response: `{"StatusCode": 202}` (request accepted) + +3. Test the `validate` action (success): + + ```bash + aws lambda invoke \ + --function-name $FUNCTION_NAME \ + --invocation-type Event \ + --cli-binary-format raw-in-base64-out \ + --payload '{"action": "validate", "value": "hello"}' \ + response.json + ``` + +4. Test the `calculate` action (success): + + ```bash + aws lambda invoke \ + --function-name $FUNCTION_NAME \ + --invocation-type Event \ + --cli-binary-format raw-in-base64-out \ + --payload '{"action": "calculate", "value": [1, 2, 3, 4, 5]}' \ + response.json + ``` + +### Test Failed Requests + +1. Test with invalid value for `process` (negative number): + + ```bash + aws lambda invoke \ + --function-name $FUNCTION_NAME \ + --invocation-type Event \ + --cli-binary-format raw-in-base64-out \ + --payload '{"action": "process", "value": -5}' \ + response.json + ``` + +2. Test with invalid value for `validate` (empty string): + + ```bash + aws lambda invoke \ + --function-name $FUNCTION_NAME \ + --invocation-type Event \ + --cli-binary-format raw-in-base64-out \ + --payload '{"action": "validate", "value": ""}' \ + response.json + ``` + +3. Test with invalid value for `calculate` (empty array): + + ```bash + aws lambda invoke \ + --function-name $FUNCTION_NAME \ + --invocation-type Event \ + --cli-binary-format raw-in-base64-out \ + --payload '{"action": "calculate", "value": []}' \ + response.json + ``` + +4. Test with unknown action: + + ```bash + aws lambda invoke \ + --function-name $FUNCTION_NAME \ + --invocation-type Event \ + --cli-binary-format raw-in-base64-out \ + --payload '{"action": "unknown", "value": "test"}' \ + response.json + ``` + +5. Wait a few minutes for the failed events to be written to Amazon S3, then verify they exist: + + ```bash + BUCKET_NAME=$(aws cloudformation describe-stacks --stack-name LambdaS3AthenaCdkStack --query 'Stacks[0].Outputs[?OutputKey==`FailedEventsBucketName`].OutputValue' --output text) + aws s3 ls s3://$BUCKET_NAME/ --recursive + ``` + + Note: Failed events typically appear in Amazon S3 within 1-2 minutes after the AWS Lambda invocation fails. + +### Query Failed Events with Amazon Athena + +1. Open the Amazon Athena console to run queries. + +2. Query to count failed events by error type: + + ```sql + SELECT + responsepayload.errortype as error_type, + COUNT(*) as error_count + FROM failed_events + GROUP BY responsepayload.errortype + ORDER BY error_count DESC; + ``` + +3. Query to see detailed error messages: + + ```sql + SELECT + timestamp, + responsepayload.errortype as error_type, + responsepayload.errormessage as error_message, + requestpayload.body as request_body + FROM failed_events + ORDER BY timestamp DESC + LIMIT 10; + ``` + +4. Query to analyze errors by action type: + + ```sql + SELECT + json_extract_scalar(requestpayload.body, '$.action') as action, + responsepayload.errortype as error_type, + COUNT(*) as count + FROM failed_events + GROUP BY + json_extract_scalar(requestpayload.body, '$.action'), + responsepayload.errortype + ORDER BY count DESC; + ``` + +5. Query to find errors within a specific time range: + + ```sql + SELECT + timestamp, + responsepayload.errormessage as error_message, + requestpayload.body as request_body + FROM failed_events + WHERE timestamp >= '2025-01-01T00:00:00Z' + ORDER BY timestamp DESC; + ``` + +## Cleanup + +1. Delete the stack: + + ```bash + cdk destroy + ``` + +2. Confirm the deletion when prompted. + +--- + +Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: MIT-0 diff --git a/lambda-s3-athena-cdk-ts/bin/lambda-s3-athena-cdk-ts.ts b/lambda-s3-athena-cdk-ts/bin/lambda-s3-athena-cdk-ts.ts new file mode 100644 index 000000000..2f59a8491 --- /dev/null +++ b/lambda-s3-athena-cdk-ts/bin/lambda-s3-athena-cdk-ts.ts @@ -0,0 +1,12 @@ +#!/usr/bin/env node +import 'source-map-support/register'; +import * as cdk from 'aws-cdk-lib'; +import { PatternStack } from '../lib/pattern-stack'; + +const app = new cdk.App(); +new PatternStack(app, 'LambdaS3AthenaCdkStack', { + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + } +}); diff --git a/lambda-s3-athena-cdk-ts/cdk.json b/lambda-s3-athena-cdk-ts/cdk.json new file mode 100644 index 000000000..8eb3ac511 --- /dev/null +++ b/lambda-s3-athena-cdk-ts/cdk.json @@ -0,0 +1,22 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/lambda-s3-athena-cdk-ts.ts", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "cdk.out" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": ["aws", "aws-cn"] + } +} diff --git a/lambda-s3-athena-cdk-ts/example-pattern.json b/lambda-s3-athena-cdk-ts/example-pattern.json new file mode 100644 index 000000000..e2cd539a9 --- /dev/null +++ b/lambda-s3-athena-cdk-ts/example-pattern.json @@ -0,0 +1,74 @@ +{ + "title": "Lambda with S3 Failed-Event Destination and Athena Analytics", + "description": "Capture Lambda failed events to S3 and analyze them with Athena for error insights and patterns using asynchronous invocations.", + "language": "TypeScript", + "level": "200", + "framework": "AWS CDK", + "introBox": { + "headline": "How it works", + "text": [ + "This pattern demonstrates how to use Amazon S3 as a failed-event destination for AWS Lambda asynchronous invocations.", + "A Lambda function implements business logic with success and failure scenarios.", + "When Lambda fails during asynchronous invocation, AWS automatically captures the failed event to S3 for analysis.", + "Amazon Athena with AWS Glue enables SQL-based analytics on failed events to identify error patterns and gain insights.", + "The pattern uses AWS CLI with --invocation-type Event to demonstrate asynchronous invocation.", + "Important: Lambda destinations only work with asynchronous invocations (S3, SNS, EventBridge) and stream-based sources (Kinesis, DynamoDB Streams, SQS). They do not trigger for synchronous invocations." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/lambda-s3-athena-cdk-ts", + "templateURL": "serverless-patterns/lambda-s3-athena-cdk-ts", + "projectFolder": "lambda-s3-athena-cdk-ts", + "templateFile": "lib/pattern-stack.ts" + } + }, + "resources": { + "bullets": [ + { + "text": "AWS Lambda S3 Failed-Event Destination Announcement", + "link": "https://aws.amazon.com/about-aws/whats-new/2024/11/aws-lambda-s3-failed-event-destination-stream-event-sources/" + }, + { + "text": "AWS Lambda Destinations Documentation", + "link": "https://docs.aws.amazon.com/lambda/latest/dg/invocation-async.html#invocation-async-destinations" + }, + { + "text": "Amazon Athena Documentation", + "link": "https://docs.aws.amazon.com/athena/latest/ug/what-is.html" + }, + { + "text": "AWS Glue Data Catalog", + "link": "https://docs.aws.amazon.com/glue/latest/dg/catalog-and-crawler.html" + } + ] + }, + "deploy": { + "text": [ + "Clone the repository: git clone https://github.com/aws-samples/serverless-patterns", + "Change directory: cd lambda-s3-athena-cdk-ts", + "Install dependencies: npm install", + "Deploy the CDK stack: cdk deploy" + ] + }, + "testing": { + "text": [ + "Get Lambda function name: FUNCTION_NAME=$(aws cloudformation describe-stacks --stack-name LambdaS3AthenaCdkStack --query 'Stacks[0].Outputs[?OutputKey==`LambdaFunctionName`].OutputValue' --output text)", + "Test successful request: aws lambda invoke --function-name $FUNCTION_NAME --invocation-type Event --payload '{\"action\": \"process\", \"value\": 10}' response.json", + "Test failed request: aws lambda invoke --function-name $FUNCTION_NAME --invocation-type Event --payload '{\"action\": \"process\", \"value\": -5}' response.json", + "Verify failed events in S3: aws s3 ls s3://$(aws cloudformation describe-stacks --stack-name LambdaS3AthenaCdkStack --query 'Stacks[0].Outputs[?OutputKey==`FailedEventsBucketName`].OutputValue' --output text)/ --recursive", + "Query errors with Athena: SELECT responsepayload.errortype, COUNT(*) FROM failed_events GROUP BY responsepayload.errortype;" + ] + }, + "cleanup": { + "text": ["Delete the stack: cdk destroy"] + }, + "authors": [ + { + "name": "Marco Jahn", + "image": "https://sessionize.com/image/e99b-400o400o2-pqR4BacUSzHrq4fgZ4wwEQ.png", + "bio": "Senior Solutions Architect, Amazon Web Services", + "linkedin": "marcojahn" + } + ] +} diff --git a/lambda-s3-athena-cdk-ts/lambda-s3-athena-cdk-ts.png b/lambda-s3-athena-cdk-ts/lambda-s3-athena-cdk-ts.png new file mode 100644 index 000000000..77e6d0dc8 Binary files /dev/null and b/lambda-s3-athena-cdk-ts/lambda-s3-athena-cdk-ts.png differ diff --git a/lambda-s3-athena-cdk-ts/lambda/processor.js b/lambda-s3-athena-cdk-ts/lambda/processor.js new file mode 100644 index 000000000..806a7bf47 --- /dev/null +++ b/lambda-s3-athena-cdk-ts/lambda/processor.js @@ -0,0 +1,58 @@ +exports.handler = async (event) => { + console.log('Event received:', JSON.stringify(event, null, 2)); + + try { + const action = event.action; + const value = event.value; + + // Simple business logic simulation + switch (action) { + case 'process': + if (!value || value < 0) { + throw new Error('Invalid value: must be a positive number'); + } + return { + statusCode: 200, + body: JSON.stringify({ + message: 'Processing successful', + result: value * 2, + action: action + }) + }; + + case 'validate': + if (typeof value !== 'string' || value.length === 0) { + throw new Error('Invalid value: must be a non-empty string'); + } + return { + statusCode: 200, + body: JSON.stringify({ + message: 'Validation successful', + result: value.toUpperCase(), + action: action + }) + }; + + case 'calculate': + if (!Array.isArray(value) || value.length === 0) { + throw new Error('Invalid value: must be a non-empty array'); + } + const sum = value.reduce((acc, curr) => acc + curr, 0); + return { + statusCode: 200, + body: JSON.stringify({ + message: 'Calculation successful', + result: sum, + action: action + }) + }; + + default: + throw new Error(`Unknown action: ${action}`); + } + } catch (error) { + console.error('Error processing request:', error); + // Lambda will automatically send this to the failed-event destination + throw error; + } +}; diff --git a/lambda-s3-athena-cdk-ts/lib/pattern-stack.ts b/lambda-s3-athena-cdk-ts/lib/pattern-stack.ts new file mode 100644 index 000000000..1391d2eeb --- /dev/null +++ b/lambda-s3-athena-cdk-ts/lib/pattern-stack.ts @@ -0,0 +1,133 @@ +import * as cdk from 'aws-cdk-lib'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as glue from 'aws-cdk-lib/aws-glue'; +import * as athena from 'aws-cdk-lib/aws-athena'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import { Construct } from 'constructs'; +import * as path from 'path'; + +export class PatternStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + // S3 bucket for failed Lambda events + const failedEventsBucket = new s3.Bucket(this, 'FailedEventsBucket', { + removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: true, + encryption: s3.BucketEncryption.S3_MANAGED, + }); + + // S3 bucket for Athena query results + const athenaResultsBucket = new s3.Bucket(this, 'AthenaResultsBucket', { + removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: true, + encryption: s3.BucketEncryption.S3_MANAGED, + }); + + // Lambda function with S3 failed-event destination + // Note: S3Destination automatically grants only necessary write permissions (PutObject) + // to the Lambda function for failed event delivery + const processorFunction = new lambda.Function(this, 'ProcessorFunction', { + runtime: lambda.Runtime.NODEJS_20_X, + handler: 'processor.handler', + code: lambda.Code.fromAsset(path.join(__dirname, '../lambda')), + timeout: cdk.Duration.seconds(30), + onFailure: new cdk.aws_lambda_destinations.S3Destination(failedEventsBucket), + }); + + // Glue Database for Athena + const glueDatabase = new glue.CfnDatabase(this, 'FailedEventsDatabase', { + catalogId: cdk.Aws.ACCOUNT_ID, + databaseInput: { + name: 'failed_events_db', + description: 'Database for failed Lambda events', + }, + }); + + // Glue Table for failed events (simplified without partitioning) + const glueTable = new glue.CfnTable(this, 'FailedEventsTable', { + catalogId: cdk.Aws.ACCOUNT_ID, + databaseName: glueDatabase.ref, + tableInput: { + name: 'failed_events', + description: 'Table for failed Lambda events stored in S3', + tableType: 'EXTERNAL_TABLE', + storageDescriptor: { + columns: [ + { name: 'version', type: 'string' }, + { name: 'timestamp', type: 'string' }, + { name: 'requestcontext', type: 'struct' }, + { name: 'requestpayload', type: 'struct,queryparam:map>' }, + { name: 'responsepayload', type: 'struct>' }, + ], + location: `s3://${failedEventsBucket.bucketName}/`, + inputFormat: 'org.apache.hadoop.mapred.TextInputFormat', + outputFormat: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat', + serdeInfo: { + serializationLibrary: 'org.openx.data.jsonserde.JsonSerDe', + parameters: { + 'case.insensitive': 'true', + }, + }, + }, + }, + }); + + glueTable.addDependency(glueDatabase); + + // Athena Workgroup + const athenaWorkgroup = new athena.CfnWorkGroup(this, 'FailedEventsWorkgroup', { + name: 'failed-events-workgroup', + workGroupConfiguration: { + resultConfiguration: { + outputLocation: `s3://${athenaResultsBucket.bucketName}/`, + }, + }, + }); + + // IAM role for Athena queries + const athenaRole = new iam.Role(this, 'AthenaQueryRole', { + assumedBy: new iam.ServicePrincipal('athena.amazonaws.com'), + }); + + failedEventsBucket.grantRead(athenaRole); + athenaResultsBucket.grantReadWrite(athenaRole); + + // Outputs + new cdk.CfnOutput(this, 'LambdaFunctionName', { + value: processorFunction.functionName, + description: 'Lambda function name for CLI invocation', + }); + + new cdk.CfnOutput(this, 'LambdaFunctionArn', { + value: processorFunction.functionArn, + description: 'Lambda function ARN', + }); + + new cdk.CfnOutput(this, 'FailedEventsBucketName', { + value: failedEventsBucket.bucketName, + description: 'S3 bucket for failed Lambda events', + }); + + new cdk.CfnOutput(this, 'AthenaResultsBucketName', { + value: athenaResultsBucket.bucketName, + description: 'S3 bucket for Athena query results', + }); + + new cdk.CfnOutput(this, 'GlueDatabaseName', { + value: glueDatabase.ref, + description: 'Glue database name', + }); + + new cdk.CfnOutput(this, 'GlueTableName', { + value: 'failed_events', + description: 'Glue table name', + }); + + new cdk.CfnOutput(this, 'AthenaWorkgroupName', { + value: athenaWorkgroup.name!, + description: 'Athena workgroup name', + }); + } +} diff --git a/lambda-s3-athena-cdk-ts/package.json b/lambda-s3-athena-cdk-ts/package.json new file mode 100644 index 000000000..40c2c71de --- /dev/null +++ b/lambda-s3-athena-cdk-ts/package.json @@ -0,0 +1,22 @@ +{ + "name": "lambda-s3-athena-cdk-ts", + "version": "0.1.0", + "bin": { + "lambda-s3-athena-cdk-ts": "bin/lambda-s3-athena-cdk-ts.js" + }, + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "cdk": "cdk" + }, + "devDependencies": { + "@types/node": "22.7.9", + "aws-cdk": "2.1003.0", + "ts-node": "^10.9.2", + "typescript": "~5.6.3" + }, + "dependencies": { + "aws-cdk-lib": "2.189.1", + "constructs": "^10.0.0" + } +} diff --git a/lambda-s3-athena-cdk-ts/tsconfig.json b/lambda-s3-athena-cdk-ts/tsconfig.json new file mode 100644 index 000000000..507608a99 --- /dev/null +++ b/lambda-s3-athena-cdk-ts/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["es2020"], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": ["./node_modules/@types"] + }, + "exclude": ["node_modules", "cdk.out"] +}