Amazon Web Services CDK V2 support for Node.js


Introduction

This section describes the support for the AWS CDK V2 framework. AWS CDK (Cloud Development Kit) is a framework that lets you define and provision AWS infrastructure.

Objects

Icon Description
NodeJS AWS Lambda Function
NodeJS Call to AWS Lambda Function
NodeJS Call to AWS Unknown Lambda Function
NodeJS AWS SNS Publisher
NodeJS AWS Unknown SNS Publisher
NodeJS AWS SNS Subscriber
NodeJS AWS Unknown SNS Subscriber
NodeJS AWS SQS Publisher
NodeJS AWS Unknown SQS Publisher
NodeJS AWS SQS Receiver
NodeJS AWS Unknown SQS Receiver
NodeJS AWS Kinesis Producer
NodeJS AWS Unknown Kinesis Producer
NodeJS AWS Kinesis Consumer
NodeJS AWS Unknown Kinesis Consumer
NodeJS AWS Firehose Producer
NodeJS AWS Unknown Firehose Producer
NodeJS AWS Firehose Stream Delivery
NodeJS AWS Unknown Firehose Stream Delivery
NodeJS AWS S3 Bucket
NodeJS AWS Unknown S3 Bucket
NodeJS Email
NodeJS SMS
NodeJS Get HttpRequest service
NodeJS Post HttpRequest service
AWS GET API Gateway
AWS PUT API Gateway
AWS POST API Gateway
AWS DELETE API Gateway
AWS PATCH API Gateway
AWS ANY API Gateway

List of supported APIs

A partial support is provided for all the following APIs:

  • aws-cdk-lib/aws-lambda.Function
  • aws-cdk-lib/aws-lambda.SingletonFunction
  • aws-cdk-lib/aws-lambda-nodejs.NodejsFunction
  • aws-cdk-lib/aws-lambda-event-sources.SqsEventSource
  • aws-cdk-lib/aws-lambda-event-sources.SnsEventSource
  • aws-cdk-lib/aws-lambda-event-sources.S3EventSource
  • aws-cdk-lib/aws-lambda-event-sources.S3EventSourceV2
  • aws-cdk-lib/aws-lambda-event-sources.DynamoEventSource
  • aws-cdk-lib/aws-lambda-event-sources.KinesisEventSource
  • aws-cdk-lib/aws-lambda-event-sources.KinesisConsumerEventSource
  • aws-cdk-lib/aws-apigateway.RestApi
  • aws-cdk-lib/aws-apigateway.LambdaRestApi
  • aws-cdk-lib/aws-apigatewayv2.HttpApi.addRoutes
  • aws-cdk-lib/aws-sns.Topic.addSubscription
  • aws-cdk-lib/aws-sns-subscriptions.LambdaSubscription
  • aws-cdk-lib/aws-sns-subscriptions.UrlSubscription
  • aws-cdk-lib/aws-sns-subscriptions.SqsSubscription
  • aws-cdk-lib/aws-sns-subscriptions.EmailSubscription
  • aws-cdk-lib/aws-sns-subscriptions.SmsSubscription
  • aws-cdk-lib/aws-s3.Bucket.addEventNotification
  • aws-cdk-lib/aws-s3-notifications.LambdaDestination
  • aws-cdk-lib/aws-s3-notifications.SqsDestination
  • aws-cdk-lib/aws-s3-notifications.SnsDestination
  • aws-cdk-lib/aws-kinesis.Stream
  • aws-cdk-lib/aws-kinesis.StreamConsumer
  • aws-cdk-lib/aws-kinesisfirehose.DeliveryStream

Physical name of the AWS service instances

When setting up a service (such as a Lambda) with AWS CDK, one can provide an explicit physical name for the instance of the service (for example, using functionName for a Lambda).

When an explicit physical name is provided, and the service type is supported, this analyzer creates an object whose name exactly matches the provided physical name.

Often, the physical name is not provided and is automatically generated by AWS based on:

  • the construct ID passed to the service constructor;
  • the stack name in which the service is defined;
  • a hash/suffix that guarantees uniqueness.

In this case, for supported services, this extension creates a corresponding object named using the following pattern:

{StackName}-{ConstructID}-{}

The trailing ‘-{}’ represents the Hash value that cannot be predicted.

The {StackName} is inferred from:

  • the stackName property provided in the StackProps during stack instantiation.

If stackName is not specified, then:

  • {StackName} defaults to the Stack ID (the second argument of the stack constructor).

In the following example, a Lambda function is created without providing an explicit physical name (the functionName property is commented out).

Therefore, the generated Lambda physical name will be FooStack-LambdaConstructID-{}.

// lib/lambda-stack.js

