From 8557783dc446f80f60e22479ad85fafd18139d7e Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 2 Mar 2023 16:09:36 +0100 Subject: [PATCH 01/26] chore(ci): fix layer publisher commands --- .github/workflows/publish_layer.yml | 4 ++-- layers/package.json | 2 +- lerna.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish_layer.yml b/.github/workflows/publish_layer.yml index 0ba75b0b63..0d2212fdd6 100644 --- a/.github/workflows/publish_layer.yml +++ b/.github/workflows/publish_layer.yml @@ -65,8 +65,8 @@ jobs: bash .github/scripts/setup_tmp_layer_files.sh - name: CDK build run: npm run cdk -w layers -- synth --context PowertoolsPackageVersion=$RELEASE_TAG_VERSION -o cdk.out - - name: zip output - run: zip -r cdk.out.zip cdk.out + - name: Zip output + run: zip -r cdk.out.zip layers/cdk.out - name: Archive CDK artifacts uses: actions/upload-artifact@v3 with: diff --git a/layers/package.json b/layers/package.json index b8803973df..973cd84369 100644 --- a/layers/package.json +++ b/layers/package.json @@ -1,6 +1,6 @@ { "name": "layers", - "version": "1.5.1", + "version": "1.6.0", "bin": { "layer": "bin/layers.js" }, diff --git a/lerna.json b/lerna.json index 7b3656a4c6..ea913fecdd 100644 --- a/lerna.json +++ b/lerna.json @@ -6,7 +6,7 @@ "packages/metrics", "examples/cdk", "examples/sam", - "layer-publisher" + "layers" ], "version": "1.6.0", "npmClient": "npm", From 7bb5ed97127de0cf85b3345b677cd9cfa3874144 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 2 Mar 2023 16:13:34 +0100 Subject: [PATCH 02/26] chore(ci): fix layer publisher commands --- .github/workflows/publish_layer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish_layer.yml b/.github/workflows/publish_layer.yml index 0d2212fdd6..d25c1fd3ea 100644 --- a/.github/workflows/publish_layer.yml +++ b/.github/workflows/publish_layer.yml @@ -71,7 +71,7 @@ jobs: uses: actions/upload-artifact@v3 with: name: cdk-layer-artifact - path: layers/cdk.out.zip + path: cdk.out.zip # Deploy layer to all regions in beta account deploy-beta: From c0a60cf90682f181857d6539eaafb99cb2c8ad43 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 2 Mar 2023 16:19:37 +0100 Subject: [PATCH 03/26] chore(ci): fix layer publisher commands --- .github/workflows/reusable_deploy_layer_stack.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/reusable_deploy_layer_stack.yml b/.github/workflows/reusable_deploy_layer_stack.yml index 8222e7811e..0f8f8b8f87 100644 --- a/.github/workflows/reusable_deploy_layer_stack.yml +++ b/.github/workflows/reusable_deploy_layer_stack.yml @@ -87,7 +87,6 @@ jobs: uses: actions/download-artifact@v3 with: name: ${{ inputs.artifact-name }} - path: layers - name: Unzip artifact run: unzip cdk.out.zip - name: Deploy Layer From 259bad77df441fee4879565bdca80b7362db940f Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 2 Mar 2023 16:27:16 +0100 Subject: [PATCH 04/26] chore(ci): fix layer publisher commands --- .github/workflows/reusable_deploy_layer_stack.yml | 4 ++-- .github/workflows/reusable_update_layer_arn_docs.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/reusable_deploy_layer_stack.yml b/.github/workflows/reusable_deploy_layer_stack.yml index 0f8f8b8f87..1f1d2ec9a6 100644 --- a/.github/workflows/reusable_deploy_layer_stack.yml +++ b/.github/workflows/reusable_deploy_layer_stack.yml @@ -95,14 +95,14 @@ jobs: if: ${{ inputs.stage == 'PROD' }} run: | mkdir cdk-layer-stack - jq -r -c '.LayerPublisherStack.LatestLayerArn' cdk-outputs.json > cdk-layer-stack/${{ matrix.region }}-layer-version.txt + jq -r -c '.LayerPublisherStack.LatestLayerArn' layers/cdk-outputs.json > cdk-layer-stack/${{ matrix.region }}-layer-version.txt cat cdk-layer-stack/${{ matrix.region }}-layer-version.txt - name: Save Layer ARN artifact if: ${{ inputs.stage == 'PROD' }} uses: actions/upload-artifact@v3 with: name: cdk-layer-stack - path: ./layer/cdk-layer-stack/* # NOTE: upload-artifact does not inherit working-directory setting. + path: ./cdk-layer-stack/* # NOTE: upload-artifact does not inherit working-directory setting. if-no-files-found: error retention-days: 1 update_layer_arn_docs: diff --git a/.github/workflows/reusable_update_layer_arn_docs.yml b/.github/workflows/reusable_update_layer_arn_docs.yml index db63440d43..5d7e383665 100644 --- a/.github/workflows/reusable_update_layer_arn_docs.yml +++ b/.github/workflows/reusable_update_layer_arn_docs.yml @@ -1,4 +1,4 @@ -name: Update V2 Layer ARN Docs +name: Update Layer ARN Docs on: workflow_call: @@ -28,7 +28,7 @@ jobs: fetch-depth: 0 - name: Git client setup and refresh tip run: | - git config user.name "Release bot" + git config user.name "Release bot[bot]" git config user.email "aws-devax-open-source@amazon.com" git config pull.rebase true git config remote.origin.url >&- || git remote add origin https://github.com/"${origin}" # Git Detached mode (release notes) doesn't have origin From 8e19d75c246c799df64717b3c72bc3fcbe04cb1e Mon Sep 17 00:00:00 2001 From: "Release bot[bot]" Date: Thu, 2 Mar 2023 15:43:27 +0000 Subject: [PATCH 05/26] chore: update layer ARN on documentation --- docs/index.md | 64 +++++++++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/index.md b/docs/index.md index 2f9eb56ff0..abf09f5b47 100644 --- a/docs/index.md +++ b/docs/index.md @@ -26,7 +26,7 @@ You can use Powertools in both TypeScript and JavaScript code bases. Powertools is available in the following formats: -* **Lambda Layer**: [**arn:aws:lambda:{region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:8**](#){: .copyMe}:clipboard: +* **Lambda Layer**: [**arn:aws:lambda:{region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:9**](#){: .copyMe}:clipboard: * **npm**: **`npm install @aws-lambda-powertools/tracer @aws-lambda-powertools/metrics @aws-lambda-powertools/logger`** ### Lambda Layer @@ -39,29 +39,29 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: | Region | Layer ARN | | ---------------- | ----------------------------------------------------------------------------------------------------------- | - | `us-east-1` | [arn:aws:lambda:us-east-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | - | `us-east-2` | [arn:aws:lambda:us-east-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | - | `us-west-1` | [arn:aws:lambda:us-west-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | - | `us-west-2` | [arn:aws:lambda:us-west-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | - | `ap-south-1` | [arn:aws:lambda:ap-south-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | - | `ap-east-1` | [arn:aws:lambda:ap-east-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | - | `ap-northeast-1` | [arn:aws:lambda:ap-northeast-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | - | `ap-northeast-2` | [arn:aws:lambda:ap-northeast-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | - | `ap-northeast-3` | [arn:aws:lambda:ap-northeast-3:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | - | `ap-southeast-1` | [arn:aws:lambda:ap-southeast-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | - | `ap-southeast-2` | [arn:aws:lambda:ap-southeast-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | - | `ap-southeast-3` | [arn:aws:lambda:ap-southeast-3:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | - | `ap-southeast-4` | [arn:aws:lambda:ap-southeast-4:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | - | `eu-central-1` | [arn:aws:lambda:eu-central-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | - | `eu-central-2` | [arn:aws:lambda:eu-central-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | - | `eu-west-1` | [arn:aws:lambda:eu-west-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | - | `eu-west-2` | [arn:aws:lambda:eu-west-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | - | `eu-west-3` | [arn:aws:lambda:eu-west-3:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | - | `eu-north-1` | [arn:aws:lambda:eu-north-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | - | `eu-south-1` | [arn:aws:lambda:eu-south-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | - | `eu-south-2` | [arn:aws:lambda:eu-south-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | - | `ca-central-1` | [arn:aws:lambda:ca-central-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | - | `sa-east-1` | [arn:aws:lambda:sa-east-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | + | `us-east-1` | [arn:aws:lambda:us-east-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | + | `us-east-2` | [arn:aws:lambda:us-east-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | + | `us-west-1` | [arn:aws:lambda:us-west-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | + | `us-west-2` | [arn:aws:lambda:us-west-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | + | `ap-south-1` | [arn:aws:lambda:ap-south-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | + | `ap-east-1` | [arn:aws:lambda:ap-east-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | + | `ap-northeast-1` | [arn:aws:lambda:ap-northeast-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | + | `ap-northeast-2` | [arn:aws:lambda:ap-northeast-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | + | `ap-northeast-3` | [arn:aws:lambda:ap-northeast-3:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | + | `ap-southeast-1` | [arn:aws:lambda:ap-southeast-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | + | `ap-southeast-2` | [arn:aws:lambda:ap-southeast-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | + | `ap-southeast-3` | [arn:aws:lambda:ap-southeast-3:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | + | `ap-southeast-4` | [arn:aws:lambda:ap-southeast-4:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | + | `eu-central-1` | [arn:aws:lambda:eu-central-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | + | `eu-central-2` | [arn:aws:lambda:eu-central-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | + | `eu-west-1` | [arn:aws:lambda:eu-west-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | + | `eu-west-2` | [arn:aws:lambda:eu-west-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | + | `eu-west-3` | [arn:aws:lambda:eu-west-3:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | + | `eu-north-1` | [arn:aws:lambda:eu-north-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | + | `eu-south-1` | [arn:aws:lambda:eu-south-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | + | `eu-south-2` | [arn:aws:lambda:eu-south-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | + | `ca-central-1` | [arn:aws:lambda:ca-central-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | + | `sa-east-1` | [arn:aws:lambda:sa-east-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | | `af-south-1` | [arn:aws:lambda:af-south-11:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | | `me-south-1` | [arn:aws:lambda:me-south-11:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | @@ -74,7 +74,7 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: Type: AWS::Serverless::Function Properties: Layers: - - !Sub arn:aws:lambda:${AWS::Region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:8 + - !Sub arn:aws:lambda:${AWS::Region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:9 ``` If you use `esbuild` to bundle your code, make sure to exclude `@aws-lambda-powertools` from being bundled since the packages will be already present the Layer: @@ -105,7 +105,7 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: hello: handler: lambda_function.lambda_handler layers: - - arn:aws:lambda:${aws::region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:8 + - arn:aws:lambda:${aws::region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:9 ``` If you use `esbuild` to bundle your code, make sure to exclude `@aws-lambda-powertools` from being bundled since the packages will be already present the Layer: @@ -137,7 +137,7 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: const powertoolsLayer = lambda.LayerVersion.fromLayerVersionArn( this, 'PowertoolsLayer', - `arn:aws:lambda:${cdk.Stack.of(this).region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:8` + `arn:aws:lambda:${cdk.Stack.of(this).region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:9` ); new lambda.Function(this, 'Function', { @@ -189,7 +189,7 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: role = ... handler = "index.handler" runtime = "nodejs16.x" - layers = ["arn:aws:lambda:{aws::region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:8"] + layers = ["arn:aws:lambda:{aws::region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:9"] source_code_hash = filebase64sha256("lambda_function_payload.zip") } ``` @@ -207,7 +207,7 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: const lambdaFunction = new aws.lambda.Function("function", { layers: [ - pulumi.interpolate`arn:aws:lambda:${aws.getRegionOutput().name}:094274105915:layer:AWSLambdaPowertoolsTypeScript:8` + pulumi.interpolate`arn:aws:lambda:${aws.getRegionOutput().name}:094274105915:layer:AWSLambdaPowertoolsTypeScript:9` ], code: new pulumi.asset.FileArchive("lambda_function_payload.zip"), tracingConfig: { @@ -231,7 +231,7 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: ? 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:{aws::region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:8 + ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:{aws::region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:9 ❯ amplify push -y # Updating an existing function and add the layer @@ -241,13 +241,13 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: - 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:{aws::region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:8 + ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:{aws::region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:9 ? Do you want to edit the local lambda function now? No ``` === "Get the Layer .zip contents" ```bash title="AWS CLI" - aws lambda get-layer-version-by-arn --arn arn:aws:lambda:{aws::region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:8 --region {region} + aws lambda get-layer-version-by-arn --arn arn:aws:lambda:{aws::region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:9 --region {region} ``` The pre-signed URL to download this Lambda Layer will be within `Location` key. From a59028420baad6cf07f2b61e7c5aeae8d52214e1 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 2 Mar 2023 16:51:23 +0100 Subject: [PATCH 06/26] chore(ci): remove unused command in docs publish --- .github/workflows/reusable-publish-docs.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/reusable-publish-docs.yml b/.github/workflows/reusable-publish-docs.yml index 9a7f114ebb..16efad0ea9 100644 --- a/.github/workflows/reusable-publish-docs.yml +++ b/.github/workflows/reusable-publish-docs.yml @@ -75,10 +75,6 @@ jobs: git config pull.rebase true git config remote.origin.url >&- || git remote add origin https://github.com/"$ORIGIN" git pull origin "$BRANCH" - - name: Build docs website and API reference - run: | - make release-docs VERSION="$VERSION" ALIAS="$ALIAS" - poetry run mike set-default --push latest - name: Build docs website and API reference env: VERSION: ${{ inputs.version }} From 36d3a90ef47c5185bf5429fbdaaf4a3f376312fe Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 2 Mar 2023 16:56:14 +0100 Subject: [PATCH 07/26] chore(ci): add workflow to trigger rebuild latest docs --- .github/workflows/rebuild-latest-docs.yml | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/rebuild-latest-docs.yml diff --git a/.github/workflows/rebuild-latest-docs.yml b/.github/workflows/rebuild-latest-docs.yml new file mode 100644 index 0000000000..e2a559e94b --- /dev/null +++ b/.github/workflows/rebuild-latest-docs.yml @@ -0,0 +1,24 @@ +name: Rebuild latest docs + +# +# === Documentation hotfix === +# +# 1. Trigger "Rebuild latest docs" workflow manually: https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow +# 2. Use the latest version released under Releases e.g. 1.6.0 + +on: + workflow_dispatch: + inputs: + latest_published_version: + description: "Latest npm published version to rebuild latest docs for, e.g. 1.6.0" + required: true + +jobs: + release-docs: + permissions: + contents: write + pages: write + uses: ./.github/workflows/reusable-publish-docs.yml + with: + version: ${{ inputs.latest_published_version }} + alias: latest \ No newline at end of file From 9f690769a5d5341408b7d8a3471c75270a1e11fa Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 2 Mar 2023 17:18:55 +0100 Subject: [PATCH 08/26] chore(ci): fix build docs --- .github/workflows/reusable-publish-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-publish-docs.yml b/.github/workflows/reusable-publish-docs.yml index 16efad0ea9..af731745e6 100644 --- a/.github/workflows/reusable-publish-docs.yml +++ b/.github/workflows/reusable-publish-docs.yml @@ -82,7 +82,7 @@ jobs: run: | rm -rf site mkdocs build - mike deploy --push --update-aliases --no-redirect ${{ env.VERSION }} ${{ env.ALIAS }}" + mike deploy --push --update-aliases --no-redirect ${{ env.VERSION }} ${{ env.ALIAS }} # Set latest version as a default mike set-default --push latest From bc5f7c99e02396223e726962432fc3856a68a29d Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 2 Mar 2023 17:25:48 +0100 Subject: [PATCH 09/26] fix(docs): typo in layer arn --- docs/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index abf09f5b47..2b2654c7bd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -62,8 +62,8 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: | `eu-south-2` | [arn:aws:lambda:eu-south-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | | `ca-central-1` | [arn:aws:lambda:ca-central-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | | `sa-east-1` | [arn:aws:lambda:sa-east-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | - | `af-south-1` | [arn:aws:lambda:af-south-11:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | - | `me-south-1` | [arn:aws:lambda:me-south-11:094274105915:layer:AWSLambdaPowertoolsTypeScript:8](#){: .copyMe}:clipboard: | + | `af-south-1` | [arn:aws:lambda:af-south-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | + | `me-south-1` | [arn:aws:lambda:me-south-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:9](#){: .copyMe}:clipboard: | ??? note "Note: Click to expand and copy code snippets for popular frameworks" From 8b9d1580914c3b1789ee9fb71c06d8702ffa059c Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 2 Mar 2023 17:31:06 +0100 Subject: [PATCH 10/26] chore(ci): fix workflow input --- .github/workflows/on_doc_merge.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/on_doc_merge.yml b/.github/workflows/on_doc_merge.yml index ff1e5ec487..ba2574fdc5 100644 --- a/.github/workflows/on_doc_merge.yml +++ b/.github/workflows/on_doc_merge.yml @@ -15,7 +15,6 @@ jobs: pages: write uses: ./.github/workflows/reusable-publish-docs.yml with: - workflow_origin: ${{ github.event.repository.full_name }} version: dev alias: stage secrets: From c82939ebdb82ae596cbad07be397794ee4b69fe5 Mon Sep 17 00:00:00 2001 From: Sergei Cherniaev Date: Fri, 3 Mar 2023 18:02:22 +0300 Subject: [PATCH 11/26] feat(logger): add silent log level to suppress the emission of all logs (#1347) * feat(logger): add silent log level to suppress the emission of all logs * test: cleanup * docs(logger): update text and formatting, add comments * docs(logger): update table formatting * docs(logger): add section about silencing logs --- docs/core/logger.md | 25 ++-- packages/logger/src/Logger.ts | 2 + packages/logger/src/types/Log.ts | 3 +- packages/logger/tests/unit/Logger.test.ts | 127 ++++++++++----------- packages/logger/tests/unit/helpers.test.ts | 24 ++-- 5 files changed, 95 insertions(+), 86 deletions(-) diff --git a/docs/core/logger.md b/docs/core/logger.md index fb25804135..a36660baa3 100644 --- a/docs/core/logger.md +++ b/docs/core/logger.md @@ -46,12 +46,12 @@ The library requires two settings. You can set them as environment variables, or These settings will be used across all logs emitted: -| Setting | Description | Environment variable | Default Value | Allowed Values | Example Value | Constructor parameter | -|-------------------------|------------------------------------------------------------------------------------------------------------------|---------------------------------|---------------------|--------------------------------|--------------------|-----------------------| -| **Service name** | Sets the name of service of which the Lambda function is part of, that will be present across all log statements | `POWERTOOLS_SERVICE_NAME` | `service_undefined` | Any string | `serverlessAirline`| `serviceName` | -| **Logging level** | Sets how verbose Logger should be | `LOG_LEVEL` | `info` |`DEBUG`, `INFO`, `WARN`, `ERROR`| `ERROR` | `logLevel` | -| **Log incoming event** | Whether to log or not the incoming event when using the decorator or middleware. | `POWERTOOLS_LOGGER_LOG_EVENT` | `false` | `true`, `false` | `false` | `logEvent` | -| **Debug log sampling** | Probability that a Lambda invocation will print all the log items regardless of the log level setting. | `POWERTOOLS_LOGGER_SAMPLE_RATE` | `0` | `0.0` to `1` | `0.5` | `sampleRateValue` | +| Setting | Description | Environment variable | Default Value | Allowed Values | Example Value | Constructor parameter | +|-------------------------|------------------------------------------------------------------------------------------------------------------|---------------------------------|---------------------|-------------------------------------------|--------------------|-----------------------| +| **Service name** | Sets the name of service of which the Lambda function is part of, that will be present across all log statements | `POWERTOOLS_SERVICE_NAME` | `service_undefined` | Any string | `serverlessAirline`| `serviceName` | +| **Logging level** | Sets how verbose Logger should be, from the most verbose to the least verbose (no logs) | `LOG_LEVEL` | `info` |`DEBUG`, `INFO`, `WARN`, `ERROR`, `SILENT` | `ERROR` | `logLevel` | +| **Log incoming event** | Whether to log or not the incoming event when using the decorator or middleware | `POWERTOOLS_LOGGER_LOG_EVENT` | `false` | `true`, `false` | `false` | `logEvent` | +| **Debug log sampling** | Probability that a Lambda invocation will print all the log items regardless of the log level setting | `POWERTOOLS_LOGGER_SAMPLE_RATE` | `0` | `0.0` to `1` | `0.5` | `sampleRateValue` | #### Example using AWS Serverless Application Model (SAM) @@ -81,7 +81,7 @@ Your Logger will include the following keys to your structured logging (default | Key | Example | Note | |-----------------------------|------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **level**: `string` | `INFO` | Logging level set for the Lambda function"s invocation | +| **level**: `string` | `INFO` | Logging level set for the Lambda function's invocation | | **message**: `string` | `Query performed to DynamoDB` | A descriptive, human-readable representation of this log item | | **sampling_rate**: `float` | `0.1` | When enabled, it prints all the logs of a percentage of invocations, e.g. 10% | | **service**: `string` | `serverlessAirline` | A unique name identifier of the service this Lambda function belongs to, by default `service_undefined` | @@ -555,6 +555,17 @@ For example, by setting the "sample rate" to `0.5`, roughly 50% of your lambda i } ``` +### Silencing logs + +The `SILENT` log level provides a simple and efficient way to suppress all log messages without the need to modify your code. When you set this log level, all log messages, regardless of their severity, will be silenced. + +This feature is useful when you want to have your code instrumented to produce logs, but due to some requirement or business decision, you prefer to not emit them. + +By setting the log level to `SILENT`, which can be done either through the `logLevel` constructor option or by using the `LOG_LEVEL` environment variable, you can easily suppress all logs as needed. + +!!! note + Use the `SILENT` log level with care, as it can make it more challenging to monitor and debug your application. Therefore, we advise using this log level judiciously. + ### Custom Log formatter (Bring Your Own Formatter) You can customize the structure (keys and values) of your log items by passing a custom log formatter, an object that implements the `LogFormatter` abstract class. diff --git a/packages/logger/src/Logger.ts b/packages/logger/src/Logger.ts index 663ce2bd37..13f80db3ed 100644 --- a/packages/logger/src/Logger.ts +++ b/packages/logger/src/Logger.ts @@ -130,11 +130,13 @@ class Logger extends Utility implements ClassThatLogs { private logLevel?: Uppercase; + // Log levels are in ascending order from the most verbose to the least verbose (no logs) private readonly logLevelThresholds: LogLevelThresholds = { DEBUG: 8, INFO: 12, WARN: 16, ERROR: 20, + SILENT: 24, }; private logsSampled: boolean = false; diff --git a/packages/logger/src/types/Log.ts b/packages/logger/src/types/Log.ts index 020b9cf200..15042910e5 100644 --- a/packages/logger/src/types/Log.ts +++ b/packages/logger/src/types/Log.ts @@ -2,8 +2,9 @@ type LogLevelDebug = 'DEBUG'; type LogLevelInfo = 'INFO'; type LogLevelWarn = 'WARN'; type LogLevelError = 'ERROR'; +type LogLevelSilent = 'SILENT'; -type LogLevel = LogLevelDebug | Lowercase | LogLevelInfo | Lowercase | LogLevelWarn | Lowercase | LogLevelError | Lowercase; +type LogLevel = LogLevelDebug | Lowercase | LogLevelInfo | Lowercase | LogLevelWarn | Lowercase | LogLevelError | Lowercase | LogLevelSilent | Lowercase; type LogLevelThresholds = { [key in Uppercase]: number; diff --git a/packages/logger/tests/unit/Logger.test.ts b/packages/logger/tests/unit/Logger.test.ts index eedb348c81..026e2ccb20 100644 --- a/packages/logger/tests/unit/Logger.test.ts +++ b/packages/logger/tests/unit/Logger.test.ts @@ -10,7 +10,7 @@ import { import { createLogger, Logger } from '../../src'; import { EnvironmentVariablesService } from '../../src/config'; import { PowertoolLogFormatter } from '../../src/formatter'; -import { ClassThatLogs, LogJsonIndent, ConstructorOptions } from '../../src/types'; +import { ClassThatLogs, LogJsonIndent, ConstructorOptions, LogLevelThresholds } from '../../src/types'; import { Context } from 'aws-lambda'; import { Console } from 'console'; @@ -22,6 +22,13 @@ describe('Class: Logger', () => { const ENVIRONMENT_VARIABLES = process.env; const context = dummyContext.helloworldContext; const event = dummyEvent.Custom.CustomEvent; + const logLevelThresholds: LogLevelThresholds = { + DEBUG: 8, + INFO: 12, + WARN: 16, + ERROR: 20, + SILENT: 24, + }; beforeEach(() => { dateSpy.mockClear(); @@ -60,9 +67,7 @@ describe('Class: Logger', () => { const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation(); // Act - if (logger[methodOfLogger]) { - logger[methodOfLogger]('foo'); - } + logger[methodOfLogger]('foo'); // Assess expect(consoleSpy).toBeCalledTimes(debugPrints ? 1 : 0); @@ -87,9 +92,7 @@ describe('Class: Logger', () => { const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation(); // Act - if (logger[methodOfLogger]) { - logger[methodOfLogger]('foo'); - } + logger[methodOfLogger]('foo'); // Assess expect(consoleSpy).toBeCalledTimes(infoPrints ? 1 : 0); @@ -114,9 +117,7 @@ describe('Class: Logger', () => { const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation(); // Act - if (logger[methodOfLogger]) { - logger[methodOfLogger]('foo'); - } + logger[methodOfLogger]('foo'); // Assess expect(consoleSpy).toBeCalledTimes(warnPrints ? 1 : 0); @@ -141,9 +142,7 @@ describe('Class: Logger', () => { const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation(); // Act - if (logger[methodOfLogger]) { - logger[methodOfLogger]('foo'); - } + logger[methodOfLogger]('foo'); // Assess expect(consoleSpy).toBeCalledTimes(errorPrints ? 1 : 0); @@ -159,6 +158,41 @@ describe('Class: Logger', () => { }); + test('when the Logger\'s log level is SILENT, it DOES NOT print to stdout', () => { + + // Prepare + const logger: Logger = createLogger({ + logLevel: 'SILENT', + }); + const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation(); + + // Act + logger[methodOfLogger]('foo'); + + // Assess + expect(consoleSpy).toBeCalledTimes(0); + }); + + test('when the Logger\'s log level is set through LOG_LEVEL env variable, it DOES print to stdout', () => { + + // Prepare + process.env.LOG_LEVEL = methodOfLogger.toUpperCase(); + const logger = new Logger(); + const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation(); + + // Act + logger[methodOfLogger]('foo'); + + // Assess + expect(consoleSpy).toBeCalledTimes(1); + expect(consoleSpy).toHaveBeenNthCalledWith(1, JSON.stringify({ + level: methodOfLogger.toUpperCase(), + message: 'foo', + service: 'hello-world', + timestamp: '2016-06-20T12:08:10.000Z', + xray_trace_id: '1-5759e988-bd862e3fe1be46a994272793', + })); + }); }); describe('Feature: sample rate', () => { @@ -169,7 +203,7 @@ describe('Class: Logger', () => { // Prepare const logger: Logger = createLogger({ - logLevel: 'ERROR', + logLevel: 'SILENT', sampleRateValue: 0, }); const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation(); @@ -180,14 +214,14 @@ describe('Class: Logger', () => { } // Assess - expect(consoleSpy).toBeCalledTimes(method === 'error' ? 1 : 0); + expect(consoleSpy).toBeCalledTimes(0); }); test('when the Logger\'s log level is higher and the current Lambda invocation IS sampled for logging, it DOES print to stdout', () => { // Prepare const logger: Logger = createLogger({ - logLevel: 'ERROR', + logLevel: 'SILENT', sampleRateValue: 1, }); const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation(); @@ -630,10 +664,7 @@ describe('Class: Logger', () => { logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'DEBUG', logLevelThresholds: { - DEBUG: 8, - ERROR: 20, - INFO: 12, - WARN: 16, + ...logLevelThresholds }, logsSampled: false, persistentLogAttributes: {}, @@ -1396,10 +1427,7 @@ describe('Class: Logger', () => { logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'DEBUG', logLevelThresholds: { - DEBUG: 8, - ERROR: 20, - INFO: 12, - WARN: 16, + ...logLevelThresholds }, logsSampled: false, persistentLogAttributes: {}, @@ -1422,10 +1450,7 @@ describe('Class: Logger', () => { logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'DEBUG', logLevelThresholds: { - DEBUG: 8, - ERROR: 20, - INFO: 12, - WARN: 16, + ...logLevelThresholds }, logsSampled: true, persistentLogAttributes: {}, @@ -1448,10 +1473,7 @@ describe('Class: Logger', () => { logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'DEBUG', logLevelThresholds: { - DEBUG: 8, - ERROR: 20, - INFO: 12, - WARN: 16, + ...logLevelThresholds }, logsSampled: true, persistentLogAttributes: {}, @@ -1512,10 +1534,7 @@ describe('Class: Logger', () => { logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'DEBUG', logLevelThresholds: { - DEBUG: 8, - ERROR: 20, - INFO: 12, - WARN: 16, + ...logLevelThresholds }, logsSampled: false, persistentLogAttributes: {}, @@ -1538,10 +1557,7 @@ describe('Class: Logger', () => { logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'DEBUG', logLevelThresholds: { - DEBUG: 8, - ERROR: 20, - INFO: 12, - WARN: 16, + ...logLevelThresholds }, logsSampled: false, persistentLogAttributes: { @@ -1566,10 +1582,7 @@ describe('Class: Logger', () => { logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'DEBUG', logLevelThresholds: { - DEBUG: 8, - ERROR: 20, - INFO: 12, - WARN: 16, + ...logLevelThresholds }, logsSampled: true, persistentLogAttributes: {}, @@ -1592,10 +1605,7 @@ describe('Class: Logger', () => { logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'ERROR', logLevelThresholds: { - DEBUG: 8, - ERROR: 20, - INFO: 12, - WARN: 16, + ...logLevelThresholds }, logsSampled: false, persistentLogAttributes: {}, @@ -1641,10 +1651,7 @@ describe('Class: Logger', () => { logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'DEBUG', logLevelThresholds: { - DEBUG: 8, - ERROR: 20, - INFO: 12, - WARN: 16, + ...logLevelThresholds }, logsSampled: false, persistentLogAttributes: {}, @@ -1667,10 +1674,7 @@ describe('Class: Logger', () => { logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'DEBUG', logLevelThresholds: { - DEBUG: 8, - ERROR: 20, - INFO: 12, - WARN: 16, + ...logLevelThresholds }, logsSampled: false, persistentLogAttributes: { @@ -1700,10 +1704,7 @@ describe('Class: Logger', () => { logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'DEBUG', logLevelThresholds: { - DEBUG: 8, - ERROR: 20, - INFO: 12, - WARN: 16, + ...logLevelThresholds }, logsSampled: false, persistentLogAttributes: { @@ -1746,10 +1747,7 @@ describe('Class: Logger', () => { logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'DEBUG', logLevelThresholds: { - DEBUG: 8, - ERROR: 20, - INFO: 12, - WARN: 16, + ...logLevelThresholds }, logsSampled: false, persistentLogAttributes: {}, @@ -1980,5 +1978,4 @@ describe('Class: Logger', () => { }); }); - }); diff --git a/packages/logger/tests/unit/helpers.test.ts b/packages/logger/tests/unit/helpers.test.ts index 31842614fb..7da1226802 100644 --- a/packages/logger/tests/unit/helpers.test.ts +++ b/packages/logger/tests/unit/helpers.test.ts @@ -6,12 +6,19 @@ import { Console } from 'console'; import { ConfigServiceInterface, EnvironmentVariablesService } from '../../src/config'; import { LogFormatter, PowertoolLogFormatter } from '../../src/formatter'; -import { ConstructorOptions } from '../../src/types'; +import { ConstructorOptions, LogLevelThresholds } from '../../src/types'; import { createLogger, Logger } from './../../src'; describe('Helper: createLogger function', () => { const ENVIRONMENT_VARIABLES = process.env; + const logLevelThresholds: LogLevelThresholds = { + DEBUG: 8, + INFO: 12, + WARN: 16, + ERROR: 20, + SILENT: 24, + }; beforeEach(() => { jest.resetModules(); @@ -83,10 +90,7 @@ describe('Helper: createLogger function', () => { logLevel: 'WARN', console: expect.any(Console), logLevelThresholds: { - DEBUG: 8, - ERROR: 20, - INFO: 12, - WARN: 16, + ...logLevelThresholds }, logsSampled: true, persistentLogAttributes: { @@ -125,10 +129,7 @@ describe('Helper: createLogger function', () => { logLevel: 'INFO', console: expect.any(Console), logLevelThresholds: { - DEBUG: 8, - ERROR: 20, - INFO: 12, - WARN: 16, + ...logLevelThresholds }, logsSampled: false, persistentLogAttributes: {}, @@ -279,10 +280,7 @@ describe('Helper: createLogger function', () => { logLevel: 'INFO', console: expect.any(Console), logLevelThresholds: { - DEBUG: 8, - ERROR: 20, - INFO: 12, - WARN: 16, + ...logLevelThresholds }, logsSampled: false, persistentLogAttributes: {}, From 74afc9cb453beaa148f4f54aa9257a1eb570cf02 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Fri, 3 Mar 2023 16:15:42 +0100 Subject: [PATCH 12/26] chore(ci): fix docs workflow secret --- .github/workflows/on_doc_merge.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/on_doc_merge.yml b/.github/workflows/on_doc_merge.yml index ba2574fdc5..6c6c8afd11 100644 --- a/.github/workflows/on_doc_merge.yml +++ b/.github/workflows/on_doc_merge.yml @@ -16,6 +16,4 @@ jobs: uses: ./.github/workflows/reusable-publish-docs.yml with: version: dev - alias: stage - secrets: - token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + alias: stage \ No newline at end of file From 1168066c5ffe6ffe14a9bd5a8380b02b7084b7ec Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Wed, 8 Mar 2023 18:45:40 +0100 Subject: [PATCH 13/26] chore(docs): updated layer info (#1358) --- docs/index.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/index.md b/docs/index.md index 2b2654c7bd..43ffd3213d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -24,13 +24,15 @@ You can use Powertools in both TypeScript and JavaScript code bases. ## Install -Powertools is available in the following formats: +You can install Powertools using one of the following options: * **Lambda Layer**: [**arn:aws:lambda:{region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:9**](#){: .copyMe}:clipboard: -* **npm**: **`npm install @aws-lambda-powertools/tracer @aws-lambda-powertools/metrics @aws-lambda-powertools/logger`** +* **npm**: [`npm install @aws-lambda-powertools/tracer @aws-lambda-powertools/metrics @aws-lambda-powertools/logger`](#){: .copyMe}:clipboard: ### Lambda Layer +???+ warning "As of now, Container Image deployment (OCI) or inline Lambda functions do not support Lambda Layers." + [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. @@ -245,17 +247,16 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: ? Do you want to edit the local lambda function now? No ``` - === "Get the Layer .zip contents" - ```bash title="AWS CLI" - aws lambda get-layer-version-by-arn --arn arn:aws:lambda:{aws::region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:9 --region {region} - ``` - - The pre-signed URL to download this Lambda Layer will be within `Location` key. +!!! info "Using Powertools via Lambda Layer? Simply add the Powertools utilities you are using as a development dependency." -???+ warning "Warning: Limitations" +??? question "Want to inspect the contents of the Layer?" + Change {region} to your AWS region, e.g. `eu-west-1` - Container Image deployment (OCI) or inline Lambda functions do not support Lambda Layers. + ```bash title="AWS CLI" + aws lambda get-layer-version-by-arn --arn arn:aws:lambda:{aws::region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:9 --region {region} + ``` + The pre-signed URL to download this Lambda Layer will be within `Location` key. ## Instrumentation From 90b7117b24962476506810cfc4afe7f259da79c4 Mon Sep 17 00:00:00 2001 From: PatriciaHeimfarth Date: Thu, 9 Mar 2023 21:31:59 +0100 Subject: [PATCH 14/26] chore(examples): fix cdk cleanup command (#1364) --- examples/cdk/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/cdk/README.md b/examples/cdk/README.md index 45c7026bc9..f4a2fd6621 100644 --- a/examples/cdk/README.md +++ b/examples/cdk/README.md @@ -72,8 +72,8 @@ As we have enabled tracing for our Lambda-Funtions, you can visit [AWS CloudWatc ## Cleanup -To delete the sample application that you created, run the command below while in the `examples/sam` directory: +To delete the sample application that you created, run the command below while in the `examples/cdk` directory: ```bash -cdk delete +cdk destroy ``` \ No newline at end of file From c5e8b5777f1cd51d4976c13b397cab613744bf5d Mon Sep 17 00:00:00 2001 From: Alexander Schueren Date: Fri, 10 Mar 2023 14:04:47 +0100 Subject: [PATCH 15/26] chore(ci): restore api build step in docs publishing (#1368) --- .github/workflows/reusable-publish-docs.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/reusable-publish-docs.yml b/.github/workflows/reusable-publish-docs.yml index af731745e6..0dfe60062b 100644 --- a/.github/workflows/reusable-publish-docs.yml +++ b/.github/workflows/reusable-publish-docs.yml @@ -85,7 +85,10 @@ jobs: mike deploy --push --update-aliases --no-redirect ${{ env.VERSION }} ${{ env.ALIAS }} # Set latest version as a default mike set-default --push latest - + - name: Build API docs + run: | + rm -rf api + npm run docs-generateApiDoc - name: Release API docs uses: peaceiris/actions-gh-pages@bd8c6b06eba6b3d25d72b7a1767993c0aeee42e7 # v3.9.2 env: From ce506596ea3cafce50a25614dbf7c220d3a84556 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Fri, 10 Mar 2023 15:26:21 +0100 Subject: [PATCH 16/26] chore: added lint-staged (#1360) --- .husky/pre-commit | 6 +- docs/snippets/package.json | 5 +- examples/cdk/package.json | 5 +- examples/sam/package.json | 5 +- layers/package.json | 3 + package-lock.json | 1372 +++++++++++++++-------------- package.json | 4 +- packages/commons/package.json | 5 +- packages/idempotency/package.json | 3 + packages/logger/package.json | 5 +- packages/metrics/package.json | 5 +- packages/parameters/package.json | 3 + packages/tracer/package.json | 5 +- 13 files changed, 759 insertions(+), 667 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index 7f18baea4f..c37466e2b3 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,8 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -npm run lint-fix -ws -cd examples/sam && npm run lint-fix -cd ../.. -cd examples/cdk && npm run lint-fix -cd ../.. \ No newline at end of file +npx lint-staged \ No newline at end of file diff --git a/docs/snippets/package.json b/docs/snippets/package.json index 8779dea179..345cfb3397 100644 --- a/docs/snippets/package.json +++ b/docs/snippets/package.json @@ -14,6 +14,9 @@ "lint": "eslint --ext .ts --no-error-on-unmatched-pattern logger tracer metrics parameters", "lint-fix": "eslint --fix --ext .ts --no-error-on-unmatched-pattern logger tracer metrics parameters" }, + "lint-staged": { + "*.ts": "npm run lint-fix" + }, "license": "MIT-0", "repository": { "type": "git", @@ -31,4 +34,4 @@ "@aws-sdk/util-dynamodb": "^3.245.0", "axios": "^1.2.4" } -} +} \ No newline at end of file diff --git a/examples/cdk/package.json b/examples/cdk/package.json index e0c57ab26b..7f2dfa61c0 100644 --- a/examples/cdk/package.json +++ b/examples/cdk/package.json @@ -23,6 +23,9 @@ "version": "npm i -D @aws-lambda-powertools/logger@latest @aws-lambda-powertools/tracer@latest @aws-lambda-powertools/metrics@latest && git add package*", "cdk": "cdk" }, + "lint-staged": { + "*.ts": "npm run lint-fix" + }, "devDependencies": { "@aws-lambda-powertools/logger": "^1.5.1", "@aws-lambda-powertools/metrics": "^1.5.1", @@ -51,4 +54,4 @@ "phin": "^3.7.0", "source-map-support": "^0.5.21" } -} +} \ No newline at end of file diff --git a/examples/sam/package.json b/examples/sam/package.json index dcc566393d..837a1198f3 100644 --- a/examples/sam/package.json +++ b/examples/sam/package.json @@ -18,6 +18,9 @@ "test:e2e": "echo 'To be implemented ...'", "version": "npm install @aws-lambda-powertools/logger@latest @aws-lambda-powertools/tracer@latest @aws-lambda-powertools/metrics@latest && git add package.json" }, + "lint-staged": { + "*.ts": "npm run lint-fix" + }, "devDependencies": { "@types/aws-lambda": "^8.10.109", "@types/jest": "^29.2.4", @@ -43,4 +46,4 @@ "@middy/core": "^3.6.2", "phin": "^3.7.0" } -} +} \ No newline at end of file diff --git a/layers/package.json b/layers/package.json index 973cd84369..4178519667 100644 --- a/layers/package.json +++ b/layers/package.json @@ -16,6 +16,9 @@ "test:unit": "jest --group=unit", "test:e2e": "RUNTIME=nodejs14x jest --group=e2e" }, + "lint-staged": { + "*.ts": "npm run lint-fix" + }, "repository": { "type": "git", "url": "git+https://github.com/awslabs/aws-lambda-powertools-typescript.git" diff --git a/package-lock.json b/package-lock.json index 02918f21f8..104f681567 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,6 @@ "devDependencies": { "@aws-cdk/cloudformation-diff": "^2.55.0", "@aws-cdk/cx-api": "^2.55.0", - "@commitlint/cli": "^17.3.0", "@middy/core": "^3.6.2", "@types/aws-lambda": "^8.10.109", "@types/jest": "^29.2.4", @@ -47,6 +46,7 @@ "jest": "^29.3.1", "jest-runner-groups": "^2.2.0", "lerna": "^4.0.0", + "lint-staged": "^13.1.2", "promptly": "^3.2.0", "proxy-agent": "^5.0.0", "ts-jest": "^29.0.3", @@ -86,7 +86,7 @@ } }, "layers": { - "version": "1.5.1", + "version": "1.6.0", "license": "MIT-0", "bin": { "layer": "bin/layers.js" @@ -2122,260 +2122,6 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "node_modules/@commitlint/cli": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-17.3.0.tgz", - "integrity": "sha512-/H0md7TsKflKzVPz226VfXzVafJFO1f9+r2KcFvmBu08V0T56lZU1s8WL7/xlxqLMqBTVaBf7Ixtc4bskdEEZg==", - "dev": true, - "dependencies": { - "@commitlint/format": "^17.0.0", - "@commitlint/lint": "^17.3.0", - "@commitlint/load": "^17.3.0", - "@commitlint/read": "^17.2.0", - "@commitlint/types": "^17.0.0", - "execa": "^5.0.0", - "lodash.isfunction": "^3.0.9", - "resolve-from": "5.0.0", - "resolve-global": "1.0.0", - "yargs": "^17.0.0" - }, - "bin": { - "commitlint": "cli.js" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/config-validator": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-17.1.0.tgz", - "integrity": "sha512-Q1rRRSU09ngrTgeTXHq6ePJs2KrI+axPTgkNYDWSJIuS1Op4w3J30vUfSXjwn5YEJHklK3fSqWNHmBhmTR7Vdg==", - "dev": true, - "dependencies": { - "@commitlint/types": "^17.0.0", - "ajv": "^8.11.0" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/ensure": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-17.3.0.tgz", - "integrity": "sha512-kWbrQHDoW5veIUQx30gXoLOCjWvwC6OOEofhPCLl5ytRPBDAQObMbxTha1Bt2aSyNE/IrJ0s0xkdZ1Gi3wJwQg==", - "dev": true, - "dependencies": { - "@commitlint/types": "^17.0.0", - "lodash.camelcase": "^4.3.0", - "lodash.kebabcase": "^4.1.1", - "lodash.snakecase": "^4.1.1", - "lodash.startcase": "^4.4.0", - "lodash.upperfirst": "^4.3.1" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/execute-rule": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-17.0.0.tgz", - "integrity": "sha512-nVjL/w/zuqjCqSJm8UfpNaw66V9WzuJtQvEnCrK4jDw6qKTmZB+1JQ8m6BQVZbNBcwfYdDNKnhIhqI0Rk7lgpQ==", - "dev": true, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/format": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-17.0.0.tgz", - "integrity": "sha512-MZzJv7rBp/r6ZQJDEodoZvdRM0vXu1PfQvMTNWFb8jFraxnISMTnPBWMMjr2G/puoMashwaNM//fl7j8gGV5lA==", - "dev": true, - "dependencies": { - "@commitlint/types": "^17.0.0", - "chalk": "^4.1.0" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/is-ignored": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-17.2.0.tgz", - "integrity": "sha512-rgUPUQraHxoMLxiE8GK430HA7/R2vXyLcOT4fQooNrZq9ERutNrP6dw3gdKLkq22Nede3+gEHQYUzL4Wu75ndg==", - "dev": true, - "dependencies": { - "@commitlint/types": "^17.0.0", - "semver": "7.3.7" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/lint": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-17.3.0.tgz", - "integrity": "sha512-VilOTPg0i9A7CCWM49E9bl5jytfTvfTxf9iwbWAWNjxJ/A5mhPKbm3sHuAdwJ87tDk1k4j8vomYfH23iaY+1Rw==", - "dev": true, - "dependencies": { - "@commitlint/is-ignored": "^17.2.0", - "@commitlint/parse": "^17.2.0", - "@commitlint/rules": "^17.3.0", - "@commitlint/types": "^17.0.0" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/load": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-17.3.0.tgz", - "integrity": "sha512-u/pV6rCAJrCUN+HylBHLzZ4qj1Ew3+eN9GBPhNi9otGxtOfA8b+8nJSxaNbcC23Ins/kcpjGf9zPSVW7628Umw==", - "dev": true, - "dependencies": { - "@commitlint/config-validator": "^17.1.0", - "@commitlint/execute-rule": "^17.0.0", - "@commitlint/resolve-extends": "^17.3.0", - "@commitlint/types": "^17.0.0", - "@types/node": "^14.0.0", - "chalk": "^4.1.0", - "cosmiconfig": "^7.0.0", - "cosmiconfig-typescript-loader": "^4.0.0", - "lodash.isplainobject": "^4.0.6", - "lodash.merge": "^4.6.2", - "lodash.uniq": "^4.5.0", - "resolve-from": "^5.0.0", - "ts-node": "^10.8.1", - "typescript": "^4.6.4" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/load/node_modules/@types/node": { - "version": "14.18.35", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.35.tgz", - "integrity": "sha512-2ATO8pfhG1kDvw4Lc4C0GXIMSQFFJBCo/R1fSgTwmUlq5oy95LXyjDQinsRVgQY6gp6ghh3H91wk9ES5/5C+Tw==", - "dev": true - }, - "node_modules/@commitlint/message": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-17.2.0.tgz", - "integrity": "sha512-/4l2KFKxBOuoEn1YAuuNNlAU05Zt7sNsC9H0mPdPm3chOrT4rcX0pOqrQcLtdMrMkJz0gC7b3SF80q2+LtdL9Q==", - "dev": true, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/parse": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-17.2.0.tgz", - "integrity": "sha512-vLzLznK9Y21zQ6F9hf8D6kcIJRb2haAK5T/Vt1uW2CbHYOIfNsR/hJs0XnF/J9ctM20Tfsqv4zBitbYvVw7F6Q==", - "dev": true, - "dependencies": { - "@commitlint/types": "^17.0.0", - "conventional-changelog-angular": "^5.0.11", - "conventional-commits-parser": "^3.2.2" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/read": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-17.2.0.tgz", - "integrity": "sha512-bbblBhrHkjxra3ptJNm0abxu7yeAaxumQ8ZtD6GIVqzURCETCP7Dm0tlVvGRDyXBuqX6lIJxh3W7oyKqllDsHQ==", - "dev": true, - "dependencies": { - "@commitlint/top-level": "^17.0.0", - "@commitlint/types": "^17.0.0", - "fs-extra": "^10.0.0", - "git-raw-commits": "^2.0.0", - "minimist": "^1.2.6" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/read/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@commitlint/resolve-extends": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-17.3.0.tgz", - "integrity": "sha512-Lf3JufJlc5yVEtJWC8o4IAZaB8FQAUaVlhlAHRACd0TTFizV2Lk2VH70et23KgvbQNf7kQzHs/2B4QZalBv6Cg==", - "dev": true, - "dependencies": { - "@commitlint/config-validator": "^17.1.0", - "@commitlint/types": "^17.0.0", - "import-fresh": "^3.0.0", - "lodash.mergewith": "^4.6.2", - "resolve-from": "^5.0.0", - "resolve-global": "^1.0.0" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/rules": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-17.3.0.tgz", - "integrity": "sha512-s2UhDjC5yP2utx3WWqsnZRzjgzAX8BMwr1nltC0u0p8T/nzpkx4TojEfhlsOUj1t7efxzZRjUAV0NxNwdJyk+g==", - "dev": true, - "dependencies": { - "@commitlint/ensure": "^17.3.0", - "@commitlint/message": "^17.2.0", - "@commitlint/to-lines": "^17.0.0", - "@commitlint/types": "^17.0.0", - "execa": "^5.0.0" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/to-lines": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-17.0.0.tgz", - "integrity": "sha512-nEi4YEz04Rf2upFbpnEorG8iymyH7o9jYIVFBG1QdzebbIFET3ir+8kQvCZuBE5pKCtViE4XBUsRZz139uFrRQ==", - "dev": true, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/top-level": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-17.0.0.tgz", - "integrity": "sha512-dZrEP1PBJvodNWYPOYiLWf6XZergdksKQaT6i1KSROLdjf5Ai0brLOv5/P+CPxBeoj3vBxK4Ax8H1Pg9t7sHIQ==", - "dev": true, - "dependencies": { - "find-up": "^5.0.0" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@commitlint/types": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-17.0.0.tgz", - "integrity": "sha512-hBAw6U+SkAT5h47zDMeOu3HSiD0SODw4Aq7rRNh1ceUmL7GyLKYhPbUvlRWqZ65XjBLPHZhFyQlRaPNz8qvUyQ==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0" - }, - "engines": { - "node": ">=v14" - } - }, "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", @@ -7480,6 +7226,112 @@ "node": ">=8" } }, + "node_modules/cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/cli-truncate/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/cli-width": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", @@ -7614,6 +7466,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true + }, "node_modules/columnify": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.6.0.tgz", @@ -7639,6 +7497,15 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/compare-func": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", @@ -7872,22 +7739,6 @@ "node": ">=10" } }, - "node_modules/cosmiconfig-typescript-loader": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.3.0.tgz", - "integrity": "sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==", - "dev": true, - "engines": { - "node": ">=12", - "npm": ">=6" - }, - "peerDependencies": { - "@types/node": "*", - "cosmiconfig": ">=7", - "ts-node": ">=10", - "typescript": ">=3" - } - }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -8245,6 +8096,12 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -9865,18 +9722,6 @@ "node": ">=10.13.0" } }, - "node_modules/global-dirs": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", - "integrity": "sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==", - "dev": true, - "dependencies": { - "ini": "^1.3.4" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/globals": { "version": "13.19.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", @@ -11953,49 +11798,273 @@ "node": ">= 10" } }, + "node_modules/lilconfig": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", + "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, - "node_modules/load-json-file": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-6.2.0.tgz", - "integrity": "sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ==", + "node_modules/lint-staged": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.1.2.tgz", + "integrity": "sha512-K9b4FPbWkpnupvK3WXZLbgu9pchUJ6N7TtVZjbaPsoizkqFUDkUReUL25xdrCljJs7uLUF3tZ7nVPeo/6lp+6w==", "dev": true, "dependencies": { - "graceful-fs": "^4.1.15", - "parse-json": "^5.0.0", - "strip-bom": "^4.0.0", - "type-fest": "^0.6.0" + "cli-truncate": "^3.1.0", + "colorette": "^2.0.19", + "commander": "^9.4.1", + "debug": "^4.3.4", + "execa": "^6.1.0", + "lilconfig": "2.0.6", + "listr2": "^5.0.5", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-inspect": "^1.12.2", + "pidtree": "^0.6.0", + "string-argv": "^0.3.1", + "yaml": "^2.1.3" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" }, "engines": { - "node": ">=8" - } - }, - "node_modules/load-json-file/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true, - "engines": { - "node": ">=8" + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" } }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/lint-staged/node_modules/execa": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-6.1.0.tgz", + "integrity": "sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==", "dev": true, "dependencies": { - "p-locate": "^5.0.0" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^3.0.1", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/human-signals": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz", + "integrity": "sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==", + "dev": true, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/lint-staged/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/yaml": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz", + "integrity": "sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/listr2": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-5.0.7.tgz", + "integrity": "sha512-MD+qXHPmtivrHIDRwPYdfNkrzqDiuaKU/rfBcec3WMyMF3xylQj3jMq344OtvQxz7zaCFViRAeqlr2AFhPvXHw==", + "dev": true, + "dependencies": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.19", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.8.0", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } + } + }, + "node_modules/listr2/node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/rxjs": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", + "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/listr2/node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/load-json-file": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-6.2.0.tgz", + "integrity": "sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "parse-json": "^5.0.0", + "strip-bom": "^4.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/load-json-file/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lodash": { @@ -12010,12 +12079,6 @@ "integrity": "sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==", "dev": true }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true - }, "node_modules/lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -12040,12 +12103,6 @@ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "dev": true }, - "node_modules/lodash.isfunction": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", - "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", - "dev": true - }, "node_modules/lodash.ismatch": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", @@ -12058,12 +12115,6 @@ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "dev": true }, - "node_modules/lodash.kebabcase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", - "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", - "dev": true - }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -12075,24 +12126,6 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, - "node_modules/lodash.mergewith": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", - "dev": true - }, - "node_modules/lodash.snakecase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", - "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", - "dev": true - }, - "node_modules/lodash.startcase": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", - "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", - "dev": true - }, "node_modules/lodash.template": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", @@ -12124,17 +12157,37 @@ "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", "dev": true }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "dev": true + "node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/lodash.upperfirst": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", - "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", - "dev": true + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } }, "node_modules/lru-cache": { "version": "7.14.1", @@ -13831,6 +13884,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/pify": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", @@ -14659,18 +14724,6 @@ "node": ">=8" } }, - "node_modules/resolve-global": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-global/-/resolve-global-1.0.0.tgz", - "integrity": "sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==", - "dev": true, - "dependencies": { - "global-dirs": "^0.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/resolve.exports": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", @@ -14712,6 +14765,12 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -15244,6 +15303,15 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-argv": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -16649,7 +16717,7 @@ }, "packages/commons": { "name": "@aws-lambda-powertools/commons", - "version": "1.5.1", + "version": "1.6.0", "license": "MIT-0" }, "packages/idempotency": { @@ -16667,10 +16735,10 @@ }, "packages/logger": { "name": "@aws-lambda-powertools/logger", - "version": "1.5.1", + "version": "1.6.0", "license": "MIT", "dependencies": { - "@aws-lambda-powertools/commons": "^1.5.1", + "@aws-lambda-powertools/commons": "^1.6.0", "lodash.merge": "^4.6.2" }, "devDependencies": { @@ -16679,10 +16747,10 @@ }, "packages/metrics": { "name": "@aws-lambda-powertools/metrics", - "version": "1.5.1", + "version": "1.6.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "^1.5.1" + "@aws-lambda-powertools/commons": "^1.6.0" }, "devDependencies": { "@types/promise-retry": "^1.1.3", @@ -16720,10 +16788,10 @@ }, "packages/tracer": { "name": "@aws-lambda-powertools/tracer", - "version": "1.5.1", + "version": "1.6.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "^1.5.1", + "@aws-lambda-powertools/commons": "^1.6.0", "aws-xray-sdk-core": "^3.4.0" }, "devDependencies": { @@ -16969,7 +17037,7 @@ "@aws-lambda-powertools/logger": { "version": "file:packages/logger", "requires": { - "@aws-lambda-powertools/commons": "^1.5.1", + "@aws-lambda-powertools/commons": "^1.6.0", "@types/lodash.merge": "^4.6.7", "lodash.merge": "^4.6.2" } @@ -16977,7 +17045,7 @@ "@aws-lambda-powertools/metrics": { "version": "file:packages/metrics", "requires": { - "@aws-lambda-powertools/commons": "^1.5.1", + "@aws-lambda-powertools/commons": "^1.6.0", "@types/promise-retry": "^1.1.3", "promise-retry": "^2.0.1" } @@ -17009,7 +17077,7 @@ "@aws-lambda-powertools/tracer": { "version": "file:packages/tracer", "requires": { - "@aws-lambda-powertools/commons": "^1.5.1", + "@aws-lambda-powertools/commons": "^1.6.0", "@aws-sdk/client-dynamodb": "^3.231.0", "@types/promise-retry": "^1.1.3", "aws-sdk": "^2.1276.0", @@ -18414,210 +18482,6 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "@commitlint/cli": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-17.3.0.tgz", - "integrity": "sha512-/H0md7TsKflKzVPz226VfXzVafJFO1f9+r2KcFvmBu08V0T56lZU1s8WL7/xlxqLMqBTVaBf7Ixtc4bskdEEZg==", - "dev": true, - "requires": { - "@commitlint/format": "^17.0.0", - "@commitlint/lint": "^17.3.0", - "@commitlint/load": "^17.3.0", - "@commitlint/read": "^17.2.0", - "@commitlint/types": "^17.0.0", - "execa": "^5.0.0", - "lodash.isfunction": "^3.0.9", - "resolve-from": "5.0.0", - "resolve-global": "1.0.0", - "yargs": "^17.0.0" - } - }, - "@commitlint/config-validator": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-17.1.0.tgz", - "integrity": "sha512-Q1rRRSU09ngrTgeTXHq6ePJs2KrI+axPTgkNYDWSJIuS1Op4w3J30vUfSXjwn5YEJHklK3fSqWNHmBhmTR7Vdg==", - "dev": true, - "requires": { - "@commitlint/types": "^17.0.0", - "ajv": "^8.11.0" - } - }, - "@commitlint/ensure": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-17.3.0.tgz", - "integrity": "sha512-kWbrQHDoW5veIUQx30gXoLOCjWvwC6OOEofhPCLl5ytRPBDAQObMbxTha1Bt2aSyNE/IrJ0s0xkdZ1Gi3wJwQg==", - "dev": true, - "requires": { - "@commitlint/types": "^17.0.0", - "lodash.camelcase": "^4.3.0", - "lodash.kebabcase": "^4.1.1", - "lodash.snakecase": "^4.1.1", - "lodash.startcase": "^4.4.0", - "lodash.upperfirst": "^4.3.1" - } - }, - "@commitlint/execute-rule": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-17.0.0.tgz", - "integrity": "sha512-nVjL/w/zuqjCqSJm8UfpNaw66V9WzuJtQvEnCrK4jDw6qKTmZB+1JQ8m6BQVZbNBcwfYdDNKnhIhqI0Rk7lgpQ==", - "dev": true - }, - "@commitlint/format": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-17.0.0.tgz", - "integrity": "sha512-MZzJv7rBp/r6ZQJDEodoZvdRM0vXu1PfQvMTNWFb8jFraxnISMTnPBWMMjr2G/puoMashwaNM//fl7j8gGV5lA==", - "dev": true, - "requires": { - "@commitlint/types": "^17.0.0", - "chalk": "^4.1.0" - } - }, - "@commitlint/is-ignored": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-17.2.0.tgz", - "integrity": "sha512-rgUPUQraHxoMLxiE8GK430HA7/R2vXyLcOT4fQooNrZq9ERutNrP6dw3gdKLkq22Nede3+gEHQYUzL4Wu75ndg==", - "dev": true, - "requires": { - "@commitlint/types": "^17.0.0", - "semver": "7.3.7" - } - }, - "@commitlint/lint": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-17.3.0.tgz", - "integrity": "sha512-VilOTPg0i9A7CCWM49E9bl5jytfTvfTxf9iwbWAWNjxJ/A5mhPKbm3sHuAdwJ87tDk1k4j8vomYfH23iaY+1Rw==", - "dev": true, - "requires": { - "@commitlint/is-ignored": "^17.2.0", - "@commitlint/parse": "^17.2.0", - "@commitlint/rules": "^17.3.0", - "@commitlint/types": "^17.0.0" - } - }, - "@commitlint/load": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-17.3.0.tgz", - "integrity": "sha512-u/pV6rCAJrCUN+HylBHLzZ4qj1Ew3+eN9GBPhNi9otGxtOfA8b+8nJSxaNbcC23Ins/kcpjGf9zPSVW7628Umw==", - "dev": true, - "requires": { - "@commitlint/config-validator": "^17.1.0", - "@commitlint/execute-rule": "^17.0.0", - "@commitlint/resolve-extends": "^17.3.0", - "@commitlint/types": "^17.0.0", - "@types/node": "^14.0.0", - "chalk": "^4.1.0", - "cosmiconfig": "^7.0.0", - "cosmiconfig-typescript-loader": "^4.0.0", - "lodash.isplainobject": "^4.0.6", - "lodash.merge": "^4.6.2", - "lodash.uniq": "^4.5.0", - "resolve-from": "^5.0.0", - "ts-node": "^10.8.1", - "typescript": "^4.6.4" - }, - "dependencies": { - "@types/node": { - "version": "14.18.35", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.35.tgz", - "integrity": "sha512-2ATO8pfhG1kDvw4Lc4C0GXIMSQFFJBCo/R1fSgTwmUlq5oy95LXyjDQinsRVgQY6gp6ghh3H91wk9ES5/5C+Tw==", - "dev": true - } - } - }, - "@commitlint/message": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-17.2.0.tgz", - "integrity": "sha512-/4l2KFKxBOuoEn1YAuuNNlAU05Zt7sNsC9H0mPdPm3chOrT4rcX0pOqrQcLtdMrMkJz0gC7b3SF80q2+LtdL9Q==", - "dev": true - }, - "@commitlint/parse": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-17.2.0.tgz", - "integrity": "sha512-vLzLznK9Y21zQ6F9hf8D6kcIJRb2haAK5T/Vt1uW2CbHYOIfNsR/hJs0XnF/J9ctM20Tfsqv4zBitbYvVw7F6Q==", - "dev": true, - "requires": { - "@commitlint/types": "^17.0.0", - "conventional-changelog-angular": "^5.0.11", - "conventional-commits-parser": "^3.2.2" - } - }, - "@commitlint/read": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-17.2.0.tgz", - "integrity": "sha512-bbblBhrHkjxra3ptJNm0abxu7yeAaxumQ8ZtD6GIVqzURCETCP7Dm0tlVvGRDyXBuqX6lIJxh3W7oyKqllDsHQ==", - "dev": true, - "requires": { - "@commitlint/top-level": "^17.0.0", - "@commitlint/types": "^17.0.0", - "fs-extra": "^10.0.0", - "git-raw-commits": "^2.0.0", - "minimist": "^1.2.6" - }, - "dependencies": { - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - } - } - }, - "@commitlint/resolve-extends": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-17.3.0.tgz", - "integrity": "sha512-Lf3JufJlc5yVEtJWC8o4IAZaB8FQAUaVlhlAHRACd0TTFizV2Lk2VH70et23KgvbQNf7kQzHs/2B4QZalBv6Cg==", - "dev": true, - "requires": { - "@commitlint/config-validator": "^17.1.0", - "@commitlint/types": "^17.0.0", - "import-fresh": "^3.0.0", - "lodash.mergewith": "^4.6.2", - "resolve-from": "^5.0.0", - "resolve-global": "^1.0.0" - } - }, - "@commitlint/rules": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-17.3.0.tgz", - "integrity": "sha512-s2UhDjC5yP2utx3WWqsnZRzjgzAX8BMwr1nltC0u0p8T/nzpkx4TojEfhlsOUj1t7efxzZRjUAV0NxNwdJyk+g==", - "dev": true, - "requires": { - "@commitlint/ensure": "^17.3.0", - "@commitlint/message": "^17.2.0", - "@commitlint/to-lines": "^17.0.0", - "@commitlint/types": "^17.0.0", - "execa": "^5.0.0" - } - }, - "@commitlint/to-lines": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-17.0.0.tgz", - "integrity": "sha512-nEi4YEz04Rf2upFbpnEorG8iymyH7o9jYIVFBG1QdzebbIFET3ir+8kQvCZuBE5pKCtViE4XBUsRZz139uFrRQ==", - "dev": true - }, - "@commitlint/top-level": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-17.0.0.tgz", - "integrity": "sha512-dZrEP1PBJvodNWYPOYiLWf6XZergdksKQaT6i1KSROLdjf5Ai0brLOv5/P+CPxBeoj3vBxK4Ax8H1Pg9t7sHIQ==", - "dev": true, - "requires": { - "find-up": "^5.0.0" - } - }, - "@commitlint/types": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-17.0.0.tgz", - "integrity": "sha512-hBAw6U+SkAT5h47zDMeOu3HSiD0SODw4Aq7rRNh1ceUmL7GyLKYhPbUvlRWqZ65XjBLPHZhFyQlRaPNz8qvUyQ==", - "dev": true, - "requires": { - "chalk": "^4.1.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", @@ -22742,6 +22606,72 @@ "restore-cursor": "^3.1.0" } }, + "cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "requires": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true + }, + "slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + } + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, "cli-width": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", @@ -22846,6 +22776,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true + }, "columnify": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.6.0.tgz", @@ -22865,6 +22801,12 @@ "delayed-stream": "~1.0.0" } }, + "commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true + }, "compare-func": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", @@ -23055,13 +22997,6 @@ "yaml": "^1.10.0" } }, - "cosmiconfig-typescript-loader": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.3.0.tgz", - "integrity": "sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==", - "dev": true, - "requires": {} - }, "crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -23341,6 +23276,12 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -24612,15 +24553,6 @@ "is-glob": "^4.0.3" } }, - "global-dirs": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", - "integrity": "sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==", - "dev": true, - "requires": { - "ini": "^1.3.4" - } - }, "globals": { "version": "13.19.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", @@ -26223,12 +26155,160 @@ } } }, + "lilconfig": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", + "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", + "dev": true + }, "lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "lint-staged": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.1.2.tgz", + "integrity": "sha512-K9b4FPbWkpnupvK3WXZLbgu9pchUJ6N7TtVZjbaPsoizkqFUDkUReUL25xdrCljJs7uLUF3tZ7nVPeo/6lp+6w==", + "dev": true, + "requires": { + "cli-truncate": "^3.1.0", + "colorette": "^2.0.19", + "commander": "^9.4.1", + "debug": "^4.3.4", + "execa": "^6.1.0", + "lilconfig": "2.0.6", + "listr2": "^5.0.5", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-inspect": "^1.12.2", + "pidtree": "^0.6.0", + "string-argv": "^0.3.1", + "yaml": "^2.1.3" + }, + "dependencies": { + "execa": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-6.1.0.tgz", + "integrity": "sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^3.0.1", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + } + }, + "human-signals": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz", + "integrity": "sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==", + "dev": true + }, + "is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true + }, + "mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true + }, + "npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "requires": { + "path-key": "^4.0.0" + } + }, + "onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "requires": { + "mimic-fn": "^4.0.0" + } + }, + "path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true + }, + "strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true + }, + "yaml": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz", + "integrity": "sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==", + "dev": true + } + } + }, + "listr2": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-5.0.7.tgz", + "integrity": "sha512-MD+qXHPmtivrHIDRwPYdfNkrzqDiuaKU/rfBcec3WMyMF3xylQj3jMq344OtvQxz7zaCFViRAeqlr2AFhPvXHw==", + "dev": true, + "requires": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.19", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.8.0", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "requires": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + } + }, + "rxjs": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", + "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + } + }, + "slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + } + } + }, "load-json-file": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-6.2.0.tgz", @@ -26270,12 +26350,6 @@ "integrity": "sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==", "dev": true }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true - }, "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -26300,12 +26374,6 @@ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "dev": true }, - "lodash.isfunction": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", - "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", - "dev": true - }, "lodash.ismatch": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", @@ -26318,12 +26386,6 @@ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "dev": true }, - "lodash.kebabcase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", - "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", - "dev": true - }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -26335,24 +26397,6 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, - "lodash.mergewith": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", - "dev": true - }, - "lodash.snakecase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", - "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", - "dev": true - }, - "lodash.startcase": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", - "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", - "dev": true - }, "lodash.template": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", @@ -26384,17 +26428,30 @@ "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", "dev": true }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "dev": true - }, - "lodash.upperfirst": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", - "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", - "dev": true + "log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "requires": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } }, "lru-cache": { "version": "7.14.1", @@ -27731,6 +27788,12 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, + "pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true + }, "pify": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", @@ -28378,15 +28441,6 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, - "resolve-global": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-global/-/resolve-global-1.0.0.tgz", - "integrity": "sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==", - "dev": true, - "requires": { - "global-dirs": "^0.1.1" - } - }, "resolve.exports": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", @@ -28415,6 +28469,12 @@ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true }, + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -28827,6 +28887,12 @@ "safe-buffer": "~5.2.0" } }, + "string-argv": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "dev": true + }, "string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", diff --git a/package.json b/package.json index d9db61efa2..6b2c6e96f6 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,6 @@ "devDependencies": { "@aws-cdk/cloudformation-diff": "^2.55.0", "@aws-cdk/cx-api": "^2.55.0", - "@commitlint/cli": "^17.3.0", "@middy/core": "^3.6.2", "@types/aws-lambda": "^8.10.109", "@types/jest": "^29.2.4", @@ -69,6 +68,7 @@ "jest": "^29.3.1", "jest-runner-groups": "^2.2.0", "lerna": "^4.0.0", + "lint-staged": "^13.1.2", "promptly": "^3.2.0", "proxy-agent": "^5.0.0", "ts-jest": "^29.0.3", @@ -87,4 +87,4 @@ "dependencies": { "hosted-git-info": "^6.1.1" } -} \ No newline at end of file +} diff --git a/packages/commons/package.json b/packages/commons/package.json index 912313b58b..90930275e2 100644 --- a/packages/commons/package.json +++ b/packages/commons/package.json @@ -23,6 +23,9 @@ "prepare": "npm run build", "postversion": "git push --tags" }, + "lint-staged": { + "*.ts": "npm run lint-fix" + }, "homepage": "https://github.com/awslabs/aws-lambda-powertools-typescript/tree/main/packages/metrics#readme", "license": "MIT-0", "main": "./lib/index.js", @@ -45,4 +48,4 @@ "serverless", "nodejs" ] -} +} \ No newline at end of file diff --git a/packages/idempotency/package.json b/packages/idempotency/package.json index 762e57dffb..cc83d4a229 100644 --- a/packages/idempotency/package.json +++ b/packages/idempotency/package.json @@ -26,6 +26,9 @@ "prepare": "npm run build", "postversion": "echo \"Not implemented\"" }, + "lint-staged": { + "*.ts": "npm run lint-fix" + }, "homepage": "https://github.com/awslabs/aws-lambda-powertools-typescript/tree/main/packages/idempotency#readme", "license": "MIT", "main": "./lib/index.js", diff --git a/packages/logger/package.json b/packages/logger/package.json index fd35e423d3..e79986eae5 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -26,6 +26,9 @@ "prepare": "npm run build", "postversion": "git push --tags" }, + "lint-staged": { + "*.ts": "npm run lint-fix" + }, "homepage": "https://github.com/awslabs/aws-lambda-powertools-typescript/tree/main/packages/logging#readme", "license": "MIT", "main": "./lib/index.js", @@ -57,4 +60,4 @@ "serverless", "nodejs" ] -} +} \ No newline at end of file diff --git a/packages/metrics/package.json b/packages/metrics/package.json index 058df1ee42..d9011fa533 100644 --- a/packages/metrics/package.json +++ b/packages/metrics/package.json @@ -26,6 +26,9 @@ "prepare": "npm run build", "postversion": "git push --tags" }, + "lint-staged": { + "*.ts": "npm run lint-fix" + }, "homepage": "https://github.com/awslabs/aws-lambda-powertools-typescript/tree/main/packages/metrics#readme", "license": "MIT-0", "main": "./lib/index.js", @@ -56,4 +59,4 @@ "serverless", "nodejs" ] -} +} \ No newline at end of file diff --git a/packages/parameters/package.json b/packages/parameters/package.json index 469e72f70b..3982a88bd0 100644 --- a/packages/parameters/package.json +++ b/packages/parameters/package.json @@ -26,6 +26,9 @@ "prepare": "npm run build", "postversion": "git push --tags" }, + "lint-staged": { + "*.ts": "npm run lint-fix" + }, "homepage": "https://github.com/awslabs/aws-lambda-powertools-typescript/tree/main/packages/parameters#readme", "license": "MIT-0", "main": "./lib/index.js", diff --git a/packages/tracer/package.json b/packages/tracer/package.json index 21d891bff8..714feea5c5 100644 --- a/packages/tracer/package.json +++ b/packages/tracer/package.json @@ -26,6 +26,9 @@ "prepare": "npm run build", "postversion": "git push --tags" }, + "lint-staged": { + "*.ts": "npm run lint-fix" + }, "homepage": "https://github.com/awslabs/aws-lambda-powertools-typescript/tree/main/packages/tracer#readme", "license": "MIT-0", "main": "./lib/index.js", @@ -60,4 +63,4 @@ "serverless", "nodejs" ] -} +} \ No newline at end of file From 97339d9336ec67568e9e7fd079b3cfe006da1bba Mon Sep 17 00:00:00 2001 From: Brian Krygsman Date: Tue, 14 Mar 2023 06:14:11 -0400 Subject: [PATCH 17/26] feat(parameters): AppConfigProvider to return the last valid value when the API returns empty value on subsequent calls (#1365) * Closes 1363 Add a local cache to AppConfigProvider to handle the scenario where the maxAge is expired, but the AppConfig session still has the latest configuration. In this case, AppConfig returns an empty value. We don't want to return empty values to our callers. This uses the same data structure we have in place to track NextPollConfigurationToken. This approach roughly the Python library's behavior. * Add E2E test coverage checking return values pre- and post-expiration Includes update to e2e lambda config type that allows test writers to specify a test function duration. Needed because to wait for parameter expiration, we needed a longer-than-default timeout. * Use timestamp/millisecond based math for checking expiration. * Revert "Use timestamp/millisecond based math for checking expiration." This reverts commit 3fea0d7e826a6d184b04f6adc8efc62bc1751069. Further testing shows that the original algorithm was correct. * Update appConfig integration tests to use single log line. Drop wait, use maxAge instead. Add comment for Test case 7. Fix test case naming (test '4' was skipped on one of the files). * Revert increase of Lambda function duration back to default. Not needed anymore since we're not waiting for Test 7. * Focus value test integration tests on Test 7 * Comma formatting * Add unit tests for ExpirableValue Doesn't refactor ExpirableValue to take an injection of Date.now, just implements tests based on what we can do with the existing interface. * Address formatting feedback and updates comments to indicate we're no longer waiting during Test 7 runs. * Adjust log message structure to match pattern Specfically test/value as top-level properties. * Move client reset to afterEach for consistency. * Drop old reset --- packages/commons/tests/utils/e2eUtils.ts | 4 +- .../src/appconfig/AppConfigProvider.ts | 20 +++- ...pConfigProvider.class.test.functionCode.ts | 37 +++++++- .../tests/e2e/appConfigProvider.class.test.ts | 29 +++++- .../tests/unit/AppConfigProvider.test.ts | 93 +++++++++++++++++++ 5 files changed, 176 insertions(+), 7 deletions(-) diff --git a/packages/commons/tests/utils/e2eUtils.ts b/packages/commons/tests/utils/e2eUtils.ts index 5d2e75f4a5..ae5c1441ae 100644 --- a/packages/commons/tests/utils/e2eUtils.ts +++ b/packages/commons/tests/utils/e2eUtils.ts @@ -5,7 +5,7 @@ * E2E utils is used by e2e tests. They are helper function that calls either CDK or SDK * to interact with services. */ -import { App, CfnOutput, Stack } from 'aws-cdk-lib'; +import { App, CfnOutput, Stack, Duration } from 'aws-cdk-lib'; import { NodejsFunction, NodejsFunctionProps @@ -37,6 +37,7 @@ export type StackWithLambdaFunctionOptions = { runtime: string bundling?: NodejsFunctionProps['bundling'] layers?: NodejsFunctionProps['layers'] + timeout?: Duration }; type FunctionPayload = {[key: string]: string | boolean | number}; @@ -55,6 +56,7 @@ export const createStackWithLambdaFunction = (params: StackWithLambdaFunctionOpt bundling: params.bundling, layers: params.layers, logRetention: RetentionDays.ONE_DAY, + timeout: params.timeout }); if (params.logGroupOutputKey) { diff --git a/packages/parameters/src/appconfig/AppConfigProvider.ts b/packages/parameters/src/appconfig/AppConfigProvider.ts index 9e9e2b0152..ce42545055 100644 --- a/packages/parameters/src/appconfig/AppConfigProvider.ts +++ b/packages/parameters/src/appconfig/AppConfigProvider.ts @@ -13,6 +13,7 @@ import type { class AppConfigProvider extends BaseProvider { public client: AppConfigDataClient; protected configurationTokenStore: Map = new Map(); + protected valueStore: Map = new Map(); private application?: string; private environment: string; @@ -79,6 +80,10 @@ class AppConfigProvider extends BaseProvider { * The new AppConfig APIs require two API calls to return the configuration * First we start the session and after that we retrieve the configuration * We need to store { name: token } pairs to use in the next execution + * We also need to store { name : value } pairs because AppConfig returns + * an empty value if the session already has the latest configuration + * but, we don't want to return an empty value to our callers. + * {@link https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-retrieving-the-configuration.html} **/ if (!this.configurationTokenStore.has(name)) { @@ -106,14 +111,25 @@ class AppConfigProvider extends BaseProvider { }); const response = await this.client.send(getConfigurationCommand); - + if (response.NextPollConfigurationToken) { this.configurationTokenStore.set(name, response.NextPollConfigurationToken); } else { this.configurationTokenStore.delete(name); } - return response.Configuration; + /** When the response is not empty, stash the result locally before returning + * See AppConfig docs: + * {@link https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-retrieving-the-configuration.html} + **/ + if (response.Configuration !== undefined && response.Configuration?.length > 0 ) { + this.valueStore.set(name, response.Configuration); + + return response.Configuration; + } + + // Otherwise, use a stashed value + return this.valueStore.get(name); } /** diff --git a/packages/parameters/tests/e2e/appConfigProvider.class.test.functionCode.ts b/packages/parameters/tests/e2e/appConfigProvider.class.test.functionCode.ts index 11d8feb01f..3633b7d5d6 100644 --- a/packages/parameters/tests/e2e/appConfigProvider.class.test.functionCode.ts +++ b/packages/parameters/tests/e2e/appConfigProvider.class.test.functionCode.ts @@ -73,10 +73,10 @@ export const handler = async (_event: unknown, _context: Context): Promise // Test 3 - get a free-form base64-encoded plain text and apply binary transformation (should return a decoded string) await _call_get(freeFormBase64encodedPlainText, 'get-freeform-base64-plaintext-binary', { transform: 'binary' }); - // Test 5 - get a feature flag and apply json transformation (should return an object) + // Test 4 - get a feature flag and apply json transformation (should return an object) await _call_get(featureFlagName, 'get-feature-flag-binary', { transform: 'json' }); - // Test 6 + // Test 5 // get parameter twice with middleware, which counts the number of requests, we check later if we only called AppConfig API once try { providerWithMiddleware.clearCache(); @@ -94,7 +94,7 @@ export const handler = async (_event: unknown, _context: Context): Promise }); } - // Test 7 + // Test 6 // get parameter twice, but force fetch 2nd time, we count number of SDK requests and check that we made two API calls try { providerWithMiddleware.clearCache(); @@ -111,4 +111,35 @@ export const handler = async (_event: unknown, _context: Context): Promise error: err.message }); } + // Test 7 + // get parameter twice, using maxAge to avoid primary cache, count SDK calls and return values + try { + providerWithMiddleware.clearCache(); + middleware.counter = 0; + const expiredResult1 = await providerWithMiddleware.get( + freeFormBase64encodedPlainText, { + maxAge: 0, + transform: 'base64' + } + ); + const expiredResult2 = await providerWithMiddleware.get( + freeFormBase64encodedPlainText, { + maxAge: 0, + transform: 'base64' + } + ); + logger.log({ + test: 'get-expired', + value: { + counter: middleware.counter, // should be 2 + result1: expiredResult1, + result2: expiredResult2 + }, + }); + } catch (err) { + logger.log({ + test: 'get-expired', + error: err.message + }); + } }; \ No newline at end of file diff --git a/packages/parameters/tests/e2e/appConfigProvider.class.test.ts b/packages/parameters/tests/e2e/appConfigProvider.class.test.ts index 02b2de3291..26b6210ea2 100644 --- a/packages/parameters/tests/e2e/appConfigProvider.class.test.ts +++ b/packages/parameters/tests/e2e/appConfigProvider.class.test.ts @@ -54,6 +54,7 @@ const freeFormJsonValue = { const freeFormYamlValue = `foo: bar `; const freeFormPlainTextValue = 'foo'; +const freeFormBase64PlainTextValue = toBase64(new TextEncoder().encode(freeFormPlainTextValue)); const featureFlagValue = { version: '1', flags: { @@ -111,6 +112,12 @@ let stack: Stack; * Test 6 * get parameter twice, but force fetch 2nd time, we count number of SDK requests and * check that we made two API calls + * check that we got matching results + * + * Test 7 + * get parameter twice, using maxAge to avoid primary cache + * we count number of SDK requests and check that we made two API calls + * and check that the values match * * Note: To avoid race conditions, we add a dependency between each pair of configuration profiles. * This allows us to influence the order of creation and ensure that each configuration profile @@ -191,7 +198,7 @@ describe(`parameters E2E tests (appConfigProvider) for runtime ${runtime}`, () = name: freeFormBase64PlainTextName, type: 'AWS.Freeform', content: { - content: toBase64(new TextEncoder().encode(freeFormPlainTextValue)), + content: freeFormBase64PlainTextValue, contentType: 'text/plain', } }); @@ -314,6 +321,26 @@ describe(`parameters E2E tests (appConfigProvider) for runtime ${runtime}`, () = }, TEST_CASE_TIMEOUT); + // Test 7 - get parameter twice, using maxAge to avoid primary cache + // we count number of SDK requests and check that we made two API calls + // and check that the values match + it('should retrieve single parameter twice, with expiration between and matching values', async () => { + + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[6]); + const result = freeFormBase64PlainTextValue; + + expect(testLog).toStrictEqual({ + test: 'get-expired', + value: { + counter: 2, + result1: result, + result2: result + } + }); + + }, TEST_CASE_TIMEOUT); + }); afterAll(async () => { diff --git a/packages/parameters/tests/unit/AppConfigProvider.test.ts b/packages/parameters/tests/unit/AppConfigProvider.test.ts index 56365d882c..5bc16e3191 100644 --- a/packages/parameters/tests/unit/AppConfigProvider.test.ts +++ b/packages/parameters/tests/unit/AppConfigProvider.test.ts @@ -4,6 +4,7 @@ * @group unit/parameters/AppConfigProvider/class */ import { AppConfigProvider } from '../../src/appconfig/index'; +import { ExpirableValue } from '../../src/ExpirableValue'; import { AppConfigProviderOptions } from '../../src/types/AppConfigProvider'; import { AppConfigDataClient, @@ -21,6 +22,10 @@ describe('Class: AppConfigProvider', () => { jest.clearAllMocks(); }); + afterEach(() => { + client.reset(); + }); + describe('Method: constructor', () => { test('when the class instantiates without SDK client and client config it has default options', async () => { // Prepare @@ -225,6 +230,49 @@ describe('Class: AppConfigProvider', () => { // Act & Assess await expect(provider.get(name)).rejects.toThrow(); }); + + test('when session returns an empty configuration on the second call, it returns the last value', async () => { + + // Prepare + const options: AppConfigProviderOptions = { + application: 'MyApp', + environment: 'MyAppProdEnv', + }; + const provider = new AppConfigProvider(options); + const name = 'MyAppFeatureFlag'; + + const fakeInitialToken = 'aW5pdGlhbFRva2Vu'; + const fakeNextToken1 = 'bmV4dFRva2Vu'; + const fakeNextToken2 = 'bmV4dFRva2Vq'; + const mockData = encoder.encode('myAppConfiguration'); + + client + .on(StartConfigurationSessionCommand) + .resolves({ + InitialConfigurationToken: fakeInitialToken, + }) + .on(GetLatestConfigurationCommand) + .resolvesOnce({ + Configuration: mockData, + NextPollConfigurationToken: fakeNextToken1, + }) + .resolvesOnce({ + Configuration: undefined, + NextPollConfigurationToken: fakeNextToken2, + }); + + // Act + + // Load local cache + const result1 = await provider.get(name, { forceFetch: true }); + + // Read from local cache, given empty response from service + const result2 = await provider.get(name, { forceFetch: true }); + + // Assess + expect(result1).toBe(mockData); + expect(result2).toBe(mockData); + }); }); describe('Method: _getMultiple', () => { @@ -243,3 +291,48 @@ describe('Class: AppConfigProvider', () => { }); }); }); + +describe('Class: ExpirableValue', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('Method: constructor', () => { + test('when created, it has ttl set to at least maxAge seconds from test start', () => { + // Prepare + const seconds = 10; + const nowTimestamp = Date.now(); + const futureTimestampSeconds = nowTimestamp/1000+(seconds); + + // Act + const expirableValue = new ExpirableValue('foo', seconds); + + // Assess + expect(expirableValue.ttl).toBeGreaterThan(futureTimestampSeconds); + }); + }); + + describe('Method: isExpired', () => { + test('when called, it returns true when maxAge is in the future', () => { + // Prepare + const seconds = 60; + + // Act + const expirableValue = new ExpirableValue('foo', seconds); + + // Assess + expect(expirableValue.isExpired()).toBeFalsy(); + }); + + test('when called, it returns false when maxAge is in the past', () => { + // Prepare + const seconds = -60; + + // Act + const expirableValue = new ExpirableValue('foo', seconds); + + // Assess + expect(expirableValue.isExpired()).toBeTruthy(); + }); + }); +}); \ No newline at end of file From 8a47e76dbb1221991086e1094c15585ff5467c69 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 16 Mar 2023 13:50:18 +0100 Subject: [PATCH 18/26] docs(parameters): add typedoc api docs (#1283) * docs(parameters): add typedoc api docs (#1283) * chore(docs): fix minor issues in api docs examples * chore(docs): add note for maxAge start caching after the first api call --------- Co-authored-by: Alexander Melnyk --- packages/parameters/jest.config.js | 1 + packages/parameters/package.json | 2 +- packages/parameters/src/BaseProvider.ts | 71 ++- packages/parameters/src/Exceptions.ts | 3 + packages/parameters/src/ExpirableValue.ts | 16 + packages/parameters/src/GetMultipleOptions.ts | 5 + packages/parameters/src/GetOptions.ts | 5 + .../src/appconfig/AppConfigProvider.ts | 200 ++++++++- .../parameters/src/appconfig/getAppConfig.ts | 117 ++++- packages/parameters/src/docs.ts | 4 + .../src/dynamodb/DynamoDBProvider.ts | 296 +++++++++++++ .../parameters/src/secrets/SecretsProvider.ts | 180 ++++++++ packages/parameters/src/secrets/getSecret.ts | 101 +++++ packages/parameters/src/ssm/SSMProvider.ts | 419 +++++++++++++++++- packages/parameters/src/ssm/getParameter.ts | 135 ++++++ packages/parameters/src/ssm/getParameters.ts | 136 ++++++ .../parameters/src/ssm/getParametersByName.ts | 157 +++++++ .../parameters/src/types/AppConfigProvider.ts | 19 +- packages/parameters/src/types/BaseProvider.ts | 47 ++ .../parameters/src/types/DynamoDBProvider.ts | 55 ++- packages/parameters/src/types/SSMProvider.ts | 61 ++- .../parameters/src/types/SecretsProvider.ts | 32 ++ .../tests/unit/AppConfigProvider.test.ts | 2 +- typedoc.js | 1 + 24 files changed, 1997 insertions(+), 68 deletions(-) create mode 100644 packages/parameters/src/docs.ts diff --git a/packages/parameters/jest.config.js b/packages/parameters/jest.config.js index 6c267d005e..d7114e8417 100644 --- a/packages/parameters/jest.config.js +++ b/packages/parameters/jest.config.js @@ -25,6 +25,7 @@ module.exports = { 'coveragePathIgnorePatterns': [ '/node_modules/', '/types/', + '/src/docs.ts', // this file is only used for documentation ], 'coverageThreshold': { 'global': { diff --git a/packages/parameters/package.json b/packages/parameters/package.json index 3982a88bd0..bee6d71f11 100644 --- a/packages/parameters/package.json +++ b/packages/parameters/package.json @@ -33,7 +33,7 @@ "license": "MIT-0", "main": "./lib/index.js", "types": "./lib/index.d.ts", - "typedocMain": "src/file_that_does_not_exist_so_its_ignored_from_api_docs.ts", + "typedocMain": "src/docs.ts", "files": [ "lib" ], diff --git a/packages/parameters/src/BaseProvider.ts b/packages/parameters/src/BaseProvider.ts index b2fc4efc1c..e9456d8e00 100644 --- a/packages/parameters/src/BaseProvider.ts +++ b/packages/parameters/src/BaseProvider.ts @@ -9,6 +9,25 @@ import type { BaseProviderInterface, GetMultipleOptionsInterface, GetOptionsInte // These providers are dinamycally intialized on first use of the helper functions const DEFAULT_PROVIDERS: Record = {}; +/** + * Base class for all providers. + * + * As an abstract class, it should not be used directly, but rather extended by other providers. + * + * It implements the common logic for all providers, such as caching, transformation, etc. + * Each provider that extends this class must implement the `_get` and `_getMultiple` abstract methods. + * + * These methods are responsible for retrieving the values from the underlying parameter store. They are + * called by the `get` and `getMultiple` methods, which are responsible for caching and transformation. + * + * If there are multiple calls to the same parameter but in a different transform, they will be stored multiple times. + * This allows us to optimize by transforming the data only once per retrieval, thus there is no need to transform cached values multiple times. + * + * However, this means that we need to make multiple calls to the underlying parameter store if we need to return it in different transforms. + * + * Since the number of supported transform is small and the probability that a given parameter will always be used in a specific transform, + * this should be an acceptable tradeoff. + */ abstract class BaseProvider implements BaseProviderInterface { protected store: Map; @@ -16,26 +35,28 @@ abstract class BaseProvider implements BaseProviderInterface { this.store = new Map(); } + /** + * Add a value to the cache. + * + * @param {string} key - Key of the cached value + * @param {string | Uint8Array | Record} value - Value to be cached + * @param {number} maxAge - Maximum age in seconds for the value to be cached + */ public addToCache(key: string, value: string | Uint8Array | Record, maxAge: number): void { if (maxAge <= 0) return; this.store.set(key, new ExpirableValue(value, maxAge)); } + /** + * Clear the cache. + */ public clearCache(): void { this.store.clear(); } /** - * Retrieve a parameter value or return the cached value - * - * If there are multiple calls to the same parameter but in a different transform, they will be stored multiple times. - * This allows us to optimize by transforming the data only once per retrieval, thus there is no need to transform cached values multiple times. - * - * However, this means that we need to make multiple calls to the underlying parameter store if we need to return it in different transforms. - * - * Since the number of supported transform is small and the probability that a given parameter will always be used in a specific transform, - * this should be an acceptable tradeoff. + * Retrieve a parameter value or return the cached value. * * @param {string} name - Parameter name * @param {GetOptionsInterface} options - Options to configure maximum age, trasformation, AWS SDK options, or force fetch @@ -68,6 +89,13 @@ abstract class BaseProvider implements BaseProviderInterface { return value; } + /** + * Retrieve multiple parameter values or return the cached values. + * + * @param {string} path - Parameters path + * @param {GetMultipleOptionsInterface} options - Options to configure maximum age, trasformation, AWS SDK options, or force fetch + * @returns + */ public async getMultiple(path: string, options?: GetMultipleOptionsInterface): Promise> { const configs = new GetMultipleOptions(options || {}); const key = [ path, configs.transform ].toString(); @@ -97,7 +125,7 @@ abstract class BaseProvider implements BaseProviderInterface { } /** - * Check whether a key has expired in the cache or not + * Check whether a key has expired in the cache or not. * * It returns true if the key is expired or not present in the cache. * @@ -111,7 +139,7 @@ abstract class BaseProvider implements BaseProviderInterface { } /** - * Retrieve parameter value from the underlying parameter store + * Retrieve parameter value from the underlying parameter store. * * @param {string} name - Parameter name * @param {unknown} options - Options to pass to the underlying implemented method @@ -119,7 +147,7 @@ abstract class BaseProvider implements BaseProviderInterface { protected abstract _get(name: string, options?: unknown): Promise; /** - * Retrieve multiple parameter values from the underlying parameter store + * Retrieve multiple parameter values from the underlying parameter store. * * @param {string} path - Parameter name * @param {unknown} options - Options to pass to the underlying implementated method @@ -128,6 +156,16 @@ abstract class BaseProvider implements BaseProviderInterface { } +/** + * Utility function to transform a value. + * + * It supports JSON and binary transformations, as well as an 'auto' mode that will try to transform the value based on the key. + * + * @param {string | Uint8Array | undefined} value - Value to be transformed + * @param {TransformOptions} transform - Transform to be applied, can be 'json', 'binary', or 'auto' + * @param {boolean} throwOnTransformError - Whether to throw an error if the transformation fails, when transforming multiple values this can be set to false + * @param {string} key - Key of the value to be transformed, used to determine the transformation method when using 'auto' + */ const transformValue = (value: string | Uint8Array | undefined, transform: TransformOptions, throwOnTransformError: boolean, key: string): string | Record | undefined => { try { const normalizedTransform = transform.toLowerCase(); @@ -159,6 +197,15 @@ const transformValue = (value: string | Uint8Array | undefined, transform: Trans } }; +/** + * Utility function to transform multiple values. + * + * It iterates over the values and applies the transformation to each one by calling the `transformValue` function. + * + * @param {Record} value - Values to be transformed + * @param {TransformOptions} transform - Transform to be applied, can be 'json', 'binary', or 'auto' + * @param {boolean} throwOnTransformError - Whether to throw an error if the transformation fails, when transforming multiple values this can be set to false + */ const transformValues = (value: Record, transform: TransformOptions, throwOnTransformError: boolean): Record | undefined> => { const transformedValues: Record | undefined> = {}; for (const [ entryKey, entryValue ] of Object.entries(value)) { diff --git a/packages/parameters/src/Exceptions.ts b/packages/parameters/src/Exceptions.ts index 845577e453..09712bb60c 100644 --- a/packages/parameters/src/Exceptions.ts +++ b/packages/parameters/src/Exceptions.ts @@ -1,5 +1,8 @@ class GetParameterError extends Error {} +/** + * Error thrown when a transform fails. + */ class TransformParameterError extends Error { public constructor(transform: string, message: string) { super(message); diff --git a/packages/parameters/src/ExpirableValue.ts b/packages/parameters/src/ExpirableValue.ts index 7dcc3a2473..35f6c13be8 100644 --- a/packages/parameters/src/ExpirableValue.ts +++ b/packages/parameters/src/ExpirableValue.ts @@ -1,15 +1,31 @@ import type { ExpirableValueInterface } from './types'; +/** + * Class to represent a value that can expire. + * + * Upon creation, the value is assigned a TTL (time to live) that is calculated + * by adding the current time with the maximum age. + */ class ExpirableValue implements ExpirableValueInterface { public ttl: number; public value: string | Uint8Array | Record; + /** + * + * @param value - Value to be cached + * @param maxAge - Maximum age in seconds for the value to be cached + */ public constructor(value: string | Uint8Array | Record, maxAge: number) { this.value = value; const timeNow = new Date(); this.ttl = timeNow.setSeconds(timeNow.getSeconds() + maxAge); } + /** + * Check if the value has expired. + * + * @returns {boolean} - True if the value has expired, false otherwise + */ public isExpired(): boolean { return this.ttl < Date.now(); } diff --git a/packages/parameters/src/GetMultipleOptions.ts b/packages/parameters/src/GetMultipleOptions.ts index 5f03a0ee3e..1b1b0180c6 100644 --- a/packages/parameters/src/GetMultipleOptions.ts +++ b/packages/parameters/src/GetMultipleOptions.ts @@ -1,6 +1,11 @@ import { DEFAULT_MAX_AGE_SECS } from './constants'; import type { GetMultipleOptionsInterface, TransformOptions } from './types'; +/** + * Options for the `getMultiple` method. + * + * It merges the default options with the provided options. + */ class GetMultipleOptions implements GetMultipleOptionsInterface { public forceFetch: boolean = false; public maxAge: number = DEFAULT_MAX_AGE_SECS; diff --git a/packages/parameters/src/GetOptions.ts b/packages/parameters/src/GetOptions.ts index 807962a5aa..254412f127 100644 --- a/packages/parameters/src/GetOptions.ts +++ b/packages/parameters/src/GetOptions.ts @@ -1,6 +1,11 @@ import { DEFAULT_MAX_AGE_SECS } from './constants'; import type { GetOptionsInterface, TransformOptions } from './types'; +/** + * Options for the `get` method. + * + * It merges the default options with the provided options. + */ class GetOptions implements GetOptionsInterface { public forceFetch: boolean = false; public maxAge: number = DEFAULT_MAX_AGE_SECS; diff --git a/packages/parameters/src/appconfig/AppConfigProvider.ts b/packages/parameters/src/appconfig/AppConfigProvider.ts index ce42545055..f663ab6de8 100644 --- a/packages/parameters/src/appconfig/AppConfigProvider.ts +++ b/packages/parameters/src/appconfig/AppConfigProvider.ts @@ -10,6 +10,159 @@ import type { AppConfigGetOptionsInterface, } from '../types/AppConfigProvider'; +/** + * ## Intro + * The Parameters utility provides an AppConfigProvider that allows to retrieve configuration profiles from AWS AppConfig. + * + * ## Getting started + * + * This utility supports AWS SDK v3 for JavaScript only. This allows the utility to be modular, and you to install only + * the SDK packages you need and keep your bundle size small. + * + * To use the provider, you must install the Parameters utility and the AWS SDK v3 for JavaScript for AppConfig: + * + * ```sh + * npm install @aws-lambda-powertools/parameters @aws-sdk/client-appconfigdata + * ``` + * + * ## Basic usage + * + * @example + * ```typescript + * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; + * + * const configProvider = new AppConfigProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve a configuration profile + * const encodedConfig = await configProvider.get('my-config'); + * const config = new TextDecoder('utf-8').decode(encodedConfig); + * }; + * ``` + * If you want to retrieve configs without customizing the provider, you can use the {@link getAppConfig} function instead. + * + * ## Advanced usage + * + * ### Caching + * + * By default, the provider will cache parameters retrieved in-memory for 5 seconds. + * You can adjust how long values should be kept in cache by using the `maxAge` parameter. + * + * @example + * ```typescript + * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; + * + * const configProvider = new AppConfigProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve a configuration profile and cache it for 10 seconds + * const encodedConfig = await configProvider.get('my-config', { maxAge: 10 }); + * const config = new TextDecoder('utf-8').decode(encodedConfig); + * }; + * ``` + * + * If instead you'd like to always ensure you fetch the latest parameter from the store regardless if already available in cache, use the `forceFetch` parameter. + * + * @example + * ```typescript + * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; + * + * const configProvider = new AppConfigProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve a config and always fetch the latest value + * const config = await configProvider.get('my-config', { forceFetch: true }); + * const config = new TextDecoder('utf-8').decode(encodedConfig); + * }; + * ``` + * + * ### Transformations + * + * For configurations stored as freeform JSON, Freature Flag, you can use the transform argument for deserialization. This will return a JavaScript object instead of a string. + * + * @example + * ```typescript + * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; + * + * const configProvider = new AppConfigProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve a JSON config or Feature Flag and parse it as JSON + * const config = await configProvider.get('my-config', { transform: 'json' }); + * }; + * ``` + * + * For configurations that are instead stored as base64-encoded binary data, you can use the transform argument set to `binary` for decoding. This will return a decoded string. + * + * @example + * ```typescript + * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; + * + * const configProvider = new AppConfigProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve a base64-encoded string and decode it + * const config = await configProvider.get('my-config', { transform: 'binary' }); + * }; + * ``` + * + * ### Extra SDK options + * + * When retrieving a configuration profile, you can pass extra options to the AWS SDK v3 for JavaScript client by using the `sdkOptions` parameter. + * + * @example + * ```typescript + * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; + * + * const configProvider = new AppConfigProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve a config and pass extra options to the AWS SDK v3 for JavaScript client + * const config = await configProvider.get('my-config', { + * sdkOptions: { + * RequiredMinimumPollIntervalInSeconds: 60, + * }, + * }); + * const config = new TextDecoder('utf-8').decode(encodedConfig); + * }; + * ``` + * + * This object accepts the same options as the [AWS SDK v3 for JavaScript AppConfigData client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-appconfigdata/interfaces/startconfigurationsessioncommandinput.html). + * + * ### Customize AWS SDK v3 for JavaScript client + * + * By default, the provider will create a new AppConfigData client using the default configuration. + * + * You can customize the client by passing a custom configuration object to the provider. + * + * @example + * ```typescript + * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; + * + * const configProvider = new AppConfigProvider({ + * clientConfig: { region: 'eu-west-1' }, + * }); + * ``` + * + * This object accepts the same options as the [AWS SDK v3 for JavaScript AppConfig Data client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-appconfigdata/interfaces/appconfigdataclientconfig.html). + * + * Otherwise, if you want to use a custom client altogether, you can pass it to the provider. + * + * @example + * ```typescript + * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; + * import { AppConfigDataClient } from '@aws-sdk/client-appconfigdata'; + * + * const client = new AppConfigDataClient({ region: 'eu-west-1' }); + * const configProvider = new AppConfigProvider({ + * awsSdkV3Client: client, + * }); + * ``` + * + * This object must be an instance of the [AWS SDK v3 for JavaScript AppConfig Data client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-appconfigdata/classes/appconfigdataclient.html). + * + * For more usage examples, see [our documentation](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/). + */ class AppConfigProvider extends BaseProvider { public client: AppConfigDataClient; protected configurationTokenStore: Map = new Map(); @@ -18,9 +171,9 @@ class AppConfigProvider extends BaseProvider { private environment: string; /** - * It initializes the AppConfigProvider class'. + * It initializes the AppConfigProvider class. * * - * @param {AppConfigProviderOptions} options + * @param {AppConfigProviderOptions} options - The configuration object. */ public constructor(options: AppConfigProviderOptions) { super(); @@ -45,7 +198,32 @@ class AppConfigProvider extends BaseProvider { } /** - * Retrieve a configuration from AWS App config. + * Retrieve a configuration profile from AWS AppConfig. + * + * @example + * ```typescript + * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; + * + * const configProvider = new AppConfigProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve a configuration profile + * const encodedConfig = await configProvider.get('my-config'); + * const config = new TextDecoder('utf-8').decode(encodedConfig); + * }; + * ``` + * + * You can customize the retrieval of the configuration profile by passing options to the function: + * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) + * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache + * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` + * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client + * + * For usage examples check {@link AppConfigProvider}. + * + * @param {string} name - The name of the configuration profile or its ID + * @param {GetAppConfigCombinedInterface} options - Options to configure the provider + * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ */ public async get( name: string, @@ -55,7 +233,7 @@ class AppConfigProvider extends BaseProvider { } /** - * Retrieving multiple configurations is not supported by AWS App Config Provider. + * Retrieving multiple configurations is not supported by AWS AppConfig. */ public async getMultiple( path: string, @@ -65,11 +243,10 @@ class AppConfigProvider extends BaseProvider { } /** - * Retrieve a configuration from AWS App config. + * Retrieve a configuration from AWS AppConfig. * - * @param {string} name - Name of the configuration + * @param {string} name - Name of the configuration or its ID * @param {AppConfigGetOptionsInterface} options - SDK options to propagate to `StartConfigurationSession` API call - * @returns {Promise} */ protected async _get( name: string, @@ -85,7 +262,6 @@ class AppConfigProvider extends BaseProvider { * but, we don't want to return an empty value to our callers. * {@link https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-retrieving-the-configuration.html} **/ - if (!this.configurationTokenStore.has(name)) { const sessionOptions: StartConfigurationSessionCommandInput = { @@ -133,7 +309,7 @@ class AppConfigProvider extends BaseProvider { } /** - * Retrieving multiple configurations is not supported by AWS App Config Provider API. + * Retrieving multiple configurations is not supported by AWS AppConfig. * * @throws Not Implemented Error. */ @@ -141,11 +317,7 @@ class AppConfigProvider extends BaseProvider { _path: string, _sdkOptions?: unknown ): Promise> { - return this._notImplementedError(); - } - - private _notImplementedError(): never { - throw new Error('Not Implemented'); + throw new Error('Method not implemented.'); } } diff --git a/packages/parameters/src/appconfig/getAppConfig.ts b/packages/parameters/src/appconfig/getAppConfig.ts index ca336ea777..7613e0d942 100644 --- a/packages/parameters/src/appconfig/getAppConfig.ts +++ b/packages/parameters/src/appconfig/getAppConfig.ts @@ -2,11 +2,120 @@ import { AppConfigProvider, DEFAULT_PROVIDERS } from './AppConfigProvider'; import type { GetAppConfigCombinedInterface } from '../types/AppConfigProvider'; /** - * Gets the AppConfig data for the specified name. + * ## Intro + * The Parameters utility provides an AppConfigProvider that allows to retrieve configuration profiles from AWS AppConfig. + * + * ## Getting started + * + * This utility supports AWS SDK v3 for JavaScript only. This allows the utility to be modular, and you to install only + * the SDK packages you need and keep your bundle size small. + * + * To use the provider, you must install the Parameters utility and the AWS SDK v3 for JavaScript for AppConfig: + * + * ```sh + * npm install @aws-lambda-powertools/parameters @aws-sdk/client-appconfigdata + * ``` * - * @param {string} name - The configuration profile ID or the configuration profile name. - * @param {GetAppConfigCombinedInterface} options - Options for the AppConfigProvider and the get method. - * @returns {Promise>} A promise that resolves to the AppConfig data or undefined if not found. + * ## Basic usage + * + * @example + * ```typescript + * import { getAppConfig } from '@aws-lambda-powertools/parameters/appconfig'; + * + * export const handler = async (): Promise => { + * // Retrieve a configuration profile + * const encodedConfig = await getAppConfig('my-config'); + * const config = new TextDecoder('utf-8').decode(encodedConfig); + * }; + * ``` + * + * ## Advanced usage + * + * ### Caching + * + * By default, the provider will cache parameters retrieved in-memory for 5 seconds. + * You can adjust how long values should be kept in cache by using the `maxAge` parameter. + * + * @example + * ```typescript + * import { getAppConfig } from '@aws-lambda-powertools/parameters/appconfig'; + * + * export const handler = async (): Promise => { + * // Retrieve a configuration profile and cache it for 10 seconds + * const encodedConfig = await getAppConfig('my-config'); + * const config = new TextDecoder('utf-8').decode(encodedConfig); + * }; + * ``` + * + * If instead you'd like to always ensure you fetch the latest parameter from the store regardless if already available in cache, use the `forceFetch` parameter. + * + * @example + * ```typescript + * import { getAppConfig } from '@aws-lambda-powertools/parameters/appconfig'; + * + * export const handler = async (): Promise => { + * // Retrieve a config and always fetch the latest value + * const config = await getAppConfig('my-config', { forceFetch: true }); + * const config = new TextDecoder('utf-8').decode(encodedConfig); + * }; + * ``` + * + * ### Transformations + * + * For configurations stored as freeform JSON, Freature Flag, you can use the transform argument for deserialization. This will return a JavaScript object instead of a string. + * + * @example + * ```typescript + * import { getAppConfig } from '@aws-lambda-powertools/parameters/appconfig'; + * + * export const handler = async (): Promise => { + * // Retrieve a JSON config or Feature Flag and parse it as JSON + * const config = await getAppConfig('my-config', { transform: 'json' }); + * }; + * ``` + * + * For configurations that are instead stored as base64-encoded binary data, you can use the transform argument set to `binary` for decoding. This will return a decoded string. + * + * @example + * ```typescript + * import { getAppConfig } from '@aws-lambda-powertools/parameters/appconfig'; + * + * export const handler = async (): Promise => { + * // Retrieve a base64-encoded string and decode it + * const config = await getAppConfig('my-config', { transform: 'binary' }); + * }; + * ``` + * + * ### Extra SDK options + * + * When retrieving a configuration profile, you can pass extra options to the AWS SDK v3 for JavaScript client by using the `sdkOptions` parameter. + * + * @example + * ```typescript + * import { getAppConfig } from '@aws-lambda-powertools/parameters/appconfig'; + * + * export const handler = async (): Promise => { + * // Retrieve a config and pass extra options to the AWS SDK v3 for JavaScript client + * const config = await getAppConfig('my-config', { + * sdkOptions: { + * RequiredMinimumPollIntervalInSeconds: 60, + * }, + * }); + * const config = new TextDecoder('utf-8').decode(encodedConfig); + * }; + * ``` + * + * This object accepts the same options as the [AWS SDK v3 for JavaScript AppConfigData client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-appconfigdata/interfaces/startconfigurationsessioncommandinput.html). + * + * ### Built-in provider class + * + * For greater flexibility such as configuring the underlying SDK client used by built-in providers, you can use the {@link AppConfigProvider} class. + * + * For more usage examples, see [our documentation](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/). + * + * @param {string} name - The name of the configuration profile or its ID + * @param {GetAppConfigCombinedInterface} options - Options to configure the provider + * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ */ const getAppConfig = ( name: string, diff --git a/packages/parameters/src/docs.ts b/packages/parameters/src/docs.ts new file mode 100644 index 0000000000..028b2bbc6b --- /dev/null +++ b/packages/parameters/src/docs.ts @@ -0,0 +1,4 @@ +export * from './appconfig'; +export * from './ssm'; +export * from './secrets'; +export * from './dynamodb'; \ No newline at end of file diff --git a/packages/parameters/src/dynamodb/DynamoDBProvider.ts b/packages/parameters/src/dynamodb/DynamoDBProvider.ts index 58f0f2456b..d178542894 100644 --- a/packages/parameters/src/dynamodb/DynamoDBProvider.ts +++ b/packages/parameters/src/dynamodb/DynamoDBProvider.ts @@ -9,6 +9,226 @@ import type { GetItemCommandInput, QueryCommandInput } from '@aws-sdk/client-dyn import { marshall, unmarshall } from '@aws-sdk/util-dynamodb'; import type { PaginationConfiguration } from '@aws-sdk/types'; +/** + * ## Intro + * The Parameters utility provides a DynamoDBProvider that allows to retrieve values from Amazon DynamoDB. + * + * ## Getting started + * + * This utility supports AWS SDK v3 for JavaScript only. This allows the utility to be modular, and you to install only + * the SDK packages you need and keep your bundle size small. + * + * To use the provider, you must install the Parameters utility and the AWS SDK v3 for JavaScript for AppConfig: + * + * ```sh + * npm install @aws-lambda-powertools/parameters @aws-sdk/client-dynamodb @aws-sdk/util-dynamodb + * ``` + * + * ## Basic usage + * + * Retrieve a value from DynamoDB: + * + * @example + * ```typescript + * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; + * + * const tableProvider = new DynamoDBProvider({ + * tableName: 'my-table', + * }); + * + * export const handler = async (): Promise => { + * // Retrieve a value from DynamoDB + * const value = await tableProvider.get('my-value-key'); + * }; + * ``` + * + * You can also retrieve multiple values at once: + * + * @example + * ```typescript + * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; + * + * const tableProvider = new DynamoDBProvider({ + * tableName: 'my-table', + * }); + * + * export const handler = async (): Promise => { + * // Retrieve multiple values from DynamoDB + * const values = await tableProvider.getMultiple('my-values-path'); + * }; + * + * ## Advanced usage + * + * ### Caching + * + * By default, the provider will cache parameters retrieved in-memory for 5 seconds. + * You can adjust how long values should be kept in cache by using the `maxAge` parameter. + * + * @example + * ```typescript + * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; + * + * const tableProvider = new DynamoDBProvider({ + * tableName: 'my-table', + * }); + * + * export const handler = async (): Promise => { + * // Retrieve a value and cache it for 10 seconds + * const value = await tableProvider.get('my-value-key', { maxAge: 10 }); + * // Retrieve multiple values and cache them for 20 seconds + * const values = await tableProvider.getMultiple('my-values-path', { maxAge: 20 }); + * }; + * ``` + * + * If instead you'd like to always ensure you fetch the latest parameter from the store regardless if already available in cache, use the `forceFetch` parameter. + * + * @example + * ```typescript + * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; + * + * const tableProvider = new DynamoDBProvider({ + * tableName: 'my-table', + * }); + * + * export const handler = async (): Promise => { + * // Retrieve a value and skip cache + * const value = await tableProvider.get('my-value-key', { forceFetch: true }); + * // Retrieve multiple values and skip cache + * const values = await tableProvider.getMultiple('my-values-path', { forceFetch: true }); + * }; + * ``` + * + * ### Transformations + * + * For values stored as JSON you can use the transform argument for deserialization. This will return a JavaScript object instead of a string. + * + * @example + * ```typescript + * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; + * + * const tableProvider = new DynamoDBProvider({ + * tableName: 'my-table', + * }); + * + * export const handler = async (): Promise => { + * // Retrieve a value and parse it as JSON + * const value = await tableProvider.get('my-value-key', { transform: 'json' }); + * // Retrieve multiple values and parse them as JSON + * const values = await tableProvider.getMultiple('my-values-path', { transform: 'json' }); + * }; + * ``` + * + * For values that are instead stored as base64-encoded binary data, you can use the transform argument set to `binary` for decoding. This will return a decoded string. + * + * @example + * ```typescript + * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; + * + * const tableProvider = new DynamoDBProvider({ + * tableName: 'my-table', + * }); + * + * export const handler = async (): Promise => { + * // Retrieve a base64-encoded string and decode it + * const value = await tableProvider.get('my-value-key', { transform: 'binary' }); + * // Retrieve multiple base64-encoded strings and decode them + * const values = await tableProvider.getMultiple('my-values-path', { transform: 'binary' }); + * }; + * ``` + * + * When retrieving multiple values, you can also use the `transform` argument set to `auto` to let the provider automatically detect the type of transformation to apply. + * The provider will use the suffix of the sort key (`sk`) to determine the transformation to apply. For example, if the sort key is `my-value-key.json`, the provider will + * automatically parse the value as JSON. Likewise, if the sort key is `my-value-key.binary`, the provider will automatically decode the value as base64-encoded binary data. + * + * @example + * ```typescript + * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; + * + * const tableProvider = new DynamoDBProvider({ + * tableName: 'my-table', + * }); + * + * export const handler = async (): Promise => { + * // Retrieve multiple values and automatically detect the transformation to apply + * const values = await tableProvider.getMultiple('my-values-path', { transform: 'auto' }); + * }; + * ``` + * + * ### Custom key names + * + * By default, the provider will use the following key names: `id` for the partition key, `sk` for the sort key, and `value` for the value. + * You can adjust the key names by using the `keyAttr`, `sortAttr`, and `valueAttr` parameters. + * + * @example + * ```typescript + * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; + * + * const tableProvider = new DynamoDBProvider({ + * tableName: 'my-table', + * keyAttr: 'key', + * sortAttr: 'sort', + * valueAttr: 'val', + * }); + * ``` + * + * ### Extra SDK options + * + * When retrieving values, you can pass extra options to the AWS SDK v3 for JavaScript client by using the `sdkOptions` parameter. + * + * @example + * ```typescript + * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; + * + * const tableProvider = new DynamoDBProvider({ + * tableName: 'my-table', + * }); + * + * export const handler = async (): Promise => { + * // Retrieve a value and pass extra options to the AWS SDK v3 for JavaScript client + * const value = await tableProvider.get('my-value-key', { + * sdkOptions: { + * ConsistentRead: true, + * }, + * }); + * }; + * ``` + * + * The objects accept the same options as respectively the [AWS SDK v3 for JavaScript PutItem command](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-dynamodb/classes/putitemcommand.html) and the [AWS SDK v3 for JavaScript DynamoDB client Query command](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-dynamodb/classes/querycommand.html). + * + * ### Customize AWS SDK v3 for JavaScript client + * + * By default, the provider will create a new DynamoDB client using the default configuration. + * + * You can customize the client by passing a custom configuration object to the provider. + * + * @example + * ```typescript + * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; + * + * const tableProvider = new DynamoDBProvider({ + * clientConfig: { region: 'eu-west-1' }, + * }); + * ``` + * + * This object accepts the same options as the [AWS SDK v3 for JavaScript DynamoDB client constructor](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-dynamodb/classes/dynamodbclient.html). + * + * Otherwise, if you want to use a custom client altogether, you can pass it to the provider. + * + * @example + * ```typescript + * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; + * import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; + * + * const client = new DynamoDBClient({ region: 'eu-west-1' }); + * const tableProvider = new DynamoDBProvider({ + * awsSdkV3Client: client, + * }); + * ``` + * + * This object must be an instance of the [AWS SDK v3 for JavaScript DynamoDB client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-dynamodb/classes/dynamodbclient.html). + * + * For more usage examples, see [our documentation](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/). + */ class DynamoDBProvider extends BaseProvider { public client: DynamoDBClient; protected keyAttr: string = 'id'; @@ -16,6 +236,11 @@ class DynamoDBProvider extends BaseProvider { protected tableName: string; protected valueAttr: string = 'value'; + /** + * It initializes the DynamoDBProvider class. + * + * @param {DynamoDBProviderOptions} config - The configuration object. + */ public constructor(config: DynamoDBProviderOptions) { super(); @@ -36,6 +261,35 @@ class DynamoDBProvider extends BaseProvider { if (config.valueAttr) this.valueAttr = config.valueAttr; } + /** + * Retrieve a value from Amazon DynamoDB. + * + * @example + * ```typescript + * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; + * + * const tableProvider = new DynamoDBProvider({ + * tableName: 'my-table', + * }); + * + * export const handler = async (): Promise => { + * // Retrieve a single value + * const value = await tableProvider.get('my-value-key'); + * }; + * ``` + * + * You can customize the retrieval of the value by passing options to the function: + * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) + * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache + * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` + * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client + * + * For usage examples check {@link DynamoDBProvider}. + * + * @param {string} name - The name of the value to retrieve (i.e. the partition key) + * @param {DynamoDBGetOptionsInterface} options - Options to configure the provider + * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ + */ public async get( name: string, options?: DynamoDBGetOptionsInterface @@ -43,6 +297,36 @@ class DynamoDBProvider extends BaseProvider { return super.get(name, options) as Promise>; } + /** + * Retrieve multiple values from Amazon DynamoDB. + * + * @example + * ```typescript + * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; + * + * const tableProvider = new DynamoDBProvider({ + * tableName: 'my-table', + * }); + * + * export const handler = async (): Promise => { + * // Retrieve multiple values + * const values = await tableProvider.getMultiple('my-values-path'); + * }; + * ``` + * + * You can customize the retrieval of the values by passing options to the function: + * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) + * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache + * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` + * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client + * * `throwOnTransformError` - Whether to throw an error if the transform fails (default: `true`) + * + * For usage examples check {@link DynamoDBProvider}. + * + * @param {string} path - The path of the values to retrieve (i.e. the partition key) + * @param {DynamoDBGetMultipleOptionsInterface} options - Options to configure the provider + * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ + */ public async getMultiple( path: string, options?: DynamoDBGetMultipleOptionsInterface @@ -50,6 +334,12 @@ class DynamoDBProvider extends BaseProvider { return super.getMultiple(path, options); } + /** + * Retrieve an item from Amazon DynamoDB. + * + * @param {string} name - Key of the item to retrieve (i.e. the partition key) + * @param {DynamoDBGetOptionsInterface} options - Options to customize the retrieval + */ protected async _get( name: string, options?: DynamoDBGetOptionsInterface @@ -68,6 +358,12 @@ class DynamoDBProvider extends BaseProvider { return result.Item ? unmarshall(result.Item)[this.valueAttr] : undefined; } + /** + * Retrieve multiple items from Amazon DynamoDB. + * + * @param {string} path - The path of the values to retrieve (i.e. the partition key) + * @param {DynamoDBGetMultipleOptionsInterface} options - Options to customize the retrieval + */ protected async _getMultiple( path: string, options?: DynamoDBGetMultipleOptionsInterface diff --git a/packages/parameters/src/secrets/SecretsProvider.ts b/packages/parameters/src/secrets/SecretsProvider.ts index 12a6703783..c8d1e56b77 100644 --- a/packages/parameters/src/secrets/SecretsProvider.ts +++ b/packages/parameters/src/secrets/SecretsProvider.ts @@ -9,9 +9,154 @@ import type { SecretsGetOptionsInterface } from '../types/SecretsProvider'; +/** + * ## Intro + * The Parameters utility provides a SecretsProvider that allows to retrieve secrets from AWS Secrets Manager. + * + * ## Getting started + * + * This utility supports AWS SDK v3 for JavaScript only. This allows the utility to be modular, and you to install only + * the SDK packages you need and keep your bundle size small. + * + * To use the provider, you must install the Parameters utility and the AWS SDK v3 for JavaScript for Secrets Manager: + * + * ```sh + * npm install @aws-lambda-powertools/parameters @aws-sdk/client-secrets-manager + * ``` + * + * ## Basic usage + * + * @example + * ```typescript + * import { SecretsProvider } from '@aws-lambda-powertools/parameters/secrets'; + * + * const secretsProvider = new SecretsProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve a secret + * const secret = await secretsProvider.get('my-secret'); + * }; + * ``` + * + * If you want to retrieve secrets without customizing the provider, you can use the {@link getSecret} function instead. + * + * ## Advanced usage + * + * ### Caching + * + * By default, the provider will cache parameters retrieved in-memory for 5 seconds. + * You can adjust how long values should be kept in cache by using the `maxAge` parameter. + * + * @example + * ```typescript + * import { SecretsProvider } from '@aws-lambda-powertools/parameters/secrets'; + * + * const secretsProvider = new SecretsProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve a secret and cache it for 10 seconds + * const secret = await secretsProvider.get('my-secret', { maxAge: 10 }); + * }; + * ``` + * + * If instead you'd like to always ensure you fetch the latest parameter from the store regardless if already available in cache, use the `forceFetch` parameter. + * + * @example + * ```typescript + * import { SecretsProvider } from '@aws-lambda-powertools/parameters/secrets'; + * + * const secretsProvider = new SecretsProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve a secret and always fetch the latest value + * const secret = await secretsProvider.get('my-secret', { forceFetch: true }); + * }; + * ``` + * + * ### Transformations + * + * For parameters stored in JSON or Base64 format, you can use the transform argument for deserialization. + * + * @example + * ```typescript + * import { SecretsProvider } from '@aws-lambda-powertools/parameters/secrets'; + * + * const secretsProvider = new SecretsProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve a secret and parse it as JSON + * const secret = await secretsProvider.get('my-secret', { transform: 'json' }); + * }; + * ``` + * + * ### Extra SDK options + * + * When retrieving a secret, you can pass extra options to the AWS SDK v3 for JavaScript client by using the `sdkOptions` parameter. + * + * @example + * ```typescript + * import { SecretsProvider } from '@aws-lambda-powertools/parameters/secrets'; + * + * const secretsProvider = new SecretsProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve a secret and pass extra options to the AWS SDK v3 for JavaScript client + * const secret = await secretsProvider.get('my-secret', { + * sdkOptions: { + * VersionId: 1, + * }, + * }); + * }; + * ``` + * + * This object accepts the same options as the [AWS SDK v3 for JavaScript Secrets Manager client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-secrets-manager/interfaces/getsecretvaluecommandinput.html). + * + * ### Customize AWS SDK v3 for JavaScript client + * + * By default, the provider will create a new Secrets Manager client using the default configuration. + * + * You can customize the client by passing a custom configuration object to the provider. + * + * @example + * ```typescript + * import { SecretsProvider } from '@aws-lambda-powertools/parameters/secrets'; + * + * const secretsProvider = new SecretsProvider({ + * clientConfig: { region: 'eu-west-1' }, + * }); + * ``` + * + * This object accepts the same options as the [AWS SDK v3 for JavaScript Secrets Manager client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-secrets-manager/interfaces/secretsmanagerclientconfig.html). + * + * Otherwise, if you want to use a custom client altogether, you can pass it to the provider. + * + * @example + * ```typescript + * import { SecretsProvider } from '@aws-lambda-powertools/parameters/secrets'; + * import { SecretsManagerClient } from '@aws-sdk/client-secrets-manager'; + * + * const client = new SecretsManagerClient({ region: 'eu-west-1' }); + * const secretsProvider = new SecretsProvider({ + * awsSdkV3Client: client, + * }); + * ``` + * + * This object must be an instance of the [AWS SDK v3 for JavaScript Secrets Manager client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-secrets-manager/classes/secretsmanagerclient.html). + * + * For more usage examples, see [our documentation](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/). + * + * @class + * @implements {BaseProvider} + * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ + */ class SecretsProvider extends BaseProvider { public client: SecretsManagerClient; + /** + * It initializes the SecretsProvider class. + * + * @param {SecretsProviderOptions} config - The configuration object. + */ public constructor (config?: SecretsProviderOptions) { super(); @@ -28,6 +173,33 @@ class SecretsProvider extends BaseProvider { } + /** + * Retrieve a secret from AWS Secrets Manager. + * + * @example + * ```typescript + * import { SecretsProvider } from '@aws-lambda-powertools/parameters/secrets'; + * + * const secretsProvider = new SecretsProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve a secret + * const secret = await secretsProvider.get('my-secret'); + * }; + * ``` + * + * You can customize the retrieval of the secret by passing options to the function: + * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) + * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache + * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` + * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client + * + * For usage examples check {@link SecretsProvider}. + * + * @param {string} name - The name of the secret + * @param {SecretsGetOptionsInterface} options - Options to customize the retrieval of the secret + * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ + */ public async get( name: string, options?: SecretsGetOptionsInterface @@ -45,6 +217,12 @@ class SecretsProvider extends BaseProvider { return super.getMultiple(path); } + /** + * Retrieve a configuration from AWS AppConfig. + * + * @param {string} name - Name of the configuration or its ID + * @param {SecretsGetOptionsInterface} options - SDK options to propagate to the AWS SDK v3 for JavaScript client + */ protected async _get( name: string, options?: SecretsGetOptionsInterface @@ -63,6 +241,8 @@ class SecretsProvider extends BaseProvider { /** * Retrieving multiple parameter values is not supported with AWS Secrets Manager. + * + * @throws Not Implemented Error. */ protected async _getMultiple( _path: string, diff --git a/packages/parameters/src/secrets/getSecret.ts b/packages/parameters/src/secrets/getSecret.ts index 92f123e6e5..a6b39b3229 100644 --- a/packages/parameters/src/secrets/getSecret.ts +++ b/packages/parameters/src/secrets/getSecret.ts @@ -2,6 +2,107 @@ import { DEFAULT_PROVIDERS } from '../BaseProvider'; import { SecretsProvider } from './SecretsProvider'; import type { SecretsGetOptionsInterface } from '../types/SecretsProvider'; +/** + * ## Intro + * The Parameters utility provides a SecretsProvider that allows to retrieve secrets from AWS Secrets Manager. + * + * ## Getting started + * + * This utility supports AWS SDK v3 for JavaScript only. This allows the utility to be modular, and you to install only + * the SDK packages you need and keep your bundle size small. + * + * To use the provider, you must install the Parameters utility and the AWS SDK v3 for JavaScript for Secrets Manager: + * + * ```sh + * npm install @aws-lambda-powertools/parameters @aws-sdk/client-secrets-manager + * ``` + * + * ## Basic usage + * + * @example + * ```typescript + * import { getSecret } from '@aws-lambda-powertools/parameters/secrets'; + * + * export const handler = async (): Promise => { + * // Retrieve a secret + * const secret = await getSecret('my-secret'); + * }; + * ``` + * + * ## Advanced usage + * + * ### Caching + * + * By default, the provider will cache parameters retrieved in-memory for 5 seconds. + * You can adjust how long values should be kept in cache by using the `maxAge` parameter. + * + * @example + * ```typescript + * import { getSecret } from '@aws-lambda-powertools/parameters/secrets'; + * + * export const handler = async (): Promise => { + * // Retrieve a secret and cache it for 10 seconds + * const secret = await getSecret('my-secret', { maxAge: 10 }); + * }; + * ``` + * + * If instead you'd like to always ensure you fetch the latest parameter from the store regardless if already available in cache, use the `forceFetch` parameter. + * + * @example + * ```typescript + * import { getSecret } from '@aws-lambda-powertools/parameters/secrets'; + * + * export const handler = async (): Promise => { + * // Retrieve a secret and always fetch the latest value + * const secret = await getSecret('my-secret', { forceFetch: true }); + * }; + * ``` + * + * ### Transformations + * + * For parameters stored as JSON or base64-encoded strings, you can use the transform argument set to `json` or `binary` for deserialization. + * + * @example + * ```typescript + * import { getSecret } from '@aws-lambda-powertools/parameters/secrets'; + * + * export const handler = async (): Promise => { + * // Retrieve a secret and parse it as JSON + * const secret = await getSecret('my-secret', { transform: 'json' }); + * }; + * ``` + * + * ### Extra SDK options + * + * When retrieving a secret, you can pass extra options to the AWS SDK v3 for JavaScript client by using the `sdkOptions` parameter. + * + * @example + * ```typescript + * import { getSecret } from '@aws-lambda-powertools/parameters/secrets'; + * + * export const handler = async (): Promise => { + * // Retrieve a secret and pass extra options to the AWS SDK v3 for JavaScript client + * const secret = await getSecret('my-secret', { + * sdkOptions: { + * VersionId: 1, + * }, + * }); + * }; + * ``` + * + * This object accepts the same options as the [AWS SDK v3 for JavaScript Secrets Manager client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-secrets-manager/interfaces/getsecretvaluecommandinput.html). + * + * ### Built-in provider class + * + * For greater flexibility such as configuring the underlying SDK client used by built-in providers, you can use the {@link SecretsProvider} class. + * + * For more usage examples, see [our documentation](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/). + * + * + * @param {string} name - The name of the secret to retrieve + * @param {SecretsGetOptionsInterface} options - Options to configure the provider + * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ + */ const getSecret = async (name: string, options?: SecretsGetOptionsInterface): Promise> => { if (!DEFAULT_PROVIDERS.hasOwnProperty('secrets')) { DEFAULT_PROVIDERS.secrets = new SecretsProvider(); diff --git a/packages/parameters/src/ssm/SSMProvider.ts b/packages/parameters/src/ssm/SSMProvider.ts index dd45070162..28818a8aaa 100644 --- a/packages/parameters/src/ssm/SSMProvider.ts +++ b/packages/parameters/src/ssm/SSMProvider.ts @@ -24,11 +24,254 @@ import type { } from '../types/SSMProvider'; import type { PaginationConfiguration } from '@aws-sdk/types'; +/** + * ## Intro + * The Parameters utility provides a SSMProvider that allows to retrieve parameters from AWS Systems Manager. + * + * ## Getting started + * + * This utility supports AWS SDK v3 for JavaScript only. This allows the utility to be modular, and you to install only + * the SDK packages you need and keep your bundle size small. + * + * To use the provider, you must install the Parameters utility and the AWS SDK v3 for JavaScript for AppConfig: + * + * ```sh + * npm install @aws-lambda-powertools/parameters @aws-sdk/client-ssm + * ``` + * + * ## Basic usage + * + * Retrieve a parameter from SSM: + * + * @example + * ```typescript + * import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm'; + * + * const parametersProvider = new SSMProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve a parameter from SSM + * const parameter = await parametersProvider.get('/my-parameter'); + * }; + * ``` + * + * If you want to retrieve a parameter without customizing the provider, you can use the {@link getParameter} function instead. + * + * You can also retrieve parameters at once. If you want to get multiple parameters under the same path, you can use the `getMultiple` method. + * + * @example + * ```typescript + * import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm'; + * + * const parametersProvider = new SSMProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve multiple parameters by path from SSM + * const parameters = await parametersProvider.getMultiple('/my-parameters-path'); + * }; + * + * If you don't need to customize the provider, you can also use the {@link getParameters} function instead. + * + * If instead you want to retrieve multiple parameters by name, you can use the `getParametersByName` method. + * + * @example + * ```typescript + * import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm'; + * + * const parametersProvider = new SSMProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve multiple parameters by name from SSM + * const parameters = await parametersProvider.getParametersByName({ + * '/my-parameter-1': {}, // Use default options + * '/my-parameter-2': { transform: 'json' }, // Parse the value as JSON + * }); + * }; + * ``` + * + * If you don't need to customize the provider, you can also use the {@link getParametersByName} function instead. + * + * ## Advanced usage + * + * ### Caching + * + * By default, the provider will cache parameters retrieved in-memory for 5 seconds. + * You can adjust how long values should be kept in cache by using the `maxAge` parameter. + * + * @example + * ```typescript + * import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm'; + * + * const parametersProvider = new SSMProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve a parameter and cache it for 10 seconds + * const parameter = await parametersProvider.get('/my-parameter', { maxAge: 10 }); + * // Retrieve multiple parameters by path and cache them for 20 seconds + * const parameters = await parametersProvider.getMultiple('/my-parameters-path', { maxAge: 20 }); + * }; + * ``` + * + * When using the `getParametersByName` method, you can set a different `maxAge` for each parameter or set a default `maxAge` for all parameters. + * + * @example + * ```typescript + * import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm'; + * + * const parametersProvider = new SSMProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve multiple parameters by name and cache them individually + * const parameters = await parametersProvider.getParametersByName({ + * '/my-parameter-1': { maxAge: 10 }, // Cache for 10 seconds + * '/my-parameter-2': { maxAge: 20 }, // Cache for 20 seconds + * }); + * // Retrieve multiple parameters by name and cache them all for 20 seconds + * const parameters = await parametersProvider.getParametersByName({ + * '/my-parameter-1': {}, + * '/my-parameter-2': {}, + * }, { maxAge: 20 }); + * }; + * ``` + * + * If instead you'd like to always ensure you fetch the latest parameter from the store regardless if already available in cache, use the `forceFetch` parameter. + * + * @example + * ```typescript + * import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm'; + * + * const parametersProvider = new SSMProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve a parameter and skip cache + * const parameter = await parametersProvider.get('/my-parameter', { forceFetch: true }); + * // Retrieve multiple parameters and skip cache + * const parameters = await parametersProvider.getMultiple('/my-parameters-path', { forceFetch: true }); + * }; + * ``` + * + * Likewise, you can use the `forceFetch` parameter with the `getParametersByName` method both for individual parameters and for all parameters. + * + * ### Decryption + * + * If you want to retrieve a parameter that is encrypted, you can use the `decrypt` parameter. This parameter is compatible with `get`, `getMultiple` and `getParametersByName`. + * + * @example + * ```typescript + * import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm'; + * + * const parametersProvider = new SSMProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve a parameter and decrypt it + * const parameter = await parametersProvider.get('/my-parameter', { decrypt: true }); + * // Retrieve multiple parameters and decrypt them + * const parameters = await parametersProvider.getMultiple('/my-parameters-path', { decrypt: true }); + * }; + * ``` + * + * ### Transformations + * + * For parameters stored as JSON you can use the transform argument for deserialization. This will return a JavaScript object instead of a string. + * + * @example + * ```typescript + * import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm'; + * + * const parametersProvider = new SSMProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve a parameter and parse it as JSON + * const parameter = await parametersProvider.get('/my-parameter', { transform: 'json' }); + * // Retrieve multiple parameters and parse them as JSON + * const parameters = await parametersProvider.getMultiple('/my-parameters-path', { transform: 'json' }); + * }; + * ``` + * + * For parameters that are instead stored as base64-encoded binary data, you can use the transform argument set to `binary` for decoding. This will return a decoded string. + * + * @example + * ```typescript + * import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm'; + * + * const parametersProvider = new SSMProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve a base64-encoded string and decode it + * const parameter = await parametersProvider.get('/my-parameter', { transform: 'binary' }); + * // Retrieve multiple base64-encoded strings and decode them + * const parameters = await parametersProvider.getMultiple('/my-parameters-path', { transform: 'binary' }); + * }; + * ``` + * + * Both type of transformations are compatible also with the `getParametersByName` method. + * + * ### Extra SDK options + * + * When retrieving parameters, you can pass extra options to the AWS SDK v3 for JavaScript client by using the `sdkOptions` parameter. + * + * @example + * ```typescript + * import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm'; + * + * const parametersProvider = new SSMProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve a parameter and pass extra options to the AWS SDK v3 for JavaScript client + * const parameter = await parametersProvider.get('/my-parameter', { + * sdkOptions: { + * WithDecryption: true, + * }, + * }); + * }; + * ``` + * + * The objects accept the same options as respectively the [AWS SDK v3 for JavaScript GetParameter command](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-ssm/classes/getparametercommand.html) and the [AWS SDK v3 for JavaScript GetParametersByPath command](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-ssm/classes/getparametersbypathcommand.html). + * + * ### Customize AWS SDK v3 for JavaScript client + * + * By default, the provider will create a new SSM client using the default configuration. + * + * You can customize the client by passing a custom configuration object to the provider. + * + * @example + * ```typescript + * import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm'; + * + * const parametersProvider = new SSMProvider({ + * clientConfig: { region: 'eu-west-1' }, + * }); + * ``` + * + * This object accepts the same options as the [AWS SDK v3 for JavaScript SSM client constructor](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-ssm/classes/ssmclient.html#constructor). + * + * Otherwise, if you want to use a custom client altogether, you can pass it to the provider. + * + * @example + * ```typescript + * import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm'; + * import { SSMClient } from '@aws-sdk/client-ssm'; + * + * const client = new SSMClient({ region: 'eu-west-1' }); + * const parametersProvider = new SSMProvider({ + * awsSdkV3Client: client, + * }); + * ``` + * + * This object must be an instance of the [AWS SDK v3 for JavaScript SSM client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-ssm/classes/ssmclient.html). + * + * For more usage examples, see [our documentation](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/). + */ class SSMProvider extends BaseProvider { public client: SSMClient; protected errorsKey = '_errors'; protected maxGetParametersItems = 10; + /** + * It initializes the SSMProvider class. + * + * @param {SSMProviderOptions} config - The configuration object. + */ public constructor(config?: SSMProviderOptions) { super(); @@ -44,6 +287,34 @@ class SSMProvider extends BaseProvider { } } + /** + * Retrieve a value from AWS Systems Manager. + * + * @example + * ```typescript + * import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm'; + * + * const parametersProvider = new SSMProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve a parameter from SSM + * const parameter = await parametersProvider.get('/my-parameter'); + * }; + * ``` + * + * You can customize the retrieval of the value by passing options to the function: + * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) + * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache + * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` + * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client + * * `decrypt` - Whether to decrypt the value before returning it. + * + * For usage examples check {@link SSMProvider}. + * + * @param {string} name - The name of the value to retrieve (i.e. the partition key) + * @param {SSMGetOptionsInterface} options - Options to configure the provider + * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ + */ public async get( name: string, options?: SSMGetOptionsInterface | undefined @@ -51,6 +322,36 @@ class SSMProvider extends BaseProvider { return super.get(name, options) as Promise | undefined>; } + /** + * Retrieve multiple values from AWS Systems Manager. + * + * @example + * ```typescript + * import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm'; + * + * const parametersProvider = new SSMProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve multiple parameters from SSM + * const parameters = await parametersProvider.getMultiple('/my-parameters-path'); + * }; + * ``` + * + * You can customize the retrieval of the values by passing options to the function: + * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) + * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache + * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` + * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client + * * `throwOnTransformError` - Whether to throw an error if the transform fails (default: `true`) + * * `decrypt` - Whether to decrypt the value before returning it. + * * `recursive` - Whether to recursively retrieve all parameters under the given path (default: `false`) + * + * For usage examples check {@link SSMProvider}. + * + * @param {string} path - The path of the parameters to retrieve + * @param {SSMGetMultipleOptionsInterface} options - Options to configure the retrieval + * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ + */ public async getMultiple( path: string, options?: SSMGetMultipleOptionsInterface | undefined @@ -59,14 +360,36 @@ class SSMProvider extends BaseProvider { } /** - * Retrieve multiple parameter values by name from SSM or cache. - * - * `ThrowOnError` decides whether to throw an error if a parameter is not found: + * Retrieve multiple parameters by name from AWS Systems Manager. + * + * @example + * ```typescript + * import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm'; + * + * const parametersProvider = new SSMProvider(); + * + * export const handler = async (): Promise => { + * // Retrieve multiple parameters by name from SSM + * const parameters = await parametersProvider.getParametersByName({ + * '/my-parameter-1': {}, // Use default options + * '/my-parameter-2': { transform: 'json' }, // Parse the value as JSON + * }); + * }; + * ``` + * You can customize the retrieval of the values by passing options to **both the function and the parameter**: + * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) + * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache + * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` + * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client + * * `throwOnTransformError` - Whether to throw an error if the transform fails (default: `true`) + * * `decrypt` - Whether to decrypt the value before returning it + * + * `throwOnError` decides whether to throw an error if a parameter is not found: * - A) Default fail-fast behavior: Throws a `GetParameterError` error upon any failure. * - B) Gracefully aggregate all parameters that failed under "_errors" key. - * + * * It transparently uses GetParameter and/or GetParameters depending on decryption requirements. - * + * * ```sh * β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” * β”Œβ”€β”€β”€β–Ά Decrypt entire batch │─────┐ @@ -81,9 +404,10 @@ class SSMProvider extends BaseProvider { * β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ GetParameters API β”‚ * β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ * ``` - * - * @param {Record[]} parameters - List of parameter names, and any optional overrides - * + * + * @param {Record} parameters - Object containing parameter names and any optional overrides + * @param {SSMGetParametersByNameOptionsInterface} options - Options to configure the retrieval + * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ */ public async getParametersByName( parameters: Record, @@ -117,7 +441,7 @@ class SSMProvider extends BaseProvider { response: batchResponse, errors: batchErrors } = await this.getParametersBatchByName(parametersToFetchInBatch, configs.throwOnError, false); - + response = { ...decryptResponse, ...batchResponse }; // Fail-fast disabled, let's aggregate errors under "_errors" key so they can handle gracefully if (!configs.throwOnError) { @@ -128,7 +452,7 @@ class SSMProvider extends BaseProvider { response: batchResponse, errors: batchErrors } = await this.getParametersBatchByName(parametersToDecrypt, configs.throwOnError, true); - + response = batchResponse; // Fail-fast disabled, let's aggregate errors under "_errors" key so they can handle gracefully if (!configs.throwOnError) { @@ -139,6 +463,12 @@ class SSMProvider extends BaseProvider { return response; } + /** + * Retrieve a parameter from AWS Systems Manager. + * + * @param {string} name - Name of the parameter to retrieve + * @param {SSMGetOptionsInterface} options - Options to customize the retrieval + */ protected async _get( name: string, options?: SSMGetOptionsInterface @@ -154,6 +484,12 @@ class SSMProvider extends BaseProvider { return result.Parameter?.Value; } + /** + * Retrieve multiple items from AWS Systems Manager. + * + * @param {string} path - The path of the parameters to retrieve + * @param {SSMGetMultipleOptionsInterface} options - Options to configure the provider + */ protected async _getMultiple( path: string, options?: SSMGetMultipleOptionsInterface @@ -171,7 +507,7 @@ class SSMProvider extends BaseProvider { options.recursive : sdkOptions.Recursive; paginationOptions.pageSize = sdkOptions.MaxResults !== undefined ? sdkOptions.MaxResults : undefined; - + const parameters: Record = {}; for await (const page of paginateGetParametersByPath(paginationOptions, sdkOptions)) { for (const parameter of page.Parameters || []) { @@ -179,9 +515,9 @@ class SSMProvider extends BaseProvider { * Standardize the parameter name * * The parameter name returned by SSM will contain the full path. - * However, for readability, we should return only the part after the path. - **/ - + * However, for readability, we should return only the part after the path. + **/ + // If the parameter is present in the response, then it has a Name // eslint-disable-next-line @typescript-eslint/no-non-null-assertion let name = parameter.Name!; @@ -196,6 +532,13 @@ class SSMProvider extends BaseProvider { return parameters; } + /** + * Retrieve multiple items by name from AWS Systems Manager. + * + * @param {Record} parameters - An object of parameter names and their options + * @param {throwOnError} throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully + * @param {boolean} decrypt - Whether to decrypt the parameters or not + */ protected async _getParametersByName( parameters: Record, throwOnError: boolean, @@ -207,7 +550,7 @@ class SSMProvider extends BaseProvider { if (decrypt) { sdkOptions.WithDecryption = true; } - + const result = await this.client.send(new GetParametersCommand(sdkOptions)); const errors = SSMProvider.handleAnyInvalidGetParameterErrors(result, throwOnError); const response = this.transformAndCacheGetParametersResponse( @@ -224,6 +567,10 @@ class SSMProvider extends BaseProvider { /** * Slice batch and fetch parameters using GetPrameters API by max permissible batch size + * + * @param {Record} parameters - An object of parameter names and their options + * @param {throwOnError} throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully + * @param {boolean} decrypt - Whether to decrypt the parameters or not */ protected async getParametersBatchByName( parameters: Record, @@ -260,6 +607,8 @@ class SSMProvider extends BaseProvider { /** * Fetch each parameter from batch that hasn't expired from cache + * + * @param {Record} parameters - An object of parameter names and their options */ protected async getParametersByNameFromCache( parameters: Record @@ -284,6 +633,13 @@ class SSMProvider extends BaseProvider { }; } + /** + * Slice object into chunks of max permissible batch size and fetch parameters + * + * @param {Record} parameters - An object of parameter names and their options + * @param {boolean} throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully + * @param {boolean} decrypt - Whether to decrypt the parameters or not + */ protected async getParametersByNameInChunks( parameters: Record, throwOnError: boolean, @@ -291,7 +647,7 @@ class SSMProvider extends BaseProvider { ): Promise { let response: Record = {}; let errors: string[] = []; - + // Slice object into chunks of max permissible batch size const chunks = Object.entries(parameters).reduce(( acc, @@ -313,7 +669,7 @@ class SSMProvider extends BaseProvider { response: chunkResponse, errors: chunkErrors } = await this._getParametersByName(chunk, throwOnError, decrypt); - + response = { ...response, ...chunkResponse }; errors = [ ...errors, ...chunkErrors ]; } @@ -324,6 +680,12 @@ class SSMProvider extends BaseProvider { }; } + /** + * Fetch parameters by name while also decrypting them + * + * @param {Record} parameters - An object of parameter names and their options + * @param {boolean} throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully + */ protected async getParametersByNameWithDecryptOption( parameters: Record, throwOnError: boolean @@ -351,6 +713,9 @@ class SSMProvider extends BaseProvider { /** * Handle any invalid parameters returned by GetParameters API * GetParameters is non-atomic. Failures don't always reflect in exceptions so we need to collect. + * + * @param {GetParametersCommandOutput} result - The result of the GetParameters API call + * @param {boolean} throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully */ protected static handleAnyInvalidGetParameterErrors( result: GetParametersCommandOutput, @@ -371,6 +736,9 @@ class SSMProvider extends BaseProvider { /** * Split parameters that can be fetched by GetParameters vs GetParameter. + * + * @param {Record} parameters - An object of parameter names and their options + * @param {SSMGetParametersByNameOptionsInterface} configs - The configs passed down */ protected static splitBatchAndDecryptParameters( parameters: Record, @@ -387,7 +755,7 @@ class SSMProvider extends BaseProvider { overrides.decrypt : configs.decrypt; overrides.maxAge = overrides.maxAge !== undefined ? overrides.maxAge : configs.maxAge; - + if (overrides.decrypt) { parametersToDecrypt[parameterName] = overrides; } else { @@ -403,10 +771,10 @@ class SSMProvider extends BaseProvider { /** * Throw a GetParameterError if fail-fast is disabled and `_errors` key is in parameters list. - * - * @param {Record} parameters - * @param {string} reservedParameter - * @param {boolean} throwOnError + * + * @param {Record} parameters + * @param {string} reservedParameter + * @param {boolean} throwOnError */ protected static throwIfErrorsKeyIsPresent( parameters: Record, @@ -420,6 +788,13 @@ class SSMProvider extends BaseProvider { } } + /** + * Transform and cache the response from GetParameters API call + * + * @param {GetParametersCommandOutput} response - The response from the GetParameters API call + * @param {Record} parameters - An object of parameter names and their options + * @param {boolean} throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully + */ protected transformAndCacheGetParametersResponse( response: GetParametersCommandOutput, parameters: Record, diff --git a/packages/parameters/src/ssm/getParameter.ts b/packages/parameters/src/ssm/getParameter.ts index fd4d8e8e0e..12b09e0c08 100644 --- a/packages/parameters/src/ssm/getParameter.ts +++ b/packages/parameters/src/ssm/getParameter.ts @@ -1,6 +1,141 @@ import { SSMProvider, DEFAULT_PROVIDERS } from './SSMProvider'; import type { SSMGetOptionsInterface } from '../types/SSMProvider'; +/** + * ## Intro + * The Parameters utility provides an SSMProvider that allows to retrieve parameters from AWS Systems Manager. + * + * ## Getting started + * + * This utility supports AWS SDK v3 for JavaScript only. This allows the utility to be modular, and you to install only + * the SDK packages you need and keep your bundle size small. + * + * To use the provider, you must install the Parameters utility and the AWS SDK v3 for JavaScript for AppConfig: + * + * ```sh + * npm install @aws-lambda-powertools/parameters @aws-sdk/client-ssm + * ``` + * + * ## Basic usage + * + * @example + * ```typescript + * import { getParameter } from '@aws-lambda-powertools/parameters/ssm'; + * + * export const handler = async (): Promise => { + * // Retrieve a parameter + * const parameter = await getParameter('/my-parameter'); + * }; + * ``` + * + * ## Advanced usage + * + * ### Decryption + * + * If you have encrypted parameters, you can use the `decrypt` option to automatically decrypt them. + * + * @example + * ```typescript + * import { getParameter } from '@aws-lambda-powertools/parameters/ssm'; + * + * export const handler = async (): Promise => { + * // Retrieve a parameter and decrypt it + * const parameter = await getParameter('/my-parameter', { decrypt: true }); + * }; + * ``` + * + * ### Caching + * + * By default, the provider will cache parameters retrieved in-memory for 5 seconds. + * You can adjust how long values should be kept in cache by using the `maxAge` parameter. + * + * @example + * ```typescript + * import { getParameter } from '@aws-lambda-powertools/parameters/ssm'; + * + * export const handler = async (): Promise => { + * // Retrieve a parameter and cache it for 10 seconds + * const parameter = await getParameter('/my-parameter', { maxAge: 10 }); + * }; + * ``` + * + * If instead you'd like to always ensure you fetch the latest parameter from the store regardless if already available in cache, use the `forceFetch` parameter. + * + * @example + * ```typescript + * import { getParameter } from '@aws-lambda-powertools/parameters/ssm'; + * + * export const handler = async (): Promise => { + * // Retrieve a parameter and always fetch the latest value + * const parameter = await getParameter('/my-parameter', { forceFetch: true }); + * }; + * ``` + * + * ### Transformations + * + * For parameters stored as JSON you can use the transform argument for deserialization. This will return a JavaScript object instead of a string. + * + * @example + * ```typescript + * import { getParameter } from '@aws-lambda-powertools/parameters/ssm'; + * + * export const handler = async (): Promise => { + * // Retrieve a parameter and parse it as JSON + * const parameter = await getParameter('/my-parameter', { transform: 'json' }); + * }; + * ``` + * + * For parameters that are instead stored as base64-encoded binary data, you can use the transform argument set to `binary` for decoding. This will return a decoded string. + * + * @example + * ```typescript + * import { getParameter } from '@aws-lambda-powertools/parameters/ssm'; + * + * export const handler = async (): Promise => { + * // Retrieve a base64-encoded string and decode it + * const parameter = await getParameter('/my-parameter', { transform: 'binary' }); + * }; + * ``` + * + * ### Extra SDK options + * + * When retrieving a parameter, you can pass extra options to the AWS SDK v3 for JavaScript client by using the `sdkOptions` parameter. + * + * @example + * ```typescript + * import { getParameter } from '@aws-lambda-powertools/parameters/ssm'; + * + * export const handler = async (): Promise => { + * // Retrieve a parameter and pass extra options to the AWS SDK v3 for JavaScript client + * const parameter = await getParameter('/my-parameter', { + * sdkOptions: { + * WithDecryption: true, + * }, + * }); + * }; + * ``` + * + * This object accepts the same options as the [AWS SDK v3 for JavaScript SSM GetParameter command](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-ssm/interfaces/getparametercommandinput.html). + * + * ### Built-in provider class + * + * For greater flexibility such as configuring the underlying SDK client used by built-in providers, you can use the {@link SSMProvider} class. + * + * ### Options + * + * * You can customize the retrieval of the value by passing options to the function: + * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) + * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache + * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` + * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client + * * `decrypt` - Whether to decrypt the value before returning it. + * + * For more usage examples, see [our documentation](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/). + * + * @param {string} name - The name of the parameter to retrieve + * @param {SSMGetOptionsInterface} options - Options to configure the provider + * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ + */ const getParameter = ( name: string, options?: SSMGetOptionsInterface diff --git a/packages/parameters/src/ssm/getParameters.ts b/packages/parameters/src/ssm/getParameters.ts index 0283bc0b5e..e93daf1ea8 100644 --- a/packages/parameters/src/ssm/getParameters.ts +++ b/packages/parameters/src/ssm/getParameters.ts @@ -1,6 +1,142 @@ import { SSMProvider, DEFAULT_PROVIDERS } from './SSMProvider'; import type { SSMGetMultipleOptionsInterface } from '../types/SSMProvider'; +/** + * ## Intro + * The Parameters utility provides an SSMProvider that allows to retrieve parameters from AWS Systems Manager. + * + * ## Getting started + * + * This utility supports AWS SDK v3 for JavaScript only. This allows the utility to be modular, and you to install only + * the SDK packages you need and keep your bundle size small. + * + * To use the provider, you must install the Parameters utility and the AWS SDK v3 for JavaScript for AppConfig: + * + * ```sh + * npm install @aws-lambda-powertools/parameters @aws-sdk/client-ssm + * ``` + * + * ## Basic usage + * + * @example + * ```typescript + * import { getParameters } from '@aws-lambda-powertools/parameters/ssm'; + * + * export const handler = async (): Promise => { + * // Retrieve parameters by path + * const parameters = await getParameters('/my-parameters-path'); + * }; + * ``` + * + * ## Advanced usage + * + * ### Decryption + * + * If you have encrypted parameters, you can use the `decrypt` option to automatically decrypt them. + * + * @example + * ```typescript + * import { getParameters } from '@aws-lambda-powertools/parameters/ssm'; + * + * export const handler = async (): Promise => { + * // Retrieve parameters and decrypt them + * const parameters = await getParameters('/my-parameters-path', { decrypt: true }); + * }; + * ``` + * + * ### Caching + * + * By default, the provider will cache parameters retrieved in-memory for 5 seconds. + * You can adjust how long values should be kept in cache by using the `maxAge` parameter. + * + * @example + * ```typescript + * import { getParameters } from '@aws-lambda-powertools/parameters/ssm'; + * + * export const handler = async (): Promise => { + * // Retrieve parameters and cache them for 10 seconds + * const parameters = await getParameters('/my-parameters-path', { maxAge: 10 }); + * }; + * ``` + * + * If instead you'd like to always ensure you fetch the latest values from the store regardless if already available in cache, use the `forceFetch` parameter. + * + * @example + * ```typescript + * import { getParameters } from '@aws-lambda-powertools/parameters/ssm'; + * + * export const handler = async (): Promise => { + * // Retrieve parameters and always fetch the latest values + * const parameters = await getParameters('/my-parameters-path', { forceFetch: true }); + * }; + * ``` + * + * ### Transformations + * + * For parameters stored as JSON you can use the transform argument for deserialization. This will return a JavaScript objects instead of a strings. + * + * @example + * ```typescript + * import { getParameters } from '@aws-lambda-powertools/parameters/ssm'; + * + * export const handler = async (): Promise => { + * // Retrieve parameters and parse them as JSON + * const parameters = await getParameters('/my-parameters-path', { transform: 'json' }); + * }; + * ``` + * + * For parameters that are instead stored as base64-encoded binary data, you can use the transform argument set to `binary` for decoding. This will return decoded strings for each parameter. + * + * @example + * ```typescript + * import { getParameters } from '@aws-lambda-powertools/parameters/ssm'; + * + * export const handler = async (): Promise => { + * // Retrieve base64-encoded strings and decode them + * const parameters = await getParameters('/my-parameters-path', { transform: 'binary' }); + * }; + * ``` + * + * ### Extra SDK options + * + * When retrieving a parameter, you can pass extra options to the AWS SDK v3 for JavaScript client by using the `sdkOptions` parameter. + * + * @example + * ```typescript + * import { getParameters } from '@aws-lambda-powertools/parameters/ssm'; + * + * export const handler = async (): Promise => { + * // Retrieve parameters and pass extra options to the AWS SDK v3 for JavaScript client + * const parameters = await getParameters('/my-parameters-path', { + * sdkOptions: { + * WithDecryption: true, + * }, + * }); + * }; + * ``` + * + * This object accepts the same options as the [AWS SDK v3 for JavaScript SSM getParametersByPath command](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-ssm/interfaces/getParameterssbypathcommandinput.html). + * + * ### Built-in provider class + * + * For greater flexibility such as configuring the underlying SDK client used by built-in providers, you can use the {@link SSMProvider} class. + * + * ### Options + * + * * You can customize the retrieval of the value by passing options to the function: + * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) + * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache + * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` + * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client + * * `decrypt` - Whether to decrypt the value before returning it. + * * `recursive` - Whether to recursively retrieve all parameters within the path. + * + * For more usage examples, see [our documentation](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/). + * + * @param {string} path - The path of the parameters to retrieve + * @param {SSMGetMultipleOptionsInterface} options - Options to configure the provider + * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ + */ const getParameters = ( path: string, options?: SSMGetMultipleOptionsInterface diff --git a/packages/parameters/src/ssm/getParametersByName.ts b/packages/parameters/src/ssm/getParametersByName.ts index 18d50073b8..5efd333f5f 100644 --- a/packages/parameters/src/ssm/getParametersByName.ts +++ b/packages/parameters/src/ssm/getParametersByName.ts @@ -3,6 +3,163 @@ import type { SSMGetParametersByNameOptionsInterface } from '../types/SSMProvider'; +/** + * ## Intro + * The Parameters utility provides an SSMProvider that allows to retrieve parameters from AWS Systems Manager. + * + * ## Getting started + * + * This utility supports AWS SDK v3 for JavaScript only. This allows the utility to be modular, and you to install only + * the SDK packages you need and keep your bundle size small. + * + * To use the provider, you must install the Parameters utility and the AWS SDK v3 for JavaScript for AppConfig: + * + * ```sh + * npm install @aws-lambda-powertools/parameters @aws-sdk/client-ssm + * ``` + * + * ## Basic usage + * + * @example + * ```typescript + * import { getParametersByName } from '@aws-lambda-powertools/parameters/ssm'; + * + * export const handler = async (): Promise => { + * // Retrieve parameters and cache them for 10 seconds + * const parameters = await getParametersByName({ + * '/my-parameter-1': {}, // Use default options + * '/my-parameter-2': { maxAge: 10 }, // Cache for 10 seconds + * }); + * }; + * ``` + * + * ## Advanced usage + * + * ### Decryption + * + * If you have encrypted parameters, you can use the `decrypt` option to automatically decrypt them. + * + * @example + * ```typescript + * import { getParametersByName } from '@aws-lambda-powertools/parameters/ssm'; + * + * export const handler = async (): Promise => { + * // Retrieve parameters and decrypt them + * const parameters = await getParametersByName({ + * '/my-parameter-1': {}, // Use default options + * '/my-parameter-2': {}, // Use default options + * }, { decrypt: true }); + * }; + * ``` + * + * ### Caching + * + * By default, the provider will cache parameters retrieved in-memory for 5 seconds. + * You can adjust how long values should be kept in cache by using the `maxAge` parameter. + * + * @example + * ```typescript + * import { getParametersByName } from '@aws-lambda-powertools/parameters/ssm'; + * + * export const handler = async (): Promise => { + * // Retrieve parameters and cache them for 10 seconds + * const parameters = await getParametersByName({ + * '/my-parameter-1': {}, // Use default options + * '/my-parameter-2': {}, // Use default options + * }, { maxAge: 10 }); + * }; + * ``` + * + * Alternatively, if you need more granular control over caching each parameter, you can pass it in the options object. + * + * @example + * ```typescript + * import { getParametersByName } from '@aws-lambda-powertools/parameters/ssm'; + * + * export const handler = async (): Promise => { + * // Retrieve parameters and cache them individually + * const parameters = await getParametersByName({ + * '/my-parameter-1': { maxAge: 10 }, // Cache for 10 seconds + * '/my-parameter-2': { maxAge: 20 }, // Cache for 20 seconds + * }); + * }; + * ``` + * + * If instead you'd like to always ensure you fetch the latest values from the store regardless if already available in cache, use the `forceFetch` parameter. + * + * @example + * ```typescript + * import { getParametersByName } from '@aws-lambda-powertools/parameters/ssm'; + * + * export const handler = async (): Promise => { + * // Retrieve parameters and pass extra options to skip cache + * const parameters = await getParametersByName({ + * '/my-parameter-1': {}, // Use default options + * '/my-parameter-2': {}, // Use default options + * }, { forceFetch: true }); + * }; + * ``` + * + * ### Transformations + * + * For parameters stored as JSON you can use the transform argument for deserialization. This will return a JavaScript objects instead of a strings. + * For parameters that are instead stored as base64-encoded binary data, you can use the transform argument set to `binary` for decoding. This will return decoded strings for each parameter. + * + * @example + * ```typescript + * import { getParametersByName } from '@aws-lambda-powertools/parameters/ssm'; + * + * export const handler = async (): Promise => { + * // Retrieve parameters and pass extra options to transform them + * const parameters = await getParametersByName({ + * '/my-parameter-1': {}, // Use default options (no transformation) + * '/my-parameter-2': { transform: 'json' }, // Parse the value as JSON + * '/my-parameter-3': { transform: 'binary' }, // Parse the value as base64-encoded binary data + * }); + * }; + * ``` + * + * + * ### Built-in provider class + * + * For greater flexibility such as configuring the underlying SDK client used by built-in providers, you can use the {@link SSMProvider} class. + * + * ### Options + * + * * You can customize the retrieval of the value by passing options to **both the function and the parameter**: + * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) + * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache + * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` + * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client + * * `decrypt` - Whether to decrypt the value before returning it + * + * `throwOnError` decides whether to throw an error if a parameter is not found: + * - A) Default fail-fast behavior: Throws a `GetParameterError` error upon any failure. + * - B) Gracefully aggregate all parameters that failed under "_errors" key. + * + * It transparently uses GetParameter and/or getParametersByName depending on decryption requirements. + * + * ```sh + * β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + * β”Œβ”€β”€β”€β–Ά Decrypt entire batch │─────┐ + * β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + * β”‚ β”œβ”€β”€β”€β”€β”€β–Ά getParametersByName API β”‚ + * β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + * β”‚ Split batch │─── ┼──▢│ No decryption required β”‚β”€β”€β”€β”€β”€β”˜ + * β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + * β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + * β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ GetParameter API β”‚ + * └──▢│Decrypt some but not all│───────────▢───────────────────── + * β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ getParametersByName API β”‚ + * β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + * ``` + * + * For more usage examples, see [our documentation](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/). + * + * @param {Record} parameters - The path of the parameters to retrieve + * @param {SSMGetParametersByNameOptionsInterface} options - Options to configure the provider + * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ + */ const getParametersByName = ( parameters: Record, options?: SSMGetParametersByNameOptionsInterface diff --git a/packages/parameters/src/types/AppConfigProvider.ts b/packages/parameters/src/types/AppConfigProvider.ts index 007bca8a50..e69fc256c0 100644 --- a/packages/parameters/src/types/AppConfigProvider.ts +++ b/packages/parameters/src/types/AppConfigProvider.ts @@ -8,9 +8,9 @@ import type { GetOptionsInterface } from 'types/BaseProvider'; /** * Base interface for AppConfigProviderOptions. * - * @interface - * @property {string} environment - The environment ID or the environment name. - * @property {string} [application] - The application ID or the application name. + * @interface + * @property {string} environment - The environment ID or the environment name. + * @property {string} [application] - The application ID or the application name. */ interface AppConfigProviderOptionsBaseInterface { environment: string @@ -20,10 +20,10 @@ interface AppConfigProviderOptionsBaseInterface { /** * Interface for AppConfigProviderOptions with clientConfig property. * - * @interface - * @extends AppConfigProviderOptionsBaseInterface - * @property {AppConfigDataClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. - * @property {never} [awsSdkV3Client] - This property should never be passed. + * @interface + * @extends AppConfigProviderOptionsBaseInterface + * @property {AppConfigDataClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. + * @property {never} [awsSdkV3Client] - This property should never be passed. */ interface AppConfigProviderOptionsWithClientConfig extends AppConfigProviderOptionsBaseInterface { clientConfig?: AppConfigDataClientConfig @@ -59,7 +59,10 @@ type AppConfigProviderOptions = AppConfigProviderOptionsWithClientConfig | AppCo * * @interface AppConfigGetOptionsInterface * @extends {GetOptionsInterface} - * @property {StartConfigurationSessionCommandInput} [sdkOptions] - Required options to start configuration session. + * @property {number} maxAge - Maximum age of the value in the cache, in seconds. + * @property {boolean} forceFetch - Force fetch the value from the parameter store, ignoring the cache. + * @property {StartConfigurationSessionCommandInput} [sdkOptions] - Additional options to pass to the AWS SDK v3 client. + * @property {TransformOptions} transform - Transform to be applied, can be 'json' or 'binary'. */ interface AppConfigGetOptionsInterface extends Omit { sdkOptions?: Omit< diff --git a/packages/parameters/src/types/BaseProvider.ts b/packages/parameters/src/types/BaseProvider.ts index 1a0ae3fa9a..c0e5a08c3d 100644 --- a/packages/parameters/src/types/BaseProvider.ts +++ b/packages/parameters/src/types/BaseProvider.ts @@ -1,21 +1,68 @@ +/** + * Type for the transform option. + */ type TransformOptions = 'auto' | 'binary' | 'json'; +/** + * Options for the `get` method. + * + * @property {number} maxAge - Maximum age of the value in the cache, in seconds. Will be applied after the first API call. + * @property {boolean} forceFetch - Force fetch the value from the parameter store, ignoring the cache. + * @property {unknown} sdkOptions - Options to pass to the underlying SDK. + * @property {TransformOptions} transform - Transform to be applied, can be 'json', 'binary', or 'auto'. + */ interface GetOptionsInterface { + /** + * Maximum age of the value in the cache, in seconds. + */ maxAge?: number + /** + * Force fetch the value from the parameter store, ignoring the cache. + */ forceFetch?: boolean + /** + * Options to pass to the underlying SDK. + */ sdkOptions?: unknown + /** + * Transform to be applied, can be 'json', 'binary', or 'auto'. + */ transform?: TransformOptions } +/** + * Options for the `getMultiple` method. + * + * @property {number} maxAge - Maximum age of the value in the cache, in seconds. Will be applied after the first API call. + * @property {boolean} forceFetch - Force fetch the value from the parameter store, ignoring the cache. + * @property {unknown} sdkOptions - Options to pass to the underlying SDK. + * @property {TransformOptions} transform - Transform to be applied, can be 'json', 'binary', or 'auto'. + * @property {boolean} throwOnTransformError - Whether to throw an error if a value cannot be transformed. + */ interface GetMultipleOptionsInterface extends GetOptionsInterface { + /** + * Whether to throw an error if a value cannot be transformed. + */ throwOnTransformError?: boolean } +/** + * Interface for a value that can expire. + */ interface ExpirableValueInterface { + /** + * Value of the parameter. + */ value: string | Uint8Array | Record + /** + * Expiration timestamp of the value. + */ ttl: number } +/** + * Interface for a parameter store provider. + */ interface BaseProviderInterface { get(name: string, options?: GetOptionsInterface): Promise> getMultiple(path: string, options?: GetMultipleOptionsInterface): Promise> diff --git a/packages/parameters/src/types/DynamoDBProvider.ts b/packages/parameters/src/types/DynamoDBProvider.ts index 140bf50fd2..075f123838 100644 --- a/packages/parameters/src/types/DynamoDBProvider.ts +++ b/packages/parameters/src/types/DynamoDBProvider.ts @@ -1,6 +1,15 @@ import type { GetOptionsInterface, GetMultipleOptionsInterface } from './BaseProvider'; import type { DynamoDBClient, GetItemCommandInput, QueryCommandInput, DynamoDBClientConfig } from '@aws-sdk/client-dynamodb'; +/** + * Base interface for DynamoDBProviderOptions. + * + * @interface + * @property {string} tableName - The DynamoDB table name. + * @property {string} [keyAttr] - The DynamoDB table key attribute name. Defaults to 'id'. + * @property {string} [sortAttr] - The DynamoDB table sort attribute name. Defaults to 'sk'. + * @property {string} [valueAttr] - The DynamoDB table value attribute name. Defaults to 'value'. + */ interface DynamoDBProviderOptionsBaseInterface { tableName: string keyAttr?: string @@ -8,30 +17,70 @@ interface DynamoDBProviderOptionsBaseInterface { valueAttr?: string } +/** + * Interface for DynamoDBProviderOptions with clientConfig property. + * + * @interface + * @extends DynamoDBProviderOptionsBaseInterface + * @property {AppConfigDataClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. + * @property {never} [awsSdkV3Client] - This property should never be passed. + */ interface DynamoDBProviderOptionsWithClientConfig extends DynamoDBProviderOptionsBaseInterface { clientConfig?: DynamoDBClientConfig awsSdkV3Client?: never } +/** + * Interface for DynamoDBProviderOptions with awsSdkV3Client property. + * + * @interface + * @extends DynamoDBProviderOptionsBaseInterface + * @property {AppConfigDataClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during AppConfigProvider class instantiation + * @property {never} [clientConfig] - This property should never be passed. + */ interface DynamoDBProviderOptionsWithClientInstance extends DynamoDBProviderOptionsBaseInterface { awsSdkV3Client?: DynamoDBClient clientConfig?: never } +/** + * Options for the AppConfigProvider class constructor. + * + * @type AppConfigProviderOptions + * @property {string} tableName - The DynamoDB table name. + * @property {string} [keyAttr] - The DynamoDB table key attribute name. Defaults to 'id'. + * @property {string} [sortAttr] - The DynamoDB table sort attribute name. Defaults to 'sk'. + * @property {string} [valueAttr] - The DynamoDB table value attribute name. Defaults to 'value'. + * @property {AppConfigDataClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. Mutually exclusive with awsSdkV3Client. + * @property {AppConfigDataClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during DynamoDBProvider class instantiation. Mutually exclusive with clientConfig. + */ type DynamoDBProviderOptions = DynamoDBProviderOptionsWithClientConfig | DynamoDBProviderOptionsWithClientInstance; /** * Options for the DynamoDBProvider get method. - * + * * @interface DynamoDBGetOptionsInterface * @extends {GetOptionsInterface} - * @property {boolean} decrypt - If true, the parameter will be decrypted. - * @property {Partial} sdkOptions - Options for the AWS SDK. + * @property {number} maxAge - Maximum age of the value in the cache, in seconds. + * @property {boolean} forceFetch - Force fetch the value from the parameter store, ignoring the cache. + * @property {GetItemCommandInput} [sdkOptions] - Additional options to pass to the AWS SDK v3 client. + * @property {TransformOptions} transform - Transform to be applied, can be 'json' or 'binary'. */ interface DynamoDBGetOptionsInterface extends GetOptionsInterface { sdkOptions?: Omit, 'Key' | 'TableName' | 'ProjectionExpression'> } +/** + * Options for the DynamoDBProvider getMultiple method. + * + * @interface DynamoDBGetMultipleOptionsInterface + * @extends {GetMultipleOptionsInterface} + * @property {number} maxAge - Maximum age of the value in the cache, in seconds. + * @property {boolean} forceFetch - Force fetch the value from the parameter store, ignoring the cache. + * @property {QueryCommandInput} [sdkOptions] - Additional options to pass to the AWS SDK v3 client. + * @property {TransformOptions} transform - Transform to be applied, can be 'json' or 'binary'. + * @property {boolean} throwOnTransformError - Whether to throw an error if the transform fails (default: `true`) + */ interface DynamoDBGetMultipleOptionsInterface extends GetMultipleOptionsInterface { sdkOptions?: Partial } diff --git a/packages/parameters/src/types/SSMProvider.ts b/packages/parameters/src/types/SSMProvider.ts index 9ce1f66e97..36d5f85cb3 100644 --- a/packages/parameters/src/types/SSMProvider.ts +++ b/packages/parameters/src/types/SSMProvider.ts @@ -10,31 +10,68 @@ import type { TransformOptions } from './BaseProvider'; +/** + * Interface for SSMProvider with clientConfig property. + * + * @interface + * @property {SSMClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. + * @property {never} [awsSdkV3Client] - This property should never be passed. + */ interface SSMProviderOptionsWithClientConfig { clientConfig?: SSMClientConfig awsSdkV3Client?: never } +/** + * Interface for SSMProvider with awsSdkV3Client property. + * + * @interface + * @property {SSMClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during SSMProvider class instantiation + * @property {never} [clientConfig] - This property should never be passed. + */ interface SSMProviderOptionsWithClientInstance { awsSdkV3Client?: SSMClient clientConfig?: never } +/** + * Options for the SSMProvider class constructor. + * + * @type SSMProviderOptions + * @property {SSMClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. Mutually exclusive with awsSdkV3Client. + * @property {SSMClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during DynamoDBProvider class instantiation. Mutually exclusive with clientConfig. + */ type SSMProviderOptions = SSMProviderOptionsWithClientConfig | SSMProviderOptionsWithClientInstance; /** - * Options for the SSMProvider get method. - * + * Options for the SSMProvider getMultiple method. + * * @interface SSMGetOptionsInterface * @extends {GetOptionsInterface} + * @property {number} maxAge - Maximum age of the value in the cache, in seconds. + * @property {boolean} forceFetch - Force fetch the value from the parameter store, ignoring the cache. + * @property {GetItemCommandInput} [sdkOptions] - Additional options to pass to the AWS SDK v3 client. + * @property {TransformOptions} transform - Transform to be applied, can be 'json' or 'binary'. * @property {boolean} decrypt - If true, the parameter will be decrypted. - * @property {Partial} sdkOptions - Options for the AWS SDK. */ interface SSMGetOptionsInterface extends GetOptionsInterface { decrypt?: boolean sdkOptions?: Partial } +/** + * Options for the SSMProvider getMultiple method. + * + * @interface SSMGetMultipleOptionsInterface + * @extends {GetMultipleOptionsInterface} + * @property {number} maxAge - Maximum age of the value in the cache, in seconds. + * @property {boolean} forceFetch - Force fetch the value from the parameter store, ignoring the cache. + * @property {GetItemCommandInput} [sdkOptions] - Additional options to pass to the AWS SDK v3 client. + * @property {TransformOptions} transform - Transform to be applied, can be 'json' or 'binary'. + * @property {boolean} decrypt - If true, the parameter will be decrypted. + * @property {boolean} recursive - If true, the parameter will be fetched recursively. + * @property {boolean} throwOnTransformError - If true, the method will throw an error if the transform fails. + */ interface SSMGetMultipleOptionsInterface extends GetMultipleOptionsInterface { sdkOptions?: Partial decrypt?: boolean @@ -42,6 +79,15 @@ interface SSMGetMultipleOptionsInterface extends GetMultipleOptionsInterface { throwOnTransformError?: boolean } +/** + * Options for the SSMProvider getParametersByName method. + * + * @interface SSMGetParametersByNameOptionsInterface + * @property {number} maxAge - Maximum age of the value in the cache, in seconds. + * @property {TransformOptions} transform - Transform to be applied, can be 'json' or 'binary'. + * @property {boolean} decrypt - If true, the parameter will be decrypted. + * @property {boolean} throwOnError - If true, the method will throw an error if one of the parameters cannot be fetched. Otherwise it will aggregate the errors under an _errors key in the response. + */ interface SSMGetParametersByNameOptionsInterface { maxAge?: number throwOnError?: boolean @@ -49,16 +95,25 @@ interface SSMGetParametersByNameOptionsInterface { transform?: TransformOptions } +/** + * Output type for the SSMProvider splitBatchAndDecryptParameters method. + */ type SSMSplitBatchAndDecryptParametersOutputType = { parametersToFetchInBatch: Record parametersToDecrypt: Record }; +/** + * Output type for the SSMProvider getParametersByName method. + */ interface SSMGetParametersByNameOutputInterface { response: Record errors: string[] } +/** + * Output type for the SSMProvider getParametersByNameFromCache method. + */ type SSMGetParametersByNameFromCacheOutputType = { cached: Record> toFetch: Record diff --git a/packages/parameters/src/types/SecretsProvider.ts b/packages/parameters/src/types/SecretsProvider.ts index 757f38b18b..152dff709b 100644 --- a/packages/parameters/src/types/SecretsProvider.ts +++ b/packages/parameters/src/types/SecretsProvider.ts @@ -1,18 +1,50 @@ import type { GetOptionsInterface } from './BaseProvider'; import type { SecretsManagerClient, SecretsManagerClientConfig, GetSecretValueCommandInput } from '@aws-sdk/client-secrets-manager'; +/** + * Base interface for SecretsProviderOptions. + * + * @interface + * @property {SecretsManagerClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. + * @property {never} [awsSdkV3Client] - This property should never be passed. + */ interface SecretsProviderOptionsWithClientConfig { clientConfig?: SecretsManagerClientConfig awsSdkV3Client?: never } +/** + * Interface for SecretsProviderOptions with awsSdkV3Client property. + * + * @interface + * @extends SecretsProviderOptionsWithClientConfig + * @property {SecretsManagerClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during SecretsProvider class instantiation + * @property {never} [clientConfig] - This property should never be passed. + */ interface SecretsProviderOptionsWithClientInstance { awsSdkV3Client?: SecretsManagerClient clientConfig?: never } +/** + * Options for the SecretsProvider class constructor. + * + * @type SecretsProviderOptions + * @property {AppConfigDataClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. Mutually exclusive with awsSdkV3Client. + * @property {AppConfigDataClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during SecretsProvider class instantiation. Mutually exclusive with clientConfig. + */ type SecretsProviderOptions = SecretsProviderOptionsWithClientConfig | SecretsProviderOptionsWithClientInstance; +/** + * Options to configure the retrieval of a secret. + * + * @interface SecretsGetOptionsInterface + * @extends {GetOptionsInterface} + * @property {number} maxAge - Maximum age of the value in the cache, in seconds. + * @property {boolean} forceFetch - Force fetch the value from the parameter store, ignoring the cache. + * @property {GetSecretValueCommandInput} sdkOptions - Options to pass to the underlying SDK. + * @property {TransformOptions} transform - Transform to be applied, can be 'json' or 'binary'. + */ interface SecretsGetOptionsInterface extends GetOptionsInterface { sdkOptions?: Omit, 'SecretId'> } diff --git a/packages/parameters/tests/unit/AppConfigProvider.test.ts b/packages/parameters/tests/unit/AppConfigProvider.test.ts index 5bc16e3191..f5924dd191 100644 --- a/packages/parameters/tests/unit/AppConfigProvider.test.ts +++ b/packages/parameters/tests/unit/AppConfigProvider.test.ts @@ -284,7 +284,7 @@ describe('Class: AppConfigProvider', () => { }; const path = '/my/path'; const provider = new AppConfigProvider(config); - const errorMessage = 'Not Implemented'; + const errorMessage = 'Method not implemented.'; // Act & Assess await expect(provider.getMultiple(path)).rejects.toThrow(errorMessage); diff --git a/typedoc.js b/typedoc.js index de645c6431..d3e9d58c9d 100644 --- a/typedoc.js +++ b/typedoc.js @@ -3,6 +3,7 @@ module.exports = { exclude: [ '**/node_modules/**', '**/*.test.ts', '**/*.json' ], name: 'aws-lambda-powertools-typescript', excludePrivate: true, + excludeInternal: true, entryPointStrategy: 'packages', readme: './README.md', }; \ No newline at end of file From 79a321b199ef51a024dc25b83673baf2eb03de69 Mon Sep 17 00:00:00 2001 From: Niko Achilles Kokkinos Date: Thu, 16 Mar 2023 21:14:19 +0200 Subject: [PATCH 19/26] feat(metrics): support high resolution metrics (#1369) --- docs/core/metrics.md | 21 ++++- .../metrics/addHighResolutionMetric.ts | 7 ++ docs/snippets/metrics/basicUsage.ts | 2 +- packages/metrics/src/Metrics.ts | 81 +++++++++++++++---- packages/metrics/src/MetricsInterface.ts | 12 ++- .../metrics/src/types/MetricResolution.ts | 8 ++ packages/metrics/src/types/Metrics.ts | 14 +++- packages/metrics/src/types/index.ts | 3 +- packages/metrics/tests/unit/Metrics.test.ts | 58 ++++++++++++- .../tests/unit/middleware/middy.test.ts | 81 ++++++++++++++++++- 10 files changed, 261 insertions(+), 26 deletions(-) create mode 100644 docs/snippets/metrics/addHighResolutionMetric.ts create mode 100644 packages/metrics/src/types/MetricResolution.ts diff --git a/docs/core/metrics.md b/docs/core/metrics.md index 12f74854bd..8cd17c0a36 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -27,6 +27,9 @@ If you're new to Amazon CloudWatch, there are two terminologies you must be awar * **Namespace**. It's the highest level container that will group multiple metrics from multiple services for a given application, for example `ServerlessEcommerce`. * **Dimensions**. Metrics metadata in key-value format. They help you slice and dice metrics visualization, for example `ColdStart` metric by Payment `service`. +* **Metric**. It's the name of the metric, for example: SuccessfulBooking or UpdatedBooking. +* **Unit**. It's a value representing the unit of measure for the corresponding metric, for example: Count or Seconds. +* **Resolution**. It's a value representing the storage resolution for the corresponding metric. Metrics can be either Standard or High resolution. Read more [here](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#Resolution_definition).
@@ -117,7 +120,23 @@ You can create metrics using the `addMetric` method, and you can create dimensio CloudWatch EMF supports a max of 100 metrics per batch. Metrics will automatically propagate all the metrics when adding the 100th metric. Subsequent metrics, e.g. 101th, will be aggregated into a new EMF object, for your convenience. !!! warning "Do not create metrics or dimensions outside the handler" - Metrics or dimensions added in the global scope will only be added during cold start. Disregard if that's the intended behaviour. + Metrics or dimensions added in the global scope will only be added during cold start. Disregard if that's the intended behavior. + +### Adding high-resolution metrics + +You can create [high-resolution metrics](https://aws.amazon.com/about-aws/whats-new/2023/02/amazon-cloudwatch-high-resolution-metric-extraction-structured-logs/) passing `resolution` as parameter to `addMetric`. + +!!! tip "When is it useful?" + High-resolution metrics are data with a granularity of one second and are very useful in several situations such as telemetry, time series, real-time incident management, and others. + +=== "Metrics with high resolution" + + ```typescript hl_lines="6" + --8<-- "docs/snippets/metrics/addHighResolutionMetric.ts" + ``` + +!!! tip "Autocomplete Metric Resolutions" + Use the `MetricResolution` type to easily find a supported metric resolution by CloudWatch. Alternatively, you can pass the allowed values of 1 or 60 as an integer. ### Adding multi-value metrics diff --git a/docs/snippets/metrics/addHighResolutionMetric.ts b/docs/snippets/metrics/addHighResolutionMetric.ts new file mode 100644 index 0000000000..0e44bcbcbb --- /dev/null +++ b/docs/snippets/metrics/addHighResolutionMetric.ts @@ -0,0 +1,7 @@ +import { Metrics, MetricUnits, MetricResolution } from '@aws-lambda-powertools/metrics'; + +const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); + +export const handler = async (_event: unknown, _context: unknown): Promise => { + metrics.addMetric('successfulBooking', MetricUnits.Count, 1, MetricResolution.High); +}; diff --git a/docs/snippets/metrics/basicUsage.ts b/docs/snippets/metrics/basicUsage.ts index 15388d2c82..ccd8606a01 100644 --- a/docs/snippets/metrics/basicUsage.ts +++ b/docs/snippets/metrics/basicUsage.ts @@ -2,6 +2,6 @@ import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics'; const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); -export const handler = async (_event, _context): Promise => { +export const handler = async (_event: unknown, _context: unknown): Promise => { metrics.addMetric('successfulBooking', MetricUnits.Count, 1); }; \ No newline at end of file diff --git a/packages/metrics/src/Metrics.ts b/packages/metrics/src/Metrics.ts index dbde8e0438..12bfc85451 100644 --- a/packages/metrics/src/Metrics.ts +++ b/packages/metrics/src/Metrics.ts @@ -11,6 +11,9 @@ import { ExtraOptions, MetricUnit, MetricUnits, + MetricResolution, + MetricDefinition, + StoredMetric, } from './types'; const MAX_METRICS_SIZE = 100; @@ -165,12 +168,33 @@ class Metrics extends Utility implements MetricsInterface { /** * Add a metric to the metrics buffer. - * @param name - * @param unit - * @param value + * + * @example + * + * Add Metric using MetricUnit Enum supported by Cloudwatch + * + * ```ts + * metrics.addMetric('successfulBooking', MetricUnits.Count, 1); + * ``` + * + * @example + * + * Add Metric using MetricResolution type with resolutions High or Standard supported by cloudwatch + * + * ```ts + * metrics.addMetric('successfulBooking', MetricUnits.Count, 1, MetricResolution.High); + * ``` + * + * @param name - The metric name + * @param unit - The metric unit + * @param value - The metric value + * @param resolution - The metric resolution + * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#Resolution_definition Amazon Cloudwatch Concepts Documentation + * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html#CloudWatch_Embedded_Metric_Format_Specification_structure_metricdefinition Metric Definition of Embedded Metric Format Specification */ - public addMetric(name: string, unit: MetricUnit, value: number): void { - this.storeMetric(name, unit, value); + + public addMetric(name: string, unit: MetricUnit, value: number, resolution: MetricResolution = MetricResolution.Standard): void { + this.storeMetric(name, unit, value, resolution); if (this.isSingleMetric) this.publishStoredMetrics(); } @@ -314,15 +338,29 @@ class Metrics extends Utility implements MetricsInterface { } /** - * Function to create the right object compliant with Cloudwatch EMF (Event Metric Format). + * Function to create the right object compliant with Cloudwatch EMF (Embedded Metric Format). + * + * + * @returns metrics as JSON object compliant EMF Schema Specification * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html for more details - * @returns {string} */ public serializeMetrics(): EmfOutput { - const metricDefinitions = Object.values(this.storedMetrics).map((metricDefinition) => ({ - Name: metricDefinition.name, - Unit: metricDefinition.unit, - })); + // For high-resolution metrics, add StorageResolution property + // Example: [ { "Name": "metric_name", "Unit": "Count", "StorageResolution": 1 } ] + + // For standard resolution metrics, don't add StorageResolution property to avoid unnecessary ingestion of data into cloudwatch + // Example: [ { "Name": "metric_name", "Unit": "Count"} ] + const metricDefinitions: MetricDefinition[] = Object.values(this.storedMetrics).map((metricDefinition) => + this.isHigh(metricDefinition['resolution']) + ? ({ + Name: metricDefinition.name, + Unit: metricDefinition.unit, + StorageResolution: metricDefinition.resolution + }): ({ + Name: metricDefinition.name, + Unit: metricDefinition.unit, + })); + if (metricDefinitions.length === 0 && this.shouldThrowOnEmptyMetrics) { throw new RangeError('The number of metrics recorded must be higher than zero'); } @@ -429,6 +467,10 @@ class Metrics extends Utility implements MetricsInterface { return this.envVarsService; } + private isHigh(resolution: StoredMetric['resolution']): resolution is typeof MetricResolution['High'] { + return resolution === MetricResolution.High; + } + private isNewMetric(name: string, unit: MetricUnit): boolean { if (this.storedMetrics[name]){ // Inconsistent units indicates a bug or typos and we want to flag this to users early @@ -479,7 +521,12 @@ class Metrics extends Utility implements MetricsInterface { } } - private storeMetric(name: string, unit: MetricUnit, value: number): void { + private storeMetric( + name: string, + unit: MetricUnit, + value: number, + resolution: MetricResolution, + ): void { if (Object.keys(this.storedMetrics).length >= MAX_METRICS_SIZE) { this.publishStoredMetrics(); } @@ -488,8 +535,10 @@ class Metrics extends Utility implements MetricsInterface { this.storedMetrics[name] = { unit, value, - name, + name, + resolution }; + } else { const storedMetric = this.storedMetrics[name]; if (!Array.isArray(storedMetric.value)) { @@ -501,4 +550,8 @@ class Metrics extends Utility implements MetricsInterface { } -export { Metrics, MetricUnits }; +export { + Metrics, + MetricUnits, + MetricResolution, +}; \ No newline at end of file diff --git a/packages/metrics/src/MetricsInterface.ts b/packages/metrics/src/MetricsInterface.ts index cda2fd577e..bb8dea45b7 100644 --- a/packages/metrics/src/MetricsInterface.ts +++ b/packages/metrics/src/MetricsInterface.ts @@ -1,11 +1,17 @@ import { Metrics } from './Metrics'; -import { MetricUnit, EmfOutput, HandlerMethodDecorator, Dimensions, MetricsOptions } from './types'; - +import { + MetricUnit, + MetricResolution, + EmfOutput, + HandlerMethodDecorator, + Dimensions, + MetricsOptions +} from './types'; interface MetricsInterface { addDimension(name: string, value: string): void addDimensions(dimensions: {[key: string]: string}): void addMetadata(key: string, value: string): void - addMetric(name: string, unit:MetricUnit, value:number): void + addMetric(name: string, unit:MetricUnit, value:number, resolution?: MetricResolution): void clearDimensions(): void clearMetadata(): void clearMetrics(): void diff --git a/packages/metrics/src/types/MetricResolution.ts b/packages/metrics/src/types/MetricResolution.ts new file mode 100644 index 0000000000..76065be623 --- /dev/null +++ b/packages/metrics/src/types/MetricResolution.ts @@ -0,0 +1,8 @@ +const MetricResolution = { + Standard: 60, + High: 1, +} as const; + +type MetricResolution = typeof MetricResolution[keyof typeof MetricResolution]; + +export { MetricResolution }; \ No newline at end of file diff --git a/packages/metrics/src/types/Metrics.ts b/packages/metrics/src/types/Metrics.ts index c25653ca44..8c0c12f541 100644 --- a/packages/metrics/src/types/Metrics.ts +++ b/packages/metrics/src/types/Metrics.ts @@ -2,6 +2,7 @@ import { Handler } from 'aws-lambda'; import { LambdaInterface, AsyncHandler, SyncHandler } from '@aws-lambda-powertools/commons'; import { ConfigServiceInterface } from '../config'; import { MetricUnit } from './MetricUnit'; +import { MetricResolution } from './MetricResolution'; type Dimensions = { [key: string]: string }; @@ -19,8 +20,8 @@ type EmfOutput = { Timestamp: number CloudWatchMetrics: { Namespace: string - Dimensions: [string[]] - Metrics: { Name: string; Unit: MetricUnit }[] + Dimensions: [string[]] + Metrics: MetricDefinition[] }[] } }; @@ -60,10 +61,17 @@ type StoredMetric = { name: string unit: MetricUnit value: number | number[] + resolution: MetricResolution }; type StoredMetrics = { [key: string]: StoredMetric }; -export { MetricsOptions, Dimensions, EmfOutput, HandlerMethodDecorator, ExtraOptions, StoredMetrics }; +type MetricDefinition = { + Name: string + Unit: MetricUnit + StorageResolution?: MetricResolution +}; + +export { MetricsOptions, Dimensions, EmfOutput, HandlerMethodDecorator, ExtraOptions, StoredMetrics, StoredMetric, MetricDefinition }; diff --git a/packages/metrics/src/types/index.ts b/packages/metrics/src/types/index.ts index 44ff701f27..14416fbd33 100644 --- a/packages/metrics/src/types/index.ts +++ b/packages/metrics/src/types/index.ts @@ -1,2 +1,3 @@ export * from './Metrics'; -export * from './MetricUnit'; \ No newline at end of file +export * from './MetricUnit'; +export * from './MetricResolution'; \ No newline at end of file diff --git a/packages/metrics/tests/unit/Metrics.test.ts b/packages/metrics/tests/unit/Metrics.test.ts index 5f2bd02bf8..a3960b9635 100644 --- a/packages/metrics/tests/unit/Metrics.test.ts +++ b/packages/metrics/tests/unit/Metrics.test.ts @@ -6,7 +6,8 @@ import { ContextExamples as dummyContext, Events as dummyEvent, LambdaInterface } from '@aws-lambda-powertools/commons'; import { Context, Callback } from 'aws-lambda'; -import { Metrics, MetricUnits } from '../../src/'; + +import { Metrics, MetricUnits, MetricResolution } from '../../src/'; const MAX_METRICS_SIZE = 100; const MAX_DIMENSION_COUNT = 29; @@ -563,6 +564,61 @@ describe('Class: Metrics', () => { }); }); + describe('Feature: Resolution of Metrics', ()=>{ + + test('serialized metrics in EMF format should not contain `StorageResolution` as key if none is set', () => { + const metrics = new Metrics(); + metrics.addMetric('test_name', MetricUnits.Seconds, 10); + const serializedMetrics = metrics.serializeMetrics(); + + expect(Object.keys(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0])).not.toContain('StorageResolution'); + expect(Object.keys(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0])).toContain('Name'); + expect(Object.keys(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0])).toContain('Unit'); + + }); + test('serialized metrics in EMF format should not contain `StorageResolution` as key if `Standard` is set', () => { + const metrics = new Metrics(); + metrics.addMetric('test_name', MetricUnits.Seconds, 10, MetricResolution.Standard); + const serializedMetrics = metrics.serializeMetrics(); + + // expect(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0].StorageResolution).toBe(MetricResolution.Standard); + // expect(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0].StorageResolution).toBe(60); + + expect(Object.keys(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0])).not.toContain('StorageResolution'); + expect(Object.keys(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0])).toContain('Name'); + expect(Object.keys(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0])).toContain('Unit'); + }); + + test('serialized metrics in EMF format should not contain `StorageResolution` as key if `60` is set',()=>{ + const metrics = new Metrics(); + metrics.addMetric('test_name', MetricUnits.Seconds, 10, 60); + const serializedMetrics = metrics.serializeMetrics(); + + expect(Object.keys(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0])).not.toContain('StorageResolution'); + expect(Object.keys(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0])).toContain('Name'); + expect(Object.keys(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0])).toContain('Unit'); + }); + + test('Should be StorageResolution `1` if MetricResolution is set to `High`',()=>{ + const metrics = new Metrics(); + metrics.addMetric('test_name', MetricUnits.Seconds, 10, MetricResolution.High); + const serializedMetrics = metrics.serializeMetrics(); + + expect(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0].StorageResolution).toBe(MetricResolution.High); + expect(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0].StorageResolution).toBe(1); + }); + + test('Should be StorageResolution `1` if MetricResolution is set to `1`',()=>{ + const metrics = new Metrics(); + metrics.addMetric('test_name', MetricUnits.Seconds, 10, 1); + const serializedMetrics = metrics.serializeMetrics(); + + expect(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0].StorageResolution).toBe(MetricResolution.High); + expect(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0].StorageResolution).toBe(1); + + }); + }); + describe('Feature: Clearing Metrics ', () => { test('Clearing metrics should return empty', async () => { const metrics = new Metrics({ namespace: 'test' }); diff --git a/packages/metrics/tests/unit/middleware/middy.test.ts b/packages/metrics/tests/unit/middleware/middy.test.ts index ad5a502120..1ee25e79d6 100644 --- a/packages/metrics/tests/unit/middleware/middy.test.ts +++ b/packages/metrics/tests/unit/middleware/middy.test.ts @@ -4,8 +4,12 @@ * @group unit/metrics/middleware */ -import { Metrics, MetricUnits, logMetrics } from '../../../../metrics/src'; -import middy from '@middy/core'; +import { + Metrics, + MetricUnits, + logMetrics, + MetricResolution +} from '../../../../metrics/src';import middy from '@middy/core'; import { ExtraOptions } from '../../../src/types'; const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); @@ -315,4 +319,77 @@ describe('Middy middleware', () => { ); }); }); + describe('Metrics resolution', () => { + + test('serialized metrics in EMF format should not contain `StorageResolution` as key if `60` is set', async () => { + // Prepare + const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); + + const lambdaHandler = (): void => { + metrics.addMetric('successfulBooking', MetricUnits.Count, 1, MetricResolution.Standard); + }; + + const handler = middy(lambdaHandler).use(logMetrics(metrics)); + + // Act + await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); + + // Assess + expect(console.log).toHaveBeenCalledWith( + JSON.stringify({ + _aws: { + Timestamp: 1466424490000, + CloudWatchMetrics: [ + { + Namespace: 'serverlessAirline', + Dimensions: [['service']], + Metrics: [{ + Name: 'successfulBooking', + Unit: 'Count', + }], + }, + ], + }, + service: 'orders', + successfulBooking: 1, + }) + ); + }); + + test('Should be StorageResolution `1` if MetricResolution is set to `High`', async () => { + // Prepare + const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' }); + + const lambdaHandler = (): void => { + metrics.addMetric('successfulBooking', MetricUnits.Count, 1, MetricResolution.High); + }; + + const handler = middy(lambdaHandler).use(logMetrics(metrics)); + + // Act + await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); + + // Assess + expect(console.log).toHaveBeenCalledWith( + JSON.stringify({ + _aws: { + Timestamp: 1466424490000, + CloudWatchMetrics: [ + { + Namespace: 'serverlessAirline', + Dimensions: [['service']], + Metrics: [{ + Name: 'successfulBooking', + Unit: 'Count', + StorageResolution: 1 + }], + }, + ], + }, + service: 'orders', + successfulBooking: 1, + }) + ); + }); + }); }); From a82fc3a89b8512f7ff5c0bfd4f27cd59e33f10f4 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Mon, 20 Mar 2023 10:01:04 +0100 Subject: [PATCH 20/26] refactor(tracer): log warning instead of throwing when segment is not found (#1370) * improv: moved logic to provider + changed middleware behavior around absent segment * tests: fixed tests for middleware * tests: fixed tests for main class * tests: fixed provider service tests * chore: bump xray-sdk-core * chore: update jsdoc for addErrorAsMetadata * chore: update jsdoc for getSegment * chore: update jsdoc for getSegment --- package-lock.json | 16 +- packages/tracer/package.json | 4 +- packages/tracer/src/Tracer.ts | 40 +- packages/tracer/src/TracerInterface.ts | 4 +- packages/tracer/src/middleware/middy.ts | 20 +- .../tracer/src/provider/ProviderService.ts | 53 ++- .../src/provider/ProviderServiceInterface.ts | 4 + .../tracer/tests/unit/ProviderService.test.ts | 142 +++++- packages/tracer/tests/unit/Tracer.test.ts | 405 +++++------------- packages/tracer/tests/unit/middy.test.ts | 169 +++----- 10 files changed, 391 insertions(+), 466 deletions(-) diff --git a/package-lock.json b/package-lock.json index 104f681567..c21c3e3d9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5815,9 +5815,9 @@ } }, "node_modules/aws-xray-sdk-core": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.4.0.tgz", - "integrity": "sha512-PH3jqjUp8fTIaRa0i6fjJxXJSR9yDwldH1Qw7nMDNAouL7txsrSM6BhrQEjcaZ2mwfU0PNx8tsFQ+2/nfNdR1w==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.4.1.tgz", + "integrity": "sha512-HPQ+9GMF+yhDDXNKPp6HGAIv2yVapKTI7WRs2aN3TT+RxQkbmgO+3JlcMvRL2/lu0RqTVWoYCIhmF9/H/zRL1A==", "dependencies": { "@aws-sdk/service-error-classification": "^3.4.1", "@aws-sdk/types": "^3.4.1", @@ -16792,7 +16792,7 @@ "license": "MIT-0", "dependencies": { "@aws-lambda-powertools/commons": "^1.6.0", - "aws-xray-sdk-core": "^3.4.0" + "aws-xray-sdk-core": "^3.4.1" }, "devDependencies": { "@aws-sdk/client-dynamodb": "^3.231.0", @@ -17081,7 +17081,7 @@ "@aws-sdk/client-dynamodb": "^3.231.0", "@types/promise-retry": "^1.1.3", "aws-sdk": "^2.1276.0", - "aws-xray-sdk-core": "^3.4.0", + "aws-xray-sdk-core": "3.4.1", "axios": "^1.2.1", "promise-retry": "^2.0.1" } @@ -21326,9 +21326,9 @@ "dev": true }, "aws-xray-sdk-core": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.4.0.tgz", - "integrity": "sha512-PH3jqjUp8fTIaRa0i6fjJxXJSR9yDwldH1Qw7nMDNAouL7txsrSM6BhrQEjcaZ2mwfU0PNx8tsFQ+2/nfNdR1w==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.4.1.tgz", + "integrity": "sha512-HPQ+9GMF+yhDDXNKPp6HGAIv2yVapKTI7WRs2aN3TT+RxQkbmgO+3JlcMvRL2/lu0RqTVWoYCIhmF9/H/zRL1A==", "requires": { "@aws-sdk/service-error-classification": "^3.4.1", "@aws-sdk/types": "^3.4.1", diff --git a/packages/tracer/package.json b/packages/tracer/package.json index 714feea5c5..e7ec65f99b 100644 --- a/packages/tracer/package.json +++ b/packages/tracer/package.json @@ -52,7 +52,7 @@ }, "dependencies": { "@aws-lambda-powertools/commons": "^1.6.0", - "aws-xray-sdk-core": "^3.4.0" + "aws-xray-sdk-core": "^3.4.1" }, "keywords": [ "aws", @@ -63,4 +63,4 @@ "serverless", "nodejs" ] -} \ No newline at end of file +} diff --git a/packages/tracer/src/Tracer.ts b/packages/tracer/src/Tracer.ts index 6d846b371f..b4fe242395 100644 --- a/packages/tracer/src/Tracer.ts +++ b/packages/tracer/src/Tracer.ts @@ -38,7 +38,7 @@ import { Segment, Subsegment } from 'aws-xray-sdk-core'; * * const tracer = new Tracer({ serviceName: 'serverlessAirline' }); * -* const lambdaHandler = async (_event: any, _context: any) => { + * const lambdaHandler = async (_event: any, _context: any) => { * ... * }; * @@ -153,20 +153,26 @@ class Tracer extends Utility implements TracerInterface { * @see https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-errors * * @param error - Error to serialize as metadata + * @param [remote] - Whether the error was thrown by a remote service. Defaults to `false` */ - public addErrorAsMetadata(error: Error): void { + public addErrorAsMetadata(error: Error, remote?: boolean): void { if (!this.isTracingEnabled()) { return; } const subsegment = this.getSegment(); + if (subsegment === undefined) { + + return; + } + if (!this.captureError) { subsegment.addErrorFlag(); return; } - subsegment.addError(error, false); + subsegment.addError(error, remote || false); } /** @@ -506,7 +512,7 @@ class Tracer extends Utility implements TracerInterface { } /** - * Get the active segment or subsegment in the current scope. + * Get the active segment or subsegment (if any) in the current scope. * * Usually you won't need to call this method unless you are creating custom subsegments or using manual mode. * @@ -525,15 +531,17 @@ class Tracer extends Utility implements TracerInterface { * } * ``` * - * @returns segment - The active segment or subsegment in the current scope. + * @returns The active segment or subsegment in the current scope. Will log a warning and return `undefined` if no segment is found. */ - public getSegment(): Segment | Subsegment { + public getSegment(): Segment | Subsegment | undefined { if (!this.isTracingEnabled()) { return new Subsegment('## Dummy segment'); } const segment = this.provider.getSegment(); if (segment === undefined) { - throw new Error('Failed to get the current sub/segment from the context.'); + console.warn( + 'Failed to get the current sub/segment from the context, this is likely because you are not using the Tracer in a Lambda function.' + ); } return segment; @@ -573,13 +581,7 @@ class Tracer extends Utility implements TracerInterface { public putAnnotation(key: string, value: string | number | boolean): void { if (!this.isTracingEnabled()) return; - const document = this.getSegment(); - if (document instanceof Segment) { - console.warn('You cannot annotate the main segment in a Lambda execution environment'); - - return; - } - document.addAnnotation(key, value); + this.provider.putAnnotation(key, value); } /** @@ -606,15 +608,7 @@ class Tracer extends Utility implements TracerInterface { public putMetadata(key: string, value: unknown, namespace?: string | undefined): void { if (!this.isTracingEnabled()) return; - const document = this.getSegment(); - if (document instanceof Segment) { - console.warn('You cannot add metadata to the main segment in a Lambda execution environment'); - - return; - } - - namespace = namespace || this.serviceName; - document.addMetadata(key, value, namespace); + this.provider.putMetadata(key, value, namespace || this.serviceName); } /** diff --git a/packages/tracer/src/TracerInterface.ts b/packages/tracer/src/TracerInterface.ts index 128a2c5553..344e44177e 100644 --- a/packages/tracer/src/TracerInterface.ts +++ b/packages/tracer/src/TracerInterface.ts @@ -2,7 +2,7 @@ import { CaptureLambdaHandlerOptions, CaptureMethodOptions, HandlerMethodDecorat import { Segment, Subsegment } from 'aws-xray-sdk-core'; interface TracerInterface { - addErrorAsMetadata(error: Error): void + addErrorAsMetadata(error: Error, remote?: boolean): void addResponseAsMetadata(data?: unknown, methodName?: string): void addServiceNameAnnotation(): void annotateColdStart(): void @@ -11,7 +11,7 @@ interface TracerInterface { captureAWSClient(service: T): void | T captureLambdaHandler(options?: CaptureLambdaHandlerOptions): HandlerMethodDecorator captureMethod(options?: CaptureMethodOptions): MethodDecorator - getSegment(): Segment | Subsegment + getSegment(): Segment | Subsegment | undefined getRootXrayTraceId(): string | undefined 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 a5c8e81b6c..399945c453 100644 --- a/packages/tracer/src/middleware/middy.ts +++ b/packages/tracer/src/middleware/middy.ts @@ -34,18 +34,26 @@ import type { * @returns middleware - The middy middleware object */ const captureLambdaHandler = (target: Tracer, options?: CaptureLambdaHandlerOptions): MiddlewareLikeObj => { - let lambdaSegment: Subsegment | Segment; + let lambdaSegment: Segment; + let handlerSegment: Subsegment; const open = (): void => { - lambdaSegment = target.getSegment(); - const handlerSegment = lambdaSegment.addNewSubsegment(`## ${process.env._HANDLER}`); + const segment = target.getSegment(); + if (segment === undefined) { + return; + } + // If segment is defined, then it is a Segment as this middleware is only used for Lambda Handlers + lambdaSegment = segment as Segment; + handlerSegment = lambdaSegment.addNewSubsegment(`## ${process.env._HANDLER}`); target.setSegment(handlerSegment); }; const close = (): void => { - const subsegment = target.getSegment(); - subsegment.close(); - target.setSegment(lambdaSegment as Segment); + if (handlerSegment === undefined || lambdaSegment === null) { + return; + } + handlerSegment.close(); + target.setSegment(lambdaSegment); }; const captureLambdaHandlerBefore = async (): Promise => { diff --git a/packages/tracer/src/provider/ProviderService.ts b/packages/tracer/src/provider/ProviderService.ts index 626e9b5585..56abd43dfd 100644 --- a/packages/tracer/src/provider/ProviderService.ts +++ b/packages/tracer/src/provider/ProviderService.ts @@ -1,10 +1,27 @@ -import { ContextMissingStrategy } from 'aws-xray-sdk-core/dist/lib/context_utils'; +import { + ContextMissingStrategy +} from 'aws-xray-sdk-core/dist/lib/context_utils'; import { Namespace } from 'cls-hooked'; import { ProviderServiceInterface } from '.'; -import { captureAWS, captureAWSClient, captureAWSv3Client, captureAsyncFunc, captureFunc, captureHTTPsGlobal, getNamespace, getSegment, setSegment, Segment, Subsegment, setContextMissingStrategy, setDaemonAddress, setLogger, Logger } from 'aws-xray-sdk-core'; +import { + captureAWS, + captureAWSClient, + captureAWSv3Client, + captureAsyncFunc, + captureFunc, + captureHTTPsGlobal, + getNamespace, + getSegment, + setSegment, + Segment, + Subsegment, + setContextMissingStrategy, + setDaemonAddress, + setLogger, + Logger +} from 'aws-xray-sdk-core'; class ProviderService implements ProviderServiceInterface { - public captureAWS(awssdk: T): T { return captureAWS(awssdk); } @@ -42,6 +59,36 @@ class ProviderService implements ProviderServiceInterface { return getSegment(); } + public putAnnotation(key: string, value: string | number | boolean): void { + const segment = this.getSegment(); + if (segment === undefined) { + console.warn('No active segment or subsegment found, skipping annotation'); + + return; + } + if (segment instanceof Segment) { + console.warn('You cannot annotate the main segment in a Lambda execution environment'); + + return; + } + segment.addAnnotation(key, value); + } + + public putMetadata(key: string, value: unknown, namespace?: string): void { + const segment = this.getSegment(); + if (segment === undefined) { + console.warn('No active segment or subsegment found, skipping metadata addition'); + + return; + } + if (segment instanceof Segment) { + console.warn('You cannot add metadata to the main segment in a Lambda execution environment'); + + return; + } + segment.addMetadata(key, value, namespace); + } + public setContextMissingStrategy(strategy: unknown): void { setContextMissingStrategy(strategy as ContextMissingStrategy); } diff --git a/packages/tracer/src/provider/ProviderServiceInterface.ts b/packages/tracer/src/provider/ProviderServiceInterface.ts index 4b4c1bef96..2376255767 100644 --- a/packages/tracer/src/provider/ProviderServiceInterface.ts +++ b/packages/tracer/src/provider/ProviderServiceInterface.ts @@ -25,6 +25,10 @@ interface ProviderServiceInterface { captureFunc(name: string, fcn: (subsegment?: Subsegment) => unknown, parent?: Segment | Subsegment): unknown captureHTTPsGlobal(): void + + putAnnotation(key: string, value: string | number | boolean): void + + putMetadata(key: string, value: unknown, namespace?: string): void } export { diff --git a/packages/tracer/tests/unit/ProviderService.test.ts b/packages/tracer/tests/unit/ProviderService.test.ts index fdafaf43a3..7335b70fd2 100644 --- a/packages/tracer/tests/unit/ProviderService.test.ts +++ b/packages/tracer/tests/unit/ProviderService.test.ts @@ -1,15 +1,31 @@ /** * Test ProviderService class * - * @group unit/tracer/all + * @group unit/tracer/providerservice */ import { ProviderService } from '../../src/provider'; -import { captureAWS, captureAWSClient, captureAWSv3Client, captureAsyncFunc, captureHTTPsGlobal, captureFunc, getNamespace, getSegment, setContextMissingStrategy, setDaemonAddress, setLogger, setSegment, Subsegment } from 'aws-xray-sdk-core'; +import { + captureAWS, + captureAWSClient, + captureAWSv3Client, + captureAsyncFunc, + captureHTTPsGlobal, + captureFunc, + getNamespace, + getSegment, + setContextMissingStrategy, + setDaemonAddress, + setLogger, + setSegment, + Subsegment, + Segment +} from 'aws-xray-sdk-core'; import http from 'http'; import https from 'https'; jest.mock('aws-xray-sdk-core', () => ({ + ...jest.requireActual('aws-xray-sdk-core'), captureAWS: jest.fn(), captureAWSClient: jest.fn(), captureAWSv3Client: jest.fn(), @@ -21,7 +37,7 @@ jest.mock('aws-xray-sdk-core', () => ({ setContextMissingStrategy: jest.fn(), setDaemonAddress: jest.fn(), setLogger: jest.fn(), - setSegment: jest.fn() + setSegment: jest.fn(), })); describe('Class: ProviderService', () => { @@ -265,4 +281,124 @@ describe('Class: ProviderService', () => { }); + describe('Method: putAnnotation', () => { + + test('when called and there is no segment, it logs a warning and does not throw', () => { + + // Prepare + const provider: ProviderService = new ProviderService(); + const logSpy = jest.spyOn(console, 'warn').mockImplementation(); + + // Act + provider.putAnnotation('foo', 'bar'); + + // Assess + expect(logSpy).toHaveBeenCalledTimes(1); + expect(logSpy).toHaveBeenCalledWith('No active segment or subsegment found, skipping annotation'); + + }); + + test('when called and the current segment is not a subsegment, it logs a warning and does not annotate the segment', () => { + + // Prepare + const provider: ProviderService = new ProviderService(); + const facade = new Segment('facade'); + const logWarningSpy = jest.spyOn(console, 'warn').mockImplementation(); + jest.spyOn(provider, 'getSegment') + .mockImplementation(() => new Segment('facade')); + const addAnnotationSpy = jest.spyOn(facade, 'addAnnotation'); + + // Act + provider.putAnnotation('foo', 'bar'); + + // Assess + expect(logWarningSpy).toHaveBeenCalledTimes(1); + expect(logWarningSpy).toHaveBeenCalledWith( + 'You cannot annotate the main segment in a Lambda execution environment' + ); + expect(addAnnotationSpy).toHaveBeenCalledTimes(0); + + }); + + test('when called and the current segment is a subsegment, it annotates it', () => { + + // Prepare + const provider: ProviderService = new ProviderService(); + const segment = new Subsegment('## dummySegment'); + jest.spyOn(provider, 'getSegment') + .mockImplementation(() => segment); + const segmentSpy = jest.spyOn(segment, 'addAnnotation'); + + // Act + provider.putAnnotation('foo', 'bar'); + + // Assess + expect(segmentSpy).toHaveBeenCalledTimes(1); + expect(segmentSpy).toHaveBeenCalledWith('foo', 'bar'); + + }); + + }); + + describe('Method: putMetadata', () => { + + test('when called and there is no segment, it logs a warning and does not throw', () => { + + // Prepare + const provider: ProviderService = new ProviderService(); + const logWarningSpy = jest.spyOn(console, 'warn').mockImplementation(); + + // Act + provider.putMetadata('foo', 'bar'); + + // Assess + expect(logWarningSpy).toHaveBeenCalledTimes(1); + expect(logWarningSpy).toHaveBeenCalledWith( + 'No active segment or subsegment found, skipping metadata addition' + ); + + }); + + test('when called and the current segment is not a subsegment, it logs a warning and does not annotate the segment', () => { + + // Prepare + const provider: ProviderService = new ProviderService(); + const facade = new Segment('facade'); + const logSpy = jest.spyOn(console, 'warn').mockImplementation(); + jest.spyOn(provider, 'getSegment') + .mockImplementation(() => new Segment('facade')); + const facadeSpy = jest.spyOn(facade, 'addMetadata'); + + // Act + provider.putMetadata('foo', 'bar'); + + // Assess + expect(logSpy).toHaveBeenCalledTimes(1); + expect(logSpy).toHaveBeenCalledWith( + 'You cannot add metadata to the main segment in a Lambda execution environment' + ); + expect(facadeSpy).toHaveBeenCalledTimes(0); + + }); + + test('when called and the current segment is a subsegment, it adds the metadata', () => { + + // Prepare + const provider: ProviderService = new ProviderService(); + const segment = new Subsegment('## dummySegment'); + jest.spyOn(provider, 'getSegment') + .mockImplementation(() => segment); + const segmentSpy = jest.spyOn(segment, 'addMetadata'); + + // Act + provider.putMetadata('foo', 'bar', 'baz'); + + // Assess + expect(segmentSpy).toHaveBeenCalledTimes(1); + expect(segmentSpy).toHaveBeenCalledWith('foo', 'bar', 'baz'); + + }); + + }); + }); \ 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 d46db1164b..dd9383861f 100644 --- a/packages/tracer/tests/unit/Tracer.test.ts +++ b/packages/tracer/tests/unit/Tracer.test.ts @@ -250,6 +250,17 @@ describe('Class: Tracer', () => { }); + test('when called and the segment is not found, it returns instead of throwing', () => { + + // Prepare + const tracer: Tracer = new Tracer(); + jest.spyOn(tracer, 'getSegment').mockImplementation(() => undefined); + + // Act & Assess + expect(() => tracer.addErrorAsMetadata(new Error('foo'))).not.toThrow(); + + }); + }); describe('Method: getRootXrayTraceId', () => { @@ -271,28 +282,20 @@ describe('Class: Tracer', () => { describe('Method: getSegment', () => { - test('when called outside of a namespace or without parent segment, and tracing is enabled, it throws an error', () => { - - // Prepare - const tracer: Tracer = new Tracer(); - - // Act / Assess - expect(() => { - tracer.getSegment(); - }).toThrow('Failed to get the current sub/segment from the context.'); - - }); - - test('when called and no segment is returned, while tracing is enabled, it throws an error', () => { + test('when called and no segment is returned, it logs a warning', () => { // Prepare const tracer: Tracer = new Tracer(); jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => undefined); + jest.spyOn(console, 'warn').mockImplementation(() => null); - // Act / Assess - expect(() => { - tracer.getSegment(); - }).toThrow('Failed to get the current sub/segment from the context.'); + // Act + tracer.getSegment(); + + // Assess + expect(console.warn).toHaveBeenCalledWith( + 'Failed to get the current sub/segment from the context, this is likely because you are not using the Tracer in a Lambda function.' + ); }); @@ -307,8 +310,8 @@ describe('Class: Tracer', () => { // Assess expect(segment).toBeInstanceOf(Subsegment); - expect(segment.name).toBe('## Dummy segment'); - + expect((segment as Subsegment).name).toBe('## Dummy segment'); + }); test('when called within a namespace, it returns the parent segment', () => { @@ -388,71 +391,28 @@ describe('Class: Tracer', () => { // Prepare const tracer: Tracer = new Tracer({ enabled: false }); - const facadeSegment = new Segment('facade', process.env._X_AMZN_TRACE_ID || null); - jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => facadeSegment); - const addAnnotationSpy = jest.spyOn(facadeSegment, 'addAnnotation'); + const putAnnotationSpy = jest.spyOn(tracer.provider, 'putAnnotation'); // Act tracer.putAnnotation('foo', 'bar'); // Assess - expect('annotations' in facadeSegment).toBe(false); - expect(addAnnotationSpy).toBeCalledTimes(0); + expect(putAnnotationSpy).toBeCalledTimes(0); }); - test('when called outside of a namespace or without parent segment, it throws an error', () => { - - // Prepare - const tracer: Tracer = new Tracer(); - - // Act / Assess - expect(() => { - tracer.putAnnotation('foo', 'bar'); - }).toThrow('Failed to get the current sub/segment from the context.'); - - }); - - test('when called within a namespace and on the main segment, it does nothing', () => { + test('it calls the provider method with the correct arguments', () => { // Prepare const tracer: Tracer = new Tracer(); - const facadeSegment = new Segment('facade', process.env._X_AMZN_TRACE_ID || null); - jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => facadeSegment); - const addAnnotationSpy = jest.spyOn(facadeSegment, 'addAnnotation'); + const putAnnotationSpy = jest.spyOn(tracer.provider, 'putAnnotation'); // Act tracer.putAnnotation('foo', 'bar'); // Assess - expect('annotations' in facadeSegment).toBe(false); - expect(addAnnotationSpy).toBeCalledTimes(0); - expect(console.warn).toBeCalledTimes(1); - expect(console.warn).toHaveBeenNthCalledWith(1, 'You cannot annotate the main segment in a Lambda execution environment'); - - }); - - test('when called within a namespace and on a subsegment, it adds an annotation', () => { - - // Prepare - const tracer: Tracer = new Tracer(); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## foo.bar'); - jest.spyOn(tracer.provider, 'getSegment') - .mockImplementation(() => newSubsegment); - const addAnnotationSpy = jest.spyOn(newSubsegment, 'addAnnotation'); - - // Act - tracer.putAnnotation('foo', 'bar'); - - // Assess - expect('annotations' in newSubsegment).toBe(true); - expect(addAnnotationSpy).toBeCalledTimes(1); - expect(addAnnotationSpy).toBeCalledWith('foo', 'bar'); - expect(newSubsegment).toEqual(expect.objectContaining({ - 'annotations': { - foo: 'bar' - } - })); + expect(putAnnotationSpy).toBeCalledTimes(1); + expect(putAnnotationSpy).toBeCalledWith('foo', 'bar'); }); @@ -460,128 +420,63 @@ describe('Class: Tracer', () => { describe('Method: putMetadata', () => { - test('when called while tracing is disabled, it does nothing', () => { + test('when tracing is disabled, it does nothing', () => { // Prepare const tracer: Tracer = new Tracer({ enabled: false }); - const facadeSegment = new Segment('facade', process.env._X_AMZN_TRACE_ID || null); - jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => facadeSegment); - const addMetadataSpy = jest.spyOn(facadeSegment, 'addMetadata'); + const putMetadataSpy = jest.spyOn(tracer.provider, 'putMetadata'); // Act tracer.putMetadata('foo', 'bar'); // Assess - expect('metadata' in facadeSegment).toBe(false); - expect(addMetadataSpy).toBeCalledTimes(0); - - }); - - test('when called outside of a namespace or without parent segment, it throws an error', () => { - - // Prepare - const tracer: Tracer = new Tracer(); - - // Act / Assess - expect(() => { - tracer.putMetadata('foo', 'bar'); - }).toThrow('Failed to get the current sub/segment from the context.'); + expect(putMetadataSpy).toBeCalledTimes(0); }); - test('when called within a namespace and on the main segment, it does nothing', () => { + test('it calls the provider method with the correct arguments', () => { // Prepare const tracer: Tracer = new Tracer(); - const facadeSegment = new Segment('facade', process.env._X_AMZN_TRACE_ID || null); - jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => facadeSegment); - const addMetadataSpy = jest.spyOn(facadeSegment, 'addMetadata'); - - // Act - tracer.putMetadata('foo', 'bar'); - - // Assess - expect('metadata' in facadeSegment).toBe(false); - expect(addMetadataSpy).toBeCalledTimes(0); - expect(console.warn).toBeCalledTimes(1); - expect(console.warn).toHaveBeenNthCalledWith(1, 'You cannot add metadata to the main segment in a Lambda execution environment'); - - }); - - test('when called within a namespace and on a subsegment, it adds the metadata with the default service name as namespace', () => { - - // Prepare - const tracer: Tracer = new Tracer(); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## foo.bar'); - jest.spyOn(tracer.provider, 'getSegment') - .mockImplementation(() => newSubsegment); - const addMetadataSpy = jest.spyOn(newSubsegment, 'addMetadata'); - + const putMetadataSpy = jest.spyOn(tracer.provider, 'putMetadata'); + // Act tracer.putMetadata('foo', 'bar'); // Assess - expect('metadata' in newSubsegment).toBe(true); - expect(addMetadataSpy).toBeCalledTimes(1); - expect(addMetadataSpy).toBeCalledWith('foo', 'bar', 'hello-world'); - expect(newSubsegment).toEqual(expect.objectContaining({ - 'metadata': { - 'hello-world': { - foo: 'bar' - } - } - })); + expect(putMetadataSpy).toBeCalledTimes(1); + // The default namespace is 'hello-world' and it comes from the service name environment variable + expect(putMetadataSpy).toBeCalledWith('foo', 'bar', 'hello-world'); + }); - test('when called within a namespace and on a subsegment, and with a custom namespace as an argument, it adds the metadata correctly', () => { + test('when passed a custom namespace, it calls the provider method with the correct arguments', () => { // Prepare const tracer: Tracer = new Tracer(); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## foo.bar'); - jest.spyOn(tracer.provider, 'getSegment') - .mockImplementation(() => newSubsegment); - const addMetadataSpy = jest.spyOn(newSubsegment, 'addMetadata'); - + const putMetadataSpy = jest.spyOn(tracer.provider, 'putMetadata'); + // Act tracer.putMetadata('foo', 'bar', 'baz'); // Assess - expect('metadata' in newSubsegment).toBe(true); - expect(addMetadataSpy).toBeCalledTimes(1); - expect(addMetadataSpy).toBeCalledWith('foo', 'bar', 'baz'); - expect(newSubsegment).toEqual(expect.objectContaining({ - 'metadata': { - 'baz': { - foo: 'bar' - } - } - })); + expect(putMetadataSpy).toBeCalledTimes(1); + expect(putMetadataSpy).toBeCalledWith('foo', 'bar', 'baz'); }); - test('when called within a namespace and on a subsegment, and while a custom namespace was set in the class, it adds the metadata correctly', () => { + test('when a custom namespace was set in the constructor, it calls the provider method with the correct arguments', () => { // Prepare const tracer: Tracer = new Tracer({ serviceName: 'baz' }); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## foo.bar'); - jest.spyOn(tracer.provider, 'getSegment') - .mockImplementation(() => newSubsegment); - const addMetadataSpy = jest.spyOn(newSubsegment, 'addMetadata'); + const putMetadataSpy = jest.spyOn(tracer.provider, 'putMetadata'); // Act tracer.putMetadata('foo', 'bar'); // Assess - expect('metadata' in newSubsegment).toBe(true); - expect(addMetadataSpy).toBeCalledTimes(1); - expect(addMetadataSpy).toBeCalledWith('foo', 'bar', 'baz'); - expect(newSubsegment).toEqual(expect.objectContaining({ - 'metadata': { - 'baz': { - foo: 'bar' - } - } - })); + expect(putMetadataSpy).toBeCalledTimes(1); + expect(putMetadataSpy).toBeCalledWith('foo', 'bar', 'baz'); }); @@ -621,10 +516,9 @@ describe('Class: Tracer', () => { // Prepare process.env.POWERTOOLS_TRACER_CAPTURE_RESPONSE = 'false'; const tracer: Tracer = new Tracer(); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => newSubsegment); - setContextMissingStrategy(() => null); const captureAsyncFuncSpy = jest.spyOn(tracer.provider, 'captureAsyncFunc'); + const putMetadataSpy = jest.spyOn(tracer, 'putMetadata'); + class Lambda implements LambdaInterface { @tracer.captureLambdaHandler() @@ -643,7 +537,7 @@ describe('Class: Tracer', () => { // Assess expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); - expect('metadata' in newSubsegment).toBe(false); + expect(putMetadataSpy).toHaveBeenCalledTimes(0); delete process.env.POWERTOOLS_TRACER_CAPTURE_RESPONSE; }); @@ -652,10 +546,9 @@ describe('Class: Tracer', () => { // Prepare const tracer: Tracer = new Tracer(); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => newSubsegment); - setContextMissingStrategy(() => null); const captureAsyncFuncSpy = jest.spyOn(tracer.provider, 'captureAsyncFunc'); + const addResponseAsMetadataSpy = jest.spyOn(tracer, 'addResponseAsMetadata'); + class Lambda implements LambdaInterface { @tracer.captureLambdaHandler({ captureResponse: false }) @@ -674,7 +567,7 @@ describe('Class: Tracer', () => { // Assess expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); - expect('metadata' in newSubsegment).toBe(false); + expect(addResponseAsMetadataSpy).toHaveBeenCalledTimes(0); }); @@ -682,11 +575,9 @@ describe('Class: Tracer', () => { // Prepare const tracer: Tracer = new Tracer(); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - jest.spyOn(tracer.provider, 'getSegment') - .mockImplementation(() => newSubsegment); - setContextMissingStrategy(() => null); const captureAsyncFuncSpy = jest.spyOn(tracer.provider, 'captureAsyncFunc'); + const addResponseAsMetadataSpy = jest.spyOn(tracer, 'addResponseAsMetadata'); + class Lambda implements LambdaInterface { @tracer.captureLambdaHandler({ captureResponse: true }) @@ -706,28 +597,18 @@ describe('Class: Tracer', () => { // Assess expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); expect(captureAsyncFuncSpy).toHaveBeenCalledWith('## index.handler', expect.anything()); - expect(newSubsegment).toEqual(expect.objectContaining({ - name: '## index.handler', - metadata: { - 'hello-world': { - 'index.handler response': { - foo: 'bar', - }, - }, - } - })); - + expect(addResponseAsMetadataSpy).toHaveBeenCalledTimes(1); + expect(addResponseAsMetadataSpy).toHaveBeenCalledWith({ foo: 'bar' }, 'index.handler'); + }); test('when used as decorator and with standard config, it captures the response as metadata', async () => { // Prepare const tracer: Tracer = new Tracer(); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - jest.spyOn(tracer.provider, 'getSegment') - .mockImplementation(() => newSubsegment); - setContextMissingStrategy(() => null); const captureAsyncFuncSpy = jest.spyOn(tracer.provider, 'captureAsyncFunc'); + const addResponseAsMetadataSpy = jest.spyOn(tracer, 'addResponseAsMetadata'); + class Lambda implements LambdaInterface { @tracer.captureLambdaHandler() @@ -747,16 +628,8 @@ describe('Class: Tracer', () => { // Assess expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); expect(captureAsyncFuncSpy).toHaveBeenCalledWith('## index.handler', expect.anything()); - expect(newSubsegment).toEqual(expect.objectContaining({ - name: '## index.handler', - metadata: { - 'hello-world': { - 'index.handler response': { - foo: 'bar', - }, - }, - } - })); + expect(addResponseAsMetadataSpy).toHaveBeenCalledTimes(1); + expect(addResponseAsMetadataSpy).toHaveBeenCalledWith({ foo: 'bar' }, 'index.handler'); }); @@ -765,13 +638,13 @@ describe('Class: Tracer', () => { // Prepare process.env.POWERTOOLS_TRACER_CAPTURE_ERROR = 'false'; const tracer: Tracer = new Tracer(); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - jest.spyOn(tracer.provider, 'getSegment') - .mockImplementation(() => newSubsegment); - setContextMissingStrategy(() => null); const captureAsyncFuncSpy = jest.spyOn(tracer.provider, 'captureAsyncFunc'); - const addErrorSpy = jest.spyOn(newSubsegment, 'addError'); + const newSubsegment = new Subsegment('### dummyMethod'); + jest.spyOn(tracer, 'getSegment') + .mockImplementation(() => newSubsegment); const addErrorFlagSpy = jest.spyOn(newSubsegment, 'addErrorFlag'); + const addErrorSpy = jest.spyOn(newSubsegment, 'addError'); + class Lambda implements LambdaInterface { @tracer.captureLambdaHandler() @@ -786,31 +659,26 @@ describe('Class: Tracer', () => { // Act & Assess await expect(new Lambda().handler({}, context, () => console.log('Lambda invoked!'))).rejects.toThrowError(Error); expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); - expect(newSubsegment).toEqual(expect.objectContaining({ - name: '## index.handler', - })); - expect('cause' in newSubsegment).toBe(false); expect(addErrorFlagSpy).toHaveBeenCalledTimes(1); expect(addErrorSpy).toHaveBeenCalledTimes(0); - expect.assertions(6); + expect.assertions(4); delete process.env.POWERTOOLS_TRACER_CAPTURE_ERROR; }); - test('when used as decorator and with standard config, it captures the exception correctly', async () => { + test('when used as decorator and with standard config, it captures the exception', async () => { // Prepare const tracer: Tracer = new Tracer(); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - jest.spyOn(tracer.provider, 'getSegment') - .mockImplementationOnce(() => new Segment('facade', process.env._X_AMZN_TRACE_ID || null)) - .mockImplementation(() => newSubsegment); - /* jest.spyOn(tracer.provider, 'captureAsyncFunc').mockImplementation( - () => tracer.provider.captureAsyncFunc('## index.handler', )); */ - setContextMissingStrategy(() => null); const captureAsyncFuncSpy = jest.spyOn(tracer.provider, 'captureAsyncFunc'); + const addErrorAsMetadataSpy = jest.spyOn(tracer, 'addErrorAsMetadata'); + const newSubsegment = new Subsegment('### dummyMethod'); + jest.spyOn(tracer, 'getSegment') + .mockImplementation(() => newSubsegment); + const addErrorFlagSpy = jest.spyOn(newSubsegment, 'addErrorFlag'); const addErrorSpy = jest.spyOn(newSubsegment, 'addError'); + class Lambda implements LambdaInterface { @tracer.captureLambdaHandler() @@ -825,28 +693,21 @@ describe('Class: Tracer', () => { // Act & Assess await expect(new Lambda().handler({}, context, () => console.log('Lambda invoked!'))).rejects.toThrowError(Error); expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); - expect(newSubsegment).toEqual(expect.objectContaining({ - name: '## index.handler', - })); - expect('cause' in newSubsegment).toBe(true); + expect(addErrorAsMetadataSpy).toHaveBeenCalledTimes(1); + expect(addErrorAsMetadataSpy).toHaveBeenCalledWith(expect.any(Error)); + expect(addErrorFlagSpy).toHaveBeenCalledTimes(0); expect(addErrorSpy).toHaveBeenCalledTimes(1); - expect(addErrorSpy).toHaveBeenCalledWith(new Error('Exception thrown!'), false); expect.assertions(6); }); - test('when used as decorator and with standard config, it annotates ColdStart correctly', async () => { + test('when used as decorator and with standard config, it annotates ColdStart', async () => { // Prepare const tracer: Tracer = new Tracer(); - const newSubsegmentFirstInvocation: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - const newSubsegmentSecondInvocation: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - jest.spyOn(tracer.provider, 'getSegment') - .mockImplementationOnce(() => newSubsegmentFirstInvocation) - .mockImplementation(() => newSubsegmentSecondInvocation); - setContextMissingStrategy(() => null); const captureAsyncFuncSpy = createCaptureAsyncFuncMock(tracer.provider); - const putAnnotationSpy = jest.spyOn(tracer, 'putAnnotation'); + const annotateColdStartSpy = jest.spyOn(tracer, 'annotateColdStart'); + class Lambda implements LambdaInterface { @tracer.captureLambdaHandler() @@ -862,41 +723,21 @@ describe('Class: Tracer', () => { // Act await new Lambda().handler(event, context, () => console.log('Lambda invoked!')); - await new Lambda().handler(event, context, () => console.log('Lambda invoked!')); // Assess - expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(2); + expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); expect(captureAsyncFuncSpy).toHaveBeenCalledWith('## index.handler', expect.anything()); - expect(putAnnotationSpy.mock.calls.filter(call => - call[0] === 'ColdStart' - )).toEqual([ - [ 'ColdStart', true ], - [ 'ColdStart', false ], - ]); - expect(newSubsegmentFirstInvocation).toEqual(expect.objectContaining({ - name: '## index.handler', - annotations: expect.objectContaining({ - 'ColdStart': true, - }) - })); - expect(newSubsegmentSecondInvocation).toEqual(expect.objectContaining({ - name: '## index.handler', - annotations: expect.objectContaining({ - 'ColdStart': false, - }) - })); + expect(annotateColdStartSpy).toHaveBeenCalledTimes(1); }); - test('when used as decorator and with standard config, it annotates Service correctly', async () => { + test('when used as decorator and with standard config, it adds the Service annotation', async () => { // Prepare const tracer: Tracer = new Tracer(); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - jest.spyOn(tracer.provider, 'getSegment') - .mockImplementation(() => newSubsegment); - setContextMissingStrategy(() => null); const captureAsyncFuncSpy = createCaptureAsyncFuncMock(tracer.provider); + const addServiceNameAnnotationSpy = jest.spyOn(tracer, 'addServiceNameAnnotation'); + class Lambda implements LambdaInterface { @tracer.captureLambdaHandler() @@ -916,12 +757,8 @@ describe('Class: Tracer', () => { // Assess expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); expect(captureAsyncFuncSpy).toHaveBeenCalledWith('## index.handler', expect.anything()); - expect(newSubsegment).toEqual(expect.objectContaining({ - name: '## index.handler', - annotations: expect.objectContaining({ - 'Service': 'hello-world', - }) - })); + // The first call is for the Cold Start annotation + expect(addServiceNameAnnotationSpy).toHaveBeenCalledTimes(1); }); @@ -1045,12 +882,9 @@ describe('Class: Tracer', () => { // 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'); + const addResponseAsMetadataSpy = jest.spyOn(tracer, 'addResponseAsMetadata'); + class Lambda implements LambdaInterface { @tracer.captureMethod() @@ -1074,14 +908,8 @@ describe('Class: Tracer', () => { // Assess expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); expect(captureAsyncFuncSpy).toHaveBeenCalledWith('### dummyMethod', expect.anything()); - expect(newSubsegment).toEqual(expect.objectContaining({ - name: '### dummyMethod', - metadata: { - 'hello-world': { - 'dummyMethod response': 'foo bar', - }, - } - })); + expect(addResponseAsMetadataSpy).toHaveBeenCalledTimes(1); + expect(addResponseAsMetadataSpy).toHaveBeenCalledWith('foo bar', 'dummyMethod'); }); @@ -1089,12 +917,9 @@ describe('Class: Tracer', () => { // 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'); + const addResponseAsMetadataSpy = jest.spyOn(tracer, 'addResponseAsMetadata'); + class Lambda implements LambdaInterface { @tracer.captureMethod({ captureResponse: false }) @@ -1118,16 +943,7 @@ describe('Class: Tracer', () => { // Assess expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); expect(captureAsyncFuncSpy).toHaveBeenCalledWith('### dummyMethod', expect.anything()); - expect(newSubsegment).toEqual(expect.objectContaining({ - name: '### dummyMethod', - })); - expect(newSubsegment).not.toEqual(expect.objectContaining({ - metadata: { - 'hello-world': { - 'dummyMethod response': 'foo bar', - }, - } - })); + expect(addResponseAsMetadataSpy).toHaveBeenCalledTimes(0); }); @@ -1135,12 +951,9 @@ describe('Class: Tracer', () => { // 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'); + const addResponseAsMetadataSpy = jest.spyOn(tracer, 'addResponseAsMetadata'); + class Lambda implements LambdaInterface { @tracer.captureMethod() @@ -1164,14 +977,7 @@ describe('Class: Tracer', () => { // Assess expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); expect(captureAsyncFuncSpy).toHaveBeenCalledWith('### dummyMethod', expect.anything()); - expect(newSubsegment).toEqual(expect.objectContaining({ - name: '### dummyMethod', - metadata: { - 'hello-world': { - 'dummyMethod response': 'foo bar', - }, - } - })); + expect(addResponseAsMetadataSpy).toHaveBeenCalledTimes(1); }); @@ -1340,11 +1146,7 @@ describe('Class: Tracer', () => { // 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); + const captureAsyncFuncSpy = jest.spyOn(tracer.provider, 'captureAsyncFunc'); // Creating custom external decorator // eslint-disable-next-line func-style @@ -1386,14 +1188,7 @@ describe('Class: Tracer', () => { 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', - }, - } - })); + expect(captureAsyncFuncSpy).toHaveBeenCalledWith('### dummyMethod', expect.any(Function)); }); diff --git a/packages/tracer/tests/unit/middy.test.ts b/packages/tracer/tests/unit/middy.test.ts index f1b33b01be..6b8a2ca7b3 100644 --- a/packages/tracer/tests/unit/middy.test.ts +++ b/packages/tracer/tests/unit/middy.test.ts @@ -90,21 +90,18 @@ describe('Middy middleware', () => { // Prepare process.env.POWERTOOLS_TRACER_CAPTURE_RESPONSE = 'false'; const tracer: Tracer = new Tracer(); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - const setSegmentSpy = jest.spyOn(tracer.provider, 'setSegment').mockImplementation(); - jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => newSubsegment); - setContextMissingStrategy(() => null); - const lambdaHandler: Handler = async (_event: unknown, _context: Context) => ({ + jest.spyOn(tracer.provider, 'setSegment').mockImplementation(); + const putMetadataSpy = jest.spyOn(tracer, 'putMetadata'); + + const handler = middy(async (_event: unknown, _context: Context) => ({ foo: 'bar' - }); - const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer)); + })).use(captureLambdaHandler(tracer)); // Act - await handler({}, context, () => console.log('Lambda invoked!')); + await handler({}, context); // Assess - expect(setSegmentSpy).toHaveBeenCalledTimes(2); - expect('metadata' in newSubsegment).toBe(false); + expect(putMetadataSpy).toHaveBeenCalledTimes(0); delete process.env.POWERTOOLS_TRACER_CAPTURE_RESPONSE; }); @@ -113,21 +110,18 @@ describe('Middy middleware', () => { // Prepare const tracer: Tracer = new Tracer(); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - const setSegmentSpy = jest.spyOn(tracer.provider, 'setSegment').mockImplementation(); - jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => newSubsegment); - setContextMissingStrategy(() => null); - const lambdaHandler: Handler = async (_event: unknown, _context: Context) => ({ + jest.spyOn(tracer.provider, 'setSegment').mockImplementation(); + const putMetadataSpy = jest.spyOn(tracer, 'putMetadata'); + + const handler = middy(async (_event: unknown, _context: Context) => ({ foo: 'bar' - }); - const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer, { captureResponse: false })); + })).use(captureLambdaHandler(tracer, { captureResponse: false })); // Act - await handler({}, context, () => console.log('Lambda invoked!')); + await handler({}, context); // Assess - expect(setSegmentSpy).toHaveBeenCalledTimes(2); - expect('metadata' in newSubsegment).toBe(false); + expect(putMetadataSpy).toHaveBeenCalledTimes(0); }); @@ -135,30 +129,19 @@ describe('Middy middleware', () => { // Prepare const tracer: Tracer = new Tracer(); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - const setSegmentSpy = jest.spyOn(tracer.provider, 'setSegment').mockImplementation(); - jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => newSubsegment); - setContextMissingStrategy(() => null); - const lambdaHandler: Handler = async (_event: unknown, _context: Context) => ({ + jest.spyOn(tracer.provider, 'setSegment').mockImplementation(); + const putMetadataSpy = jest.spyOn(tracer, 'putMetadata'); + + const handler = middy(async (_event: unknown, _context: Context) => ({ foo: 'bar' - }); - const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer, { captureResponse: true })); + })).use(captureLambdaHandler(tracer, { captureResponse: true })); // Act - await handler({}, context, () => console.log('Lambda invoked!')); + await handler({}, context); // Assess - expect(setSegmentSpy).toHaveBeenCalledTimes(2); - expect(newSubsegment).toEqual(expect.objectContaining({ - name: '## index.handler', - metadata: { - 'hello-world': { - 'index.handler response': { - foo: 'bar', - }, - }, - } - })); + expect(putMetadataSpy).toHaveBeenCalledTimes(1); + expect(putMetadataSpy).toHaveBeenCalledWith('index.handler response', { foo: 'bar' }); }); @@ -166,30 +149,19 @@ describe('Middy middleware', () => { // Prepare const tracer: Tracer = new Tracer(); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - const setSegmentSpy = jest.spyOn(tracer.provider, 'setSegment').mockImplementation(); - jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => newSubsegment); - setContextMissingStrategy(() => null); - const lambdaHandler: Handler = async (_event: unknown, _context: Context) => ({ + jest.spyOn(tracer.provider, 'setSegment').mockImplementation(); + const putMetadataSpy = jest.spyOn(tracer, 'putMetadata'); + + const handler = middy(async (_event: unknown, _context: Context) => ({ foo: 'bar' - }); - const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer)); + })).use(captureLambdaHandler(tracer)); // Act - await handler({}, context, () => console.log('Lambda invoked!')); + await handler({}, context); // Assess - expect(setSegmentSpy).toHaveBeenCalledTimes(2); - expect(newSubsegment).toEqual(expect.objectContaining({ - name: '## index.handler', - metadata: { - 'hello-world': { - 'index.handler response': { - foo: 'bar', - }, - }, - } - })); + expect(putMetadataSpy).toHaveBeenCalledTimes(1); + expect(putMetadataSpy).toHaveBeenCalledWith('index.handler response', { foo: 'bar' }); }); @@ -251,46 +223,27 @@ describe('Middy middleware', () => { // Prepare const tracer: Tracer = new Tracer(); - const facadeSegment = new Segment('facade'); - const newSubsegmentFirstInvocation: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - const newSubsegmentSecondInvocation: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - const setSegmentSpy = jest.spyOn(tracer.provider, 'setSegment').mockImplementation(); + jest.spyOn(tracer.provider, 'setSegment').mockImplementation(() => ({})); jest.spyOn(tracer.provider, 'getSegment') - .mockImplementationOnce(() => facadeSegment) - .mockImplementationOnce(() => newSubsegmentFirstInvocation) - .mockImplementationOnce(() => facadeSegment) - .mockImplementation(() => newSubsegmentSecondInvocation); - setContextMissingStrategy(() => null); + .mockImplementationOnce(() => new Segment('facade')) + .mockImplementationOnce(() => new Subsegment('## index.handler')) + .mockImplementationOnce(() => new Segment('facade')) + .mockImplementation(() => new Subsegment('## index.handler')); const putAnnotationSpy = jest.spyOn(tracer, 'putAnnotation'); - const lambdaHandler: Handler = async (_event: unknown, _context: Context) => ({ + + const handler = middy(async (_event: unknown, _context: Context) => ({ foo: 'bar' - }); - const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer)); + })).use(captureLambdaHandler(tracer)); // Act - await handler({}, context, () => console.log('Lambda invoked!')); - await handler({}, context, () => console.log('Lambda invoked!')); + await handler({}, context); + await handler({}, context); // Assess - expect(setSegmentSpy).toHaveBeenCalledTimes(4); - expect(putAnnotationSpy.mock.calls.filter(call => - call[0] === 'ColdStart' - )).toEqual([ - [ 'ColdStart', true ], - [ 'ColdStart', false ], - ]); - expect(newSubsegmentFirstInvocation).toEqual(expect.objectContaining({ - name: '## index.handler', - annotations: expect.objectContaining({ - 'ColdStart': true, - }) - })); - expect(newSubsegmentSecondInvocation).toEqual(expect.objectContaining({ - name: '## index.handler', - annotations: expect.objectContaining({ - 'ColdStart': false, - }) - })); + // 2x Cold Start + 2x Service + expect(putAnnotationSpy).toHaveBeenCalledTimes(4); + expect(putAnnotationSpy).toHaveBeenNthCalledWith(1, 'ColdStart', true); + expect(putAnnotationSpy).toHaveBeenNthCalledWith(3, 'ColdStart', false); }); @@ -298,35 +251,23 @@ describe('Middy middleware', () => { // Prepare const tracer: Tracer = new Tracer(); - const facadeSegment = new Segment('facade'); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - const setSegmentSpy = jest.spyOn(tracer.provider, 'setSegment').mockImplementation(); + jest.spyOn(tracer.provider, 'setSegment').mockImplementation(() => ({})); jest.spyOn(tracer.provider, 'getSegment') - .mockImplementationOnce(() => facadeSegment) - .mockImplementation(() => newSubsegment); - setContextMissingStrategy(() => null); - const lambdaHandler: Handler = async (_event: unknown, _context: Context) => ({ + .mockImplementationOnce(() => new Segment('facade')) + .mockImplementation(() => new Subsegment('## index.handler')); + const putAnnotationSpy = jest.spyOn(tracer, 'putAnnotation'); + + const handler = middy(async (_event: unknown, _context: Context) => ({ foo: 'bar' - }); - const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer)); + })).use(captureLambdaHandler(tracer)); // Act - await handler({}, context, () => console.log('Lambda invoked!')); + await handler({}, context); // Assess - expect(setSegmentSpy).toHaveBeenCalledTimes(2); - expect(setSegmentSpy.mock.calls.map(arg => ({ - name: arg[0].name, - }))).toEqual([ - expect.objectContaining({ name: '## index.handler' }), - expect.objectContaining({ name: 'facade' }), - ]); - expect(newSubsegment).toEqual(expect.objectContaining({ - name: '## index.handler', - annotations: expect.objectContaining({ - 'Service': 'hello-world', - }) - })); + // The first call is for the Cold Start annotation + expect(putAnnotationSpy).toHaveBeenCalledTimes(2); + expect(putAnnotationSpy).toHaveBeenNthCalledWith(2, 'Service', 'hello-world'); }); From 720d8a3b1ef5ddbc4a1ccf01d8cd18bb592c9185 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Mon, 20 Mar 2023 13:32:44 +0100 Subject: [PATCH 21/26] docs(parameters): write utility readme update main one (#1280) * chore: update README files to include Parameters util * chore: updated milestone link * chore: update tagline in README * Update packages/commons/README.md * Update README.md --- README.md | 3 ++ packages/commons/README.md | 3 ++ packages/idempotency/README.md | 9 ++-- packages/logger/README.md | 3 ++ packages/metrics/README.md | 3 ++ packages/parameters/README.md | 90 ++++++++++++++++++++++++++++++++++ packages/tracer/README.md | 3 ++ 7 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 packages/parameters/README.md diff --git a/README.md b/README.md index 213d08a1b5..cf5930b25c 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ You can use the library in both TypeScript and JavaScript code bases. * **[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) +* **[Parameters (beta)](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/)** - High-level functions to retrieve one or more parameters from AWS SSM Parameter Store, AWS Secrets Manager, AWS AppConfig, and Amazon DynamoDB ## Getting started @@ -66,6 +67,8 @@ Or refer to the installation guide of each utility: πŸ‘‰ [Installation guide for the **Metrics** utility](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/core/metrics#getting-started) +πŸ‘‰ [Installation guide for the **Parameters** utility](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/#getting-started) + ### Examples * [CDK](https://github.com/awslabs/aws-lambda-powertools-typescript/tree/main/examples/cdk) diff --git a/packages/commons/README.md b/packages/commons/README.md index c319faf205..1ae29b9561 100644 --- a/packages/commons/README.md +++ b/packages/commons/README.md @@ -27,6 +27,7 @@ You can use the library in both TypeScript and JavaScript code bases. * **[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) +* **[Parameters (beta)](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/)** - High-level functions to retrieve one or more parameters from AWS SSM, Secrets Manager, AppConfig, and DynamoDB ## Getting started @@ -52,6 +53,8 @@ Or refer to the installation guide of each utility: πŸ‘‰ [Installation guide for the **Metrics** utility](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/core/metrics#getting-started) +πŸ‘‰ [Installation guide for the **Parameters** utility](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/#getting-started) + ### Examples * [CDK](https://github.com/awslabs/aws-lambda-powertools-typescript/tree/main/examples/cdk) diff --git a/packages/idempotency/README.md b/packages/idempotency/README.md index 98355a19e5..72111def9a 100644 --- a/packages/idempotency/README.md +++ b/packages/idempotency/README.md @@ -1,4 +1,4 @@ -# AWS Lambda Powertools for TypeScript +# AWS Lambda Powertools for TypeScript Powertools is a developer toolkit to implement Serverless [best practices and increase developer velocity](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/#features). @@ -8,13 +8,13 @@ AWS Lambda Powertools for [Python](https://github.com/awslabs/aws-lambda-powerto **[πŸ“œ 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 +## Table of contents - [Features](#features) - [Getting started](#getting-started) - [Installation](#installation) - [Examples](#examples) - - [Serverless TypeScript Demo](#serverless-typescript-demo-application) + - [Serverless TypeScript Demo application](#serverless-typescript-demo-application) - [Contribute](#contribute) - [Roadmap](#roadmap) - [Connect](#connect) @@ -26,6 +26,7 @@ AWS Lambda Powertools for [Python](https://github.com/awslabs/aws-lambda-powerto * **[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) +* **[Parameters (beta)](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/)** - High-level functions to retrieve one or more parameters from AWS SSM, Secrets Manager, AppConfig, and DynamoDB ## Getting started @@ -50,6 +51,8 @@ Or refer to the installation guide of each utility: πŸ‘‰ [Installation guide for the **Metrics** utility](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/core/metrics#getting-started) +πŸ‘‰ [Installation guide for the **Parameters** utility](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/#getting-started) + ### Examples * [CDK](https://github.com/awslabs/aws-lambda-powertools-typescript/tree/main/examples/cdk) diff --git a/packages/logger/README.md b/packages/logger/README.md index 972bd4af2f..ed42dc21bf 100644 --- a/packages/logger/README.md +++ b/packages/logger/README.md @@ -26,6 +26,7 @@ You can use the library in both TypeScript and JavaScript code bases. * **[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) +* **[Parameters (beta)](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/)** - High-level functions to retrieve one or more parameters from AWS SSM, Secrets Manager, AppConfig, and DynamoDB ## Getting started @@ -51,6 +52,8 @@ Or refer to the installation guide of each utility: πŸ‘‰ [Installation guide for the **Metrics** utility](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/core/metrics#getting-started) +πŸ‘‰ [Installation guide for the **Parameters** utility](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/#getting-started) + ### Examples * [CDK](https://github.com/awslabs/aws-lambda-powertools-typescript/tree/main/examples/cdk) diff --git a/packages/metrics/README.md b/packages/metrics/README.md index 8344b0262c..071cec0567 100644 --- a/packages/metrics/README.md +++ b/packages/metrics/README.md @@ -26,6 +26,7 @@ You can use the library in both TypeScript and JavaScript code bases. * **[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) +* **[Parameters (beta)](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/)** - High-level functions to retrieve one or more parameters from AWS SSM, Secrets Manager, AppConfig, and DynamoDB ## Getting started @@ -50,6 +51,8 @@ Or refer to the installation guide of each utility: πŸ‘‰ [Installation guide for the **Metrics** utility](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/core/metrics#getting-started) +πŸ‘‰ [Installation guide for the **Parameters** utility](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/#getting-started) + ### Examples * [CDK](https://github.com/awslabs/aws-lambda-powertools-typescript/tree/main/examples/cdk) diff --git a/packages/parameters/README.md b/packages/parameters/README.md new file mode 100644 index 0000000000..ae056f76b4 --- /dev/null +++ b/packages/parameters/README.md @@ -0,0 +1,90 @@ +# AWS Lambda Powertools for TypeScript + +| ⚠️ **WARNING: Do not use this utility in production just yet!** ⚠️ | +| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| This AWS Lambda Powertools for TypeScript utility is currently released as beta developer preview and is intended strictly for feedback and testing purposes only.
This version is not stable, and significant breaking changes might incur before going [before the GA release](https://github.com/awslabs/aws-lambda-powertools-typescript/milestone/10). | _ | + +Powertools is a developer toolkit to implement Serverless [best practices and increase developer velocity](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/#features). + +You can use the library in both TypeScript and JavaScript code bases. + +> Also available in [Python](https://github.com/awslabs/aws-lambda-powertools-python), [Java](https://github.com/awslabs/aws-lambda-powertools-java), and [.NET](https://awslabs.github.io/aws-lambda-powertools-dotnet/). + +**[Documentation](https://awslabs.github.io/aws-lambda-powertools-typescript/)** | **[npm](https://www.npmjs.com/org/aws-lambda-powertools)** | **[Roadmap](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/roadmap)** | **[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 application](#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) +* **[Parameters (beta)](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/)** - High-level functions to retrieve one or more parameters from AWS SSM, Secrets Manager, AppConfig, and DynamoDB + +## 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) + +πŸ‘‰ [Installation guide for the **Parameters** utility](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/#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. diff --git a/packages/tracer/README.md b/packages/tracer/README.md index 8344b0262c..071cec0567 100644 --- a/packages/tracer/README.md +++ b/packages/tracer/README.md @@ -26,6 +26,7 @@ You can use the library in both TypeScript and JavaScript code bases. * **[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) +* **[Parameters (beta)](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/)** - High-level functions to retrieve one or more parameters from AWS SSM, Secrets Manager, AppConfig, and DynamoDB ## Getting started @@ -50,6 +51,8 @@ Or refer to the installation guide of each utility: πŸ‘‰ [Installation guide for the **Metrics** utility](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/core/metrics#getting-started) +πŸ‘‰ [Installation guide for the **Parameters** utility](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/#getting-started) + ### Examples * [CDK](https://github.com/awslabs/aws-lambda-powertools-typescript/tree/main/examples/cdk) From d5e2aea52626832eef9919ad7ff07accae2a9160 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Mon, 20 Mar 2023 14:40:50 +0100 Subject: [PATCH 22/26] chore(parameters): add package to release process (#1377) * chore: added release prepack script * chore: add parameters to lerna config * fix: version variable * fix: version suffic --- .github/scripts/release_patch_package_json.js | 85 +++++++++++++++++++ lerna.json | 1 + packages/parameters/package.json | 8 +- 3 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 .github/scripts/release_patch_package_json.js diff --git a/.github/scripts/release_patch_package_json.js b/.github/scripts/release_patch_package_json.js new file mode 100644 index 0000000000..7900a1a9a5 --- /dev/null +++ b/.github/scripts/release_patch_package_json.js @@ -0,0 +1,85 @@ +const { readFileSync, writeFileSync } = require('node:fs'); + +const outDir = './lib'; +const betaPackages = [ + '@aws-lambda-powertools/parameters', +]; + +/** + * This script is used to create a new package.json file for the tarball that will be published to npm. + * + * The original package.json file is read and the following fields are extracted: + * - name + * - version + * - description + * - author + * - license + * - homepage + * - repository + * - bugs + * - keywords + * - dependencies + * - devDependencies + * + * For beta packages, the version number is updated to include a beta suffix. + * + * The new package.json file is written to the lib folder, which is the folder that will be packed and published to npm. + * For beta packages, the original package.json file is also temporarily updated with the new beta version number so that + * the version number in the registry is correct and matches the tarball. + */ +(() => { + try { + // Read the original package.json file + const pkgJson = JSON.parse(readFileSync('./package.json', 'utf8')) + // Extract the fields we want to keep + const { + name, + version: originalVersion, + description, + author, + license, + homepage, + repository, + bugs, + keywords, + dependencies, + devDependencies, + } = pkgJson; + + let version = originalVersion; + // Add a beta suffix to the version + if (betaPackages.includes(name)) { + version = `${version}-beta`; + } + + // Create a new package.json file with the updated version for the tarball + const newPkgJson = { + name, + version, + description, + author, + license, + homepage, + repository, + bugs, + keywords, + dependencies, + devDependencies, + main: './index.js', + types: './index.d.ts', + }; + + // Write the new package.json file inside the folder that will be packed + writeFileSync(`${outDir}/package.json`, JSON.stringify(newPkgJson, null, 2)); + + if (betaPackages.includes(name)) { + // Temporarily update the original package.json file with the new beta version. + // This version number will be picked up during the `npm publish` step, so that + // the version number in the registry is correct and matches the tarball. + // The original package.json file will be restored by lerna after the publish step. + writeFileSync('package.json', JSON.stringify({ ...pkgJson, version }, null, 2)); + } + } catch (err) { + throw err; + } +})(); diff --git a/lerna.json b/lerna.json index ea913fecdd..83f9e908c0 100644 --- a/lerna.json +++ b/lerna.json @@ -4,6 +4,7 @@ "packages/tracer", "packages/logger", "packages/metrics", + "packages/parameters", "examples/cdk", "examples/sam", "layers" diff --git a/packages/parameters/package.json b/packages/parameters/package.json index bee6d71f11..55f6381ac6 100644 --- a/packages/parameters/package.json +++ b/packages/parameters/package.json @@ -1,13 +1,14 @@ { "name": "@aws-lambda-powertools/parameters", - "version": "1.5.0", + "version": "1.6.0", "description": "The parameters package for the AWS Lambda Powertools for TypeScript library", "author": { "name": "Amazon Web Services", "url": "https://aws.amazon.com" }, "publishConfig": { - "access": "public" + "access": "public", + "directory": "lib" }, "scripts": { "commit": "commit", @@ -24,7 +25,8 @@ "package": "mkdir -p dist/ && npm pack && mv *.tgz dist/", "package-bundle": "../../package-bundler.sh parameters-bundle ./dist", "prepare": "npm run build", - "postversion": "git push --tags" + "postversion": "git push --tags", + "prepack": "cp ../../.github/scripts/release_patch_package_json.js . && node release_patch_package_json.js" }, "lint-staged": { "*.ts": "npm run lint-fix" From 1ca50683fc7bd1ccdec3a08c33fe210ad0a4cf81 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Mon, 20 Mar 2023 14:42:35 +0100 Subject: [PATCH 23/26] chore: added parameters to docs navbar --- mkdocs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index bab9c616bb..623be28a6d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,6 +13,8 @@ nav: - core/tracer.md - core/logger.md - core/metrics.md + - Utilities: + - utilities/parameters.md theme: name: material From 38f64b114cabb57bf6dc7cd401139c5d6ce9427c Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Mon, 20 Mar 2023 15:29:15 +0100 Subject: [PATCH 24/26] chore: add publishConfig modifier to non utilities --- examples/cdk/package.json | 3 +++ examples/sam/package.json | 3 +++ layers/package.json | 3 +++ 3 files changed, 9 insertions(+) diff --git a/examples/cdk/package.json b/examples/cdk/package.json index 7f2dfa61c0..5916642773 100644 --- a/examples/cdk/package.json +++ b/examples/cdk/package.json @@ -5,6 +5,9 @@ "name": "Amazon Web Services", "url": "https://aws.amazon.com" }, + "publishConfig": { + "access": "restricted" + }, "description": "This project contains source code and supporting files for a serverless application that you can deploy with CDK.", "license": "MIT-0", "bin": { diff --git a/examples/sam/package.json b/examples/sam/package.json index 837a1198f3..9add211e14 100644 --- a/examples/sam/package.json +++ b/examples/sam/package.json @@ -5,6 +5,9 @@ "name": "Amazon Web Services", "url": "https://aws.amazon.com" }, + "publishConfig": { + "access": "restricted" + }, "description": "This project contains source code and supporting files for a serverless application that you can deploy with the [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html). The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API.", "license": "MIT-0", "scripts": { diff --git a/layers/package.json b/layers/package.json index 4178519667..bb60235dfc 100644 --- a/layers/package.json +++ b/layers/package.json @@ -4,6 +4,9 @@ "bin": { "layer": "bin/layers.js" }, + "publishConfig": { + "access": "restricted" + }, "description": "This CDK app is meant to be used to publish Powertools for TypeScript Lambda Layer. It is composed of a single stack deploying the Layer into the target account.", "scripts": { "build": "tsc", From 09fea8f82d775f01a2bbf75f161f1d8bddb061cc Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Mon, 20 Mar 2023 15:33:17 +0100 Subject: [PATCH 25/26] chore: add private modifier to non utilities --- examples/cdk/package.json | 4 +--- examples/sam/package.json | 4 +--- layers/package.json | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/examples/cdk/package.json b/examples/cdk/package.json index 5916642773..024c1e0293 100644 --- a/examples/cdk/package.json +++ b/examples/cdk/package.json @@ -5,9 +5,7 @@ "name": "Amazon Web Services", "url": "https://aws.amazon.com" }, - "publishConfig": { - "access": "restricted" - }, + "private": true, "description": "This project contains source code and supporting files for a serverless application that you can deploy with CDK.", "license": "MIT-0", "bin": { diff --git a/examples/sam/package.json b/examples/sam/package.json index 9add211e14..dbb3e400f6 100644 --- a/examples/sam/package.json +++ b/examples/sam/package.json @@ -5,9 +5,7 @@ "name": "Amazon Web Services", "url": "https://aws.amazon.com" }, - "publishConfig": { - "access": "restricted" - }, + "private": true, "description": "This project contains source code and supporting files for a serverless application that you can deploy with the [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html). The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API.", "license": "MIT-0", "scripts": { diff --git a/layers/package.json b/layers/package.json index bb60235dfc..8bb6f1cd37 100644 --- a/layers/package.json +++ b/layers/package.json @@ -4,9 +4,7 @@ "bin": { "layer": "bin/layers.js" }, - "publishConfig": { - "access": "restricted" - }, + "private": true, "description": "This CDK app is meant to be used to publish Powertools for TypeScript Lambda Layer. It is composed of a single stack deploying the Layer into the target account.", "scripts": { "build": "tsc", From 6c7bf23b7dcaacb8877ccea65bb537bbbed78441 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 20 Mar 2023 14:37:34 +0000 Subject: [PATCH 26/26] chore(release): v1.7.0 [skip ci] --- CHANGELOG.md | 18 ++++++++ examples/cdk/CHANGELOG.md | 8 ++++ examples/cdk/package-lock.json | 70 ++++++++++++++++---------------- examples/cdk/package.json | 10 ++--- examples/sam/CHANGELOG.md | 8 ++++ examples/sam/package-lock.json | 70 ++++++++++++++++---------------- examples/sam/package.json | 10 ++--- layers/CHANGELOG.md | 8 ++++ layers/package.json | 4 +- lerna.json | 2 +- packages/commons/CHANGELOG.md | 11 +++++ packages/commons/package.json | 4 +- packages/logger/CHANGELOG.md | 11 +++++ packages/logger/package.json | 6 +-- packages/metrics/CHANGELOG.md | 11 +++++ packages/metrics/package.json | 6 +-- packages/parameters/CHANGELOG.md | 11 +++++ packages/parameters/package.json | 4 +- packages/tracer/CHANGELOG.md | 8 ++++ packages/tracer/package.json | 4 +- 20 files changed, 189 insertions(+), 95 deletions(-) create mode 100644 packages/parameters/CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md index a8e61293d5..20abb82190 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,24 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.7.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.6.0...v1.7.0) (2023-03-20) + + +### Bug Fixes + +* **docs:** typo in layer arn ([bc5f7c9](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/bc5f7c99e02396223e726962432fc3856a68a29d)) + + +### Features + +* **logger:** add silent log level to suppress the emission of all logs ([#1347](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1347)) ([c82939e](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/c82939ebdb82ae596cbad07be397794ee4b69fe5)) +* **metrics:** support high resolution metrics ([#1369](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1369)) ([79a321b](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/79a321b199ef51a024dc25b83673baf2eb03de69)) +* **parameters:** AppConfigProvider to return the last valid value when the API returns empty value on subsequent calls ([#1365](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1365)) ([97339d9](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/97339d9336ec67568e9e7fd079b3cfe006da1bba)) + + + + + # [1.6.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.5.1...v1.6.0) (2023-03-02) diff --git a/examples/cdk/CHANGELOG.md b/examples/cdk/CHANGELOG.md index c740e87e5c..10f4d26952 100644 --- a/examples/cdk/CHANGELOG.md +++ b/examples/cdk/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.7.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.6.0...v1.7.0) (2023-03-20) + +**Note:** Version bump only for package cdk-sample + + + + + # [1.6.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.5.1...v1.6.0) (2023-03-02) **Note:** Version bump only for package cdk-sample diff --git a/examples/cdk/package-lock.json b/examples/cdk/package-lock.json index ad83490a26..ea34d58256 100644 --- a/examples/cdk/package-lock.json +++ b/examples/cdk/package-lock.json @@ -1,12 +1,12 @@ { "name": "cdk-sample", - "version": "1.6.0", + "version": "1.7.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cdk-sample", - "version": "1.6.0", + "version": "1.7.0", "license": "MIT-0", "dependencies": { "@middy/core": "^3.6.2", @@ -20,9 +20,9 @@ "cdk-app": "bin/cdk-app.js" }, "devDependencies": { - "@aws-lambda-powertools/logger": "^1.5.1", - "@aws-lambda-powertools/metrics": "^1.5.1", - "@aws-lambda-powertools/tracer": "^1.5.1", + "@aws-lambda-powertools/logger": "^1.6.0", + "@aws-lambda-powertools/metrics": "^1.6.0", + "@aws-lambda-powertools/tracer": "^1.6.0", "@aws-sdk/lib-dynamodb": "^3.231.0", "@types/aws-lambda": "^8.10.109", "@types/jest": "^29.2.4", @@ -165,37 +165,37 @@ "peer": true }, "node_modules/@aws-lambda-powertools/commons": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.5.1.tgz", - "integrity": "sha512-qp13/WksB6M/liEHgV3301qmI/aeQKmrYk1iODB+rrkVWeKW4AJqBMuWQ26gIlbD8t82EtuUNPUFle1a2qwjIg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.6.0.tgz", + "integrity": "sha512-83YWf63FZN872Bodk+tJHdSZ2o8y5zsAcpQZgBTqL8TEFNBef/zkDuIuvDIUBmif9q+ZaYVAEtileWjjDTvgGA==", "dev": true }, "node_modules/@aws-lambda-powertools/logger": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.5.1.tgz", - "integrity": "sha512-dXvw7c4Z0DUEY62zEWsQ4DQZ3yf+JsEAqojQH+kw+rXVv0cjnIEbM4ZKONPG2jcPS5IMzO6gmCbu/PKB1PYgyA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.6.0.tgz", + "integrity": "sha512-RIx8nHDH8exxPfMhkTnxWZMNOEOgf1CjkzbisLvX89/8Z0tMyruDFHdnolZXQ6Z1AyUE5FVaqN5FWlkmyqxS4A==", "dev": true, "dependencies": { - "@aws-lambda-powertools/commons": "^1.5.1", + "@aws-lambda-powertools/commons": "^1.6.0", "lodash.merge": "^4.6.2" } }, "node_modules/@aws-lambda-powertools/metrics": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.5.1.tgz", - "integrity": "sha512-kBvJR9phGdH5UqtcWyxVMQoZU57liQeVxZCFbDMSAhdlxBbW1pkPBASaKv/FLlX++Tb4TeO1Tj28dox1yL1t+w==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.6.0.tgz", + "integrity": "sha512-W/dyDxUyF9Vkhl37nMfNQZLjzS27bBji17nEAF9whqvpaaSJftKGdcS5ou/dxTsjcSh1sgB38uexW0EVgShwUQ==", "dev": true, "dependencies": { - "@aws-lambda-powertools/commons": "^1.5.1" + "@aws-lambda-powertools/commons": "^1.6.0" } }, "node_modules/@aws-lambda-powertools/tracer": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.5.1.tgz", - "integrity": "sha512-T/BHOzg+LbY5PF6drKuVhyk4tcAakyn8gI/lGrx8NhZ72vc0YZOCGaMhvUGbImoZ0UFyn5ApP0n70qvk1i1A/Q==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.6.0.tgz", + "integrity": "sha512-eAjw3kK6wC2QJ2zra9clDyC04Q28QgsFboCTMBAjmvxmJMH0IETmq+GxZ8HL8HSOkbCwxyd75ef0LBWFLfBxew==", "dev": true, "dependencies": { - "@aws-lambda-powertools/commons": "^1.5.1", + "@aws-lambda-powertools/commons": "^1.6.0", "aws-xray-sdk-core": "^3.4.0" } }, @@ -8187,37 +8187,37 @@ } }, "@aws-lambda-powertools/commons": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.5.1.tgz", - "integrity": "sha512-qp13/WksB6M/liEHgV3301qmI/aeQKmrYk1iODB+rrkVWeKW4AJqBMuWQ26gIlbD8t82EtuUNPUFle1a2qwjIg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.6.0.tgz", + "integrity": "sha512-83YWf63FZN872Bodk+tJHdSZ2o8y5zsAcpQZgBTqL8TEFNBef/zkDuIuvDIUBmif9q+ZaYVAEtileWjjDTvgGA==", "dev": true }, "@aws-lambda-powertools/logger": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.5.1.tgz", - "integrity": "sha512-dXvw7c4Z0DUEY62zEWsQ4DQZ3yf+JsEAqojQH+kw+rXVv0cjnIEbM4ZKONPG2jcPS5IMzO6gmCbu/PKB1PYgyA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.6.0.tgz", + "integrity": "sha512-RIx8nHDH8exxPfMhkTnxWZMNOEOgf1CjkzbisLvX89/8Z0tMyruDFHdnolZXQ6Z1AyUE5FVaqN5FWlkmyqxS4A==", "dev": true, "requires": { - "@aws-lambda-powertools/commons": "^1.5.1", + "@aws-lambda-powertools/commons": "^1.6.0", "lodash.merge": "^4.6.2" } }, "@aws-lambda-powertools/metrics": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.5.1.tgz", - "integrity": "sha512-kBvJR9phGdH5UqtcWyxVMQoZU57liQeVxZCFbDMSAhdlxBbW1pkPBASaKv/FLlX++Tb4TeO1Tj28dox1yL1t+w==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.6.0.tgz", + "integrity": "sha512-W/dyDxUyF9Vkhl37nMfNQZLjzS27bBji17nEAF9whqvpaaSJftKGdcS5ou/dxTsjcSh1sgB38uexW0EVgShwUQ==", "dev": true, "requires": { - "@aws-lambda-powertools/commons": "^1.5.1" + "@aws-lambda-powertools/commons": "^1.6.0" } }, "@aws-lambda-powertools/tracer": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.5.1.tgz", - "integrity": "sha512-T/BHOzg+LbY5PF6drKuVhyk4tcAakyn8gI/lGrx8NhZ72vc0YZOCGaMhvUGbImoZ0UFyn5ApP0n70qvk1i1A/Q==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.6.0.tgz", + "integrity": "sha512-eAjw3kK6wC2QJ2zra9clDyC04Q28QgsFboCTMBAjmvxmJMH0IETmq+GxZ8HL8HSOkbCwxyd75ef0LBWFLfBxew==", "dev": true, "requires": { - "@aws-lambda-powertools/commons": "^1.5.1", + "@aws-lambda-powertools/commons": "^1.6.0", "aws-xray-sdk-core": "^3.4.0" } }, diff --git a/examples/cdk/package.json b/examples/cdk/package.json index 024c1e0293..7487259b54 100644 --- a/examples/cdk/package.json +++ b/examples/cdk/package.json @@ -1,6 +1,6 @@ { "name": "cdk-sample", - "version": "1.6.0", + "version": "1.7.0", "author": { "name": "Amazon Web Services", "url": "https://aws.amazon.com" @@ -28,9 +28,9 @@ "*.ts": "npm run lint-fix" }, "devDependencies": { - "@aws-lambda-powertools/logger": "^1.5.1", - "@aws-lambda-powertools/metrics": "^1.5.1", - "@aws-lambda-powertools/tracer": "^1.5.1", + "@aws-lambda-powertools/logger": "^1.6.0", + "@aws-lambda-powertools/metrics": "^1.6.0", + "@aws-lambda-powertools/tracer": "^1.6.0", "@aws-sdk/lib-dynamodb": "^3.231.0", "@types/aws-lambda": "^8.10.109", "@types/jest": "^29.2.4", @@ -55,4 +55,4 @@ "phin": "^3.7.0", "source-map-support": "^0.5.21" } -} \ No newline at end of file +} diff --git a/examples/sam/CHANGELOG.md b/examples/sam/CHANGELOG.md index 880873e218..44bb5be04c 100644 --- a/examples/sam/CHANGELOG.md +++ b/examples/sam/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.7.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.6.0...v1.7.0) (2023-03-20) + +**Note:** Version bump only for package sam-example + + + + + # [1.6.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.5.1...v1.6.0) (2023-03-02) **Note:** Version bump only for package sam-example diff --git a/examples/sam/package-lock.json b/examples/sam/package-lock.json index 87988d76c1..df145bfcfd 100644 --- a/examples/sam/package-lock.json +++ b/examples/sam/package-lock.json @@ -1,17 +1,17 @@ { "name": "sam-example", - "version": "1.6.0", + "version": "1.7.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "sam-example", - "version": "1.6.0", + "version": "1.7.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/logger": "^1.5.1", - "@aws-lambda-powertools/metrics": "^1.5.1", - "@aws-lambda-powertools/tracer": "^1.5.1", + "@aws-lambda-powertools/logger": "^1.6.0", + "@aws-lambda-powertools/metrics": "^1.6.0", + "@aws-lambda-powertools/tracer": "^1.6.0", "@aws-sdk/client-dynamodb": "^3.231.0", "@aws-sdk/lib-dynamodb": "^3.231.0", "@middy/core": "^3.6.2", @@ -124,33 +124,33 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@aws-lambda-powertools/commons": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.5.1.tgz", - "integrity": "sha512-qp13/WksB6M/liEHgV3301qmI/aeQKmrYk1iODB+rrkVWeKW4AJqBMuWQ26gIlbD8t82EtuUNPUFle1a2qwjIg==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.6.0.tgz", + "integrity": "sha512-83YWf63FZN872Bodk+tJHdSZ2o8y5zsAcpQZgBTqL8TEFNBef/zkDuIuvDIUBmif9q+ZaYVAEtileWjjDTvgGA==" }, "node_modules/@aws-lambda-powertools/logger": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.5.1.tgz", - "integrity": "sha512-dXvw7c4Z0DUEY62zEWsQ4DQZ3yf+JsEAqojQH+kw+rXVv0cjnIEbM4ZKONPG2jcPS5IMzO6gmCbu/PKB1PYgyA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.6.0.tgz", + "integrity": "sha512-RIx8nHDH8exxPfMhkTnxWZMNOEOgf1CjkzbisLvX89/8Z0tMyruDFHdnolZXQ6Z1AyUE5FVaqN5FWlkmyqxS4A==", "dependencies": { - "@aws-lambda-powertools/commons": "^1.5.1", + "@aws-lambda-powertools/commons": "^1.6.0", "lodash.merge": "^4.6.2" } }, "node_modules/@aws-lambda-powertools/metrics": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.5.1.tgz", - "integrity": "sha512-kBvJR9phGdH5UqtcWyxVMQoZU57liQeVxZCFbDMSAhdlxBbW1pkPBASaKv/FLlX++Tb4TeO1Tj28dox1yL1t+w==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.6.0.tgz", + "integrity": "sha512-W/dyDxUyF9Vkhl37nMfNQZLjzS27bBji17nEAF9whqvpaaSJftKGdcS5ou/dxTsjcSh1sgB38uexW0EVgShwUQ==", "dependencies": { - "@aws-lambda-powertools/commons": "^1.5.1" + "@aws-lambda-powertools/commons": "^1.6.0" } }, "node_modules/@aws-lambda-powertools/tracer": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.5.1.tgz", - "integrity": "sha512-T/BHOzg+LbY5PF6drKuVhyk4tcAakyn8gI/lGrx8NhZ72vc0YZOCGaMhvUGbImoZ0UFyn5ApP0n70qvk1i1A/Q==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.6.0.tgz", + "integrity": "sha512-eAjw3kK6wC2QJ2zra9clDyC04Q28QgsFboCTMBAjmvxmJMH0IETmq+GxZ8HL8HSOkbCwxyd75ef0LBWFLfBxew==", "dependencies": { - "@aws-lambda-powertools/commons": "^1.5.1", + "@aws-lambda-powertools/commons": "^1.6.0", "aws-xray-sdk-core": "^3.4.0" } }, @@ -7472,33 +7472,33 @@ } }, "@aws-lambda-powertools/commons": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.5.1.tgz", - "integrity": "sha512-qp13/WksB6M/liEHgV3301qmI/aeQKmrYk1iODB+rrkVWeKW4AJqBMuWQ26gIlbD8t82EtuUNPUFle1a2qwjIg==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.6.0.tgz", + "integrity": "sha512-83YWf63FZN872Bodk+tJHdSZ2o8y5zsAcpQZgBTqL8TEFNBef/zkDuIuvDIUBmif9q+ZaYVAEtileWjjDTvgGA==" }, "@aws-lambda-powertools/logger": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.5.1.tgz", - "integrity": "sha512-dXvw7c4Z0DUEY62zEWsQ4DQZ3yf+JsEAqojQH+kw+rXVv0cjnIEbM4ZKONPG2jcPS5IMzO6gmCbu/PKB1PYgyA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.6.0.tgz", + "integrity": "sha512-RIx8nHDH8exxPfMhkTnxWZMNOEOgf1CjkzbisLvX89/8Z0tMyruDFHdnolZXQ6Z1AyUE5FVaqN5FWlkmyqxS4A==", "requires": { - "@aws-lambda-powertools/commons": "^1.5.1", + "@aws-lambda-powertools/commons": "^1.6.0", "lodash.merge": "^4.6.2" } }, "@aws-lambda-powertools/metrics": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.5.1.tgz", - "integrity": "sha512-kBvJR9phGdH5UqtcWyxVMQoZU57liQeVxZCFbDMSAhdlxBbW1pkPBASaKv/FLlX++Tb4TeO1Tj28dox1yL1t+w==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.6.0.tgz", + "integrity": "sha512-W/dyDxUyF9Vkhl37nMfNQZLjzS27bBji17nEAF9whqvpaaSJftKGdcS5ou/dxTsjcSh1sgB38uexW0EVgShwUQ==", "requires": { - "@aws-lambda-powertools/commons": "^1.5.1" + "@aws-lambda-powertools/commons": "^1.6.0" } }, "@aws-lambda-powertools/tracer": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.5.1.tgz", - "integrity": "sha512-T/BHOzg+LbY5PF6drKuVhyk4tcAakyn8gI/lGrx8NhZ72vc0YZOCGaMhvUGbImoZ0UFyn5ApP0n70qvk1i1A/Q==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.6.0.tgz", + "integrity": "sha512-eAjw3kK6wC2QJ2zra9clDyC04Q28QgsFboCTMBAjmvxmJMH0IETmq+GxZ8HL8HSOkbCwxyd75ef0LBWFLfBxew==", "requires": { - "@aws-lambda-powertools/commons": "^1.5.1", + "@aws-lambda-powertools/commons": "^1.6.0", "aws-xray-sdk-core": "^3.4.0" } }, diff --git a/examples/sam/package.json b/examples/sam/package.json index dbb3e400f6..dfafaa9fa0 100644 --- a/examples/sam/package.json +++ b/examples/sam/package.json @@ -1,6 +1,6 @@ { "name": "sam-example", - "version": "1.6.0", + "version": "1.7.0", "author": { "name": "Amazon Web Services", "url": "https://aws.amazon.com" @@ -39,12 +39,12 @@ "typescript": "^4.9.4" }, "dependencies": { - "@aws-lambda-powertools/logger": "^1.5.1", - "@aws-lambda-powertools/metrics": "^1.5.1", - "@aws-lambda-powertools/tracer": "^1.5.1", + "@aws-lambda-powertools/logger": "^1.6.0", + "@aws-lambda-powertools/metrics": "^1.6.0", + "@aws-lambda-powertools/tracer": "^1.6.0", "@aws-sdk/client-dynamodb": "^3.231.0", "@aws-sdk/lib-dynamodb": "^3.231.0", "@middy/core": "^3.6.2", "phin": "^3.7.0" } -} \ No newline at end of file +} diff --git a/layers/CHANGELOG.md b/layers/CHANGELOG.md index b1875365e4..50ca59dd0b 100644 --- a/layers/CHANGELOG.md +++ b/layers/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.7.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.6.0...v1.7.0) (2023-03-20) + +**Note:** Version bump only for package layers + + + + + ## [1.5.1](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.5.0...v1.5.1) (2023-01-13) **Note:** Version bump only for package layer-publisher diff --git a/layers/package.json b/layers/package.json index 8bb6f1cd37..048d46dd3f 100644 --- a/layers/package.json +++ b/layers/package.json @@ -1,6 +1,6 @@ { "name": "layers", - "version": "1.6.0", + "version": "1.7.0", "bin": { "layer": "bin/layers.js" }, @@ -36,4 +36,4 @@ "devDependencies": { "source-map-support": "^0.5.21" } -} \ No newline at end of file +} diff --git a/lerna.json b/lerna.json index 83f9e908c0..d193793fcf 100644 --- a/lerna.json +++ b/lerna.json @@ -9,7 +9,7 @@ "examples/sam", "layers" ], - "version": "1.6.0", + "version": "1.7.0", "npmClient": "npm", "message": "chore(release): %s [skip ci]" } \ No newline at end of file diff --git a/packages/commons/CHANGELOG.md b/packages/commons/CHANGELOG.md index 961dc25a13..f6fa3f85b2 100644 --- a/packages/commons/CHANGELOG.md +++ b/packages/commons/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.7.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.6.0...v1.7.0) (2023-03-20) + + +### Features + +* **parameters:** AppConfigProvider to return the last valid value when the API returns empty value on subsequent calls ([#1365](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1365)) ([97339d9](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/97339d9336ec67568e9e7fd079b3cfe006da1bba)) + + + + + # [1.6.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.5.1...v1.6.0) (2023-03-02) **Note:** Version bump only for package @aws-lambda-powertools/commons diff --git a/packages/commons/package.json b/packages/commons/package.json index 90930275e2..47ec5e83b6 100644 --- a/packages/commons/package.json +++ b/packages/commons/package.json @@ -1,6 +1,6 @@ { "name": "@aws-lambda-powertools/commons", - "version": "1.6.0", + "version": "1.7.0", "description": "A shared utility package for AWS Lambda Powertools for TypeScript libraries", "author": { "name": "Amazon Web Services", @@ -48,4 +48,4 @@ "serverless", "nodejs" ] -} \ No newline at end of file +} diff --git a/packages/logger/CHANGELOG.md b/packages/logger/CHANGELOG.md index c20096bb3c..be37a84cd8 100644 --- a/packages/logger/CHANGELOG.md +++ b/packages/logger/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.7.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.6.0...v1.7.0) (2023-03-20) + + +### Features + +* **logger:** add silent log level to suppress the emission of all logs ([#1347](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1347)) ([c82939e](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/c82939ebdb82ae596cbad07be397794ee4b69fe5)) + + + + + # [1.6.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.5.1...v1.6.0) (2023-03-02) diff --git a/packages/logger/package.json b/packages/logger/package.json index e79986eae5..4211e4e31a 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -1,6 +1,6 @@ { "name": "@aws-lambda-powertools/logger", - "version": "1.6.0", + "version": "1.7.0", "description": "The logging package for the AWS Lambda Powertools for TypeScript library", "author": { "name": "Amazon Web Services", @@ -48,7 +48,7 @@ "url": "https://github.com/awslabs/aws-lambda-powertools-typescript/issues" }, "dependencies": { - "@aws-lambda-powertools/commons": "^1.6.0", + "@aws-lambda-powertools/commons": "^1.7.0", "lodash.merge": "^4.6.2" }, "keywords": [ @@ -60,4 +60,4 @@ "serverless", "nodejs" ] -} \ No newline at end of file +} diff --git a/packages/metrics/CHANGELOG.md b/packages/metrics/CHANGELOG.md index e0f9ef3e8a..255653d776 100644 --- a/packages/metrics/CHANGELOG.md +++ b/packages/metrics/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.7.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.6.0...v1.7.0) (2023-03-20) + + +### Features + +* **metrics:** support high resolution metrics ([#1369](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1369)) ([79a321b](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/79a321b199ef51a024dc25b83673baf2eb03de69)) + + + + + # [1.6.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.5.1...v1.6.0) (2023-03-02) **Note:** Version bump only for package @aws-lambda-powertools/metrics diff --git a/packages/metrics/package.json b/packages/metrics/package.json index d9011fa533..e07a5ca77c 100644 --- a/packages/metrics/package.json +++ b/packages/metrics/package.json @@ -1,6 +1,6 @@ { "name": "@aws-lambda-powertools/metrics", - "version": "1.6.0", + "version": "1.7.0", "description": "The metrics package for the AWS Lambda Powertools for TypeScript library", "author": { "name": "Amazon Web Services", @@ -49,7 +49,7 @@ "url": "https://github.com/awslabs/aws-lambda-powertools-typescript/issues" }, "dependencies": { - "@aws-lambda-powertools/commons": "^1.6.0" + "@aws-lambda-powertools/commons": "^1.7.0" }, "keywords": [ "aws", @@ -59,4 +59,4 @@ "serverless", "nodejs" ] -} \ No newline at end of file +} diff --git a/packages/parameters/CHANGELOG.md b/packages/parameters/CHANGELOG.md new file mode 100644 index 0000000000..de3507c5ed --- /dev/null +++ b/packages/parameters/CHANGELOG.md @@ -0,0 +1,11 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [1.7.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.6.0...v1.7.0) (2023-03-20) + + +### Features + +* **parameters:** AppConfigProvider to return the last valid value when the API returns empty value on subsequent calls ([#1365](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1365)) ([97339d9](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/97339d9336ec67568e9e7fd079b3cfe006da1bba)) diff --git a/packages/parameters/package.json b/packages/parameters/package.json index 55f6381ac6..89ec7cfb25 100644 --- a/packages/parameters/package.json +++ b/packages/parameters/package.json @@ -1,6 +1,6 @@ { "name": "@aws-lambda-powertools/parameters", - "version": "1.6.0", + "version": "1.7.0", "description": "The parameters package for the AWS Lambda Powertools for TypeScript library", "author": { "name": "Amazon Web Services", @@ -67,4 +67,4 @@ "dependencies": { "@aws-sdk/util-base64-node": "^3.209.0" } -} \ No newline at end of file +} diff --git a/packages/tracer/CHANGELOG.md b/packages/tracer/CHANGELOG.md index e4b7505622..2cb4444d2e 100644 --- a/packages/tracer/CHANGELOG.md +++ b/packages/tracer/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.7.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.6.0...v1.7.0) (2023-03-20) + +**Note:** Version bump only for package @aws-lambda-powertools/tracer + + + + + # [1.6.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.5.1...v1.6.0) (2023-03-02) **Note:** Version bump only for package @aws-lambda-powertools/tracer diff --git a/packages/tracer/package.json b/packages/tracer/package.json index e7ec65f99b..cf51abecfb 100644 --- a/packages/tracer/package.json +++ b/packages/tracer/package.json @@ -1,6 +1,6 @@ { "name": "@aws-lambda-powertools/tracer", - "version": "1.6.0", + "version": "1.7.0", "description": "The tracer package for the AWS Lambda Powertools for TypeScript library", "author": { "name": "Amazon Web Services", @@ -51,7 +51,7 @@ "url": "https://github.com/awslabs/aws-lambda-powertools-typescript/issues" }, "dependencies": { - "@aws-lambda-powertools/commons": "^1.6.0", + "@aws-lambda-powertools/commons": "^1.7.0", "aws-xray-sdk-core": "^3.4.1" }, "keywords": [