diff --git a/.github/scripts/get_pr_info.js b/.github/scripts/get_pr_info.js new file mode 100644 index 0000000000..ebb6a76d94 --- /dev/null +++ b/.github/scripts/get_pr_info.js @@ -0,0 +1,27 @@ +module.exports = async ({ github, context, core }) => { + const prNumber = process.env.PR_NUMBER; + + if (prNumber === "") { + core.setFailed(`No PR number was passed. Aborting`); + } + + try { + const { + data: { head, base }, + } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + }); + + core.setOutput("headRef", head.ref); + core.setOutput("headSHA", head.sha); + core.setOutput("baseRef", base.ref); + core.setOutput("baseSHA", base.sha); + } catch (error) { + core.setFailed( + `Unable to retrieve info from PR number ${prNumber}.\n\n Error details: ${error}` + ); + throw error; + } +}; diff --git a/.github/scripts/label_related_issue.js b/.github/scripts/label_related_issue.js index 06f7643696..94ec9c7998 100644 --- a/.github/scripts/label_related_issue.js +++ b/.github/scripts/label_related_issue.js @@ -39,25 +39,15 @@ module.exports = async ({github, context, core}) => { const { groups: {issue} } = isMatch; try { - core.info(`Auto-labeling related issue ${issue} for release`) - await github.rest.issues.addLabels({ + core.info(`Auto-labeling related issue ${issue} for release`); + return await github.rest.issues.addLabels({ issue_number: issue, owner: context.repo.owner, repo: context.repo.repo, - labels: [LABEL_PENDING_RELEASE] + labels: [LABEL_PENDING_RELEASE], }); } catch (error) { core.setFailed(`Is this issue number (${issue}) valid? Perhaps a discussion?`); throw new Error(error); } - - const { groups: {relatedIssueNumber} } = isMatch; - - core.info(`Auto-labeling related issue ${relatedIssueNumber} for release`); - return await github.rest.issues.addLabels({ - issue_number: relatedIssueNumber, - owner: context.repo.owner, - repo: context.repo.repo, - labels: [relatedIssueNumber] - }); -} \ No newline at end of file +} diff --git a/.github/workflows/make-release.yml b/.github/workflows/make-release.yml index 677b126602..0cec051608 100644 --- a/.github/workflows/make-release.yml +++ b/.github/workflows/make-release.yml @@ -47,11 +47,3 @@ jobs: git remote set-url origin https://x-access-token:${GH_TOKEN}@github.com/$GITHUB_REPOSITORY npx lerna version --conventional-commits --force-publish --yes npx lerna publish from-git --no-verify-access --yes - publish: - needs: publish-npm - uses: ./.github/workflows/reusable-publish-docs.yml - with: - workflow_origin: ${{ github.event.repository.full_name }} - isRelease: "true" - secrets: - token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish_layer.yaml b/.github/workflows/publish_layer.yaml index 913bd9dc4d..df95624540 100644 --- a/.github/workflows/publish_layer.yaml +++ b/.github/workflows/publish_layer.yaml @@ -14,7 +14,7 @@ on: required: true # Automatic trigger after release workflow_run: - workflows: ["release"] + workflows: ["Make Release"] types: - completed diff --git a/.github/workflows/run-e2e-tests.yml b/.github/workflows/run-e2e-tests.yml index e5c4c27b0a..23fde6dc1f 100644 --- a/.github/workflows/run-e2e-tests.yml +++ b/.github/workflows/run-e2e-tests.yml @@ -1,47 +1,19 @@ -name: run-e2e-tests +name: Run e2e Tests + on: - workflow_dispatch: {} + workflow_dispatch: + inputs: + prNumber: + description: "(Optional) PR Number. If you specify a value the value of the branch field will be ignored." + required: false + default: "" + jobs: - example-and-package-check: - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - name: "Checkout" - uses: actions/checkout@v3 - - name: "Use NodeJS 16" - uses: actions/setup-node@v3 - with: - node-version: 16 - - name: "Install npm@8.x" - run: npm i -g npm@next-8 - - name: "Install monorepo packages" - # This installs all the dependencies of ./packages/* - # See https://github.com/npm/cli/issues/4475 to see why --foreground-scripts - run: npm ci --foreground-scripts - - name: Install CDK example packages - # Since we are not managing the CDK examples with npm workspaces we install - # the dependencies in a separate step - working-directory: ./examples/cdk - run: npm ci - - name: "Setup SAM" - # We use an ad-hoc action so we can specify the SAM CLI version - uses: aws-actions/setup-sam@v2 - with: - version: 1.49.0 - - name: Install SAM example packages - # Since we are not managing the SAM examples with npm workspaces we install - # the dependencies in a separate step - working-directory: ./examples/sam - run: npm ci - - name: "Test packaging" - run: | - npm run lerna-package - cd examples/cdk - npm install ../../packages/**/dist/aws-lambda-powertools-* - npm run test - package-e2e-tests: + run-e2e-tests-on-utils: runs-on: ubuntu-latest + env: + NODE_ENV: dev + PR_NUMBER: ${{ inputs.prNumber }} permissions: id-token: write # needed to interact with GitHub's OIDC Token endpoint. contents: read @@ -51,28 +23,47 @@ jobs: version: [12, 14, 16] fail-fast: false steps: - - name: "Checkout" + - name: Checkout Repo uses: actions/checkout@v3 - - name: "Use NodeJS" + # If we pass a PR Number when triggering the workflow we will retrieve the PR info and get its headSHA + - name: Extract PR details + id: extract_PR_details + if: ${{ inputs.prNumber != '' }} + uses: actions/github-script@v6 + with: + script: | + const script = require('.github/scripts/get_pr_info.js'); + await script({github, context, core}); + # Only if a PR Number was passed and the headSHA of the PR extracted, + # we checkout the PR at that point in time + - name: Checkout PR code + if: ${{ inputs.prNumber != '' }} + uses: actions/checkout@v3 + with: + ref: ${{ steps.extract_PR_details.outputs.headSHA }} + - name: Setup NodeJS uses: actions/setup-node@v3 with: node-version: ${{ matrix.version }} - - name: "Install npm@8.x" + - name: Setup npm run: npm i -g npm@next-8 - - name: "Install monorepo packages" + - name: Install dependencies # This installs all the dependencies of ./packages/* # See https://github.com/npm/cli/issues/4475 to see why --foreground-scripts run: npm ci --foreground-scripts - - name: "Configure AWS credentials" + - name: Setup AWS credentials uses: aws-actions/configure-aws-credentials@v1.6.1 with: role-to-assume: ${{ secrets.AWS_ROLE_ARN_TO_ASSUME }} aws-region: eu-west-1 - - name: "Run packages integration tests" + - name: Run integration tests on utils run: | RUNTIME=nodejs${{ matrix.version }}x npm run test:e2e -w packages/${{ matrix.package }} layer-e2e-tests: runs-on: ubuntu-latest + env: + NODE_ENV: dev + PR_NUMBER: ${{ inputs.prNumber }} permissions: id-token: write # needed to interact with GitHub's OIDC Token endpoint. contents: read @@ -81,14 +72,30 @@ jobs: matrix: version: [12, 14, 16] steps: - - name: Checkout + - name: Checkout Repo + uses: actions/checkout@v3 + # If we pass a PR Number when triggering the workflow we will retrieve the PR info and get its headSHA + - name: Extract PR details + id: extract_PR_details + if: ${{ inputs.prNumber != '' }} + uses: actions/github-script@v6 + with: + script: | + const script = require('.github/scripts/get_pr_info.js'); + await script({github, context, core}); + # Only if a PR Number was passed and the headSHA of the PR extracted, + # we checkout the PR at that point in time + - name: Checkout PR code + if: ${{ inputs.prNumber != '' }} uses: actions/checkout@v3 - - name: Use NodeJS + with: + ref: ${{ steps.extract_PR_details.outputs.headSHA }} + - name: Setup NodeJS uses: actions/setup-node@v3 with: # Always use version 16 as we use TypeScript target es2020 node-version: 16 - - name: Install npm@8.x + - name: Setup npm run: npm i -g npm@next-8 - name: "Configure AWS credentials" uses: aws-actions/configure-aws-credentials@v1.6.1 @@ -115,9 +122,8 @@ jobs: if: steps.cache-node-modules.outputs.cache-hit == 'true' run: | npm run build -w packages/commons - - name: "Run layer integration tests" + - name: Run integration test on layers run: | npm ci --foreground-scripts RUNTIME=nodejs${{ matrix.version }}.x npm run test:e2e working-directory: layer-publisher - diff --git a/.husky/pre-commit b/.husky/pre-commit index 8527102f54..64ad72dea3 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -npm run lerna-lint +npm run lerna-lint-fix diff --git a/.husky/pre-push b/.husky/pre-push index 771caa1dda..78f6873145 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,5 +1,5 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -npm run lerna-lint +npm run lerna-lint-fix npm run test diff --git a/CHANGELOG.md b/CHANGELOG.md index fe1bc2ae3e..eaf2fd4a19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,33 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.3.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.2.1...v1.3.0) (2022-10-17) + + +### Bug Fixes + +* **all:** update version command to use lint-fix ([#1119](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1119)) ([6f14fb3](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/6f14fb3229882b1dd0c20d18c87a542993432da9)) +* captureColdStartMetric and throwOnEmptyMetrics when set to false was interpreted as true ([#1090](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1090)) ([127aad4](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/127aad4698412d72368c093812dd4034839119ca)) +* captureMethod correctly detect method name when used with external decorators ([#1109](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1109)) ([a574406](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/a574406134b65c17f56dfb3d3130aa237ece4160)) +* **logger:** wait for decorated method return before clearing out state ([#1087](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1087)) ([133ed3c](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/133ed3c31ce1d99eb8f427f54721896781438ef7)) +* ts-node version for layer-publisher ([#1112](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1112)) ([ee243de](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/ee243dea0b9268ed793df19f0b04e680f68e41a6)) + + +### Features + +* **idempotency:** create initial class structure for function idempotency ([#1086](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1086)) ([06fbaae](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/06fbaae4db3825557aa84d40372a53422e42840d)) +* publish lib as Lambda Layer ([#1095](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1095)) ([83f6efb](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/83f6efba1db32ba2dc8fff026e258b5de66783e0)) +* **tracer:** specify subsegment name when capturing class method ([#1092](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1092)) ([d4174eb](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/d4174eb7a894215e2d37f306016429de3bde8029)) + + +### Reverts + +* Revert "chore(release): v1.3.0 [skip ci]" ([237b99f](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/237b99f9f6eff5e6e26d779603cf13cd4422c156)) + + + + + ## [1.2.1](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.2.0...v1.2.1) (2022-08-25) **Note:** Version bump only for package aws-lambda-powertools-typescript diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bcd69bf3e2..1107ef31bf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,7 +71,7 @@ The following tools need to be installed on your system prior to starting workin - [AWS SAM CLI >= 1.49.0](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) - AWS SAM CLI is a command line interface for AWS Serverless Application Model (SAM), it's used in one of the examples, and it's part of the pre-push hook. - [Docker](https://docs.docker.com/get-docker/) - - If you are not planning on making changes to the documentation, you can skip this step. + - Docker is used to build documentation and Layer. First, [fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) the repository, and then run the following commands to clone and initialize the repository locally. diff --git a/docs/core/tracer.md b/docs/core/tracer.md index fe9c0c31fd..bba9442920 100644 --- a/docs/core/tracer.md +++ b/docs/core/tracer.md @@ -244,7 +244,7 @@ You can trace other Class methods using the `captureMethod` decorator or any arb class Lambda implements LambdaInterface { // Decorate your class method - @tracer.captureMethod() + @tracer.captureMethod() // (1) public getChargeId(): string { /* ... */ return 'foo bar'; @@ -256,10 +256,11 @@ You can trace other Class methods using the `captureMethod` decorator or any arb } const handlerClass = new Lambda(); - export const handler = handlerClass.handler.bind(handlerClass); // (1) + export const handler = handlerClass.handler.bind(handlerClass); // (2) ``` - 1. Binding your handler method allows your handler to access `this`. + 1. You can set a custom name for the subsegment by passing `subSegmentName` to the decorator, like: `@tracer.captureMethod({ subSegmentName: '### myCustomMethod' })`. + 2. Binding your handler method allows your handler to access `this`. === "Manual" diff --git a/docs/index.md b/docs/index.md index 4cff31bcb7..17b5a5f39d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -28,6 +28,233 @@ Core utilities such as Tracer, Logger, Metrics, and Event Handler will be availa ## Installation +You can use Powertools through [AWS Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-concepts.html#gettingstarted-concepts-layer) or install it as your dependency via NPM: + +* **Lambda Layer**: [**arn:aws:lambda:{region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:2**](#){: .copyMe}:clipboard: +* **NPM**: **`npm install @aws-lambda-powertools/tracer @aws-lambda-powertools/metrics @aws-lambda-powertools/logger`** + +???+ hint "Support this project by using Lambda Layers :heart:" + Lambda Layers allow us to understand who uses this library in a non-intrusive way. This helps us justify and gain future investments for other Lambda Powertools languages. + + When using Layers, you can add Lambda Powertools as a dev dependency to not impact the development process. + + +### Lambda Layer + +[Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html){target="_blank"} is a .zip file archive that can contain additional code, pre-packaged dependencies, data, or configuration files. Layers promote code sharing and separation of responsibilities so that you can iterate faster on writing business logic. + +You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https://docs.aws.amazon.com/lambda/latest/dg/invocation-layers.html#invocation-layers-using){target="_blank"}, or your preferred deployment framework. + +??? note "Note: Expand to copy any regional Lambda Layer ARN" + + | Region | Layer ARN + |--------------------------- | --------------------------- + | `us-east-1` | [arn:aws:lambda:us-east-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:2](#){: .copyMe}:clipboard: + | `us-east-2` | [arn:aws:lambda:us-east-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:2](#){: .copyMe}:clipboard: + | `us-west-1` | [arn:aws:lambda:us-west-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:2](#){: .copyMe}:clipboard: + | `us-west-2` | [arn:aws:lambda:us-west-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:2](#){: .copyMe}:clipboard: + | `ap-south-1` | [arn:aws:lambda:ap-south-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:2](#){: .copyMe}:clipboard: + | `ap-northeast-1` | [arn:aws:lambda:ap-northeast-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:2](#){: .copyMe}:clipboard: + | `ap-northeast-2` | [arn:aws:lambda:ap-northeast-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:2](#){: .copyMe}:clipboard: + | `ap-northeast-3` | [arn:aws:lambda:ap-northeast-3:094274105915:layer:AWSLambdaPowertoolsTypeScript:2](#){: .copyMe}:clipboard: + | `ap-southeast-1` | [arn:aws:lambda:ap-southeast-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:2](#){: .copyMe}:clipboard: + | `ap-southeast-2` | [arn:aws:lambda:ap-southeast-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:2](#){: .copyMe}:clipboard: + | `eu-central-1` | [arn:aws:lambda:eu-central-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:2](#){: .copyMe}:clipboard: + | `eu-west-1` | [arn:aws:lambda:eu-west-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:2](#){: .copyMe}:clipboard: + | `eu-west-2` | [arn:aws:lambda:eu-west-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:2](#){: .copyMe}:clipboard: + | `eu-west-3` | [arn:aws:lambda:eu-west-3:094274105915:layer:AWSLambdaPowertoolsTypeScript:2](#){: .copyMe}:clipboard: + | `eu-north-1` | [arn:aws:lambda:eu-north-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:2](#){: .copyMe}:clipboard: + | `ca-central-1` | [arn:aws:lambda:ca-central-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:2](#){: .copyMe}:clipboard: + | `sa-east-1` | [arn:aws:lambda:sa-east-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:2](#){: .copyMe}:clipboard: + +??? question "Can't find our Lambda Layer for your preferred AWS region?" + You can use our [CDK Layer Construct](https://github.com/aws-samples/cdk-lambda-powertools-python-layer){target="_blank"}, or NPM like you normally would for any other library. + + Please do file a feature request with the region you'd want us to prioritize making our Lambda Layer available. + +=== "SAM" + + ```yaml hl_lines="5" + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Layers: + - !Sub arn:aws:lambda:${AWS::Region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:2 + ``` + +=== "Serverless framework" + + ```yaml hl_lines="5" + functions: + hello: + handler: lambda_function.lambda_handler + layers: + - arn:aws:lambda:${aws:region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:2 + ``` + +=== "CDK" + + ```typescript hl_lines="11 16" + import * as cdk from 'aws-cdk-lib'; + import { Construct } from 'constructs'; + import * as lambda from 'aws-cdk-lib/aws-lambda'; + export class SampleFunctionWithLayer extends Construct { + constructor(scope: Construct, id: string) { + super(scope, id); + // Create a Layer with AWS Lambda Powertools for TypeScript + const powertoolsLayer = lambda.LayerVersion.fromLayerVersionArn( + this, + 'PowertoolsLayer', + `arn:aws:lambda:${cdk.Stack.of(this).region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:2` + ); + new lambda.Function(this, 'Function', { + runtime: lambda.Runtime.NODEJS_16_X, + // Add the Layer to a Lambda function + layers: [powertoolsLayer], + code: lambda.Code.fromInline(` + const { Logger } = require('@aws-lambda-powertools/logger'); + const { Metrics } = require('@aws-lambda-powertools/metrics'); + const { Tracer } = require('@aws-lambda-powertools/tracer'); + const logger = new Logger({logLevel: 'DEBUG'}); + const metrics = new Metrics(); + const tracer = new Tracer(); + exports.handler = function(event, ctx) { + logger.debug("Hello World!"); + }`), + handler: 'index.handler', + }); + } + } + ``` + +=== "Terraform" + + ```terraform hl_lines="9 38" + terraform { + required_version = "~> 1.0.5" + required_providers { + aws = "~> 3.50.0" + } + } + provider "aws" { + region = "{region}" + } + resource "aws_iam_role" "iam_for_lambda" { + name = "iam_for_lambda" + assume_role_policy = < + ? Choose the runtime that you want to use: NodeJS + ? Do you want to configure advanced settings? Yes + ... + ? Do you want to enable Lambda layers for this function? Yes + ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:2 + ❯ amplify push -y + # Updating an existing function and add the layer + ❯ amplify update function + ? Select the Lambda function you want to update test2 + General information + - Name: + ? Which setting do you want to update? Lambda layers configuration + ? Do you want to enable Lambda layers for this function? Yes + ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:2 + ? Do you want to edit the local lambda function now? No + ``` + +=== "Get the Layer .zip contents" + Change `{region}` to your AWS region, e.g. `eu-west-1` + + ```bash title="AWS CLI" + aws lambda get-layer-version-by-arn --arn arn:aws:lambda:{region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:2 --region {region} + ``` + + The pre-signed URL to download this Lambda Layer will be within `Location` key. + +???+ warning "Warning: Limitations" + + Container Image deployment (OCI) or inline Lambda functions do not support Lambda Layers. + +If you use `esbuild` to bundle your code, make sure to exclude `@aws-lambda-powertools` from being bundled since the packages will be brought by the Layer: + +=== "SAM" (check the [doc](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-using-build-typescript.html) for more details) + + ```yaml hl_lines="5" + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + ... + Metadata: + # Manage esbuild properties + BuildMethod: esbuild + BuildProperties: + Minify: true + External: + - '@aws-lambda-powertools/commons' + - '@aws-lambda-powertools/logger' + - '@aws-lambda-powertools/metrics' + - '@aws-lambda-powertools/tracer' + ``` + +=== "Serverless framework (check the [doc](https://floydspace.github.io/serverless-esbuild/) for more details)" + + ```yaml hl_lines="5" + custom: + esbuild: + external: + - '@aws-lambda-powertools/commons' + - '@aws-lambda-powertools/logger' + - '@aws-lambda-powertools/metrics' + - '@aws-lambda-powertools/tracer' + ``` + +=== "CDK (check the [doc](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda_nodejs.BundlingOptions.html#externalmodules) for more details)" + + ```typescript hl_lines="11 16" + new awsLambdaNodejs.NodejsFunction(this, 'Function', { + ... + bundling: { + externalModules: [ + '@aws-lambda-powertools/commons', + '@aws-lambda-powertools/logger', + '@aws-lambda-powertools/metrics', + '@aws-lambda-powertools/tracer', + ], + } + }); + ``` + +### NPM Modules + The AWS Lambda Powertools for TypeScript utilities (which from here will be referred as Powertools) follow a modular approach, similar to the official [AWS SDK v3 for JavaScript](https://github.com/aws/aws-sdk-js-v3). Each TypeScript utility is installed as standalone NPM package. diff --git a/examples/cdk/.gitignore b/examples/cdk/.gitignore index f60797b6a9..7a7613a698 100644 --- a/examples/cdk/.gitignore +++ b/examples/cdk/.gitignore @@ -6,3 +6,4 @@ node_modules # CDK asset staging directory .cdk.staging cdk.out +lib \ No newline at end of file diff --git a/examples/cdk/CHANGELOG.md b/examples/cdk/CHANGELOG.md index 215c5f374c..228e2d67e2 100644 --- a/examples/cdk/CHANGELOG.md +++ b/examples/cdk/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.3.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.2.1...v1.3.0) (2022-10-17) + + +### Features + +* publish lib as Lambda Layer ([#1095](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1095)) ([83f6efb](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/83f6efba1db32ba2dc8fff026e258b5de66783e0)) + + +### Reverts + +* Revert "chore(release): v1.3.0 [skip ci]" ([237b99f](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/237b99f9f6eff5e6e26d779603cf13cd4422c156)) + + + + + ## [1.2.1](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.2.0...v1.2.1) (2022-08-25) **Note:** Version bump only for package cdk-app diff --git a/examples/cdk/README.md b/examples/cdk/README.md index 11d0b35ebc..a2e3eafd29 100644 --- a/examples/cdk/README.md +++ b/examples/cdk/README.md @@ -9,7 +9,7 @@ The example functions, located in the `src` folder, are invoked automatically, t ## Deploying the stack * Navigate to this location of the repo in your terminal (`examples/cdk`) - * `npm install` + * `npm ci` * `npm run cdk deploy --all --profile ` Note: Prior to deploying you may need to run `cdk bootstrap aws:/// --profile ` if you have not already bootstrapped your account for CDK. diff --git a/examples/cdk/package-lock.json b/examples/cdk/package-lock.json index bc755191f2..3714ac5a2d 100644 --- a/examples/cdk/package-lock.json +++ b/examples/cdk/package-lock.json @@ -1,21 +1,21 @@ { "name": "cdk-app", - "version": "1.2.1", + "version": "1.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cdk-app", - "version": "1.2.1", + "version": "1.3.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/logger": "^1.2.0", - "@aws-lambda-powertools/metrics": "^1.2.0", - "@aws-lambda-powertools/tracer": "^1.2.0", + "@aws-lambda-powertools/logger": "^1.2.1", + "@aws-lambda-powertools/metrics": "^1.2.1", + "@aws-lambda-powertools/tracer": "^1.2.1", "@aws-sdk/client-sts": "^3.53.0", "@middy/core": "^2.5.6", "@types/aws-lambda": "^8.10.86", - "aws-cdk-lib": "2.27.0", + "aws-cdk-lib": "^2.0.0", "aws-sdk": "^2.1088.0", "constructs": "^10.0.82", "source-map-support": "^0.5.16" @@ -123,35 +123,35 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@aws-lambda-powertools/commons": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.2.0.tgz", - "integrity": "sha512-0cEFA4yy51xkcanzXwO+NWmrK+pKLuO4l/HnWT4Fz2CS0clkuw2cPKoPMS9lhaV0EhAHOpdVASn26qh+9t8HAw==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.2.1.tgz", + "integrity": "sha512-wHdAgzXQfRqcm6kIuxrQjKL8x02sVTMc7rcMJPkHU1DsGNL7Z3g0H+tkrlmFimGkRqPv724J2OqNdEvBJKaoMQ==" }, "node_modules/@aws-lambda-powertools/logger": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.2.0.tgz", - "integrity": "sha512-4IzHzpwGl0beT8ZOC/YyqIZCmxu/YY66MuITVS1jr0ajPiSvOudoAH62EkBMq/txws1OAYaAiJMssQYTdKAtKg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.2.1.tgz", + "integrity": "sha512-Yb9uOi49h7yOut/NujnHjTd+sP4nBMsnuxMIsNtKvBFZ2ftaPlGAfqOdevqC3ImcIXlyiypxXd1gQmEMq3uJ8Q==", "dependencies": { - "@aws-lambda-powertools/commons": "^1.2.0", + "@aws-lambda-powertools/commons": "^1.2.1", "lodash.clonedeep": "^4.5.0", "lodash.merge": "^4.6.2", "lodash.pickby": "^4.6.0" } }, "node_modules/@aws-lambda-powertools/metrics": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.2.0.tgz", - "integrity": "sha512-hHzKYovVtuGO5zDV2IYwAAA3kmM+x3v21FgM4ktc2VhI87TtlNVM5W70IdynBJ7dH0VZkSwrNy7dF8cpd66tfA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.2.1.tgz", + "integrity": "sha512-X0B0rkepFoeiq+l5AQpnOR+qT+Fr6tzc22RxhPc6IB3Lz+QNdory4i+w9ToSUEDrS0Lls77hlHNoEHeuW7H12w==", "dependencies": { - "@aws-lambda-powertools/commons": "^1.2.0" + "@aws-lambda-powertools/commons": "^1.2.1" } }, "node_modules/@aws-lambda-powertools/tracer": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.2.0.tgz", - "integrity": "sha512-8sBlFw2gO+Zu9B/2SD2C1qrLBCQkJ5/+4LE/z1YUqUPwiWrf9CKvUfezcS1gx2jpU67mZb7Kc6Zf0GWKF13+hg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.2.1.tgz", + "integrity": "sha512-OPhb887RabgKYgKS2B5sAu+8o5r9VuLoz3sNyFsIp1GyCue4aswAevuGRUo0sJkJlhHvi1beTh2/QqQQzWd5iA==", "dependencies": { - "@aws-lambda-powertools/commons": "^1.2.0", + "@aws-lambda-powertools/commons": "^1.2.1", "aws-xray-sdk-core": "^3.3.6" } }, @@ -2264,9 +2264,9 @@ "integrity": "sha512-EFGCRj4kLX1dHv1cDzTk+xbjBFj1GnJDpui52YmEcxxHHEWjYyT6l51U7n6WQ28osZH4S9gSybxe56Vm7vB61Q==" }, "node_modules/aws-cdk": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.15.0.tgz", - "integrity": "sha512-MtMkebXK+rSkT1cLwRr6yFjwpbHcJq5TE0XpJm7qFVpeiLAWZR+P2gAvCayxjaI+XUEGqGXnSfiAgDSm+IlX5g==", + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.27.0.tgz", + "integrity": "sha512-TAKSP4ViFqj+jktFwJl4IE4jZbQCsH32t7L/VcGY71VZV9VqvuzkjF4gGx+hrES+A/dFBtLmPXsumyJu5y0elQ==", "dev": true, "bin": { "cdk": "bin/cdk" @@ -6731,35 +6731,35 @@ } }, "@aws-lambda-powertools/commons": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.2.0.tgz", - "integrity": "sha512-0cEFA4yy51xkcanzXwO+NWmrK+pKLuO4l/HnWT4Fz2CS0clkuw2cPKoPMS9lhaV0EhAHOpdVASn26qh+9t8HAw==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.2.1.tgz", + "integrity": "sha512-wHdAgzXQfRqcm6kIuxrQjKL8x02sVTMc7rcMJPkHU1DsGNL7Z3g0H+tkrlmFimGkRqPv724J2OqNdEvBJKaoMQ==" }, "@aws-lambda-powertools/logger": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.2.0.tgz", - "integrity": "sha512-4IzHzpwGl0beT8ZOC/YyqIZCmxu/YY66MuITVS1jr0ajPiSvOudoAH62EkBMq/txws1OAYaAiJMssQYTdKAtKg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.2.1.tgz", + "integrity": "sha512-Yb9uOi49h7yOut/NujnHjTd+sP4nBMsnuxMIsNtKvBFZ2ftaPlGAfqOdevqC3ImcIXlyiypxXd1gQmEMq3uJ8Q==", "requires": { - "@aws-lambda-powertools/commons": "^1.2.0", + "@aws-lambda-powertools/commons": "^1.2.1", "lodash.clonedeep": "^4.5.0", "lodash.merge": "^4.6.2", "lodash.pickby": "^4.6.0" } }, "@aws-lambda-powertools/metrics": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.2.0.tgz", - "integrity": "sha512-hHzKYovVtuGO5zDV2IYwAAA3kmM+x3v21FgM4ktc2VhI87TtlNVM5W70IdynBJ7dH0VZkSwrNy7dF8cpd66tfA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.2.1.tgz", + "integrity": "sha512-X0B0rkepFoeiq+l5AQpnOR+qT+Fr6tzc22RxhPc6IB3Lz+QNdory4i+w9ToSUEDrS0Lls77hlHNoEHeuW7H12w==", "requires": { - "@aws-lambda-powertools/commons": "^1.2.0" + "@aws-lambda-powertools/commons": "^1.2.1" } }, "@aws-lambda-powertools/tracer": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.2.0.tgz", - "integrity": "sha512-8sBlFw2gO+Zu9B/2SD2C1qrLBCQkJ5/+4LE/z1YUqUPwiWrf9CKvUfezcS1gx2jpU67mZb7Kc6Zf0GWKF13+hg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.2.1.tgz", + "integrity": "sha512-OPhb887RabgKYgKS2B5sAu+8o5r9VuLoz3sNyFsIp1GyCue4aswAevuGRUo0sJkJlhHvi1beTh2/QqQQzWd5iA==", "requires": { - "@aws-lambda-powertools/commons": "^1.2.0", + "@aws-lambda-powertools/commons": "^1.2.1", "aws-xray-sdk-core": "^3.3.6" } }, @@ -8488,9 +8488,9 @@ "integrity": "sha512-EFGCRj4kLX1dHv1cDzTk+xbjBFj1GnJDpui52YmEcxxHHEWjYyT6l51U7n6WQ28osZH4S9gSybxe56Vm7vB61Q==" }, "aws-cdk": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.15.0.tgz", - "integrity": "sha512-MtMkebXK+rSkT1cLwRr6yFjwpbHcJq5TE0XpJm7qFVpeiLAWZR+P2gAvCayxjaI+XUEGqGXnSfiAgDSm+IlX5g==", + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.27.0.tgz", + "integrity": "sha512-TAKSP4ViFqj+jktFwJl4IE4jZbQCsH32t7L/VcGY71VZV9VqvuzkjF4gGx+hrES+A/dFBtLmPXsumyJu5y0elQ==", "dev": true, "requires": { "fsevents": "2.3.2" diff --git a/examples/cdk/package.json b/examples/cdk/package.json index 32139c6b3e..9784881e78 100644 --- a/examples/cdk/package.json +++ b/examples/cdk/package.json @@ -1,6 +1,6 @@ { "name": "cdk-app", - "version": "1.2.1", + "version": "1.3.0", "author": { "name": "Amazon Web Services", "url": "https://aws.amazon.com" @@ -14,6 +14,8 @@ "build": "tsc --skipLibCheck", "watch": "tsc -w", "test": "npm run test:unit", + "lint": "eslint --ext .ts --no-error-on-unmatched-pattern src tests", + "lint-fix": "eslint --fix --ext .ts --fix --no-error-on-unmatched-pattern src tests", "package": "echo 'Not applicable'", "package-bundle": "echo 'Not applicable'", "test:unit": "npm run build && jest", @@ -32,13 +34,13 @@ "typescript": "^4.1.3" }, "dependencies": { - "@aws-lambda-powertools/logger": "^1.2.0", - "@aws-lambda-powertools/metrics": "^1.2.0", - "@aws-lambda-powertools/tracer": "^1.2.0", + "@aws-lambda-powertools/logger": "^1.2.1", + "@aws-lambda-powertools/metrics": "^1.2.1", + "@aws-lambda-powertools/tracer": "^1.2.1", "@aws-sdk/client-sts": "^3.53.0", "@middy/core": "^2.5.6", "@types/aws-lambda": "^8.10.86", - "aws-cdk-lib": "2.27.0", + "aws-cdk-lib": "^2.0.0", "aws-sdk": "^2.1088.0", "constructs": "^10.0.82", "source-map-support": "^0.5.16" diff --git a/examples/cdk/src/example-function.ts b/examples/cdk/src/example-function.ts index be9bbd97a7..a0a7cdd1ec 100644 --- a/examples/cdk/src/example-function.ts +++ b/examples/cdk/src/example-function.ts @@ -1,31 +1,54 @@ -import { custom_resources, aws_iam } from 'aws-cdk-lib'; +import { custom_resources, aws_iam, Stack } from 'aws-cdk-lib'; import { Events } from '@aws-lambda-powertools/commons'; import { Construct } from 'constructs'; import { NodejsFunction, NodejsFunctionProps } from 'aws-cdk-lib/aws-lambda-nodejs'; -import { Tracing, Runtime } from 'aws-cdk-lib/aws-lambda'; +import { Tracing, Runtime, LayerVersion } from 'aws-cdk-lib/aws-lambda'; interface ExampleFunctionProps { readonly functionName: string readonly tracingActive?: boolean + readonly useLayer?: boolean readonly invocations?: number readonly fnProps?: Partial } class ExampleFunction extends Construct { - public constructor(scope: Construct, id: string, props: ExampleFunctionProps) { super(scope, id); - const { functionName, tracingActive, invocations, fnProps } = Object.assign({ - tracingActive: false, - invocations: 2 - }, props); + const { functionName, tracingActive, invocations, fnProps } = Object.assign( + { + tracingActive: false, + invocations: 2, + }, + props + ); - const fn = new NodejsFunction(this, functionName, { + const fnOptions = { tracing: tracingActive ? Tracing.ACTIVE : Tracing.DISABLED, runtime: Runtime.NODEJS_16_X, - ...fnProps - }); + ...fnProps, + }; + + if (props.useLayer) { + fnOptions.bundling = { + externalModules: [ + '@aws-lambda-powertools/commons', + '@aws-lambda-powertools/logger', + '@aws-lambda-powertools/metrics', + '@aws-lambda-powertools/tracer', + ], + }; + fnOptions.layers = [ + LayerVersion.fromLayerVersionArn( + this, + 'AWSLambdaPowertoolsTypeScript', + `arn:aws:lambda:${Stack.of(this).region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:1` + ), + ]; + } + + const fn = new NodejsFunction(this, functionName, fnOptions); for (let i = 0; i < invocations; i++) { new custom_resources.AwsCustomResource(this, `Invoke-${functionName}-${i}`, { @@ -37,14 +60,12 @@ class ExampleFunction extends Construct { FunctionName: fn.functionName, InvocationType: 'RequestResponse', Payload: JSON.stringify(Events.Custom.CustomEvent), - } + }, }, policy: custom_resources.AwsCustomResourcePolicy.fromStatements([ new aws_iam.PolicyStatement({ effect: aws_iam.Effect.ALLOW, - resources: [ - fn.functionArn, - ], + resources: [fn.functionArn], actions: ['lambda:InvokeFunction'], }), ]), @@ -53,6 +74,4 @@ class ExampleFunction extends Construct { } } -export { - ExampleFunction -}; \ No newline at end of file +export { ExampleFunction }; diff --git a/examples/cdk/src/example-stack.ts b/examples/cdk/src/example-stack.ts index 067d7fa8d4..64e31aba96 100644 --- a/examples/cdk/src/example-stack.ts +++ b/examples/cdk/src/example-stack.ts @@ -1,5 +1,6 @@ import { Stack, StackProps } from 'aws-cdk-lib'; import { Construct } from 'constructs'; +import path from 'path'; import { ExampleFunction } from './example-function'; export class CdkAppStack extends Stack { @@ -66,5 +67,13 @@ export class CdkAppStack extends Stack { tracingActive: true, }); + new ExampleFunction(this, 'MyLayeredFunction', { + functionName: 'MyLayeredFunction', + tracingActive: true, + useLayer: true, + fnProps: { + entry: path.join(__dirname, 'example-function.MyFunction.ts') + } + }); } } diff --git a/examples/cdk/tests/cdk-app.test.ts b/examples/cdk/tests/cdk-app.test.ts index b758253c47..725ba886ba 100644 --- a/examples/cdk/tests/cdk-app.test.ts +++ b/examples/cdk/tests/cdk-app.test.ts @@ -5,5 +5,5 @@ 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', 13); + Template.fromStack(stack).resourceCountIs('AWS::Lambda::Function', 14); }); diff --git a/examples/sam/CHANGELOG.md b/examples/sam/CHANGELOG.md index f9e9ab5fe4..f9f717db62 100644 --- a/examples/sam/CHANGELOG.md +++ b/examples/sam/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.3.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.2.1...v1.3.0) (2022-10-17) + + +### Features + +* publish lib as Lambda Layer ([#1095](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1095)) ([83f6efb](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/83f6efba1db32ba2dc8fff026e258b5de66783e0)) + + +### Reverts + +* Revert "chore(release): v1.3.0 [skip ci]" ([237b99f](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/237b99f9f6eff5e6e26d779603cf13cd4422c156)) + + + + + ## [1.2.1](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.2.0...v1.2.1) (2022-08-25) **Note:** Version bump only for package powertools-typescript-sam-example diff --git a/examples/sam/README.md b/examples/sam/README.md index cf24fc6b7d..95d843ca23 100644 --- a/examples/sam/README.md +++ b/examples/sam/README.md @@ -150,6 +150,41 @@ 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 + +This example bundle all your dependencies in a single JS file thanks to esbuild but you can switch the AWSLambdaPowertoolsTypeScript Layer by: +1. specifying the right ARN in `Layers` list under the function's `Properties` +1. instructing esbuild to not bundle `@aws-lambda-powertools` under the function `Metadata/BuildProperties` + +Here is the diff of the current sam template leveraging `AWSLambdaPowertoolsTypeScript` layer: + +```diff +diff --git a/examples/sam/template.yaml b/examples/sa/template.yaml +index 18a5662b..d4e90b55 100644 +--- a/examples/sam/template.yaml ++++ b/examples/sam/template.yaml +@@ -99,6 +99,8 @@ Resources: + putItemFunction: + Type: AWS::Serverless::Function + Properties: ++ Layers: ++ - arn:aws:lambda:eu-west-3:094274105915:laye:AWSLambdaPowertoolsTypeScript:1 + Handler: src/put-item.putItemHandler + Description: A simple example includes a HTTP ost method to add one item to a DynamoDB table. + Policies: +@@ -124,6 +126,11 @@ Resources: + BuildMethod: esbuild + BuildProperties: + Minify: true ++ External: ++ - '@aws-lambda-powertools/commons' ++ - '@aws-lambda-powertools/logger' ++ - '@aws-lambda-powertools/metrics' ++ - '@aws-lambda-powertools/tracer' + Target: "es2020" + Sourcemap: true, + EntryPoints: +``` ## Cleanup diff --git a/examples/sam/package-lock.json b/examples/sam/package-lock.json index 2110927506..c1edda4323 100644 --- a/examples/sam/package-lock.json +++ b/examples/sam/package-lock.json @@ -1,17 +1,17 @@ { "name": "powertools-typescript-sam-example", - "version": "1.2.1", + "version": "1.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "powertools-typescript-sam-example", - "version": "1.2.1", + "version": "1.3.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/logger": "^1.2.0", - "@aws-lambda-powertools/metrics": "^1.2.0", - "@aws-lambda-powertools/tracer": "^1.2.0", + "@aws-lambda-powertools/logger": "^1.2.1", + "@aws-lambda-powertools/metrics": "^1.2.1", + "@aws-lambda-powertools/tracer": "^1.2.1", "aws-sdk": "^2.1122.0" }, "devDependencies": { @@ -40,35 +40,35 @@ } }, "node_modules/@aws-lambda-powertools/commons": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.2.0.tgz", - "integrity": "sha512-0cEFA4yy51xkcanzXwO+NWmrK+pKLuO4l/HnWT4Fz2CS0clkuw2cPKoPMS9lhaV0EhAHOpdVASn26qh+9t8HAw==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.2.1.tgz", + "integrity": "sha512-wHdAgzXQfRqcm6kIuxrQjKL8x02sVTMc7rcMJPkHU1DsGNL7Z3g0H+tkrlmFimGkRqPv724J2OqNdEvBJKaoMQ==" }, "node_modules/@aws-lambda-powertools/logger": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.2.0.tgz", - "integrity": "sha512-4IzHzpwGl0beT8ZOC/YyqIZCmxu/YY66MuITVS1jr0ajPiSvOudoAH62EkBMq/txws1OAYaAiJMssQYTdKAtKg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.2.1.tgz", + "integrity": "sha512-Yb9uOi49h7yOut/NujnHjTd+sP4nBMsnuxMIsNtKvBFZ2ftaPlGAfqOdevqC3ImcIXlyiypxXd1gQmEMq3uJ8Q==", "dependencies": { - "@aws-lambda-powertools/commons": "^1.2.0", + "@aws-lambda-powertools/commons": "^1.2.1", "lodash.clonedeep": "^4.5.0", "lodash.merge": "^4.6.2", "lodash.pickby": "^4.6.0" } }, "node_modules/@aws-lambda-powertools/metrics": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.2.0.tgz", - "integrity": "sha512-hHzKYovVtuGO5zDV2IYwAAA3kmM+x3v21FgM4ktc2VhI87TtlNVM5W70IdynBJ7dH0VZkSwrNy7dF8cpd66tfA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.2.1.tgz", + "integrity": "sha512-X0B0rkepFoeiq+l5AQpnOR+qT+Fr6tzc22RxhPc6IB3Lz+QNdory4i+w9ToSUEDrS0Lls77hlHNoEHeuW7H12w==", "dependencies": { - "@aws-lambda-powertools/commons": "^1.2.0" + "@aws-lambda-powertools/commons": "^1.2.1" } }, "node_modules/@aws-lambda-powertools/tracer": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.2.0.tgz", - "integrity": "sha512-8sBlFw2gO+Zu9B/2SD2C1qrLBCQkJ5/+4LE/z1YUqUPwiWrf9CKvUfezcS1gx2jpU67mZb7Kc6Zf0GWKF13+hg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.2.1.tgz", + "integrity": "sha512-OPhb887RabgKYgKS2B5sAu+8o5r9VuLoz3sNyFsIp1GyCue4aswAevuGRUo0sJkJlhHvi1beTh2/QqQQzWd5iA==", "dependencies": { - "@aws-lambda-powertools/commons": "^1.2.0", + "@aws-lambda-powertools/commons": "^1.2.1", "aws-xray-sdk-core": "^3.3.6" } }, @@ -5419,35 +5419,35 @@ } }, "@aws-lambda-powertools/commons": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.2.0.tgz", - "integrity": "sha512-0cEFA4yy51xkcanzXwO+NWmrK+pKLuO4l/HnWT4Fz2CS0clkuw2cPKoPMS9lhaV0EhAHOpdVASn26qh+9t8HAw==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.2.1.tgz", + "integrity": "sha512-wHdAgzXQfRqcm6kIuxrQjKL8x02sVTMc7rcMJPkHU1DsGNL7Z3g0H+tkrlmFimGkRqPv724J2OqNdEvBJKaoMQ==" }, "@aws-lambda-powertools/logger": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.2.0.tgz", - "integrity": "sha512-4IzHzpwGl0beT8ZOC/YyqIZCmxu/YY66MuITVS1jr0ajPiSvOudoAH62EkBMq/txws1OAYaAiJMssQYTdKAtKg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.2.1.tgz", + "integrity": "sha512-Yb9uOi49h7yOut/NujnHjTd+sP4nBMsnuxMIsNtKvBFZ2ftaPlGAfqOdevqC3ImcIXlyiypxXd1gQmEMq3uJ8Q==", "requires": { - "@aws-lambda-powertools/commons": "^1.2.0", + "@aws-lambda-powertools/commons": "^1.2.1", "lodash.clonedeep": "^4.5.0", "lodash.merge": "^4.6.2", "lodash.pickby": "^4.6.0" } }, "@aws-lambda-powertools/metrics": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.2.0.tgz", - "integrity": "sha512-hHzKYovVtuGO5zDV2IYwAAA3kmM+x3v21FgM4ktc2VhI87TtlNVM5W70IdynBJ7dH0VZkSwrNy7dF8cpd66tfA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.2.1.tgz", + "integrity": "sha512-X0B0rkepFoeiq+l5AQpnOR+qT+Fr6tzc22RxhPc6IB3Lz+QNdory4i+w9ToSUEDrS0Lls77hlHNoEHeuW7H12w==", "requires": { - "@aws-lambda-powertools/commons": "^1.2.0" + "@aws-lambda-powertools/commons": "^1.2.1" } }, "@aws-lambda-powertools/tracer": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.2.0.tgz", - "integrity": "sha512-8sBlFw2gO+Zu9B/2SD2C1qrLBCQkJ5/+4LE/z1YUqUPwiWrf9CKvUfezcS1gx2jpU67mZb7Kc6Zf0GWKF13+hg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.2.1.tgz", + "integrity": "sha512-OPhb887RabgKYgKS2B5sAu+8o5r9VuLoz3sNyFsIp1GyCue4aswAevuGRUo0sJkJlhHvi1beTh2/QqQQzWd5iA==", "requires": { - "@aws-lambda-powertools/commons": "^1.2.0", + "@aws-lambda-powertools/commons": "^1.2.1", "aws-xray-sdk-core": "^3.3.6" } }, diff --git a/examples/sam/package.json b/examples/sam/package.json index b365e2bdc0..55b0ea1dd9 100644 --- a/examples/sam/package.json +++ b/examples/sam/package.json @@ -1,6 +1,6 @@ { "name": "powertools-typescript-sam-example", - "version": "1.2.1", + "version": "1.3.0", "author": { "name": "Amazon Web Services", "url": "https://aws.amazon.com" @@ -10,6 +10,8 @@ "scripts": { "build": "sam build --beta-features", "test": "npm run test:unit", + "lint": "eslint --ext .ts --no-error-on-unmatched-pattern src tests", + "lint-fix": "eslint --fix --ext .ts --fix --no-error-on-unmatched-pattern src tests", "package": "echo 'Not applicable'", "package-bundle": "echo 'Not applicable'", "test:unit": "npm run build && jest", @@ -28,9 +30,9 @@ "typescript": "^4.1.3" }, "dependencies": { - "@aws-lambda-powertools/logger": "^1.2.0", - "@aws-lambda-powertools/metrics": "^1.2.0", - "@aws-lambda-powertools/tracer": "^1.2.0", + "@aws-lambda-powertools/logger": "^1.2.1", + "@aws-lambda-powertools/metrics": "^1.2.1", + "@aws-lambda-powertools/tracer": "^1.2.1", "aws-sdk": "^2.1122.0" } } diff --git a/layer-publisher/CHANGELOG.md b/layer-publisher/CHANGELOG.md index f5e2b49464..b3231c037c 100644 --- a/layer-publisher/CHANGELOG.md +++ b/layer-publisher/CHANGELOG.md @@ -3,6 +3,28 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.3.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.2.1...v1.3.0) (2022-10-17) + + +### Bug Fixes + +* ts-node version for layer-publisher ([#1112](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1112)) ([ee243de](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/ee243dea0b9268ed793df19f0b04e680f68e41a6)) + + +### Features + +* **idempotency:** create initial class structure for function idempotency ([#1086](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1086)) ([06fbaae](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/06fbaae4db3825557aa84d40372a53422e42840d)) +* publish lib as Lambda Layer ([#1095](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1095)) ([83f6efb](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/83f6efba1db32ba2dc8fff026e258b5de66783e0)) + + +### Reverts + +* Revert "chore(release): v1.3.0 [skip ci]" ([237b99f](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/237b99f9f6eff5e6e26d779603cf13cd4422c156)) + + + + + ## [1.2.1](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.2.0...v1.2.1) (2022-08-25) **Note:** Version bump only for package layer-publisher diff --git a/layer-publisher/cdk-lambda-powertools-python-layer@0.0.0.jsii.tgz b/layer-publisher/cdk-lambda-powertools-python-layer@0.0.0.jsii.tgz deleted file mode 100644 index 2b823956d8..0000000000 Binary files a/layer-publisher/cdk-lambda-powertools-python-layer@0.0.0.jsii.tgz and /dev/null differ diff --git a/layer-publisher/package-lock.json b/layer-publisher/package-lock.json index c7a63d263a..12c0094d75 100644 --- a/layer-publisher/package-lock.json +++ b/layer-publisher/package-lock.json @@ -1,18 +1,18 @@ { "name": "layer-publisher", - "version": "1.2.1", + "version": "1.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "layer-publisher", - "version": "0.1.0", + "version": "1.2.1", "dependencies": { "aws-cdk-lib": "2.25.0", - "cdk-lambda-powertools-python-layer": "file:cdk-lambda-powertools-python-layer@0.0.0.jsii.tgz", "constructs": "^10.0.0", "if-node-version": "^1.1.1", - "source-map-support": "^0.5.16" + "source-map-support": "^0.5.16", + "ts-md5": "^1.3.1" }, "bin": { "layer-publisher": "bin/layer-publisher.js" @@ -25,8 +25,8 @@ "aws-cdk": "2.25.0", "jest": "^26.4.2", "ts-jest": "^26.2.0", - "ts-node": "^9.0.0", - "typescript": "~3.9.7" + "ts-node": "10.9.1", + "typescript": "4.8.3" } }, "node_modules/@ampproject/remapping": { @@ -756,6 +756,28 @@ "node": ">=0.1.95" } }, + "node_modules/@cspotcode/source-map-support": { + "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" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "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" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1082,6 +1104,30 @@ "node": ">= 6" } }, + "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 + }, + "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 + }, + "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 + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, "node_modules/@types/babel__core": { "version": "7.1.19", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", @@ -1930,16 +1976,6 @@ "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/cdk-lambda-powertools-python-layer": { - "version": "0.0.0", - "resolved": "file:cdk-lambda-powertools-python-layer@0.0.0.jsii.tgz", - "integrity": "sha512-PaFq1yEEQ6lHk+fPuuaOSF0W/RZk5NV85/qIoQM/0MzC/YNIFW09IqZ1/iKasXrVRYiItESIsd51vs+nvEDPSA==", - "license": "MIT-0", - "peerDependencies": { - "aws-cdk-lib": "^2.24.1", - "constructs": "^10.0.5" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -6310,30 +6346,64 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/ts-md5": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/ts-md5/-/ts-md5-1.3.1.tgz", + "integrity": "sha512-DiwiXfwvcTeZ5wCE0z+2A9EseZsztaiZtGrtSaY5JOD7ekPnR/GoIVD5gXZAlK9Na9Kvpo9Waz5rW64WKAWApg==", + "engines": { + "node": ">=12" + } + }, "node_modules/ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", - "dev": true, - "dependencies": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", "arg": "^4.1.0", "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", - "source-map-support": "^0.5.17", + "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" }, "bin": { "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", "ts-node-script": "dist/bin-script.js", "ts-node-transpile-only": "dist/bin-transpile.js", "ts-script": "dist/bin-script-deprecated.js" }, - "engines": { - "node": ">=10.0.0" - }, "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" } }, "node_modules/ts-node/node_modules/diff": { @@ -6388,9 +6458,9 @@ } }, "node_modules/typescript": { - "version": "3.9.10", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", - "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", + "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -6542,6 +6612,12 @@ "uuid": "dist/bin/uuid" } }, + "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 + }, "node_modules/v8-to-istanbul": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz", @@ -7346,6 +7422,27 @@ "minimist": "^1.2.0" } }, + "@cspotcode/source-map-support": { + "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, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } + } + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -7619,6 +7716,30 @@ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "dev": true }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "@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 + }, + "@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 + }, + "@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, "@types/babel__core": { "version": "7.1.19", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", @@ -8252,11 +8373,6 @@ "rsvp": "^4.8.4" } }, - "cdk-lambda-powertools-python-layer": { - "version": "file:cdk-lambda-powertools-python-layer@0.0.0.jsii.tgz", - "integrity": "sha512-PaFq1yEEQ6lHk+fPuuaOSF0W/RZk5NV85/qIoQM/0MzC/YNIFW09IqZ1/iKasXrVRYiItESIsd51vs+nvEDPSA==", - "requires": {} - }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -11677,20 +11793,38 @@ } } }, + "ts-md5": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/ts-md5/-/ts-md5-1.3.1.tgz", + "integrity": "sha512-DiwiXfwvcTeZ5wCE0z+2A9EseZsztaiZtGrtSaY5JOD7ekPnR/GoIVD5gXZAlK9Na9Kvpo9Waz5rW64WKAWApg==" + }, "ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", - "dev": true, - "requires": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", "arg": "^4.1.0", "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", - "source-map-support": "^0.5.17", + "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" }, "dependencies": { + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -11730,9 +11864,9 @@ } }, "typescript": { - "version": "3.9.10", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", - "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", + "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", "dev": true }, "union-value": { @@ -11839,6 +11973,12 @@ "dev": true, "optional": true }, + "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 + }, "v8-to-istanbul": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz", diff --git a/layer-publisher/package.json b/layer-publisher/package.json index 8de22d05a1..2aaae9b124 100644 --- a/layer-publisher/package.json +++ b/layer-publisher/package.json @@ -1,6 +1,6 @@ { "name": "layer-publisher", - "version": "1.2.1", + "version": "1.3.0", "bin": { "layer-publisher": "bin/layer-publisher.js" }, @@ -10,6 +10,8 @@ "test": "npm run test:unit", "cdk": "cdk", "package": "echo 'Not applicable'", + "lint": "eslint --ext .ts --no-error-on-unmatched-pattern src tests", + "lint-fix": "eslint --fix --ext .ts --fix --no-error-on-unmatched-pattern src tests", "test:unit": "if-node-version '>12' jest --testPathPattern=unit -u", "test:e2e": "if-node-version '>12' jest --testPathPattern=e2e" }, @@ -21,14 +23,14 @@ "aws-cdk": "2.25.0", "jest": "^26.4.2", "ts-jest": "^26.2.0", - "ts-node": "^9.0.0", - "typescript": "~3.9.7" + "ts-node": "10.9.1", + "typescript": "4.8.3" }, "dependencies": { "aws-cdk-lib": "2.25.0", - "cdk-lambda-powertools-python-layer": "file:cdk-lambda-powertools-python-layer@0.0.0.jsii.tgz", "constructs": "^10.0.0", "if-node-version": "^1.1.1", - "source-map-support": "^0.5.16" + "source-map-support": "^0.5.16", + "ts-md5": "^1.3.1" } } diff --git a/layer-publisher/src/layer-publisher-stack.ts b/layer-publisher/src/layer-publisher-stack.ts index ec4545733d..47708b30af 100644 --- a/layer-publisher/src/layer-publisher-stack.ts +++ b/layer-publisher/src/layer-publisher-stack.ts @@ -1,9 +1,9 @@ import { CfnOutput, RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib'; import { Construct } from 'constructs'; import * as lambda from 'aws-cdk-lib/aws-lambda'; -import { LambdaPowertoolsLayer } from 'cdk-lambda-powertools-python-layer'; import { StringParameter } from 'aws-cdk-lib/aws-ssm'; import { CfnLayerVersionPermission } from 'aws-cdk-lib/aws-lambda'; +import { PowerToolsTypeScriptLayer } from './powertools-typescript-layer'; export interface LayerPublisherStackProps extends StackProps { readonly layerName?: string @@ -16,9 +16,8 @@ export class LayerPublisherStack extends Stack { public constructor(scope: Construct, id: string, props: LayerPublisherStackProps) { super(scope, id, props); - this.lambdaLayerVersion = new LambdaPowertoolsLayer(this, 'LambdaPowertoolsLayer', { + this.lambdaLayerVersion = new PowerToolsTypeScriptLayer(this, 'LambdaPowertoolsLayer', { layerVersionName: props?.layerName, - runtimeFamily: lambda.RuntimeFamily.NODEJS, version: props?.powerToolsPackageVersion, }); diff --git a/layer-publisher/src/powertools-typescript-layer.ts b/layer-publisher/src/powertools-typescript-layer.ts new file mode 100644 index 0000000000..029925aa65 --- /dev/null +++ b/layer-publisher/src/powertools-typescript-layer.ts @@ -0,0 +1,62 @@ +import * as path from 'path'; +import { aws_lambda as lambda } from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import { execSync } from 'child_process'; +import { Md5 } from 'ts-md5'; + +export interface PowerToolsTypeScriptLayerProps { + /** + * The powertools package version from npm repository. + */ + readonly version?: string + + /** + * the name of the layer, will be randomised by cdk if empty + */ + readonly layerVersionName?: string +} + +export class PowerToolsTypeScriptLayer extends lambda.LayerVersion { + public constructor(scope: Construct, id: string, props?: PowerToolsTypeScriptLayerProps) { + const version = props?.version ?? 'latest'; + console.log(`publishing layer ${props?.layerVersionName} version : ${version}`); + + const commands = [ + 'mkdir nodejs', + 'cd nodejs', + 'npm init -y', + `npm install --save \ + @aws-lambda-powertools/commons@${version} \ + @aws-lambda-powertools/logger@${version} \ + @aws-lambda-powertools/metrics@${version} \ + @aws-lambda-powertools/tracer@${version}`, + 'rm package.json package-lock.json', + ]; + const commandJoined = commands.join(' && '); + + super(scope, id, { + layerVersionName: props?.layerVersionName, + description: `Lambda Powertools for TypeScript version ${props?.version}`, + compatibleRuntimes: [ lambda.Runtime.NODEJS_12_X, lambda.Runtime.NODEJS_14_X, lambda.Runtime.NODEJS_16_X ], + code: lambda.Code.fromAsset(path.join(__dirname, '.'), { + assetHash: Md5.hashStr(commandJoined), + bundling: { + image: lambda.Runtime.NODEJS_12_X.bundlingImage, + local: { + tryBundle(outputDir: string) { + try { + execSync('npm --version && zip --version'); + } catch { + return false; + } + + execSync(commandJoined, { cwd: outputDir }); + + return true; + }, + }, + }, + }), + }); + } +} diff --git a/layer-publisher/tests/e2e/happy-case.test.lambda.ts b/layer-publisher/tests/e2e/happy-case.test.lambda.ts index db91d2724b..f454713f79 100644 --- a/layer-publisher/tests/e2e/happy-case.test.lambda.ts +++ b/layer-publisher/tests/e2e/happy-case.test.lambda.ts @@ -31,7 +31,7 @@ exports.handler = function (_event: never, _ctx: unknown): void { if (packageJSON.version != process.env.POWERTOOLS_PACKAGE_VERSION) { throw new Error( - `Package version mismatch: \${packageJSON.version} != \${process.env.POWERTOOLS_PACKAGE_VERSION}` + `Package version mismatch: ${packageJSON.version} != ${process.env.POWERTOOLS_PACKAGE_VERSION}` ); } } catch (error) { diff --git a/layer-publisher/tests/unit/__snapshots__/layer-publisher.test.ts.snap b/layer-publisher/tests/unit/__snapshots__/layer-publisher.test.ts.snap index e8aaff9650..161eb78d35 100644 --- a/layer-publisher/tests/unit/__snapshots__/layer-publisher.test.ts.snap +++ b/layer-publisher/tests/unit/__snapshots__/layer-publisher.test.ts.snap @@ -32,11 +32,10 @@ Object { "S3Bucket": Object { "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", }, - "S3Key": "23ee10788554a52cf42e2930c2fb70b2d0260807f69479e9cef221a77d0cbc28.zip", + "S3Key": "c2f621503b147cecccf2e6cc6df420a8f11ee29ae042d2c1f523cf5db3f47ca2.zip", }, "Description": "Lambda Powertools for TypeScript version 1.0.1", "LayerName": "AWSLambdaPowertoolsTypeScript", - "LicenseInfo": "MIT-0", }, "Type": "AWS::Lambda::LayerVersion", "UpdateReplacePolicy": "Retain", diff --git a/lerna.json b/lerna.json index 86ac8ddca3..2502a624f1 100644 --- a/lerna.json +++ b/lerna.json @@ -8,7 +8,7 @@ "examples/sam", "layer-publisher" ], - "version": "1.2.1", + "version": "1.3.0", "npmClient": "npm", "message": "chore(release): %s [skip ci]" } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d80443a22c..cda483229d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15040,9 +15040,9 @@ } }, "node_modules/vm2": { - "version": "3.9.9", - "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.9.tgz", - "integrity": "sha512-xwTm7NLh/uOjARRBs8/95H0e8fT3Ukw5D/JJWhxMbhKzNh1Nu981jQKvkep9iKYNxzlVrdzD0mlBGkDKZWprlw==", + "version": "3.9.11", + "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.11.tgz", + "integrity": "sha512-PFG8iJRSjvvBdisowQ7iVF580DXb1uCIiGaXgm7tynMR1uTBlv7UJlB1zdv5KJ+Tmq1f0Upnj3fayoEOPpCBKg==", "dev": true, "dependencies": { "acorn": "^8.7.0", @@ -15542,15 +15542,15 @@ }, "packages/commons": { "name": "@aws-lambda-powertools/commons", - "version": "0.11.1-rc.0", + "version": "1.2.1", "license": "MIT-0" }, "packages/logger": { "name": "@aws-lambda-powertools/logger", - "version": "0.11.1-rc.0", + "version": "1.2.1", "license": "MIT", "dependencies": { - "@aws-lambda-powertools/commons": "^0.11.1-rc.0", + "@aws-lambda-powertools/commons": "^1.2.1", "lodash.clonedeep": "^4.5.0", "lodash.merge": "^4.6.2", "lodash.pickby": "^4.6.0" @@ -15563,10 +15563,10 @@ }, "packages/metrics": { "name": "@aws-lambda-powertools/metrics", - "version": "0.11.1-rc.0", + "version": "1.2.1", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "^0.11.1-rc.0" + "@aws-lambda-powertools/commons": "^1.2.1" }, "devDependencies": { "@types/promise-retry": "^1.1.3", @@ -15575,10 +15575,10 @@ }, "packages/tracer": { "name": "@aws-lambda-powertools/tracer", - "version": "0.11.1-rc.0", + "version": "1.2.1", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "^0.11.1-rc.0", + "@aws-lambda-powertools/commons": "^1.2.1", "aws-xray-sdk-core": "^3.3.6" }, "devDependencies": { @@ -15805,7 +15805,7 @@ "@aws-lambda-powertools/logger": { "version": "file:packages/logger", "requires": { - "@aws-lambda-powertools/commons": "^0.11.1-rc.0", + "@aws-lambda-powertools/commons": "^1.2.1", "@types/lodash.clonedeep": "^4.5.7", "@types/lodash.merge": "^4.6.7", "@types/lodash.pickby": "^4.6.7", @@ -15817,7 +15817,7 @@ "@aws-lambda-powertools/metrics": { "version": "file:packages/metrics", "requires": { - "@aws-lambda-powertools/commons": "^0.11.1-rc.0", + "@aws-lambda-powertools/commons": "^1.2.1", "@types/promise-retry": "^1.1.3", "promise-retry": "^2.0.1" } @@ -15825,7 +15825,7 @@ "@aws-lambda-powertools/tracer": { "version": "file:packages/tracer", "requires": { - "@aws-lambda-powertools/commons": "^0.11.1-rc.0", + "@aws-lambda-powertools/commons": "^1.2.1", "@aws-sdk/client-dynamodb": "^3.100.0", "@types/promise-retry": "^1.1.3", "aws-xray-sdk-core": "^3.3.6", @@ -27409,9 +27409,9 @@ } }, "vm2": { - "version": "3.9.9", - "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.9.tgz", - "integrity": "sha512-xwTm7NLh/uOjARRBs8/95H0e8fT3Ukw5D/JJWhxMbhKzNh1Nu981jQKvkep9iKYNxzlVrdzD0mlBGkDKZWprlw==", + "version": "3.9.11", + "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.11.tgz", + "integrity": "sha512-PFG8iJRSjvvBdisowQ7iVF580DXb1uCIiGaXgm7tynMR1uTBlv7UJlB1zdv5KJ+Tmq1f0Upnj3fayoEOPpCBKg==", "dev": true, "requires": { "acorn": "^8.7.0", diff --git a/package.json b/package.json index 19329e669a..211dcd6919 100644 --- a/package.json +++ b/package.json @@ -22,12 +22,12 @@ "lerna-package": "lerna exec -- npm run package", "lerna-package-bundle": "lerna exec -- npm run package-bundle", "lerna-build": "lerna exec -- tsc", - "lerna-lint": "lerna exec -- eslint \"./{src,tests}/**/*.ts ./src/*.ts\"", - "lerna-format": "lerna exec -- eslint --fix \"./{src,tests}/**/*.ts ./src/*.ts\"", + "lerna-lint": "lerna exec -- npm run lint", + "lerna-lint-fix": "lerna exec -- npm run lint-fix", "lerna-prepare": "lerna exec -- npm run build", "lerna-prepublishOnly": "lerna exec -- npm test && lerna exec -- npm run lint", "lerna-preversion": "lerna exec -- npm run lint", - "lerna-version": "lerna exec -- npm run format && git add -A src", + "lerna-version": "lerna exec -- npm run lint-fix && git add -A src", "postversion": "git push && git push --tags", "docs-website-build-run": "npm run docs-buildDockerImage && npm run docs-runLocalDocker", "docs-buildDockerImage": "docker build -t powertool-typescript/docs ./docs/", diff --git a/packages/commons/CHANGELOG.md b/packages/commons/CHANGELOG.md index fba139c0da..a3e0a202c8 100644 --- a/packages/commons/CHANGELOG.md +++ b/packages/commons/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.3.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.2.1...v1.3.0) (2022-10-17) + + +### Bug Fixes + +* **all:** update version command to use lint-fix ([#1119](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1119)) ([6f14fb3](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/6f14fb3229882b1dd0c20d18c87a542993432da9)) + + +### Reverts + +* Revert "chore(release): v1.3.0 [skip ci]" ([237b99f](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/237b99f9f6eff5e6e26d779603cf13cd4422c156)) + + + + + ## [1.2.1](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.2.0...v1.2.1) (2022-08-25) **Note:** Version bump only for package @aws-lambda-powertools/commons diff --git a/packages/commons/package.json b/packages/commons/package.json index 534083afa9..4eb20766e3 100644 --- a/packages/commons/package.json +++ b/packages/commons/package.json @@ -1,6 +1,6 @@ { "name": "@aws-lambda-powertools/commons", - "version": "1.2.1", + "version": "1.3.0", "description": "A shared utility package for AWS Lambda Powertools for TypeScript libraries", "author": { "name": "Amazon Web Services", @@ -16,14 +16,14 @@ "test:e2e": "echo 'Not Applicable'", "watch": "jest --watch", "build": "tsc", - "lint": "eslint --ext .ts --fix --no-error-on-unmatched-pattern src tests", - "format": "eslint --fix --ext .ts --fix --no-error-on-unmatched-pattern src tests", + "lint": "eslint --ext .ts --no-error-on-unmatched-pattern src tests", + "lint-fix": "eslint --fix --ext .ts --no-error-on-unmatched-pattern src tests", "package": "mkdir -p dist/ && npm pack && mv *.tgz dist/", "package-bundle": "../../package-bundler.sh commons-bundle ./dist", "prepare": "npm run build", "prepublishOnly": "npm test && npm run lint", "preversion": "npm run lint", - "version": "npm run format && git add -A src", + "version": "npm run lint-fix && git add -A src", "postversion": "git push && git push --tags" }, "homepage": "https://github.com/awslabs/aws-lambda-powertools-typescript/tree/master/packages/metrics#readme", diff --git a/packages/idempotency/LICENSE-THIRD-PARTY b/packages/idempotency/LICENSE-THIRD-PARTY new file mode 100644 index 0000000000..27fdb6b8bf --- /dev/null +++ b/packages/idempotency/LICENSE-THIRD-PARTY @@ -0,0 +1,83 @@ +@aws-lambda-powertools/commons +0.0.2 +license: MIT* +authors: Amazon Web Services + +****************************** + +@types/aws-lambda +8.10.87 + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +****************************** + +lodash +4.17.21 +Copyright OpenJS Foundation and other contributors + +Based on Underscore.js, copyright Jeremy Ashkenas, +DocumentCloud and Investigative Reporters & Editors + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/lodash/lodash + +The following license applies to all parts of this software except as +documented below: + +==== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +Copyright and related rights for sample code are waived via CC0. Sample +code is defined as all source code displayed within the prose of the +documentation. + +CC0: http://creativecommons.org/publicdomain/zero/1.0/ + +==== + +Files located in the node_modules and vendor directories are externally +maintained libraries used by this software which have their own +licenses; we recommend you read them, as their terms may differ from the +terms above. diff --git a/packages/idempotency/README.md b/packages/idempotency/README.md new file mode 100644 index 0000000000..848588fe31 --- /dev/null +++ b/packages/idempotency/README.md @@ -0,0 +1,83 @@ +# AWS Lambda Powertools for TypeScript + +A suite of utilities for AWS Lambda functions to ease the adoption of best practices such as tracing, structured logging, custom metrics, and more. + +You can use the library in both TypeScript and JavaScript code bases. + +AWS Lambda Powertools for [Python](https://github.com/awslabs/aws-lambda-powertools-python) and AWS Lambda Powertools for [Java](https://github.com/awslabs/aws-lambda-powertools-java) are also available. + +**[📜 Documentation](https://awslabs.github.io/aws-lambda-powertools-typescript/)** | **[NPM](https://www.npmjs.com/org/aws-lambda-powertools)** | **[Roadmap](https://github.com/awslabs/aws-lambda-powertools-roadmap/projects/1)** | **[Examples](https://github.com/awslabs/aws-lambda-powertools-typescript/tree/main/examples)** | **[Serverless TypeScript Demo](https://github.com/aws-samples/serverless-typescript-demo)** + +## Table of contents + +- [Features](#features) +- [Getting started](#getting-started) + - [Installation](#installation) + - [Examples](#examples) + - [Serverless TypeScript Demo](#serverless-typescript-demo-application) +- [Contribute](#contribute) +- [Roadmap](#roadmap) +- [Connect](#connect) +- [Credits](#credits) +- [License](#license) + +## Features + +* **[Tracer](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/core/tracer/)** - Utilities to trace Lambda function handlers, and both synchronous and asynchronous functions +* **[Logger](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/core/logger/)** - Structured logging made easier, and a middleware to enrich log items with key details of the Lambda context +* **[Metrics](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/core/metrics/)** - Custom Metrics created asynchronously via CloudWatch Embedded Metric Format (EMF) + +## Getting started + +Find the complete project's [documentation here](https://awslabs.github.io/aws-lambda-powertools-typescript). + +### Installation + +The AWS Lambda Powertools for TypeScript utilities follow a modular approach, similar to the official [AWS SDK v3 for JavaScript](https://github.com/aws/aws-sdk-js-v3). +Each TypeScript utility is installed as standalone NPM package. + +Install all three core utilities at once with this single command: + +```shell +npm install @aws-lambda-powertools/logger @aws-lambda-powertools/tracer @aws-lambda-powertools/metrics +``` + +Or refer to the installation guide of each utility: + +👉 [Installation guide for the **Tracer** utility](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/core/tracer#getting-started) + +👉 [Installation guide for the **Logger** utility](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/core/logger#getting-started) + +👉 [Installation guide for the **Metrics** utility](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/core/metrics#getting-started) + +### Examples + +* [CDK](https://github.com/awslabs/aws-lambda-powertools-typescript/tree/main/examples/cdk) +* [SAM](https://github.com/awslabs/aws-lambda-powertools-typescript/tree/main/examples/sam) + +### Serverless TypeScript Demo application + +The [Serverless TypeScript Demo](https://github.com/aws-samples/serverless-typescript-demo) shows how to use Lambda Powertools for TypeScript. +You can find instructions on how to deploy and load test this application in the [repository](https://github.com/aws-samples/serverless-typescript-demo). + +## Contribute + +If you are interested in contributing to this project, please refer to our [Contributing Guidelines](https://github.com/awslabs/aws-lambda-powertools-typescript/blob/main/CONTRIBUTING.md). + +## Roadmap + +The roadmap of Powertools is driven by customers’ demand. +Help us prioritize upcoming functionalities or utilities by [upvoting existing RFCs and feature requests](https://github.com/awslabs/aws-lambda-powertools-typescript/issues), or [creating new ones](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/new/choose), in this GitHub repository. + +## Connect + +* **AWS Lambda Powertools on Discord**: `#typescript` - **[Invite link](https://discord.gg/B8zZKbbyET)** +* **Email**: aws-lambda-powertools-feedback@amazon.com + +## Credits + +Credits for the Lambda Powertools idea go to [DAZN](https://github.com/getndazn) and their [DAZN Lambda Powertools](https://github.com/getndazn/dazn-lambda-powertools/). + +## License + +This library is licensed under the MIT-0 License. See the LICENSE file. \ No newline at end of file diff --git a/packages/idempotency/jest.config.js b/packages/idempotency/jest.config.js new file mode 100644 index 0000000000..dd0e63cc41 --- /dev/null +++ b/packages/idempotency/jest.config.js @@ -0,0 +1,45 @@ +module.exports = { + displayName: { + name: 'AWS Lambda Powertools utility: IDEMPOTENCY', + color: 'cyan', + }, + 'runner': 'groups', + 'preset': 'ts-jest', + 'transform': { + '^.+\\.ts?$': 'ts-jest', + }, + moduleFileExtensions: [ 'js', 'ts' ], + 'collectCoverageFrom': [ + '**/src/**/*.ts', + '!**/node_modules/**', + ], + 'testMatch': ['**/?(*.)+(spec|test).ts'], + 'roots': [ + '/src', + '/tests', + ], + 'testPathIgnorePatterns': [ + '/node_modules/', + ], + 'testEnvironment': 'node', + 'coveragePathIgnorePatterns': [ + '/node_modules/', + '/types/', + ], + 'coverageThreshold': { + 'global': { + 'statements': 100, + 'branches': 100, + 'functions': 100, + 'lines': 100, + }, + }, + 'coverageReporters': [ + 'json-summary', + 'text', + 'lcov' + ], + 'setupFiles': [ + '/tests/helpers/populateEnvironmentVariables.ts' + ] +}; \ No newline at end of file diff --git a/packages/idempotency/package.json b/packages/idempotency/package.json new file mode 100644 index 0000000000..a782055d5a --- /dev/null +++ b/packages/idempotency/package.json @@ -0,0 +1,57 @@ +{ + "name": "@aws-lambda-powertools/idempotency", + "version": "0.0.11", + "description": "The idempotency package for the AWS Lambda Powertools for TypeScript library. It provides options to make your Lambda functions idempotent and safe to retry.", + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "commit": "commit", + "test": "npm run test:unit", + "test:unit": "jest --group=unit --detectOpenHandles --coverage --verbose", + "test:e2e:nodejs12x": "RUNTIME=nodejs12x jest --group=e2e", + "test:e2e:nodejs14x": "RUNTIME=nodejs14x jest --group=e2e", + "test:e2e:nodejs16x": "RUNTIME=nodejs16x jest --group=e2e", + "test:e2e": "jest --group=e2e", + "watch": "jest --watch --group=unit", + "build": "tsc", + "lint": "eslint --ext .ts --no-error-on-unmatched-pattern src tests", + "lint-fix": "eslint --fix --ext .ts --no-error-on-unmatched-pattern src tests", + "package": "mkdir -p dist/ && npm pack && mv *.tgz dist/", + "package-bundle": "../../package-bundler.sh logger-bundle ./dist", + "prepare": "npm run build", + "prepublishOnly": "npm test && npm run lint", + "preversion": "npm run lint", + "version": "npm run lint-fix && git add -A src", + "postversion": "git push && git push --tags" + }, + "homepage": "https://github.com/awslabs/aws-lambda-powertools-typescript/tree/master/packages/idempotency#readme", + "license": "MIT", + "main": "./lib/index.js", + "types": "./lib/index.d.ts", + "typedocMain": "src/index.ts", + "files": [ + "lib" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/awslabs/aws-lambda-powertools-typescript.git" + }, + "bugs": { + "url": "https://github.com/awslabs/aws-lambda-powertools-typescript/issues" + }, + "dependencies": { + "@aws-lambda-powertools/commons": "^1.2.1" + }, + "keywords": [ + "aws", + "lambda", + "powertools", + "serverless", + "nodejs" + ] +} \ No newline at end of file diff --git a/packages/idempotency/src/IdempotencyOptions.ts b/packages/idempotency/src/IdempotencyOptions.ts new file mode 100644 index 0000000000..53893cfe42 --- /dev/null +++ b/packages/idempotency/src/IdempotencyOptions.ts @@ -0,0 +1,8 @@ +import { PersistenceLayer } from './persistence/PersistenceLayer'; + +type IdempotencyOptions = { + dataKeywordArgument: string + persistenceStore: PersistenceLayer +}; + +export { IdempotencyOptions }; diff --git a/packages/idempotency/src/makeFunctionIdempotent.ts b/packages/idempotency/src/makeFunctionIdempotent.ts new file mode 100644 index 0000000000..dc851396d6 --- /dev/null +++ b/packages/idempotency/src/makeFunctionIdempotent.ts @@ -0,0 +1,10 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { AnyFunction } from './types/AnyFunction'; +import { IdempotencyOptions } from './IdempotencyOptions'; + +const makeFunctionIdempotent = ( + fn: AnyFunction, + _options: IdempotencyOptions +): (...args: Array) => Promise => (...args) => fn(...args); + +export { makeFunctionIdempotent }; diff --git a/packages/idempotency/src/persistence/DynamoDbPersistenceLayer.ts b/packages/idempotency/src/persistence/DynamoDbPersistenceLayer.ts new file mode 100644 index 0000000000..6592841b3f --- /dev/null +++ b/packages/idempotency/src/persistence/DynamoDbPersistenceLayer.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +import { IdempotencyRecord, PersistenceLayer } from './PersistenceLayer'; + +class DynamoDBPersistenceLayer extends PersistenceLayer { + public constructor(_tableName: string, _key_attr: string = 'id') { + super(); + } + protected async _deleteRecord(): Promise {} + protected async _getRecord(): Promise { + return Promise.resolve({} as IdempotencyRecord); + } + protected async _putRecord(_record: IdempotencyRecord): Promise {} + protected async _updateRecord(): Promise {} +} + +export { + DynamoDBPersistenceLayer +}; \ No newline at end of file diff --git a/packages/idempotency/src/persistence/IdempotencyRecord.ts b/packages/idempotency/src/persistence/IdempotencyRecord.ts new file mode 100644 index 0000000000..7f8fac70ec --- /dev/null +++ b/packages/idempotency/src/persistence/IdempotencyRecord.ts @@ -0,0 +1,27 @@ +import { IdempotencyRecordStatus } from 'types/IdempotencyRecordStatus'; + +class IdempotencyRecord { + + public constructor(public idempotencyKey: string, + private status: IdempotencyRecordStatus, + public expiryTimestamp: number | undefined, + public inProgressExpiryTimestamp: number | undefined, + public responseData: string |undefined, + public payloadHash: string | undefined) {} + + public getStatus(): IdempotencyRecordStatus { + return IdempotencyRecordStatus.INPROGRESS; + } + + public isExpired(): boolean { + return false; + } + + public responseJsonAsObject(): Record | undefined { + return; + } +} + +export { + IdempotencyRecord +}; \ No newline at end of file diff --git a/packages/idempotency/src/persistence/PersistenceLayer.ts b/packages/idempotency/src/persistence/PersistenceLayer.ts new file mode 100644 index 0000000000..1cd2c54411 --- /dev/null +++ b/packages/idempotency/src/persistence/PersistenceLayer.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +import { IdempotencyRecord } from './IdempotencyRecord'; +import { PersistenceLayerInterface } from './PersistenceLayerInterface'; + +abstract class PersistenceLayer implements PersistenceLayerInterface { + public constructor() { } + public configure(_functionName: string = ''): void {} + public async deleteRecord(): Promise { } + public async getRecord(): Promise { + return Promise.resolve({} as IdempotencyRecord); + } + public async saveInProgress(): Promise { } + public async saveSuccess(): Promise { } + + protected abstract _deleteRecord(): Promise; + protected abstract _getRecord(): Promise; + protected abstract _putRecord(record: IdempotencyRecord): Promise; + protected abstract _updateRecord(): Promise; +} + +export { + IdempotencyRecord, + PersistenceLayer +}; \ No newline at end of file diff --git a/packages/idempotency/src/persistence/PersistenceLayerInterface.ts b/packages/idempotency/src/persistence/PersistenceLayerInterface.ts new file mode 100644 index 0000000000..4b2da46b38 --- /dev/null +++ b/packages/idempotency/src/persistence/PersistenceLayerInterface.ts @@ -0,0 +1,11 @@ +import { IdempotencyRecord } from './PersistenceLayer'; + +interface PersistenceLayerInterface { + configure(functionName: string): void + saveInProgress(): Promise + saveSuccess(): Promise + deleteRecord(): Promise + getRecord(): Promise +} + +export { PersistenceLayerInterface }; diff --git a/packages/idempotency/src/types/AnyFunction.ts b/packages/idempotency/src/types/AnyFunction.ts new file mode 100644 index 0000000000..bddcd8fc15 --- /dev/null +++ b/packages/idempotency/src/types/AnyFunction.ts @@ -0,0 +1,6 @@ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type AnyFunction = (...args: Array) => Promise; + +export { + AnyFunction +}; \ No newline at end of file diff --git a/packages/idempotency/src/types/IdempotencyRecordStatus.ts b/packages/idempotency/src/types/IdempotencyRecordStatus.ts new file mode 100644 index 0000000000..95fdac0637 --- /dev/null +++ b/packages/idempotency/src/types/IdempotencyRecordStatus.ts @@ -0,0 +1,9 @@ +enum IdempotencyRecordStatus { + INPROGRESS = 'INPROGRESS', + COMPLETED = 'COMPLETED', + EXPIRED = 'EXPIRED' +} + +export { + IdempotencyRecordStatus +}; \ No newline at end of file diff --git a/packages/idempotency/tsconfig-dev.json b/packages/idempotency/tsconfig-dev.json new file mode 100644 index 0000000000..6f766859ea --- /dev/null +++ b/packages/idempotency/tsconfig-dev.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declarationMap": true, + "esModuleInterop": false + }, + "include": [ "src/**/*", "examples/**/*", "**/tests/**/*" ], + "types": [ + "jest" + ] +} \ No newline at end of file diff --git a/packages/idempotency/tsconfig.es.json b/packages/idempotency/tsconfig.es.json new file mode 100644 index 0000000000..6f766859ea --- /dev/null +++ b/packages/idempotency/tsconfig.es.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declarationMap": true, + "esModuleInterop": false + }, + "include": [ "src/**/*", "examples/**/*", "**/tests/**/*" ], + "types": [ + "jest" + ] +} \ No newline at end of file diff --git a/packages/idempotency/tsconfig.json b/packages/idempotency/tsconfig.json new file mode 100644 index 0000000000..555c42c9e8 --- /dev/null +++ b/packages/idempotency/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "experimentalDecorators": true, + "noImplicitAny": true, + "target": "ES2019", + "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"], + "watchOptions": { + "watchFile": "useFsEvents", + "watchDirectory": "useFsEvents", + "fallbackPolling": "dynamicPriority" + }, + "lib": [ "es2019" ], + "types": [ + "node" + ] +} \ No newline at end of file diff --git a/packages/logger/CHANGELOG.md b/packages/logger/CHANGELOG.md index c05a733fbf..469426f99b 100644 --- a/packages/logger/CHANGELOG.md +++ b/packages/logger/CHANGELOG.md @@ -3,6 +3,23 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.3.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.2.1...v1.3.0) (2022-10-17) + + +### Bug Fixes + +* **all:** update version command to use lint-fix ([#1119](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1119)) ([6f14fb3](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/6f14fb3229882b1dd0c20d18c87a542993432da9)) +* **logger:** wait for decorated method return before clearing out state ([#1087](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1087)) ([133ed3c](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/133ed3c31ce1d99eb8f427f54721896781438ef7)) + + +### Reverts + +* Revert "chore(release): v1.3.0 [skip ci]" ([237b99f](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/237b99f9f6eff5e6e26d779603cf13cd4422c156)) + + + + + ## [1.2.1](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.2.0...v1.2.1) (2022-08-25) **Note:** Version bump only for package @aws-lambda-powertools/logger diff --git a/packages/logger/package.json b/packages/logger/package.json index 386ac9fd12..cea2f8c60b 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -1,6 +1,6 @@ { "name": "@aws-lambda-powertools/logger", - "version": "1.2.1", + "version": "1.3.0", "description": "The logging package for the AWS Lambda Powertools for TypeScript library", "author": { "name": "Amazon Web Services", @@ -19,14 +19,14 @@ "test:e2e": "jest --group=e2e", "watch": "jest --watch --group=unit", "build": "tsc", - "lint": "eslint --ext .ts --fix --no-error-on-unmatched-pattern src tests", - "format": "eslint --fix --ext .ts --fix --no-error-on-unmatched-pattern src tests", + "lint": "eslint --ext .ts --no-error-on-unmatched-pattern src tests", + "lint-fix": "eslint --fix --ext .ts --no-error-on-unmatched-pattern src tests", "package": "mkdir -p dist/ && npm pack && mv *.tgz dist/", "package-bundle": "../../package-bundler.sh logger-bundle ./dist", "prepare": "npm run build", "prepublishOnly": "npm test && npm run lint", "preversion": "npm run lint", - "version": "npm run format && git add -A src", + "version": "npm run lint-fix && git add -A src", "postversion": "git push && git push --tags" }, "homepage": "https://github.com/awslabs/aws-lambda-powertools-typescript/tree/master/packages/logging#readme", @@ -50,7 +50,7 @@ "url": "https://github.com/awslabs/aws-lambda-powertools-typescript/issues" }, "dependencies": { - "@aws-lambda-powertools/commons": "^1.2.1", + "@aws-lambda-powertools/commons": "^1.3.0", "lodash.clonedeep": "^4.5.0", "lodash.merge": "^4.6.2", "lodash.pickby": "^4.6.0" diff --git a/packages/logger/src/Logger.ts b/packages/logger/src/Logger.ts index 2bbdb3fcc1..805e7a5d1a 100644 --- a/packages/logger/src/Logger.ts +++ b/packages/logger/src/Logger.ts @@ -275,7 +275,7 @@ class Logger extends Utility implements ClassThatLogs { * @returns {HandlerMethodDecorator} */ public injectLambdaContext(options?: HandlerOptions): HandlerMethodDecorator { - return (target, _propertyKey, descriptor) => { + return (_target, _propertyKey, descriptor) => { /** * The descriptor.value is the method this decorator decorates, it cannot be undefined. */ @@ -285,7 +285,7 @@ class Logger extends Utility implements ClassThatLogs { const loggerRef = this; // Use a function() {} instead of an () => {} arrow function so that we can // access `myClass` as `this` in a decorated `myClass.myMethod()`. - descriptor.value = (function (this: Handler, event, context, callback) { + descriptor.value = (async function (this: Handler, event, context, callback) { let initialPersistentAttributes = {}; if (options && options.clearState === true) { @@ -297,7 +297,7 @@ class Logger extends Utility implements ClassThatLogs { /* eslint-disable @typescript-eslint/no-non-null-assertion */ let result: unknown; try { - result = originalMethod!.apply(this, [ event, context, callback ]); + result = await originalMethod!.apply(this, [ event, context, callback ]); } catch (error) { throw error; } finally { diff --git a/packages/logger/tests/unit/Logger.test.ts b/packages/logger/tests/unit/Logger.test.ts index 14efa09204..17b7969230 100644 --- a/packages/logger/tests/unit/Logger.test.ts +++ b/packages/logger/tests/unit/Logger.test.ts @@ -1166,6 +1166,48 @@ describe('Class: Logger', () => { }); + test('when used as decorator on an async method, the method is awaited correctly', async () => { + + // Prepare + const injectLambdaContextAfterOrOnErrorMock = jest.fn().mockReturnValue('worked'); + // Temporarily override the cleanup static method so that we can "spy" on it. + // This method is always called after the handler has returned in the decorator + // implementation. + Logger.injectLambdaContextAfterOrOnError = injectLambdaContextAfterOrOnErrorMock; + const logger = new Logger({ + logLevel: 'DEBUG', + }); + const consoleSpy = jest.spyOn(logger['console'], 'info').mockImplementation(); + class LambdaFunction implements LambdaInterface { + @logger.injectLambdaContext() + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public async handler(_event: unknown, _context: Context, _callback: Callback): void | Promise { + await this.dummyMethod(); + logger.info('This is a DEBUG log'); + + return; + } + + private async dummyMethod(): Promise { + return; + } + } + + // Act + const lambda = new LambdaFunction(); + const handler = lambda.handler.bind(lambda); + await handler({}, dummyContext, () => console.log('Lambda invoked!')); + + // Assess + expect(consoleSpy).toBeCalledTimes(1); + // Here we assert that the logger.info method is called before the cleanup function that should awlays + // be called after the handler has returned. If logger.info is called after it means the decorator is + // NOT awaiting the handler which would cause the test to fail. + expect(consoleSpy.mock.invocationCallOrder[0]).toBeLessThan(injectLambdaContextAfterOrOnErrorMock.mock.invocationCallOrder[0]); + + }); + }); describe('Method: refreshSampleRateCalculation', () => { diff --git a/packages/metrics/CHANGELOG.md b/packages/metrics/CHANGELOG.md index dbe971f16f..dae17bc14e 100644 --- a/packages/metrics/CHANGELOG.md +++ b/packages/metrics/CHANGELOG.md @@ -3,6 +3,23 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.3.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.2.1...v1.3.0) (2022-10-17) + + +### Bug Fixes + +* **all:** update version command to use lint-fix ([#1119](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1119)) ([6f14fb3](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/6f14fb3229882b1dd0c20d18c87a542993432da9)) +* captureColdStartMetric and throwOnEmptyMetrics when set to false was interpreted as true ([#1090](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1090)) ([127aad4](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/127aad4698412d72368c093812dd4034839119ca)) + + +### Reverts + +* Revert "chore(release): v1.3.0 [skip ci]" ([237b99f](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/237b99f9f6eff5e6e26d779603cf13cd4422c156)) + + + + + ## [1.2.1](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.2.0...v1.2.1) (2022-08-25) **Note:** Version bump only for package @aws-lambda-powertools/metrics diff --git a/packages/metrics/package.json b/packages/metrics/package.json index c999ddfc51..b4f81b3a1e 100644 --- a/packages/metrics/package.json +++ b/packages/metrics/package.json @@ -1,6 +1,6 @@ { "name": "@aws-lambda-powertools/metrics", - "version": "1.2.1", + "version": "1.3.0", "description": "The metrics package for the AWS Lambda Powertools for TypeScript library", "author": { "name": "Amazon Web Services", @@ -19,14 +19,14 @@ "test:e2e": "jest --group=e2e", "watch": "jest --group=unit --watch ", "build": "tsc", - "lint": "eslint --ext .ts --fix --no-error-on-unmatched-pattern src tests", - "format": "eslint --fix --ext .ts --fix --no-error-on-unmatched-pattern src tests", + "lint": "eslint --ext .ts --no-error-on-unmatched-pattern src tests", + "lint-fix": "eslint --fix --ext .ts --no-error-on-unmatched-pattern src tests", "package": "mkdir -p dist/ && npm pack && mv *.tgz dist/", "package-bundle": "../../package-bundler.sh metrics-bundle ./dist", "prepare": "npm run build", "prepublishOnly": "npm test && npm run lint", "preversion": "npm run lint", - "version": "npm run format && git add -A src", + "version": "npm run lint-fix && git add -A src", "postversion": "git push && git push --tags" }, "homepage": "https://github.com/awslabs/aws-lambda-powertools-typescript/tree/master/packages/metrics#readme", @@ -49,7 +49,7 @@ "url": "https://github.com/awslabs/aws-lambda-powertools-typescript/issues" }, "dependencies": { - "@aws-lambda-powertools/commons": "^1.2.1" + "@aws-lambda-powertools/commons": "^1.3.0" }, "keywords": [ "aws", diff --git a/packages/metrics/src/middleware/middy.ts b/packages/metrics/src/middleware/middy.ts index 5a0e1b7243..2b1bafb1e3 100644 --- a/packages/metrics/src/middleware/middy.ts +++ b/packages/metrics/src/middleware/middy.ts @@ -9,13 +9,13 @@ const logMetrics = (target: Metrics | Metrics[], options: ExtraOptions = {}): mi metricsInstances.forEach((metrics: Metrics) => { metrics.setFunctionName(request.context.functionName); const { throwOnEmptyMetrics, defaultDimensions, captureColdStartMetric } = options; - if (throwOnEmptyMetrics !== undefined) { + if (throwOnEmptyMetrics) { metrics.throwOnEmptyMetrics(); } if (defaultDimensions !== undefined) { metrics.setDefaultDimensions(defaultDimensions); } - if (captureColdStartMetric !== undefined) { + if (captureColdStartMetric) { metrics.captureColdStartMetric(); } }); diff --git a/packages/metrics/tests/unit/Metrics.test.ts b/packages/metrics/tests/unit/Metrics.test.ts index 345306714e..8ce2956969 100644 --- a/packages/metrics/tests/unit/Metrics.test.ts +++ b/packages/metrics/tests/unit/Metrics.test.ts @@ -371,6 +371,32 @@ describe('Class: Metrics', () => { } }); + test('Error should not be thrown on empty metrics if throwOnEmptyMetrics is set to false', async () => { + expect.assertions(1); + + const metrics = new Metrics({ namespace: 'test' }); + class LambdaFunction implements LambdaInterface { + @metrics.logMetrics({ throwOnEmptyMetrics: false }) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public handler( + _event: TEvent, + _context: Context, + _callback: Callback, + ): void | Promise { + return; + } + } + + try { + await new LambdaFunction().handler(dummyEvent, dummyContext.helloworldContext, () => console.log('Lambda invoked!')); + } catch (e) { + fail(`Should not throw but got the following Error: ${e}`); + } + const loggedData = JSON.parse(consoleSpy.mock.calls[0][0]); + expect(loggedData._aws.CloudWatchMetrics[0].Metrics.length).toBe(0); + }); + test('Error should be thrown on empty metrics when throwOnEmptyMetrics() is called', async () => { expect.assertions(1); diff --git a/packages/metrics/tests/unit/middleware/middy.test.ts b/packages/metrics/tests/unit/middleware/middy.test.ts index 3d4bed94f6..a31cb20d5e 100644 --- a/packages/metrics/tests/unit/middleware/middy.test.ts +++ b/packages/metrics/tests/unit/middleware/middy.test.ts @@ -16,15 +16,159 @@ const mockDate = new Date(1466424490000); const dateSpy = jest.spyOn(global, 'Date').mockImplementation(() => mockDate as unknown as string); describe('Middy middleware', () => { - beforeEach(() => { jest.resetModules(); consoleSpy.mockClear(); dateSpy.mockClear(); }); - describe('logMetrics', () => { + describe('throwOnEmptyMetrics', () => { + const getRandomInt = (): number => Math.floor(Math.random() * 1000000000); + const awsRequestId = getRandomInt().toString(); + + const context = { + callbackWaitsForEmptyEventLoop: true, + functionVersion: '$LATEST', + functionName: 'foo-bar-function', + memoryLimitInMB: '128', + logGroupName: '/aws/lambda/foo-bar-function', + logStreamName: '2021/03/09/[$LATEST]abcdef123456abcdef123456abcdef123456', + invokedFunctionArn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', + awsRequestId: awsRequestId, + getRemainingTimeInMillis: () => 1234, + done: () => console.log('Done!'), + fail: () => console.log('Failed!'), + succeed: () => console.log('Succeeded!'), + }; + + test('should throw on empty metrics if set to true', async () => { + // Prepare + const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); + + const lambdaHandler = (): void => { + console.log('do nothing'); + }; + + const handler = middy(lambdaHandler).use(logMetrics(metrics, { throwOnEmptyMetrics: true })); + + try { + await handler(event, context, () => console.log('Lambda invoked!')); + } catch (e) { + expect((e).message).toBe('The number of metrics recorded must be higher than zero'); + } + }); + test('should not throw on empty metrics if set to false', async () => { + // Prepare + const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); + + const lambdaHandler = (): void => { + console.log('do nothing'); + }; + + const handler = middy(lambdaHandler).use(logMetrics(metrics, { throwOnEmptyMetrics: false })); + + try { + await handler(event, context, () => console.log('Lambda invoked!')); + } catch (e) { + fail(`Should not throw but got the following Error: ${e}`); + } + }); + + test('should not throw on empty metrics if not set', async () => { + // Prepare + const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); + + const lambdaHandler = (): void => { + console.log('do nothing'); + }; + + const handler = middy(lambdaHandler).use(logMetrics(metrics)); + + try { + await handler(event, context, () => console.log('Lambda invoked!')); + } catch (e) { + fail(`Should not throw but got the following Error: ${e}`); + } + }); + }); + + describe('captureColdStartMetric', () => { + const getRandomInt = (): number => Math.floor(Math.random() * 1000000000); + const awsRequestId = getRandomInt().toString(); + + const context = { + callbackWaitsForEmptyEventLoop: true, + functionVersion: '$LATEST', + functionName: 'foo-bar-function', + memoryLimitInMB: '128', + logGroupName: '/aws/lambda/foo-bar-function', + logStreamName: '2021/03/09/[$LATEST]abcdef123456abcdef123456abcdef123456', + invokedFunctionArn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', + awsRequestId: awsRequestId, + getRemainingTimeInMillis: () => 1234, + done: () => console.log('Done!'), + fail: () => console.log('Failed!'), + succeed: () => console.log('Succeeded!'), + }; + + test('should capture cold start metric if set to true', async () => { + // Prepare + const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); + + const lambdaHandler = (): void => { + console.log('{"message": "do nothing"}'); + }; + + const handler = middy(lambdaHandler).use(logMetrics(metrics, { captureColdStartMetric: true })); + + await handler(event, context, () => console.log('Lambda invoked!')); + await handler(event, context, () => console.log('Lambda invoked! again')); + const loggedData = [ JSON.parse(consoleSpy.mock.calls[0][0]), JSON.parse(consoleSpy.mock.calls[1][0]) ]; + + expect(console.log).toBeCalledTimes(5); + expect(loggedData[0]._aws.CloudWatchMetrics[0].Metrics.length).toBe(1); + expect(loggedData[0]._aws.CloudWatchMetrics[0].Metrics[0].Name).toBe('ColdStart'); + expect(loggedData[0]._aws.CloudWatchMetrics[0].Metrics[0].Unit).toBe('Count'); + expect(loggedData[0].ColdStart).toBe(1); + }); + + test('should not capture cold start metrics if set to false', async () => { + // Prepare + const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); + + const lambdaHandler = (): void => { + console.log('{"message": "do nothing"}'); + }; + + const handler = middy(lambdaHandler).use(logMetrics(metrics, { captureColdStartMetric: false })); + + await handler(event, context, () => console.log('Lambda invoked!')); + await handler(event, context, () => console.log('Lambda invoked! again')); + const loggedData = [ JSON.parse(consoleSpy.mock.calls[0][0]), JSON.parse(consoleSpy.mock.calls[1][0]) ]; + + expect(loggedData[0]._aws).toBe(undefined); + }); + + test('should not throw on empty metrics if not set', async () => { + // Prepare + const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); + + const lambdaHandler = (): void => { + console.log('{"message": "do nothing"}'); + }; + + const handler = middy(lambdaHandler).use(logMetrics(metrics)); + + await handler(event, context, () => console.log('Lambda invoked!')); + await handler(event, context, () => console.log('Lambda invoked! again')); + const loggedData = [ JSON.parse(consoleSpy.mock.calls[0][0]), JSON.parse(consoleSpy.mock.calls[1][0]) ]; + + expect(loggedData[0]._aws).toBe(undefined); + }); + }); + + describe('logMetrics', () => { const getRandomInt = (): number => Math.floor(Math.random() * 1000000000); const awsRequestId = getRandomInt().toString(); @@ -45,7 +189,7 @@ describe('Middy middleware', () => { test('when a metrics instance receive multiple metrics with the same name, it prints multiple values in an array format', async () => { // Prepare - const metrics = new Metrics({ namespace:'serverlessAirline', serviceName:'orders' }); + const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); const lambdaHandler = (): void => { metrics.addMetric('successfulBooking', MetricUnits.Count, 2); @@ -53,42 +197,41 @@ describe('Middy middleware', () => { }; const handler = middy(lambdaHandler).use(logMetrics(metrics)); - + // Act await handler(event, context, () => console.log('Lambda invoked!')); - + // Assess - expect(console.log).toHaveBeenNthCalledWith(1, JSON.stringify({ - '_aws': { - 'Timestamp': 1466424490000, - 'CloudWatchMetrics': [{ - 'Namespace': 'serverlessAirline', - 'Dimensions': [ - ['service'] + expect(console.log).toHaveBeenNthCalledWith( + 1, + JSON.stringify({ + _aws: { + Timestamp: 1466424490000, + CloudWatchMetrics: [ + { + Namespace: 'serverlessAirline', + Dimensions: [['service']], + Metrics: [{ Name: 'successfulBooking', Unit: 'Count' }], + }, ], - 'Metrics': [{ 'Name': 'successfulBooking', 'Unit': 'Count' }], - }], - }, - 'service': 'orders', - 'successfulBooking': [ - 2, - 1, - ], - })); + }, + service: 'orders', + successfulBooking: [ 2, 1 ], + }) + ); }); test('when a metrics instance is passed WITH custom options, it prints the metrics in the stdout', async () => { - // Prepare - const metrics = new Metrics({ namespace:'serverlessAirline', serviceName:'orders' }); + const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); const lambdaHandler = (): void => { metrics.addMetric('successfulBooking', MetricUnits.Count, 1); }; const metricsOptions: ExtraOptions = { throwOnEmptyMetrics: true, - defaultDimensions: { environment : 'prod', aws_region: 'eu-west-1' }, - captureColdStartMetric: true + defaultDimensions: { environment: 'prod', aws_region: 'eu-west-1' }, + captureColdStartMetric: true, }; const handler = middy(lambdaHandler).use(logMetrics(metrics, metricsOptions)); @@ -96,46 +239,50 @@ describe('Middy middleware', () => { await handler(event, context, () => console.log('Lambda invoked!')); // Assess - expect(console.log).toHaveBeenNthCalledWith(1, JSON.stringify({ - '_aws': { - 'Timestamp': 1466424490000, - 'CloudWatchMetrics': [{ - 'Namespace': 'serverlessAirline', - 'Dimensions': [ - [ 'environment', 'aws_region', 'service', 'function_name' ] + expect(console.log).toHaveBeenNthCalledWith( + 1, + JSON.stringify({ + _aws: { + Timestamp: 1466424490000, + CloudWatchMetrics: [ + { + Namespace: 'serverlessAirline', + Dimensions: [[ 'environment', 'aws_region', 'service', 'function_name' ]], + Metrics: [{ Name: 'ColdStart', Unit: 'Count' }], + }, ], - 'Metrics': [{ 'Name': 'ColdStart', 'Unit': 'Count' }], - }], - }, - 'environment': 'prod', - 'aws_region' : 'eu-west-1', - 'service': 'orders', - 'function_name': 'foo-bar-function', - 'ColdStart': 1, - })); - expect(console.log).toHaveBeenNthCalledWith(2, JSON.stringify({ - '_aws': { - 'Timestamp': 1466424490000, - 'CloudWatchMetrics': [{ - 'Namespace': 'serverlessAirline', - 'Dimensions': [ - [ 'environment', 'aws_region', 'service' ] + }, + environment: 'prod', + aws_region: 'eu-west-1', + service: 'orders', + function_name: 'foo-bar-function', + ColdStart: 1, + }) + ); + expect(console.log).toHaveBeenNthCalledWith( + 2, + JSON.stringify({ + _aws: { + Timestamp: 1466424490000, + CloudWatchMetrics: [ + { + Namespace: 'serverlessAirline', + Dimensions: [[ 'environment', 'aws_region', 'service' ]], + Metrics: [{ Name: 'successfulBooking', Unit: 'Count' }], + }, ], - 'Metrics': [{ 'Name': 'successfulBooking', 'Unit': 'Count' }], - }], - }, - 'environment': 'prod', - 'aws_region' : 'eu-west-1', - 'service': 'orders', - 'successfulBooking': 1, - })); - + }, + environment: 'prod', + aws_region: 'eu-west-1', + service: 'orders', + successfulBooking: 1, + }) + ); }); test('when a metrics instance is passed WITHOUT custom options, it prints the metrics in the stdout', async () => { - // Prepare - const metrics = new Metrics({ namespace:'serverlessAirline', serviceName:'orders' }); + const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); const lambdaHandler = (): void => { metrics.addMetric('successfulBooking', MetricUnits.Count, 1); @@ -147,33 +294,34 @@ describe('Middy middleware', () => { await handler(event, context, () => console.log('Lambda invoked!')); // Assess - expect(console.log).toHaveBeenNthCalledWith(1, JSON.stringify({ - '_aws': { - 'Timestamp': 1466424490000, - 'CloudWatchMetrics': [{ - 'Namespace': 'serverlessAirline', - 'Dimensions': [ - ['service'] + expect(console.log).toHaveBeenNthCalledWith( + 1, + JSON.stringify({ + _aws: { + Timestamp: 1466424490000, + CloudWatchMetrics: [ + { + Namespace: 'serverlessAirline', + Dimensions: [['service']], + Metrics: [{ Name: 'successfulBooking', Unit: 'Count' }], + }, ], - 'Metrics': [{ 'Name': 'successfulBooking', 'Unit': 'Count' }], - }], - }, - 'service': 'orders', - 'successfulBooking': 1, - })); - + }, + service: 'orders', + successfulBooking: 1, + }) + ); }); test('when an array of Metrics instances is passed, it prints the metrics in the stdout', async () => { - // Prepare - const metrics = new Metrics({ namespace:'serverlessAirline', serviceName:'orders' }); + const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); const lambdaHandler = (): void => { metrics.addMetric('successfulBooking', MetricUnits.Count, 1); }; const metricsOptions: ExtraOptions = { - throwOnEmptyMetrics: true + throwOnEmptyMetrics: true, }; const handler = middy(lambdaHandler).use(logMetrics([metrics], metricsOptions)); @@ -181,23 +329,23 @@ describe('Middy middleware', () => { await handler(event, context, () => console.log('Lambda invoked!')); // Assess - expect(console.log).toHaveBeenNthCalledWith(1, JSON.stringify({ - '_aws': { - 'Timestamp': 1466424490000, - 'CloudWatchMetrics': [{ - 'Namespace': 'serverlessAirline', - 'Dimensions': [ - ['service'] + expect(console.log).toHaveBeenNthCalledWith( + 1, + JSON.stringify({ + _aws: { + Timestamp: 1466424490000, + CloudWatchMetrics: [ + { + Namespace: 'serverlessAirline', + Dimensions: [['service']], + Metrics: [{ Name: 'successfulBooking', Unit: 'Count' }], + }, ], - 'Metrics': [{ 'Name': 'successfulBooking', 'Unit': 'Count' }], - }], - }, - 'service': 'orders', - 'successfulBooking': 1, - })); - + }, + service: 'orders', + successfulBooking: 1, + }) + ); }); - }); - -}); \ No newline at end of file +}); diff --git a/packages/tracer/CHANGELOG.md b/packages/tracer/CHANGELOG.md index 0c8c39fb5c..3e3f65710f 100644 --- a/packages/tracer/CHANGELOG.md +++ b/packages/tracer/CHANGELOG.md @@ -3,6 +3,28 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.3.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.2.1...v1.3.0) (2022-10-17) + + +### Bug Fixes + +* **all:** update version command to use lint-fix ([#1119](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1119)) ([6f14fb3](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/6f14fb3229882b1dd0c20d18c87a542993432da9)) +* captureMethod correctly detect method name when used with external decorators ([#1109](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1109)) ([a574406](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/a574406134b65c17f56dfb3d3130aa237ece4160)) + + +### Features + +* **tracer:** specify subsegment name when capturing class method ([#1092](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1092)) ([d4174eb](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/d4174eb7a894215e2d37f306016429de3bde8029)) + + +### Reverts + +* Revert "chore(release): v1.3.0 [skip ci]" ([237b99f](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/237b99f9f6eff5e6e26d779603cf13cd4422c156)) + + + + + ## [1.2.1](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.2.0...v1.2.1) (2022-08-25) **Note:** Version bump only for package @aws-lambda-powertools/tracer diff --git a/packages/tracer/package.json b/packages/tracer/package.json index e006663457..b4c974a48f 100644 --- a/packages/tracer/package.json +++ b/packages/tracer/package.json @@ -1,6 +1,6 @@ { "name": "@aws-lambda-powertools/tracer", - "version": "1.2.1", + "version": "1.3.0", "description": "The tracer package for the AWS Lambda Powertools for TypeScript library", "author": { "name": "Amazon Web Services", @@ -19,12 +19,12 @@ "test:e2e": "jest --group=e2e", "watch": "jest --watch", "build": "tsc", - "lint": "eslint --ext .ts --fix --no-error-on-unmatched-pattern src tests", - "format": "eslint --fix --ext .ts --fix --no-error-on-unmatched-pattern src tests", + "lint": "eslint --ext .ts --no-error-on-unmatched-pattern src tests", + "lint-fix": "eslint --fix --ext .ts --no-error-on-unmatched-pattern src tests", "prepare": "npm run build", "prepublishOnly": "npm test && npm run lint", "preversion": "npm run lint", - "version": "npm run format && git add -A src", + "version": "npm run lint-fix && git add -A src", "postversion": "git push && git push --tags", "package": "mkdir -p dist/ && npm pack && mv *.tgz dist/", "package-bundle": "../../package-bundler.sh tracer-bundle ./dist" @@ -50,7 +50,7 @@ "url": "https://github.com/awslabs/aws-lambda-powertools-typescript/issues" }, "dependencies": { - "@aws-lambda-powertools/commons": "^1.2.1", + "@aws-lambda-powertools/commons": "^1.3.0", "aws-xray-sdk-core": "^3.3.6" }, "keywords": [ diff --git a/packages/tracer/src/Tracer.ts b/packages/tracer/src/Tracer.ts index e7663b2673..e4fe06553b 100644 --- a/packages/tracer/src/Tracer.ts +++ b/packages/tracer/src/Tracer.ts @@ -2,7 +2,7 @@ import { Handler } from 'aws-lambda'; import { AsyncHandler, SyncHandler, Utility } from '@aws-lambda-powertools/commons'; import { TracerInterface } from '.'; import { ConfigServiceInterface, EnvironmentVariablesService } from './config'; -import { HandlerMethodDecorator, TracerOptions, HandlerOptions, MethodDecorator } from './types'; +import { HandlerMethodDecorator, TracerOptions, MethodDecorator, CaptureLambdaHandlerOptions, CaptureMethodOptions } from './types'; import { ProviderService, ProviderServiceInterface } from './provider'; import { Segment, Subsegment } from 'aws-xray-sdk-core'; @@ -338,8 +338,9 @@ class Tracer extends Utility implements TracerInterface { * ``` * * @decorator Class + * @param options - (_optional_) Options for the decorator */ - public captureLambdaHandler(options?: HandlerOptions): HandlerMethodDecorator { + public captureLambdaHandler(options?: CaptureLambdaHandlerOptions): HandlerMethodDecorator { return (_target, _propertyKey, descriptor) => { /** * The descriptor.value is the method this decorator decorates, it cannot be undefined. @@ -418,9 +419,10 @@ class Tracer extends Utility implements TracerInterface { * ``` * * @decorator Class + * @param options - (_optional_) Options for the decorator */ - public captureMethod(options?: HandlerOptions): MethodDecorator { - return (_target, _propertyKey, descriptor) => { + public captureMethod(options?: CaptureMethodOptions): MethodDecorator { + return (_target, propertyKey, descriptor) => { // The descriptor.value is the method this decorator decorates, it cannot be undefined. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const originalMethod = descriptor.value!; @@ -434,12 +436,15 @@ class Tracer extends Utility implements TracerInterface { return originalMethod.apply(this, [...args]); } - return tracerRef.provider.captureAsyncFunc(`### ${originalMethod.name}`, async subsegment => { + const methodName = String(propertyKey); + const subsegmentName = options?.subSegmentName ? options.subSegmentName : `### ${methodName}`; + + return tracerRef.provider.captureAsyncFunc(subsegmentName, async subsegment => { let result; try { result = await originalMethod.apply(this, [...args]); if (options?.captureResponse ?? true) { - tracerRef.addResponseAsMetadata(result, originalMethod.name); + tracerRef.addResponseAsMetadata(result, methodName); } } catch (error) { tracerRef.addErrorAsMetadata(error as Error); diff --git a/packages/tracer/src/TracerInterface.ts b/packages/tracer/src/TracerInterface.ts index 673d0ed2a3..20ae8f60c9 100644 --- a/packages/tracer/src/TracerInterface.ts +++ b/packages/tracer/src/TracerInterface.ts @@ -1,4 +1,4 @@ -import { HandlerMethodDecorator, MethodDecorator } from './types'; +import { CaptureLambdaHandlerOptions, CaptureMethodOptions, HandlerMethodDecorator, MethodDecorator } from './types'; import { Segment, Subsegment } from 'aws-xray-sdk-core'; interface TracerInterface { @@ -9,8 +9,8 @@ interface TracerInterface { captureAWS(aws: T): void | T captureAWSv3Client(service: T): void | T captureAWSClient(service: T): void | T - captureLambdaHandler(): HandlerMethodDecorator - captureMethod(): MethodDecorator + captureLambdaHandler(options?: CaptureLambdaHandlerOptions): HandlerMethodDecorator + captureMethod(options?: CaptureMethodOptions): MethodDecorator getSegment(): Segment | Subsegment isTracingEnabled(): boolean putAnnotation: (key: string, value: string | number | boolean) => void diff --git a/packages/tracer/src/middleware/middy.ts b/packages/tracer/src/middleware/middy.ts index 0774e02fda..b6fc75b708 100644 --- a/packages/tracer/src/middleware/middy.ts +++ b/packages/tracer/src/middleware/middy.ts @@ -1,7 +1,7 @@ import type middy from '@middy/core'; import type { Tracer } from '../Tracer'; import type { Segment, Subsegment } from 'aws-xray-sdk-core'; -import type { HandlerOptions } from '../types'; +import type { CaptureLambdaHandlerOptions } from '../types'; /** * A middy middleware automating capture of metadata and annotations on segments or subsegments for a Lambda Handler. @@ -25,9 +25,10 @@ import type { HandlerOptions } from '../types'; * ``` * * @param target - The Tracer instance to use for tracing + * @param options - (_optional_) Options for the middleware * @returns middleware object - The middy middleware object */ -const captureLambdaHandler = (target: Tracer, options?: HandlerOptions): middy.MiddlewareObj => { +const captureLambdaHandler = (target: Tracer, options?: CaptureLambdaHandlerOptions): middy.MiddlewareObj => { let lambdaSegment: Subsegment | Segment; const open = (): void => { diff --git a/packages/tracer/src/types/Tracer.ts b/packages/tracer/src/types/Tracer.ts index 9003b1e997..90b8db2d49 100644 --- a/packages/tracer/src/types/Tracer.ts +++ b/packages/tracer/src/types/Tracer.ts @@ -29,21 +29,70 @@ type TracerOptions = { /** * Options for handler decorators and middleware. * + * Options supported: + * * `captureResponse` - (_optional_) - Disable response serialization as subsegment metadata + * + * Middleware usage: + * @example + * ```typescript + * import middy from '@middy/core'; + * + * const tracer = new Tracer(); + * + * const lambdaHandler = async (_event: any, _context: any): Promise => {}; + * + * export const handler = middy(lambdaHandler) + * .use(captureLambdaHandler(tracer, { captureResponse: false })); + * ``` + * + * Decorator usage: + * @example + * ```typescript + * const tracer = new Tracer(); + * + * class Lambda implements LambdaInterface { + * @tracer.captureLambdaHandler({ captureResponse: false }) + * public async handler(_event: any, _context: any): Promise {} + * } + * + * const handlerClass = new Lambda(); + * export const handler = handlerClass.handler.bind(handlerClass); + * ``` + */ +type CaptureLambdaHandlerOptions = { + captureResponse?: boolean +}; + +/** + * Options for method decorators. + * + * Options supported: + * * `subSegmentName` - (_optional_) - Set a custom name for the subsegment + * * `captureResponse` - (_optional_) - Disable response serialization as subsegment metadata + * * Usage: * @example * ```typescript * const tracer = new Tracer(); * * class Lambda implements LambdaInterface { + * @tracer.captureMethod({ subSegmentName: 'gettingChargeId', captureResponse: false }) + * private getChargeId(): string { + * return 'foo bar'; + * } + * * @tracer.captureLambdaHandler({ captureResponse: false }) - * async handler(_event: any, _context: any): Promise {} + * public async handler(_event: any, _context: any): Promise { + * this.getChargeId(); + * } * } * * const handlerClass = new Lambda(); * export const handler = handlerClass.handler.bind(handlerClass); * ``` */ -type HandlerOptions = { +type CaptureMethodOptions = { + subSegmentName?: string captureResponse?: boolean }; @@ -59,7 +108,8 @@ type MethodDecorator = (target: any, propertyKey: string | symbol, descriptor: T export { TracerOptions, - HandlerOptions, + CaptureLambdaHandlerOptions, + CaptureMethodOptions, HandlerMethodDecorator, MethodDecorator }; \ No newline at end of file diff --git a/packages/tracer/tests/e2e/allFeatures.decorator.test.functionCode.ts b/packages/tracer/tests/e2e/allFeatures.decorator.test.functionCode.ts index 337aec311d..7c19622479 100644 --- a/packages/tracer/tests/e2e/allFeatures.decorator.test.functionCode.ts +++ b/packages/tracer/tests/e2e/allFeatures.decorator.test.functionCode.ts @@ -9,8 +9,8 @@ const serviceName = process.env.EXPECTED_SERVICE_NAME ?? 'MyFunctionWithStandard const customAnnotationKey = process.env.EXPECTED_CUSTOM_ANNOTATION_KEY ?? 'myAnnotation'; const customAnnotationValue = process.env.EXPECTED_CUSTOM_ANNOTATION_VALUE ?? 'myValue'; const customMetadataKey = process.env.EXPECTED_CUSTOM_METADATA_KEY ?? 'myMetadata'; -const customMetadataValue = JSON.parse(process.env.EXPECTED_CUSTOM_METADATA_VALUE) ?? { bar: 'baz' }; -const customResponseValue = JSON.parse(process.env.EXPECTED_CUSTOM_RESPONSE_VALUE) ?? { foo: 'bar' }; +const customMetadataValue = process.env.EXPECTED_CUSTOM_METADATA_VALUE ? JSON.parse(process.env.EXPECTED_CUSTOM_METADATA_VALUE) : { bar: 'baz' }; +const customResponseValue = process.env.EXPECTED_CUSTOM_RESPONSE_VALUE ? JSON.parse(process.env.EXPECTED_CUSTOM_RESPONSE_VALUE) : { foo: 'bar' }; const customErrorMessage = process.env.EXPECTED_CUSTOM_ERROR_MESSAGE ?? 'An error has occurred'; const testTableName = process.env.TEST_TABLE_NAME ?? 'TestTable'; @@ -42,8 +42,6 @@ export class MyFunctionBase { this.returnValue = customResponseValue; } - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore public handler(event: CustomEvent, _context: Context, _callback: Callback): void | Promise { tracer.putAnnotation(customAnnotationKey, customAnnotationValue); tracer.putMetadata(customMetadataKey, customMetadataValue); @@ -78,8 +76,6 @@ export class MyFunctionBase { }); } - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore public myMethod(): string { return this.returnValue; } @@ -121,4 +117,4 @@ class MyFunctionWithDecoratorCaptureResponseFalse extends MyFunctionBase { } const handlerWithCaptureResponseFalseClass = new MyFunctionWithDecoratorCaptureResponseFalse(); -export const handlerWithCaptureResponseFalse = handlerClass.handler.bind(handlerWithCaptureResponseFalseClass); \ No newline at end of file +export const handlerWithCaptureResponseFalse = handlerWithCaptureResponseFalseClass.handler.bind(handlerWithCaptureResponseFalseClass); \ No newline at end of file diff --git a/packages/tracer/tests/e2e/allFeatures.decorator.test.ts b/packages/tracer/tests/e2e/allFeatures.decorator.test.ts index befe56830b..b44d824f8a 100644 --- a/packages/tracer/tests/e2e/allFeatures.decorator.test.ts +++ b/packages/tracer/tests/e2e/allFeatures.decorator.test.ts @@ -62,7 +62,7 @@ let startTime: Date; * Function #1 is with all flags enabled. */ const uuidFunction1 = v4(); -const functionNameWithAllFlagsEnabled = generateUniqueName(RESOURCE_NAME_PREFIX, uuidFunction1, runtime, 'AllFeatures-Decoratory-AllFlagsEnabled'); +const functionNameWithAllFlagsEnabled = generateUniqueName(RESOURCE_NAME_PREFIX, uuidFunction1, runtime, 'AllFeatures-Decorator-AllFlagsEnabled'); const serviceNameWithAllFlagsEnabled = functionNameWithAllFlagsEnabled; /** @@ -79,7 +79,7 @@ const functionNameWithTracerDisabled = generateUniqueName(RESOURCE_NAME_PREFIX, const serviceNameWithTracerDisabled = functionNameWithNoCaptureErrorOrResponse; /** - * Function #4 disables tracer + * Function #4 disables capture response via decorator options */ const uuidFunction4 = v4(); const functionNameWithCaptureResponseFalse = generateUniqueName(RESOURCE_NAME_PREFIX, uuidFunction4, runtime, 'AllFeatures-Decorator-CaptureResponseFalse'); diff --git a/packages/tracer/tests/e2e/asyncHandler.decorator.test.functionCode.ts b/packages/tracer/tests/e2e/asyncHandler.decorator.test.functionCode.ts index ae4306ed30..61f16b43c2 100644 --- a/packages/tracer/tests/e2e/asyncHandler.decorator.test.functionCode.ts +++ b/packages/tracer/tests/e2e/asyncHandler.decorator.test.functionCode.ts @@ -9,10 +9,11 @@ const serviceName = process.env.EXPECTED_SERVICE_NAME ?? 'MyFunctionWithStandard const customAnnotationKey = process.env.EXPECTED_CUSTOM_ANNOTATION_KEY ?? 'myAnnotation'; const customAnnotationValue = process.env.EXPECTED_CUSTOM_ANNOTATION_VALUE ?? 'myValue'; const customMetadataKey = process.env.EXPECTED_CUSTOM_METADATA_KEY ?? 'myMetadata'; -const customMetadataValue = JSON.parse(process.env.EXPECTED_CUSTOM_METADATA_VALUE) ?? { bar: 'baz' }; -const customResponseValue = JSON.parse(process.env.EXPECTED_CUSTOM_RESPONSE_VALUE) ?? { foo: 'bar' }; +const customMetadataValue = process.env.EXPECTED_CUSTOM_METADATA_VALUE ? JSON.parse(process.env.EXPECTED_CUSTOM_METADATA_VALUE) : { bar: 'baz' }; +const customResponseValue = process.env.EXPECTED_CUSTOM_RESPONSE_VALUE ? JSON.parse(process.env.EXPECTED_CUSTOM_RESPONSE_VALUE) : { foo: 'bar' }; const customErrorMessage = process.env.EXPECTED_CUSTOM_ERROR_MESSAGE ?? 'An error has occurred'; const testTableName = process.env.TEST_TABLE_NAME ?? 'TestTable'; +const customSubSegmentName = process.env.EXPECTED_CUSTOM_SUBSEGMENT_NAME ?? 'mySubsegment'; interface CustomEvent { throw: boolean @@ -35,16 +36,13 @@ const refreshAWSSDKImport = (): void => { const tracer = new Tracer({ serviceName: serviceName }); const dynamoDBv3 = tracer.captureAWSv3Client(new DynamoDBClient({})); -export class MyFunctionWithDecorator { +export class MyFunctionBase { private readonly returnValue: string; public constructor() { this.returnValue = customResponseValue; } - @tracer.captureLambdaHandler() - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore public async handler(event: CustomEvent, _context: Context): Promise { tracer.putAnnotation(customAnnotationKey, customAnnotationValue); tracer.putMetadata(customMetadataKey, customMetadataValue); @@ -74,13 +72,45 @@ export class MyFunctionWithDecorator { } } + public myMethod(): string { + return this.returnValue; + } +} + +class MyFunctionWithDecorator extends MyFunctionBase { + @tracer.captureLambdaHandler() + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public async handler(event: CustomEvent, _context: Context, _callback: Callback): void | Promise { + return super.handler(event, _context); + } + @tracer.captureMethod() // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore public myMethod(): string { - return this.returnValue; + return super.myMethod(); } } const handlerClass = new MyFunctionWithDecorator(); -export const handler = handlerClass.handler.bind(handlerClass); \ No newline at end of file +export const handler = handlerClass.handler.bind(handlerClass); + +export class MyFunctionWithDecoratorAndCustomNamedSubSegmentForMethod extends MyFunctionBase { + @tracer.captureLambdaHandler() + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public async handler(event: CustomEvent, _context: Context, _callback: Callback): void | Promise { + return super.handler(event, _context); + } + + @tracer.captureMethod({ subSegmentName: customSubSegmentName }) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public myMethod(): string { + return super.myMethod(); + } +} + +const handlerWithCustomSubsegmentNameInMethodClass = new MyFunctionWithDecoratorAndCustomNamedSubSegmentForMethod(); +export const handlerWithCustomSubsegmentNameInMethod = handlerClass.handler.bind(handlerWithCustomSubsegmentNameInMethodClass); \ No newline at end of file diff --git a/packages/tracer/tests/e2e/asyncHandler.decorator.test.ts b/packages/tracer/tests/e2e/asyncHandler.decorator.test.ts index 21cd44840c..5c14bc153c 100644 --- a/packages/tracer/tests/e2e/asyncHandler.decorator.test.ts +++ b/packages/tracer/tests/e2e/asyncHandler.decorator.test.ts @@ -34,6 +34,7 @@ import { expectedCustomMetadataKey, expectedCustomMetadataValue, expectedCustomResponseValue, + expectedCustomSubSegmentName, } from './constants'; import { assertAnnotation, @@ -50,9 +51,19 @@ const stackName = generateUniqueName(RESOURCE_NAME_PREFIX, v4(), runtime, 'AllFe const lambdaFunctionCodeFile = 'asyncHandler.decorator.test.functionCode.ts'; let startTime: Date; -const uuid = v4(); -const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'AllFeatures-Decoratory-AllFlagsEnabled'); -const serviceName = functionName; +/** + * Function #1 is with all flags enabled. + */ +const uuidFunction1 = v4(); +const functionNameWithAllFlagsEnabled = generateUniqueName(RESOURCE_NAME_PREFIX, uuidFunction1, runtime, 'AllFeatures-Decorator-Async-AllFlagsEnabled'); +const serviceNameWithAllFlagsEnabled = functionNameWithAllFlagsEnabled; + +/** + * Function #2 sets a custom subsegment name in the decorated method + */ +const uuidFunction2 = v4(); +const functionNameWithCustomSubsegmentNameInMethod = generateUniqueName(RESOURCE_NAME_PREFIX, uuidFunction2, runtime, 'AllFeatures-Decorator-Async-CustomSubsegmentNameInMethod'); +const serviceNameWithCustomSubsegmentNameInMethod = functionNameWithCustomSubsegmentNameInMethod; const xray = new AWS.XRay(); const invocations = 3; @@ -82,9 +93,9 @@ describe(`Tracer E2E tests, asynchronous handler with decorator instantiation fo const entry = path.join(__dirname, lambdaFunctionCodeFile); const functionWithAllFlagsEnabled = createTracerTestFunction({ stack, - functionName: functionName, + functionName: functionNameWithAllFlagsEnabled, entry, - expectedServiceName: serviceName, + expectedServiceName: serviceNameWithAllFlagsEnabled, environmentParams: { TEST_TABLE_NAME: ddbTableName, POWERTOOLS_TRACER_CAPTURE_RESPONSE: 'true', @@ -95,10 +106,30 @@ describe(`Tracer E2E tests, asynchronous handler with decorator instantiation fo }); ddbTable.grantWriteData(functionWithAllFlagsEnabled); + const functionWithCustomSubsegmentNameInMethod = createTracerTestFunction({ + stack, + functionName: functionNameWithCustomSubsegmentNameInMethod, + handler: 'handlerWithCustomSubsegmentNameInMethod', + entry, + expectedServiceName: serviceNameWithCustomSubsegmentNameInMethod, + environmentParams: { + TEST_TABLE_NAME: ddbTableName, + EXPECTED_CUSTOM_SUBSEGMENT_NAME: expectedCustomSubSegmentName, + POWERTOOLS_TRACER_CAPTURE_RESPONSE: 'true', + POWERTOOLS_TRACER_CAPTURE_ERROR: 'true', + POWERTOOLS_TRACE_ENABLED: 'true', + }, + runtime + }); + ddbTable.grantWriteData(functionWithCustomSubsegmentNameInMethod); + await deployStack(integTestApp, stack); // Act - await invokeAllTestCases(functionName); + await Promise.all([ + invokeAllTestCases(functionNameWithAllFlagsEnabled), + invokeAllTestCases(functionNameWithCustomSubsegmentNameInMethod), + ]); }, SETUP_TIMEOUT); @@ -110,7 +141,7 @@ describe(`Tracer E2E tests, asynchronous handler with decorator instantiation fo it('should generate all custom traces', async () => { - const tracesWhenAllFlagsEnabled = await getTraces(xray, startTime, await getFunctionArn(functionName), invocations, 5); + const tracesWhenAllFlagsEnabled = await getTraces(xray, startTime, await getFunctionArn(functionNameWithAllFlagsEnabled), invocations, 5); expect(tracesWhenAllFlagsEnabled.length).toBe(invocations); @@ -159,7 +190,7 @@ describe(`Tracer E2E tests, asynchronous handler with decorator instantiation fo }, TEST_CASE_TIMEOUT); it('should have correct annotations and metadata', async () => { - const traces = await getTraces(xray, startTime, await getFunctionArn(functionName), invocations, 5); + const traces = await getTraces(xray, startTime, await getFunctionArn(functionNameWithAllFlagsEnabled), invocations, 5); for (let i = 0; i < invocations; i++) { const trace = traces[i]; @@ -171,7 +202,7 @@ describe(`Tracer E2E tests, asynchronous handler with decorator instantiation fo assertAnnotation({ annotations, isColdStart, - expectedServiceName: serviceName, + expectedServiceName: serviceNameWithAllFlagsEnabled, expectedCustomAnnotationKey, expectedCustomAnnotationValue, }); @@ -179,16 +210,66 @@ describe(`Tracer E2E tests, asynchronous handler with decorator instantiation fo if (!metadata) { fail('metadata is missing'); } - expect(metadata[serviceName][expectedCustomMetadataKey]) + expect(metadata[serviceNameWithAllFlagsEnabled][expectedCustomMetadataKey]) .toEqual(expectedCustomMetadataValue); const shouldThrowAnError = (i === (invocations - 1)); if (!shouldThrowAnError) { // Assert that the metadata object contains the response - expect(metadata[serviceName]['index.handler response']) + expect(metadata[serviceNameWithAllFlagsEnabled]['index.handler response']) .toEqual(expectedCustomResponseValue); } } }, TEST_CASE_TIMEOUT); + + it('should have a custom name as the subsegment\'s name for the decorated method', async () => { + + const tracesWhenCustomSubsegmentNameInMethod = await getTraces(xray, startTime, await getFunctionArn(functionNameWithCustomSubsegmentNameInMethod), invocations, 5); + + expect(tracesWhenCustomSubsegmentNameInMethod.length).toBe(invocations); + + // Assess + for (let i = 0; i < invocations; i++) { + const trace = tracesWhenCustomSubsegmentNameInMethod[i]; + + /** + * Expect the trace to have 5 segments: + * 1. Lambda Context (AWS::Lambda) + * 2. Lambda Function (AWS::Lambda::Function) + * 3. DynamoDB (AWS::DynamoDB) + * 4. DynamoDB Table (AWS::DynamoDB::Table) + * 5. Remote call (httpbin.org) + */ + expect(trace.Segments.length).toBe(5); + const invocationSubsegment = getInvocationSubsegment(trace); + + /** + * Invocation subsegment should have a subsegment '## index.handler' (default behavior for PowerTool tracer) + * '## index.handler' subsegment should have 4 subsegments + * 1. DynamoDB (PutItem on the table) + * 2. DynamoDB (PutItem overhead) + * 3. httpbin.org (Remote call) + * 4. '### mySubsegment' (method decorator with custom name) + */ + const handlerSubsegment = getFirstSubsegment(invocationSubsegment); + expect(handlerSubsegment.name).toBe('## index.handlerWithCustomSubsegmentNameInMethod'); + expect(handlerSubsegment?.subsegments).toHaveLength(4); + + if (!handlerSubsegment.subsegments) { + fail('"## index.handler" subsegment should have subsegments'); + } + const subsegments = splitSegmentsByName(handlerSubsegment.subsegments, [ 'DynamoDB', 'httpbin.org', expectedCustomSubSegmentName ]); + expect(subsegments.get('DynamoDB')?.length).toBe(2); + expect(subsegments.get('httpbin.org')?.length).toBe(1); + expect(subsegments.get(expectedCustomSubSegmentName)?.length).toBe(1); + expect(subsegments.get('other')?.length).toBe(0); + + const shouldThrowAnError = (i === (invocations - 1)); + if (shouldThrowAnError) { + assertErrorAndFault(invocationSubsegment, expectedCustomErrorMessage); + } + } + + }, TEST_CASE_TIMEOUT); }); diff --git a/packages/tracer/tests/e2e/constants.ts b/packages/tracer/tests/e2e/constants.ts index 6d738a3d36..b16ff806dd 100644 --- a/packages/tracer/tests/e2e/constants.ts +++ b/packages/tracer/tests/e2e/constants.ts @@ -9,4 +9,5 @@ export const expectedCustomAnnotationValue = 'myValue'; export const expectedCustomMetadataKey = 'myMetadata'; export const expectedCustomMetadataValue = { bar: 'baz' }; export const expectedCustomResponseValue = { foo: 'bar' }; -export const expectedCustomErrorMessage = 'An error has occurred'; \ No newline at end of file +export const expectedCustomErrorMessage = 'An error has occurred'; +export const expectedCustomSubSegmentName = 'mySubsegment'; \ No newline at end of file diff --git a/packages/tracer/tests/unit/Tracer.test.ts b/packages/tracer/tests/unit/Tracer.test.ts index 4e77ebf490..ce1acc976e 100644 --- a/packages/tracer/tests/unit/Tracer.test.ts +++ b/packages/tracer/tests/unit/Tracer.test.ts @@ -15,10 +15,12 @@ interface LambdaInterface { } type CaptureAsyncFuncMock = jest.SpyInstance unknown, parent?: Segment | Subsegment]>; -const createCaptureAsyncFuncMock = function(provider: ProviderServiceInterface): CaptureAsyncFuncMock { +const createCaptureAsyncFuncMock = function(provider: ProviderServiceInterface, subsegment?: Subsegment): CaptureAsyncFuncMock { return jest.spyOn(provider, 'captureAsyncFunc') .mockImplementation(async (methodName, callBackFn) => { - const subsegment = new Subsegment(`### ${methodName}`); + if (!subsegment) { + subsegment = new Subsegment(`### ${methodName}`); + } jest.spyOn(subsegment, 'flush').mockImplementation(() => null); await callBackFn(subsegment); }); @@ -956,6 +958,56 @@ describe('Class: Tracer', () => { expect(await handler({}, context, () => console.log('Lambda invoked!'))).toEqual('memberVariable:someValue'); }); + + test('when used as decorator on an async method, the method is awaited correctly', async () => { + + // Prepare + const tracer: Tracer = new Tracer(); + const newSubsegment: Segment | Subsegment | undefined = new Subsegment('### dummyMethod'); + + jest.spyOn(tracer.provider, 'getSegment') + .mockImplementation(() => newSubsegment); + setContextMissingStrategy(() => null); + const subsegmentCloseSpy = jest.spyOn(newSubsegment, 'close').mockImplementation(); + createCaptureAsyncFuncMock(tracer.provider, newSubsegment); + + class Lambda implements LambdaInterface { + public async dummyMethod(): Promise { + return; + } + + @tracer.captureLambdaHandler() + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public async handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { + await this.dummyMethod(); + this.otherDummyMethod(); + + return; + } + + public otherDummyMethod(): void { + return; + } + + } + + // Act + const lambda = new Lambda(); + const otherDummyMethodSpy = jest.spyOn(lambda, 'otherDummyMethod').mockImplementation(); + const handler = lambda.handler.bind(lambda); + await handler({}, context, () => console.log('Lambda invoked!')); + + // Assess + // Here we assert that the otherDummyMethodSpy method is called before the cleanup logic (inside the finally of decorator) + // that should always be called after the handler has returned. If otherDummyMethodSpy is called after it means the + // decorator is NOT awaiting the handler which would cause the test to fail. + const dummyCallOrder = subsegmentCloseSpy.mock.invocationCallOrder[0]; + const otherDummyCallOrder = otherDummyMethodSpy.mock.invocationCallOrder[0]; + expect(otherDummyCallOrder).toBeLessThan(dummyCallOrder); + + }); + }); describe('Method: captureMethod', () => { @@ -1239,6 +1291,150 @@ describe('Class: Tracer', () => { }); + test('when used as decorator on an async method, the method is awaited correctly', async () => { + + // Prepare + const tracer: Tracer = new Tracer(); + const newSubsegment: Segment | Subsegment | undefined = new Subsegment('### dummyMethod'); + + jest.spyOn(tracer.provider, 'getSegment') + .mockImplementation(() => newSubsegment); + setContextMissingStrategy(() => null); + const subsegmentCloseSpy = jest.spyOn(newSubsegment, 'close').mockImplementation(); + createCaptureAsyncFuncMock(tracer.provider, newSubsegment); + + class Lambda implements LambdaInterface { + @tracer.captureMethod() + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public async dummyMethod(): Promise { + return; + } + + public async handler(_event: TEvent, _context: Context, _callback: Callback): Promise { + await this.dummyMethod(); + this.otherDummyMethod(); + + return; + } + + public otherDummyMethod(): void { + return; + } + } + + // Act + const lambda = new Lambda(); + const otherDummyMethodSpy = jest.spyOn(lambda, 'otherDummyMethod').mockImplementation(); + const handler = lambda.handler.bind(lambda); + await handler({}, context, () => console.log('Lambda invoked!')); + + // Here we assert that the subsegment.close() (inside the finally of decorator) is called before the other otherDummyMethodSpy method + // that should always be called after the handler has returned. If subsegment.close() is called after it means the + // decorator is NOT awaiting the method which would cause the test to fail. + const dummyCallOrder = subsegmentCloseSpy.mock.invocationCallOrder[0]; + const otherDummyCallOrder = otherDummyMethodSpy.mock.invocationCallOrder[0]; + expect(dummyCallOrder).toBeLessThan(otherDummyCallOrder); + + }); + + test('when used as decorator together with another external decorator, the method name is detected properly', async () => { + + // Prepare + const tracer: Tracer = new Tracer(); + const newSubsegment: Segment | Subsegment | undefined = new Subsegment('### dummyMethod'); + jest.spyOn(tracer.provider, 'getSegment') + .mockImplementation(() => newSubsegment); + setContextMissingStrategy(() => null); + createCaptureAsyncFuncMock(tracer.provider, newSubsegment); + + // Creating custom external decorator + // eslint-disable-next-line func-style + function passThrough() { + // A decorator that calls the original method. + return ( + _target: unknown, + _propertyKey: string, + descriptor: PropertyDescriptor + ) => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const originalMethod = descriptor.value!; + descriptor.value = function (...args: unknown[]) { + return originalMethod.apply(this, [...args]); + }; + }; + } + + class Lambda implements LambdaInterface { + @tracer.captureMethod() + @passThrough() + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public async dummyMethod(): Promise { + return `foo`; + } + + public async handler(_event: TEvent, _context: Context, _callback: Callback): Promise { + await this.dummyMethod(); + + return; + } + + } + + // Act / Assess + const lambda = new Lambda(); + const handler = lambda.handler.bind(lambda); + await handler({}, context, () => console.log('Lambda invoked!')); + + // Assess + expect(newSubsegment).toEqual(expect.objectContaining({ + metadata: { + 'hello-world': { + // Assess that the method name is added correctly + 'dummyMethod response': 'foo', + }, + } + })); + + }); + + test('when used as decorator and with a custom subSegmentName, it sets the correct name for the subsegment', async () => { + + // Prepare + const tracer: Tracer = new Tracer(); + const newSubsegment: Segment | Subsegment | undefined = new Subsegment('### dummyMethod'); + jest.spyOn(newSubsegment, 'flush').mockImplementation(() => null); + jest.spyOn(tracer.provider, 'getSegment') + .mockImplementation(() => newSubsegment); + setContextMissingStrategy(() => null); + const captureAsyncFuncSpy = jest.spyOn(tracer.provider, 'captureAsyncFunc'); + class Lambda implements LambdaInterface { + + @tracer.captureMethod({ subSegmentName: '#### myCustomMethod' }) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public async dummyMethod(some: string): Promise { + return new Promise((resolve, _reject) => setTimeout(() => resolve(some), 3000)); + } + + public async handler(_event: TEvent, _context: Context, _callback: Callback): Promise { + const result = await this.dummyMethod('foo bar'); + + return new Promise((resolve, _reject) => resolve(result as unknown as TResult)); + } + + } + + // Act + await new Lambda().handler(event, context, () => console.log('Lambda invoked!')); + + // Assess + expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); + expect(captureAsyncFuncSpy).toHaveBeenCalledWith('#### myCustomMethod', expect.anything()); + + }); + }); describe('Method: captureAWS', () => {