const cdk = require('aws-cdk-lib');
const lambda = require('aws-cdk-lib/aws-lambda');

class LambdaStack extends cdk.Stack {
  constructor(scope, id, props) {
    super(scope, id, props);
    const fn = new lambda.Function(this, 'LambdaConstructID', {
      runtime: lambda.Runtime.NODEJS_18_X,
      handler: 'index.handler',
      code: mycode,
      //functionName: 'FooLambda'
    });

    this.mylambda_name = fn.functionName;
  }
}

module.exports = { LambdaStack };
// index.js

const cdk = require('aws-cdk-lib');
const { LambdaStack } = require('./lib/lambda-stack');

const app = new cdk.App();
new LambdaStack(app, 'StackId', {
  stackName: 'FooStack'
});

Lambda support

Supported APIs

A basic support for the following API is provided:

  • aws-cdk-lib/aws-lambda.Function
  • aws-cdk-lib/aws-lambda.SingletonFunction
  • aws-cdk-lib/aws-lambda-nodejs.NodejsFunction

Detailed support for aws-lambda.Function and Lambda aws-lambda.SingletonFunction

When an instantiation of aws-cdk-lib/aws-lambda.Function or aws-cdk-lib/aws-lambda.SingletonFunction is found in the analyzed source code, a NodeJS AWS Lambda Function object is created. It has a properties storing the handler path and the runtime. The linking from the lambda function to the handler function is then carried out by one of the following extensions (depending on the runtime):

Runtime Extension
java com.castsoftware.awsjavaexternal link
dotnet com.castsoftware.awsdotnetexternal link
python com.castsoftware.pythonexternal link
node.js com.castsoftware.nodejsexternal link (when the handler is written in .js)
com.castsoftware.typescriptexternal link (when the handler is written in .ts)

Note that the link to the handler function only works when the code property is provided via lambda.Code.fromAsset pointing to a directory, not to a pre-compressed ZIP file.

When analyzing the following source codes:

// lib/lambda-stack.js

const cdk = require('aws-cdk-lib');
const lambda = require('aws-cdk-lib/aws-lambda');

class LambdaLayerStack extends cdk.Stack {
  constructor(scope, id, props) {
    super(scope, id, props);

    const fn = new lambda.Function(this, 'LambdaConstructID', {
      runtime: lambda.Runtime.NODEJS_LATEST,
      code: lambda.Code.fromAsset('resources/lambda'),
      handler: 'index.handler',
      functionName: 'FooFunction',
    });
  }
}

module.exports = { LambdaLayerStack };
// resources/lambda/index.js

exports.handler = async (event) => {
  console.log('Received event:', JSON.stringify(event));
  return { statusCode: 200, body: 'OK' };
};

a NodeJS AWS Lambda Function object named FooFunction is created with link to the handler function as shown in the following snippet:

Known limitations

  • for node.js or python runtime, the link to the handler will be correctly created only when the code is provided through lambda.Code.fromAsset. In other cases, this extension will try to find the best match but may not find the right handler or select a wrong handler;
  • for node.js or python runtime, the lambda.Code.fromAsset should not take a compressed file;
  • code provided using lambda.Code.fromInline is not analyzed.

Detailed support for aws-lambda-nodejs.NodejsFunction

When an instantiation of aws-cdk-lib/aws-lambda-nodejs.NodejsFunction is found in the analyzed source code, a NodeJS AWS Lambda Function object is created. It has a properties storing the handler path and the runtime. A link to the handler function is created.

For example, when analyzing the following source codes:

// lib/my-stack.js

const path = require('path');
const { Stack } = require('aws-cdk-lib');
const { NodejsFunction } = require('aws-cdk-lib/aws-lambda-nodejs');
const lambda = require('aws-cdk-lib/aws-lambda');

class MyStack extends Stack {
  constructor(scope, id, props) {
    super(scope, id, props);

    new NodejsFunction(this, 'MyFunction', {
      runtime: lambda.Runtime.NODEJS_18_X,
      entry: path.join(__dirname, '../lambda/handler.js'),
      handler: 'handler',
      functionName: 'FooPhysicalName'
    });
  }
}

module.exports = { MyStack };
// lambda/handler.js

exports.handler = async (event) => {
  console.log('Received event:', JSON.stringify(event));
  return { statusCode: 200, body: 'OK' };
};

a NodeJS AWS Lambda Function object named FooPhysicalName is created with link to the handler function as shown in the following snippet:

Detailed support for aws-lambda-event-sources

Whenever the source code contains a call to the addEventSource API of an instance of a lambda Function (either aws-cdk-lib/aws-lambda.Function or aws-cdk-lib/aws-lambda-nodejs.NodejsFunction), the first argument of the addMethod API is checked.

