From 91fbd6507ecc9872664ef78cf5cd1342ff23c0cd Mon Sep 17 00:00:00 2001 From: Edward Chen Date: Sat, 16 Nov 2024 14:07:02 -0800 Subject: [PATCH 1/3] feat(typescript/ddb-stream-lambda-sns) Add an example of dynamodb stream integration with lambda and sns --- typescript/ddb-stream-lambda-sns/.gitignore | 9 ++ typescript/ddb-stream-lambda-sns/.npmignore | 6 + typescript/ddb-stream-lambda-sns/README.md | 124 ++++++++++++++++++ .../ddb-stream-lambda-sns/bin/ddb-stream.ts | 7 + typescript/ddb-stream-lambda-sns/cdk.json | 72 ++++++++++ .../images/architecture.jpg | Bin 0 -> 71671 bytes typescript/ddb-stream-lambda-sns/item.json | 11 ++ .../ddb-stream-lambda-sns/jest.config.js | 8 ++ .../lib/ddb-stream-stack.ts | 93 +++++++++++++ typescript/ddb-stream-lambda-sns/package.json | 28 ++++ .../resources/lambda/index.mjs | 44 +++++++ .../test/ddb-stream.test.ts | 77 +++++++++++ .../ddb-stream-lambda-sns/tsconfig.json | 31 +++++ 13 files changed, 510 insertions(+) create mode 100644 typescript/ddb-stream-lambda-sns/.gitignore create mode 100644 typescript/ddb-stream-lambda-sns/.npmignore create mode 100644 typescript/ddb-stream-lambda-sns/README.md create mode 100644 typescript/ddb-stream-lambda-sns/bin/ddb-stream.ts create mode 100644 typescript/ddb-stream-lambda-sns/cdk.json create mode 100644 typescript/ddb-stream-lambda-sns/images/architecture.jpg create mode 100644 typescript/ddb-stream-lambda-sns/item.json create mode 100644 typescript/ddb-stream-lambda-sns/jest.config.js create mode 100644 typescript/ddb-stream-lambda-sns/lib/ddb-stream-stack.ts create mode 100644 typescript/ddb-stream-lambda-sns/package.json create mode 100644 typescript/ddb-stream-lambda-sns/resources/lambda/index.mjs create mode 100644 typescript/ddb-stream-lambda-sns/test/ddb-stream.test.ts create mode 100644 typescript/ddb-stream-lambda-sns/tsconfig.json diff --git a/typescript/ddb-stream-lambda-sns/.gitignore b/typescript/ddb-stream-lambda-sns/.gitignore new file mode 100644 index 0000000000..61bf891f65 --- /dev/null +++ b/typescript/ddb-stream-lambda-sns/.gitignore @@ -0,0 +1,9 @@ +*.js +!jest.config.js +*.d.ts +node_modules + +# CDK asset staging directory +.cdk.staging +cdk.out +.DS_Store diff --git a/typescript/ddb-stream-lambda-sns/.npmignore b/typescript/ddb-stream-lambda-sns/.npmignore new file mode 100644 index 0000000000..c1d6d45dcf --- /dev/null +++ b/typescript/ddb-stream-lambda-sns/.npmignore @@ -0,0 +1,6 @@ +*.ts +!*.d.ts + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/typescript/ddb-stream-lambda-sns/README.md b/typescript/ddb-stream-lambda-sns/README.md new file mode 100644 index 0000000000..cbda80effe --- /dev/null +++ b/typescript/ddb-stream-lambda-sns/README.md @@ -0,0 +1,124 @@ +DynamoDB Stream Constructs for AWS CDK +--- + + +--- + +![Stability: Developer Preview](https://img.shields.io/badge/stability-Developer--Preview-important.svg?style=for-the-badge) + +> **This is an experimental example. It may not build out of the box** +> +> This example is built on Construct Libraries marked "Developer Preview" and may not be updated for latest breaking changes. +> +> It may additionally requires infrastructure prerequisites that must be created before successful build. +> +> If build is unsuccessful, please create an [issue](https://github.com/aws-samples/aws-cdk-examples/issues/new) so that we may debug the problem +--- + + +## Overview + +This repository provides both L2 and L3 constructs example usage for working with DynamoDB streams [AWS Cloud Development Kit (CDK)](https://aws.amazon.com/cdk/) with TypeScript. It showcases the integration of DynamoDB streams with AWS Lambda and Amazon SNS (Simple Notification Service), providing an example of real-time data processing and notification workflows. + +This solution demonstrates a use case for real-time notifications: alerting users about low inventory of an item in the system. + +## Architecture Diagram + +![Architecture Diagram](images/architecture.jpg) + +## Features + +- L2 (low-level) construct for fine-grained control over DynamoDB streams +- [L3 (high-level)](https://docs.aws.amazon.com/solutions/latest/constructs/aws-dynamodbstreams-lambda.html) construct for simplified, best-practice implementations of DynamoDB streams +- Integration with Lambda functions for stream processing +- Implements an SQS Dead Letter Queue (DLQ) for the Lambda function processing the DynamoDB stream, enhancing reliability and error handling +- Shows how to use Amazon SNS to distribute stream processing results or notifications. + + +## Build, Deploy and Testing + +### Prerequisites + +Before you begin, ensure you have met the following requirements: + +* You have installed the latest version of [Node.js and npm](https://nodejs.org/en/download/) +* You have installed the [AWS CLI](https://aws.amazon.com/cli/) and configured it with your credentials +* You have installed the [AWS CDK Toolkit](https://docs.aws.amazon.com/cdk/latest/guide/cli.html) globally +* You have an AWS account and have set up your [AWS credentials](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html) +* You have [bootstrapped your AWS account](https://docs.aws.amazon.com/cdk/latest/guide/bootstrapping.html) for CDK + + + +### Build +To build this app, you need to be in this example's root folder. Then run the following: + +```bash +npm install +npm run build +``` + +This will install the dependencies for this example. + +### Deploy + +Run `cdk deploy`. This will deploy the Stack to your AWS Account. + +Post deployment, you should see the table arn, lambda function arn and sns topic arn on the output of your CLI. + +## Testing +```bash +npm run test +``` + +## Usage + +### Configuring SNS Notification Subscription + +1. After deploying the stack, locate the SNS topic Amazon Resource Name (ARN) from the CLI output. + +2. To subscribe an email address to the SNS topic: + + ```bash + aws sns subscribe --topic-arn --protocol email --notification-endpoint your-email@example.com + ``` +Replace with the actual ARN of your SNS topic, and your-email@example.com with the email address you want to subscribe. + +3. Check your email inbox for a confirmation message from AWS. Click the link in the email to confirm your subscription. + +### Creating an Item in DynamoDB with id, itemName, and count +To trigger the stream processing and email notification, you need to create an item in your DynamoDB table with the fields id, itemName, and count. You can do this using the AWS CLI or AWS Management Console. + +Example item.json provided in this repo: +```bash +{ + "id": { + "S": "1" + }, + "count": { + "N": "10" + }, + "itemName": { + "S": "Coffee Beans" + } +} +``` + +1. Use the following command to put the item into your DynamoDB table: + +```bash +aws dynamodb put-item --table-name --item file://item.json +``` + +Replace with the actual name of your DynamoDB table. + +2. Whenever you update the count field of the item to 0, the DynamoDB stream will trigger the Lambda function, which will process the data and send a notification to the subscribed email address via SNS. + + + +## Cleanup + +To avoid incurring future charges, please destroy the resources when they are no longer needed: + +```bash +cdk destroy +``` diff --git a/typescript/ddb-stream-lambda-sns/bin/ddb-stream.ts b/typescript/ddb-stream-lambda-sns/bin/ddb-stream.ts new file mode 100644 index 0000000000..e05b77e61f --- /dev/null +++ b/typescript/ddb-stream-lambda-sns/bin/ddb-stream.ts @@ -0,0 +1,7 @@ +#!/usr/bin/env node +import 'source-map-support/register'; +import * as cdk from 'aws-cdk-lib'; +import { DdbStreamStack } from '../lib/ddb-stream-stack'; + +const app = new cdk.App(); +new DdbStreamStack(app, 'DdbStreamStack', {}); \ No newline at end of file diff --git a/typescript/ddb-stream-lambda-sns/cdk.json b/typescript/ddb-stream-lambda-sns/cdk.json new file mode 100644 index 0000000000..a39d7d4fb7 --- /dev/null +++ b/typescript/ddb-stream-lambda-sns/cdk.json @@ -0,0 +1,72 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/ddb-stream.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, + "@aws-cdk/aws-efs:denyAnonymousAccess": true, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, + "@aws-cdk/aws-eks:nodegroupNameAttribute": true, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true, + "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true, + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false, + "@aws-cdk/aws-s3:keepNotificationInImportedBucket": false + } +} diff --git a/typescript/ddb-stream-lambda-sns/images/architecture.jpg b/typescript/ddb-stream-lambda-sns/images/architecture.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6f1f8652b1f14e6e28f553c297cd326f0a5d7e70 GIT binary patch literal 71671 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/test'], + testMatch: ['**/*.test.ts'], + transform: { + '^.+\\.tsx?$': 'ts-jest' + } +}; diff --git a/typescript/ddb-stream-lambda-sns/lib/ddb-stream-stack.ts b/typescript/ddb-stream-lambda-sns/lib/ddb-stream-stack.ts new file mode 100644 index 0000000000..e4995afa4a --- /dev/null +++ b/typescript/ddb-stream-lambda-sns/lib/ddb-stream-stack.ts @@ -0,0 +1,93 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as lambdaEventSources from 'aws-cdk-lib/aws-lambda-event-sources'; +import { DynamoDBStreamsToLambda } from '@aws-solutions-constructs/aws-dynamodbstreams-lambda'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; + +export class DdbStreamStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + const aws_sns_kms_key = kms.Alias.fromAliasName( + this, + "aws-managed-sns-kms-key", + "alias/aws/sns", + ) + + const snsTopic = new sns.Topic(this, 'ddb-stream-topic', { + topicName: 'ddb-stream-topic', + displayName: 'SNS Topic for DDB streams', + enforceSSL: true, + masterKey: aws_sns_kms_key, + }); + + //L2 CDK Construct + const deadLetterQueueL2 = new sqs.Queue(this, 'ddb-stream-l2-dlq', { + queueName: 'ddb-stream-l2-dlq', + encryption: sqs.QueueEncryption.KMS_MANAGED, + retentionPeriod: cdk.Duration.days(4), // Adjust retention period as needed + }); + + const itemL2Table = new dynamodb.Table(this, 'itemL2Table', { + partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, + stream: dynamodb.StreamViewType.NEW_AND_OLD_IMAGES, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + encryption: dynamodb.TableEncryption.AWS_MANAGED, + //If you wish to retain the table after running cdk destroy, comment out the line below + removalPolicy: cdk.RemovalPolicy.DESTROY + }); + + const itemL2TableLambdaFunction = new lambda.Function(this, 'itemL2TableLambdaFunction', { + runtime: lambda.Runtime.NODEJS_20_X, + handler: 'index.handler', + tracing: lambda.Tracing.ACTIVE, + code: lambda.Code.fromAsset('resources/lambda'), + environment: { + SNS_TOPIC_ARN: snsTopic.topicArn, + AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1' + }, + }); + itemL2TableLambdaFunction.addEventSource(new lambdaEventSources.DynamoEventSource(itemL2Table, { + startingPosition: lambda.StartingPosition.TRIM_HORIZON, + onFailure: new lambdaEventSources.SqsDlq(deadLetterQueueL2), + bisectBatchOnError: true, + maxRecordAge: cdk.Duration.hours(24), + retryAttempts: 500, + })); + + deadLetterQueueL2.grantSendMessages(itemL2TableLambdaFunction); + + itemL2Table.grantStreamRead(itemL2TableLambdaFunction); + + //L3 CDK Construct + const itemL3Table = new DynamoDBStreamsToLambda(this, 'itemL3Table', { + dynamoTableProps: { + partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, + stream: dynamodb.StreamViewType.NEW_AND_OLD_IMAGES, + //If you wish to retain the table after running cdk destroy, comment out the line below + removalPolicy: cdk.RemovalPolicy.DESTROY + }, + lambdaFunctionProps: { + code: lambda.Code.fromAsset('resources/lambda'), + runtime: lambda.Runtime.NODEJS_20_X, + handler: 'index.handler', + environment: { + SNS_TOPIC_ARN: snsTopic.topicArn, + }, + }, + }); + + snsTopic.grantPublish(itemL2TableLambdaFunction); + snsTopic.grantPublish(itemL3Table.lambdaFunction); + + new cdk.CfnOutput(this, 'itemL2TableLambdaFunctionArn', { value: itemL2TableLambdaFunction.functionArn }); + new cdk.CfnOutput(this, 'itemL3TableLambdaFunctionArn', { value: itemL3Table.lambdaFunction.functionArn }); + new cdk.CfnOutput(this, 'l3TableArn', { value: itemL3Table.dynamoTableInterface.tableArn }); + new cdk.CfnOutput(this, 'l2TableArn', { value: itemL2Table.tableArn }); + new cdk.CfnOutput(this, 'topicArn', { value: snsTopic.topicArn }); + new cdk.CfnOutput(this, 'l2DLQArn', { value: deadLetterQueueL2.queueArn }) + } +} diff --git a/typescript/ddb-stream-lambda-sns/package.json b/typescript/ddb-stream-lambda-sns/package.json new file mode 100644 index 0000000000..cf8feb2ce4 --- /dev/null +++ b/typescript/ddb-stream-lambda-sns/package.json @@ -0,0 +1,28 @@ +{ + "name": "ddb-stream-lambda-sns", + "version": "0.1.0", + "bin": { + "ddb-stream": "bin/ddb-stream.js" + }, + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "test": "jest", + "cdk": "cdk" + }, + "devDependencies": { + "@types/jest": "^29.5.14", + "@types/node": "22.9.0", + "aws-cdk": "2.166.0", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "ts-node": "^10.9.2", + "typescript": "~5.6.3" + }, + "dependencies": { + "@aws-solutions-constructs/aws-dynamodbstreams-lambda": "^2.74.0", + "aws-cdk-lib": "2.167.0", + "constructs": "^10.4.2", + "source-map-support": "^0.5.21" + } +} \ No newline at end of file diff --git a/typescript/ddb-stream-lambda-sns/resources/lambda/index.mjs b/typescript/ddb-stream-lambda-sns/resources/lambda/index.mjs new file mode 100644 index 0000000000..f11513da70 --- /dev/null +++ b/typescript/ddb-stream-lambda-sns/resources/lambda/index.mjs @@ -0,0 +1,44 @@ +import { SNSClient, PublishCommand } from "@aws-sdk/client-sns"; + +const snsClient = new SNSClient(); + +/** + * Lambda function handler to monitor DynamoDB stream events for inventory changes + * Sends email notifications when an item's count reaches zero + * @param {Object} event - DynamoDB Stream event + * @returns {Object} - Status of the execution + */ + +export const handler = async (event) => { + try { + for (const record of event.Records) { + // Only process MODIFY events + if (record.eventName === "MODIFY") { + const newImage = record.dynamodb.NewImage; + const oldImage = record.dynamodb.OldImage; + + const newCount = newImage.count ? parseInt(newImage.count.N) : null; + const oldCount = oldImage?.count ? parseInt(oldImage.count.N) : null; + + // Check if count changed to 0 from a non-zero value + if (newCount === 0 && oldCount > 0) { + const itemName = newImage.itemName ? newImage.itemName.S : "Unknown item"; + + const params = { + Message: `Alert: ${itemName} has reached zero inventory! Previous count was ${oldCount}.`, + Subject: `Stock Alert - ${itemName} Out of Stock`, + TopicArn: process.env.SNS_TOPIC_ARN + }; + + await snsClient.send(new PublishCommand(params)); + + console.log(`Notification sent for ${itemName} - count dropped to 0 from ${oldCount}`); + } + } + } + return { statusCode: 200, body: 'Processing complete.' }; + } catch (error) { + console.error('Error processing records:', error); + throw error; + } +}; diff --git a/typescript/ddb-stream-lambda-sns/test/ddb-stream.test.ts b/typescript/ddb-stream-lambda-sns/test/ddb-stream.test.ts new file mode 100644 index 0000000000..5c8f9a2041 --- /dev/null +++ b/typescript/ddb-stream-lambda-sns/test/ddb-stream.test.ts @@ -0,0 +1,77 @@ +import * as cdk from 'aws-cdk-lib'; +import { Template, Match } from 'aws-cdk-lib/assertions'; +import * as ddbStream from '../lib/ddb-stream-stack'; + +const app = new cdk.App(); +const stack = new ddbStream.DdbStreamStack(app, 'MyTestStack'); +const template = Template.fromStack(stack); + +test('DynamoDB Table, Lambda Function, and SNS Topic Created', () => { + template.hasResourceProperties('AWS::DynamoDB::Table', { + StreamSpecification: { + StreamViewType: 'NEW_AND_OLD_IMAGES', + } + }); + template.hasResourceProperties('AWS::Lambda::Function', { + Runtime: 'nodejs20.x', + Handler: 'index.handler', + + }); + template.hasResourceProperties('AWS::SNS::Topic', { + KmsMasterKeyId: Match.anyValue(), + }); +}); + +test('Lambda Function has permission to publish to SNS', () => { + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: Match.arrayWith([ + Match.objectLike({ + Effect: 'Allow', + Action: 'sns:Publish', + Resource: Match.anyValue() + }) + ]) + } + }); +}); + +test('Lambda Function has correct environment variables', () => { + template.hasResourceProperties('AWS::Lambda::Function', { + Environment: { + Variables: { + SNS_TOPIC_ARN: Match.anyValue() + } + } + }); +}); + + +test('Lambda Functions have DynamoDB Stream Event Sources', () => { + template.hasResourceProperties('AWS::Lambda::EventSourceMapping', { + BatchSize: 100, + EventSourceArn: { + 'Fn::GetAtt': [Match.anyValue(), 'StreamArn'] + }, + FunctionName: { + Ref: Match.anyValue() + }, + StartingPosition: 'TRIM_HORIZON' + }); +}); + +test('DLQ is created for Lambda functions', () => { + template.hasResourceProperties('AWS::SQS::Queue', { + KmsMasterKeyId: Match.anyValue() + }); +}); + + +test('Stack has the correct number of resources', () => { + template.resourceCountIs('AWS::DynamoDB::Table', 2); + template.resourceCountIs('AWS::Lambda::Function', 2); + template.resourceCountIs('AWS::SNS::Topic', 1); + template.resourceCountIs('AWS::SQS::Queue', 2); + template.resourceCountIs('AWS::Lambda::EventSourceMapping', 2); +}); + diff --git a/typescript/ddb-stream-lambda-sns/tsconfig.json b/typescript/ddb-stream-lambda-sns/tsconfig.json new file mode 100644 index 0000000000..aaa7dc510f --- /dev/null +++ b/typescript/ddb-stream-lambda-sns/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": [ + "es2020", + "dom" + ], + "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" + ] +} From 4e5b05450d20b46a8685bc2ec36123289c58f0ce Mon Sep 17 00:00:00 2001 From: Edward Chen Date: Sat, 16 Nov 2024 14:22:19 -0800 Subject: [PATCH 2/3] docs(typescript/ddb-stream-lambda-sns) Update README.md --- typescript/ddb-stream-lambda-sns/README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/typescript/ddb-stream-lambda-sns/README.md b/typescript/ddb-stream-lambda-sns/README.md index cbda80effe..655cbedae4 100644 --- a/typescript/ddb-stream-lambda-sns/README.md +++ b/typescript/ddb-stream-lambda-sns/README.md @@ -1,5 +1,4 @@ -DynamoDB Stream Constructs for AWS CDK ---- +# DynamoDB Stream Integration with Lambda and SNS --- @@ -31,7 +30,7 @@ This solution demonstrates a use case for real-time notifications: alerting user - L2 (low-level) construct for fine-grained control over DynamoDB streams - [L3 (high-level)](https://docs.aws.amazon.com/solutions/latest/constructs/aws-dynamodbstreams-lambda.html) construct for simplified, best-practice implementations of DynamoDB streams - Integration with Lambda functions for stream processing -- Implements an SQS Dead Letter Queue (DLQ) for the Lambda function processing the DynamoDB stream, enhancing reliability and error handling +- Implements an SQS Dead Letter Queue (DLQ) for the Lambda function failure handling - Shows how to use Amazon SNS to distribute stream processing results or notifications. From 95fad1d703b1c7cd90fd50f541634268b28b842a Mon Sep 17 00:00:00 2001 From: Edward Chen Date: Sat, 16 Nov 2024 14:36:54 -0800 Subject: [PATCH 3/3] fixes(typescript/ddb-stream-lambda-sns) Remove unused packages --- typescript/ddb-stream-lambda-sns/bin/ddb-stream.ts | 1 - typescript/ddb-stream-lambda-sns/package.json | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/typescript/ddb-stream-lambda-sns/bin/ddb-stream.ts b/typescript/ddb-stream-lambda-sns/bin/ddb-stream.ts index e05b77e61f..2e47901d00 100644 --- a/typescript/ddb-stream-lambda-sns/bin/ddb-stream.ts +++ b/typescript/ddb-stream-lambda-sns/bin/ddb-stream.ts @@ -1,5 +1,4 @@ #!/usr/bin/env node -import 'source-map-support/register'; import * as cdk from 'aws-cdk-lib'; import { DdbStreamStack } from '../lib/ddb-stream-stack'; diff --git a/typescript/ddb-stream-lambda-sns/package.json b/typescript/ddb-stream-lambda-sns/package.json index cf8feb2ce4..8ef8796e63 100644 --- a/typescript/ddb-stream-lambda-sns/package.json +++ b/typescript/ddb-stream-lambda-sns/package.json @@ -22,7 +22,6 @@ "dependencies": { "@aws-solutions-constructs/aws-dynamodbstreams-lambda": "^2.74.0", "aws-cdk-lib": "2.167.0", - "constructs": "^10.4.2", - "source-map-support": "^0.5.21" + "constructs": "^10.4.2" } } \ No newline at end of file