diff --git a/.github/boring-cyborg.yml b/.github/boring-cyborg.yml index 8b7e65729f..bdf30f990e 100644 --- a/.github/boring-cyborg.yml +++ b/.github/boring-cyborg.yml @@ -71,22 +71,7 @@ labelPRBasedOnFilePath: - docs/**/* - mkdocs.yml - typedoc.js - - examples/cdk/bin/* - - examples/cdk/functions/* - - examples/cdk/functions/**/* - - examples/cdk/src/* - - examples/cdk/src/**/* - - examples/cdk/tests/* - - examples/cdk/tests/**/* - - examples/cdk/README.md - - examples/cdk/cdk.json - - examples/sam/events/* - - examples/sam/src/* - - examples/sam/src/**/* - - examples/sam/tests/* - - examples/sam/tests/**/* - - examples/sam/README.md - - examples/sam/template.yaml + - examples/app/* area/automation: - .github/scripts/* @@ -137,8 +122,7 @@ labelPRBasedOnFilePath: - packages/parser/README.md - layers/tsconfig*.json - layers/README.md - - examples/sam/tsconfig*.json - - examples/cdk/tsconfig*.json + - examples/app/tsconfig*.json type/dependencies: - package.json @@ -153,8 +137,7 @@ labelPRBasedOnFilePath: - packages/validator/package.json - packages/batch/package.json - layers/package.json - - examples/cdk/package.json - - examples/sam/package.json + - examples/app/package.json ##### Greetings ######################################################################################################## firstPRWelcomeComment: > diff --git a/.github/workflows/reusable-run-linting-check-and-unit-tests.yml b/.github/workflows/reusable-run-linting-check-and-unit-tests.yml index 519ab4845f..fd662fa7c9 100644 --- a/.github/workflows/reusable-run-linting-check-and-unit-tests.yml +++ b/.github/workflows/reusable-run-linting-check-and-unit-tests.yml @@ -37,7 +37,7 @@ jobs: NODE_ENV: dev strategy: matrix: - example: ["sam", "cdk"] + example: ["app"] fail-fast: false defaults: run: diff --git a/.gitignore b/.gitignore index 873ab3e50d..1dde8041bc 100644 --- a/.gitignore +++ b/.gitignore @@ -41,10 +41,6 @@ site # Generated API documentation (from TypeDoc) /api -# SAM Example copies files -/examples/sam/src/handlers/* -!/examples/sam/src/handlers/COPY_LAMBDA_FUNCTIONS_HERE - # Layer temp files tmp diff --git a/README.md b/README.md index 71a3bb7613..b405d9d883 100644 --- a/README.md +++ b/README.md @@ -84,8 +84,7 @@ Or refer to the installation guide of each utility: ### Examples -* [CDK](https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/examples/cdk) -* [SAM](https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/examples/sam) +You can find examples of how to use Powertools for AWS Lambda (TypeScript) in the [examples](https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/examples/app) directory. The application is a simple REST API that can be deployed via either AWS CDK or AWS SAM. ### Demo applications diff --git a/docs/contributing/conventions.md b/docs/contributing/conventions.md index 517c556fff..84efdc0dfc 100644 --- a/docs/contributing/conventions.md +++ b/docs/contributing/conventions.md @@ -31,7 +31,7 @@ Whenever possible, we use the same directory structure for all utilities. This m There are also a few other workspaces that are not utilities published to npm, but that still share dependencies and/or runtime code with the utilities. These workspaces are: * `docs/snippets`: contains the documentation code snippets -* `examples/*`: contains the example projects deployed via AWS CDK or AWS SAM +* `examples/app`: contains an example project that can be deployed via AWS CDK or AWS SAM * `layers`: contains the code used to build and publish the [Lambda layers](../index.md#lambda-layer) ## Testing definition diff --git a/docs/index.md b/docs/index.md index cda7576b08..f668ad1ac8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -272,12 +272,9 @@ The examples in this documentation will feature all the approaches described abo ## Examples -The project's repository includes examples of how to instrument your functions both in AWS CDK and AWS SAM: +You can find examples of how to use Powertools for AWS Lambda (TypeScript) in the [examples](https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/examples/app){target="_blank"} directory. The application is a simple REST API that can be deployed via either AWS CDK or AWS SAM. -* [AWS CDK](https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/examples/cdk){target="_blank"} -* [AWS SAM](https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/examples/sam){target="_blank"} - -If instead you want to see Powertools for AWS Lambda (TypeScript) in a slightly more complex use case, check the [Serverless TypeScript Demo](https://github.com/aws-samples/serverless-typescript-demo) or the [AWS Lambda performance tuning](https://github.com/aws-samples/optimizations-for-lambda-functions) repository. Both demos use Powertools for AWS Lambda (TypeScript) as well as demonstrating other common techniques for Lambda functions written in TypeScript. +If instead you want to see Powertools for AWS Lambda (TypeScript) in slightly different use cases, check the [Serverless TypeScript Demo](https://github.com/aws-samples/serverless-typescript-demo) or the [AWS Lambda performance tuning](https://github.com/aws-samples/optimizations-for-lambda-functions) repository. Both demos use Powertools for AWS Lambda (TypeScript) as well as demonstrating other common techniques for Lambda functions written in TypeScript. ## Features diff --git a/examples/cdk/.gitignore b/examples/app/.gitignore similarity index 62% rename from examples/cdk/.gitignore rename to examples/app/.gitignore index c32e2a3e10..46b9212ee4 100644 --- a/examples/cdk/.gitignore +++ b/examples/app/.gitignore @@ -8,5 +8,13 @@ node_modules .cdk.staging cdk.out lib +# npm lock file - this is managed by the npm workspace +package-lock.json + +**/.aws-sam + +# SAM +samconfig.toml + # npm lock file - this is managed by the npm workspace package-lock.json \ No newline at end of file diff --git a/examples/cdk/CHANGELOG.md b/examples/app/CHANGELOG.md similarity index 100% rename from examples/cdk/CHANGELOG.md rename to examples/app/CHANGELOG.md diff --git a/examples/cdk/README.md b/examples/app/README.md similarity index 100% rename from examples/cdk/README.md rename to examples/app/README.md diff --git a/examples/cdk/cdk.json b/examples/app/cdk.json similarity index 90% rename from examples/cdk/cdk.json rename to examples/app/cdk.json index bdc18cde00..35c81a9ac2 100644 --- a/examples/cdk/cdk.json +++ b/examples/app/cdk.json @@ -1,5 +1,5 @@ { - "app": "npx ts-node --prefer-ts-exts bin/cdk-app.ts", + "app": "tsx cdk/example-app.ts", "watch": { "include": [ "**" diff --git a/examples/app/cdk/example-app.ts b/examples/app/cdk/example-app.ts new file mode 100644 index 0000000000..d68dbdc1e3 --- /dev/null +++ b/examples/app/cdk/example-app.ts @@ -0,0 +1,7 @@ +#!/usr/bin/env node +import 'source-map-support/register'; +import { App } from 'aws-cdk-lib'; +import { PowertoolsExampleStack } from './example-stack.js'; + +const app = new App(); +new PowertoolsExampleStack(app, 'PowertoolsTypeScript-Example-CDK', {}); diff --git a/examples/app/cdk/example-stack.ts b/examples/app/cdk/example-stack.ts new file mode 100644 index 0000000000..b5e7f66284 --- /dev/null +++ b/examples/app/cdk/example-stack.ts @@ -0,0 +1,240 @@ +import { RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib'; +import { LambdaIntegration, RestApi } from 'aws-cdk-lib/aws-apigateway'; +import { + AttributeType, + BillingMode, + StreamViewType, + Table, + TableClass, +} from 'aws-cdk-lib/aws-dynamodb'; +import { + FilterCriteria, + FilterRule, + LayerVersion, + StartingPosition, +} from 'aws-cdk-lib/aws-lambda'; +import { SqsDestination } from 'aws-cdk-lib/aws-lambda-destinations'; +import { DynamoEventSource } from 'aws-cdk-lib/aws-lambda-event-sources'; +import { OutputFormat } from 'aws-cdk-lib/aws-lambda-nodejs'; +import { Queue } from 'aws-cdk-lib/aws-sqs'; +import { StringParameter } from 'aws-cdk-lib/aws-ssm'; +import { Construct } from 'constructs'; +import { FunctionWithLogGroup } from './function-with-logstream-construct.js'; + +export class PowertoolsExampleStack extends Stack { + public constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + // Import Powertools layer to be used in some of the functions + const powertoolsLayer = LayerVersion.fromLayerVersionArn( + this, + 'powertools-layer', + `arn:aws:lambda:${ + Stack.of(this).region + }:094274105915:layer:AWSLambdaPowertoolsTypeScriptV2:3` + ); + + // Items table + const itemsTable = new Table(this, 'items-table', { + tableName: 'powertools-example-items', + tableClass: TableClass.STANDARD_INFREQUENT_ACCESS, + billingMode: BillingMode.PAY_PER_REQUEST, + partitionKey: { + type: AttributeType.STRING, + name: 'id', + }, + removalPolicy: RemovalPolicy.DESTROY, + stream: StreamViewType.NEW_IMAGE, // we use the stream to trigger the processItemsStreamFn + }); + + // Idempotency table + const idempotencyTable = new Table(this, 'idempotencyTable', { + tableName: 'powertools-example-idempotency', + partitionKey: { + name: 'id', + type: AttributeType.STRING, + }, + timeToLiveAttribute: 'expiration', + billingMode: BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // for demo only, change to RETAIN in production + }); + + /** + * We will store the idempotency table name in a SSM parameter to simulate a potential + * cross-stack reference. This is not strictly necessary in this example, but it's a good way of showing + * how to use SSM parameters and retrieve them using Powertools. + */ + const idempotencyTableNameParam = new StringParameter( + this, + 'idempotency-table-name', + { + parameterName: '/items-store/idempotency-table-name', + stringValue: idempotencyTable.tableName, + } + ); + + /** + * In this example, we use ESM and bundle all the dependencies + * including the AWS SDK. + * + * Because we are using ESM and tree shake, we create an optimized bundle. + */ + const putItemFn = new FunctionWithLogGroup(this, 'put-item-fn', { + entry: './functions/put-item.ts', + functionName: 'powertools-example-put-item', + bundling: { + minify: true, + sourceMap: true, + keepNames: true, + format: OutputFormat.ESM, + sourcesContent: true, + mainFields: ['module', 'main'], + externalModules: [], // we bundle all the dependencies + esbuildArgs: { + '--tree-shaking': 'true', + }, + // We include this polyfill to support `require` in ESM due to AWS X-Ray SDK for Node.js not being ESM compatible + banner: + 'import { createRequire } from "module";const require = createRequire(import.meta.url);', + }, + }); + putItemFn.bindTable({ table: itemsTable, accessMode: 'RW' }); + /** + * Allow the function to read and write to the idempotency table so it + * can persist idempotency data + */ + putItemFn.bindTable({ + table: idempotencyTable, + accessOnly: true, + accessMode: 'RW', + }); + /** + * Also allow the function to fetch the SSM parameter + * that contains the idempotency table name + */ + idempotencyTableNameParam.grantRead(putItemFn); + putItemFn.addEnvironment( + 'SSM_PARAMETER_NAME', + idempotencyTableNameParam.parameterName + ); + + /** + * In this example, we instead use the Powertools layer to include Powertools + * as well as the AWS SDK, this is a convenient way to use Powertools + * in a centralized way across all your functions. + */ + const getAllItemsFn = new FunctionWithLogGroup(this, 'get-all-items-fn', { + entry: './functions/get-all-items.ts', + functionName: 'powertools-example-get-all-items', + layers: [powertoolsLayer], // we use the powertools layer + bundling: { + minify: true, + sourceMap: true, + keepNames: true, + format: OutputFormat.ESM, + mainFields: ['module', 'main'], + sourcesContent: true, + externalModules: ['@aws-sdk/*', '@aws-lambda-powertools/*'], // the dependencies are included in the layer + esbuildArgs: { + '--tree-shaking': 'true', + }, + // We include this polyfill to support `require` in ESM due to AWS X-Ray SDK for Node.js not being ESM compatible + banner: + 'import { createRequire } from "module";const require = createRequire(import.meta.url);', + }, + }); + getAllItemsFn.bindTable({ table: itemsTable }); + + /** + * In this examle, we emit a CommonJS (CJS) bundle and include all the + * dependencies in it. + */ + const getByIdFn = new FunctionWithLogGroup(this, 'get-by-id-fn', { + entry: './functions/get-by-id.ts', + functionName: 'powertools-example-get-by-id', + bundling: { + minify: true, + sourceMap: true, + keepNames: true, + format: OutputFormat.CJS, + mainFields: ['main'], + sourcesContent: true, + externalModules: [], // we bundle all the dependencies + }, + }); + getByIdFn.bindTable({ table: itemsTable }); + + /** + * In this example, we use the Powertools layer to include Powertools + * but we also bundle the function as CommonJS (CJS). + */ + const processItemsStreamFn = new FunctionWithLogGroup( + this, + 'process-items-stream-fn', + { + entry: './functions/process-items-stream.ts', + functionName: 'powertools-example-process-items-stream', + layers: [powertoolsLayer], + bundling: { + minify: true, + sourceMap: true, + keepNames: true, + format: OutputFormat.CJS, + mainFields: ['main'], + sourcesContent: true, + externalModules: ['@aws-sdk/*', '@aws-lambda-powertools/*'], // the dependencies are included in the layer + }, + } + ); + // Dead letter queue for the items that fail to be processed after the retry attempts + const dlq = new Queue(this, 'dead-letter-queue', { + queueName: 'powertools-example-dead-letter-queue', + removalPolicy: RemovalPolicy.DESTROY, + }); + // Add the DynamoDB event source to the function + processItemsStreamFn.addEventSource( + new DynamoEventSource(itemsTable, { + startingPosition: StartingPosition.LATEST, + reportBatchItemFailures: true, // Enable batch failure reporting + onFailure: new SqsDestination(dlq), + batchSize: 100, + retryAttempts: 3, + filters: [ + // Filter by the INSERT event type and the presence of the id and name attributes + FilterCriteria.filter({ + eventName: FilterRule.isEqual('INSERT'), + dynamodb: { + NewImage: { + id: { + S: FilterRule.exists(), + }, + name: { + S: FilterRule.exists(), + }, + }, + }, + }), + ], + }) + ); + + // Create an API Gateway to expose the items service + const api = new RestApi(this, 'items-api', { + restApiName: 'Items Service', + description: 'This service serves items.', + deployOptions: { + tracingEnabled: true, + }, + }); + + const itemPutIntegration = new LambdaIntegration(putItemFn); + api.root.addMethod('POST', itemPutIntegration); + + const itemsIntegration = new LambdaIntegration(getAllItemsFn); + api.root.addMethod('GET', itemsIntegration); + + const item = api.root.addResource('{id}'); + const itemIntegration = new LambdaIntegration(getByIdFn); + item.addMethod('GET', itemIntegration); + } +} diff --git a/examples/app/cdk/function-with-logstream-construct.ts b/examples/app/cdk/function-with-logstream-construct.ts new file mode 100644 index 0000000000..55e3c713c3 --- /dev/null +++ b/examples/app/cdk/function-with-logstream-construct.ts @@ -0,0 +1,103 @@ +import { Duration, RemovalPolicy } from 'aws-cdk-lib'; +import type { Table } from 'aws-cdk-lib/aws-dynamodb'; +import { Architecture, Runtime, Tracing } from 'aws-cdk-lib/aws-lambda'; +import { + NodejsFunction, + type NodejsFunctionProps, +} from 'aws-cdk-lib/aws-lambda-nodejs'; +import { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs'; +import type { Construct } from 'constructs'; + +/** + * The access mode for the table. + * + * Values are: read-only (RO) or read-write (RW) + */ +type AccessMode = 'RO' | 'RW'; + +type BindTableProps = { + /** + * The DynamoDB table to bind the function to + */ + table: Table; + /** + * The access mode for the table, defaults to 'RO' (read-only) + * @default 'RO' + */ + accessMode?: AccessMode; + /** + * Whether to grant the function access to the table only, + * @default false + */ + accessOnly?: boolean; + /** + * The name of the environment variable to use for the table name. + * @default `TABLE_NAME` + */ + envVarName?: string; +}; + +/** + * Custom construct that extends the `NodejsFunction` construct to include a log group + * as well as some default properties for the function and a helper method to bind the function to a DynamoDB table. + * + * The function is created with the following properties: + * - `handler` set to `handler` + * - `runtime` set to `Runtime.NODEJS_20_X` + * - `tracing` set to `Tracing.ACTIVE` + * - `architecture` set to `Architecture.ARM_64` + * - `timeout` set to `Duration.seconds(30)` + * - `environment` set to `{ NODE_OPTIONS: '--enable-source-maps' }` + * - `logGroup` set to a new `LogGroup` with the log group name set to `/aws/lambda/${functionName}` + * + * By setting a custom log group, you can control the log retention policy and other log group settings + * without having to deploy custom resources. + */ +export class FunctionWithLogGroup extends NodejsFunction { + public constructor(scope: Construct, id: string, props: NodejsFunctionProps) { + const { functionName } = props; + + super(scope, id, { + ...props, + handler: 'handler', + runtime: Runtime.NODEJS_20_X, + tracing: Tracing.ACTIVE, + architecture: Architecture.ARM_64, + timeout: Duration.seconds(30), + environment: { + NODE_OPTIONS: '--enable-source-maps', // see https://docs.aws.amazon.com/lambda/latest/dg/typescript-exceptions.html + }, + logGroup: new LogGroup(scope, `${id}-LogGroup`, { + logGroupName: `/aws/lambda/${functionName}`, + removalPolicy: RemovalPolicy.DESTROY, + retention: RetentionDays.ONE_DAY, + }), + }); + } + + /** + * Binds the function to a DynamoDB table by adding the table name to the environment + * under the key `TABLE_NAME` and granting the function read-only or read-write access + * based on the access mode provided. + * + * @param table The DynamoDB table to bind the function to + * @param accessMode The access mode for the table, defaults to 'RO' (read-only) + * @param envVarName The name of the environment variable to use for the table name, defaults to `TABLE_NAME` + */ + public bindTable({ + table, + accessMode, + accessOnly, + envVarName, + }: BindTableProps): void { + if (accessOnly !== true) { + this.addEnvironment(envVarName ?? 'TABLE_NAME', table.tableName); + } + if (accessMode === 'RW') { + table.grantReadWriteData(this); + + return; + } + table.grantReadData(this); + } +} diff --git a/examples/app/functions/commons/clients/dynamodb.ts b/examples/app/functions/commons/clients/dynamodb.ts new file mode 100644 index 0000000000..dea7eff6bf --- /dev/null +++ b/examples/app/functions/commons/clients/dynamodb.ts @@ -0,0 +1,15 @@ +import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb'; +import { tracer } from '#powertools'; + +// Create DynamoDB Client and patch it for tracing +const ddbClient = tracer.captureAWSv3Client(new DynamoDBClient({})); + +// Create the DynamoDB Document client. +const docClient = DynamoDBDocumentClient.from(ddbClient, { + marshallOptions: { + removeUndefinedValues: true, + }, +}); + +export { ddbClient, docClient }; diff --git a/examples/app/functions/commons/constants.ts b/examples/app/functions/commons/constants.ts new file mode 100644 index 0000000000..88d8313f5d --- /dev/null +++ b/examples/app/functions/commons/constants.ts @@ -0,0 +1,6 @@ +import { getStringFromEnv } from '#helpers/utils'; + +// Get the DynamoDB table name from environment variables +const itemsTableName = getStringFromEnv('TABLE_NAME'); + +export { itemsTableName }; diff --git a/examples/app/functions/commons/errors.ts b/examples/app/functions/commons/errors.ts new file mode 100644 index 0000000000..9ab7164572 --- /dev/null +++ b/examples/app/functions/commons/errors.ts @@ -0,0 +1,8 @@ +class ItemNotFound extends Error { + public constructor(message: string) { + super(message); + this.name = 'ItemNotFound'; + } +} + +export { ItemNotFound }; diff --git a/examples/app/functions/commons/helpers/get-item.ts b/examples/app/functions/commons/helpers/get-item.ts new file mode 100644 index 0000000000..3ce1030ea7 --- /dev/null +++ b/examples/app/functions/commons/helpers/get-item.ts @@ -0,0 +1,32 @@ +import { docClient } from '#clients/dynamodb'; +import { itemsTableName } from '#constants'; +import type { DebugLogger } from '#types'; +import { GetCommand, type GetCommandOutput } from '@aws-sdk/lib-dynamodb'; + +/** + * Fetch an item from the DynamoDB table. + * + * @param id The ID of the item to fetch from the DynamoDB table + * @param logger A logger instance + */ +const getItemDynamoDB = async ( + id: string, + logger: DebugLogger +): Promise => { + const response = await docClient.send( + new GetCommand({ + TableName: itemsTableName, + Key: { + id, + }, + }) + ); + + logger.debug(`ddb response`, { + response, + }); + + return response.Item; +}; + +export { getItemDynamoDB }; diff --git a/examples/app/functions/commons/helpers/get-string-param.ts b/examples/app/functions/commons/helpers/get-string-param.ts new file mode 100644 index 0000000000..56812470a0 --- /dev/null +++ b/examples/app/functions/commons/helpers/get-string-param.ts @@ -0,0 +1,20 @@ +import { getParameter } from '@aws-lambda-powertools/parameters/ssm'; + +/** + * Fetch a string from an SSM parameter. + * + * Throws an error if the parameter is not found. + * + * @param name The name of the parameter + */ +const getSSMStringParameter = async (name: string): Promise => { + const parameter = await getParameter(name, { maxAge: 600 }); + + if (!parameter) { + throw new Error(`Parameter ${name} not found`); + } + + return parameter; +}; + +export { getSSMStringParameter }; diff --git a/examples/app/functions/commons/helpers/put-item.ts b/examples/app/functions/commons/helpers/put-item.ts new file mode 100644 index 0000000000..a978e0382c --- /dev/null +++ b/examples/app/functions/commons/helpers/put-item.ts @@ -0,0 +1,38 @@ +import { docClient } from '#clients/dynamodb'; +import { itemsTableName } from '#constants'; +import type { DebugLogger } from '#types'; +import { PutCommand } from '@aws-sdk/lib-dynamodb'; +import { randomUUID } from 'node:crypto'; + +/** + * Put an item in the DynamoDB table. + * + * When the item is put in the table, the item's ID and name are returned. + * + * @param name The name of the item to put in the DynamoDB table + * @param logger A logger instance + */ +const putItemInDynamoDB = async ( + name: string, + logger: DebugLogger +): Promise<{ id: string; name: string }> => { + const item = { + id: randomUUID(), + name, + }; + + const response = await docClient.send( + new PutCommand({ + TableName: itemsTableName, + Item: item, + }) + ); + + logger.debug(`ddb response`, { + response, + }); + + return item; +}; + +export { putItemInDynamoDB }; diff --git a/examples/app/functions/commons/helpers/scan-items.ts b/examples/app/functions/commons/helpers/scan-items.ts new file mode 100644 index 0000000000..858b752c1d --- /dev/null +++ b/examples/app/functions/commons/helpers/scan-items.ts @@ -0,0 +1,29 @@ +import { docClient } from '#clients/dynamodb'; +import { itemsTableName } from '#constants'; +import type { DebugLogger } from '#types'; +import { ScanCommand, type ScanCommandOutput } from '@aws-sdk/lib-dynamodb'; + +/** + * Scan the DynamoDB table and return all items. + * + * @note this function is purposefully not paginated to keep the example simple + * + * @param logger A logger instance + */ +const scanItemsDynamoDB = async ( + logger: DebugLogger +): Promise => { + const response = await docClient.send( + new ScanCommand({ + TableName: itemsTableName, + }) + ); + + logger.debug(`ddb response`, { + response, + }); + + return response.Items; +}; + +export { scanItemsDynamoDB }; diff --git a/examples/app/functions/commons/helpers/utils.ts b/examples/app/functions/commons/helpers/utils.ts new file mode 100644 index 0000000000..2ebc3d1e01 --- /dev/null +++ b/examples/app/functions/commons/helpers/utils.ts @@ -0,0 +1,35 @@ +/** + * Assert that the given value is an error. + * + * TypeScript treats errors as `unknown` when caught, + * so we need to assert that the value is an error. + * + * We need this because in JavaScript, any value can be thrown + * using the `throw` keyword i.e. `throw 1` is valid. + * + * @param error The value to assert is an error + */ +// eslint-disable-next-line func-style -- type assertions cannot use arrow functions microsoft/TypeScript#34523 +function assertIsError(error: unknown): asserts error is Error { + if (!(error instanceof Error)) { + throw error; + } +} + +/** + * Fetch a string from an environment variable. + * + * Throws an error if the environment variable is not set. + * + * @param name The name of the environment variable + */ +const getStringFromEnv = (name: string): string => { + const value = process.env[name]; + if (!value) { + throw new Error(`${name} environment variable is not set`); + } + + return value; +}; + +export { assertIsError, getStringFromEnv }; diff --git a/examples/app/functions/commons/powertools/constants.ts b/examples/app/functions/commons/powertools/constants.ts new file mode 100644 index 0000000000..32d6cda6cb --- /dev/null +++ b/examples/app/functions/commons/powertools/constants.ts @@ -0,0 +1,20 @@ +/** + * Service name for the application to use in logs, metrics, and traces. + * + * Can also be configured via POWERTOOLS_SERVICE_NAME environment variable. + */ +const serviceName = 'items-store'; +/** + * Namespace for metrics to use in metrics. + * + * Can also be configured via POWERTOOLS_METRICS_NAMESPACE environment variable. + */ +const metricsNamespace = 'pwwertools-example'; +/** + * Key-value pairs to include in all metrics and logs. + */ +const defaultValues = { + executionEnv: process.env.AWS_EXECUTION_ENV || 'N/A', +}; + +export { serviceName, metricsNamespace, defaultValues }; diff --git a/examples/app/functions/commons/powertools/index.ts b/examples/app/functions/commons/powertools/index.ts new file mode 100644 index 0000000000..0ccf2b04bd --- /dev/null +++ b/examples/app/functions/commons/powertools/index.ts @@ -0,0 +1,10 @@ +/** + * We have single entry point for all the powertools modules so that functions that need only one + * can bundle only that one that they need and keep the bundle size small. + */ +import { logger } from './logger.js'; +import { metrics } from './metrics.js'; +import { tracer } from './tracer.js'; + +// We export all three modules for those functions who need to use all of them +export { logger, metrics, tracer }; diff --git a/examples/app/functions/commons/powertools/logger.ts b/examples/app/functions/commons/powertools/logger.ts new file mode 100644 index 0000000000..be5ac19db6 --- /dev/null +++ b/examples/app/functions/commons/powertools/logger.ts @@ -0,0 +1,22 @@ +import { Logger } from '@aws-lambda-powertools/logger'; +import { PT_VERSION as version } from '@aws-lambda-powertools/commons'; +import { serviceName, defaultValues } from './constants.js'; + +/** + * Create logger instance with centralized configuration so that + * all functions have consistent logging behavior. + */ +const logger = new Logger({ + logLevel: 'debug', + serviceName, + persistentLogAttributes: { + ...defaultValues, + region: process.env.AWS_REGION || 'N/A', + logger: { + name: '@aws-lambda-powertools/logger', + version, + }, + }, +}); + +export { logger }; diff --git a/examples/app/functions/commons/powertools/metrics.ts b/examples/app/functions/commons/powertools/metrics.ts new file mode 100644 index 0000000000..76196508a1 --- /dev/null +++ b/examples/app/functions/commons/powertools/metrics.ts @@ -0,0 +1,14 @@ +import { Metrics } from '@aws-lambda-powertools/metrics'; +import { serviceName, metricsNamespace, defaultValues } from './constants.js'; + +/** + * Create metrics instance with centralized configuration so that + * all functions have the same dimensions and namespace. + */ +const metrics = new Metrics({ + serviceName, + namespace: metricsNamespace, + defaultDimensions: defaultValues, +}); + +export { metrics }; diff --git a/examples/app/functions/commons/powertools/tracer.ts b/examples/app/functions/commons/powertools/tracer.ts new file mode 100644 index 0000000000..41569416ca --- /dev/null +++ b/examples/app/functions/commons/powertools/tracer.ts @@ -0,0 +1,12 @@ +import { Tracer } from '@aws-lambda-powertools/tracer'; +import { serviceName } from './constants.js'; + +/** + * Create tracer instance with centralized configuration so that + * all traces have the same service name as an annotation. + */ +const tracer = new Tracer({ + serviceName, +}); + +export { tracer }; diff --git a/examples/app/functions/commons/types.ts b/examples/app/functions/commons/types.ts new file mode 100644 index 0000000000..e6e20fe628 --- /dev/null +++ b/examples/app/functions/commons/types.ts @@ -0,0 +1,7 @@ +import type { LogItemExtraInput } from '@aws-lambda-powertools/logger/types'; + +type DebugLogger = { + debug: (message: string, ...extraInput: LogItemExtraInput) => void; +}; + +export type { DebugLogger }; diff --git a/examples/app/functions/get-all-items.ts b/examples/app/functions/get-all-items.ts new file mode 100644 index 0000000000..2e1d169ff3 --- /dev/null +++ b/examples/app/functions/get-all-items.ts @@ -0,0 +1,66 @@ +import { scanItemsDynamoDB } from '#helpers/scan-items'; +import { assertIsError } from '#helpers/utils'; +import { logger, metrics, tracer } from '#powertools'; +import { injectLambdaContext } from '@aws-lambda-powertools/logger/middleware'; +import { logMetrics } from '@aws-lambda-powertools/metrics/middleware'; +import { captureLambdaHandler } from '@aws-lambda-powertools/tracer/middleware'; +import middy from '@middy/core'; +import type { + APIGatewayProxyEvent, + APIGatewayProxyResult, + Context, +} from 'aws-lambda'; + +/* + * + * This example uses the Middy middleware instrumentation. + * It is the best choice if your existing code base relies on the Middy middleware engine. + * Powertools for AWS Lambda (TypeScript) offers compatible Middy middleware to make this integration seamless. + * + * Find more Information in the docs: https://docs.powertools.aws.dev/lambda/typescript/ + * + * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format + * @param {Object} event - API Gateway Lambda Proxy Input Format + * + * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html + * @returns {Object} object - API Gateway Lambda Proxy Output Format + * + */ +const functionHandler = async ( + event: APIGatewayProxyEvent, + context: Context +): Promise => { + if (event.httpMethod !== 'GET') { + throw new Error( + `getAllItems only accepts GET method, you tried: ${event.httpMethod}` + ); + } + + // Tracer: Add awsRequestId as annotation + tracer.putAnnotation('awsRequestId', context.awsRequestId); + + try { + const items = await scanItemsDynamoDB(logger); + + return { + statusCode: 200, + body: JSON.stringify({ message: 'success', items }), + }; + } catch (error) { + assertIsError(error); + + logger.error('error reading from table', error); + + return { + statusCode: 500, + body: JSON.stringify({ message: 'Error reading from table.' }), + }; + } +}; + +// Wrap the handler with middy and apply the middlewares +export const handler = middy(functionHandler) + .use(logMetrics(metrics)) + .use(injectLambdaContext(logger, { logEvent: true, clearState: true })) + // Since we are returning multiple items and the X-Ray segment limit is 64kb, we disable response capture to avoid data loss + .use(captureLambdaHandler(tracer, { captureResponse: false })); diff --git a/examples/app/functions/get-by-id.ts b/examples/app/functions/get-by-id.ts new file mode 100644 index 0000000000..3ee5fb9cd2 --- /dev/null +++ b/examples/app/functions/get-by-id.ts @@ -0,0 +1,103 @@ +import { ItemNotFound } from '#errors'; +import { getItemDynamoDB } from '#helpers/get-item'; +import { assertIsError } from '#helpers/utils'; +import { logger, metrics, tracer } from '#powertools'; +import type { LambdaInterface } from '@aws-lambda-powertools/commons/types'; +import type { + APIGatewayProxyEvent, + APIGatewayProxyResult, + Context, +} from 'aws-lambda'; + +/* + * + * This example uses the Method decorator instrumentation. + * + * Use TypeScript method decorators if you prefer writing your business logic using TypeScript Classes. + * If you aren’t using Classes, this requires the most significant refactoring. + * + * Find more Information in the docs: https://docs.powertools.aws.dev/lambda/typescript/ + * + * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format + * @param {APIGatewayProxyEvent} event - API Gateway Lambda Proxy Input Format + * + * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html + * @returns {Promise} object - API Gateway Lambda Proxy Output Format + * + */ +class Lambda implements LambdaInterface { + @tracer.captureMethod() + public async getItem(itemId: string): Promise<{ id: string; name: string }> { + const item = (await getItemDynamoDB(itemId, logger)) as { + id: string; + name: string; + }; + + if (!item) { + throw new ItemNotFound('item not found'); + } + + return item; + } + + @tracer.captureLambdaHandler() + @logger.injectLambdaContext({ logEvent: true, clearState: true }) + @metrics.logMetrics({ + throwOnEmptyMetrics: false, + }) + public async handler( + event: APIGatewayProxyEvent, + context: Context + ): Promise { + if (event.httpMethod !== 'GET') { + throw new Error( + `getById only accepts GET method, you tried: ${event.httpMethod}` + ); + } + if (!event.pathParameters) { + throw new Error('event does not contain pathParameters'); + } + if (!event.pathParameters.id) { + throw new Error('PathParameter id is missing'); + } + const { id: itemId } = event.pathParameters; + + // Tracer: Add awsRequestId as annotation + tracer.putAnnotation('awsRequestId', context.awsRequestId); + + // Logger: Append itemId to each log statement + logger.appendKeys({ itemId }); + + // Tracer: Add itemId as annotation, so you can search traces by itemId + tracer.putAnnotation('itemId', itemId); + + try { + const item = await this.getItem(itemId); + + return { + statusCode: 200, + body: JSON.stringify({ message: 'success', item }), + }; + } catch (err) { + assertIsError(err); + + let statusCode = 500; + let message = 'unable to get item from table'; + + if (err instanceof ItemNotFound) { + statusCode = 404; + message = err.message; + } + + logger.error('error reading from table', { err }); + + return { + statusCode, + body: JSON.stringify({ message, itemId }), + }; + } + } +} + +const handlerClass = new Lambda(); +export const handler = handlerClass.handler.bind(handlerClass); diff --git a/examples/app/functions/process-items-stream.ts b/examples/app/functions/process-items-stream.ts new file mode 100644 index 0000000000..83b89b419e --- /dev/null +++ b/examples/app/functions/process-items-stream.ts @@ -0,0 +1,98 @@ +import { assertIsError } from '#helpers/utils'; +import { logger } from '#powertools/logger'; +import { tracer } from '#powertools/tracer'; +import { + BatchProcessor, + EventType, + processPartialResponse, +} from '@aws-lambda-powertools/batch'; +import type { AttributeValue } from '@aws-sdk/client-dynamodb'; +import { unmarshall } from '@aws-sdk/util-dynamodb'; +import type { + Context, + DynamoDBBatchResponse, + DynamoDBRecord, + DynamoDBStreamEvent, +} from 'aws-lambda'; +import { equal } from 'node:assert/strict'; + +const processor = new BatchProcessor(EventType.DynamoDBStreams); + +/** + * This function processes a single DynamoDB record from a DynamoDB stream. + * + * If the function returns normally, the item is considered to be successfully processed. + * If it throws an error, the item is considered to have failed processing and will be marked for retry. + * + * @param record The DynamoDB record to process + */ +const recordHandler = async (record: DynamoDBRecord): Promise => { + logger.debug('processing record', { record }); + const subsegment = tracer + .getSegment() + ?.addNewSubsegment('#### recordHandler'); + try { + if (record.dynamodb?.NewImage) { + const payload = unmarshall( + record.dynamodb.NewImage as { [key: string]: AttributeValue } + ); + + // Add itemId to the logger so that it's included in every log message + logger.appendKeys({ itemId: payload.id }); + // Also add it to the subsegment, so you can search for traces by itemId + subsegment?.addAnnotation('itemId', payload.id); + + const query = new URLSearchParams(); + query.set('name', payload.name); + + const remoteUrl = `https://httpbin.org/anything?${query.toString()}`; + logger.debug('sending request', { remoteUrl }); + + // This request doesn't show up in the trace yet, see #1619 for updates + const response = await fetch(remoteUrl); + // If the request was successful, the response.ok property will be true + equal(response.ok, true); + + logger.debug('request completed', { + response: await response.json(), + status: response.status, + }); + } + } catch (error) { + assertIsError(error); + logger.error('error processing record', error); + subsegment?.addError(error, true); + + throw error; + } finally { + logger.removeKeys(['itemId']); + subsegment?.close(); + } +}; + +/** + * This function shows how to process a batch of DynamoDB records from a DynamoDB stream. + * + * The functions uses the `processPartialResponse` function from the `@aws-lambda-powertools/batch` package + * to process the records in parallel using the `recordHandler` function. + * + * The `processPartialResponse` is wrapped in a `tracer.provider.captureAsyncFunc` to ensure that the whole + * process is captured in a single trace. + */ +export const handler = async ( + event: DynamoDBStreamEvent, + context: Context +): Promise => { + return tracer.provider.captureAsyncFunc('### handler', async (segment) => { + const result = await processPartialResponse( + event, + recordHandler, + processor, + { context } + ); + + segment?.close(); + + return result; + }) as DynamoDBBatchResponse; +}; diff --git a/examples/app/functions/put-item.ts b/examples/app/functions/put-item.ts new file mode 100644 index 0000000000..aed36c92c4 --- /dev/null +++ b/examples/app/functions/put-item.ts @@ -0,0 +1,129 @@ +import { ddbClient } from '#clients/dynamodb'; +import { getSSMStringParameter } from '#helpers/get-string-param'; +import { putItemInDynamoDB } from '#helpers/put-item'; +import { assertIsError, getStringFromEnv } from '#helpers/utils'; +import { logger, metrics, tracer } from '#powertools'; +import { + IdempotencyConfig, + makeIdempotent, +} from '@aws-lambda-powertools/idempotency'; +import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb'; +import { MetricUnit } from '@aws-lambda-powertools/metrics'; +import type { + APIGatewayProxyEvent, + APIGatewayProxyResult, + Context, +} from 'aws-lambda'; +import type { Subsegment } from 'aws-xray-sdk-core'; + +// Initialize the persistence store for idempotency +const ssmParameterName = getStringFromEnv('SSM_PARAMETER_NAME'); +const persistenceStore = new DynamoDBPersistenceLayer({ + tableName: await getSSMStringParameter(ssmParameterName), + // Pass DynamoDB client, so we can trace the calls + awsSdkV3Client: ddbClient, +}); +// Define the idempotency configuration +const idempotencyConfig = new IdempotencyConfig({ + expiresAfterSeconds: 60 * 60 * 24, +}); + +/** + * Make the `putItemInDynamoDB` function idempotent by wrapping it with the `makeIdempotent` function. + * + * The function will now return the same result for the same input, even if the function is called multiple times. + * + * In this case, it won't write a new item to the DynamoDB table if the name was already processed. + */ +const idempotentPutItem = makeIdempotent(putItemInDynamoDB, { + persistenceStore, + config: idempotencyConfig, +}); + +/** + * + * This example uses the manual instrumentation. + * + * This instrumentation, although more verbose, is the best choice if you want to have full control over how the tracing, logging, and metrics are added to your code + * or if you don't want to use decorators (see `get-by-id.ts`) or Middy.js middlewares (`get-all-items.ts`). + * + * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format + * @param {APIGatewayProxyEvent} event - API Gateway Lambda Proxy Input Format + * + * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html + * @returns {Promise} object - API Gateway Lambda Proxy Output Format + * + */ +export const handler = async ( + event: APIGatewayProxyEvent, + context: Context +): Promise => { + // Logger: Log the incoming event + logger.debug('event', { event }); + + if (event.httpMethod !== 'POST') { + throw new Error( + `putItem only accepts POST method, you tried: ${event.httpMethod}` + ); + } + if (!event.body) { + throw new Error('Event does not contain body'); + } + + // Register the Lambda context with the idempotency configuration so that it can handle function timeouts + idempotencyConfig.registerLambdaContext(context); + // And do the same with the Logger so that all logs have contextual information + logger.addContext(context); + + // Get facade segment created by AWS Lambda, create subsegment for the function, and set it as active + const segment = tracer.getSegment(); + let handlerSegment: Subsegment | undefined; + if (segment) { + handlerSegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`); + tracer.setSegment(handlerSegment); + } + + // Annotate the subsegment with the cold start & serviceName + tracer.annotateColdStart(); + tracer.addServiceNameAnnotation(); + + // Capture cold start metric + metrics.captureColdStartMetric(); + + try { + // Get the name from the body of the request + const body = JSON.parse(event.body); + const { name } = body; + + const item = await idempotentPutItem(name, logger); + + // Create a custom metric to count the number of items added + metrics.addMetric('itemsAdded', MetricUnit.Count, 1); + + return { + statusCode: 200, + body: JSON.stringify({ message: 'success', item }), + }; + } catch (err) { + assertIsError(err); + + // Add the error to the subsegment so it shows up in the trace + tracer.addErrorAsMetadata(err); + // Create a custom metric to count the number of errors + metrics.addMetric('itemsInsertErrors', MetricUnit.Count, 1); + + logger.error('error storing item', err); + + return { + statusCode: 500, + body: JSON.stringify({ message: 'error writing data to table' }), + }; + } finally { + if (segment && handlerSegment) { + // Tracer: Close subsegment (the AWS Lambda one is closed automatically) + handlerSegment.close(); // (## index.handler) + // Tracer: Set the facade segment as active again (the one created by AWS Lambda) + tracer.setSegment(segment); + } + } +}; diff --git a/examples/app/jest.config.cjs b/examples/app/jest.config.cjs new file mode 100644 index 0000000000..4bf6bb1d25 --- /dev/null +++ b/examples/app/jest.config.cjs @@ -0,0 +1,14 @@ +module.exports = { + preset: 'ts-jest', + moduleNameMapper: { + '^(\\.{1,2}/.*)\\.js$': '$1', + }, + transform: { + '^.+\\.ts?$': 'ts-jest', + }, + moduleFileExtensions: ['js', 'ts'], + testMatch: ['**/?(*.)+(spec|test).ts'], + testPathIgnorePatterns: ['/node_modules/'], + testEnvironment: 'node', + roots: ['/tests'], +}; diff --git a/examples/cdk/package.json b/examples/app/package.json similarity index 61% rename from examples/cdk/package.json rename to examples/app/package.json index 45589aa412..3d677d663d 100644 --- a/examples/cdk/package.json +++ b/examples/app/package.json @@ -1,5 +1,5 @@ { - "name": "cdk-sample", + "name": "powertools-sample-app", "version": "2.0.3", "author": { "name": "Amazon Web Services", @@ -8,15 +8,12 @@ "private": true, "description": "This project contains source code and supporting files for a serverless application that you can deploy with CDK.", "license": "MIT-0", - "bin": { - "cdk-app": "bin/cdk-app.js" - }, "scripts": { "build": "echo 'Not applicable, run `npx cdk synth` instead to build the stack'", "test": "npm run test:unit", "lint": "eslint --ext .ts,.js --no-error-on-unmatched-pattern .", "lint-fix": "eslint --fix --ext .ts,.js --fix --no-error-on-unmatched-pattern .", - "test:unit": "export POWERTOOLS_DEV=true && npm run build && jest --silent", + "test:unit": "export POWERTOOLS_DEV=true && jest --silent", "test:e2e": "echo 'To be implemented ...'", "cdk": "cdk" }, @@ -24,14 +21,39 @@ "*.ts": "npm run lint-fix", "*.js": "npm run lint-fix" }, + "type": "module", + "imports": { + "#types": "./functions/commons/types.js", + "#constants": "./functions/commons/constants.js", + "#powertools": "./functions/commons/powertools/index.js", + "#powertools/*": "./functions/commons/powertools/*.js", + "#clients/*": "./functions/commons/clients/*.js", + "#helpers/*": "./functions/commons/helpers/*.js", + "#errors": "./functions/commons/errors.js" + }, "devDependencies": { - "@aws-lambda-powertools/commons": "^2.0.3", + "@types/aws-lambda": "^8.10.136", + "@types/jest": "^29.5.12", + "@types/node": "20.11.28", + "aws-cdk": "^2.133.0", + "aws-cdk-lib": "^2.133.0", + "constructs": "^10.3.0", + "jest": "^29.7.0", + "source-map-support": "^0.5.21", + "ts-jest": "^29.1.2", + "tsx": "^4.7.1", + "typescript": "^5.4.2" + }, + "dependencies": { + "@aws-lambda-powertools/batch": "^2.0.3", + "@aws-lambda-powertools/idempotency": "^2.0.3", "@aws-lambda-powertools/logger": "^2.0.3", "@aws-lambda-powertools/metrics": "^2.0.3", "@aws-lambda-powertools/parameters": "^2.0.3", "@aws-lambda-powertools/tracer": "^2.0.3", "@aws-sdk/client-ssm": "^3.535.0", "@aws-sdk/lib-dynamodb": "^3.538.0", + "@middy/core": "^4.7.0", "@types/aws-lambda": "^8.10.136", "@types/jest": "^29.5.12", "@types/node": "20.11.30", @@ -42,12 +64,5 @@ "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "typescript": "^5.4.3" - }, - "dependencies": { - "@middy/core": "^4.7.0", - "aws-cdk-lib": "^2.133.0", - "construct": "^1.0.0", - "phin": "^3.7.0", - "source-map-support": "^0.5.21" } } diff --git a/examples/app/template.yaml b/examples/app/template.yaml new file mode 100644 index 0000000000..bac8f32b7c --- /dev/null +++ b/examples/app/template.yaml @@ -0,0 +1,264 @@ +AWSTemplateFormatVersion: 2010-09-09 +Description: >- + An example application with Powertools for AWS Lambda (TypeScript). + +# Transform section specifies one or more macros that AWS CloudFormation uses to process your template +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-section-structure.html +Transform: AWS::Serverless-2016-10-31 + +# Global configuration that all Functions inherit +# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-template-anatomy-globals.html +Globals: + Function: + Runtime: nodejs20.x + Tracing: Active + Architectures: + - arm64 + MemorySize: 128 + Timeout: 30 + Environment: + Variables: + NODE_OPTIONS: '--enable-source-maps' # see https://docs.aws.amazon.com/lambda/latest/dg/typescript-exceptions.html + +# Resources declares the AWS resources that you want to include in the stack +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html +Resources: + # Items table + itemsTable: + Type: AWS::DynamoDB::Table + Properties: + TableName: powertools-example-items + TableClass: STANDARD_INFREQUENT_ACCESS + BillingMode: PAY_PER_REQUEST + AttributeDefinitions: + - AttributeName: id + AttributeType: S + KeySchema: + - AttributeName: id + KeyType: HASH + StreamSpecification: + StreamViewType: NEW_IMAGE # we use the stream to trigger the processItemsStreamFn function + + # Idempotency table + idempotencyTable: + Type: AWS::DynamoDB::Table + Properties: + TableName: powertools-example-idempotency + BillingMode: PAY_PER_REQUEST + AttributeDefinitions: + - AttributeName: id + AttributeType: S + KeySchema: + - AttributeName: id + KeyType: HASH + TimeToLiveSpecification: + AttributeName: expiration + Enabled: true + + # We store the idempotency table name in a SSM Parameter to simulate a potential cross-stack reference. + # This is not strictly necessary in this example, but it's a good way of showing how to use SSM parameters + # and retrieve them using Powertools. + idempotencyTableNameParam: + Type: AWS::SSM::Parameter + Properties: + Name: /items-store/idempotency-table-name + Type: String + Value: !Ref idempotencyTable + + # In this example, we use ESM and bundle all the dependencies including the AWS SDK. + # Because we are using ESM and tree shaking, we create an optimized bundle. + putItemFunction: + Type: AWS::Serverless::Function + Properties: + FunctionName: powertools-example-put-item + Handler: functions/put-item.handler + Policies: + # Allow the function to read/write to the items table + - DynamoDBCrudPolicy: + TableName: !Ref itemsTable + # Allow the function to read/write to the idempotency table + - DynamoDBCrudPolicy: + TableName: !Ref idempotencyTable + # Allow the function to read the idempotency table name from SSM + - SSMParameterWithSlashPrefixReadPolicy: + ParameterName: !Ref idempotencyTableNameParam + Environment: + Variables: + TABLE_NAME: !Ref itemsTable + SSM_PARAMETER_NAME: !Ref idempotencyTableNameParam + Events: + Api: + Type: Api + Properties: + Path: / + Method: POST + LoggingConfig: + LogGroupName: !Sub "/aws/lambda/${putItemFunction}" + Metadata: + BuildMethod: esbuild + BuildProperties: + Minify: true + Target: ES2022 + Sourcemap: true + KeepNames: true + Format: esm + SourcesContent: true + MainFields: module,main + TreeShaking: true + Banner: + - js=import { createRequire } from "module";const require = createRequire(import.meta.url); + EntryPoints: + - functions/put-item.ts + + # Log group for the putItemFunction with a retention of 1 day + putItemLogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Sub "/aws/lambda/${putItemFunction}" + RetentionInDays: 1 + + # In this example, we use ESM with the Powertools Lambda Layer which includes Powertools + # as well as the AWS SDK, this is a convenient way to use Powertools in a centralized way across + # multiple functions. + getAllItemsFunction: + Type: AWS::Serverless::Function + Properties: + Handler: functions/get-all-items.handler + Description: A simple example includes a HTTP get method to get all items from a DynamoDB table. + Policies: + # Allow the function to read to the items table + - DynamoDBReadPolicy: + TableName: !Ref itemsTable + Layers: + - !Sub arn:aws:lambda:${AWS::Region}:094274105915:layer:AWSLambdaPowertoolsTypeScriptV2:3 + Environment: + Variables: + TABLE_NAME: !Ref itemsTable + Events: + Api: + Type: Api + Properties: + Path: / + Method: GET + LoggingConfig: + LogGroupName: !Sub "/aws/lambda/${getAllItemsFunction}" + Metadata: + BuildMethod: esbuild + BuildProperties: + Minify: true + Target: ES2022 + Sourcemap: true + KeepNames: true + Format: esm + SourcesContent: true + MainFields: module,main + TreeShaking: true + External: + - '@aws-sdk/*' + - '@aws-lambda-powertools/*' + Banner: + - js=import { createRequire } from "module";const require = createRequire(import.meta.url); + EntryPoints: + - functions/get-all-items.ts + + # Log group for the getAllItemsFunction with a retention of 1 day + getAllItemsLogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Sub "/aws/lambda/${getAllItemsFunction}" + RetentionInDays: 1 + + # In this example we emit a CommonJS (CJS) bundle and include all the dependencies in it. + getByIdFunction: + Type: AWS::Serverless::Function + Properties: + FunctionName: powertools-example-get-by-id + Handler: functions/get-by-id.handler + Policies: + # Allow the function to read to the items table + - DynamoDBReadPolicy: + TableName: !Ref itemsTable + Environment: + Variables: + TABLE_NAME: !Ref itemsTable + Events: + Api: + Type: Api + Properties: + Path: /{id} + Method: GET + LoggingConfig: + LogGroupName: !Sub "/aws/lambda/${getByIdFunction}" + Metadata: + BuildMethod: esbuild + BuildProperties: + Minify: true + Target: ES2022 + Sourcemap: true + KeepNames: true + Format: cjs + SourcesContent: true + MainFields: main + EntryPoints: + - functions/get-by-id.ts + + # Log group for the getByIdFunction with a retention of 1 day + getByIdLogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Sub "/aws/lambda/${getByIdFunction}" + RetentionInDays: 1 + + # In this example we use the AWS Lambda Powertools Layer to include Powertools but + # we use the CommonJS (CJS) format for the function code. + processItemsStreamFunction: + Type: AWS::Serverless::Function + Properties: + FunctionName: powertools-example-get-by-id + Handler: functions/process-items-stream.handler + Events: + Stream: + Type: DynamoDB + Properties: + Stream: !GetAtt itemsTable.StreamArn + StartingPosition: LATEST + BatchSize: 100 + FilterCriteria: + Filters: + - Pattern: "{\"eventName\":[\"INSERT\"],\"dynamodb\":{\"NewImage\":{\"id\":{\"S\":[{\"exists\":true}]},\"name\":{\"S\":[{\"exists\":true}]}}}}" + MaximumRetryAttempts: 3 + FunctionResponseTypes: + - ReportBatchItemFailures + DestinationConfig: + OnFailure: + Destination: !GetAtt processItemsDeadLetterQueue.Arn + LoggingConfig: + LogGroupName: !Sub "/aws/lambda/${processItemsStreamFunction}" + Metadata: + BuildMethod: esbuild + BuildProperties: + Minify: true + Target: ES2022 + Sourcemap: true + KeepNames: true + Format: cjs + SourcesContent: true + MainFields: main + External: + - '@aws-sdk/*' + - '@aws-lambda-powertools/*' + EntryPoints: + - functions/process-items-stream.ts + + # Log group for the getByIdFunction with a retention of 1 day + processItemsStreamLogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Sub "/aws/lambda/${processItemsStreamFunction}" + RetentionInDays: 1 + + # Dead letter queue for items that failed to be processed by the processItemsStreamFunction + processItemsDeadLetterQueue: + Type: AWS::SQS::Queue + Properties: + QueueName: powertools-example-dead-letter-queue \ No newline at end of file diff --git a/examples/app/tests/cdk-app.test.ts b/examples/app/tests/cdk-app.test.ts new file mode 100644 index 0000000000..f7ebfd9404 --- /dev/null +++ b/examples/app/tests/cdk-app.test.ts @@ -0,0 +1,9 @@ +import { App } from 'aws-cdk-lib'; +import { Template } from 'aws-cdk-lib/assertions'; +import { PowertoolsExampleStack } from '../cdk/example-stack.js'; + +test('CDK code synthesize', () => { + const app = new App(); + const stack = new PowertoolsExampleStack(app, 'MyTestStack'); + Template.fromStack(stack).resourceCountIs('AWS::Lambda::Function', 4); +}); diff --git a/examples/cdk/tests/tsconfig.json b/examples/app/tests/tsconfig.json similarity index 100% rename from examples/cdk/tests/tsconfig.json rename to examples/app/tests/tsconfig.json diff --git a/examples/app/tsconfig.json b/examples/app/tsconfig.json new file mode 100644 index 0000000000..635d0bf5a5 --- /dev/null +++ b/examples/app/tsconfig.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "esModuleInterop": true, + "skipLibCheck": true, + "target": "es2022", + "moduleDetection": "force", + "isolatedModules": true, + "strict": true, + "noUncheckedIndexedAccess": true, + "moduleResolution": "NodeNext", + "module": "NodeNext", + "outDir": "lib/esm", + "sourceMap": true, + "experimentalDecorators": true, + "declaration": true, + "declarationMap": true, + "removeComments": false, + "pretty": true, + "lib": [ + "ES2022" + ], + "rootDir": "." + }, + "include": [ + "functions/**/*", + "cdk/**/*", + "test/**/*", + "bin/**/*", + ], + "exclude": [ + "./node_modules" + ], + "watchOptions": { + "watchFile": "useFsEvents", + "watchDirectory": "useFsEvents", + "fallbackPolling": "dynamicPriority" + }, + "types": [ + "node" + ] +} \ No newline at end of file diff --git a/examples/cdk/bin/cdk-app.ts b/examples/cdk/bin/cdk-app.ts deleted file mode 100644 index 7142e425b9..0000000000 --- a/examples/cdk/bin/cdk-app.ts +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env node -import 'source-map-support/register'; -import * as cdk from 'aws-cdk-lib'; -import { CdkAppStack } from '../src/example-stack'; - -const app = new cdk.App(); -new CdkAppStack(app, 'LambdaPowertoolsTypeScript-ExamplesCdkStack', {}); diff --git a/examples/cdk/functions/common/constants.ts b/examples/cdk/functions/common/constants.ts deleted file mode 100644 index 3496c50046..0000000000 --- a/examples/cdk/functions/common/constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Get the DynamoDB table name from environment variables -const tableName = process.env.SAMPLE_TABLE; - -export { tableName }; diff --git a/examples/cdk/functions/common/dynamodb-client.ts b/examples/cdk/functions/common/dynamodb-client.ts deleted file mode 100644 index 40a7c994b8..0000000000 --- a/examples/cdk/functions/common/dynamodb-client.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; -import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb'; -import { tracer } from './powertools'; - -// Create DynamoDB Client and patch it for tracing -const ddbClient = tracer.captureAWSv3Client(new DynamoDBClient({})); - -const marshallOptions = { - // Whether to automatically convert empty strings, blobs, and sets to `null`. - convertEmptyValues: false, // false, by default. - // Whether to remove undefined values while marshalling. - removeUndefinedValues: false, // false, by default. - // Whether to convert typeof object to map attribute. - convertClassInstanceToMap: false, // false, by default. -}; - -const unmarshallOptions = { - // Whether to return numbers as a string instead of converting them to native JavaScript numbers. - wrapNumbers: false, // false, by default. -}; - -const translateConfig = { marshallOptions, unmarshallOptions }; - -// Create the DynamoDB Document client. -const docClient = DynamoDBDocumentClient.from(ddbClient, translateConfig); - -export { docClient }; diff --git a/examples/cdk/functions/common/getUuid.ts b/examples/cdk/functions/common/getUuid.ts deleted file mode 100644 index eb567314bd..0000000000 --- a/examples/cdk/functions/common/getUuid.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { logger } from './powertools'; -import { getParameter } from '@aws-lambda-powertools/parameters/ssm'; -import { randomUUID } from 'node:crypto'; -import { default as request } from 'phin'; - -export const getUuid = async (): Promise => { - const uuidApiUrl = await getParameter('/app/uuid-api-url'); - if (!uuidApiUrl) { - // create uuid locally - logger.warn('No uuid-api-url parameter found, creating uuid locally'); - - return randomUUID(); - } else { - // Request a sample random uuid from a webservice - const res = await request<{ uuid: string }>({ - url: uuidApiUrl, - parse: 'json', - }); - - return res.body.uuid; - } -}; diff --git a/examples/cdk/functions/common/powertools.ts b/examples/cdk/functions/common/powertools.ts deleted file mode 100644 index fa0b798afa..0000000000 --- a/examples/cdk/functions/common/powertools.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Logger } from '@aws-lambda-powertools/logger'; -import { Metrics } from '@aws-lambda-powertools/metrics'; -import { Tracer } from '@aws-lambda-powertools/tracer'; -import { PT_VERSION } from '@aws-lambda-powertools/commons'; - -const defaultValues = { - region: process.env.AWS_REGION || 'N/A', - executionEnv: process.env.AWS_EXECUTION_ENV || 'N/A', -}; - -const logger = new Logger({ - persistentLogAttributes: { - ...defaultValues, - logger: { - name: '@aws-lambda-powertools/logger', - version: PT_VERSION, - }, - }, -}); - -const metrics = new Metrics({ - defaultDimensions: defaultValues, -}); - -const tracer = new Tracer(); - -export { logger, metrics, tracer }; diff --git a/examples/cdk/functions/get-all-items.ts b/examples/cdk/functions/get-all-items.ts deleted file mode 100644 index 878bdbb37a..0000000000 --- a/examples/cdk/functions/get-all-items.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { injectLambdaContext } from '@aws-lambda-powertools/logger/middleware'; -import { logMetrics } from '@aws-lambda-powertools/metrics/middleware'; -import { captureLambdaHandler } from '@aws-lambda-powertools/tracer/middleware'; -import { ScanCommand } from '@aws-sdk/lib-dynamodb'; -import middy from '@middy/core'; -import { - APIGatewayProxyEvent, - APIGatewayProxyResult, - Context, -} from 'aws-lambda'; -import { tableName } from './common/constants'; -import { docClient } from './common/dynamodb-client'; -import { getUuid } from './common/getUuid'; -import { logger, metrics, tracer } from './common/powertools'; - -/* - * - * This example uses the Middy middleware instrumentation. - * It is the best choice if your existing code base relies on the Middy middleware engine. - * Powertools for AWS Lambda (TypeScript) offers compatible Middy middleware to make this integration seamless. - * Find more Information in the docs: https://docs.powertools.aws.dev/lambda/typescript/ - * - * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format - * @param {Object} event - API Gateway Lambda Proxy Input Format - * - * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html - * @returns {Object} object - API Gateway Lambda Proxy Output Format - * - */ -const getAllItemsHandler = async ( - event: APIGatewayProxyEvent, - context: Context -): Promise => { - if (event.httpMethod !== 'GET') { - throw new Error( - `getAllItems only accepts GET method, you tried: ${event.httpMethod}` - ); - } - - // Tracer: Add awsRequestId as annotation - tracer.putAnnotation('awsRequestId', context.awsRequestId); - - // Logger: Append awsRequestId to each log statement - logger.appendKeys({ - awsRequestId: context.awsRequestId, - }); - - const uuid = await getUuid(); - - // Logger: Append uuid to each log statement - logger.appendKeys({ uuid }); - - // Tracer: Add uuid as annotation - tracer.putAnnotation('uuid', uuid); - - // Metrics: Add uuid as metadata - metrics.addMetadata('uuid', uuid); - - // get all items from the table (only first 1MB data, you can use `LastEvaluatedKey` to get the rest of data) - // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#scan-property - // https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html - try { - if (!tableName) { - throw new Error('SAMPLE_TABLE environment variable is not set'); - } - - const data = await docClient.send( - new ScanCommand({ - TableName: tableName, - }) - ); - const { Items: items } = data; - - // Logger: All log statements are written to CloudWatch - logger.debug(`retrieved items: ${items?.length || 0}`); - - logger.info(`Response ${event.path}`, { - statusCode: 200, - body: items, - }); - - return { - statusCode: 200, - body: JSON.stringify(items), - }; - } catch (err) { - tracer.addErrorAsMetadata(err as Error); - logger.error('Error reading from table. ' + err); - - return { - statusCode: 500, - body: JSON.stringify({ error: 'Error reading from table.' }), - }; - } -}; - -// Wrap the handler with middy -export const handler = middy(getAllItemsHandler) - // Use the middleware by passing the Metrics instance as a parameter - .use(logMetrics(metrics)) - // Use the middleware by passing the Logger instance as a parameter - .use(injectLambdaContext(logger, { logEvent: true })) - // Use the middleware by passing the Tracer instance as a parameter - .use(captureLambdaHandler(tracer, { captureResponse: false })); // by default the tracer would add the response as metadata on the segment, but there is a chance to hit the 64kb segment size limit. Therefore set captureResponse: false diff --git a/examples/cdk/functions/get-by-id.ts b/examples/cdk/functions/get-by-id.ts deleted file mode 100644 index f35c053eef..0000000000 --- a/examples/cdk/functions/get-by-id.ts +++ /dev/null @@ -1,114 +0,0 @@ -import type { LambdaInterface } from '@aws-lambda-powertools/commons/types'; -import { GetCommand } from '@aws-sdk/lib-dynamodb'; -import { - APIGatewayProxyEvent, - APIGatewayProxyResult, - Context, -} from 'aws-lambda'; -import { tableName } from './common/constants'; -import { docClient } from './common/dynamodb-client'; -import { getUuid } from './common/getUuid'; -import { logger, metrics, tracer } from './common/powertools'; - -/* - * - * This example uses the Method decorator instrumentation. - * Use TypeScript method decorators if you prefer writing your business logic using TypeScript Classes. - * If you aren’t using Classes, this requires the most significant refactoring. - * Find more Information in the docs: https://docs.powertools.aws.dev/lambda/typescript/ - * - * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format - * @param {APIGatewayProxyEvent} event - API Gateway Lambda Proxy Input Format - * - * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html - * @returns {Promise} object - API Gateway Lambda Proxy Output Format - * - */ - -class Lambda implements LambdaInterface { - @tracer.captureMethod() - public async getUuid(): Promise { - return getUuid(); - } - - @tracer.captureLambdaHandler({ captureResponse: false }) // by default the tracer would add the response as metadata on the segment, but there is a chance to hit the 64kb segment size limit. Therefore set captureResponse: false - @logger.injectLambdaContext({ logEvent: true }) - @metrics.logMetrics({ - throwOnEmptyMetrics: false, - captureColdStartMetric: true, - }) - public async handler( - event: APIGatewayProxyEvent, - context: Context - ): Promise { - if (event.httpMethod !== 'GET') { - throw new Error( - `getById only accepts GET method, you tried: ${event.httpMethod}` - ); - } - - // Tracer: Add awsRequestId as annotation - tracer.putAnnotation('awsRequestId', context.awsRequestId); - - // Logger: Append awsRequestId to each log statement - logger.appendKeys({ - awsRequestId: context.awsRequestId, - }); - - // Call the getUuid function - const uuid = await this.getUuid(); - - // Logger: Append uuid to each log statement - logger.appendKeys({ uuid }); - - // Tracer: Add uuid as annotation - tracer.putAnnotation('uuid', uuid); - - // Metrics: Add uuid as metadata - metrics.addMetadata('uuid', uuid); - - // Get the item from the table - // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#get-property - try { - if (!tableName) { - throw new Error('SAMPLE_TABLE environment variable is not set'); - } - if (!event.pathParameters) { - throw new Error('event does not contain pathParameters'); - } - if (!event.pathParameters.id) { - throw new Error('PathParameter id is missing'); - } - const data = await docClient.send( - new GetCommand({ - TableName: tableName, - Key: { - id: event.pathParameters.id, - }, - }) - ); - const item = data.Item; - - logger.info(`Response ${event.path}`, { - statusCode: 200, - body: item, - }); - - return { - statusCode: 200, - body: JSON.stringify(item), - }; - } catch (err) { - tracer.addErrorAsMetadata(err as Error); - logger.error('Error reading from table. ' + err); - - return { - statusCode: 500, - body: JSON.stringify({ error: 'Error reading from table.' }), - }; - } - } -} - -const handlerClass = new Lambda(); -export const handler = handlerClass.handler.bind(handlerClass); diff --git a/examples/cdk/functions/put-item.ts b/examples/cdk/functions/put-item.ts deleted file mode 100644 index b686244d2f..0000000000 --- a/examples/cdk/functions/put-item.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { PutCommand } from '@aws-sdk/lib-dynamodb'; -import { - APIGatewayProxyEvent, - APIGatewayProxyResult, - Context, -} from 'aws-lambda'; -import type { Subsegment } from 'aws-xray-sdk-core'; -import { tableName } from './common/constants'; -import { docClient } from './common/dynamodb-client'; -import { getUuid } from './common/getUuid'; -import { logger, metrics, tracer } from './common/powertools'; - -/** - * - * This example uses the manual instrumentation. - * - * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format - * @param {APIGatewayProxyEvent} event - API Gateway Lambda Proxy Input Format - * - * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html - * @returns {Promise} object - API Gateway Lambda Proxy Output Format - * - */ -export const handler = async ( - event: APIGatewayProxyEvent, - context: Context -): Promise => { - if (event.httpMethod !== 'POST') { - throw new Error( - `putItem only accepts POST method, you tried: ${event.httpMethod}` - ); - } - - // Logger: Log the incoming event - logger.info('Lambda invocation event', { event }); - - // Tracer: Get facade segment created by AWS Lambda - const segment = tracer.getSegment(); - - // Tracer: Create subsegment for the function & set it as active - let handlerSegment: Subsegment | undefined; - if (segment) { - handlerSegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`); - tracer.setSegment(handlerSegment); - } - - // Tracer: Annotate the subsegment with the cold start & serviceName - tracer.annotateColdStart(); - tracer.addServiceNameAnnotation(); - - // Tracer: Add awsRequestId as annotation - tracer.putAnnotation('awsRequestId', context.awsRequestId); - - // Metrics: Capture cold start metrics - metrics.captureColdStartMetric(); - - // Logger: Append awsRequestId to each log statement - logger.appendKeys({ - awsRequestId: context.awsRequestId, - }); - - const uuid = await getUuid(); - - // Logger: Append uuid to each log statement - logger.appendKeys({ uuid }); - - // Tracer: Add uuid as annotation - tracer.putAnnotation('uuid', uuid); - - // Metrics: Add uuid as metadata - metrics.addMetadata('uuid', uuid); - - // Creates a new item, or replaces an old item with a new item - // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#put-property - try { - if (!tableName) { - throw new Error('SAMPLE_TABLE environment variable is not set'); - } - if (!event.body) { - throw new Error('Event does not contain body'); - } - - // Get id and name from the body of the request - const body = JSON.parse(event.body); - const { id, name } = body; - - await docClient.send( - new PutCommand({ - TableName: tableName, - Item: { - id, - name, - }, - }) - ); - - logger.info(`Response ${event.path}`, { - statusCode: 200, - body, - }); - - return { - statusCode: 200, - body: JSON.stringify(body), - }; - } catch (err) { - tracer.addErrorAsMetadata(err as Error); - logger.error('Error writing data to table. ' + err); - - return { - statusCode: 500, - body: JSON.stringify({ error: 'Error writing data to table.' }), - }; - } finally { - if (segment && handlerSegment) { - // Tracer: Close subsegment (the AWS Lambda one is closed automatically) - handlerSegment.close(); // (## index.handler) - // Tracer: Set the facade segment as active again (the one created by AWS Lambda) - tracer.setSegment(segment); - } - } -}; diff --git a/examples/cdk/functions/uuid.ts b/examples/cdk/functions/uuid.ts deleted file mode 100644 index 04ab559e1f..0000000000 --- a/examples/cdk/functions/uuid.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { randomUUID } from 'node:crypto'; - -exports.handler = async () => { - return { - statusCode: 200, - body: JSON.stringify(randomUUID()), - }; -}; diff --git a/examples/cdk/jest.config.js b/examples/cdk/jest.config.js deleted file mode 100644 index 6cfd2c7be4..0000000000 --- a/examples/cdk/jest.config.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - testEnvironment: 'node', - roots: ['/tests'], - testMatch: ['**/*.test.ts'], - transform: { - '^.+\\.tsx?$': 'ts-jest', - }, -}; diff --git a/examples/cdk/src/example-stack.ts b/examples/cdk/src/example-stack.ts deleted file mode 100644 index 9d5236c054..0000000000 --- a/examples/cdk/src/example-stack.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { Duration, Stack, StackProps } from 'aws-cdk-lib'; -import { LambdaIntegration, RestApi } from 'aws-cdk-lib/aws-apigateway'; -import { AttributeType, BillingMode, Table } from 'aws-cdk-lib/aws-dynamodb'; -import { LayerVersion, Runtime, Tracing } from 'aws-cdk-lib/aws-lambda'; -import { - NodejsFunction, - NodejsFunctionProps, -} from 'aws-cdk-lib/aws-lambda-nodejs'; -import { RetentionDays } from 'aws-cdk-lib/aws-logs'; -import { StringParameter } from 'aws-cdk-lib/aws-ssm'; -import { Construct } from 'constructs'; - -const commonProps: Partial = { - runtime: Runtime.NODEJS_20_X, - tracing: Tracing.ACTIVE, - timeout: Duration.seconds(30), - logRetention: RetentionDays.ONE_DAY, - environment: { - NODE_OPTIONS: '--enable-source-maps', // see https://docs.aws.amazon.com/lambda/latest/dg/typescript-exceptions.html - POWERTOOLS_SERVICE_NAME: 'items-store', - POWERTOOLS_METRICS_NAMESPACE: 'PowertoolsCDKExample', - POWERTOOLS_LOG_LEVEL: 'DEBUG', - }, - bundling: { - externalModules: [ - '@aws-lambda-powertools/commons', - '@aws-lambda-powertools/logger', - '@aws-lambda-powertools/tracer', - '@aws-lambda-powertools/metrics', - '@aws-lambda-powertools/parameters', - ], - }, - layers: [], -}; - -export class CdkAppStack extends Stack { - public constructor(scope: Construct, id: string, props?: StackProps) { - super(scope, id, props); - - const uuidApi = new UuidApi(this, 'uuid-api'); - - const table = new Table(this, 'Table', { - billingMode: BillingMode.PAY_PER_REQUEST, - partitionKey: { - type: AttributeType.STRING, - name: 'id', - }, - }); - - commonProps.layers?.push( - LayerVersion.fromLayerVersionArn( - this, - 'powertools-layer', - `arn:aws:lambda:${ - Stack.of(this).region - }:094274105915:layer:AWSLambdaPowertoolsTypeScript:24` - ) - ); - - const putItemFn = new NodejsFunction(this, 'put-item-fn', { - ...commonProps, - entry: './functions/put-item.ts', - handler: 'handler', - }); - putItemFn.addEnvironment('SAMPLE_TABLE', table.tableName); - table.grantWriteData(putItemFn); - - const getAllItemsFn = new NodejsFunction(this, 'get-all-items-fn', { - ...commonProps, - entry: './functions/get-all-items.ts', - handler: 'handler', - }); - getAllItemsFn.addEnvironment('SAMPLE_TABLE', table.tableName); - table.grantReadData(getAllItemsFn); - - const getByIdFn = new NodejsFunction(this, 'get-by-id-fn', { - ...commonProps, - entry: './functions/get-by-id.ts', - handler: 'handler', - }); - - uuidApi.apiUrlParam.grantRead(getByIdFn); - uuidApi.apiUrlParam.grantRead(putItemFn); - uuidApi.apiUrlParam.grantRead(getAllItemsFn); - - getByIdFn.addEnvironment('SAMPLE_TABLE', table.tableName); - table.grantReadData(getByIdFn); - - const api = new RestApi(this, 'items-api', { - restApiName: 'Items Service', - description: 'This service serves items.', - deployOptions: { - tracingEnabled: true, - }, - }); - - const itemPutIntegration = new LambdaIntegration(putItemFn); - api.root.addMethod('POST', itemPutIntegration); - - const itemsIntegration = new LambdaIntegration(getAllItemsFn); - api.root.addMethod('GET', itemsIntegration); - - const item = api.root.addResource('{id}'); - const itemIntegration = new LambdaIntegration(getByIdFn); - item.addMethod('GET', itemIntegration); - } -} - -class UuidApi extends Construct { - public readonly apiUrlParam: StringParameter; - public constructor(scope: Construct, id: string) { - super(scope, id); - - const uuidFn = new NodejsFunction(this, 'UuidFn', { - runtime: Runtime.NODEJS_20_X, - entry: './functions/uuid.ts', - }); - - const api = new RestApi(this, 'uuid-api', { - restApiName: 'UUID Service', - description: 'This service serves UUIDs.', - deployOptions: { - tracingEnabled: true, - }, - }); - - const uuidIntegration = new LambdaIntegration(uuidFn); - const uuid = api.root.addResource('uuid'); - uuid.addMethod('GET', uuidIntegration); - - this.apiUrlParam = new StringParameter(this, 'uuid-api-url', { - parameterName: '/app/uuid-api-url', - stringValue: `${api.url}/uuid`, - }); - } -} diff --git a/examples/cdk/tests/cdk-app.test.ts b/examples/cdk/tests/cdk-app.test.ts deleted file mode 100644 index 83ebfe2105..0000000000 --- a/examples/cdk/tests/cdk-app.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import * as cdk from 'aws-cdk-lib'; -import { Template } from 'aws-cdk-lib/assertions'; -import * as CdkApp from '../src/example-stack'; - -test('CDK code synthesize', () => { - const app = new cdk.App(); - const stack = new CdkApp.CdkAppStack(app, 'MyTestStack'); - Template.fromStack(stack).resourceCountIs('AWS::Lambda::Function', 5); // The stack has 4 functions: 3 for the example, and 1 for the log retention that is deployed by CDK -}); diff --git a/examples/cdk/tsconfig.json b/examples/cdk/tsconfig.json deleted file mode 100644 index f0a3dcb587..0000000000 --- a/examples/cdk/tsconfig.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "compilerOptions": { - "experimentalDecorators": true, - "noImplicitAny": true, - "target": "ES2020", - "module": "commonjs", - "declaration": true, - "outDir": "lib", - "strict": true, - "inlineSourceMap": true, - "moduleResolution": "node", - "resolveJsonModule": true, - "pretty": true, - "baseUrl": "src/", - "rootDirs": [ - "src/" - ], - "esModuleInterop": true - }, - "include": [ - "src/**/*" - ], - "exclude": [ - "./node_modules", - "cdk.out" - ], - "watchOptions": { - "watchFile": "useFsEvents", - "watchDirectory": "useFsEvents", - "fallbackPolling": "dynamicPriority" - }, - "lib": [ - "ES2020" - ], - "types": [ - "jest", - "node" - ] -} \ No newline at end of file diff --git a/examples/sam/.gitignore b/examples/sam/.gitignore deleted file mode 100644 index f981fac977..0000000000 --- a/examples/sam/.gitignore +++ /dev/null @@ -1,208 +0,0 @@ -### Linux ### -*~ - -# temporary files which can be created if a process still has a handle open of a deleted file -.fuse_hidden* - -# KDE directory preferences -.directory - -# Linux trash folder which might appear on any partition or disk -.Trash-* - -# .nfs files are created when an open file is removed but is still being accessed -.nfs* - -### Node ### -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional stylelint cache -.stylelintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env -.env.test -.env*.local - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next - -# Nuxt.js build / generate output -.nuxt -dist - -# Storybook build outputs -.out -.storybook-out -storybook-static - -# rollup.js default build output -dist/ - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test - -# Temporary folders -tmp/ -temp/ - -### OSX ### -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### SAM ### -# Ignore build directories for the AWS Serverless Application Model (SAM) -# Info: https://aws.amazon.com/serverless/sam/ -# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html - -**/.aws-sam - -### Windows ### -# Windows thumbnail cache files -Thumbs.db -Thumbs.db:encryptable -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - -# SAM -samconfig.toml - -# npm lock file - this is managed by the npm workspace -package-lock.json \ No newline at end of file diff --git a/examples/sam/CHANGELOG.md b/examples/sam/CHANGELOG.md deleted file mode 100644 index 4850908a46..0000000000 --- a/examples/sam/CHANGELOG.md +++ /dev/null @@ -1,204 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## 2.0.3 (2024-03-15) - -**Note:** Version bump only for package sam-example - - - - - -## 2.0.2 (2024-03-05) - -**Note:** Version bump only for package sam-example - - - - -## 2.0.1 (2024-03-04) - -**Note:** Version bump only for package sam-example - - - - - -# [2.0.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.18.1...v2.0.0) (2024-03-04) - -**Note:** Version bump only for package sam-example - - - - - -## 1.18.1 (2024-02-20) - -**Note:** Version bump only for package sam-example - - - - - -## [1.18.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.17.0...v1.18.0) (2024-01-26) - -**Note:** Version bump only for package sam-sample - -## [1.17.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.16.0...v1.17.0) (2023-11-24) - -**Note:** Version bump only for package sam-example - -# [1.16.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.15.0...v1.16.0) (2023-11-16) - -### Features - -- **logger:** add support for `AWS_LAMBDA_LOG_LEVEL` and `POWERTOOLS_LOG_LEVEL` ([#1795](https://github.com/aws-powertools/powertools-lambda-typescript/issues/1795)) ([e69abfb](https://github.com/aws-powertools/powertools-lambda-typescript/commit/e69abfb5f1b5d3bf993ac2fe66fae85a85af9905)) - -# [1.15.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.14.2...v1.15.0) (2023-11-14) - -### Features - -- **maintenance:** add support for nodejs20.x runtime ([#1790](https://github.com/aws-powertools/powertools-lambda-typescript/issues/1790)) ([6b9b1bc](https://github.com/aws-powertools/powertools-lambda-typescript/commit/6b9b1bcb9baf4b3d8e0e5ec6709594aca09bb033)) - -## [1.14.2](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.14.1...v1.14.2) (2023-11-03) - -**Note:** Version bump only for package sam-example - -## [1.14.1](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.14.0...v1.14.1) (2023-10-31) - -**Note:** Version bump only for package sam-example - -# [1.14.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.13.1...v1.14.0) (2023-09-29) - -**Note:** Version bump only for package sam-example - -## [1.13.1](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.13.0...v1.13.1) (2023-09-21) - -**Note:** Version bump only for package sam-example - -# [1.13.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.12.1...v1.13.0) (2023-09-18) - -**Note:** Version bump only for package sam-example - -## [1.12.1](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.12.0...v1.12.1) (2023-07-25) - -**Note:** Version bump only for package sam-example - -# [1.12.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.11.1...v1.12.0) (2023-07-25) - -**Note:** Version bump only for package sam-example - -## [1.11.1](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.11.0...v1.11.1) (2023-07-11) - -**Note:** Version bump only for package sam-example - -# [1.11.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.10.0...v1.11.0) (2023-06-29) - -**Note:** Version bump only for package sam-example - -# [1.10.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.9.0...v1.10.0) (2023-06-23) - -**Note:** Version bump only for package sam-example - -# [1.9.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.8.0...v1.9.0) (2023-06-09) - -**Note:** Version bump only for package sam-example - -# [1.8.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.7.0...v1.8.0) (2023-04-07) - -**Note:** Version bump only for package sam-example - -# [1.7.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.6.0...v1.7.0) (2023-03-20) - -**Note:** Version bump only for package sam-example - -# [1.6.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.5.1...v1.6.0) (2023-03-02) - -**Note:** Version bump only for package sam-example - -## [1.5.1](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.5.0...v1.5.1) (2023-01-13) - -**Note:** Version bump only for package sam-example - -# [1.5.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.4.1...v1.5.0) (2022-11-25) - -**Note:** Version bump only for package sam-example - -## [1.4.1](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.4.0...v1.4.1) (2022-11-09) - -**Note:** Version bump only for package sam-example - -# [1.4.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.3.0...v1.4.0) (2022-10-27) - -**Note:** Version bump only for package powertools-typescript-sam-example - -# [1.3.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.2.1...v1.3.0) (2022-10-17) - -### Features - -- publish lib as Lambda Layer ([#1095](https://github.com/aws-powertools/powertools-lambda-typescript/issues/1095)) ([83f6efb](https://github.com/aws-powertools/powertools-lambda-typescript/commit/83f6efba1db32ba2dc8fff026e258b5de66783e0)) - -### Reverts - -- Revert "chore(release): v1.3.0 [skip ci]" ([237b99f](https://github.com/aws-powertools/powertools-lambda-typescript/commit/237b99f9f6eff5e6e26d779603cf13cd4422c156)) - -## [1.2.1](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.2.0...v1.2.1) (2022-08-25) - -**Note:** Version bump only for package powertools-typescript-sam-example - -# [1.2.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.1.1...v1.2.0) (2022-08-23) - -**Note:** Version bump only for package powertools-typescript-sam-example - -## [1.1.1](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.1.0...v1.1.1) (2022-08-18) - -**Note:** Version bump only for package powertools-typescript-sam-example - -# [1.1.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.0.2...v1.1.0) (2022-08-12) - -**Note:** Version bump only for package powertools-typescript-sam-example - -## [1.0.2](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v1.0.1...v1.0.2) (2022-07-19) - -**Note:** Version bump only for package powertools-typescript-sam-example - -## [1.0.1](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v0.12.0-rc.1...v1.0.1) (2022-07-14) - -**Note:** Version bump only for package powertools-typescript-sam-example - -# [0.12.0-rc.1](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v0.12.0-rc.0...v0.12.0-rc.1) (2022-07-14) - -**Note:** Version bump only for package powertools-typescript-sam-example - -# [0.12.0-rc.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v0.11.1-rc.0...v0.12.0-rc.0) (2022-07-14) - -### Reverts - -- Revert "chore(release): v0.12.0-rc.0 [skip ci]" ([9397f1d](https://github.com/aws-powertools/powertools-lambda-typescript/commit/9397f1d3624eb0bfbeb5e4c2702ae51e558a5b4a)) -- Revert "chore(release): v0.12.0-rc.0 [skip ci]" (#1017) ([51c18da](https://github.com/aws-powertools/powertools-lambda-typescript/commit/51c18da20db434f8b12f320e5074e3e0a146046e)), closes [#1017](https://github.com/aws-powertools/powertools-lambda-typescript/issues/1017) - -## [0.11.1-rc.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v0.11.0...v0.11.1-rc.0) (2022-06-24) - -**Note:** Version bump only for package powertools-typescript-sam-example - -# [0.11.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v0.10.0...v0.11.0) (2022-06-23) - -**Note:** Version bump only for package powertools-typescript-sam-example - -# [0.10.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v0.9.1...v0.10.0) (2022-06-02) - -### Features - -- **all:** nodejs16x support ([#877](https://github.com/aws-powertools/powertools-lambda-typescript/issues/877)) ([d2b13c9](https://github.com/aws-powertools/powertools-lambda-typescript/commit/d2b13c945adb1a74b7c5f76d45f28a6979ce6429)) - -## [0.9.1](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v0.9.0...v0.9.1) (2022-05-24) - -**Note:** Version bump only for package powertools-typescript-sam-example - -# [0.9.0](https://github.com/aws-powertools/powertools-lambda-typescript/compare/v0.8.1...v0.9.0) (2022-05-16) - -### Features - -- **examples:** added sam example to workflows ([#849](https://github.com/aws-powertools/powertools-lambda-typescript/issues/849)) ([93f1c7b](https://github.com/aws-powertools/powertools-lambda-typescript/commit/93f1c7b55cb159dfcbbcb41149ccec7fd5db1660)) diff --git a/examples/sam/README.md b/examples/sam/README.md deleted file mode 100644 index 7dca2d2518..0000000000 --- a/examples/sam/README.md +++ /dev/null @@ -1,201 +0,0 @@ -# Powertools for AWS Lambda (TypeScript) examples in SAM - -This project contains source code and supporting files for a serverless application that you can deploy with the [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html). The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. - -This project includes the following files and folders: - -- `src` - Code for the application's Lambda function written in TypeScript. See "Prepare the project" below for instructions on how to copy the Lambda handler code here. -- `events` - Invocation events that you can use to invoke the function. -- `template.yaml` - A template that defines the application's AWS resources. - -The application uses several AWS resources, including Lambda functions and an API Gateway API. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. - -> **Note** -> You will need to have a valid AWS Account in order to deploy these resources. These resources may incur costs to your AWS Account. The cost from **some services** are covered by the [AWS Free Tier](https://aws.amazon.com/free/?all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=*all&awsf.Free%20Tier%20Categories=*all) but not all of them. If you don't have an AWS Account follow [these instructions to create one](https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/). - -## Prepare the project - -All the following commands in this file must be executed inside the folder `examples/sam` - -Before deploying this example install the npm dependencies: - -```bash -npm i -``` - -> **Note** -> In order to run this example you'll need [AWS SAM CLI version 1.65 or later](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html). If you have an older version of the AWS SAM CLI, see [Upgrading the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/manage-sam-cli-versions.html#manage-sam-cli-versions-upgrade). - -## Deploy the sample application - -To build and deploy your application for the first time, run the following in your shell: - -```bash -sam build --beta-features -sam deploy --guided -``` - -The first command will build the source of your application. Using esbuild for bundling Node.js and TypeScript is a beta feature, therefore we add the `--beta-features` parameter. The second command will package and deploy your application to AWS, with a series of prompts: - -* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. -* **AWS Region**: The AWS region you want to deploy your app to. -* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. -* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. -* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. - -You can find your API Gateway Endpoint URL in the output values displayed after deployment. - -## Execute the functions via API Gateway - -Use the API Gateway Endpoint URL from the output values to execute the functions. First, let's add two items to the DynamoDB Table by running: - -```bash -curl -XPOST --header 'Content-Type: application/json' --data '{"id":"myfirstitem","name":"Some Name for the first item"}' https://randomid12345.execute-api.eu-central-1.amazonaws.com/Prod/ -curl -XPOST --header 'Content-Type: application/json' --data '{"id":"myseconditem","name":"Some Name for the second item"}' https://randomid1245.execute-api.eu-central-1.amazonaws.com/Prod/ -```` - -Now, let's retrieve all items by running: - -```bash -curl -XGET https://randomid12345.execute-api.eu-central-1.amazonaws.com/Prod/ -``` - -And finally, let's retrieve a specific item by running: -```bash -curl -XGET https://randomid12345.execute-api.eu-central-1.amazonaws.com/Prod/myseconditem/ -``` - -## Observe the outputs in AWS CloudWatch & X-Ray - -### CloudWatch - -If we check the logs in CloudWatch, we can see that the logs are structured like this -``` -2022-04-26T17:00:23.808Z e8a51294-6c6a-414c-9777-6b0f24d8739b DEBUG -{ - "level": "DEBUG", - "message": "retrieved items: 0", - "service": "getAllItems", - "timestamp": "2022-04-26T17:00:23.808Z", - "awsRequestId": "e8a51294-6c6a-414c-9777-6b0f24d8739b" -} -``` - -By having structured logs like this, we can easily search and analyse them in [CloudWatch Logs Insight](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AnalyzingLogData.html). Run the following query to get all messages for a specific `awsRequestId`: - -```` -filter awsRequestId="bcd50969-3a55-49b6-a997-91798b3f133a" - | fields timestamp, message -```` -### AWS X-Ray - -As we have enabled tracing for our Lambda-Funtions, you can visit [AWS CloudWatch Console](https://console.aws.amazon.com/cloudwatch/) and see [Traces](https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-traces) and a [Service Map](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-using-xray-maps.html) for our application. - -You can also use the AWS SAM CLI to retrieve traces by running `sam traces`. - -## Use the SAM CLI to build and test locally - -Build your application with the `sam build` command. - -```bash -sam build --beta-features -``` - -The SAM CLI installs dependencies defined in `package.json`, compiles TypeScript with esbuild, creates a deployment package, and saves it in the `.aws-sam/build` folder. - -Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. - -Run functions locally and invoke them with the `sam local invoke` command. - -```bash -sam local invoke getAllItemsFunction --event events/event-get-all-items.json -``` - -The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000. - -```bash -sam local start-api -curl http://localhost:3000/ -``` - -The SAM CLI reads the application template to determine the API's routes and the functions that they invoke. The `Events` property on each function's definition includes the route and method for each path. - -```yaml -Events: - HelloWorld: - Type: Api - Properties: - Path: / - Method: get -``` - -## Fetch, tail, and filter Lambda function logs - -To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. - -`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. - -```bash -sam logs -n getAllItemsFunction --stack-name powertools-example --tail -``` - -You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). - -## Switch to Lambda Layer - -In this example we are including Powertools for AWS Lambda (TypeScript) as a dependency in our function's `package.json`. This is the recommended approach for development and testing. However, for production, you can also use Powertools for AWS as a Lambda Layer. - -To start using the public Lambda Layer, you need to: -1. Specify the Layer's ARN in `Layers` section under each function's `Properties` section -2. Instruct `esbuild` to not bundle `@aws-lambda-powertools` under each function's `Metadata/BuildProperties` section - -To do so, open the `template.yaml` file, and **for each Lambda Function**, update the following sections: -```diff -Resources: - putItemFunction: - Type: AWS::Serverless::Function - Properties: -+ Layers: -+ - arn:aws:lambda:eu-west-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:6 - Handler: src/put-item.putItemHandler - Description: A simple example includes a HTTP ost method to add one item to a DynamoDB table. - Policies: -``` - -and: - -```diff -Metadata: - # Manage esbuild properties - BuildMethod: esbuild - BuildProperties: - BuildMethod: esbuild - BuildProperties: - Minify: true - Target: "ES2020" - Sourcemap: true - External: - - "@aws-sdk/lib-dynamodb" - - "@aws-sdk/client-dynamodb" -+ - "@aws-lambda-powertools/commons" -+ - "@aws-lambda-powertools/logger' -+ - "@aws-lambda-powertools/metrics" -+ - "@aws-lambda-powertools/tracer" - EntryPoints: -``` - -Learn more about Lambda Layers [here](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html) and about the Powertools for AWS Lambda (TypeScript) layers [here](https://docs.powertools.aws.dev/lambda/typescript/latest/#lambda-layer). - -## Cleanup - -To delete the sample application that you created, run the command below while in the `examples/sam` directory: - -```bash -sam delete -``` - -## Resources - -See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. - -Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/examples/sam/events/event-get-all-items.json b/examples/sam/events/event-get-all-items.json deleted file mode 100644 index 3a0cb5f77b..0000000000 --- a/examples/sam/events/event-get-all-items.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "httpMethod": "GET" -} \ No newline at end of file diff --git a/examples/sam/events/event-get-by-id.json b/examples/sam/events/event-get-by-id.json deleted file mode 100644 index 63a64fb458..0000000000 --- a/examples/sam/events/event-get-by-id.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "httpMethod": "GET", - "pathParameters": { - "id": "id1" - } -} \ No newline at end of file diff --git a/examples/sam/events/event-post-item.json b/examples/sam/events/event-post-item.json deleted file mode 100644 index 6367003e54..0000000000 --- a/examples/sam/events/event-post-item.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "httpMethod": "POST", - "body": "{\"id\": \"id1\",\"name\": \"name1\"}" -} \ No newline at end of file diff --git a/examples/sam/jest.config.js b/examples/sam/jest.config.js deleted file mode 100644 index 6cfd2c7be4..0000000000 --- a/examples/sam/jest.config.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - testEnvironment: 'node', - roots: ['/tests'], - testMatch: ['**/*.test.ts'], - transform: { - '^.+\\.tsx?$': 'ts-jest', - }, -}; diff --git a/examples/sam/package.json b/examples/sam/package.json deleted file mode 100644 index 139739c29a..0000000000 --- a/examples/sam/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "sam-example", - "version": "2.0.3", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com" - }, - "private": true, - "description": "This project contains source code and supporting files for a serverless application that you can deploy with the [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html). The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API.", - "license": "MIT-0", - "scripts": { - "build": "echo 'Not applicable, run `sam build --beta-features` instead to build the stack'", - "test": "npm run test:unit", - "lint": "eslint --ext .ts,.js --no-error-on-unmatched-pattern .", - "lint-fix": "eslint --fix --ext .ts,.js --fix --no-error-on-unmatched-pattern .", - "test:unit": "export POWERTOOLS_DEV=true && npm run build && jest --silent", - "test:e2e": "echo 'To be implemented ...'" - }, - "lint-staged": { - "*.ts": "npm run lint-fix", - "*.js": "npm run lint-fix" - }, - "devDependencies": { - "@aws-sdk/client-dynamodb": "^3.538.0", - "@aws-sdk/client-ssm": "^3.535.0", - "@aws-sdk/lib-dynamodb": "^3.538.0", - "@types/aws-lambda": "^8.10.136", - "@types/jest": "^29.5.12", - "@types/node": "^20.11.30", - "esbuild": "^0.20.2", - "jest": "^29.7.0", - "ts-jest": "^29.1.2", - "ts-node": "^10.9.2", - "typescript": "^5.4.3" - }, - "dependencies": { - "@aws-lambda-powertools/logger": "^2.0.3", - "@aws-lambda-powertools/metrics": "^2.0.3", - "@aws-lambda-powertools/parameters": "^2.0.3", - "@aws-lambda-powertools/tracer": "^2.0.3", - "@middy/core": "^4.7.0", - "phin": "^3.7.0" - } -} diff --git a/examples/sam/src/common/constants.ts b/examples/sam/src/common/constants.ts deleted file mode 100644 index 3496c50046..0000000000 --- a/examples/sam/src/common/constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Get the DynamoDB table name from environment variables -const tableName = process.env.SAMPLE_TABLE; - -export { tableName }; diff --git a/examples/sam/src/common/dynamodb-client.ts b/examples/sam/src/common/dynamodb-client.ts deleted file mode 100644 index 40a7c994b8..0000000000 --- a/examples/sam/src/common/dynamodb-client.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; -import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb'; -import { tracer } from './powertools'; - -// Create DynamoDB Client and patch it for tracing -const ddbClient = tracer.captureAWSv3Client(new DynamoDBClient({})); - -const marshallOptions = { - // Whether to automatically convert empty strings, blobs, and sets to `null`. - convertEmptyValues: false, // false, by default. - // Whether to remove undefined values while marshalling. - removeUndefinedValues: false, // false, by default. - // Whether to convert typeof object to map attribute. - convertClassInstanceToMap: false, // false, by default. -}; - -const unmarshallOptions = { - // Whether to return numbers as a string instead of converting them to native JavaScript numbers. - wrapNumbers: false, // false, by default. -}; - -const translateConfig = { marshallOptions, unmarshallOptions }; - -// Create the DynamoDB Document client. -const docClient = DynamoDBDocumentClient.from(ddbClient, translateConfig); - -export { docClient }; diff --git a/examples/sam/src/common/getUuid.ts b/examples/sam/src/common/getUuid.ts deleted file mode 100644 index eb567314bd..0000000000 --- a/examples/sam/src/common/getUuid.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { logger } from './powertools'; -import { getParameter } from '@aws-lambda-powertools/parameters/ssm'; -import { randomUUID } from 'node:crypto'; -import { default as request } from 'phin'; - -export const getUuid = async (): Promise => { - const uuidApiUrl = await getParameter('/app/uuid-api-url'); - if (!uuidApiUrl) { - // create uuid locally - logger.warn('No uuid-api-url parameter found, creating uuid locally'); - - return randomUUID(); - } else { - // Request a sample random uuid from a webservice - const res = await request<{ uuid: string }>({ - url: uuidApiUrl, - parse: 'json', - }); - - return res.body.uuid; - } -}; diff --git a/examples/sam/src/common/powertools.ts b/examples/sam/src/common/powertools.ts deleted file mode 100644 index fa0b798afa..0000000000 --- a/examples/sam/src/common/powertools.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Logger } from '@aws-lambda-powertools/logger'; -import { Metrics } from '@aws-lambda-powertools/metrics'; -import { Tracer } from '@aws-lambda-powertools/tracer'; -import { PT_VERSION } from '@aws-lambda-powertools/commons'; - -const defaultValues = { - region: process.env.AWS_REGION || 'N/A', - executionEnv: process.env.AWS_EXECUTION_ENV || 'N/A', -}; - -const logger = new Logger({ - persistentLogAttributes: { - ...defaultValues, - logger: { - name: '@aws-lambda-powertools/logger', - version: PT_VERSION, - }, - }, -}); - -const metrics = new Metrics({ - defaultDimensions: defaultValues, -}); - -const tracer = new Tracer(); - -export { logger, metrics, tracer }; diff --git a/examples/sam/src/get-all-items.ts b/examples/sam/src/get-all-items.ts deleted file mode 100644 index cb2b09fbfd..0000000000 --- a/examples/sam/src/get-all-items.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { - APIGatewayProxyEvent, - APIGatewayProxyResult, - Context, -} from 'aws-lambda'; -import middy from '@middy/core'; -import { tableName } from './common/constants'; -import { logger, tracer, metrics } from './common/powertools'; -import { logMetrics } from '@aws-lambda-powertools/metrics/middleware'; -import { injectLambdaContext } from '@aws-lambda-powertools/logger/middleware'; -import { captureLambdaHandler } from '@aws-lambda-powertools/tracer/middleware'; -import { docClient } from './common/dynamodb-client'; -import { ScanCommand } from '@aws-sdk/lib-dynamodb'; -import { getUuid } from './common/getUuid'; - -/* - * - * This example uses the Middy middleware instrumentation. - * It is the best choice if your existing code base relies on the Middy middleware engine. - * Powertools for AWS Lambda (TypeScript) offers compatible Middy middleware to make this integration seamless. - * Find more Information in the docs: https://docs.powertools.aws.dev/lambda/typescript/ - * - * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format - * @param {Object} event - API Gateway Lambda Proxy Input Format - * - * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html - * @returns {Object} object - API Gateway Lambda Proxy Output Format - * - */ -const getAllItemsHandler = async ( - event: APIGatewayProxyEvent, - context: Context -): Promise => { - if (event.httpMethod !== 'GET') { - throw new Error( - `getAllItems only accepts GET method, you tried: ${event.httpMethod}` - ); - } - - // Tracer: Add awsRequestId as annotation - tracer.putAnnotation('awsRequestId', context.awsRequestId); - - // Logger: Append awsRequestId to each log statement - logger.appendKeys({ - awsRequestId: context.awsRequestId, - }); - - const uuid = await getUuid(); - - // Logger: Append uuid to each log statement - logger.appendKeys({ uuid }); - - // Tracer: Add uuid as annotation - tracer.putAnnotation('uuid', uuid); - - // Metrics: Add uuid as metadata - metrics.addMetadata('uuid', uuid); - - // get all items from the table (only first 1MB data, you can use `LastEvaluatedKey` to get the rest of data) - // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#scan-property - // https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html - try { - if (!tableName) { - throw new Error('SAMPLE_TABLE environment variable is not set'); - } - - const data = await docClient.send( - new ScanCommand({ - TableName: tableName, - }) - ); - const { Items: items } = data; - - // Logger: All log statements are written to CloudWatch - logger.debug(`retrieved items: ${items?.length || 0}`); - - logger.info(`Response ${event.path}`, { - statusCode: 200, - body: items, - }); - - return { - statusCode: 200, - body: JSON.stringify(items), - }; - } catch (err) { - tracer.addErrorAsMetadata(err as Error); - logger.error('Error reading from table. ' + err); - - return { - statusCode: 500, - body: JSON.stringify({ error: 'Error reading from table.' }), - }; - } -}; - -// Wrap the handler with middy -export const handler = middy(getAllItemsHandler) - // Use the middleware by passing the Metrics instance as a parameter - .use(logMetrics(metrics)) - // Use the middleware by passing the Logger instance as a parameter - .use(injectLambdaContext(logger, { logEvent: true })) - // Use the middleware by passing the Tracer instance as a parameter - .use(captureLambdaHandler(tracer, { captureResponse: false })); // by default the tracer would add the response as metadata on the segment, but there is a chance to hit the 64kb segment size limit. Therefore set captureResponse: false diff --git a/examples/sam/src/get-by-id.ts b/examples/sam/src/get-by-id.ts deleted file mode 100644 index f35c053eef..0000000000 --- a/examples/sam/src/get-by-id.ts +++ /dev/null @@ -1,114 +0,0 @@ -import type { LambdaInterface } from '@aws-lambda-powertools/commons/types'; -import { GetCommand } from '@aws-sdk/lib-dynamodb'; -import { - APIGatewayProxyEvent, - APIGatewayProxyResult, - Context, -} from 'aws-lambda'; -import { tableName } from './common/constants'; -import { docClient } from './common/dynamodb-client'; -import { getUuid } from './common/getUuid'; -import { logger, metrics, tracer } from './common/powertools'; - -/* - * - * This example uses the Method decorator instrumentation. - * Use TypeScript method decorators if you prefer writing your business logic using TypeScript Classes. - * If you aren’t using Classes, this requires the most significant refactoring. - * Find more Information in the docs: https://docs.powertools.aws.dev/lambda/typescript/ - * - * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format - * @param {APIGatewayProxyEvent} event - API Gateway Lambda Proxy Input Format - * - * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html - * @returns {Promise} object - API Gateway Lambda Proxy Output Format - * - */ - -class Lambda implements LambdaInterface { - @tracer.captureMethod() - public async getUuid(): Promise { - return getUuid(); - } - - @tracer.captureLambdaHandler({ captureResponse: false }) // by default the tracer would add the response as metadata on the segment, but there is a chance to hit the 64kb segment size limit. Therefore set captureResponse: false - @logger.injectLambdaContext({ logEvent: true }) - @metrics.logMetrics({ - throwOnEmptyMetrics: false, - captureColdStartMetric: true, - }) - public async handler( - event: APIGatewayProxyEvent, - context: Context - ): Promise { - if (event.httpMethod !== 'GET') { - throw new Error( - `getById only accepts GET method, you tried: ${event.httpMethod}` - ); - } - - // Tracer: Add awsRequestId as annotation - tracer.putAnnotation('awsRequestId', context.awsRequestId); - - // Logger: Append awsRequestId to each log statement - logger.appendKeys({ - awsRequestId: context.awsRequestId, - }); - - // Call the getUuid function - const uuid = await this.getUuid(); - - // Logger: Append uuid to each log statement - logger.appendKeys({ uuid }); - - // Tracer: Add uuid as annotation - tracer.putAnnotation('uuid', uuid); - - // Metrics: Add uuid as metadata - metrics.addMetadata('uuid', uuid); - - // Get the item from the table - // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#get-property - try { - if (!tableName) { - throw new Error('SAMPLE_TABLE environment variable is not set'); - } - if (!event.pathParameters) { - throw new Error('event does not contain pathParameters'); - } - if (!event.pathParameters.id) { - throw new Error('PathParameter id is missing'); - } - const data = await docClient.send( - new GetCommand({ - TableName: tableName, - Key: { - id: event.pathParameters.id, - }, - }) - ); - const item = data.Item; - - logger.info(`Response ${event.path}`, { - statusCode: 200, - body: item, - }); - - return { - statusCode: 200, - body: JSON.stringify(item), - }; - } catch (err) { - tracer.addErrorAsMetadata(err as Error); - logger.error('Error reading from table. ' + err); - - return { - statusCode: 500, - body: JSON.stringify({ error: 'Error reading from table.' }), - }; - } - } -} - -const handlerClass = new Lambda(); -export const handler = handlerClass.handler.bind(handlerClass); diff --git a/examples/sam/src/get-uuid.ts b/examples/sam/src/get-uuid.ts deleted file mode 100644 index 04ab559e1f..0000000000 --- a/examples/sam/src/get-uuid.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { randomUUID } from 'node:crypto'; - -exports.handler = async () => { - return { - statusCode: 200, - body: JSON.stringify(randomUUID()), - }; -}; diff --git a/examples/sam/src/put-item.ts b/examples/sam/src/put-item.ts deleted file mode 100644 index aab7116602..0000000000 --- a/examples/sam/src/put-item.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { PutCommand } from '@aws-sdk/lib-dynamodb'; -import { - APIGatewayProxyEvent, - APIGatewayProxyResult, - Context, -} from 'aws-lambda'; -import type { Subsegment } from 'aws-xray-sdk-core'; -import { tableName } from './common/constants'; -import { docClient } from './common/dynamodb-client'; -import { logger, metrics, tracer } from './common/powertools'; -import { getUuid } from './common/getUuid'; - -/** - * - * This example uses the manual instrumentation. - * - * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format - * @param {APIGatewayProxyEvent} event - API Gateway Lambda Proxy Input Format - * - * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html - * @returns {Promise} object - API Gateway Lambda Proxy Output Format - * - */ -export const handler = async ( - event: APIGatewayProxyEvent, - context: Context -): Promise => { - if (event.httpMethod !== 'POST') { - throw new Error( - `putItem only accepts POST method, you tried: ${event.httpMethod}` - ); - } - - // Logger: Log the incoming event - logger.info('Lambda invocation event', { event }); - - // Tracer: Get facade segment created by AWS Lambda - const segment = tracer.getSegment(); - - // Tracer: Create subsegment for the function & set it as active - let handlerSegment: Subsegment | undefined; - if (segment) { - const handlerSegment = segment.addNewSubsegment( - `## ${process.env._HANDLER}` - ); - tracer.setSegment(handlerSegment); - } - - // Tracer: Annotate the subsegment with the cold start & serviceName - tracer.annotateColdStart(); - tracer.addServiceNameAnnotation(); - - // Tracer: Add awsRequestId as annotation - tracer.putAnnotation('awsRequestId', context.awsRequestId); - - // Metrics: Capture cold start metrics - metrics.captureColdStartMetric(); - - // Logger: Append awsRequestId to each log statement - logger.appendKeys({ - awsRequestId: context.awsRequestId, - }); - - const uuid = await getUuid(); - // Logger: Append uuid to each log statement - logger.appendKeys({ uuid }); - - // Tracer: Add uuid as annotation - tracer.putAnnotation('uuid', uuid); - - // Metrics: Add uuid as metadata - metrics.addMetadata('uuid', uuid); - - // Creates a new item, or replaces an old item with a new item - // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#put-property - try { - if (!tableName) { - throw new Error('SAMPLE_TABLE environment variable is not set'); - } - if (!event.body) { - throw new Error('Event does not contain body'); - } - - // Get id and name from the body of the request - const body = JSON.parse(event.body); - const { id, name } = body; - - await docClient.send( - new PutCommand({ - TableName: tableName, - Item: { - id, - name, - }, - }) - ); - - logger.info(`Response ${event.path}`, { - statusCode: 200, - body, - }); - - return { - statusCode: 200, - body: JSON.stringify(body), - }; - } catch (err) { - tracer.addErrorAsMetadata(err as Error); - logger.error('Error writing data to table. ' + err); - - return { - statusCode: 500, - body: JSON.stringify({ error: 'Error writing data to table.' }), - }; - } finally { - if (segment && handlerSegment) { - // Tracer: Close subsegment (the AWS Lambda one is closed automatically) - handlerSegment.close(); // (## index.handler) - // Tracer: Set the facade segment as active again (the one created by AWS Lambda) - tracer.setSegment(segment); - } - } -}; diff --git a/examples/sam/template.yaml b/examples/sam/template.yaml deleted file mode 100644 index 9596113f30..0000000000 --- a/examples/sam/template.yaml +++ /dev/null @@ -1,284 +0,0 @@ -# This is the SAM template that represents the architecture of your serverless application -# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template-basics.html - -# The AWSTemplateFormatVersion identifies the capabilities of the template -# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/format-version-structure.html -AWSTemplateFormatVersion: 2010-09-09 -Description: >- - An example application with Powertools for AWS Lambda (TypeScript) instrumented. Its purpose is to demonstrate how to use the Powertools for AWS Lambda (TypeScript) with AWS SAM. The application an API with contains 3 endpoints (get all items, get an item by ids, put an item). Each Lambda function utilises Logger, Metrics, and Tracers. - -# Transform section specifies one or more macros that AWS CloudFormation uses to process your template -# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-section-structure.html -Transform: AWS::Serverless-2016-10-31 - -Parameters: - apiGatewayName: - Type: String - Default: uuid-api - apiGatewayStageName: - Type: String - AllowedPattern: '[a-z0-9]+' - Default: dev - apiGatewayHTTPMethod: - Type: String - Default: GET - lambdaFunctionName: - Type: String - AllowedPattern: '[a-zA-Z0-9]+[a-zA-Z0-9-]+[a-zA-Z0-9]+' - Default: uuid-lambda - -# Global configuration that all Functions inherit -# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-template-anatomy-globals.html -Globals: - Function: - Runtime: nodejs20.x - Architectures: - - x86_64 - MemorySize: 128 - Timeout: 100 - -# Resources declares the AWS resources that you want to include in the stack -# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html -Resources: - # Each Lambda function is defined by properties: - # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction - - # This is a Lambda function config associated with the source code: get-all-items.js - getAllItemsFunction: - Type: AWS::Serverless::Function - Properties: - Handler: src/get-all-items.handler - Description: A simple example includes a HTTP get method to get all items from a DynamoDB table. - Policies: - # Give Create/Read/Update/Delete Permissions to the SampleTable - - DynamoDBReadPolicy: - TableName: !Ref SampleTable - - SSMParameterWithSlashPrefixReadPolicy: - ParameterName: !Ref uuidApiUrlParameter - Tracing: Active - Environment: - Variables: - # Make table name accessible as environment variable from function code during execution - NODE_OPTIONS: '--enable-source-maps' # see https://docs.aws.amazon.com/lambda/latest/dg/typescript-exceptions.html - AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1 # see https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/node-reusing-connections.html - SAMPLE_TABLE: !Ref SampleTable - POWERTOOLS_SERVICE_NAME: items-store - POWERTOOLS_METRICS_NAMESPACE: PowertoolsSAMExample - POWERTOOLS_LOG_LEVEL: debug - Events: - Api: - Type: Api - Properties: - Path: / - Method: GET - Metadata: - # Manage esbuild properties - BuildMethod: esbuild - BuildProperties: - Minify: true - Target: 'ES2020' - Sourcemap: true - EntryPoints: - - src/get-all-items.ts - - # This is a Lambda function config associated with the source code: get-by-id.js - getByIdFunction: - Type: AWS::Serverless::Function - Properties: - Handler: src/get-by-id.handler - Description: A simple example includes a HTTP get method to get one item by id from a DynamoDB table. - Policies: - # Give Create/Read/Update/Delete Permissions to the SampleTable - - DynamoDBReadPolicy: - TableName: - !Ref SampleTable - # add ssm:getParameter permission to the function - - SSMParameterWithSlashPrefixReadPolicy: - ParameterName: !Ref uuidApiUrlParameter - Tracing: Active - Environment: - Variables: - # Make table name accessible as environment variable from function code during execution - NODE_OPTIONS: '--enable-source-maps' # see https://docs.aws.amazon.com/lambda/latest/dg/typescript-exceptions.html - AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1 # see https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/node-reusing-connections.html - SAMPLE_TABLE: !Ref SampleTable - POWERTOOLS_SERVICE_NAME: items-store - POWERTOOLS_METRICS_NAMESPACE: PowertoolsSAMExample - POWERTOOLS_LOG_LEVEL: debug - Events: - Api: - Type: Api - Properties: - Path: /{id} - Method: GET - Metadata: - # Manage esbuild properties - BuildMethod: esbuild - BuildProperties: - Minify: true - Target: 'ES2020' - Sourcemap: true - EntryPoints: - - src/get-by-id.ts - - # This is a Lambda function config associated with the source code: get-uuid.js - getUuidFunction: - Type: AWS::Serverless::Function - Properties: - Handler: src/get-uuid.handler - Description: A simple example includes a HTTP get method to get a UUID. - Tracing: Active - FunctionName: !Ref lambdaFunctionName - Role: !GetAtt lambdaIAMRole.Arn - Environment: - Variables: - POWERTOOLS_SERVICE_NAME: items-store - POWERTOOLS_METRICS_NAMESPACE: PowertoolsSAMExample - POWERTOOLS_LOG_LEVEL: debug - # add ssm:getParameter permission to the function - Policies: - - SSMParameterWithSlashPrefixReadPolicy: - ParameterName: !Ref uuidApiUrlParameter - Events: - Api: - Type: Api - Properties: - Path: / - Method: GET - RestApiId: !Ref apiGateway - Metadata: - # Manage esbuild properties - BuildMethod: esbuild - BuildProperties: - Minify: true - Target: 'ES2020' - Sourcemap: true - EntryPoints: - - src/get-uuid.ts - - # create ssm parameter with the value of the uuid api url - uuidApiUrlParameter: - Type: AWS::SSM::Parameter - Properties: - Name: /app/uuid-api-url - Type: String - Description: 'Example parameter for UUID API URL' - Value: !Sub 'https://${apiGateway}.execute-api.${AWS::Region}.amazonaws.com/${apiGatewayStageName}/' - - # This is a Lambda function config associated with the source code: put-item.js - putItemFunction: - Type: AWS::Serverless::Function - Properties: - Handler: src/put-item.handler - Description: A simple example includes a HTTP post method to add one item to a DynamoDB table. - Policies: - # Give Create/Read/Update/Delete Permissions to the SampleTable - - DynamoDBCrudPolicy: - TableName: !Ref SampleTable - - SSMParameterWithSlashPrefixReadPolicy: - ParameterName: !Ref uuidApiUrlParameter - Tracing: Active - Environment: - Variables: - # Make table name accessible as environment variable from function code during execution - NODE_OPTIONS: '--enable-source-maps' # see https://docs.aws.amazon.com/lambda/latest/dg/typescript-exceptions.html - AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1 # see https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/node-reusing-connections.html - SAMPLE_TABLE: !Ref SampleTable - POWERTOOLS_SERVICE_NAME: items-store - POWERTOOLS_METRICS_NAMESPACE: PowertoolsSAMExample - POWERTOOLS_LOG_LEVEL: debug - Events: - Api: - Type: Api - Properties: - Path: / - Method: POST - Metadata: - # Manage esbuild properties - BuildMethod: esbuild - BuildProperties: - Minify: true - Target: 'ES2020' - Sourcemap: true - EntryPoints: - - src/put-item.ts - - # DynamoDB table to store item: {id: , name: } - SampleTable: - Type: AWS::Serverless::SimpleTable - Properties: - PrimaryKey: - Name: id - Type: String - - apiGateway: - Type: AWS::ApiGateway::RestApi - Properties: - Name: !Sub ${AWS::StackName}-uuid-api - Description: 'Example API for UUID generation' - - apiGatewayDeployment: - Type: AWS::ApiGateway::Deployment - DependsOn: - - apiGatewayRootMethod - Properties: - RestApiId: !Ref apiGateway - StageName: !Ref apiGatewayStageName - - apiGatewayRootMethod: - Type: AWS::ApiGateway::Method - Properties: - AuthorizationType: NONE # NOSONAR - HttpMethod: !Ref apiGatewayHTTPMethod - Integration: - IntegrationHttpMethod: POST - Type: AWS_PROXY - Uri: !Sub - - arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations - - lambdaArn: !GetAtt getUuidFunction.Arn - ResourceId: !GetAtt apiGateway.RootResourceId - RestApiId: !Ref apiGateway - - lambdaApiGatewayInvoke: - Type: AWS::Lambda::Permission - Properties: - Action: lambda:InvokeFunction - FunctionName: !GetAtt getUuidFunction.Arn - Principal: apigateway.amazonaws.com - SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${apiGateway}/${apiGatewayStageName}/*/ - - lambdaIAMRole: - Type: AWS::IAM::Role - Properties: - AssumeRolePolicyDocument: - Version: 2012-10-17 - Statement: - - Action: - - sts:AssumeRole - Effect: Allow - Principal: - Service: - - lambda.amazonaws.com - Policies: - - PolicyDocument: - Version: 2012-10-17 - Statement: - - Action: - - logs:CreateLogGroup - - logs:CreateLogStream - - logs:PutLogEvents - Effect: Allow - Resource: - - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${lambdaFunctionName}:* - PolicyName: lambda - - lambdaLogGroup: - Type: AWS::Logs::LogGroup - Properties: - LogGroupName: !Sub /aws/lambda/${lambdaFunctionName} - RetentionInDays: 1 - -Outputs: - UuidWebEndpoint: - Description: 'API Gateway endpoint URL for UUID endpoint' - Value: !Sub 'https://${apiGateway}.execute-api.${AWS::Region}.amazonaws.com/dev/' diff --git a/examples/sam/tests/sam.test.ts b/examples/sam/tests/sam.test.ts deleted file mode 100644 index 988753f694..0000000000 --- a/examples/sam/tests/sam.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { APIGatewayProxyEvent, Context } from 'aws-lambda'; -import { handler as getAllItemsHandler } from '../src/get-all-items'; -import { handler as getByIdHandler } from '../src/get-by-id'; -import { handler as putItemHandler } from '../src/put-item'; - -test('getAllItemsHandler function imports & throws correctly', () => { - expect( - getAllItemsHandler({} as APIGatewayProxyEvent, {} as Context) - ).rejects.toThrow( - 'getAllItems only accepts GET method, you tried: undefined' - ); -}); - -test('getByIdHandler function imports & throws correctly', () => { - expect( - getByIdHandler({} as APIGatewayProxyEvent, {} as Context) - ).rejects.toThrow('getById only accepts GET method, you tried: undefined'); -}); - -test('putItemHandler function imports & throws correctly', () => { - expect( - putItemHandler({} as APIGatewayProxyEvent, {} as Context) - ).rejects.toThrow('putItem only accepts POST method, you tried: undefined'); -}); diff --git a/examples/sam/tests/tsconfig.json b/examples/sam/tests/tsconfig.json deleted file mode 100644 index 03963f6b04..0000000000 --- a/examples/sam/tests/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "rootDir": "../", - "noEmit": true - }, - "include": [ - "../src/**/*", - "./**/*", - ] -} \ No newline at end of file diff --git a/examples/sam/tsconfig.json b/examples/sam/tsconfig.json deleted file mode 100644 index 9bb600c308..0000000000 --- a/examples/sam/tsconfig.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "compilerOptions": { - "experimentalDecorators": true, - "noImplicitAny": true, - "target": "ES2020", - "module": "commonjs", - "declaration": true, - "declarationMap": true, - "outDir": "lib", - "removeComments": false, - "strict": true, - "inlineSourceMap": true, - "moduleResolution": "node", - "resolveJsonModule": true, - "pretty": true, - "esModuleInterop": true, - "allowJs": true - }, - "exclude": [ - "./node_modules" - ], - "watchOptions": { - "watchFile": "useFsEvents", - "watchDirectory": "useFsEvents", - "fallbackPolling": "dynamicPriority" - }, - "lib": [ - "ES2020" - ], - "types": [ - "node" - ] -} \ No newline at end of file diff --git a/lerna.json b/lerna.json index a8de03dfe1..89dd0d235d 100644 --- a/lerna.json +++ b/lerna.json @@ -9,8 +9,7 @@ "packages/batch", "packages/testing", "packages/jmespath", - "examples/cdk", - "examples/sam", + "examples/app", "layers", "docs/snippets" ], diff --git a/package-lock.json b/package-lock.json index 016900181f..0f89373cd4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,8 +20,7 @@ "packages/jmespath", "docs/snippets", "layers", - "examples/cdk", - "examples/sam" + "examples/app" ], "devDependencies": { "@middy/core": "^4.7.0", @@ -69,28 +68,20 @@ "hashi-vault-js": "^0.4.14" } }, - "examples/cdk": { - "name": "cdk-sample", + "examples/app": { + "name": "powertools-sample-app", "version": "2.0.3", "license": "MIT-0", "dependencies": { - "@middy/core": "^4.7.0", - "aws-cdk-lib": "^2.133.0", - "construct": "^1.0.0", - "phin": "^3.7.0", - "source-map-support": "^0.5.21" - }, - "bin": { - "cdk-app": "bin/cdk-app.js" - }, - "devDependencies": { - "@aws-lambda-powertools/commons": "^2.0.3", + "@aws-lambda-powertools/batch": "^2.0.3", + "@aws-lambda-powertools/idempotency": "^2.0.3", "@aws-lambda-powertools/logger": "^2.0.3", "@aws-lambda-powertools/metrics": "^2.0.3", "@aws-lambda-powertools/parameters": "^2.0.3", "@aws-lambda-powertools/tracer": "^2.0.3", "@aws-sdk/client-ssm": "^3.535.0", "@aws-sdk/lib-dynamodb": "^3.538.0", + "@middy/core": "^4.7.0", "@types/aws-lambda": "^8.10.136", "@types/jest": "^29.5.12", "@types/node": "20.11.30", @@ -101,32 +92,28 @@ "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "typescript": "^5.4.3" - } - }, - "examples/sam": { - "name": "sam-example", - "version": "2.0.3", - "license": "MIT-0", - "dependencies": { - "@aws-lambda-powertools/logger": "^2.0.3", - "@aws-lambda-powertools/metrics": "^2.0.3", - "@aws-lambda-powertools/parameters": "^2.0.3", - "@aws-lambda-powertools/tracer": "^2.0.3", - "@middy/core": "^4.7.0", - "phin": "^3.7.0" }, "devDependencies": { - "@aws-sdk/client-dynamodb": "^3.538.0", - "@aws-sdk/client-ssm": "^3.535.0", - "@aws-sdk/lib-dynamodb": "^3.538.0", "@types/aws-lambda": "^8.10.136", "@types/jest": "^29.5.12", - "@types/node": "^20.11.30", - "esbuild": "^0.20.2", + "@types/node": "20.11.28", + "aws-cdk": "^2.133.0", + "aws-cdk-lib": "^2.133.0", + "constructs": "^10.3.0", "jest": "^29.7.0", + "source-map-support": "^0.5.21", "ts-jest": "^29.1.2", - "ts-node": "^10.9.2", - "typescript": "^5.4.3" + "tsx": "^4.7.1", + "typescript": "^5.4.2" + } + }, + "examples/app/node_modules/@types/node": { + "version": "20.11.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.28.tgz", + "integrity": "sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" } }, "layers": { @@ -426,7 +413,6 @@ "version": "3.538.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.538.0.tgz", "integrity": "sha512-5OAc3AdcAlfzWLOJtIc0vBm2o9VHUyrmOayN+/OcQVvaOWCcGHwqanYVoEmqNaMKiAc5DKV1uARBj5MDLHyhqA==", - "dev": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -480,7 +466,6 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "dev": true, "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -613,7 +598,6 @@ "version": "3.535.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.535.0.tgz", "integrity": "sha512-W2fOV3LJeHLRBVVioNtBx4cCdyNCM66TfHBi/oe4NZw5Hp58+w5W8N5sV2VVEGawigczt+pp83zGFftjj7OwOg==", - "dev": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -666,7 +650,6 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "dev": true, "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -1021,7 +1004,6 @@ "version": "3.535.0", "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.535.0.tgz", "integrity": "sha512-sPG2l00iVuporK9AmPWq4UBcJURs2RN+vKA8QLRQANmQS3WFHWHamvGltxCjK79izkeqri882V4XlFpZfWhemA==", - "dev": true, "dependencies": { "mnemonist": "0.38.3", "tslib": "^2.6.2" @@ -1045,7 +1027,6 @@ "version": "3.538.0", "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.538.0.tgz", "integrity": "sha512-v+FjXrAVy6Kx9iQx4JnOeQF4fEX7XhhFKC3Au48PHajFKv5t/fdZMkJm+2om77OzoVh6688vcnke4sirXW4YGA==", - "dev": true, "dependencies": { "@aws-sdk/util-dynamodb": "3.538.0", "@smithy/smithy-client": "^2.5.0", @@ -1063,7 +1044,6 @@ "version": "3.535.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.535.0.tgz", "integrity": "sha512-+EsqJB5A15RoTf0HxUdknF3hp+2WDg0HWc+QERUctzzYXy9l5LIQjmhQ96cWDyFttKmHE+4h6fjMZjJEeWOeYQ==", - "dev": true, "dependencies": { "@aws-sdk/endpoint-cache": "3.535.0", "@aws-sdk/types": "3.535.0", @@ -1205,7 +1185,6 @@ "version": "3.538.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.538.0.tgz", "integrity": "sha512-swCwUyd0BgpDhTytTsL0wGfEuqWvi4qFesRsI0GygTEMrY26tEzqBnWfFy3M3r5UuFszKKiv9lnuiJ0cBuIUFA==", - "dev": true, "dependencies": { "tslib": "^2.6.2" }, @@ -1935,7 +1914,6 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -1947,7 +1925,6 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -2940,7 +2917,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -2957,8 +2933,7 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.21", @@ -4863,26 +4838,22 @@ "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" }, "node_modules/@tufjs/canonical-json": { "version": "1.0.0", @@ -5079,9 +5050,9 @@ "dev": true }, "node_modules/@types/semver": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, "node_modules/@types/sinon": { @@ -5382,7 +5353,6 @@ "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -5403,7 +5373,6 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -5568,8 +5537,7 @@ "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" }, "node_modules/argparse": { "version": "2.0.1", @@ -6503,7 +6471,8 @@ "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true }, "node_modules/builtins": { "version": "5.0.1", @@ -6691,15 +6660,6 @@ } ] }, - "node_modules/cdk-sample": { - "resolved": "examples/cdk", - "link": true - }, - "node_modules/centra": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/centra/-/centra-2.6.0.tgz", - "integrity": "sha512-dgh+YleemrT8u85QL11Z6tYhegAs3MMxsaWAq/oXeAmYJ7VxL3SI9TZtnfaEvNDMAPolj25FXIb3S+HCI4wQaQ==" - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7085,14 +7045,6 @@ "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "dev": true }, - "node_modules/construct": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/construct/-/construct-1.0.0.tgz", - "integrity": "sha512-AGU/GtcAyDLIToOmzth6/dfynQq7EJuhY4t3nK6Z8aYU+k3/SHOouD1S40q2rALxZN8glMN07jdFsMdVUpomjQ==", - "engines": { - "node": "*" - } - }, "node_modules/constructs": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.3.0.tgz", @@ -7279,8 +7231,7 @@ "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, "node_modules/cross-spawn": { "version": "7.0.3", @@ -7489,7 +7440,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, "engines": { "node": ">=0.3.1" } @@ -11980,8 +11930,7 @@ "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" }, "node_modules/make-fetch-happen": { "version": "13.0.0", @@ -12476,7 +12425,6 @@ "version": "0.38.3", "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.3.tgz", "integrity": "sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==", - "dev": true, "dependencies": { "obliterator": "^1.6.1" } @@ -13359,8 +13307,7 @@ "node_modules/obliterator": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-1.6.1.tgz", - "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==", - "dev": true + "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==" }, "node_modules/once": { "version": "1.4.0", @@ -14036,17 +13983,6 @@ "node": ">=8" } }, - "node_modules/phin": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/phin/-/phin-3.7.0.tgz", - "integrity": "sha512-DqnVNrpYhKGBZppNKprD+UJylMeEKOZxHgPB+ZP6mGzf3uA2uox4Ep9tUm+rUc8WLIdHT3HcAE4X8fhwQA9JKg==", - "dependencies": { - "centra": "^2.6.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -14162,6 +14098,10 @@ "node": ">=8" } }, + "node_modules/powertools-sample-app": { + "resolved": "examples/app", + "link": true + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -14963,10 +14903,6 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, - "node_modules/sam-example": { - "resolved": "examples/sam", - "link": true - }, "node_modules/sax": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", @@ -15392,6 +15328,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -15400,6 +15337,7 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -16042,7 +15980,6 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -16119,6 +16056,445 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/tsx": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.1.tgz", + "integrity": "sha512-8d6VuibXHtlN5E3zFkgY8u4DX7Y3Z27zvvPKVmLon/D4AjuKzarkUBTLDBgj9iTQ0hg5xM7c/mYiRVM+HETf0g==", + "dev": true, + "dependencies": { + "esbuild": "~0.19.10", + "get-tsconfig": "^4.7.2" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/tsx/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/tuf-js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.1.7.tgz", @@ -16411,7 +16787,6 @@ "version": "5.4.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", - "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16587,8 +16962,7 @@ "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" }, "node_modules/v8-to-istanbul": { "version": "9.2.0", @@ -16956,7 +17330,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, "engines": { "node": ">=6" } diff --git a/package.json b/package.json index fc7180914e..e608b97e7c 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,7 @@ "packages/jmespath", "docs/snippets", "layers", - "examples/cdk", - "examples/sam" + "examples/app" ], "scripts": { "test": "npm t -ws", diff --git a/packages/commons/README.md b/packages/commons/README.md index 6a8f627f81..c60e624068 100644 --- a/packages/commons/README.md +++ b/packages/commons/README.md @@ -63,8 +63,7 @@ Or refer to the installation guide of each utility: ### Examples -* [CDK](https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/examples/cdk) -* [SAM](https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/examples/sam) +You can find examples of how to use Powertools for AWS Lambda (TypeScript) in the [examples](https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/examples/app) directory. The application is a simple REST API that can be deployed via either AWS CDK or AWS SAM. ### Demo applications diff --git a/packages/logger/README.md b/packages/logger/README.md index e457d14357..c9189b1d98 100644 --- a/packages/logger/README.md +++ b/packages/logger/README.md @@ -60,8 +60,7 @@ Or refer to the installation guide of each utility: ### Examples -* [CDK](https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/examples/cdk) -* [SAM](https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/examples/sam) +You can find examples of how to use Powertools for AWS Lambda (TypeScript) in the [examples](https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/examples/app) directory. The application is a simple REST API that can be deployed via either AWS CDK or AWS SAM. ### Demo applications diff --git a/packages/metrics/README.md b/packages/metrics/README.md index dcd1d01588..31743d3756 100644 --- a/packages/metrics/README.md +++ b/packages/metrics/README.md @@ -59,8 +59,7 @@ Or refer to the installation guide of each utility: ### Examples -* [CDK](https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/examples/cdk) -* [SAM](https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/examples/sam) +You can find examples of how to use Powertools for AWS Lambda (TypeScript) in the [examples](https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/examples/app) directory. The application is a simple REST API that can be deployed via either AWS CDK or AWS SAM. ### Demo applications diff --git a/packages/tracer/README.md b/packages/tracer/README.md index dcd1d01588..31743d3756 100644 --- a/packages/tracer/README.md +++ b/packages/tracer/README.md @@ -59,8 +59,7 @@ Or refer to the installation guide of each utility: ### Examples -* [CDK](https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/examples/cdk) -* [SAM](https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/examples/sam) +You can find examples of how to use Powertools for AWS Lambda (TypeScript) in the [examples](https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/examples/app) directory. The application is a simple REST API that can be deployed via either AWS CDK or AWS SAM. ### Demo applications