A support is provided if that argument is an instance of one of the following EventSource:

  • aws-cdk-lib/aws-lambda-event-sources.SqsEventSource
  • aws-cdk-lib/aws-lambda-event-sources.SnsEventSource
  • aws-cdk-lib/aws-lambda-event-sources.S3EventSource
  • aws-cdk-lib/aws-lambda-event-sources.S3EventSourceV2
  • aws-cdk-lib/aws-lambda-event-sources.DynamoEventSource
  • aws-cdk-lib/aws-lambda-event-sources.KinesisEventSource
  • aws-cdk-lib/aws-lambda-event-sources.KinesisConsumerEventSource

Detailed support for each EventSource is given in the following subsections.

SqsEventSource

When the call to the addEventSource API takes an instance of aws-cdk-lib/aws-lambda-event-sources.SqsEventSource, a NodeJS AWS SQS Receiver or a NodeJS AWS Unknown SQS Receiver (if the evaluation of the queue name fails) object is created with a callLink to the lambda.

For example, when analyzing the following source code:

const cdk = require('aws-cdk-lib');
const sqs = require('aws-cdk-lib/aws-sqs');
const lambda = require('aws-cdk-lib/aws-lambda');
const { SqsEventSource } = require('aws-cdk-lib/aws-lambda-event-sources');

class SqsLambdaStack extends cdk.Stack {
  constructor(scope, id, props) {
    super(scope, id, props);

    const fn = new lambda.Function(this, 'MyLambda', {
      runtime: lambda.Runtime.NODEJS_18_X,
      code: lambda.Code.fromAsset('resources/lambda'),
      handler: 'index.handler',
      functionName: 'FooFunction',
    });

    const queue = new sqs.Queue(this, 'MyQueue', {
      queueName: 'fooqueue',
    });
    fn.addEventSource(new SqsEventSource(queue));
  }
}

you will get the following result:

SnsEventSource

When the call to the addEventSource API takes an instance of aws-cdk-lib/aws-lambda-event-sources.SnsEventSource, a NodeJS AWS SNS Subscriber or a NodeJS AWS Unknown SNS Subscriber (if the evaluation of the topic name fails) object is created with a callLink to the lambda.

For example, when analyzing the following source code:

const cdk = require('aws-cdk-lib');
const sns = require('aws-cdk-lib/aws-sns');
const lambda = require('aws-cdk-lib/aws-lambda');
const { SnsEventSource } = require('aws-cdk-lib/aws-lambda-event-sources');

class SnsLambdaStack extends cdk.Stack {
  constructor(scope, id, props) {
    super(scope, id, props);

    const fn = new lambda.Function(this, 'MyLambda', {
      runtime: lambda.Runtime.NODEJS_18_X,
      code: lambda.Code.fromAsset('resources/lambda'),
      handler: 'index.handler',
      functionName: 'FooFunction',
    });

    const topic = new sns.Topic(this, 'MyTopic', {
      topicName: 'fooTopic',
    });
    fn.addEventSource(new SnsEventSource(topic));
  }
}

you will get the following result:

S3EventSource and S3EventSourceV2

When the call to the addEventSource API takes an instance of aws-cdk-lib/aws-lambda-event-sources.S3EventSource or aws-cdk-lib/aws-lambda-event-sources.S3EventSourceV2, a property Names of s3 events triggering the lambda is added to the lambda. This property saves the name of the S3 bucket as well event type that would trigger the lambda. The analyzer creates a callLink to the lambda function object from all callables linked to the given bucket through a link of matching type. The following table tells which link type will match which event type (in the table, the * matches any string).

Event type Matching link types
No event type all
OBJECT_CREATED useInsert, useUpdate
OBJECT_CREATED_* useInsert, useUpdate
OBJECT_REMOVED useDelete
OBJECT_REMOVED_* useDelete
other event types None

For example, when analyzing the following source code:

const cdk = require('aws-cdk-lib');
const lambda = require('aws-cdk-lib/aws-lambda');
const { S3EventSource } = require('aws-cdk-lib/aws-lambda-event-sources');
const s3 = require('aws-cdk-lib/aws-s3');

class S3LambdaStack extends cdk.Stack {
  constructor(scope, id, props) {
    super(scope, id, props);

    const fn = new lambda.Function(this, 'MyLambda', {
      runtime: lambda.Runtime.NODEJS_18_X,
      code: lambda.Code.fromAsset('resources/lambda'),
      handler: 'index.handler',
      functionName: 'FooFunction',
    });

    const bucket = new s3.Bucket(this, 'MyBucket', {
      bucketName: 'fooBucket',
    });
    fn.addEventSource(
      new S3EventSource(bucket, {
        events: [s3.EventType.OBJECT_CREATED],
      })
    );
  }
}

and there is a my_s3_put function that puts an object in the fooBucket S3 Bucket:

const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');

const client = new S3Client();

async function my_s3_put() {
  await client.send(new PutObjectCommand({
    Bucket: 'fooBucket',
    Key: 'my-object-key',
    Body: 'Hello World',
  }));
}

module.exports = { my_s3_put };

you will get the following result:

Known limitations

Some filters can also be specified in the S3EventSource to determine which objects trigger this event. These are not supported, and this extension will create links regardless of filters.

DynamoEventSource

When the call to the addEventSource API takes an instance of aws-cdk-lib/aws-lambda-event-sources.DynamoEventSource, a property Name of dynamodb tables triggering the lambda is added to the lambda. This property saves the name of the dynamoDB Tables that would trigger the lambda. The analyzer creates a callLink to the lambda function object from all callables linked to the given table.

For example, when analyzing the following source code:

const cdk = require('aws-cdk-lib');
const lambda = require('aws-cdk-lib/aws-lambda');
const { DynamoEventSource } = require('aws-cdk-lib/aws-lambda-event-sources');
const dynamodb = require('aws-cdk-lib/aws-dynamodb');

class DynamoLambdaStack extends cdk.Stack {
  constructor(scope, id, props) {
    super(scope, id, props);

    const fn = new lambda.Function(this, 'MyLambda', {
      runtime: lambda.Runtime.NODEJS_18_X,
      code: lambda.Code.fromAsset('resources/lambda'),
      handler: 'index.handler',
      functionName: 'FooFunction',
    });

    const table = new dynamodb.Table(this, 'MyTable', {
      tableName: 'fooTable',
    });
    fn.addEventSource(new DynamoEventSource(table, {
      startingPosition: lambda.StartingPosition.LATEST,
    }));
  }
}

and there is a my_dynamodb_put function that puts an item in the fooTable DynamoDB table:

const { DynamoDBClient, PutItemCommand } = require('@aws-sdk/client-dynamodb');

const client = new DynamoDBClient();

async function my_dynamodb_put() {
  await client.send(new PutItemCommand({
    TableName: 'fooTable',
    Item: {
      id: { S: '123' },
      name: { S: 'example' },
    },
  }));
}

module.exports = { my_dynamodb_put };

you will get the following result:

KinesisEventSource and KinesisConsumerEventSource

When the call to the addEventSource API takes an instance of aws-cdk-lib/aws-lambda-event-sources.KinesisEventSource or aws-cdk-lib/aws-lambda-event-sources.KinesisConsumerEventSource, a NodeJS AWS Kinesis Consumer or a NodeJS AWS Unknown Kinesis Consumer (if the evaluation of the stream name fails) object is created with a callLink to the lambda.

  • KinesisEventSource takes a kinesis.Stream instance as its first argument. The stream name is read from the streamName property of the stream’s props, or falls back to the {StackName}-{ConstructID}-{} pattern.
  • KinesisConsumerEventSource takes a kinesis.StreamConsumer instance. The stream name is resolved by following the stream property of the consumer’s props back to its parent kinesis.Stream.

For example, when analyzing the following source code:

const cdk = require('aws-cdk-lib');
const lambda = require('aws-cdk-lib/aws-lambda');
const kinesis = require('aws-cdk-lib/aws-kinesis');
const { KinesisEventSource } = require('aws-cdk-lib/aws-lambda-event-sources');

class KinesisLambdaStack extends cdk.Stack {
  constructor(scope, id, props) {
    super(scope, id, props);

    const fn = new lambda.Function(this, 'MyLambda', {
      runtime: lambda.Runtime.NODEJS_18_X,
      code: lambda.Code.fromAsset('resources/lambda'),
      handler: 'index.handler',
      functionName: 'FooFunction',
    });

    const stream = new kinesis.Stream(this, 'MyStream', {
      streamName: 'fooStream',
    });
    fn.addEventSource(new KinesisEventSource(stream, {
      startingPosition: lambda.StartingPosition.LATEST,
    }));
  }
}

a NodeJS AWS Kinesis Consumer object named fooStream is created with a callLink to the lambda, you will get the following result:

Known limitations

  • if the lambda instance is accessed through the fromFunctionArn or the fromFunctionName API the eventSource support will not work.

API Gateway support

Supported APIs

A basic support is provided for the following APIs:

  • aws-cdk-lib/aws-apigateway.RestApi
  • aws-cdk-lib/aws-apigateway.LambdaRestApi
  • aws-cdk-lib/aws-apigatewayv2.HttpApi.addRoutes

Detailed support for aws-apigateway

We support RestApi instantiated with both:

  • aws-cdk-lib/aws-apigateway.RestApi
  • aws-cdk-lib/aws-apigateway.LambdaRestApi

The RestApi instances have a root attribute that represents the root resource of the API endpoint (’/’). From this root, a path can be built using a succession of addResource method calls.

When an addMethod API call is found on a Resource, an AWS {Verb} API Gateway object is created (where {Verb} is derived from the first argument of the API call). The second argument of the addMethod API is checked:

  • if it is an instance of aws-cdk-lib/aws-apigateway.LambdaIntegration, a callLink from the API Gateway to the corresponding NodeJS AWS Lambda Function is created.
  • if it is an instance of aws-cdk-lib/aws-apigateway.HttpIntegration, a NodeJS {Verb} HttpRequest service object is created, along with a callLink from the API Gateway to that object. The name of this object is derived from the argument passed to the HttpIntegration instantiation.
  • if it is an instance of aws-cdk-lib/aws-apigateway.AwsIntegration and if the service property is ‘sqs’ or ‘sns’ a NodeJS AWS SQS Publisher or NodeJS AWS SNS Publisher object is created, along with a callLink from the API Gateway to that object.

When no integration is provided to the addMethod API, and the RestApi was instantiated using LambdaRestApi, a callLink is created from the API Gateway to the NodeJS AWS Lambda Function that was passed as the handler property to the LambdaRestApi instantiation.

Here is an example, when analyzing the following source code:

const cdk = require('aws-cdk-lib');
const apigateway = require('aws-cdk-lib/aws-apigateway');
const lambda = require('aws-cdk-lib/aws-lambda');
const sns = require('aws-cdk-lib/aws-sns');

class LambdaLayerStack extends cdk.Stack {
  constructor(scope, id, props) {
    super(scope, id, props);

    const fn = new lambda.Function(this, 'LambdaConstructID', {
      runtime: lambda.Runtime.NODEJS_LATEST,
      code: lambda.Code.fromAsset('resources/lambda'),
      handler: 'index.handler',
      functionName: 'FooFunction'
    });

    const topic = new sns.Topic(this, 'MyTopic', {
      topicName: 'my_topic',
    });

    const api = new apigateway.RestApi(this, 'myapi', {
      proxy: false
    });

    const base = api.root.addResource('base');
    const r1 = base.addResource('path');
    r1.addMethod('GET', new apigateway.LambdaIntegration(fn));

    const r2 = base.addResource('otherpath');
    r2.addMethod('GET', new apigateway.HttpIntegration('http://some/url'));
    r2.addMethod('POST', new apigateway.AwsIntegration({
      service: 'sns',
      integrationHttpMethod: 'POST',
      path: `${this.account}/${topic.topicName}`,
      options: {}
    }));
  }
}

you will get the following result:

Known limitations

  • aws-cdk-lib/aws-apigateway.AwsIntegration is supported only for sns and sqs services
  • addProxy API is not supported

Detailed support aws-apigatewayv2

When the aws-cdk-lib/aws-apigatewayv2.HttpApi.addRoutes API is used in a .js source file, an AWS {Verb} API Gateway object is created, where {Verb} is extracted from the methods property. The name of this object is derived from the path property.

If the integration property is instance of aws-cdk-lib/aws-apigatewayv2-integrations.HttpLambdaIntegration, a link is created from the API Gateway to the corresponding NodeJS AWS Lambda Function.

If the integration property is an instance of aws-cdk-lib/aws-apigatewayv2-integrations.HttpUrlIntegration, a NodeJS {Verb} HttpRequest service object is created along with a callLink from the API Gateway to that object. The name of that object is derived from the second argument passed to the HttpUrlIntegration instantiation.

For example, when analyzing the following source code:

const cdk = require('aws-cdk-lib');
const apigwv2 = require('aws-cdk-lib/aws-apigatewayv2');
const lambda = require('aws-cdk-lib/aws-lambda');
const integrations = require('aws-cdk-lib/aws-apigatewayv2-integrations');

class LambdaLayerStack extends cdk.Stack {
  constructor(scope, id, props) {
    super(scope, id, props);

    const fn = new lambda.Function(this, 'LambdaConstructID', {
      runtime: lambda.Runtime.NODEJS_LATEST,
      code: lambda.Code.fromAsset('resources/lambda'),
      handler: 'index.handler',
      functionName: 'FooFunction'
    });

    const api = new apigwv2.HttpApi(this, 'HttpApi');

    api.addRoutes({
      path: '/foo/path',
      methods: [apigwv2.HttpMethod.GET],
      integration: new integrations.HttpLambdaIntegration(
        'LambdaIntegration',
        fn,
      ),
    });

    api.addRoutes({
      path: '/books',
      methods: [apigwv2.HttpMethod.GET],
      integration: new integrations.HttpUrlIntegration(
        'GetBooksIntegration',
        'https://get-books-proxy.example.com'
      ),
    });
  }
}

you will get the following result:

Detailed support for aws-sns-subscriptions

When an instance of aws-cdk-lib/aws-sns.Topic is found in the analyzed source code, and that instance invokes the addSubscription API at least once with a supported subscription type as its argument, a NodeJS AWS SNS Subscriber object is created.

The name of the NodeJS AWS SNS Subscriber object is derived from the name of the SNS Topic.

For each supported subscription passed to addSubscription, a corresponding object is created, and a callLink is established from the NodeJS AWS SNS Subscriber to that object.

The table below lists the supported subscription types and the corresponding objects that the extension creates:

Supported subscription Object created
aws-cdk-lib/aws-sns-subscriptions.LambdaSubscription NodeJS Call to AWS Lambda Function
aws-cdk-lib/aws-sns-subscriptions.UrlSubscription NodeJS Post HttpRequest service
aws-cdk-lib/aws-sns-subscriptions.SqsSubscription NodeJS AWS SQS Publisher
aws-cdk-lib/aws-sns-subscriptions.EmailSubscription NodeJS Email
aws-cdk-lib/aws-sns-subscriptions.SmsSubscription NodeJS SMS

For example, when analyzing the following source code:

const { Stack } = require('aws-cdk-lib');
const sns = require('aws-cdk-lib/aws-sns');
const subscriptions = require('aws-cdk-lib/aws-sns-subscriptions');
const lambda = require('aws-cdk-lib/aws-lambda');
const sqs = require('aws-cdk-lib/aws-sqs');

class SnsLambdaStack extends Stack {
  constructor(scope, id, props) {
    super(scope, id, props);

    const topic = new sns.Topic(this, 'Topic', {
      topicName: 'FooTopic',
    });

    const fn = new lambda.Function(this, 'Handler', {
      functionName: 'FooFunction',
      runtime: lambda.Runtime.NODEJS_18_X,
      handler: 'index.handler',
      code: lambda.Code.fromInline(`
        exports.handler = async (event) => {
          console.log(JSON.stringify(event, null, 2));
        };
      `),
    });

    topic.addSubscription(
      new subscriptions.LambdaSubscription(fn)
    );

    const queue = new sqs.Queue(this, 'MyQueue', {
      queueName: 'FooQueue',
    });

    topic.addSubscription(
      new subscriptions.UrlSubscription(
        'https://example.com/webhook',
      )
    );

    topic.addSubscription(
      new subscriptions.SqsSubscription(queue)
    );

    topic.addSubscription(
      new subscriptions.EmailSubscription(
        'user@example.com'
      )
    );

    topic.addSubscription(
      new subscriptions.SmsSubscription(
        '+1234567890'
      )
    );
  }
}

you will get the following result:

Support for aws-s3.Bucket.addEventNotification

When a call to the aws-cdk-lib/aws-s3.Bucket.addEventNotification API is detected, the first argument of the call (the event type) and the destination object are analyzed.

Support is provided only when the destination argument is an instance of one of the following types:

  • aws-cdk-lib/aws-s3-notifications.LambdaDestination
  • aws-cdk-lib/aws-s3-notifications.SqsDestination
  • aws-cdk-lib/aws-s3-notifications.SnsDestination

For supported destinations, the corresponding S3 Event Handler is inferred and linked as described below.

Detailed support for aws-s3-notifications.LambdaDestination

When the addEventNotification API is called with an instance of aws-cdk-lib/aws-s3-notifications.LambdaDestination, the corresponding NodeJS AWS Lambda Function object is inferred from the first argument of the destination instantiation.

This NodeJS AWS Lambda Function object is considered the S3 Event Handler.

The property CAST_AWS_S3_Event_Handler.s3_events is stored on that object and contains:

  • the bucket name
  • the S3 event type that triggers the handler

Detailed support for aws-s3-notifications.SqsDestination

When the addEventNotification API is called with an instance of aws-cdk-lib/aws-s3-notifications.SqsDestination, the queue name is inferred from the first argument of the destination instantiation.

If the queue name is successfully inferred, a NodeJS AWS SQS Publisher object is created. Otherwise, a NodeJS AWS Unknown SQS Publisher object is created.

The created object is considered the S3 Event Handler.

The property CAST_AWS_S3_Event_Handler.s3_events is stored on that object and contains:

  • the bucket name
  • the S3 event type that triggers the handler

The com.castsoftware.wbslinker extension then creates a callLink to this S3 Event Handler based on the stored s3_events property.

Detailed support for aws-s3-notifications.SnsDestination

When the addEventNotification API is called with an instance of aws-cdk-lib/aws-s3-notifications.SnsDestination, the topic name is inferred from the first argument of the destination instantiation.

If the topic name is successfully inferred, a NodeJS AWS SNS Publisher object is created. Otherwise, a NodeJS AWS Unknown SNS Publisher object is created.

The created object is considered the S3 Event Handler.

The property CAST_AWS_S3_Event_Handler.s3_events is stored on that object and contains:

  • the bucket name
  • the S3 event type that triggers the handler

The com.castsoftware.wbslinker extension then creates a callLink to this S3 Event Handler based on the stored s3_events property.

Based on the bucket name and event type provided in CAST_AWS_S3_Event_Handler.s3_events, the com.castsoftware.wbslinker extension creates a callLink to the S3 Event Handler object.

The link is created from all callables already linked to the given bucket through a matching link type, as defined in the table below (* matches any string):

Event type Matching link types
OBJECT_CREATED useInsert, useUpdate
OBJECT_CREATED_* useInsert, useUpdate
OBJECT_REMOVED useDelete
OBJECT_REMOVED_* useDelete
other event types None

Filters (such as prefix or suffix) can be specified in the addEventNotification call to restrict which objects trigger the event. These filters are not currently supported, and links are created regardless of filters.

Example

When analyzing the following source code:

const cdk = require('aws-cdk-lib');
const s3 = require('aws-cdk-lib/aws-s3');
const sqs = require('aws-cdk-lib/aws-sqs');
const s3n = require('aws-cdk-lib/aws-s3-notifications');
const sns = require('aws-cdk-lib/aws-sns');
const lambda = require('aws-cdk-lib/aws-lambda');

class SimpleStack extends cdk.Stack {
  constructor(scope, id, props) {
    super(scope, id, props);

    const bucket = new s3.Bucket(this, 'MyBucket', {
      bucketName: 'fooBucket',
    });

    const queue = new sqs.Queue(this, 'MyQueue', {
      queueName: 'fooQueue',
    });

    const topic = new sns.Topic(this, 'MyTopic', {
      topicName: 'fooTopic',
    });

    const fn = new lambda.Function(this, 'MyFunction', {
      runtime: lambda.Runtime.NODEJS_18_X,
      handler: 'index.handler',
      functionName: 'MyFunction',
      code: lambda.Code.fromInline(
        'exports.handler = async (event) => { console.log(JSON.stringify(event)); };'
      ),
    });

    bucket.addEventNotification(
      s3.EventType.OBJECT_CREATED,
      new s3n.LambdaDestination(fn)
    );

    bucket.addEventNotification(
      s3.EventType.OBJECT_DELETED,
      new s3n.SqsDestination(queue)
    );

    bucket.addEventNotification(
      s3.EventType.OBJECT_CREATED,
      new s3n.SnsDestination(topic)
    );

    bucket.addEventNotification(
      s3.EventType.OBJECT_DELETED,
      new s3n.SnsDestination(topic)
    );
  }
}

and there are two my_s3_put and my_s3_delete functions that acts an object in the fooBucket S3 Bucket:

const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');

const client = new S3Client();

async function my_s3_put() {
  await client.send(new PutObjectCommand({
    Bucket: 'fooBucket',
    Key: 'my-object-key',
    Body: 'Hello World',
  }));
}

module.exports = { my_s3_put };
const { S3Client, DeleteObjectCommand } = require('@aws-sdk/client-s3');

const client = new S3Client();

async function my_s3_delete() {
  await client.send(new DeleteObjectCommand({
    Bucket: 'fooBucket',
    Key: 'my-object-key',
    Body: 'Hello World',
  }));
}

module.exports = { my_s3_delete };

you will get the following result:

Known limitations

  • if the lambda instance is accessed through the fromFunctionArn or the fromFunctionName API the LambdaDestination support will not work.

Firehose support

Supported APIs

A basic support is provided for the following APIs:

  • aws-cdk-lib/aws-kinesisfirehose.DeliveryStream
  • aws-cdk-lib/aws-kinesisfirehose.KinesisStreamSource

Detailed support for aws-kinesisfirehose.DeliveryStream

When an instantiation of aws-cdk-lib/aws-kinesisfirehose.DeliveryStream is found in the analyzed source code, a NodeJS AWS Firehose Stream Delivery object is created (or a NodeJS AWS Unknown Firehose Stream Delivery if the name cannot be resolved). Its name is derived from the streamName property of the props argument. If streamName is not provided, the name falls back to the construct ID pattern {StackName}-{ConstructID}-{}.

The destination property of the props is analyzed to determine the delivery target. For supported delivery an object is created with a link from the NodeJS AWS Firehose Stream Delivery object to that object. The following destination classes are supported:

Destination class Object created Link type
S3Bucket NodeJS S3 Bucket useInsertLink
HttpEndpointDestination NodeJS Post HttpRequest service callLink

When an S3Bucket destination includes a LambdaFunctionProcessor in its processors list, an additional callLink is created from the delivery stream to the corresponding NodeJS Call to AWS Lambda Function.

For example, when analyzing the following source code:

const cdk = require('aws-cdk-lib');
const firehose = require('aws-cdk-lib/aws-kinesisfirehose');
const s3 = require('aws-cdk-lib/aws-s3');
const lambda = require('aws-cdk-lib/aws-lambda');

class FirehoseStack extends cdk.Stack {
  constructor(scope, id, props) {
    super(scope, id, props);

    const bucket = new s3.Bucket(this, 'MyBucket', {
      bucketName: 'my-firehose-bucket'
    });

    const fn = new lambda.Function(this, 'Processor', {
      runtime: lambda.Runtime.NODEJS_18_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('resources/lambda'),
      functionName: 'my-processor',
    });

    new firehose.DeliveryStream(this, 'MyStream', {
      streamName: 'my-delivery-stream',
      destination: new firehose.S3Bucket(bucket, {
        processors: [new firehose.LambdaFunctionProcessor(fn)],
      }),
    });

    new firehose.DeliveryStream(this, 'HttpStream', {
      streamName: 'my-http-stream',
      destination: new firehose.HttpEndpointDestination(
        'https://my-endpoint.example.com'
      ),
    });
  }
}

this will produce the following results:

  • a NodeJS AWS Firehose Stream Delivery object named my-delivery-stream with a useInsertLink to the S3 bucket my-firehose-bucket and a callLink to the Lambda function my-processor;
  • a NodeJS AWS Firehose Stream Delivery object named my-http-stream with a callLink to the NodeJS Post HttpRequest service https://my-endpoint.example.comexternal link.

Detailed support for aws-kinesisfirehose.KinesisStreamSource

When a DeliveryStream is configured with a KinesisStreamSource as its source property, a NodeJS AWS Kinesis Consumer object is created (or a NodeJS AWS Unknown Kinesis Consumer if the stream name cannot be resolved) with a callLink to the NodeJS AWS Firehose Stream Delivery object.

The Kinesis stream name is resolved from:

  • the streamName property of the stream’s props (when the stream is instantiated inline), or
  • the stream name extracted from the ARN (when the stream is imported via kinesis.Stream.fromStreamArn or kinesis.Stream.fromStreamAttributes).

For example, when analyzing the following source code:

const { cdk } = require('aws-cdk-lib');
const firehose = require('aws-cdk-lib/aws-kinesisfirehose');
const s3 = require('aws-cdk-lib/aws-s3');
const kinesis = require('aws-cdk-lib/aws-kinesis');

class FirehoseKinesisStack extends cdk.Stack {
  constructor(scope, id, props) {
    super(scope, id, props);

    const bucket = new s3.Bucket(this, 'MyBucket', {
      bucketName: 'my-firehose-bucket',
    });

    // Inline stream creation
    const stream = new kinesis.Stream(this, 'MyStream', {
      streamName: 'my-kinesis-stream',
    });

    new firehose.DeliveryStream(this, 'MyDeliveryStream', {
      streamName: 'my-delivery-stream',
      source: new firehose.KinesisStreamSource(stream),
      destination: new firehose.S3Bucket(bucket),
    });

    // Imported stream via ARN
    const importedStream = kinesis.Stream.fromStreamArn(
      this, 'ImportedStream',
      'arn:aws:kinesis:us-east-1:123456789012:stream/imported-kinesis-stream'
    );

    new firehose.DeliveryStream(this, 'ImportedDeliveryStream', {
      streamName: 'imported-delivery-stream',
      source: new firehose.KinesisStreamSource(importedStream),
      destination: new firehose.S3Bucket(bucket),
    });
  }
}

this will produce the following results:

  • a NodeJS AWS Kinesis Consumer object named my-kinesis-stream with a callLink to the NodeJS AWS Firehose Stream Delivery named my-delivery-stream;
  • a NodeJS AWS Kinesis Consumer object named imported-kinesis-stream with a callLink to the NodeJS AWS Firehose Stream Delivery named imported-delivery-stream;
  • both NodeJS AWS Firehose Stream Delivery objects have a useInsertLink to the NodeJS AWS S3 Bucket named my-firehose-bucket.