From 8533abeab4d0a8a9432907da2021d0e483a8108c Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Wed, 9 Nov 2022 15:07:43 +0100 Subject: [PATCH 01/15] docs: update layer version to 5 (#1150) --- docs/index.md | 52 +++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/index.md b/docs/index.md index 88b35688a6..2b40b2a656 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:4**](#){: .copyMe}:clipboard: +* **Lambda Layer**: [**arn:aws:lambda:{region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:5**](#){: .copyMe}:clipboard: * **npm**: **`npm install @aws-lambda-powertools/tracer @aws-lambda-powertools/metrics @aws-lambda-powertools/logger`** ### Lambda Layer @@ -39,23 +39,23 @@ 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:4](#){: .copyMe}:clipboard: - | `us-east-2` | [arn:aws:lambda:us-east-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:4](#){: .copyMe}:clipboard: - | `us-west-1` | [arn:aws:lambda:us-west-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:4](#){: .copyMe}:clipboard: - | `us-west-2` | [arn:aws:lambda:us-west-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:4](#){: .copyMe}:clipboard: - | `ap-south-1` | [arn:aws:lambda:ap-south-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:4](#){: .copyMe}:clipboard: - | `ap-northeast-1` | [arn:aws:lambda:ap-northeast-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:4](#){: .copyMe}:clipboard: - | `ap-northeast-2` | [arn:aws:lambda:ap-northeast-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:4](#){: .copyMe}:clipboard: - | `ap-northeast-3` | [arn:aws:lambda:ap-northeast-3:094274105915:layer:AWSLambdaPowertoolsTypeScript:4](#){: .copyMe}:clipboard: - | `ap-southeast-1` | [arn:aws:lambda:ap-southeast-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:4](#){: .copyMe}:clipboard: - | `ap-southeast-2` | [arn:aws:lambda:ap-southeast-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:4](#){: .copyMe}:clipboard: - | `eu-central-1` | [arn:aws:lambda:eu-central-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:4](#){: .copyMe}:clipboard: - | `eu-west-1` | [arn:aws:lambda:eu-west-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:4](#){: .copyMe}:clipboard: - | `eu-west-2` | [arn:aws:lambda:eu-west-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:4](#){: .copyMe}:clipboard: - | `eu-west-3` | [arn:aws:lambda:eu-west-3:094274105915:layer:AWSLambdaPowertoolsTypeScript:4](#){: .copyMe}:clipboard: - | `eu-north-1` | [arn:aws:lambda:eu-north-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:4](#){: .copyMe}:clipboard: - | `ca-central-1` | [arn:aws:lambda:ca-central-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:4](#){: .copyMe}:clipboard: - | `sa-east-1` | [arn:aws:lambda:sa-east-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:4](#){: .copyMe}:clipboard: + | `us-east-1` | [arn:aws:lambda:us-east-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:5](#){: .copyMe}:clipboard: + | `us-east-2` | [arn:aws:lambda:us-east-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:5](#){: .copyMe}:clipboard: + | `us-west-1` | [arn:aws:lambda:us-west-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:5](#){: .copyMe}:clipboard: + | `us-west-2` | [arn:aws:lambda:us-west-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:5](#){: .copyMe}:clipboard: + | `ap-south-1` | [arn:aws:lambda:ap-south-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:5](#){: .copyMe}:clipboard: + | `ap-northeast-1` | [arn:aws:lambda:ap-northeast-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:5](#){: .copyMe}:clipboard: + | `ap-northeast-2` | [arn:aws:lambda:ap-northeast-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:5](#){: .copyMe}:clipboard: + | `ap-northeast-3` | [arn:aws:lambda:ap-northeast-3:094274105915:layer:AWSLambdaPowertoolsTypeScript:5](#){: .copyMe}:clipboard: + | `ap-southeast-1` | [arn:aws:lambda:ap-southeast-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:5](#){: .copyMe}:clipboard: + | `ap-southeast-2` | [arn:aws:lambda:ap-southeast-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:5](#){: .copyMe}:clipboard: + | `eu-central-1` | [arn:aws:lambda:eu-central-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:5](#){: .copyMe}:clipboard: + | `eu-west-1` | [arn:aws:lambda:eu-west-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:5](#){: .copyMe}:clipboard: + | `eu-west-2` | [arn:aws:lambda:eu-west-2:094274105915:layer:AWSLambdaPowertoolsTypeScript:5](#){: .copyMe}:clipboard: + | `eu-west-3` | [arn:aws:lambda:eu-west-3:094274105915:layer:AWSLambdaPowertoolsTypeScript:5](#){: .copyMe}:clipboard: + | `eu-north-1` | [arn:aws:lambda:eu-north-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:5](#){: .copyMe}:clipboard: + | `ca-central-1` | [arn:aws:lambda:ca-central-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:5](#){: .copyMe}:clipboard: + | `sa-east-1` | [arn:aws:lambda:sa-east-1:094274105915:layer:AWSLambdaPowertoolsTypeScript:5](#){: .copyMe}:clipboard: ??? note "Note: Click to expand and copy code snippets for popular frameworks" @@ -66,7 +66,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:4 + - !Sub arn:aws:lambda:${AWS::Region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:5 ``` 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: @@ -97,7 +97,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:4 + - arn:aws:lambda:${aws::region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:5 ``` 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: @@ -129,7 +129,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:4` + `arn:aws:lambda:${cdk.Stack.of(this).region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:5` ); new lambda.Function(this, 'Function', { @@ -181,7 +181,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:4"] + layers = ["arn:aws:lambda:{aws::region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:5"] source_code_hash = filebase64sha256("lambda_function_payload.zip") } ``` @@ -199,7 +199,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:4` + pulumi.interpolate`arn:aws:lambda:${aws.getRegionOutput().name}:094274105915:layer:AWSLambdaPowertoolsTypeScript:5` ], code: new pulumi.asset.FileArchive("lambda_function_payload.zip"), tracingConfig: { @@ -223,7 +223,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:4 + ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:{aws::region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:5 ❯ amplify push -y # Updating an existing function and add the layer @@ -233,13 +233,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:4 + ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:{aws::region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:5 ? 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:4 --region {region} + aws lambda get-layer-version-by-arn --arn arn:aws:lambda:{aws::region}:094274105915:layer:AWSLambdaPowertoolsTypeScript:5 --region {region} ``` The pre-signed URL to download this Lambda Layer will be within `Location` key. From 8d52660eb6b8324e284421c2484c45d9a0839346 Mon Sep 17 00:00:00 2001 From: Sergei Cherniaev Date: Thu, 10 Nov 2022 17:25:43 +0400 Subject: [PATCH 02/15] feat(logger): pretty printing logs in local and non-prod environment (#1141) * test(logger): add tests for POWERTOOLS_DEV env var and for pretty printing logs when var is set to truthy value * feat(logger): add POWERTOOLS_DEV env var and method to get its value * test(logger): fix tests adding method that gets value of POWERTOOLS_DEV env var * feat(logger): add pretty printing to logs if POWERTOOLS_DEV env var set to truthy value * refactor(review): add private class property for indent size, move evaluation of this property to setOptions() * test(logger): fix tests according to the changes in default logger config * docs(logger): update sections for pretty printing logs using POWERTOOLS_DEV env var * Update docs/core/logger.md * Update docs/core/logger.md Co-authored-by: ijemmy * fix(docs): update default value for POWERTOOLS_DEV env var * feat(logger): add isValueTrue method that checks the string value for truthiness * test(logger): adjust tests for config interface * test(logger): add tests for isValueTrue method, remove redundunt tests Co-authored-by: Andrea Amorosi Co-authored-by: ijemmy --- docs/core/logger.md | 3 + docs/index.md | 1 + packages/logger/src/Logger.ts | 19 ++- .../src/config/ConfigServiceInterface.ts | 15 +++ .../src/config/EnvironmentVariablesService.ts | 28 ++++- packages/logger/src/types/Logger.ts | 5 + packages/logger/tests/unit/Logger.test.ts | 52 ++++++++- .../EnvironmentVariablesService.test.ts | 108 ++++++++++++------ packages/logger/tests/unit/helpers.test.ts | 11 +- .../tests/unit/middleware/middy.test.ts | 7 +- 10 files changed, 211 insertions(+), 38 deletions(-) diff --git a/docs/core/logger.md b/docs/core/logger.md index ab0bd44986..1153edfb3d 100644 --- a/docs/core/logger.md +++ b/docs/core/logger.md @@ -104,6 +104,9 @@ Your Logger will include the following keys to your structured logging (default | **xray_trace_id**: `string` | `1-5759e988-bd862e3fe1be46a994272793` | X-Ray Trace ID. This value is always presented in Lambda environment, whether [tracing is enabled](https://docs.aws.amazon.com/lambda/latest/dg/services-xray.html){target="_blank"} or not. Logger will always log this value. | | **error**: `Object` | `{ name: "Error", location: "/my-project/handler.ts:18", message: "Unexpected error #1", stack: "[stacktrace]"}` | Optional - An object containing information about the Error passed to the logger | +???+ info + When `POWERTOOLS_DEV` environment variable is present and set to `"true"` or `"1"`, Logger will pretty-print log messages for easier readability. We recommend to use this setting only when debugging on local environments. + ### Capturing Lambda context info You can enrich your structured logs with key Lambda context information in multiple ways. diff --git a/docs/index.md b/docs/index.md index 2b40b2a656..8a96d71be8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -293,6 +293,7 @@ Core utilities such as Tracing, Logging, and Metrics will be available across al | **POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS** | Captures HTTP(s) requests as segments. | [Tracer](./core/tracer) | `true` | | **POWERTOOLS_LOGGER_LOG_EVENT** | Logs incoming event | [Logger](./core/logger) | `false` | | **POWERTOOLS_LOGGER_SAMPLE_RATE** | Debug log sampling | [Logger](./core/logger) | `0` | +| **POWERTOOLS_DEV** | Increase JSON indentation to ease debugging when running functions locally or in a non-production environment | [Logger](./core/logger) | `false` | | **LOG_LEVEL** | Sets logging level | [Logger](./core/logger) | `INFO` | ## Tenets diff --git a/packages/logger/src/Logger.ts b/packages/logger/src/Logger.ts index 92f0d9ee07..e2198153ae 100644 --- a/packages/logger/src/Logger.ts +++ b/packages/logger/src/Logger.ts @@ -6,6 +6,7 @@ import { LogItem } from './log'; import cloneDeep from 'lodash.clonedeep'; import merge from 'lodash.merge'; import { ConfigServiceInterface, EnvironmentVariablesService } from './config'; +import { LogJsonIndent } from './types'; import type { ClassThatLogs, Environment, @@ -127,6 +128,8 @@ class Logger extends Utility implements ClassThatLogs { private logFormatter?: LogFormatterInterface; + private logIndentation: number = LogJsonIndent.COMPACT; + private logLevel?: LogLevel; private readonly logLevelThresholds: LogLevelThresholds = { @@ -595,7 +598,7 @@ class Logger extends Utility implements ClassThatLogs { const consoleMethod = logLevel.toLowerCase() as keyof ClassThatLogs; - this.console[consoleMethod](JSON.stringify(log.getAttributes(), this.removeCircularDependencies())); + this.console[consoleMethod](JSON.stringify(log.getAttributes(), this.removeCircularDependencies(), this.logIndentation)); } /** @@ -688,6 +691,19 @@ class Logger extends Utility implements ClassThatLogs { this.logFormatter = logFormatter || new PowertoolLogFormatter(); } + /** + * If the `POWERTOOLS_DEV' env variable is set, + * it adds JSON indentation for pretty printing logs. + * + * @private + * @returns {void} + */ + private setLogIndentation(): void { + if (this.getEnvVarsService().getDevMode()) { + this.logIndentation = LogJsonIndent.PRETTY; + } + } + /** * It sets the Logger's instance log level. * @@ -757,6 +773,7 @@ class Logger extends Utility implements ClassThatLogs { this.setLogFormatter(logFormatter); this.setPowertoolLogData(serviceName, environment); this.setLogEvent(); + this.setLogIndentation(); this.addPersistentLogAttributes(persistentLogAttributes); diff --git a/packages/logger/src/config/ConfigServiceInterface.ts b/packages/logger/src/config/ConfigServiceInterface.ts index 1f1a6ecc32..43f25d3649 100644 --- a/packages/logger/src/config/ConfigServiceInterface.ts +++ b/packages/logger/src/config/ConfigServiceInterface.ts @@ -22,6 +22,13 @@ interface ConfigServiceInterface { */ getCurrentEnvironment(): string + /** + * It returns the value of the POWERTOOLS_DEV environment variable. + * + * @returns {boolean} + */ + getDevMode(): boolean + /** * It returns the value of the POWERTOOLS_LOGGER_LOG_EVENT environment variable. * @@ -50,6 +57,14 @@ interface ConfigServiceInterface { */ getServiceName(): string + /** + * It returns true if the string value represents a boolean true value. + * + * @param {string} value + * @returns boolean + */ + isValueTrue(value: string): boolean + } export { diff --git a/packages/logger/src/config/EnvironmentVariablesService.ts b/packages/logger/src/config/EnvironmentVariablesService.ts index 28b5ee7b4b..fab7ec9fc8 100644 --- a/packages/logger/src/config/EnvironmentVariablesService.ts +++ b/packages/logger/src/config/EnvironmentVariablesService.ts @@ -20,6 +20,7 @@ class EnvironmentVariablesService extends CommonEnvironmentVariablesService impl // Reserved environment variables private awsRegionVariable = 'AWS_REGION'; private currentEnvironmentVariable = 'ENVIRONMENT'; + private devModeVariable = 'POWERTOOLS_DEV'; private functionNameVariable = 'AWS_LAMBDA_FUNCTION_NAME'; private functionVersionVariable = 'AWS_LAMBDA_FUNCTION_VERSION'; private logEventVariable = 'POWERTOOLS_LOGGER_LOG_EVENT'; @@ -45,6 +46,17 @@ class EnvironmentVariablesService extends CommonEnvironmentVariablesService impl return this.get(this.currentEnvironmentVariable); } + /** + * It returns the value of the POWERTOOLS_DEV environment variable. + * + * @returns {boolean} + */ + public getDevMode(): boolean { + const value = this.get(this.devModeVariable); + + return this.isValueTrue(value); + } + /** * It returns the value of the AWS_LAMBDA_FUNCTION_MEMORY_SIZE environment variable. * @@ -81,8 +93,8 @@ class EnvironmentVariablesService extends CommonEnvironmentVariablesService impl */ public getLogEvent(): boolean { const value = this.get(this.logEventVariable); - - return value.toLowerCase() === 'true' || value === '1'; + + return this.isValueTrue(value); } /** @@ -105,6 +117,18 @@ class EnvironmentVariablesService extends CommonEnvironmentVariablesService impl return (value && value.length > 0) ? Number(value) : undefined; } + /** + * It returns true if the string value represents a boolean true value. + * + * @param {string} value + * @returns boolean + */ + public isValueTrue(value: string): boolean { + const truthyValues: string[] = [ '1', 'y', 'yes', 't', 'true', 'on' ]; + + return truthyValues.includes(value.toLowerCase()); + } + } export { diff --git a/packages/logger/src/types/Logger.ts b/packages/logger/src/types/Logger.ts index add56ee28a..2976277c3c 100644 --- a/packages/logger/src/types/Logger.ts +++ b/packages/logger/src/types/Logger.ts @@ -74,3 +74,8 @@ export { ConstructorOptions, HandlerOptions }; + +export const enum LogJsonIndent { + PRETTY = 4, + COMPACT = 0, +} diff --git a/packages/logger/tests/unit/Logger.test.ts b/packages/logger/tests/unit/Logger.test.ts index 17b7969230..6fbf9a527a 100644 --- a/packages/logger/tests/unit/Logger.test.ts +++ b/packages/logger/tests/unit/Logger.test.ts @@ -11,7 +11,7 @@ import * as dummyEvent from '../../../../tests/resources/events/custom/hello-wor import { createLogger, Logger } from '../../src'; import { EnvironmentVariablesService } from '../../src/config'; import { PowertoolLogFormatter } from '../../src/formatter'; -import { ClassThatLogs } from '../../src/types'; +import { ClassThatLogs, LogJsonIndent } from '../../src/types'; import { Context, Handler } from 'aws-lambda'; import { Console } from 'console'; @@ -573,6 +573,7 @@ describe('Class: Logger', () => { customConfigService: undefined, envVarsService: expect.any(EnvironmentVariablesService), logEvent: false, + logIndentation: 0, logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'DEBUG', logLevelThresholds: { @@ -1271,6 +1272,7 @@ describe('Class: Logger', () => { customConfigService: undefined, envVarsService: expect.any(EnvironmentVariablesService), logEvent: false, + logIndentation: 0, logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'DEBUG', logLevelThresholds: { @@ -1295,6 +1297,7 @@ describe('Class: Logger', () => { customConfigService: undefined, envVarsService: expect.any(EnvironmentVariablesService), logEvent: false, + logIndentation: 0, logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'DEBUG', logLevelThresholds: { @@ -1321,6 +1324,7 @@ describe('Class: Logger', () => { customConfigService: undefined, envVarsService: expect.any(EnvironmentVariablesService), logEvent: false, + logIndentation: 0, logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'DEBUG', logLevelThresholds: { @@ -1345,6 +1349,7 @@ describe('Class: Logger', () => { customConfigService: undefined, envVarsService: expect.any(EnvironmentVariablesService), logEvent: false, + logIndentation: 0, logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'ERROR', logLevelThresholds: { @@ -1411,4 +1416,49 @@ describe('Class: Logger', () => { }); }); + describe('Feature: Pretty indentation for a local or non-production environment', () => { + + test('when the `POWERTOOLS_DEV` env var is SET it makes log output has multiple lines', () => { + + // Prepare + process.env.POWERTOOLS_DEV = 'true'; + const INDENTATION = LogJsonIndent.PRETTY; + const logger = new Logger(); + const consoleSpy = jest.spyOn(logger['console'], 'info').mockImplementation(); + + // Act + logger.info('Message with pretty identation'); + + // Assess + expect(consoleSpy).toBeCalledTimes(1); + expect(consoleSpy).toHaveBeenNthCalledWith(1, JSON.stringify({ + level: 'INFO', + message: 'Message with pretty identation', + service: 'hello-world', + timestamp: '2016-06-20T12:08:10.000Z', + xray_trace_id: '1-5759e988-bd862e3fe1be46a994272793', + }, null, INDENTATION)); + }); + + test('when the `POWERTOOLS_DEV` env var is NOT SET it makes log output as one-liner', () => { + + // Prepare + const logger = new Logger(); + const consoleSpy = jest.spyOn(logger['console'], 'info').mockImplementation(); + + // Act + logger.info('Message without pretty identation'); + + // Assess + expect(consoleSpy).toBeCalledTimes(1); + expect(consoleSpy).toHaveBeenNthCalledWith(1, JSON.stringify({ + level: 'INFO', + message: 'Message without pretty identation', + service: 'hello-world', + timestamp: '2016-06-20T12:08:10.000Z', + xray_trace_id: '1-5759e988-bd862e3fe1be46a994272793', + })); + }); + }); + }); \ No newline at end of file diff --git a/packages/logger/tests/unit/config/EnvironmentVariablesService.test.ts b/packages/logger/tests/unit/config/EnvironmentVariablesService.test.ts index b64c4c228f..ccaf0084bf 100644 --- a/packages/logger/tests/unit/config/EnvironmentVariablesService.test.ts +++ b/packages/logger/tests/unit/config/EnvironmentVariablesService.test.ts @@ -119,105 +119,149 @@ describe('Class: EnvironmentVariablesService', () => { expect(value).toEqual(true); }); - test('It returns true if the environment variable POWERTOOLS_LOGGER_LOG_EVENT is "TRUE"', () => { + test('It returns false if the environment variable POWERTOOLS_LOGGER_LOG_EVENT is "false"', () => { // Prepare - process.env.POWERTOOLS_LOGGER_LOG_EVENT = 'TRUE'; + process.env.POWERTOOLS_LOGGER_LOG_EVENT = 'false'; const service = new EnvironmentVariablesService(); // Act const value = service.getLogEvent(); // Assess - expect(value).toEqual(true); + expect(value).toEqual(false); }); - test('It returns true if the environment variable POWERTOOLS_LOGGER_LOG_EVENT is "1"', () => { + test('It returns false if the environment variable POWERTOOLS_LOGGER_LOG_EVENT is "somethingsilly"', () => { // Prepare - process.env.POWERTOOLS_LOGGER_LOG_EVENT = '1'; + process.env.POWERTOOLS_LOGGER_LOG_EVENT = 'somethingsilly'; const service = new EnvironmentVariablesService(); // Act const value = service.getLogEvent(); // Assess - expect(value).toEqual(true); + expect(value).toEqual(false); }); - test('It returns false if the environment variable POWERTOOLS_LOGGER_LOG_EVENT is "false"', () => { + }); + + describe('Method: getLogLevel', () => { + + test('It returns the value of the environment variable LOG_LEVEL', () => { // Prepare - process.env.POWERTOOLS_LOGGER_LOG_EVENT = 'false'; + process.env.LOG_LEVEL = 'ERROR'; const service = new EnvironmentVariablesService(); // Act - const value = service.getLogEvent(); + const value = service.getLogLevel(); // Assess - expect(value).toEqual(false); + expect(value).toEqual('ERROR'); }); - test('It returns false if the environment variable POWERTOOLS_LOGGER_LOG_EVENT is "0"', () => { + }); + + describe('Method: getSampleRateValue', () => { + + test('It returns the value of the environment variable POWERTOOLS_LOGGER_SAMPLE_RATE', () => { // Prepare - process.env.POWERTOOLS_LOGGER_LOG_EVENT = '0'; + process.env.POWERTOOLS_LOGGER_SAMPLE_RATE = '0.01'; const service = new EnvironmentVariablesService(); // Act - const value = service.getLogEvent(); + const value = service.getSampleRateValue(); // Assess - expect(value).toEqual(false); + expect(value).toEqual(0.01); }); - test('It returns false if the environment variable POWERTOOLS_LOGGER_LOG_EVENT is "somethingsilly"', () => { + }); + + describe('Method: getDevMode', () => { + + test('It returns true if the environment variable POWERTOOLS_DEV is "true"', () => { // Prepare - process.env.POWERTOOLS_LOGGER_LOG_EVENT = 'somethingsilly'; + process.env.POWERTOOLS_DEV = 'true'; const service = new EnvironmentVariablesService(); // Act - const value = service.getLogEvent(); + const value = service.getDevMode(); // Assess - expect(value).toEqual(false); + expect(value).toEqual(true); }); - }); - - describe('Method: getLogLevel', () => { - - test('It returns the value of the environment variable LOG_LEVEL', () => { + test('It returns false if the environment variable POWERTOOLS_DEV is "false"', () => { // Prepare - process.env.LOG_LEVEL = 'ERROR'; + process.env.POWERTOOLS_DEV = 'false'; const service = new EnvironmentVariablesService(); // Act - const value = service.getLogLevel(); + const value = service.getDevMode(); // Assess - expect(value).toEqual('ERROR'); + expect(value).toEqual(false); }); - }); + test('It returns false if the environment variable POWERTOOLS_DEV is NOT set', () => { - describe('Method: getSampleRateValue', () => { + // Prepare + process.env.POWERTOOLS_DEV = 'somethingsilly'; + const service = new EnvironmentVariablesService(); - test('It returns the value of the environment variable POWERTOOLS_LOGGER_SAMPLE_RATE', () => { + // Act + const value = service.getDevMode(); + + // Assess + expect(value).toEqual(false); + }); + + test('It returns false if the environment variable POWERTOOLS_DEV is "somethingsilly"', () => { // Prepare - process.env.POWERTOOLS_LOGGER_SAMPLE_RATE = '0.01'; + process.env.POWERTOOLS_DEV = 'somethingsilly'; const service = new EnvironmentVariablesService(); // Act - const value = service.getSampleRateValue(); + const value = service.getDevMode(); // Assess - expect(value).toEqual(0.01); + expect(value).toEqual(false); }); }); + describe('Method: isValueTrue', () => { + + const valuesToTest: Array> = [ + [ '1', true ], + [ 'y', true ], + [ 'yes', true ], + [ 't', true ], + [ 'TRUE', true ], + [ 'on', true ], + [ '', false ], + [ 'false', false ], + [ 'fasle', false ], + [ 'somethingsilly', false ], + [ '0', false ] + ]; + + test.each(valuesToTest)('It takes string "%s" and returns %s', (input, output) => { + // Prepare + const service = new EnvironmentVariablesService(); + // Act + const value = service.isValueTrue(input as string); + // Assess + expect(value).toBe(output); + }); + + }); + }); \ No newline at end of file diff --git a/packages/logger/tests/unit/helpers.test.ts b/packages/logger/tests/unit/helpers.test.ts index 9309bea207..ab49edd114 100644 --- a/packages/logger/tests/unit/helpers.test.ts +++ b/packages/logger/tests/unit/helpers.test.ts @@ -77,6 +77,7 @@ describe('Helper: createLogger function', () => { customConfigService: expect.any(EnvironmentVariablesService), envVarsService: expect.any(EnvironmentVariablesService), logEvent: false, + logIndentation: 0, logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'WARN', console: expect.any(Console), @@ -117,6 +118,7 @@ describe('Helper: createLogger function', () => { customConfigService: undefined, envVarsService: expect.any(EnvironmentVariablesService), logEvent: false, + logIndentation: 0, logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'INFO', console: expect.any(Console), @@ -238,6 +240,7 @@ describe('Helper: createLogger function', () => { customConfigService: undefined, envVarsService: expect.any(EnvironmentVariablesService), logEvent: false, + logIndentation: 0, logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'INFO', console: expect.any(Console), @@ -307,7 +310,13 @@ describe('Helper: createLogger function', () => { getServiceName(): string { return 'my-backend-service'; }, - + getDevMode(): boolean { + return false; + }, + isValueTrue(): boolean { + return true; + }, + }; // Prepare const loggerOptions:ConstructorOptions = { diff --git a/packages/logger/tests/unit/middleware/middy.test.ts b/packages/logger/tests/unit/middleware/middy.test.ts index e734e61e61..88e37b9fce 100644 --- a/packages/logger/tests/unit/middleware/middy.test.ts +++ b/packages/logger/tests/unit/middleware/middy.test.ts @@ -336,7 +336,12 @@ describe('Middy middleware', () => { getServiceName(): string { return 'my-backend-service'; }, - + getDevMode(): boolean { + return false; + }, + isValueTrue(): boolean { + return true; + }, }; const logger = new Logger({ From d7326dbbdb748fadedd213dd8e32ec4444914da2 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 10 Nov 2022 19:03:45 +0100 Subject: [PATCH 03/15] chore: add codespaces badge (#1155) --- .devcontainer/Dockerfile | 7 ------- CONTRIBUTING.md | 4 +++- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 5803ca8a28..bf55eac0a8 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -7,14 +7,7 @@ FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT} RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ && apt-get -y install --no-install-recommends bash-completion -# [Optional] Uncomment if you want to install an additional version of node using nvm -# ARG EXTRA_NODE_VERSION=10 -# RUN su node -c "umask 0002 && ./usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" - RUN wget https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip \ && unzip aws-sam-cli-linux-x86_64.zip -d sam-installation \ && sudo ./sam-installation/install \ && rm -rf sam-installation aws-sam-cli-linux-* - -# Global node modules -RUN su node -c "npm install -g npm-check-updates npm@next-8" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e710c843bd..a67c3340d3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,9 +55,11 @@ reported the issue. Please try to include as much information as you can. Detail ## Getting Started The following steps describe how to set up the AWS Lambda Powertools for TypeScript repository on your local machine. -The alternative is to use a Cloud IDE like [Gitpod](https://www.gitpod.io/) or [Codespaces](https://github.com/features/codespaces) for your development. + +The alternative is to use a Cloud IDE like [Gitpod](https://www.gitpod.io/) or [GitHub CodeSpaces](https://github.com/features/codespaces) for your development. To quickly get up and running you can click one of these button to open a cloned version of this repo with all of the prerequisites and dependencies installed: [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/awslabs/aws-lambda-powertools-typescript) +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=305501331) ### Setup From 8af1c17cd555ddb02b02f36e8fbc4a9d57bc7eaf Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Fri, 11 Nov 2022 11:21:45 +0100 Subject: [PATCH 04/15] docs: clarify nodejs16 requirement + fix command (#1152) --- CONTRIBUTING.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a67c3340d3..ef11f898cd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -77,6 +77,9 @@ The following tools need to be installed on your system prior to starting workin First, [fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) the repository, and then run the following commands to clone and initialize the repository locally. +> **Note** +> In order for the commands below to work you need Node.js `16.x` and npm `8.x` + ```console git clone https://github.com/{your-account}/aws-lambda-powertools-typescript.git cd aws-lambda-powertools-typescript diff --git a/package.json b/package.json index 967b36b170..36deb61fcd 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "test": "npm t -ws", "commit": "commit", "package": "npm run package", - "setup-local": "npm ci --foreground-scripts && cd examples/cdk && npm ci && cd ../../examples/sam && npm ci && cd ../../layer-publisher && npm ci && cd ../.. && npm run init-environment", + "setup-local": "export PROJECT_ROOT=$(pwd) && npm ci --foreground-scripts && cd $PROJECT_ROOT/examples/cdk && npm ci && cd $PROJECT_ROOT/examples/sam && npm ci && cd $PROJECT_ROOT/layer-publisher && npm ci && cd $PROJECT_ROOT/ && npm run init-environment", "build": "npm run build -ws", "postversion": "git push && git push --tags", "docs-website-build-run": "npm run docs-buildDockerImage && npm run docs-runLocalDocker", From 0a6676ac24abdadaaff2d95fc8d75d3a7137a00b Mon Sep 17 00:00:00 2001 From: jeffrey-baker-vg <111442225+jeffrey-baker-vg@users.noreply.github.com> Date: Fri, 11 Nov 2022 06:30:06 -0500 Subject: [PATCH 05/15] feat(idempotency): Add persistence layer and DynamoDB implementation (#1110) * feat: initial idempotency classes * feat: refactor persistence layer classes into their own folder * feat: rename idempotency config to differentiate from idempotency options * feat: added type for a generic function * feat: remove idempotency configuration for this FR * feat: refactored type of function to accept any combo of parameters * feat: adding PersistenceLayer * feat: PersistenceLayer unit tests for saveInProgress * feat: added saveSuccess * feat: added getRecord * feat: added delete record * feat: branch coverage and cleaning up imports * feat: added more tests * feat: deleted unused methods * feat: added comments * feat: implement get command for dynamo persistence layer * feat: implement get command for dynamo persistence layer * feat: allow for data attr to be passed and return in persistence layer get * feat: added implementation for delete, update, put * feat: create condition on put for not in progress status * feat: use inprogress enum for status * feat: added error when unable to get record for idempotency key * feat: added error for conditional write of an existing record * feat: tests added for put record on dynamo persistence layer * feat: implemented the idempotency record functions for status, expiry, and json response * test: check if the status is expired * test: idempotency record is not expired and status maintained * feat: added tests for get record * feat: add aws-sdk-client-mock jest assertion library * feat: add unit tests for update record and delete record * feat: remove optional chaining from item made unnecessary with error branch * feat: remove unused block * feat: refactored mock child class to be shared amongst dynamo persistence layer tests * test: add path to get the response data from the data record * feat: added branch to handle conditional check failure * feat: add configuration option to dynamo client creation to remove undefined values * feat: change how time is measured to seconds * feat: change type of the response/result to a record * feat:updated imports * refactor: create constructor object for dynamo persistence layer * fix: remove temp eslint disable * fix: adjust verbiage on test blocks Co-authored-by: ijemmy * style: put constructor parameters onto one line for readability * fix: update dynamo persistence layer tests to use new construtor options * fix: remove unneeded eslint ignore from persistence layer * style: put parameters for dynamo client command object onto one line for readability * fix: move lib-dynamo dep under the correct package * refactor: change idempotency record to use options object in contructor * feat: add consistent read to dynamo persistence layer * fix: revert changes to layer-publisher package-lock Co-authored-by: vgphoenixcampos <111440293+vgphoenixcampos@users.noreply.github.com> Co-authored-by: KevenFuentes9 Co-authored-by: Phoenix Campos Co-authored-by: ijemmy --- package-lock.json | 16 +- package.json | 8 +- packages/idempotency/jest.config.js | 3 - packages/idempotency/package-lock.json | 3294 +++++++++++++++++ packages/idempotency/package.json | 11 +- .../src/EnvironmentVariablesService.ts | 25 + packages/idempotency/src/Exceptions.ts | 17 + .../persistence/DynamoDbPersistenceLayer.ts | 130 +- .../src/persistence/IdempotencyRecord.ts | 48 +- .../src/persistence/PersistenceLayer.ts | 154 +- .../persistence/PersistenceLayerInterface.ts | 10 +- packages/idempotency/src/persistence/index.ts | 4 + .../DynamoPersistenceConstructorOptions.ts | 12 + .../src/types/IdempotencyRecordOptions.ts | 14 + packages/idempotency/src/types/index.ts | 2 + .../helpers/populateEnvironmentVariables.ts | 2 + .../unit/EnvironmentVariableService.test.ts | 34 + .../DynamoDbPersistenceLayer.test.ts | 282 ++ .../persistence/IdempotencyRecord.test.ts | 97 + .../unit/persistence/PersistenceLayer.test.ts | 251 ++ 20 files changed, 4350 insertions(+), 64 deletions(-) create mode 100644 packages/idempotency/package-lock.json create mode 100644 packages/idempotency/src/EnvironmentVariablesService.ts create mode 100644 packages/idempotency/src/Exceptions.ts create mode 100644 packages/idempotency/src/persistence/index.ts create mode 100644 packages/idempotency/src/types/DynamoPersistenceConstructorOptions.ts create mode 100644 packages/idempotency/src/types/IdempotencyRecordOptions.ts create mode 100644 packages/idempotency/src/types/index.ts create mode 100644 packages/idempotency/tests/helpers/populateEnvironmentVariables.ts create mode 100644 packages/idempotency/tests/unit/EnvironmentVariableService.test.ts create mode 100644 packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts create mode 100644 packages/idempotency/tests/unit/persistence/IdempotencyRecord.test.ts create mode 100644 packages/idempotency/tests/unit/persistence/PersistenceLayer.test.ts diff --git a/package-lock.json b/package-lock.json index cda483229d..e743677bb0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15542,15 +15542,14 @@ }, "packages/commons": { "name": "@aws-lambda-powertools/commons", - "version": "1.2.1", + "version": "1.4.0", "license": "MIT-0" }, "packages/logger": { "name": "@aws-lambda-powertools/logger", - "version": "1.2.1", + "version": "1.4.0", "license": "MIT", "dependencies": { - "@aws-lambda-powertools/commons": "^1.2.1", "lodash.clonedeep": "^4.5.0", "lodash.merge": "^4.6.2", "lodash.pickby": "^4.6.0" @@ -15563,11 +15562,8 @@ }, "packages/metrics": { "name": "@aws-lambda-powertools/metrics", - "version": "1.2.1", + "version": "1.4.0", "license": "MIT-0", - "dependencies": { - "@aws-lambda-powertools/commons": "^1.2.1" - }, "devDependencies": { "@types/promise-retry": "^1.1.3", "promise-retry": "^2.0.1" @@ -15575,10 +15571,9 @@ }, "packages/tracer": { "name": "@aws-lambda-powertools/tracer", - "version": "1.2.1", + "version": "1.4.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "^1.2.1", "aws-xray-sdk-core": "^3.3.6" }, "devDependencies": { @@ -15805,7 +15800,6 @@ "@aws-lambda-powertools/logger": { "version": "file:packages/logger", "requires": { - "@aws-lambda-powertools/commons": "^1.2.1", "@types/lodash.clonedeep": "^4.5.7", "@types/lodash.merge": "^4.6.7", "@types/lodash.pickby": "^4.6.7", @@ -15817,7 +15811,6 @@ "@aws-lambda-powertools/metrics": { "version": "file:packages/metrics", "requires": { - "@aws-lambda-powertools/commons": "^1.2.1", "@types/promise-retry": "^1.1.3", "promise-retry": "^2.0.1" } @@ -15825,7 +15818,6 @@ "@aws-lambda-powertools/tracer": { "version": "file:packages/tracer", "requires": { - "@aws-lambda-powertools/commons": "^1.2.1", "@aws-sdk/client-dynamodb": "^3.100.0", "@types/promise-retry": "^1.1.3", "aws-xray-sdk-core": "^3.3.6", diff --git a/package.json b/package.json index 36deb61fcd..c077b3c592 100644 --- a/package.json +++ b/package.json @@ -40,10 +40,8 @@ }, "homepage": "https://github.com/awslabs/aws-lambda-powertools-typescript#readme", "devDependencies": { - "@aws-cdk/cx-api": "^2.17.0", "@aws-cdk/cloudformation-diff": "^2.17.0", - "aws-cdk-lib": "^2.17.0", - "constructs": "^10.0.92", + "@aws-cdk/cx-api": "^2.17.0", "@commitlint/cli": "^17.0.0", "@middy/core": "^2.5.6", "@types/aws-lambda": "^8.10.72", @@ -54,8 +52,10 @@ "@typescript-eslint/parser": "^5.12.1", "archiver": "^5.3.0", "aws-cdk": "^2.17.0", + "aws-cdk-lib": "^2.17.0", "aws-sdk": "^2.1082.0", "cdk-assets": "^2.17.0", + "constructs": "^10.0.92", "esbuild": "^0.14.23", "eslint": "^8.4.0", "eslint-import-resolver-node": "^0.3.6", @@ -83,4 +83,4 @@ "dependencies": { "hosted-git-info": "^5.0.0" } -} \ No newline at end of file +} diff --git a/packages/idempotency/jest.config.js b/packages/idempotency/jest.config.js index dd0e63cc41..8f1701110c 100644 --- a/packages/idempotency/jest.config.js +++ b/packages/idempotency/jest.config.js @@ -38,8 +38,5 @@ module.exports = { 'json-summary', 'text', 'lcov' - ], - 'setupFiles': [ - '/tests/helpers/populateEnvironmentVariables.ts' ] }; \ No newline at end of file diff --git a/packages/idempotency/package-lock.json b/packages/idempotency/package-lock.json new file mode 100644 index 0000000000..29fef4816a --- /dev/null +++ b/packages/idempotency/package-lock.json @@ -0,0 +1,3294 @@ +{ + "name": "@aws-lambda-powertools/idempotency", + "version": "0.0.11", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@aws-lambda-powertools/idempotency", + "version": "0.0.11", + "license": "MIT", + "dependencies": { + "@aws-lambda-powertools/commons": "^1.2.1", + "@aws-sdk/lib-dynamodb": "^3.170.0" + }, + "devDependencies": { + "aws-sdk-client-mock": "^2.0.0", + "aws-sdk-client-mock-jest": "^2.0.0" + } + }, + "node_modules/@aws-crypto/ie11-detection": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-2.0.2.tgz", + "integrity": "sha512-5XDMQY98gMAf/WRTic5G++jfmS/VLM0rwpiOpaainKi4L0nqWMSB1SzsrEG5rjFZGYN6ZAefO+/Yta2dFM0kMw==", + "peer": true, + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/ie11-detection/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "peer": true + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-2.0.0.tgz", + "integrity": "sha512-rYXOQ8BFOaqMEHJrLHul/25ckWH6GTJtdLSajhlqGMx0PmSueAuvboCuZCTqEKlxR8CQOwRarxYMZZSYlhRA1A==", + "peer": true, + "dependencies": { + "@aws-crypto/ie11-detection": "^2.0.0", + "@aws-crypto/sha256-js": "^2.0.0", + "@aws-crypto/supports-web-crypto": "^2.0.0", + "@aws-crypto/util": "^2.0.0", + "@aws-sdk/types": "^3.1.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "peer": true + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-2.0.0.tgz", + "integrity": "sha512-VZY+mCY4Nmrs5WGfitmNqXzaE873fcIZDu54cbaDaaamsaTOP1DBImV9F4pICc3EHjQXujyE8jig+PFCaew9ig==", + "peer": true, + "dependencies": { + "@aws-crypto/util": "^2.0.0", + "@aws-sdk/types": "^3.1.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "peer": true + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-2.0.2.tgz", + "integrity": "sha512-6mbSsLHwZ99CTOOswvCRP3C+VCWnzBf+1SnbWxzzJ9lR0mA0JnY2JEAhp8rqmTE0GPFy88rrM27ffgp62oErMQ==", + "peer": true, + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "peer": true + }, + "node_modules/@aws-crypto/util": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-2.0.2.tgz", + "integrity": "sha512-Lgu5v/0e/BcrZ5m/IWqzPUf3UYFTy/PpeED+uc9SWUR1iZQL8XXbGQg10UfllwwBryO3hFF5dizK+78aoXC1eA==", + "peer": true, + "dependencies": { + "@aws-sdk/types": "^3.110.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/util/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "peer": true + }, + "node_modules/@aws-lambda-powertools/commons": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.2.1.tgz", + "integrity": "sha512-wHdAgzXQfRqcm6kIuxrQjKL8x02sVTMc7rcMJPkHU1DsGNL7Z3g0H+tkrlmFimGkRqPv724J2OqNdEvBJKaoMQ==" + }, + "node_modules/@aws-sdk/abort-controller": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.201.0.tgz", + "integrity": "sha512-xJ984k+CKlGjBmvNarzM8Y+b6X4L1Zt0TycQmVBJq7fAr/ju9l13pQIoXR5WlDIW1FkGeVczF5Nu6fN46SCORQ==", + "peer": true, + "dependencies": { + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-dynamodb": { + "version": "3.202.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.202.0.tgz", + "integrity": "sha512-/S8+YqNQmIDMAkn40bIO9Fww1Lqzc1oSX82xUhzn4/k8UE36S9/pVDYgE3Nu8tHCqf71vvJUrVPiwj+9u7DDsg==", + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "2.0.0", + "@aws-crypto/sha256-js": "2.0.0", + "@aws-sdk/client-sts": "3.202.0", + "@aws-sdk/config-resolver": "3.201.0", + "@aws-sdk/credential-provider-node": "3.202.0", + "@aws-sdk/fetch-http-handler": "3.201.0", + "@aws-sdk/hash-node": "3.201.0", + "@aws-sdk/invalid-dependency": "3.201.0", + "@aws-sdk/middleware-content-length": "3.201.0", + "@aws-sdk/middleware-endpoint": "3.201.0", + "@aws-sdk/middleware-endpoint-discovery": "3.201.0", + "@aws-sdk/middleware-host-header": "3.201.0", + "@aws-sdk/middleware-logger": "3.201.0", + "@aws-sdk/middleware-recursion-detection": "3.201.0", + "@aws-sdk/middleware-retry": "3.201.0", + "@aws-sdk/middleware-serde": "3.201.0", + "@aws-sdk/middleware-signing": "3.201.0", + "@aws-sdk/middleware-stack": "3.201.0", + "@aws-sdk/middleware-user-agent": "3.201.0", + "@aws-sdk/node-config-provider": "3.201.0", + "@aws-sdk/node-http-handler": "3.201.0", + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/smithy-client": "3.201.0", + "@aws-sdk/types": "3.201.0", + "@aws-sdk/url-parser": "3.201.0", + "@aws-sdk/util-base64-browser": "3.188.0", + "@aws-sdk/util-base64-node": "3.201.0", + "@aws-sdk/util-body-length-browser": "3.188.0", + "@aws-sdk/util-body-length-node": "3.201.0", + "@aws-sdk/util-defaults-mode-browser": "3.201.0", + "@aws-sdk/util-defaults-mode-node": "3.201.0", + "@aws-sdk/util-endpoints": "3.202.0", + "@aws-sdk/util-user-agent-browser": "3.201.0", + "@aws-sdk/util-user-agent-node": "3.201.0", + "@aws-sdk/util-utf8-browser": "3.188.0", + "@aws-sdk/util-utf8-node": "3.201.0", + "@aws-sdk/util-waiter": "3.201.0", + "tslib": "^2.3.1", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.202.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.202.0.tgz", + "integrity": "sha512-c0impiZUbJeB5AdyZyER81tsqF9bxxaEz6p2LYkTn62NWVXPWEUo/1CHQRj36MUzorz1xiWKIN0NPgK6GBJkPQ==", + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "2.0.0", + "@aws-crypto/sha256-js": "2.0.0", + "@aws-sdk/config-resolver": "3.201.0", + "@aws-sdk/fetch-http-handler": "3.201.0", + "@aws-sdk/hash-node": "3.201.0", + "@aws-sdk/invalid-dependency": "3.201.0", + "@aws-sdk/middleware-content-length": "3.201.0", + "@aws-sdk/middleware-endpoint": "3.201.0", + "@aws-sdk/middleware-host-header": "3.201.0", + "@aws-sdk/middleware-logger": "3.201.0", + "@aws-sdk/middleware-recursion-detection": "3.201.0", + "@aws-sdk/middleware-retry": "3.201.0", + "@aws-sdk/middleware-serde": "3.201.0", + "@aws-sdk/middleware-stack": "3.201.0", + "@aws-sdk/middleware-user-agent": "3.201.0", + "@aws-sdk/node-config-provider": "3.201.0", + "@aws-sdk/node-http-handler": "3.201.0", + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/smithy-client": "3.201.0", + "@aws-sdk/types": "3.201.0", + "@aws-sdk/url-parser": "3.201.0", + "@aws-sdk/util-base64-browser": "3.188.0", + "@aws-sdk/util-base64-node": "3.201.0", + "@aws-sdk/util-body-length-browser": "3.188.0", + "@aws-sdk/util-body-length-node": "3.201.0", + "@aws-sdk/util-defaults-mode-browser": "3.201.0", + "@aws-sdk/util-defaults-mode-node": "3.201.0", + "@aws-sdk/util-endpoints": "3.202.0", + "@aws-sdk/util-user-agent-browser": "3.201.0", + "@aws-sdk/util-user-agent-node": "3.201.0", + "@aws-sdk/util-utf8-browser": "3.188.0", + "@aws-sdk/util-utf8-node": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sts": { + "version": "3.202.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.202.0.tgz", + "integrity": "sha512-WGRFzODig8+cZR903q3fa7OAzGigSuzD9AoK+ybefQa7bxSuhT2ous4GNPOJz9WYWvugEPyrJu8vbG35IoF1ZQ==", + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "2.0.0", + "@aws-crypto/sha256-js": "2.0.0", + "@aws-sdk/config-resolver": "3.201.0", + "@aws-sdk/credential-provider-node": "3.202.0", + "@aws-sdk/fetch-http-handler": "3.201.0", + "@aws-sdk/hash-node": "3.201.0", + "@aws-sdk/invalid-dependency": "3.201.0", + "@aws-sdk/middleware-content-length": "3.201.0", + "@aws-sdk/middleware-endpoint": "3.201.0", + "@aws-sdk/middleware-host-header": "3.201.0", + "@aws-sdk/middleware-logger": "3.201.0", + "@aws-sdk/middleware-recursion-detection": "3.201.0", + "@aws-sdk/middleware-retry": "3.201.0", + "@aws-sdk/middleware-sdk-sts": "3.201.0", + "@aws-sdk/middleware-serde": "3.201.0", + "@aws-sdk/middleware-signing": "3.201.0", + "@aws-sdk/middleware-stack": "3.201.0", + "@aws-sdk/middleware-user-agent": "3.201.0", + "@aws-sdk/node-config-provider": "3.201.0", + "@aws-sdk/node-http-handler": "3.201.0", + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/smithy-client": "3.201.0", + "@aws-sdk/types": "3.201.0", + "@aws-sdk/url-parser": "3.201.0", + "@aws-sdk/util-base64-browser": "3.188.0", + "@aws-sdk/util-base64-node": "3.201.0", + "@aws-sdk/util-body-length-browser": "3.188.0", + "@aws-sdk/util-body-length-node": "3.201.0", + "@aws-sdk/util-defaults-mode-browser": "3.201.0", + "@aws-sdk/util-defaults-mode-node": "3.201.0", + "@aws-sdk/util-endpoints": "3.202.0", + "@aws-sdk/util-user-agent-browser": "3.201.0", + "@aws-sdk/util-user-agent-node": "3.201.0", + "@aws-sdk/util-utf8-browser": "3.188.0", + "@aws-sdk/util-utf8-node": "3.201.0", + "fast-xml-parser": "4.0.11", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/config-resolver": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.201.0.tgz", + "integrity": "sha512-6YLIel7OGMGi+r8XC1A54cQJRIpx/NJ4fBALy44zFpQ+fdJUEmw4daUf1LECmAQiPA2Pr/hD0nBtX+wiiTf5/g==", + "peer": true, + "dependencies": { + "@aws-sdk/signature-v4": "3.201.0", + "@aws-sdk/types": "3.201.0", + "@aws-sdk/util-config-provider": "3.201.0", + "@aws-sdk/util-middleware": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.201.0.tgz", + "integrity": "sha512-g2MJsowzFhSsIOITUjYp7EzWFeHINjEP526Uf+5z2/p2kxQVwYYWZQK7j+tPE2Bk3MEjGOCmVHbbE7IFj0rNHw==", + "peer": true, + "dependencies": { + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-imds": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.201.0.tgz", + "integrity": "sha512-i8U2k3/L3iUWJJ1GSlwVBMfLQ2OTUT97E8yJi/xz5GavYuPOsUQWQe4fp7WGQivxh+AqybXAGFUCYub6zfUqag==", + "peer": true, + "dependencies": { + "@aws-sdk/node-config-provider": "3.201.0", + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/types": "3.201.0", + "@aws-sdk/url-parser": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.202.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.202.0.tgz", + "integrity": "sha512-d0kiYMpGzAq3EBXgEJ1SdeoMXVf3lk6NKHDi/Gy8LB03sZqgc5cY4XFCnY3cqE3DNWWZNR26M4j/KiA0LIjAVA==", + "peer": true, + "dependencies": { + "@aws-sdk/credential-provider-env": "3.201.0", + "@aws-sdk/credential-provider-imds": "3.201.0", + "@aws-sdk/credential-provider-sso": "3.202.0", + "@aws-sdk/credential-provider-web-identity": "3.201.0", + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/shared-ini-file-loader": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.202.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.202.0.tgz", + "integrity": "sha512-/uHNs3c1O3oFpH7z9nnpjyg8NKNyRbNxUDIHkuHkNSUUKXpfBisDX6TMbD4VcflGuNdkbT+8spkw5vsE8ox3ig==", + "peer": true, + "dependencies": { + "@aws-sdk/credential-provider-env": "3.201.0", + "@aws-sdk/credential-provider-imds": "3.201.0", + "@aws-sdk/credential-provider-ini": "3.202.0", + "@aws-sdk/credential-provider-process": "3.201.0", + "@aws-sdk/credential-provider-sso": "3.202.0", + "@aws-sdk/credential-provider-web-identity": "3.201.0", + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/shared-ini-file-loader": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.201.0.tgz", + "integrity": "sha512-jTK3HSZgNj/hVrWb0wuF/cPUWSJYoRI/80fnN55o6QLS8WWIgOI8o2PNeVTAT5OrKioSoN4fgKTeUm3DZy3npQ==", + "peer": true, + "dependencies": { + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/shared-ini-file-loader": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.202.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.202.0.tgz", + "integrity": "sha512-EBUY/qKboJwy3qxPHiD/LAnhzga4xR1p++QMoxg2BKgkgwlvGb23lYGr5DSCNhdtJj5o165YZDbGYH+PKn2NVw==", + "peer": true, + "dependencies": { + "@aws-sdk/client-sso": "3.202.0", + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/shared-ini-file-loader": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.201.0.tgz", + "integrity": "sha512-U54bqhYaClPVZfswgknhlICp3BAtKXpOgHQCUF8cko5xUgbL4lVgd1rC3lWviGFMQAaTIF3QOXyEouemxr3VXw==", + "peer": true, + "dependencies": { + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/endpoint-cache": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.201.0.tgz", + "integrity": "sha512-QgT4Wm19yQ/HbvxNeYaR7m9uEYhW+0YY9nSpnRLHmhMJP90kmt9ANESIHCukPxc6ecDEZXIo7C2rica9P3H/ew==", + "peer": true, + "dependencies": { + "mnemonist": "0.38.3", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/fetch-http-handler": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.201.0.tgz", + "integrity": "sha512-uiEoH79j6WOpbp4THcpvD9XmD+vPgy+00oyYXjtZqJnv2PM/9b6tGWKTdI+TJW4P/oPv7HP7JmRlkGaTnkIdXw==", + "peer": true, + "dependencies": { + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/querystring-builder": "3.201.0", + "@aws-sdk/types": "3.201.0", + "@aws-sdk/util-base64-browser": "3.188.0", + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/hash-node": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.201.0.tgz", + "integrity": "sha512-WJsMZg5/TMoWnLM+0NuwLwFzHsi89Bi9J1Dt7JdJHXFLoEZV54FEz1PK/Sq5NOldhVljpXQwWOB2dHA2wxFztg==", + "peer": true, + "dependencies": { + "@aws-sdk/types": "3.201.0", + "@aws-sdk/util-buffer-from": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/invalid-dependency": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.201.0.tgz", + "integrity": "sha512-f/zgntOfIozNyKSaG9dvHjjBaR3y20kYNswMYkSuCM2NIT5LpyHiiq5I11TwaocatUFcDztWpcsv7vHpIgI5Ig==", + "peer": true, + "dependencies": { + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/is-array-buffer": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.201.0.tgz", + "integrity": "sha512-UPez5qLh3dNgt0DYnPD/q0mVJY84rA17QE26hVNOW3fAji8W2wrwrxdacWOxyXvlxWsVRcKmr+lay1MDqpAMfg==", + "peer": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/lib-dynamodb": { + "version": "3.202.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.202.0.tgz", + "integrity": "sha512-VJSIDw+meJKVBXz4pqnSSbxPMMfq4LhhFsVwmsLtNIHEg1evi0LKnSl+vQROOAWjVruUGk/L2Y1uluLOccn1kA==", + "dependencies": { + "@aws-sdk/util-dynamodb": "3.202.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.0.0", + "@aws-sdk/smithy-client": "^3.0.0", + "@aws-sdk/types": "^3.0.0" + } + }, + "node_modules/@aws-sdk/middleware-content-length": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.201.0.tgz", + "integrity": "sha512-p4G9AtdrKO8A3Z4RyZiy0isEYwuge7bQRBS7UzcGkcIOhJONq2pcM+gRZYz+NWvfYYNWUg5uODsFQfU8342yKg==", + "peer": true, + "dependencies": { + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-endpoint": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.201.0.tgz", + "integrity": "sha512-F3JlXo5GusbeZR956hA9VxmDxUeg77Xh6o8fveAE2+G4Bjcb1iq9jPNlw6A14vDj3oTKenv2LLnjL2OIfl6hRA==", + "peer": true, + "dependencies": { + "@aws-sdk/middleware-serde": "3.201.0", + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/signature-v4": "3.201.0", + "@aws-sdk/types": "3.201.0", + "@aws-sdk/url-parser": "3.201.0", + "@aws-sdk/util-config-provider": "3.201.0", + "@aws-sdk/util-middleware": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-endpoint-discovery": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.201.0.tgz", + "integrity": "sha512-EHQ+AH/bflmfM2qDN3Aa2ufhw2irv6+FE/13yW09KsunaKiuSyg7Clh6+T8ESw3fCaI4IE2ol0d6HNwJ4JLOVQ==", + "peer": true, + "dependencies": { + "@aws-sdk/config-resolver": "3.201.0", + "@aws-sdk/endpoint-cache": "3.201.0", + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.201.0.tgz", + "integrity": "sha512-7KNzdV7nFcKAoahvgGAlzsOq9FFDsU5h3w2iPtVdJhz6ZRDH/2v6WFeUCji+UNZip36gFfMPivoO8Y5smb5r/A==", + "peer": true, + "dependencies": { + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.201.0.tgz", + "integrity": "sha512-kYLsa9x3oUJxYU7V5KOO50Kl7b0kk+I4ltkrdarLvvXcVI7ZXmWHzHLT2dkUhj8S0ceVdi0FYHVPJ3GoE8re4A==", + "peer": true, + "dependencies": { + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.201.0.tgz", + "integrity": "sha512-NGOr+n559ZcJLdFoJR8LNGdrOJFIp2BTuWEDYeicNdNb0bETTXrkzcfT1BRhV9CWqCDmjFvjdrzbhS0cw/UUGA==", + "peer": true, + "dependencies": { + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-retry": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.201.0.tgz", + "integrity": "sha512-4jQjSKCpSc4oB1X9nNq4FbIAwQrr+mvmUSmg/oe2Llf42Ak1G9gg3rNTtQdfzA/wNMlL4ZFfF5Br+uz06e1hnQ==", + "peer": true, + "dependencies": { + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/service-error-classification": "3.201.0", + "@aws-sdk/types": "3.201.0", + "@aws-sdk/util-middleware": "3.201.0", + "tslib": "^2.3.1", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-sts": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.201.0.tgz", + "integrity": "sha512-clZuXcoN0mAP4JH5C6pW5+0tdF25+fpFJqE7GNRjjH/NYNk6ImVI0Kq2espEWwVBuaS0/chTDK3b+pK8YOWdhw==", + "peer": true, + "dependencies": { + "@aws-sdk/middleware-signing": "3.201.0", + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/signature-v4": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-serde": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.201.0.tgz", + "integrity": "sha512-Z7AzIuqEDvsZmp80zeT1oYxsoB8uQZby20Z8kF6/vNoq3sIzaGf/wHeNn0p+Vgo2auGSbZcVUZKoDptQLSLwIQ==", + "peer": true, + "dependencies": { + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-signing": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.201.0.tgz", + "integrity": "sha512-08ri5+mB28tva9RjVIXFcUP5lRTx+Pj8C2HYqF2GL5H3uAo+h3RQ++fEG1uwUMLf7tCEFivcw6SHA1KmCnB7+w==", + "peer": true, + "dependencies": { + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/signature-v4": "3.201.0", + "@aws-sdk/types": "3.201.0", + "@aws-sdk/util-middleware": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-stack": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.201.0.tgz", + "integrity": "sha512-lqHYSBP5FBxzA5w5XiYYYpfXabFzleXonqRkqZts1tapNJ4sOd+itiKG8JoNP7LDOwJ8qxNW/a33/gQeh3wkwQ==", + "peer": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.201.0.tgz", + "integrity": "sha512-/rYZ93WN1gDJudXis/0382CEoTqRa4qZJA608u2EPWs5aiMocUrm7pjH5XvKm2OYX8K/lyaMSBvL2OTIMzXGaQ==", + "peer": true, + "dependencies": { + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/node-config-provider": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.201.0.tgz", + "integrity": "sha512-JO0K2qPTYn+pPC7g8rWr1oueg9CqGCkYbINuAuz79vjToOLUQnZT9GiFm7QADe6J6RT1oGEKRQabNaJnp8cFpQ==", + "peer": true, + "dependencies": { + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/shared-ini-file-loader": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/node-http-handler": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.201.0.tgz", + "integrity": "sha512-bWjXBd4WCiQcV4PwY+eFnlz9tZ4UiqfiJteav4MDt8YWkVlsVnR8RutmVSm3KZZjO2tJNSrla0ZWBebkNnI/Xg==", + "peer": true, + "dependencies": { + "@aws-sdk/abort-controller": "3.201.0", + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/querystring-builder": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/property-provider": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.201.0.tgz", + "integrity": "sha512-lVMP75VsYHIW04uYbkjA0I8Bb7b+aEj6PBBLdFoA22S0uCeJOD42OSr2Gtg2fToDGO7LQJw/K2D+LMCYKfZ3vQ==", + "peer": true, + "dependencies": { + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/protocol-http": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.201.0.tgz", + "integrity": "sha512-RdOc1elWFpj8MogxG87nkhtylw0a+OD7W8WFM+Gw4yJMkl7cwW42VIBFfb0+KCGZfIQltIeSLRvfe3WvVPyo7Q==", + "peer": true, + "dependencies": { + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/querystring-builder": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.201.0.tgz", + "integrity": "sha512-FgQnVHpYR19w/HmHEgWpykCn9tdogW0n45Ins6LBCo2aImDf9kBATD4xgN/F2rtogGuLGgu5LIIMHIOj1Tzs/w==", + "peer": true, + "dependencies": { + "@aws-sdk/types": "3.201.0", + "@aws-sdk/util-uri-escape": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/querystring-parser": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.201.0.tgz", + "integrity": "sha512-vS9Ljbqrwi0sIKYxgyZYJUN1AcE291hvuqwty9etgD2w/26SbWiMhjIW/fXJUOZjUvGKkYCpbivJYSzAGAuWfQ==", + "peer": true, + "dependencies": { + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/service-error-classification": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.201.0.tgz", + "integrity": "sha512-Pfcfmurgq8UpM0rXco6FVblcruqN4Mo3TW8/yaXrbctWpmdNT/8v19fffQIIgk94TU8Vf/nPJ7E5DXL7MZr4Fw==", + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/shared-ini-file-loader": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.201.0.tgz", + "integrity": "sha512-Pbxk0TXep0yI8MnK7Prly6JuBm5Me9AITav8/zPEgTZ3fMhXhQhhiuQcuTCI9GeosSzoiu8VvK53oPtBZZFnXQ==", + "peer": true, + "dependencies": { + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.201.0.tgz", + "integrity": "sha512-zEHoG1/hzJq169slggkPy1SN9YPWI78Bbe/MvHGYmCmQDspblu60JSBIbAatNqAxAmcWKc2HqpyGKjCkMG94ZA==", + "peer": true, + "dependencies": { + "@aws-sdk/is-array-buffer": "3.201.0", + "@aws-sdk/types": "3.201.0", + "@aws-sdk/util-hex-encoding": "3.201.0", + "@aws-sdk/util-middleware": "3.201.0", + "@aws-sdk/util-uri-escape": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/smithy-client": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.201.0.tgz", + "integrity": "sha512-cL87Jgxczee8YFkWGWKQ2Ze0vjn4+eCa1kDvEYMCOQvNujTuFgatXLgije5a7nVkSnL9WLoIP7Y7fsBGrKfMnQ==", + "peer": true, + "dependencies": { + "@aws-sdk/middleware-stack": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.201.0.tgz", + "integrity": "sha512-RCQj2pQyHD330Jd4c5CHJ87k2ZqC3Mmtl6nhwH1dy3vbnGUpc3q+3yinOKoTAY934kIa7ia32Y/2EjuyHxaj1A==", + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/url-parser": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.201.0.tgz", + "integrity": "sha512-V15aqj0tj4Y79VpuIdHUvX4Nvn4hYPB0RAn/qg5CCComIl0doLOirAQtW1MOBOyctdRlD9Uv7d1QdPLzJZMHjQ==", + "peer": true, + "dependencies": { + "@aws-sdk/querystring-parser": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/util-base64-browser": { + "version": "3.188.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64-browser/-/util-base64-browser-3.188.0.tgz", + "integrity": "sha512-qlH+5NZBLiyKziL335BEPedYxX6j+p7KFRWXvDQox9S+s+gLCayednpK+fteOhBenCcR9fUZOVuAPScy1I8qCg==", + "peer": true, + "dependencies": { + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/util-base64-node": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64-node/-/util-base64-node-3.201.0.tgz", + "integrity": "sha512-ydZqNpB3l5kiicInpPDExPb5xHI7uyVIa1vMupnuIrJ412iNb0F2+K8LlFynzw6fSJShVKnqFcWOYRA96z1iIw==", + "peer": true, + "dependencies": { + "@aws-sdk/util-buffer-from": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-body-length-browser": { + "version": "3.188.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.188.0.tgz", + "integrity": "sha512-8VpnwFWXhnZ/iRSl9mTf+VKOX9wDE8QtN4bj9pBfxwf90H1X7E8T6NkiZD3k+HubYf2J94e7DbeHs7fuCPW5Qg==", + "peer": true, + "dependencies": { + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/util-body-length-node": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.201.0.tgz", + "integrity": "sha512-q+gwQoLn/DOwirb2hgZJeEwo1D3vLhoD6FfSV42Ecfvtb4jHnWReWMHguujfCubuDgZCrMEvYQzuocS75HHsbA==", + "peer": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-buffer-from": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.201.0.tgz", + "integrity": "sha512-s6Wjltd9vU+vR3n0pqSPmNDcrrkrVTdV4t7x2zz3nDsFKTI77iVNafDmuaUlOA/bIlpjCJqaWecoVrZmEKeR7A==", + "peer": true, + "dependencies": { + "@aws-sdk/is-array-buffer": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-config-provider": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.201.0.tgz", + "integrity": "sha512-cCRJlnRRP8vrLJomzJRBIyiyohsjJKmnIaQ9t0tAhGCywZbyjx6TlpYRZYfVWo+MwdF1Pi8ZScTrFPW0JuBOIQ==", + "peer": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-defaults-mode-browser": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.201.0.tgz", + "integrity": "sha512-skRMAM+xrV/sDvvtHC81ExEKQEiZFaRrRdUT39fBX1SpGnFTo2wpv7XK+rAW2XopGgnLPytXLQD97Kub79o4zA==", + "peer": true, + "dependencies": { + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/types": "3.201.0", + "bowser": "^2.11.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@aws-sdk/util-defaults-mode-node": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.201.0.tgz", + "integrity": "sha512-9N5LXRhxigbkbEcjQ4nNXHuQxp0VFlbc2/5wbcuPjIKX/OROiQI4mYQ6nuSKk7eku5sNFb9FtEHeD/RZo8od6Q==", + "peer": true, + "dependencies": { + "@aws-sdk/config-resolver": "3.201.0", + "@aws-sdk/credential-provider-imds": "3.201.0", + "@aws-sdk/node-config-provider": "3.201.0", + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@aws-sdk/util-dynamodb": { + "version": "3.202.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.202.0.tgz", + "integrity": "sha512-rJqJ6XjtvM0GWi+g/T6BXXaICoCHOG1i++iznFoz9WE1xpCTd1dtXtjw29tfAuvF7f6+lIJQemc8TWoczxWj2g==", + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.202.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.202.0.tgz", + "integrity": "sha512-sNees5uDp7nfEbvzaA1DAHqoEvEb9ZOkdNH5gcj/FMBETbr00YtsuXsTZogTHQsX/otRTiudZBE3iH7R4SLSAQ==", + "peer": true, + "dependencies": { + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-hex-encoding": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.201.0.tgz", + "integrity": "sha512-7t1vR1pVxKx0motd3X9rI3m/xNp78p3sHtP5yo4NP4ARpxyJ0fokBomY8ScaH2D/B+U5o9ARxldJUdMqyBlJcA==", + "peer": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.201.0.tgz", + "integrity": "sha512-hPJgifWh/rADabLAk1C9xXA2B3O4NUmbU58KgBRgC1HksiiHGFVZObB5fkBH8US/XV2jwORkpSf4OhretXQuKg==", + "peer": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-middleware": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.201.0.tgz", + "integrity": "sha512-iAitcEZo17IyKn4ku1IBgtomr25esu5OuSRjw5Or4bNOeqXB0w50cItf/9qft8LIhbvBEAUtNAYXvqNzvhTZdQ==", + "peer": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-uri-escape": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.201.0.tgz", + "integrity": "sha512-TeTWbGx4LU2c5rx0obHeDFeO9HvwYwQtMh1yniBz00pQb6Qt6YVOETVQikRZ+XRQwEyCg/dA375UplIpiy54mA==", + "peer": true, + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.201.0.tgz", + "integrity": "sha512-iL2gyz7GuUVtZcMZpqvfxdFrl9hc28qpagymmJ/w2yhN86YNPHdK8Sx1Yo6VxNGVDCCWGb7tHXf7VP+U4Yv/Lg==", + "peer": true, + "dependencies": { + "@aws-sdk/types": "3.201.0", + "bowser": "^2.11.0", + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.201.0.tgz", + "integrity": "sha512-6lhhvwB3AZSISnYQpDGdlyTrzfYK2P9QYjy7vZEBRd9TSOaggiFICXe03ZvZfVOSeg0EInlMKn1fIHzPUHRuHQ==", + "peer": true, + "dependencies": { + "@aws-sdk/node-config-provider": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/util-utf8-browser": { + "version": "3.188.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.188.0.tgz", + "integrity": "sha512-jt627x0+jE+Ydr9NwkFstg3cUvgWh56qdaqAMDsqgRlKD21md/6G226z/Qxl7lb1VEW2LlmCx43ai/37Qwcj2Q==", + "peer": true, + "dependencies": { + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/util-utf8-node": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-node/-/util-utf8-node-3.201.0.tgz", + "integrity": "sha512-A+bJFR/1rHYOJg137E69L1sX0I+LH+xf9ZjMXG9BVO0hSo7yDPoJVpHrzTJyOc3tuRITjIGBv9Qi4TKcoOSi1A==", + "peer": true, + "dependencies": { + "@aws-sdk/util-buffer-from": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-waiter": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-waiter/-/util-waiter-3.201.0.tgz", + "integrity": "sha512-NE8+BkPDXq86oyVr9EKN1s+iN8GID8mhj6DbtEZKZES3fJ36xH7MldRylgCewgv1Qpd1W00M4c/mVvUx3zp7sg==", + "peer": true, + "dependencies": { + "@aws-sdk/abort-controller": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@jest/expect-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", + "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", + "dev": true, + "dependencies": { + "jest-get-type": "^28.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.24.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.24.42", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.42.tgz", + "integrity": "sha512-d+2AtrHGyWek2u2ITF0lHRIv6Tt7X0dEHW+0rP+5aDCEjC3fiN2RBjrLD0yU0at52BcZbRGxLbAtXiR0hFCjYw==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.1.1.tgz", + "integrity": "sha512-cZ7rKJTLiE7u7Wi/v9Hc2fs3Ucc3jrWeMgPHbbTCeVAB2S0wOBbYlkJVeNSL04i7fdhT8wIbDq1zhC/PXTD2SA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "28.1.8", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-28.1.8.tgz", + "integrity": "sha512-8TJkV++s7B6XqnDrzR1m/TT0A0h948Pnl/097veySPN67VRAgQ4gZ7n2KfJo2rVq6njQjdxU3GCCyDvAeuHoiw==", + "dev": true, + "dependencies": { + "expect": "^28.0.0", + "pretty-format": "^28.0.0" + } + }, + "node_modules/@types/node": { + "version": "18.7.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.19.tgz", + "integrity": "sha512-Sq1itGUKUX1ap7GgZlrzdBydjbsJL/NSQt/4wkAxUJ7/OS5c2WkoN6WSpWc2Yc5wtKMZOUA0VCs/j2XJadN3HA==", + "dev": true + }, + "node_modules/@types/sinon": { + "version": "10.0.13", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.13.tgz", + "integrity": "sha512-UVjDqJblVNQYvVNUsj0PuYYw0ELRmgt1Nt5Vk0pT5f16ROGfcKJY8o1HVuMOJOpD727RrGB9EGvoaTQE5tgxZQ==", + "dev": true, + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", + "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.12", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz", + "integrity": "sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aws-sdk-client-mock": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aws-sdk-client-mock/-/aws-sdk-client-mock-2.0.0.tgz", + "integrity": "sha512-yjC39Ud78Cgwu9jLc7LoFILT8xtyvR9doCfd8tjeqaOI4oQ5IeTaIYDezWpWKndmrjXZzVLw/odWKP1hpuvsVQ==", + "dev": true, + "dependencies": { + "@types/sinon": "^10.0.10", + "sinon": "^11.1.1", + "tslib": "^2.1.0" + } + }, + "node_modules/aws-sdk-client-mock-jest": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aws-sdk-client-mock-jest/-/aws-sdk-client-mock-jest-2.0.0.tgz", + "integrity": "sha512-HwJeJThngXDUyyyd3Hk12Ailu3smhQzyox+nrF14TxE/LNSJsSmJ2rXahzj7Zdyxn9jDSe2h2ulrfUNYgbPYlw==", + "dev": true, + "dependencies": { + "@types/jest": "^28.1.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "aws-sdk-client-mock": "2.0.0" + } + }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "peer": true + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ci-info": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.4.0.tgz", + "integrity": "sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/fast-xml-parser": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.11.tgz", + "integrity": "sha512-4aUg3aNRR/WjQAcpceODG1C3x3lFANXRo8+1biqfieHmg9pyMt7qB4lQV/Ta6sJCTbA5vfD8fnA8S54JATiFUA==", + "peer": true, + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + }, + "funding": { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + }, + "node_modules/jest-diff": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", + "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", + "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mnemonist": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.3.tgz", + "integrity": "sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==", + "peer": true, + "dependencies": { + "obliterator": "^1.6.1" + } + }, + "node_modules/nise": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.1.tgz", + "integrity": "sha512-yr5kW2THW1AkxVmCnKEh4nbYkJdB3I7LUkiUgOvEkOp414mc2UMaHMA7pjq1nYowhdoJZGwEKGaQVbxfpWj10A==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": ">=5", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, + "node_modules/obliterator": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-1.6.1.tgz", + "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==", + "peer": true + }, + "node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/sinon": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-11.1.2.tgz", + "integrity": "sha512-59237HChms4kg7/sXhiRcUzdSkKuydDeTiamT/jesUVHshBgL8XAmhgFo0GfK6RruMDM/iRSij1EybmMog9cJw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": "^7.1.2", + "@sinonjs/samsam": "^6.0.2", + "diff": "^5.0.0", + "nise": "^5.1.0", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stack-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "peer": true + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "peer": true, + "bin": { + "uuid": "dist/bin/uuid" + } + } + }, + "dependencies": { + "@aws-crypto/ie11-detection": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-2.0.2.tgz", + "integrity": "sha512-5XDMQY98gMAf/WRTic5G++jfmS/VLM0rwpiOpaainKi4L0nqWMSB1SzsrEG5rjFZGYN6ZAefO+/Yta2dFM0kMw==", + "peer": true, + "requires": { + "tslib": "^1.11.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "peer": true + } + } + }, + "@aws-crypto/sha256-browser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-2.0.0.tgz", + "integrity": "sha512-rYXOQ8BFOaqMEHJrLHul/25ckWH6GTJtdLSajhlqGMx0PmSueAuvboCuZCTqEKlxR8CQOwRarxYMZZSYlhRA1A==", + "peer": true, + "requires": { + "@aws-crypto/ie11-detection": "^2.0.0", + "@aws-crypto/sha256-js": "^2.0.0", + "@aws-crypto/supports-web-crypto": "^2.0.0", + "@aws-crypto/util": "^2.0.0", + "@aws-sdk/types": "^3.1.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "peer": true + } + } + }, + "@aws-crypto/sha256-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-2.0.0.tgz", + "integrity": "sha512-VZY+mCY4Nmrs5WGfitmNqXzaE873fcIZDu54cbaDaaamsaTOP1DBImV9F4pICc3EHjQXujyE8jig+PFCaew9ig==", + "peer": true, + "requires": { + "@aws-crypto/util": "^2.0.0", + "@aws-sdk/types": "^3.1.0", + "tslib": "^1.11.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "peer": true + } + } + }, + "@aws-crypto/supports-web-crypto": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-2.0.2.tgz", + "integrity": "sha512-6mbSsLHwZ99CTOOswvCRP3C+VCWnzBf+1SnbWxzzJ9lR0mA0JnY2JEAhp8rqmTE0GPFy88rrM27ffgp62oErMQ==", + "peer": true, + "requires": { + "tslib": "^1.11.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "peer": true + } + } + }, + "@aws-crypto/util": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-2.0.2.tgz", + "integrity": "sha512-Lgu5v/0e/BcrZ5m/IWqzPUf3UYFTy/PpeED+uc9SWUR1iZQL8XXbGQg10UfllwwBryO3hFF5dizK+78aoXC1eA==", + "peer": true, + "requires": { + "@aws-sdk/types": "^3.110.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "peer": true + } + } + }, + "@aws-lambda-powertools/commons": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.2.1.tgz", + "integrity": "sha512-wHdAgzXQfRqcm6kIuxrQjKL8x02sVTMc7rcMJPkHU1DsGNL7Z3g0H+tkrlmFimGkRqPv724J2OqNdEvBJKaoMQ==" + }, + "@aws-sdk/abort-controller": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.201.0.tgz", + "integrity": "sha512-xJ984k+CKlGjBmvNarzM8Y+b6X4L1Zt0TycQmVBJq7fAr/ju9l13pQIoXR5WlDIW1FkGeVczF5Nu6fN46SCORQ==", + "peer": true, + "requires": { + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/client-dynamodb": { + "version": "3.202.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.202.0.tgz", + "integrity": "sha512-/S8+YqNQmIDMAkn40bIO9Fww1Lqzc1oSX82xUhzn4/k8UE36S9/pVDYgE3Nu8tHCqf71vvJUrVPiwj+9u7DDsg==", + "peer": true, + "requires": { + "@aws-crypto/sha256-browser": "2.0.0", + "@aws-crypto/sha256-js": "2.0.0", + "@aws-sdk/client-sts": "3.202.0", + "@aws-sdk/config-resolver": "3.201.0", + "@aws-sdk/credential-provider-node": "3.202.0", + "@aws-sdk/fetch-http-handler": "3.201.0", + "@aws-sdk/hash-node": "3.201.0", + "@aws-sdk/invalid-dependency": "3.201.0", + "@aws-sdk/middleware-content-length": "3.201.0", + "@aws-sdk/middleware-endpoint": "3.201.0", + "@aws-sdk/middleware-endpoint-discovery": "3.201.0", + "@aws-sdk/middleware-host-header": "3.201.0", + "@aws-sdk/middleware-logger": "3.201.0", + "@aws-sdk/middleware-recursion-detection": "3.201.0", + "@aws-sdk/middleware-retry": "3.201.0", + "@aws-sdk/middleware-serde": "3.201.0", + "@aws-sdk/middleware-signing": "3.201.0", + "@aws-sdk/middleware-stack": "3.201.0", + "@aws-sdk/middleware-user-agent": "3.201.0", + "@aws-sdk/node-config-provider": "3.201.0", + "@aws-sdk/node-http-handler": "3.201.0", + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/smithy-client": "3.201.0", + "@aws-sdk/types": "3.201.0", + "@aws-sdk/url-parser": "3.201.0", + "@aws-sdk/util-base64-browser": "3.188.0", + "@aws-sdk/util-base64-node": "3.201.0", + "@aws-sdk/util-body-length-browser": "3.188.0", + "@aws-sdk/util-body-length-node": "3.201.0", + "@aws-sdk/util-defaults-mode-browser": "3.201.0", + "@aws-sdk/util-defaults-mode-node": "3.201.0", + "@aws-sdk/util-endpoints": "3.202.0", + "@aws-sdk/util-user-agent-browser": "3.201.0", + "@aws-sdk/util-user-agent-node": "3.201.0", + "@aws-sdk/util-utf8-browser": "3.188.0", + "@aws-sdk/util-utf8-node": "3.201.0", + "@aws-sdk/util-waiter": "3.201.0", + "tslib": "^2.3.1", + "uuid": "^8.3.2" + } + }, + "@aws-sdk/client-sso": { + "version": "3.202.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.202.0.tgz", + "integrity": "sha512-c0impiZUbJeB5AdyZyER81tsqF9bxxaEz6p2LYkTn62NWVXPWEUo/1CHQRj36MUzorz1xiWKIN0NPgK6GBJkPQ==", + "peer": true, + "requires": { + "@aws-crypto/sha256-browser": "2.0.0", + "@aws-crypto/sha256-js": "2.0.0", + "@aws-sdk/config-resolver": "3.201.0", + "@aws-sdk/fetch-http-handler": "3.201.0", + "@aws-sdk/hash-node": "3.201.0", + "@aws-sdk/invalid-dependency": "3.201.0", + "@aws-sdk/middleware-content-length": "3.201.0", + "@aws-sdk/middleware-endpoint": "3.201.0", + "@aws-sdk/middleware-host-header": "3.201.0", + "@aws-sdk/middleware-logger": "3.201.0", + "@aws-sdk/middleware-recursion-detection": "3.201.0", + "@aws-sdk/middleware-retry": "3.201.0", + "@aws-sdk/middleware-serde": "3.201.0", + "@aws-sdk/middleware-stack": "3.201.0", + "@aws-sdk/middleware-user-agent": "3.201.0", + "@aws-sdk/node-config-provider": "3.201.0", + "@aws-sdk/node-http-handler": "3.201.0", + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/smithy-client": "3.201.0", + "@aws-sdk/types": "3.201.0", + "@aws-sdk/url-parser": "3.201.0", + "@aws-sdk/util-base64-browser": "3.188.0", + "@aws-sdk/util-base64-node": "3.201.0", + "@aws-sdk/util-body-length-browser": "3.188.0", + "@aws-sdk/util-body-length-node": "3.201.0", + "@aws-sdk/util-defaults-mode-browser": "3.201.0", + "@aws-sdk/util-defaults-mode-node": "3.201.0", + "@aws-sdk/util-endpoints": "3.202.0", + "@aws-sdk/util-user-agent-browser": "3.201.0", + "@aws-sdk/util-user-agent-node": "3.201.0", + "@aws-sdk/util-utf8-browser": "3.188.0", + "@aws-sdk/util-utf8-node": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/client-sts": { + "version": "3.202.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.202.0.tgz", + "integrity": "sha512-WGRFzODig8+cZR903q3fa7OAzGigSuzD9AoK+ybefQa7bxSuhT2ous4GNPOJz9WYWvugEPyrJu8vbG35IoF1ZQ==", + "peer": true, + "requires": { + "@aws-crypto/sha256-browser": "2.0.0", + "@aws-crypto/sha256-js": "2.0.0", + "@aws-sdk/config-resolver": "3.201.0", + "@aws-sdk/credential-provider-node": "3.202.0", + "@aws-sdk/fetch-http-handler": "3.201.0", + "@aws-sdk/hash-node": "3.201.0", + "@aws-sdk/invalid-dependency": "3.201.0", + "@aws-sdk/middleware-content-length": "3.201.0", + "@aws-sdk/middleware-endpoint": "3.201.0", + "@aws-sdk/middleware-host-header": "3.201.0", + "@aws-sdk/middleware-logger": "3.201.0", + "@aws-sdk/middleware-recursion-detection": "3.201.0", + "@aws-sdk/middleware-retry": "3.201.0", + "@aws-sdk/middleware-sdk-sts": "3.201.0", + "@aws-sdk/middleware-serde": "3.201.0", + "@aws-sdk/middleware-signing": "3.201.0", + "@aws-sdk/middleware-stack": "3.201.0", + "@aws-sdk/middleware-user-agent": "3.201.0", + "@aws-sdk/node-config-provider": "3.201.0", + "@aws-sdk/node-http-handler": "3.201.0", + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/smithy-client": "3.201.0", + "@aws-sdk/types": "3.201.0", + "@aws-sdk/url-parser": "3.201.0", + "@aws-sdk/util-base64-browser": "3.188.0", + "@aws-sdk/util-base64-node": "3.201.0", + "@aws-sdk/util-body-length-browser": "3.188.0", + "@aws-sdk/util-body-length-node": "3.201.0", + "@aws-sdk/util-defaults-mode-browser": "3.201.0", + "@aws-sdk/util-defaults-mode-node": "3.201.0", + "@aws-sdk/util-endpoints": "3.202.0", + "@aws-sdk/util-user-agent-browser": "3.201.0", + "@aws-sdk/util-user-agent-node": "3.201.0", + "@aws-sdk/util-utf8-browser": "3.188.0", + "@aws-sdk/util-utf8-node": "3.201.0", + "fast-xml-parser": "4.0.11", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/config-resolver": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.201.0.tgz", + "integrity": "sha512-6YLIel7OGMGi+r8XC1A54cQJRIpx/NJ4fBALy44zFpQ+fdJUEmw4daUf1LECmAQiPA2Pr/hD0nBtX+wiiTf5/g==", + "peer": true, + "requires": { + "@aws-sdk/signature-v4": "3.201.0", + "@aws-sdk/types": "3.201.0", + "@aws-sdk/util-config-provider": "3.201.0", + "@aws-sdk/util-middleware": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/credential-provider-env": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.201.0.tgz", + "integrity": "sha512-g2MJsowzFhSsIOITUjYp7EzWFeHINjEP526Uf+5z2/p2kxQVwYYWZQK7j+tPE2Bk3MEjGOCmVHbbE7IFj0rNHw==", + "peer": true, + "requires": { + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/credential-provider-imds": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.201.0.tgz", + "integrity": "sha512-i8U2k3/L3iUWJJ1GSlwVBMfLQ2OTUT97E8yJi/xz5GavYuPOsUQWQe4fp7WGQivxh+AqybXAGFUCYub6zfUqag==", + "peer": true, + "requires": { + "@aws-sdk/node-config-provider": "3.201.0", + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/types": "3.201.0", + "@aws-sdk/url-parser": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/credential-provider-ini": { + "version": "3.202.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.202.0.tgz", + "integrity": "sha512-d0kiYMpGzAq3EBXgEJ1SdeoMXVf3lk6NKHDi/Gy8LB03sZqgc5cY4XFCnY3cqE3DNWWZNR26M4j/KiA0LIjAVA==", + "peer": true, + "requires": { + "@aws-sdk/credential-provider-env": "3.201.0", + "@aws-sdk/credential-provider-imds": "3.201.0", + "@aws-sdk/credential-provider-sso": "3.202.0", + "@aws-sdk/credential-provider-web-identity": "3.201.0", + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/shared-ini-file-loader": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/credential-provider-node": { + "version": "3.202.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.202.0.tgz", + "integrity": "sha512-/uHNs3c1O3oFpH7z9nnpjyg8NKNyRbNxUDIHkuHkNSUUKXpfBisDX6TMbD4VcflGuNdkbT+8spkw5vsE8ox3ig==", + "peer": true, + "requires": { + "@aws-sdk/credential-provider-env": "3.201.0", + "@aws-sdk/credential-provider-imds": "3.201.0", + "@aws-sdk/credential-provider-ini": "3.202.0", + "@aws-sdk/credential-provider-process": "3.201.0", + "@aws-sdk/credential-provider-sso": "3.202.0", + "@aws-sdk/credential-provider-web-identity": "3.201.0", + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/shared-ini-file-loader": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/credential-provider-process": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.201.0.tgz", + "integrity": "sha512-jTK3HSZgNj/hVrWb0wuF/cPUWSJYoRI/80fnN55o6QLS8WWIgOI8o2PNeVTAT5OrKioSoN4fgKTeUm3DZy3npQ==", + "peer": true, + "requires": { + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/shared-ini-file-loader": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/credential-provider-sso": { + "version": "3.202.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.202.0.tgz", + "integrity": "sha512-EBUY/qKboJwy3qxPHiD/LAnhzga4xR1p++QMoxg2BKgkgwlvGb23lYGr5DSCNhdtJj5o165YZDbGYH+PKn2NVw==", + "peer": true, + "requires": { + "@aws-sdk/client-sso": "3.202.0", + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/shared-ini-file-loader": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/credential-provider-web-identity": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.201.0.tgz", + "integrity": "sha512-U54bqhYaClPVZfswgknhlICp3BAtKXpOgHQCUF8cko5xUgbL4lVgd1rC3lWviGFMQAaTIF3QOXyEouemxr3VXw==", + "peer": true, + "requires": { + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/endpoint-cache": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.201.0.tgz", + "integrity": "sha512-QgT4Wm19yQ/HbvxNeYaR7m9uEYhW+0YY9nSpnRLHmhMJP90kmt9ANESIHCukPxc6ecDEZXIo7C2rica9P3H/ew==", + "peer": true, + "requires": { + "mnemonist": "0.38.3", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/fetch-http-handler": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.201.0.tgz", + "integrity": "sha512-uiEoH79j6WOpbp4THcpvD9XmD+vPgy+00oyYXjtZqJnv2PM/9b6tGWKTdI+TJW4P/oPv7HP7JmRlkGaTnkIdXw==", + "peer": true, + "requires": { + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/querystring-builder": "3.201.0", + "@aws-sdk/types": "3.201.0", + "@aws-sdk/util-base64-browser": "3.188.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/hash-node": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.201.0.tgz", + "integrity": "sha512-WJsMZg5/TMoWnLM+0NuwLwFzHsi89Bi9J1Dt7JdJHXFLoEZV54FEz1PK/Sq5NOldhVljpXQwWOB2dHA2wxFztg==", + "peer": true, + "requires": { + "@aws-sdk/types": "3.201.0", + "@aws-sdk/util-buffer-from": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/invalid-dependency": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.201.0.tgz", + "integrity": "sha512-f/zgntOfIozNyKSaG9dvHjjBaR3y20kYNswMYkSuCM2NIT5LpyHiiq5I11TwaocatUFcDztWpcsv7vHpIgI5Ig==", + "peer": true, + "requires": { + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/is-array-buffer": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.201.0.tgz", + "integrity": "sha512-UPez5qLh3dNgt0DYnPD/q0mVJY84rA17QE26hVNOW3fAji8W2wrwrxdacWOxyXvlxWsVRcKmr+lay1MDqpAMfg==", + "peer": true, + "requires": { + "tslib": "^2.3.1" + } + }, + "@aws-sdk/lib-dynamodb": { + "version": "3.202.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.202.0.tgz", + "integrity": "sha512-VJSIDw+meJKVBXz4pqnSSbxPMMfq4LhhFsVwmsLtNIHEg1evi0LKnSl+vQROOAWjVruUGk/L2Y1uluLOccn1kA==", + "requires": { + "@aws-sdk/util-dynamodb": "3.202.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/middleware-content-length": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.201.0.tgz", + "integrity": "sha512-p4G9AtdrKO8A3Z4RyZiy0isEYwuge7bQRBS7UzcGkcIOhJONq2pcM+gRZYz+NWvfYYNWUg5uODsFQfU8342yKg==", + "peer": true, + "requires": { + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/middleware-endpoint": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.201.0.tgz", + "integrity": "sha512-F3JlXo5GusbeZR956hA9VxmDxUeg77Xh6o8fveAE2+G4Bjcb1iq9jPNlw6A14vDj3oTKenv2LLnjL2OIfl6hRA==", + "peer": true, + "requires": { + "@aws-sdk/middleware-serde": "3.201.0", + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/signature-v4": "3.201.0", + "@aws-sdk/types": "3.201.0", + "@aws-sdk/url-parser": "3.201.0", + "@aws-sdk/util-config-provider": "3.201.0", + "@aws-sdk/util-middleware": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/middleware-endpoint-discovery": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.201.0.tgz", + "integrity": "sha512-EHQ+AH/bflmfM2qDN3Aa2ufhw2irv6+FE/13yW09KsunaKiuSyg7Clh6+T8ESw3fCaI4IE2ol0d6HNwJ4JLOVQ==", + "peer": true, + "requires": { + "@aws-sdk/config-resolver": "3.201.0", + "@aws-sdk/endpoint-cache": "3.201.0", + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/middleware-host-header": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.201.0.tgz", + "integrity": "sha512-7KNzdV7nFcKAoahvgGAlzsOq9FFDsU5h3w2iPtVdJhz6ZRDH/2v6WFeUCji+UNZip36gFfMPivoO8Y5smb5r/A==", + "peer": true, + "requires": { + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/middleware-logger": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.201.0.tgz", + "integrity": "sha512-kYLsa9x3oUJxYU7V5KOO50Kl7b0kk+I4ltkrdarLvvXcVI7ZXmWHzHLT2dkUhj8S0ceVdi0FYHVPJ3GoE8re4A==", + "peer": true, + "requires": { + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/middleware-recursion-detection": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.201.0.tgz", + "integrity": "sha512-NGOr+n559ZcJLdFoJR8LNGdrOJFIp2BTuWEDYeicNdNb0bETTXrkzcfT1BRhV9CWqCDmjFvjdrzbhS0cw/UUGA==", + "peer": true, + "requires": { + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/middleware-retry": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.201.0.tgz", + "integrity": "sha512-4jQjSKCpSc4oB1X9nNq4FbIAwQrr+mvmUSmg/oe2Llf42Ak1G9gg3rNTtQdfzA/wNMlL4ZFfF5Br+uz06e1hnQ==", + "peer": true, + "requires": { + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/service-error-classification": "3.201.0", + "@aws-sdk/types": "3.201.0", + "@aws-sdk/util-middleware": "3.201.0", + "tslib": "^2.3.1", + "uuid": "^8.3.2" + } + }, + "@aws-sdk/middleware-sdk-sts": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.201.0.tgz", + "integrity": "sha512-clZuXcoN0mAP4JH5C6pW5+0tdF25+fpFJqE7GNRjjH/NYNk6ImVI0Kq2espEWwVBuaS0/chTDK3b+pK8YOWdhw==", + "peer": true, + "requires": { + "@aws-sdk/middleware-signing": "3.201.0", + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/signature-v4": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/middleware-serde": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.201.0.tgz", + "integrity": "sha512-Z7AzIuqEDvsZmp80zeT1oYxsoB8uQZby20Z8kF6/vNoq3sIzaGf/wHeNn0p+Vgo2auGSbZcVUZKoDptQLSLwIQ==", + "peer": true, + "requires": { + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/middleware-signing": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.201.0.tgz", + "integrity": "sha512-08ri5+mB28tva9RjVIXFcUP5lRTx+Pj8C2HYqF2GL5H3uAo+h3RQ++fEG1uwUMLf7tCEFivcw6SHA1KmCnB7+w==", + "peer": true, + "requires": { + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/signature-v4": "3.201.0", + "@aws-sdk/types": "3.201.0", + "@aws-sdk/util-middleware": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/middleware-stack": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.201.0.tgz", + "integrity": "sha512-lqHYSBP5FBxzA5w5XiYYYpfXabFzleXonqRkqZts1tapNJ4sOd+itiKG8JoNP7LDOwJ8qxNW/a33/gQeh3wkwQ==", + "peer": true, + "requires": { + "tslib": "^2.3.1" + } + }, + "@aws-sdk/middleware-user-agent": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.201.0.tgz", + "integrity": "sha512-/rYZ93WN1gDJudXis/0382CEoTqRa4qZJA608u2EPWs5aiMocUrm7pjH5XvKm2OYX8K/lyaMSBvL2OTIMzXGaQ==", + "peer": true, + "requires": { + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/node-config-provider": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.201.0.tgz", + "integrity": "sha512-JO0K2qPTYn+pPC7g8rWr1oueg9CqGCkYbINuAuz79vjToOLUQnZT9GiFm7QADe6J6RT1oGEKRQabNaJnp8cFpQ==", + "peer": true, + "requires": { + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/shared-ini-file-loader": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/node-http-handler": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.201.0.tgz", + "integrity": "sha512-bWjXBd4WCiQcV4PwY+eFnlz9tZ4UiqfiJteav4MDt8YWkVlsVnR8RutmVSm3KZZjO2tJNSrla0ZWBebkNnI/Xg==", + "peer": true, + "requires": { + "@aws-sdk/abort-controller": "3.201.0", + "@aws-sdk/protocol-http": "3.201.0", + "@aws-sdk/querystring-builder": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/property-provider": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.201.0.tgz", + "integrity": "sha512-lVMP75VsYHIW04uYbkjA0I8Bb7b+aEj6PBBLdFoA22S0uCeJOD42OSr2Gtg2fToDGO7LQJw/K2D+LMCYKfZ3vQ==", + "peer": true, + "requires": { + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/protocol-http": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.201.0.tgz", + "integrity": "sha512-RdOc1elWFpj8MogxG87nkhtylw0a+OD7W8WFM+Gw4yJMkl7cwW42VIBFfb0+KCGZfIQltIeSLRvfe3WvVPyo7Q==", + "peer": true, + "requires": { + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/querystring-builder": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.201.0.tgz", + "integrity": "sha512-FgQnVHpYR19w/HmHEgWpykCn9tdogW0n45Ins6LBCo2aImDf9kBATD4xgN/F2rtogGuLGgu5LIIMHIOj1Tzs/w==", + "peer": true, + "requires": { + "@aws-sdk/types": "3.201.0", + "@aws-sdk/util-uri-escape": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/querystring-parser": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.201.0.tgz", + "integrity": "sha512-vS9Ljbqrwi0sIKYxgyZYJUN1AcE291hvuqwty9etgD2w/26SbWiMhjIW/fXJUOZjUvGKkYCpbivJYSzAGAuWfQ==", + "peer": true, + "requires": { + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/service-error-classification": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.201.0.tgz", + "integrity": "sha512-Pfcfmurgq8UpM0rXco6FVblcruqN4Mo3TW8/yaXrbctWpmdNT/8v19fffQIIgk94TU8Vf/nPJ7E5DXL7MZr4Fw==", + "peer": true + }, + "@aws-sdk/shared-ini-file-loader": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.201.0.tgz", + "integrity": "sha512-Pbxk0TXep0yI8MnK7Prly6JuBm5Me9AITav8/zPEgTZ3fMhXhQhhiuQcuTCI9GeosSzoiu8VvK53oPtBZZFnXQ==", + "peer": true, + "requires": { + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/signature-v4": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.201.0.tgz", + "integrity": "sha512-zEHoG1/hzJq169slggkPy1SN9YPWI78Bbe/MvHGYmCmQDspblu60JSBIbAatNqAxAmcWKc2HqpyGKjCkMG94ZA==", + "peer": true, + "requires": { + "@aws-sdk/is-array-buffer": "3.201.0", + "@aws-sdk/types": "3.201.0", + "@aws-sdk/util-hex-encoding": "3.201.0", + "@aws-sdk/util-middleware": "3.201.0", + "@aws-sdk/util-uri-escape": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/smithy-client": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.201.0.tgz", + "integrity": "sha512-cL87Jgxczee8YFkWGWKQ2Ze0vjn4+eCa1kDvEYMCOQvNujTuFgatXLgije5a7nVkSnL9WLoIP7Y7fsBGrKfMnQ==", + "peer": true, + "requires": { + "@aws-sdk/middleware-stack": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/types": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.201.0.tgz", + "integrity": "sha512-RCQj2pQyHD330Jd4c5CHJ87k2ZqC3Mmtl6nhwH1dy3vbnGUpc3q+3yinOKoTAY934kIa7ia32Y/2EjuyHxaj1A==", + "peer": true + }, + "@aws-sdk/url-parser": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.201.0.tgz", + "integrity": "sha512-V15aqj0tj4Y79VpuIdHUvX4Nvn4hYPB0RAn/qg5CCComIl0doLOirAQtW1MOBOyctdRlD9Uv7d1QdPLzJZMHjQ==", + "peer": true, + "requires": { + "@aws-sdk/querystring-parser": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-base64-browser": { + "version": "3.188.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64-browser/-/util-base64-browser-3.188.0.tgz", + "integrity": "sha512-qlH+5NZBLiyKziL335BEPedYxX6j+p7KFRWXvDQox9S+s+gLCayednpK+fteOhBenCcR9fUZOVuAPScy1I8qCg==", + "peer": true, + "requires": { + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-base64-node": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64-node/-/util-base64-node-3.201.0.tgz", + "integrity": "sha512-ydZqNpB3l5kiicInpPDExPb5xHI7uyVIa1vMupnuIrJ412iNb0F2+K8LlFynzw6fSJShVKnqFcWOYRA96z1iIw==", + "peer": true, + "requires": { + "@aws-sdk/util-buffer-from": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-body-length-browser": { + "version": "3.188.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.188.0.tgz", + "integrity": "sha512-8VpnwFWXhnZ/iRSl9mTf+VKOX9wDE8QtN4bj9pBfxwf90H1X7E8T6NkiZD3k+HubYf2J94e7DbeHs7fuCPW5Qg==", + "peer": true, + "requires": { + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-body-length-node": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.201.0.tgz", + "integrity": "sha512-q+gwQoLn/DOwirb2hgZJeEwo1D3vLhoD6FfSV42Ecfvtb4jHnWReWMHguujfCubuDgZCrMEvYQzuocS75HHsbA==", + "peer": true, + "requires": { + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-buffer-from": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.201.0.tgz", + "integrity": "sha512-s6Wjltd9vU+vR3n0pqSPmNDcrrkrVTdV4t7x2zz3nDsFKTI77iVNafDmuaUlOA/bIlpjCJqaWecoVrZmEKeR7A==", + "peer": true, + "requires": { + "@aws-sdk/is-array-buffer": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-config-provider": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.201.0.tgz", + "integrity": "sha512-cCRJlnRRP8vrLJomzJRBIyiyohsjJKmnIaQ9t0tAhGCywZbyjx6TlpYRZYfVWo+MwdF1Pi8ZScTrFPW0JuBOIQ==", + "peer": true, + "requires": { + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-defaults-mode-browser": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.201.0.tgz", + "integrity": "sha512-skRMAM+xrV/sDvvtHC81ExEKQEiZFaRrRdUT39fBX1SpGnFTo2wpv7XK+rAW2XopGgnLPytXLQD97Kub79o4zA==", + "peer": true, + "requires": { + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/types": "3.201.0", + "bowser": "^2.11.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-defaults-mode-node": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.201.0.tgz", + "integrity": "sha512-9N5LXRhxigbkbEcjQ4nNXHuQxp0VFlbc2/5wbcuPjIKX/OROiQI4mYQ6nuSKk7eku5sNFb9FtEHeD/RZo8od6Q==", + "peer": true, + "requires": { + "@aws-sdk/config-resolver": "3.201.0", + "@aws-sdk/credential-provider-imds": "3.201.0", + "@aws-sdk/node-config-provider": "3.201.0", + "@aws-sdk/property-provider": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-dynamodb": { + "version": "3.202.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.202.0.tgz", + "integrity": "sha512-rJqJ6XjtvM0GWi+g/T6BXXaICoCHOG1i++iznFoz9WE1xpCTd1dtXtjw29tfAuvF7f6+lIJQemc8TWoczxWj2g==", + "requires": { + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-endpoints": { + "version": "3.202.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.202.0.tgz", + "integrity": "sha512-sNees5uDp7nfEbvzaA1DAHqoEvEb9ZOkdNH5gcj/FMBETbr00YtsuXsTZogTHQsX/otRTiudZBE3iH7R4SLSAQ==", + "peer": true, + "requires": { + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-hex-encoding": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.201.0.tgz", + "integrity": "sha512-7t1vR1pVxKx0motd3X9rI3m/xNp78p3sHtP5yo4NP4ARpxyJ0fokBomY8ScaH2D/B+U5o9ARxldJUdMqyBlJcA==", + "peer": true, + "requires": { + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-locate-window": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.201.0.tgz", + "integrity": "sha512-hPJgifWh/rADabLAk1C9xXA2B3O4NUmbU58KgBRgC1HksiiHGFVZObB5fkBH8US/XV2jwORkpSf4OhretXQuKg==", + "peer": true, + "requires": { + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-middleware": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.201.0.tgz", + "integrity": "sha512-iAitcEZo17IyKn4ku1IBgtomr25esu5OuSRjw5Or4bNOeqXB0w50cItf/9qft8LIhbvBEAUtNAYXvqNzvhTZdQ==", + "peer": true, + "requires": { + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-uri-escape": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.201.0.tgz", + "integrity": "sha512-TeTWbGx4LU2c5rx0obHeDFeO9HvwYwQtMh1yniBz00pQb6Qt6YVOETVQikRZ+XRQwEyCg/dA375UplIpiy54mA==", + "peer": true, + "requires": { + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-user-agent-browser": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.201.0.tgz", + "integrity": "sha512-iL2gyz7GuUVtZcMZpqvfxdFrl9hc28qpagymmJ/w2yhN86YNPHdK8Sx1Yo6VxNGVDCCWGb7tHXf7VP+U4Yv/Lg==", + "peer": true, + "requires": { + "@aws-sdk/types": "3.201.0", + "bowser": "^2.11.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-user-agent-node": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.201.0.tgz", + "integrity": "sha512-6lhhvwB3AZSISnYQpDGdlyTrzfYK2P9QYjy7vZEBRd9TSOaggiFICXe03ZvZfVOSeg0EInlMKn1fIHzPUHRuHQ==", + "peer": true, + "requires": { + "@aws-sdk/node-config-provider": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-utf8-browser": { + "version": "3.188.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.188.0.tgz", + "integrity": "sha512-jt627x0+jE+Ydr9NwkFstg3cUvgWh56qdaqAMDsqgRlKD21md/6G226z/Qxl7lb1VEW2LlmCx43ai/37Qwcj2Q==", + "peer": true, + "requires": { + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-utf8-node": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-node/-/util-utf8-node-3.201.0.tgz", + "integrity": "sha512-A+bJFR/1rHYOJg137E69L1sX0I+LH+xf9ZjMXG9BVO0hSo7yDPoJVpHrzTJyOc3tuRITjIGBv9Qi4TKcoOSi1A==", + "peer": true, + "requires": { + "@aws-sdk/util-buffer-from": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-waiter": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-waiter/-/util-waiter-3.201.0.tgz", + "integrity": "sha512-NE8+BkPDXq86oyVr9EKN1s+iN8GID8mhj6DbtEZKZES3fJ36xH7MldRylgCewgv1Qpd1W00M4c/mVvUx3zp7sg==", + "peer": true, + "requires": { + "@aws-sdk/abort-controller": "3.201.0", + "@aws-sdk/types": "3.201.0", + "tslib": "^2.3.1" + } + }, + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@jest/expect-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", + "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", + "dev": true, + "requires": { + "jest-get-type": "^28.0.2" + } + }, + "@jest/schemas": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.24.1" + } + }, + "@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "dev": true, + "requires": { + "@jest/schemas": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "@sinclair/typebox": { + "version": "0.24.42", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.42.tgz", + "integrity": "sha512-d+2AtrHGyWek2u2ITF0lHRIv6Tt7X0dEHW+0rP+5aDCEjC3fiN2RBjrLD0yU0at52BcZbRGxLbAtXiR0hFCjYw==", + "dev": true + }, + "@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@sinonjs/samsam": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.1.1.tgz", + "integrity": "sha512-cZ7rKJTLiE7u7Wi/v9Hc2fs3Ucc3jrWeMgPHbbTCeVAB2S0wOBbYlkJVeNSL04i7fdhT8wIbDq1zhC/PXTD2SA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "28.1.8", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-28.1.8.tgz", + "integrity": "sha512-8TJkV++s7B6XqnDrzR1m/TT0A0h948Pnl/097veySPN67VRAgQ4gZ7n2KfJo2rVq6njQjdxU3GCCyDvAeuHoiw==", + "dev": true, + "requires": { + "expect": "^28.0.0", + "pretty-format": "^28.0.0" + } + }, + "@types/node": { + "version": "18.7.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.19.tgz", + "integrity": "sha512-Sq1itGUKUX1ap7GgZlrzdBydjbsJL/NSQt/4wkAxUJ7/OS5c2WkoN6WSpWc2Yc5wtKMZOUA0VCs/j2XJadN3HA==", + "dev": true + }, + "@types/sinon": { + "version": "10.0.13", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.13.tgz", + "integrity": "sha512-UVjDqJblVNQYvVNUsj0PuYYw0ELRmgt1Nt5Vk0pT5f16ROGfcKJY8o1HVuMOJOpD727RrGB9EGvoaTQE5tgxZQ==", + "dev": true, + "requires": { + "@types/sinonjs__fake-timers": "*" + } + }, + "@types/sinonjs__fake-timers": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", + "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", + "dev": true + }, + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "@types/yargs": { + "version": "17.0.12", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz", + "integrity": "sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "aws-sdk-client-mock": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aws-sdk-client-mock/-/aws-sdk-client-mock-2.0.0.tgz", + "integrity": "sha512-yjC39Ud78Cgwu9jLc7LoFILT8xtyvR9doCfd8tjeqaOI4oQ5IeTaIYDezWpWKndmrjXZzVLw/odWKP1hpuvsVQ==", + "dev": true, + "requires": { + "@types/sinon": "^10.0.10", + "sinon": "^11.1.1", + "tslib": "^2.1.0" + } + }, + "aws-sdk-client-mock-jest": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aws-sdk-client-mock-jest/-/aws-sdk-client-mock-jest-2.0.0.tgz", + "integrity": "sha512-HwJeJThngXDUyyyd3Hk12Ailu3smhQzyox+nrF14TxE/LNSJsSmJ2rXahzj7Zdyxn9jDSe2h2ulrfUNYgbPYlw==", + "dev": true, + "requires": { + "@types/jest": "^28.1.3", + "tslib": "^2.1.0" + } + }, + "bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "peer": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "ci-info": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.4.0.tgz", + "integrity": "sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true + }, + "diff-sequences": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", + "dev": true + }, + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + }, + "expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", + "dev": true, + "requires": { + "@jest/expect-utils": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3" + } + }, + "fast-xml-parser": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.11.tgz", + "integrity": "sha512-4aUg3aNRR/WjQAcpceODG1C3x3lFANXRo8+1biqfieHmg9pyMt7qB4lQV/Ta6sJCTbA5vfD8fnA8S54JATiFUA==", + "peer": true, + "requires": { + "strnum": "^1.0.5" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + }, + "jest-diff": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", + "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + } + }, + "jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true + }, + "jest-matcher-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", + "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + } + }, + "jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mnemonist": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.3.tgz", + "integrity": "sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==", + "peer": true, + "requires": { + "obliterator": "^1.6.1" + } + }, + "nise": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.1.tgz", + "integrity": "sha512-yr5kW2THW1AkxVmCnKEh4nbYkJdB3I7LUkiUgOvEkOp414mc2UMaHMA7pjq1nYowhdoJZGwEKGaQVbxfpWj10A==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": ">=5", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, + "obliterator": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-1.6.1.tgz", + "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==", + "peer": true + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dev": true, + "requires": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "sinon": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-11.1.2.tgz", + "integrity": "sha512-59237HChms4kg7/sXhiRcUzdSkKuydDeTiamT/jesUVHshBgL8XAmhgFo0GfK6RruMDM/iRSij1EybmMog9cJw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": "^7.1.2", + "@sinonjs/samsam": "^6.0.2", + "diff": "^5.0.0", + "nise": "^5.1.0", + "supports-color": "^7.2.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "stack-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + } + }, + "strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "peer": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "peer": true + } + } +} diff --git a/packages/idempotency/package.json b/packages/idempotency/package.json index e61559f02b..23d75bafb6 100644 --- a/packages/idempotency/package.json +++ b/packages/idempotency/package.json @@ -42,7 +42,8 @@ "url": "https://github.com/awslabs/aws-lambda-powertools-typescript/issues" }, "dependencies": { - "@aws-lambda-powertools/commons": "^1.2.1" + "@aws-lambda-powertools/commons": "^1.2.1", + "@aws-sdk/lib-dynamodb": "^3.170.0" }, "keywords": [ "aws", @@ -50,5 +51,9 @@ "powertools", "serverless", "nodejs" - ] -} \ No newline at end of file + ], + "devDependencies": { + "aws-sdk-client-mock": "^2.0.0", + "aws-sdk-client-mock-jest": "^2.0.0" + } +} diff --git a/packages/idempotency/src/EnvironmentVariablesService.ts b/packages/idempotency/src/EnvironmentVariablesService.ts new file mode 100644 index 0000000000..4d9892e307 --- /dev/null +++ b/packages/idempotency/src/EnvironmentVariablesService.ts @@ -0,0 +1,25 @@ +class EnvironmentVariablesService { + private lambdaFunctionNameVariable = 'AWS_LAMBDA_FUNCTION_NAME'; + + /** + * retrieve the value of an environment variable + * + * @param name the name of the environment variable + * @returns the value of the environment variable + */ + public get(name: string): string { + return process.env[name]?.trim() || ''; + } + /** + * retrieve the name of the Lambda function + * + * @returns the Lambda function name + */ + public getLambdaFunctionName(): string{ + return this.get(this.lambdaFunctionNameVariable); + } +} + +export { + EnvironmentVariablesService +}; \ No newline at end of file diff --git a/packages/idempotency/src/Exceptions.ts b/packages/idempotency/src/Exceptions.ts new file mode 100644 index 0000000000..405a59aa66 --- /dev/null +++ b/packages/idempotency/src/Exceptions.ts @@ -0,0 +1,17 @@ +class IdempotencyItemNotFoundError extends Error { + +} + +class IdempotencyItemAlreadyExistsError extends Error{ + +} + +class IdempotencyInvalidStatusError extends Error { + +} + +export { + IdempotencyItemNotFoundError, + IdempotencyItemAlreadyExistsError, + IdempotencyInvalidStatusError +}; \ No newline at end of file diff --git a/packages/idempotency/src/persistence/DynamoDbPersistenceLayer.ts b/packages/idempotency/src/persistence/DynamoDbPersistenceLayer.ts index 6592841b3f..97cc752533 100644 --- a/packages/idempotency/src/persistence/DynamoDbPersistenceLayer.ts +++ b/packages/idempotency/src/persistence/DynamoDbPersistenceLayer.ts @@ -1,18 +1,130 @@ -/* eslint-disable @typescript-eslint/no-empty-function */ -import { IdempotencyRecord, PersistenceLayer } from './PersistenceLayer'; +import { DynamoDB, DynamoDBServiceException } from '@aws-sdk/client-dynamodb'; +import { DynamoDBDocument, GetCommandOutput } from '@aws-sdk/lib-dynamodb'; +import { DynamoPersistenceConstructorOptions } from '../types/DynamoPersistenceConstructorOptions'; +import { IdempotencyItemAlreadyExistsError, IdempotencyItemNotFoundError } from '../Exceptions'; +import { IdempotencyRecordStatus } from '../types/IdempotencyRecordStatus'; +import { IdempotencyRecord } from './IdempotencyRecord'; +import { PersistenceLayer } from './PersistenceLayer'; class DynamoDBPersistenceLayer extends PersistenceLayer { - public constructor(_tableName: string, _key_attr: string = 'id') { + private dataAttr: string; + private expiryAttr: string; + private inProgressExpiryAttr: string; + private keyAttr: string; + private statusAttr: string; + private table: DynamoDBDocument | undefined; + private tableName: string; + + public constructor(constructorOptions: DynamoPersistenceConstructorOptions) { super(); + + this.tableName = constructorOptions.tableName; + this.keyAttr = constructorOptions.keyAttr ?? 'id'; + this.statusAttr = constructorOptions.statusAttr ?? 'status'; + this.expiryAttr = constructorOptions.expiryAttr ?? 'expiration'; + this.inProgressExpiryAttr = constructorOptions.inProgressExpiryAttr ?? 'in_progress_expiry_attr'; + this.dataAttr = constructorOptions.data_attr ?? 'data'; + } + + protected async _deleteRecord(record: IdempotencyRecord): Promise { + const table: DynamoDBDocument = this.getTable(); + await table.delete({ + TableName: this.tableName, Key: { [this.keyAttr]: record.idempotencyKey } + }); + } + + protected async _getRecord(idempotencyKey: string): Promise { + const table: DynamoDBDocument = this.getTable(); + const output: GetCommandOutput = await table.get( + { + TableName: this.tableName, Key: { + [this.keyAttr]: idempotencyKey + }, + ConsistentRead: true + } + ); + + if (!output.Item) { + throw new IdempotencyItemNotFoundError(); + } + + return new IdempotencyRecord({ + idempotencyKey: output.Item[this.keyAttr], + status: output.Item[this.statusAttr], + expiryTimestamp: output.Item[this.expiryAttr], + inProgressExpiryTimestamp: output.Item[this.inProgressExpiryAttr], + responseData: output.Item[this.dataAttr] + }); + } + + protected async _putRecord(_record: IdempotencyRecord): Promise { + const table: DynamoDBDocument = this.getTable(); + + const item = { + [this.keyAttr]: _record.idempotencyKey, + [this.expiryAttr]: _record.expiryTimestamp, + [this.statusAttr]: _record.getStatus() + }; + + const idempotencyKeyDoesNotExist = 'attribute_not_exists(#id)'; + const idempotencyKeyExpired = '#expiry < :now'; + const notInProgress = 'NOT #status = :inprogress'; + const conditionalExpression = `${idempotencyKeyDoesNotExist} OR ${idempotencyKeyExpired} OR ${notInProgress}`; + try { + await table.put({ + TableName: this.tableName, + Item: item, + ExpressionAttributeNames: { + '#id': this.keyAttr, + '#expiry': this.expiryAttr, + '#status': this.statusAttr + }, + ExpressionAttributeValues: { + ':now': Date.now() / 1000, + ':inprogress': IdempotencyRecordStatus.INPROGRESS + }, + ConditionExpression: conditionalExpression + } + ); + } catch (e){ + if ((e as DynamoDBServiceException).name === 'ConditionalCheckFailedException'){ + throw new IdempotencyItemAlreadyExistsError(); + } + + throw e; + } } - protected async _deleteRecord(): Promise {} - protected async _getRecord(): Promise { - return Promise.resolve({} as IdempotencyRecord); + + protected async _updateRecord(record: IdempotencyRecord): Promise { + const table: DynamoDBDocument = this.getTable(); + await table.update( + { + TableName: this.tableName, + Key: { + [this.keyAttr]: record.idempotencyKey + }, + UpdateExpression: 'SET #status = :status, #expiry = :expiry', + ExpressionAttributeNames: { + '#status': this.statusAttr, + '#expiry': this.expiryAttr + }, + ExpressionAttributeValues: { + ':status': record.getStatus(), + ':expiry': record.expiryTimestamp + } + } + ); + } + + private getTable(): DynamoDBDocument { + if (!this.table) + this.table = DynamoDBDocument.from(new DynamoDB({}), { marshallOptions: { removeUndefinedValues: true } }); + + return this.table; } - protected async _putRecord(_record: IdempotencyRecord): Promise {} - protected async _updateRecord(): Promise {} } export { DynamoDBPersistenceLayer -}; \ No newline at end of file +}; + diff --git a/packages/idempotency/src/persistence/IdempotencyRecord.ts b/packages/idempotency/src/persistence/IdempotencyRecord.ts index 7f8fac70ec..b512cc5786 100644 --- a/packages/idempotency/src/persistence/IdempotencyRecord.ts +++ b/packages/idempotency/src/persistence/IdempotencyRecord.ts @@ -1,25 +1,41 @@ -import { IdempotencyRecordStatus } from 'types/IdempotencyRecordStatus'; +import { IdempotencyRecordOptions } from 'types/IdempotencyRecordOptions'; +import { IdempotencyInvalidStatusError } from '../Exceptions'; +import { IdempotencyRecordStatus } from '../types/IdempotencyRecordStatus'; class IdempotencyRecord { + public expiryTimestamp: number | undefined; + public idempotencyKey: string; + public inProgressExpiryTimestamp: number | undefined; + public payloadHash: string | undefined; + public responseData: Record | undefined; + private status: IdempotencyRecordStatus; + + public constructor(constructorOptions: IdempotencyRecordOptions) { + this.idempotencyKey = constructorOptions.idempotencyKey; + this.expiryTimestamp = constructorOptions.expiryTimestamp; + this.inProgressExpiryTimestamp = constructorOptions.inProgressExpiryTimestamp; + this.responseData = constructorOptions.responseData; + this.payloadHash = constructorOptions.payloadHash; + this.status = constructorOptions.status; + } + + public getResponse(): Record | undefined { + return this.responseData; + } - public constructor(public idempotencyKey: string, - private status: IdempotencyRecordStatus, - public expiryTimestamp: number | undefined, - public inProgressExpiryTimestamp: number | undefined, - public responseData: string |undefined, - public payloadHash: string | undefined) {} - public getStatus(): IdempotencyRecordStatus { - return IdempotencyRecordStatus.INPROGRESS; + if (this.isExpired()) { + return IdempotencyRecordStatus.EXPIRED; + } else if (Object.values(IdempotencyRecordStatus).includes(this.status)) { + return this.status; + } else { + throw new IdempotencyInvalidStatusError(); + } } - - public isExpired(): boolean { - return false; + + private isExpired(): boolean { + return this.expiryTimestamp !== undefined && ((Date.now() / 1000) > this.expiryTimestamp); } - - public responseJsonAsObject(): Record | undefined { - return; - } } export { diff --git a/packages/idempotency/src/persistence/PersistenceLayer.ts b/packages/idempotency/src/persistence/PersistenceLayer.ts index 1cd2c54411..d64c3d1546 100644 --- a/packages/idempotency/src/persistence/PersistenceLayer.ts +++ b/packages/idempotency/src/persistence/PersistenceLayer.ts @@ -1,24 +1,154 @@ -/* eslint-disable @typescript-eslint/no-empty-function */ +import { BinaryToTextEncoding, createHash, Hash } from 'crypto'; +import { IdempotencyRecordStatus } from '../types/IdempotencyRecordStatus'; +import { EnvironmentVariablesService } from '../EnvironmentVariablesService'; import { IdempotencyRecord } from './IdempotencyRecord'; import { PersistenceLayerInterface } from './PersistenceLayerInterface'; abstract class PersistenceLayer implements PersistenceLayerInterface { - public constructor() { } - public configure(_functionName: string = ''): void {} - public async deleteRecord(): Promise { } - public async getRecord(): Promise { - return Promise.resolve({} as IdempotencyRecord); + + // envVarsService is always initialized in the constructor + private envVarsService!: EnvironmentVariablesService; + + private expiresAfterSeconds: number; + + private functionName: string = ''; + + private hashDigest: BinaryToTextEncoding; + + private hashFunction: string; + + public constructor() { + this.setEnvVarsService(); + this.expiresAfterSeconds = 60 * 60; //one hour is the default expiration + this.hashFunction = 'md5'; + this.hashDigest = 'base64'; + } - public async saveInProgress(): Promise { } - public async saveSuccess(): Promise { } + public configure(functionName: string = ''): void { + this.functionName = this.getEnvVarsService().getLambdaFunctionName() + '.' + functionName; + } + + /** + * Deletes a record from the persistence store for the persistence key generated from the data passed in. + * + * @param data - the data payload that will be hashed to create the hash portion of the idempotency key + */ + public async deleteRecord(data: unknown): Promise { + const idempotencyRecord: IdempotencyRecord = new IdempotencyRecord({ + idempotencyKey: this.getHashedIdempotencyKey(data), + status: IdempotencyRecordStatus.EXPIRED + }); + + this._deleteRecord(idempotencyRecord); + } + /** + * Retrieves idempotency key for the provided data and fetches data for that key from the persistence store + * + * @param data - the data payload that will be hashed to create the hash portion of the idempotency key + */ + public async getRecord(data: unknown): Promise { + const idempotencyKey: string = this.getHashedIdempotencyKey(data); + + return this._getRecord(idempotencyKey); + } + + /** + * Saves a record indicating that the function's execution is currently in progress + * + * @param data - the data payload that will be hashed to create the hash portion of the idempotency key + */ + public async saveInProgress(data: unknown): Promise { + const idempotencyRecord: IdempotencyRecord = + new IdempotencyRecord({ + idempotencyKey: this.getHashedIdempotencyKey(data), + status: IdempotencyRecordStatus.INPROGRESS, + expiryTimestamp: this.getExpiryTimestamp() + }); + + return this._putRecord(idempotencyRecord); + } + + /** + * Saves a record of the function completing successfully. This will create a record with a COMPLETED status + * and will save the result of the completed function in the idempotency record. + * + * @param data - the data payload that will be hashed to create the hash portion of the idempotency key + * @param result - the result of the successfully completed function + */ + public async saveSuccess(data: unknown, result: Record): Promise { + const idempotencyRecord: IdempotencyRecord = + new IdempotencyRecord({ + idempotencyKey: this.getHashedIdempotencyKey(data), + status: IdempotencyRecordStatus.COMPLETED, + expiryTimestamp: this.getExpiryTimestamp(), + responseData: result + }); - protected abstract _deleteRecord(): Promise; - protected abstract _getRecord(): Promise; + this._updateRecord(idempotencyRecord); + + } + + protected abstract _deleteRecord(record: IdempotencyRecord): Promise; + protected abstract _getRecord(idempotencyKey: string): Promise; protected abstract _putRecord(record: IdempotencyRecord): Promise; - protected abstract _updateRecord(): Promise; + protected abstract _updateRecord(record: IdempotencyRecord): Promise; + + /** + * Generates a hash of the data and returns the digest of that hash + * + * @param data the data payload that will generate the hash + * @returns the digest of the generated hash + */ + private generateHash(data: string): string{ + const hash: Hash = createHash(this.hashFunction); + hash.update(data); + + return hash.digest(this.hashDigest); + } + + /** + * Getter for `envVarsService`. + * Used internally during initialization. + */ + private getEnvVarsService(): EnvironmentVariablesService { + return this.envVarsService; + } + + /** + * Creates the expiry timestamp for the idempotency record + * + * @returns the expiry time for the record expressed as number of seconds past the UNIX epoch + */ + private getExpiryTimestamp(): number { + const currentTime: number = Date.now() / 1000; + + return currentTime + this.expiresAfterSeconds; + } + + /** + * Generates the idempotency key used to identify records in the persistence store. + * + * @param data the data payload that will be hashed to create the hash portion of the idempotency key + * @returns the idempotency key + */ + private getHashedIdempotencyKey(data: unknown): string { + if (!data){ + console.warn('No data found for idempotency key'); + } + + return this.functionName + '#' + this.generateHash(JSON.stringify(data)); + } + + /** + * Setter and initializer for `envVarsService`. + * Used internally during initialization. + */ + private setEnvVarsService(): void { + this.envVarsService = new EnvironmentVariablesService(); + } + } export { - IdempotencyRecord, PersistenceLayer }; \ No newline at end of file diff --git a/packages/idempotency/src/persistence/PersistenceLayerInterface.ts b/packages/idempotency/src/persistence/PersistenceLayerInterface.ts index 4b2da46b38..e6bce97fde 100644 --- a/packages/idempotency/src/persistence/PersistenceLayerInterface.ts +++ b/packages/idempotency/src/persistence/PersistenceLayerInterface.ts @@ -1,11 +1,11 @@ -import { IdempotencyRecord } from './PersistenceLayer'; +import { IdempotencyRecord } from './IdempotencyRecord'; interface PersistenceLayerInterface { configure(functionName: string): void - saveInProgress(): Promise - saveSuccess(): Promise - deleteRecord(): Promise - getRecord(): Promise + saveInProgress(data: unknown): Promise + saveSuccess(data: unknown, result: unknown): Promise + deleteRecord(data: unknown): Promise + getRecord(data: unknown): Promise } export { PersistenceLayerInterface }; diff --git a/packages/idempotency/src/persistence/index.ts b/packages/idempotency/src/persistence/index.ts new file mode 100644 index 0000000000..7256fa4e7b --- /dev/null +++ b/packages/idempotency/src/persistence/index.ts @@ -0,0 +1,4 @@ +export * from './DynamoDbPersistenceLayer'; +export * from './PersistenceLayer'; +export * from './PersistenceLayerInterface'; +export * from './IdempotencyRecord'; \ No newline at end of file diff --git a/packages/idempotency/src/types/DynamoPersistenceConstructorOptions.ts b/packages/idempotency/src/types/DynamoPersistenceConstructorOptions.ts new file mode 100644 index 0000000000..c2308b362e --- /dev/null +++ b/packages/idempotency/src/types/DynamoPersistenceConstructorOptions.ts @@ -0,0 +1,12 @@ +type DynamoPersistenceConstructorOptions = { + tableName: string + keyAttr?: string + statusAttr?: string + expiryAttr?: string + inProgressExpiryAttr?: string + data_attr?: string +}; + +export { + DynamoPersistenceConstructorOptions +}; \ No newline at end of file diff --git a/packages/idempotency/src/types/IdempotencyRecordOptions.ts b/packages/idempotency/src/types/IdempotencyRecordOptions.ts new file mode 100644 index 0000000000..07c9b91bb3 --- /dev/null +++ b/packages/idempotency/src/types/IdempotencyRecordOptions.ts @@ -0,0 +1,14 @@ +import { IdempotencyRecordStatus } from './IdempotencyRecordStatus'; + +type IdempotencyRecordOptions = { + idempotencyKey: string + status: IdempotencyRecordStatus + expiryTimestamp?: number + inProgressExpiryTimestamp?: number + responseData?: Record + payloadHash?: string +}; + +export { + IdempotencyRecordOptions +}; \ No newline at end of file diff --git a/packages/idempotency/src/types/index.ts b/packages/idempotency/src/types/index.ts new file mode 100644 index 0000000000..850ccbd19c --- /dev/null +++ b/packages/idempotency/src/types/index.ts @@ -0,0 +1,2 @@ +export * from './AnyFunction'; +export * from './IdempotencyRecordStatus'; \ No newline at end of file diff --git a/packages/idempotency/tests/helpers/populateEnvironmentVariables.ts b/packages/idempotency/tests/helpers/populateEnvironmentVariables.ts new file mode 100644 index 0000000000..42b8feeb63 --- /dev/null +++ b/packages/idempotency/tests/helpers/populateEnvironmentVariables.ts @@ -0,0 +1,2 @@ +// Reserved variables +process.env.AWS_REGION = 'us-east-1'; diff --git a/packages/idempotency/tests/unit/EnvironmentVariableService.test.ts b/packages/idempotency/tests/unit/EnvironmentVariableService.test.ts new file mode 100644 index 0000000000..2480c328cb --- /dev/null +++ b/packages/idempotency/tests/unit/EnvironmentVariableService.test.ts @@ -0,0 +1,34 @@ +/** + * Test EnvironmentVariableService class + * + * @group unit/idempotency/all + */ +import { EnvironmentVariablesService } from '../../src/EnvironmentVariablesService'; + +describe('Class: EnvironmentVariableService', ()=> { + describe('Method: getLambdaFunctionName', ()=> { + beforeEach(() => { + process.env.AWS_LAMBDA_FUNCTION_NAME = 'testFunction'; + }); + + afterEach(()=> { + delete process.env.AWS_LAMBDA_FUNCTION_NAME; + }); + + test('When called, it gets the Lambda function name from the environment variable', ()=> { + const expectedName = process.env.AWS_LAMBDA_FUNCTION_NAME; + + const lambdaName = new EnvironmentVariablesService().getLambdaFunctionName(); + + expect(lambdaName).toEqual(expectedName); + }); + + test('When called without the environment variable set, it returns an empty string', ()=> { + delete process.env.AWS_LAMBDA_FUNCTION_NAME; + + const lambdaName = new EnvironmentVariablesService().getLambdaFunctionName(); + + expect(lambdaName).toEqual(''); + }); + }); +}); \ No newline at end of file diff --git a/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts b/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts new file mode 100644 index 0000000000..8ca16818dd --- /dev/null +++ b/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts @@ -0,0 +1,282 @@ +import { DeleteCommand, DynamoDBDocument, GetCommand, PutCommand, UpdateCommand } from '@aws-sdk/lib-dynamodb'; +import { mockClient } from 'aws-sdk-client-mock'; +import 'aws-sdk-client-mock-jest'; +import { IdempotencyItemAlreadyExistsError, IdempotencyItemNotFoundError } from '../../../src/Exceptions'; +import { DynamoDBPersistenceLayer } from '../../../src/persistence/DynamoDbPersistenceLayer'; +import { IdempotencyRecord } from '../../../src/persistence/IdempotencyRecord'; +import { IdempotencyRecordStatus } from '../../../src/types/IdempotencyRecordStatus'; + +/** + * Test DynamoDBPersistenceLayer class + * + * @group unit/idempotency/all + */ + +describe('Class: DynamoDbPersistenceLayer', () => { + class TestDynamoPersistenceLayer extends DynamoDBPersistenceLayer { + public _deleteRecord(record: IdempotencyRecord): Promise { + return super._deleteRecord(record); + } + + public _getRecord(idempotencyKey: string): Promise { + return super._getRecord(idempotencyKey); + } + + public _putRecord(_record: IdempotencyRecord): Promise { + return super._putRecord(_record); + } + + public _updateRecord(record: IdempotencyRecord): Promise { + return super._updateRecord(record); + } + } + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('Method: _putRecord', () => { + const currentDateInMilliseconds = 1000; + const currentDateInSeconds = 1; + + beforeEach(() => { + jest.spyOn(Date, 'now').mockReturnValue(currentDateInMilliseconds); + }); + + test('when called with a record that meets conditions, it puts record in dynamo table', async () => { + // Prepare + const tableName = 'tableName'; + const persistenceLayer = new TestDynamoPersistenceLayer({ tableName }); + + const key = 'key'; + const status = IdempotencyRecordStatus.EXPIRED; + const expiryTimestamp = 0; + const inProgressExpiryTimestamp = 0; + const record = new IdempotencyRecord({ + idempotencyKey: key, + status, + expiryTimestamp, + inProgressExpiryTimestamp + }); + const dynamoClient = mockClient(DynamoDBDocument).on(PutCommand).resolves({}); + + // Act + await persistenceLayer._putRecord(record); + + // Assess + expect(dynamoClient).toReceiveCommandWith(PutCommand, { + TableName: tableName, + Item: { 'id': key, 'expiration': expiryTimestamp, status: status }, + ExpressionAttributeNames: { '#id': 'id', '#expiry': 'expiration', '#status': 'status' }, + ExpressionAttributeValues: { ':now': currentDateInSeconds, ':inprogress': IdempotencyRecordStatus.INPROGRESS }, + ConditionExpression: 'attribute_not_exists(#id) OR #expiry < :now OR NOT #status = :inprogress' + }); + }); + + test('when called with a record that fails any condition, it throws IdempotencyItemAlreadyExistsError', async () => { + // Prepare + const tableName = 'tableName'; + const persistenceLayer = new TestDynamoPersistenceLayer({ tableName }); + + const key = 'key'; + const status = IdempotencyRecordStatus.EXPIRED; + const expiryTimestamp = 0; + const inProgressExpiryTimestamp = 0; + const record = new IdempotencyRecord({ + idempotencyKey: key, + status, + expiryTimestamp, + inProgressExpiryTimestamp + }); + + const dynamoClient = mockClient(DynamoDBDocument).on(PutCommand).rejects({ name: 'ConditionalCheckFailedException' }); + + // Act + let error: unknown; + try { + await persistenceLayer._putRecord(record); + } catch (e){ + error = e; + } + + // Assess + expect(dynamoClient).toReceiveCommandWith(PutCommand, { + TableName: tableName, + Item: { 'id': key, 'expiration': expiryTimestamp, status: status }, + ExpressionAttributeNames: { '#id': 'id', '#expiry': 'expiration', '#status': 'status' }, + ExpressionAttributeValues: { ':now': currentDateInSeconds, ':inprogress': IdempotencyRecordStatus.INPROGRESS }, + ConditionExpression: 'attribute_not_exists(#id) OR #expiry < :now OR NOT #status = :inprogress' + }); + expect(error).toBeInstanceOf(IdempotencyItemAlreadyExistsError); + }); + + test('when encountering an unknown error, it throws the causing error', async () => { + // Prepare + const tableName = 'tableName'; + const persistenceLayer = new TestDynamoPersistenceLayer({ tableName }); + + const key = 'key'; + const status = IdempotencyRecordStatus.EXPIRED; + const expiryTimestamp = 0; + const inProgressExpiryTimestamp = 0; + const record = new IdempotencyRecord({ + idempotencyKey: key, + status, + expiryTimestamp, + inProgressExpiryTimestamp + }); + + const dynamoClient = mockClient(DynamoDBDocument).on(PutCommand).rejects(new Error()); + + // Act + let error: unknown; + try { + await persistenceLayer._putRecord(record); + } catch (e){ + error = e; + } + + // Assess + expect(dynamoClient).toReceiveCommandWith(PutCommand, { + TableName: tableName, + Item: { 'id': key, 'expiration': expiryTimestamp, status: status }, + ExpressionAttributeNames: { '#id': 'id', '#expiry': 'expiration', '#status': 'status' }, + ExpressionAttributeValues: { ':now': currentDateInSeconds, ':inprogress': IdempotencyRecordStatus.INPROGRESS }, + ConditionExpression: 'attribute_not_exists(#id) OR #expiry < :now OR NOT #status = :inprogress' + }); + expect(error).toBe(error); + }); + }); + + describe('Method: _getRecord', () => { + test('when called with a record whose key exists, it gets the correct record', async () => { + // Prepare + const tableName = 'tableName'; + const persistenceLayer = new TestDynamoPersistenceLayer({ tableName }); + + const key = 'key'; + + const status = IdempotencyRecordStatus.INPROGRESS; + const expiryTimestamp = 10; + const inProgressExpiryTimestamp = 10; + const responseData = {}; + const dynamoClient = mockClient(DynamoDBDocument).on(GetCommand).resolves({ + Item: { + id: key, + status, + 'expiration': expiryTimestamp, + 'in_progress_expiry_attr': inProgressExpiryTimestamp, + data: responseData + } + }); + jest.spyOn(Date, 'now').mockReturnValue(0); + + // Act + const record: IdempotencyRecord = await persistenceLayer._getRecord(key); + + // Assess + expect(dynamoClient).toReceiveCommandWith(GetCommand, { + TableName: tableName, + Key: { + id: key + }, + ConsistentRead: true + }); + expect(record.getStatus()).toEqual(IdempotencyRecordStatus.INPROGRESS); + expect(record.idempotencyKey).toEqual(key); + expect(record.inProgressExpiryTimestamp).toEqual(inProgressExpiryTimestamp); + expect(record.responseData).toEqual(responseData); + expect(record.expiryTimestamp).toEqual(expiryTimestamp); + }); + + test('when called with a record whose key does not exist, it throws IdempotencyItemNotFoundError', async () => { + // Prepare + const tableName = 'tableName'; + const persistenceLayer = new TestDynamoPersistenceLayer({ tableName }); + + const key = 'key'; + + const dynamoClient = mockClient(DynamoDBDocument).on(GetCommand).resolves({ Item: undefined }); + jest.spyOn(Date, 'now').mockReturnValue(0); + + // Act + let error: unknown; + try { + await persistenceLayer._getRecord(key); + } catch (e){ + error = e; + } + + // Assess + expect(dynamoClient).toReceiveCommandWith(GetCommand, { + TableName: tableName, + Key: { + id: key + }, + ConsistentRead: true + }); + expect(error).toBeInstanceOf(IdempotencyItemNotFoundError); + }); + }); + + describe('Method: _updateRecord', () => { + test('when called to update a record, it resolves successfully', async () => { + // Prepare + const tableName = 'tableName'; + const persistenceLayer = new TestDynamoPersistenceLayer({ tableName }); + + const key = 'key'; + const status = IdempotencyRecordStatus.EXPIRED; + const expiryTimestamp = 0; + const inProgressExpiryTimestamp = 0; + const record = new IdempotencyRecord({ + idempotencyKey: key, + status, + expiryTimestamp, + inProgressExpiryTimestamp + }); + const dynamoClient = mockClient(DynamoDBDocument).on(UpdateCommand).resolves({}); + + // Act + await persistenceLayer._updateRecord(record); + + // Assess + expect(dynamoClient).toReceiveCommandWith(UpdateCommand, { + TableName: tableName, + Key: { id: key }, + UpdateExpression: 'SET #status = :status, #expiry = :expiry', + ExpressionAttributeNames: { '#status': 'status', '#expiry': 'expiration' }, + ExpressionAttributeValues: { ':status': IdempotencyRecordStatus.EXPIRED,':expiry': expiryTimestamp }, + }); + }); + }); + + describe('Method: _deleteRecord', () => { + test('when called with a valid record, record is deleted', async () => { + // Prepare + const tableName = 'tableName'; + const persistenceLayer = new TestDynamoPersistenceLayer({ tableName }); + + const key = 'key'; + const status = IdempotencyRecordStatus.EXPIRED; + const expiryTimestamp = 0; + const inProgressExpiryTimestamp = 0; + const record = new IdempotencyRecord({ + idempotencyKey: key, + status, + expiryTimestamp, + inProgressExpiryTimestamp + }); + const dynamoClient = mockClient(DynamoDBDocument).on(DeleteCommand).resolves({}); + + // Act + await persistenceLayer._deleteRecord(record); + + // Assess + expect(dynamoClient).toReceiveCommandWith(DeleteCommand, { + TableName: tableName, + Key: { id: key } + }); + }); + }); +}); \ No newline at end of file diff --git a/packages/idempotency/tests/unit/persistence/IdempotencyRecord.test.ts b/packages/idempotency/tests/unit/persistence/IdempotencyRecord.test.ts new file mode 100644 index 0000000000..7f0c00db04 --- /dev/null +++ b/packages/idempotency/tests/unit/persistence/IdempotencyRecord.test.ts @@ -0,0 +1,97 @@ +import { IdempotencyInvalidStatusError } from '../../../src/Exceptions'; +import { IdempotencyRecord } from '../../../src/persistence/IdempotencyRecord'; +import { IdempotencyRecordStatus } from '../../../src/types/IdempotencyRecordStatus'; +/** + * Test IdempotencyRecord class + * + * @group unit/idempotency/all + */ +const mockIdempotencyKey = '123'; +const mockData = undefined; +const mockInProgressExpiry = 123; +const mockPayloadHash = '123'; + +describe('Given an INPROGRESS record that has already expired', () => { + let idempotencyRecord: IdempotencyRecord; + beforeEach(() => { + const mockNowAfterExpiryTime = 1487076708000; + const expiryTimeBeforeNow = 1487076707; + Date.now = jest.fn(() => mockNowAfterExpiryTime); + idempotencyRecord = new IdempotencyRecord({ + idempotencyKey: mockIdempotencyKey, + status: IdempotencyRecordStatus.INPROGRESS, + expiryTimestamp: expiryTimeBeforeNow, + inProgressExpiryTimestamp: mockInProgressExpiry, + responseData: mockData, + payloadHash: mockPayloadHash + }); + }); + describe('When checking the status of the idempotency record', () => { + let resultingStatus: IdempotencyRecordStatus; + beforeEach(() => { + resultingStatus = idempotencyRecord.getStatus(); + }); + + test('Then the status is EXPIRED', () => { + expect(resultingStatus).toEqual(IdempotencyRecordStatus.EXPIRED); + }); + }); +}); + +describe('Given an idempotency record that is not expired', () => { + let idempotencyRecord: IdempotencyRecord; + beforeEach(() => { + const mockNowBeforeExiryTime = 1487076707000; + const expiryTimeAfterNow = 1487076708; + Date.now = jest.fn(() => mockNowBeforeExiryTime); + idempotencyRecord = new IdempotencyRecord({ + idempotencyKey: mockIdempotencyKey, + status: IdempotencyRecordStatus.INPROGRESS, + expiryTimestamp: expiryTimeAfterNow, + inProgressExpiryTimestamp: mockInProgressExpiry, + responseData: mockData, + payloadHash: mockPayloadHash + }); + }); + describe('When checking the status of the idempotency record', () => { + test('Then the status is EXPIRED', () => { + expect(idempotencyRecord.getStatus()).toEqual(IdempotencyRecordStatus.INPROGRESS); + }); + + test('Then the record is returned', () => { + expect(idempotencyRecord.getResponse()).toEqual(mockData); + }); + }); +}); + +describe('Given an idempotency record that has a status not in the IdempotencyRecordStatus enum', () => { + let idempotencyRecord: IdempotencyRecord; + beforeEach(() => { + const mockNowBeforeExiryTime = 1487076707000; + const expiryTimeAfterNow = 1487076708; + Date.now = jest.fn(() => mockNowBeforeExiryTime); + idempotencyRecord = new IdempotencyRecord({ + idempotencyKey: mockIdempotencyKey, + status: 'NOT_A_STATUS' as IdempotencyRecordStatus, + expiryTimestamp: expiryTimeAfterNow, + inProgressExpiryTimestamp: mockInProgressExpiry, + responseData: mockData, + payloadHash: mockPayloadHash + }); + }); + describe('When checking the status of the idempotency record', () => { + let resultingError: Error; + beforeEach(() => { + try { + idempotencyRecord.getStatus(); + } catch (e: unknown) { + resultingError = e as Error; + } + + }); + + test('Then an IdempotencyInvalidStatusError is thrown ', () => { + expect(resultingError).toBeInstanceOf(IdempotencyInvalidStatusError); + }); + }); +}); \ No newline at end of file diff --git a/packages/idempotency/tests/unit/persistence/PersistenceLayer.test.ts b/packages/idempotency/tests/unit/persistence/PersistenceLayer.test.ts new file mode 100644 index 0000000000..ee899c3342 --- /dev/null +++ b/packages/idempotency/tests/unit/persistence/PersistenceLayer.test.ts @@ -0,0 +1,251 @@ +/** + * Test PersistenceLayer class + * + * @group unit/idempotency/all + */ +import { createHash, Hash } from 'crypto'; +import { EnvironmentVariablesService } from '../../../src/EnvironmentVariablesService'; +import { IdempotencyRecord, PersistenceLayer } from '../../../src/persistence'; +import { IdempotencyRecordStatus } from '../../../src/types/IdempotencyRecordStatus'; + +jest.mock('crypto', () => ({ + createHash: jest.fn(), +})); + +const cryptoUpdateMock = jest.fn(); +const cryptoDigestMock = jest.fn(); +const mockDigest = 'hashDigest'; + +describe('Class: Persistence Layer', ()=> { + + const deleteRecord = jest.fn(); + const getRecord = jest.fn(); + const putRecord = jest.fn(); + const updateRecord = jest.fn(); + + class PersistenceLayerTestClass extends PersistenceLayer { + + protected _deleteRecord = deleteRecord; + + protected _getRecord = getRecord; + + protected _putRecord = putRecord; + + protected _updateRecord = updateRecord; + } + + describe('Method: saveInProgress', ()=> { + beforeEach(()=> { + putRecord.mockClear(); + (createHash as jest.MockedFunction).mockReturnValue( + { + update: cryptoUpdateMock, + digest: cryptoDigestMock.mockReturnValue(mockDigest) + } as unknown as Hash + ); + }); + + test('When called, it saves an IN_PROGRESS idempotency record via _putRecord()', async ()=> { + const data = 'someData'; + const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); + + await persistenceLayer.saveInProgress(data); + + const savedIdempotencyRecord: IdempotencyRecord = putRecord.mock.calls[0][0]; + expect(savedIdempotencyRecord.getStatus()).toBe(IdempotencyRecordStatus.INPROGRESS); + }); + + test('When called, it creates an idempotency key from the function name and a digest of the md5 hash of the data', async ()=> { + const data = 'someData'; + const lambdaFunctionName = 'LambdaName'; + jest.spyOn(EnvironmentVariablesService.prototype, 'getLambdaFunctionName').mockReturnValue(lambdaFunctionName); + + const functionName = 'functionName'; + + const expectedIdempotencyKey = lambdaFunctionName + '.' + functionName + '#' + mockDigest; + const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); + persistenceLayer.configure(functionName); + + await persistenceLayer.saveInProgress(data); + + const savedIdempotencyRecord: IdempotencyRecord = putRecord.mock.calls[0][0]; + + expect(createHash).toHaveBeenCalledWith( + expect.stringMatching('md5'), + ); + expect(cryptoUpdateMock).toHaveBeenCalledWith(expect.stringMatching(data)); + expect(cryptoDigestMock).toHaveBeenCalledWith( + expect.stringMatching('base64') + ); + expect(savedIdempotencyRecord.idempotencyKey).toEqual(expectedIdempotencyKey); + }); + + test('When called without a function name, it creates an idempotency key from the Lambda name only and a digest of the md5 hash of the data', async ()=> { + const data = 'someData'; + const lambdaFunctionName = 'LambdaName'; + jest.spyOn(EnvironmentVariablesService.prototype, 'getLambdaFunctionName').mockReturnValue(lambdaFunctionName); + + const expectedIdempotencyKey = lambdaFunctionName + '.' + '#' + mockDigest; + const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); + persistenceLayer.configure(); + + await persistenceLayer.saveInProgress(data); + + const savedIdempotencyRecord: IdempotencyRecord = putRecord.mock.calls[0][0]; + + expect(createHash).toHaveBeenCalledWith( + expect.stringMatching('md5'), + ); + expect(cryptoUpdateMock).toHaveBeenCalledWith(expect.stringMatching(data)); + expect(cryptoDigestMock).toHaveBeenCalledWith( + expect.stringMatching('base64') + ); + expect(savedIdempotencyRecord.idempotencyKey).toEqual(expectedIdempotencyKey); + }); + + test('When called, it sets the expiry timestamp to one hour in the future', async ()=> { + const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); + const data = 'someData'; + const currentMillisTime = 3000; + const currentSeconds = currentMillisTime / 1000; + const oneHourSeconds = 60 * 60; + jest.spyOn(Date, 'now').mockReturnValue(currentMillisTime); + + await persistenceLayer.saveInProgress(data); + + const savedIdempotencyRecord: IdempotencyRecord = putRecord.mock.calls[0][0]; + expect(savedIdempotencyRecord.expiryTimestamp).toEqual(currentSeconds + oneHourSeconds); + + }); + + test('When called without data, it logs a warning', async ()=> { + const consoleSpy = jest.spyOn(console, 'warn'); + const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); + + await persistenceLayer.saveInProgress(''); + expect(consoleSpy).toHaveBeenCalled(); + }); + + }); + + describe('Method: saveSuccess', ()=> { + beforeEach(()=> { + updateRecord.mockClear(); + (createHash as jest.MockedFunction).mockReturnValue( + { + update: cryptoUpdateMock, + digest: cryptoDigestMock + } as unknown as Hash + ); + }); + + test('When called, it updates the idempotency record status to COMPLETED', async () => { + const data = 'someData'; + const result = {}; + const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); + + await persistenceLayer.saveSuccess(data, result); + + const savedIdempotencyRecord: IdempotencyRecord = updateRecord.mock.calls[0][0]; + expect(savedIdempotencyRecord.getStatus()).toBe(IdempotencyRecordStatus.COMPLETED); + + }); + + test('When called, it generates the idempotency key from the function name and a digest of the md5 hash of the data', async ()=> { + const data = 'someData'; + const result = {}; + const lambdaFunctionName = 'LambdaName'; + jest.spyOn(EnvironmentVariablesService.prototype, 'getLambdaFunctionName').mockReturnValue(lambdaFunctionName); + + const functionName = 'functionName'; + + const expectedIdempotencyKey = lambdaFunctionName + '.' + functionName + '#' + mockDigest; + const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); + persistenceLayer.configure(functionName); + + await persistenceLayer.saveSuccess(data, result); + + const savedIdempotencyRecord: IdempotencyRecord = updateRecord.mock.calls[0][0]; + + expect(createHash).toHaveBeenCalledWith( + expect.stringMatching('md5'), + ); + expect(cryptoUpdateMock).toHaveBeenCalledWith(expect.stringMatching(data)); + expect(cryptoDigestMock).toHaveBeenCalledWith( + expect.stringMatching('base64') + ); + expect(savedIdempotencyRecord.idempotencyKey).toEqual(expectedIdempotencyKey); + }); + + test('When called, it sets the expiry timestamp to one hour in the future', async ()=> { + const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); + const data = 'someData'; + const result = {}; + const currentMillisTime = 3000; + const currentSeconds = currentMillisTime / 1000; + const oneHourSeconds = 60 * 60; + jest.spyOn(Date, 'now').mockReturnValue(currentMillisTime); + + await persistenceLayer.saveSuccess(data, result); + + const savedIdempotencyRecord: IdempotencyRecord = updateRecord.mock.calls[0][0]; + expect(savedIdempotencyRecord.expiryTimestamp).toEqual(currentSeconds + oneHourSeconds); + + }); + + }); + + describe('Method: getRecord', ()=> { + beforeEach(()=> { + putRecord.mockClear(); + (createHash as jest.MockedFunction).mockReturnValue( + { + update: cryptoUpdateMock, + digest: cryptoDigestMock.mockReturnValue(mockDigest) + } as unknown as Hash + ); + }); + test('When called, it gets the record for the idempotency key for the data passed in', ()=> { + const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); + const data = 'someData'; + const lambdaFunctionName = 'LambdaName'; + jest.spyOn(EnvironmentVariablesService.prototype, 'getLambdaFunctionName').mockReturnValue(lambdaFunctionName); + + const functionName = 'functionName'; + const expectedIdempotencyKey = lambdaFunctionName + '.' + functionName + '#' + mockDigest; + persistenceLayer.configure(functionName); + + persistenceLayer.getRecord(data); + + expect(getRecord).toHaveBeenCalledWith(expectedIdempotencyKey); + }); + }); + + describe('Method: deleteRecord', ()=> { + beforeEach(()=> { + putRecord.mockClear(); + (createHash as jest.MockedFunction).mockReturnValue( + { + update: cryptoUpdateMock, + digest: cryptoDigestMock.mockReturnValue(mockDigest) + } as unknown as Hash + ); + }); + + test('When called, it deletes the record with the idempotency key for the data passed in', ()=> { + const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); + const data = 'someData'; + const lambdaFunctionName = 'LambdaName'; + jest.spyOn(EnvironmentVariablesService.prototype, 'getLambdaFunctionName').mockReturnValue(lambdaFunctionName); + + const functionName = 'functionName'; + const expectedIdempotencyKey = lambdaFunctionName + '.' + functionName + '#' + mockDigest; + persistenceLayer.configure(functionName); + + persistenceLayer.deleteRecord(data); + const deletedIdempotencyRecord: IdempotencyRecord = deleteRecord.mock.calls[0][0]; + + expect(deletedIdempotencyRecord.idempotencyKey).toEqual(expectedIdempotencyKey); + }); + }); +}); \ No newline at end of file From 664c4da08ac668ff1311dcc7fc1fc866f9ac06ff Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Sun, 13 Nov 2022 16:38:28 +0100 Subject: [PATCH 06/15] chore(internal): remove unused test folder from project's root (#1162) * chore: removed redundant test folder * test: import event & context from commons --- .../samples/resources/contexts/hello-world.ts | 4 +- packages/logger/tests/unit/Logger.test.ts | 37 +++--- packages/metrics/tests/unit/Metrics.test.ts | 65 +++++---- .../tests/unit/middleware/middy.test.ts | 101 +++++--------- packages/tracer/tests/unit/Tracer.test.ts | 28 +--- tests/resources/contexts/hello-world.ts | 20 --- .../events/aws/apigateway-authorizer.json | 5 - .../events/aws/apigateway-aws-proxy.json | 123 ------------------ .../events/aws/appsync-resolver.json | 74 ----------- tests/resources/events/aws/batch-get-job.json | 5 - .../events/aws/batch-submit-job.json | 5 - .../aws/cloudformation-create-request.json | 16 --- .../events/aws/cloudfront-ab-test.json | 36 ----- ...cloudfront-access-request-in-response.json | 42 ------ .../events/aws/cloudfront-http-direct.json | 30 ----- .../aws/cloudfront-modify-querystring.json | 37 ------ .../cloudfront-modify-response-header.json | 35 ----- ...tiple-remote-calls-aggregate-response.json | 30 ----- ...lize-querystring-to-improve-cache-hit.json | 37 ------ ...cloudfront-redirect-on-viewer-country.json | 30 ----- ...dfront-redirect-unauthenticated-users.json | 24 ---- .../aws/cloudfront-response-generation.json | 24 ---- ...udfront-serve-object-on-viewer-device.json | 48 ------- .../aws/cloudfront-simple-remote-call.json | 36 ----- .../resources/events/aws/cloudwatch-logs.json | 5 - .../aws/cloudwatch-scheduled-event.json | 12 -- .../events/aws/codecommit-repository.json | 27 ---- .../events/aws/codepipeline-job.json | 45 ------- .../events/aws/cognito-sync-trigger.json | 20 --- ...ig-oversized-item-change-notification.json | 12 -- .../events/aws/config-periodic-rule.json | 12 -- .../aws/confit-item-change-notification.json | 12 -- .../aws/connect-contact-flow-event.json | 33 ----- .../events/aws/dynamodb-update-2.json | 90 ------------- .../resources/events/aws/dynamodb-update.json | 93 ------------- .../aws/kinesis-analytics-compressed.json | 11 -- .../aws/kinesis-analytics-dynamodb.json | 11 -- .../events/aws/kinesis-analytics-kpl.json | 27 ---- .../events/aws/kinesis-analytics.json | 11 -- .../events/aws/kinesis-apachelog.json | 11 -- .../kinesis-cloudwatch-logs-processor.json | 27 ---- .../events/aws/kinesis-get-records.json | 20 --- .../events/aws/kinesis-kinesis-firehose.json | 12 -- .../events/aws/kinesis-streams-source.json | 19 --- .../resources/events/aws/kinesis-syslog.json | 16 --- .../events/aws/rekognition-s3-request.json | 38 ------ tests/resources/events/aws/s3-delete.json | 36 ----- tests/resources/events/aws/s3-put.json | 38 ------ ...ground-truth-annotation-consolidation.json | 9 -- .../aws/sagemaker-ground-truth-pre-human.json | 7 - .../events/aws/ses-email-receiving.json | 97 -------------- .../events/aws/sns-notification.json | 31 ----- .../events/aws/sqs-receive-message.json | 20 --- .../events/aws/stepfunctions-error.json | 1 - .../resources/events/custom/hello-world.json | 5 - 55 files changed, 85 insertions(+), 1615 deletions(-) delete mode 100644 tests/resources/contexts/hello-world.ts delete mode 100644 tests/resources/events/aws/apigateway-authorizer.json delete mode 100644 tests/resources/events/aws/apigateway-aws-proxy.json delete mode 100644 tests/resources/events/aws/appsync-resolver.json delete mode 100644 tests/resources/events/aws/batch-get-job.json delete mode 100644 tests/resources/events/aws/batch-submit-job.json delete mode 100644 tests/resources/events/aws/cloudformation-create-request.json delete mode 100644 tests/resources/events/aws/cloudfront-ab-test.json delete mode 100644 tests/resources/events/aws/cloudfront-access-request-in-response.json delete mode 100644 tests/resources/events/aws/cloudfront-http-direct.json delete mode 100644 tests/resources/events/aws/cloudfront-modify-querystring.json delete mode 100644 tests/resources/events/aws/cloudfront-modify-response-header.json delete mode 100644 tests/resources/events/aws/cloudfront-multiple-remote-calls-aggregate-response.json delete mode 100644 tests/resources/events/aws/cloudfront-normalize-querystring-to-improve-cache-hit.json delete mode 100644 tests/resources/events/aws/cloudfront-redirect-on-viewer-country.json delete mode 100644 tests/resources/events/aws/cloudfront-redirect-unauthenticated-users.json delete mode 100644 tests/resources/events/aws/cloudfront-response-generation.json delete mode 100644 tests/resources/events/aws/cloudfront-serve-object-on-viewer-device.json delete mode 100644 tests/resources/events/aws/cloudfront-simple-remote-call.json delete mode 100644 tests/resources/events/aws/cloudwatch-logs.json delete mode 100644 tests/resources/events/aws/cloudwatch-scheduled-event.json delete mode 100644 tests/resources/events/aws/codecommit-repository.json delete mode 100644 tests/resources/events/aws/codepipeline-job.json delete mode 100644 tests/resources/events/aws/cognito-sync-trigger.json delete mode 100644 tests/resources/events/aws/config-oversized-item-change-notification.json delete mode 100644 tests/resources/events/aws/config-periodic-rule.json delete mode 100644 tests/resources/events/aws/confit-item-change-notification.json delete mode 100644 tests/resources/events/aws/connect-contact-flow-event.json delete mode 100644 tests/resources/events/aws/dynamodb-update-2.json delete mode 100644 tests/resources/events/aws/dynamodb-update.json delete mode 100644 tests/resources/events/aws/kinesis-analytics-compressed.json delete mode 100644 tests/resources/events/aws/kinesis-analytics-dynamodb.json delete mode 100644 tests/resources/events/aws/kinesis-analytics-kpl.json delete mode 100644 tests/resources/events/aws/kinesis-analytics.json delete mode 100644 tests/resources/events/aws/kinesis-apachelog.json delete mode 100644 tests/resources/events/aws/kinesis-cloudwatch-logs-processor.json delete mode 100644 tests/resources/events/aws/kinesis-get-records.json delete mode 100644 tests/resources/events/aws/kinesis-kinesis-firehose.json delete mode 100644 tests/resources/events/aws/kinesis-streams-source.json delete mode 100644 tests/resources/events/aws/kinesis-syslog.json delete mode 100644 tests/resources/events/aws/rekognition-s3-request.json delete mode 100644 tests/resources/events/aws/s3-delete.json delete mode 100644 tests/resources/events/aws/s3-put.json delete mode 100644 tests/resources/events/aws/sagemaker-ground-truth-annotation-consolidation.json delete mode 100644 tests/resources/events/aws/sagemaker-ground-truth-pre-human.json delete mode 100644 tests/resources/events/aws/ses-email-receiving.json delete mode 100644 tests/resources/events/aws/sns-notification.json delete mode 100644 tests/resources/events/aws/sqs-receive-message.json delete mode 100644 tests/resources/events/aws/stepfunctions-error.json delete mode 100644 tests/resources/events/custom/hello-world.json diff --git a/packages/commons/src/samples/resources/contexts/hello-world.ts b/packages/commons/src/samples/resources/contexts/hello-world.ts index 006cae3089..16f707376e 100644 --- a/packages/commons/src/samples/resources/contexts/hello-world.ts +++ b/packages/commons/src/samples/resources/contexts/hello-world.ts @@ -7,8 +7,8 @@ const helloworldContext: Context = { memoryLimitInMB: '128', logGroupName: '/aws/lambda/foo-bar-function-123456abcdef', logStreamName: '2021/03/09/[$LATEST]abcdef123456abcdef123456abcdef123456', - invokedFunctionArn: 'arn:aws:lambda:eu-west-1:123456789012:function:Example', - awsRequestId: 'c6af9ac6-7b61-11e6-9a41-93e8deadbeef', + invokedFunctionArn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', + awsRequestId: 'c6af9ac6-7b61-11e6-9a41-93e812345678', getRemainingTimeInMillis: () => 1234, done: () => console.log('Done!'), fail: () => console.log('Failed!'), diff --git a/packages/logger/tests/unit/Logger.test.ts b/packages/logger/tests/unit/Logger.test.ts index 6fbf9a527a..42c1d99e72 100644 --- a/packages/logger/tests/unit/Logger.test.ts +++ b/packages/logger/tests/unit/Logger.test.ts @@ -4,27 +4,22 @@ * @group unit/logger/all */ -import { context as dummyContext } from '../../../../tests/resources/contexts/hello-world'; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import * as dummyEvent from '../../../../tests/resources/events/custom/hello-world.json'; +import { ContextExamples as dummyContext, Events as dummyEvent, LambdaInterface } from '@aws-lambda-powertools/commons'; import { createLogger, Logger } from '../../src'; import { EnvironmentVariablesService } from '../../src/config'; import { PowertoolLogFormatter } from '../../src/formatter'; import { ClassThatLogs, LogJsonIndent } from '../../src/types'; -import { Context, Handler } from 'aws-lambda'; +import { Context } from 'aws-lambda'; import { Console } from 'console'; -interface LambdaInterface { - handler: Handler -} - const mockDate = new Date(1466424490000); const dateSpy = jest.spyOn(global, 'Date').mockImplementation(() => mockDate as unknown as string); describe('Class: Logger', () => { const ENVIRONMENT_VARIABLES = process.env; - + const context = dummyContext.helloworldContext; + const event = dummyEvent.Custom.CustomEvent; + beforeEach(() => { dateSpy.mockClear(); process.env = { ...ENVIRONMENT_VARIABLES }; @@ -244,7 +239,7 @@ describe('Class: Logger', () => { const logger: Logger & { addContext: (context: Context) => void } = createLogger({ logLevel: 'DEBUG', }); - logger.addContext(dummyContext); + logger.addContext(context); const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation(); // Act @@ -829,7 +824,7 @@ describe('Class: Logger', () => { } // Act - await new LambdaFunction().handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); + await new LambdaFunction().handler(event, context, () => console.log('Lambda invoked!')); // Assess expect(consoleSpy).toBeCalledTimes(1); @@ -865,7 +860,7 @@ describe('Class: Logger', () => { // Act logger.info('An INFO log without context!'); - await new LambdaFunction().handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); + await new LambdaFunction().handler(event, context, () => console.log('Lambda invoked!')); // Assess @@ -912,7 +907,7 @@ describe('Class: Logger', () => { // Act logger.info('An INFO log without context!'); - const actualResult = await new LambdaFunction().handler(dummyEvent, dummyContext); + const actualResult = await new LambdaFunction().handler(event, context); // Assess @@ -971,7 +966,7 @@ describe('Class: Logger', () => { const persistentAttribs = { ...logger.getPersistentLogAttributes() }; // Act - await new LambdaFunction().handler({ user_id: '123456' }, dummyContext, () => console.log('Lambda invoked!')); + await new LambdaFunction().handler({ user_id: '123456' }, context, () => console.log('Lambda invoked!')); const persistentAttribsAfterInvocation = { ...logger.getPersistentLogAttributes() }; // Assess @@ -1017,7 +1012,7 @@ describe('Class: Logger', () => { // Act & Assess const executeLambdaHandler = async (): Promise => { - await new LambdaFunction().handler({ user_id: '123456' }, dummyContext, () => console.log('Lambda invoked!')); + await new LambdaFunction().handler({ user_id: '123456' }, context, () => console.log('Lambda invoked!')); }; await expect(executeLambdaHandler()).rejects.toThrow('Unexpected error occurred!'); const persistentAttribsAfterInvocation = { ...logger.getPersistentLogAttributes() }; @@ -1050,7 +1045,7 @@ describe('Class: Logger', () => { } // Act - await new LambdaFunction().handler({ user_id: '123456' }, dummyContext, () => console.log('Lambda invoked!')); + await new LambdaFunction().handler({ user_id: '123456' }, context, () => console.log('Lambda invoked!')); // Assess expect(consoleSpy).toBeCalledTimes(1); @@ -1094,7 +1089,7 @@ describe('Class: Logger', () => { } // Act - await new LambdaFunction().handler({ user_id: '123456' }, dummyContext, () => console.log('Lambda invoked!')); + await new LambdaFunction().handler({ user_id: '123456' }, context, () => console.log('Lambda invoked!')); // Assess expect(consoleSpy).toBeCalledTimes(1); @@ -1148,7 +1143,7 @@ describe('Class: Logger', () => { // Act const lambda = new LambdaFunction('someValue'); const handler = lambda.handler.bind(lambda); - await handler({}, dummyContext, () => console.log('Lambda invoked!')); + await handler({}, context, () => console.log('Lambda invoked!')); // Assess expect(consoleSpy).toBeCalledTimes(1); @@ -1198,7 +1193,7 @@ describe('Class: Logger', () => { // Act const lambda = new LambdaFunction(); const handler = lambda.handler.bind(lambda); - await handler({}, dummyContext, () => console.log('Lambda invoked!')); + await handler({}, context, () => console.log('Lambda invoked!')); // Assess expect(consoleSpy).toBeCalledTimes(1); @@ -1381,7 +1376,7 @@ describe('Class: Logger', () => { const consoleSpy = jest.spyOn(logger['console'], 'info').mockImplementation(); // Act - logger.logEventIfEnabled(dummyEvent); + logger.logEventIfEnabled(event); // Assess diff --git a/packages/metrics/tests/unit/Metrics.test.ts b/packages/metrics/tests/unit/Metrics.test.ts index b3f0086d70..e6a0e6045a 100644 --- a/packages/metrics/tests/unit/Metrics.test.ts +++ b/packages/metrics/tests/unit/Metrics.test.ts @@ -4,11 +4,8 @@ * @group unit/metrics/class */ -import { ContextExamples as dummyContext, LambdaInterface } from '@aws-lambda-powertools/commons'; +import { ContextExamples as dummyContext, Events as dummyEvent, LambdaInterface } from '@aws-lambda-powertools/commons'; import { Context, Callback } from 'aws-lambda'; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import * as dummyEvent from '../../../../tests/resources/events/custom/hello-world.json'; import { Metrics, MetricUnits } from '../../src/'; import { populateEnvironmentVariables } from '../helpers'; @@ -22,14 +19,10 @@ interface LooseObject { [key: string]: string } -type DummyEvent = { - key1: string - key2: string - key3: string -}; - describe('Class: Metrics', () => { const originalEnvironmentVariables = process.env; + const context = dummyContext.helloworldContext; + const event = dummyEvent.Custom.CustomEvent; beforeEach(() => { consoleSpy.mockClear(); @@ -99,7 +92,7 @@ describe('Class: Metrics', () => { } } - await new LambdaFunction().handler(dummyEvent, dummyContext.helloworldContext, () => console.log('Lambda invoked!')); + await new LambdaFunction().handler(event, context, () => console.log('Lambda invoked!')); const loggedData = [ JSON.parse(consoleSpy.mock.calls[0][0]), JSON.parse(consoleSpy.mock.calls[1][0]) ]; expect(console.log).toBeCalledTimes(2); @@ -193,7 +186,7 @@ describe('Class: Metrics', () => { } } - await new LambdaFunction().handler(dummyEvent, dummyContext.helloworldContext, () => console.log('Lambda invoked!')); + await new LambdaFunction().handler(event, context, () => console.log('Lambda invoked!')); const loggedData = [ JSON.parse(consoleSpy.mock.calls[0][0]), JSON.parse(consoleSpy.mock.calls[1][0]) ]; expect(console.log).toBeCalledTimes(2); @@ -227,7 +220,7 @@ describe('Class: Metrics', () => { } } - await new LambdaFunction().handler(dummyEvent, dummyContext.helloworldContext, () => console.log('Lambda invoked!')); + await new LambdaFunction().handler(event, context, () => console.log('Lambda invoked!')); } catch (e) { expect((e).message).toBe('Max dimension count hit'); } @@ -255,7 +248,7 @@ describe('Class: Metrics', () => { } } - await new LambdaFunction().handler(dummyEvent, dummyContext.helloworldContext, () => console.log('Lambda invoked!')); + await new LambdaFunction().handler(event, context, () => console.log('Lambda invoked!')); const loggedData = JSON.parse(consoleSpy.mock.calls[0][0]); expect(console.log).toBeCalledTimes(1); @@ -283,7 +276,7 @@ describe('Class: Metrics', () => { } } - await new LambdaFunction().handler(dummyEvent, dummyContext.helloworldContext, () => console.log('Lambda invoked!')); + await new LambdaFunction().handler(event, context, () => console.log('Lambda invoked!')); const loggedData = JSON.parse(consoleSpy.mock.calls[0][0]); expect(console.log).toBeCalledTimes(1); @@ -297,13 +290,13 @@ describe('Class: Metrics', () => { test('Cold start metric should only be written out once and flushed automatically', async () => { const metrics = new Metrics({ namespace: 'test' }); - const handler = async (_event: DummyEvent, _context: Context): Promise => { + const handler = async (_event: unknown, _context: Context): Promise => { // Should generate only one log metrics.captureColdStartMetric(); }; - await handler(dummyEvent, dummyContext.helloworldContext); - await handler(dummyEvent, dummyContext.helloworldContext); + await handler(event, context); + await handler(event, context); const loggedData = [JSON.parse(consoleSpy.mock.calls[0][0])]; expect(console.log).toBeCalledTimes(1); @@ -329,8 +322,8 @@ describe('Class: Metrics', () => { } } - await new LambdaFunction().handler(dummyEvent, dummyContext.helloworldContext, () => console.log('Lambda invoked!')); - await new LambdaFunction().handler(dummyEvent, dummyContext.helloworldContext, () => console.log('Lambda invoked again!')); + await new LambdaFunction().handler(event, context, () => console.log('Lambda invoked!')); + await new LambdaFunction().handler(event, context, () => console.log('Lambda invoked again!')); const loggedData = [ JSON.parse(consoleSpy.mock.calls[0][0]), JSON.parse(consoleSpy.mock.calls[1][0]) ]; expect(console.log).toBeCalledTimes(3); @@ -356,7 +349,7 @@ describe('Class: Metrics', () => { metrics.addMetric('test_name', MetricUnits.Seconds, 10); } } - await new LambdaFunction().handler(dummyEvent, dummyContext.helloworldContext, () => console.log('Lambda invoked!')); + await new LambdaFunction().handler(event, context, () => console.log('Lambda invoked!')); const loggedData = JSON.parse(consoleSpy.mock.calls[0][0]); expect(console.log).toBeCalledTimes(2); @@ -364,7 +357,7 @@ describe('Class: Metrics', () => { expect(loggedData._aws.CloudWatchMetrics[0].Metrics[0].Name).toBe('ColdStart'); expect(loggedData._aws.CloudWatchMetrics[0].Metrics[0].Unit).toBe('Count'); expect(loggedData.service).toBe(serviceName); - expect(loggedData.function_name).toBe(dummyContext.helloworldContext.functionName); + expect(loggedData.function_name).toBe(context.functionName); expect(loggedData._aws.CloudWatchMetrics[0].Dimensions[0]).toContain('service'); expect(loggedData._aws.CloudWatchMetrics[0].Dimensions[0]).toContain('function_name'); expect(loggedData.ColdStart).toBe(1); @@ -373,8 +366,8 @@ describe('Class: Metrics', () => { test('Cold should still log, without a function name', async () => { const serviceName = 'test-service'; const metrics = new Metrics({ namespace: 'test', serviceName: serviceName }); - const newDummyContext = JSON.parse(JSON.stringify(dummyContext)); - delete newDummyContext.functionName; + const newContext = JSON.parse(JSON.stringify(context)); + delete newContext.functionName; class LambdaFunction implements LambdaInterface { @metrics.logMetrics({ captureColdStartMetric: true }) // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -388,7 +381,7 @@ describe('Class: Metrics', () => { } } - await new LambdaFunction().handler(dummyEvent, newDummyContext, () => console.log('Lambda invoked!')); + await new LambdaFunction().handler(event, newContext, () => console.log('Lambda invoked!')); const loggedData = JSON.parse(consoleSpy.mock.calls[0][0]); expect(console.log).toBeCalledTimes(2); @@ -421,7 +414,7 @@ describe('Class: Metrics', () => { } try { - await new LambdaFunction().handler(dummyEvent, dummyContext.helloworldContext, () => console.log('Lambda invoked!')); + await new LambdaFunction().handler(event, context, () => console.log('Lambda invoked!')); } catch (e) { expect((e).message).toBe('The number of metrics recorded must be higher than zero'); } @@ -445,7 +438,7 @@ describe('Class: Metrics', () => { } try { - await new LambdaFunction().handler(dummyEvent, dummyContext.helloworldContext, () => console.log('Lambda invoked!')); + await new LambdaFunction().handler(event, context, () => console.log('Lambda invoked!')); } catch (e) { fail(`Should not throw but got the following Error: ${e}`); } @@ -457,14 +450,14 @@ describe('Class: Metrics', () => { expect.assertions(1); const metrics = new Metrics({ namespace: 'test' }); - const handler = async (_event: DummyEvent, _context: Context): Promise => { + const handler = async (_event: unknown, _context: Context): Promise => { metrics.throwOnEmptyMetrics(); // Logic goes here metrics.publishStoredMetrics(); }; try { - await handler(dummyEvent, dummyContext.helloworldContext); + await handler(event, context); } catch (e) { expect((e).message).toBe('The number of metrics recorded must be higher than zero'); } @@ -490,7 +483,7 @@ describe('Class: Metrics', () => { } } - await new LambdaFunction().handler(dummyEvent, dummyContext.helloworldContext, () => console.log('Lambda invoked!')); + await new LambdaFunction().handler(event, context, () => console.log('Lambda invoked!')); const loggedData = [ JSON.parse(consoleSpy.mock.calls[0][0]), JSON.parse(consoleSpy.mock.calls[1][0]) ]; expect(console.log).toBeCalledTimes(2); @@ -591,7 +584,7 @@ describe('Class: Metrics', () => { } } - await new LambdaFunction().handler(dummyEvent, dummyContext.helloworldContext, () => console.log('Lambda invoked!')); + await new LambdaFunction().handler(event, context, () => console.log('Lambda invoked!')); }); test('Publish Stored Metrics should log and clear', async () => { @@ -610,7 +603,7 @@ describe('Class: Metrics', () => { } } - await new LambdaFunction().handler(dummyEvent, dummyContext.helloworldContext, () => console.log('Lambda invoked!')); + await new LambdaFunction().handler(event, context, () => console.log('Lambda invoked!')); const loggedData = [ JSON.parse(consoleSpy.mock.calls[0][0]), JSON.parse(consoleSpy.mock.calls[1][0]) ]; expect(console.log).toBeCalledTimes(2); @@ -640,7 +633,7 @@ describe('Class: Metrics', () => { } // Act - await new LambdaFunction().handler(dummyEvent, dummyContext.helloworldContext); + await new LambdaFunction().handler(event, context); // Assess expect(console.log).toBeCalledTimes(1); @@ -669,7 +662,7 @@ describe('Class: Metrics', () => { } } - await new LambdaFunction().handler(dummyEvent, dummyContext.helloworldContext); + await new LambdaFunction().handler(event, context); const loggedData = JSON.parse(consoleSpy.mock.calls[0][0]); expect(console.log).toBeCalledTimes(1); @@ -695,7 +688,7 @@ describe('Class: Metrics', () => { } try { - await new LambdaFunction().handler(dummyEvent, dummyContext.helloworldContext, () => console.log('Lambda invoked!')); + await new LambdaFunction().handler(event, context, () => console.log('Lambda invoked!')); } catch (error) { // DO NOTHING } @@ -724,7 +717,7 @@ describe('Class: Metrics', () => { metrics.addMetric('test_name', MetricUnits.Seconds, 1); } } - await new LambdaFunction().handler(dummyEvent, dummyContext.helloworldContext, () => console.log('Lambda invoked!')); + await new LambdaFunction().handler(event, context, () => console.log('Lambda invoked!')); const loggedData = JSON.parse(consoleSpy.mock.calls[0][0]); // Assess diff --git a/packages/metrics/tests/unit/middleware/middy.test.ts b/packages/metrics/tests/unit/middleware/middy.test.ts index ced312f1e3..16c284c61e 100644 --- a/packages/metrics/tests/unit/middleware/middy.test.ts +++ b/packages/metrics/tests/unit/middleware/middy.test.ts @@ -6,9 +6,6 @@ import { Metrics, MetricUnits, logMetrics } from '../../../../metrics/src'; import middy from '@middy/core'; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import * as event from '../../../../../tests/resources/events/custom/hello-world.json'; import { ExtraOptions } from '../../../src/types'; const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); @@ -16,6 +13,27 @@ const mockDate = new Date(1466424490000); const dateSpy = jest.spyOn(global, 'Date').mockImplementation(() => mockDate as unknown as string); describe('Middy middleware', () => { + + const dummyEvent = { + key1: 'value1', + key2: 'value2', + key3: 'value3', + }; + const dummyContext = { + callbackWaitsForEmptyEventLoop: true, + functionVersion: '$LATEST', + functionName: 'foo-bar-function', + memoryLimitInMB: '128', + logGroupName: '/aws/lambda/foo-bar-function-123456abcdef', + logStreamName: '2021/03/09/[$LATEST]abcdef123456abcdef123456abcdef123456', + invokedFunctionArn: 'arn:aws:lambda:eu-west-1:123456789012:function:Example', + awsRequestId: 'c6af9ac6-7b61-11e6-9a41-93e812345678', + getRemainingTimeInMillis: () => 1234, + done: () => console.log('Done!'), + fail: () => console.log('Failed!'), + succeed: () => console.log('Succeeded!'), + }; + beforeEach(() => { jest.resetModules(); consoleSpy.mockClear(); @@ -23,23 +41,6 @@ describe('Middy middleware', () => { }); describe('throwOnEmptyMetrics', () => { - const getRandomInt = (): number => Math.floor(Math.random() * 1000000000); - const awsRequestId = getRandomInt().toString(); - - const context = { - callbackWaitsForEmptyEventLoop: true, - functionVersion: '$LATEST', - functionName: 'foo-bar-function', - memoryLimitInMB: '128', - logGroupName: '/aws/lambda/foo-bar-function', - logStreamName: '2021/03/09/[$LATEST]abcdef123456abcdef123456abcdef123456', - invokedFunctionArn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', - awsRequestId: awsRequestId, - getRemainingTimeInMillis: () => 1234, - done: () => console.log('Done!'), - fail: () => console.log('Failed!'), - succeed: () => console.log('Succeeded!'), - }; test('should throw on empty metrics if set to true', async () => { // Prepare @@ -52,7 +53,7 @@ describe('Middy middleware', () => { const handler = middy(lambdaHandler).use(logMetrics(metrics, { throwOnEmptyMetrics: true })); try { - await handler(event, context, () => console.log('Lambda invoked!')); + await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); } catch (e) { expect((e).message).toBe('The number of metrics recorded must be higher than zero'); } @@ -69,7 +70,7 @@ describe('Middy middleware', () => { const handler = middy(lambdaHandler).use(logMetrics(metrics, { throwOnEmptyMetrics: false })); try { - await handler(event, context, () => console.log('Lambda invoked!')); + await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); } catch (e) { fail(`Should not throw but got the following Error: ${e}`); } @@ -86,7 +87,7 @@ describe('Middy middleware', () => { const handler = middy(lambdaHandler).use(logMetrics(metrics)); try { - await handler(event, context, () => console.log('Lambda invoked!')); + await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); } catch (e) { fail(`Should not throw but got the following Error: ${e}`); } @@ -94,23 +95,6 @@ describe('Middy middleware', () => { }); describe('captureColdStartMetric', () => { - const getRandomInt = (): number => Math.floor(Math.random() * 1000000000); - const awsRequestId = getRandomInt().toString(); - - const context = { - callbackWaitsForEmptyEventLoop: true, - functionVersion: '$LATEST', - functionName: 'foo-bar-function', - memoryLimitInMB: '128', - logGroupName: '/aws/lambda/foo-bar-function', - logStreamName: '2021/03/09/[$LATEST]abcdef123456abcdef123456abcdef123456', - invokedFunctionArn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', - awsRequestId: awsRequestId, - getRemainingTimeInMillis: () => 1234, - done: () => console.log('Done!'), - fail: () => console.log('Failed!'), - succeed: () => console.log('Succeeded!'), - }; test('should capture cold start metric if set to true', async () => { // Prepare @@ -122,8 +106,8 @@ describe('Middy middleware', () => { const handler = middy(lambdaHandler).use(logMetrics(metrics, { captureColdStartMetric: true })); - await handler(event, context, () => console.log('Lambda invoked!')); - await handler(event, context, () => console.log('Lambda invoked! again')); + await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); + await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked! again')); const loggedData = [ JSON.parse(consoleSpy.mock.calls[0][0]), JSON.parse(consoleSpy.mock.calls[1][0]) ]; expect(console.log).toBeCalledTimes(5); @@ -143,8 +127,8 @@ describe('Middy middleware', () => { const handler = middy(lambdaHandler).use(logMetrics(metrics, { captureColdStartMetric: false })); - await handler(event, context, () => console.log('Lambda invoked!')); - await handler(event, context, () => console.log('Lambda invoked! again')); + await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); + await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked! again')); const loggedData = [ JSON.parse(consoleSpy.mock.calls[0][0]), JSON.parse(consoleSpy.mock.calls[1][0]) ]; expect(loggedData[0]._aws).toBe(undefined); @@ -160,8 +144,8 @@ describe('Middy middleware', () => { const handler = middy(lambdaHandler).use(logMetrics(metrics)); - await handler(event, context, () => console.log('Lambda invoked!')); - await handler(event, context, () => console.log('Lambda invoked! again')); + await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); + await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked! again')); const loggedData = [ JSON.parse(consoleSpy.mock.calls[0][0]), JSON.parse(consoleSpy.mock.calls[1][0]) ]; expect(loggedData[0]._aws).toBe(undefined); @@ -169,23 +153,6 @@ describe('Middy middleware', () => { }); describe('logMetrics', () => { - const getRandomInt = (): number => Math.floor(Math.random() * 1000000000); - const awsRequestId = getRandomInt().toString(); - - const context = { - callbackWaitsForEmptyEventLoop: true, - functionVersion: '$LATEST', - functionName: 'foo-bar-function', - memoryLimitInMB: '128', - logGroupName: '/aws/lambda/foo-bar-function', - logStreamName: '2021/03/09/[$LATEST]abcdef123456abcdef123456abcdef123456', - invokedFunctionArn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', - awsRequestId: awsRequestId, - getRemainingTimeInMillis: () => 1234, - done: () => console.log('Done!'), - fail: () => console.log('Failed!'), - succeed: () => console.log('Succeeded!'), - }; test('when a metrics instance receive multiple metrics with the same name, it prints multiple values in an array format', async () => { // Prepare @@ -199,7 +166,7 @@ describe('Middy middleware', () => { const handler = middy(lambdaHandler).use(logMetrics(metrics)); // Act - await handler(event, context, () => console.log('Lambda invoked!')); + await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); // Assess expect(console.log).toHaveBeenNthCalledWith( @@ -236,7 +203,7 @@ describe('Middy middleware', () => { const handler = middy(lambdaHandler).use(logMetrics(metrics, metricsOptions)); // Act - await handler(event, context, () => console.log('Lambda invoked!')); + await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); // Assess expect(console.log).toHaveBeenNthCalledWith( @@ -291,7 +258,7 @@ describe('Middy middleware', () => { const handler = middy(lambdaHandler).use(logMetrics(metrics)); // Act - await handler(event, context, () => console.log('Lambda invoked!')); + await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); // Assess expect(console.log).toHaveBeenNthCalledWith( @@ -326,7 +293,7 @@ describe('Middy middleware', () => { const handler = middy(lambdaHandler).use(logMetrics([metrics], metricsOptions)); // Act - await handler(event, context, () => console.log('Lambda invoked!')); + await handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!')); // Assess expect(console.log).toHaveBeenNthCalledWith( diff --git a/packages/tracer/tests/unit/Tracer.test.ts b/packages/tracer/tests/unit/Tracer.test.ts index f541f2e4db..6a71c93bd0 100644 --- a/packages/tracer/tests/unit/Tracer.test.ts +++ b/packages/tracer/tests/unit/Tracer.test.ts @@ -4,16 +4,13 @@ * @group unit/tracer/all */ +import { ContextExamples as dummyContext, Events as dummyEvent, LambdaInterface } from '@aws-lambda-powertools/commons'; import { Tracer } from '../../src'; -import { Callback, Context, Handler } from 'aws-lambda/handler'; +import { Callback, Context } from 'aws-lambda/handler'; import { Segment, setContextMissingStrategy, Subsegment } from 'aws-xray-sdk-core'; import { DynamoDB } from 'aws-sdk'; import { ProviderServiceInterface } from '../../src/provider'; -interface LambdaInterface { - handler: Handler -} - type CaptureAsyncFuncMock = jest.SpyInstance unknown, parent?: Segment | Subsegment]>; const createCaptureAsyncFuncMock = function(provider: ProviderServiceInterface, subsegment?: Subsegment): CaptureAsyncFuncMock { return jest.spyOn(provider, 'captureAsyncFunc') @@ -32,25 +29,8 @@ jest.spyOn(console, 'error').mockImplementation(() => null); describe('Class: Tracer', () => { const ENVIRONMENT_VARIABLES = process.env; - const event = { - key1: 'value1', - key2: 'value2', - key3: 'value3', - }; - const context = { - callbackWaitsForEmptyEventLoop: true, - functionVersion: '$LATEST', - functionName: 'foo-bar-function', - memoryLimitInMB: '128', - logGroupName: '/aws/lambda/foo-bar-function-123456abcdef', - logStreamName: '2021/03/09/[$LATEST]abcdef123456abcdef123456abcdef123456', - invokedFunctionArn: 'arn:aws:lambda:eu-west-1:123456789012:function:Example', - awsRequestId: 'c6af9ac6-7b61-11e6-9a41-93e8deadbeef', - getRemainingTimeInMillis: () => 1234, - done: () => console.log('Done!'), - fail: () => console.log('Failed!'), - succeed: () => console.log('Succeeded!'), - }; + const context = dummyContext.helloworldContext; + const event = dummyEvent.Custom.CustomEvent; beforeEach(() => { jest.clearAllMocks(); diff --git a/tests/resources/contexts/hello-world.ts b/tests/resources/contexts/hello-world.ts deleted file mode 100644 index dbb23c366d..0000000000 --- a/tests/resources/contexts/hello-world.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Context } from 'aws-lambda'; - -const context: Context = { - callbackWaitsForEmptyEventLoop: true, - functionVersion: '$LATEST', - functionName: 'foo-bar-function', - memoryLimitInMB: '128', - logGroupName: '/aws/lambda/foo-bar-function', - logStreamName: '2021/03/09/[$LATEST]abcdef123456abcdef123456abcdef123456', - invokedFunctionArn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', - awsRequestId: 'c6af9ac6-7b61-11e6-9a41-93e812345678', - getRemainingTimeInMillis: () => 1234, - done: () => console.log('Done!'), - fail: () => console.log('Failed!'), - succeed: () => console.log('Succeeded!'), -}; - -export { - context -}; \ No newline at end of file diff --git a/tests/resources/events/aws/apigateway-authorizer.json b/tests/resources/events/aws/apigateway-authorizer.json deleted file mode 100644 index 2bbf060353..0000000000 --- a/tests/resources/events/aws/apigateway-authorizer.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "TOKEN", - "authorizationToken": "incoming-client-token", - "methodArn": "arn:aws:execute-api:eu-central-1:123456789012:example/prod/POST/{proxy+}" -} \ No newline at end of file diff --git a/tests/resources/events/aws/apigateway-aws-proxy.json b/tests/resources/events/aws/apigateway-aws-proxy.json deleted file mode 100644 index 2369993716..0000000000 --- a/tests/resources/events/aws/apigateway-aws-proxy.json +++ /dev/null @@ -1,123 +0,0 @@ -{ - "body": "eyJ0ZXN0IjoiYm9keSJ9", - "resource": "/{proxy+}", - "path": "/path/to/resource", - "httpMethod": "POST", - "isBase64Encoded": true, - "queryStringParameters": { - "foo": "bar" - }, - "multiValueQueryStringParameters": { - "foo": [ - "bar" - ] - }, - "pathParameters": { - "proxy": "/path/to/resource" - }, - "stageVariables": { - "baz": "qux" - }, - "headers": { - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "Accept-Encoding": "gzip, deflate, sdch", - "Accept-Language": "en-US,en;q=0.8", - "Cache-Control": "max-age=0", - "CloudFront-Forwarded-Proto": "https", - "CloudFront-Is-Desktop-Viewer": "true", - "CloudFront-Is-Mobile-Viewer": "false", - "CloudFront-Is-SmartTV-Viewer": "false", - "CloudFront-Is-Tablet-Viewer": "false", - "CloudFront-Viewer-Country": "US", - "Host": "1234567890.execute-api.eu-central-1.amazonaws.com", - "Upgrade-Insecure-Requests": "1", - "User-Agent": "Custom User Agent String", - "Via": "1.1 08f32312345678a7af34d5feb414ce27.cloudfront.net (CloudFront)", - "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", - "X-Forwarded-For": "127.0.0.1, 127.0.0.2", - "X-Forwarded-Port": "443", - "X-Forwarded-Proto": "https" - }, - "multiValueHeaders": { - "Accept": [ - "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8" - ], - "Accept-Encoding": [ - "gzip, deflate, sdch" - ], - "Accept-Language": [ - "en-US,en;q=0.8" - ], - "Cache-Control": [ - "max-age=0" - ], - "CloudFront-Forwarded-Proto": [ - "https" - ], - "CloudFront-Is-Desktop-Viewer": [ - "true" - ], - "CloudFront-Is-Mobile-Viewer": [ - "false" - ], - "CloudFront-Is-SmartTV-Viewer": [ - "false" - ], - "CloudFront-Is-Tablet-Viewer": [ - "false" - ], - "CloudFront-Viewer-Country": [ - "US" - ], - "Host": [ - "0123456789.execute-api.eu-central-1.amazonaws.com" - ], - "Upgrade-Insecure-Requests": [ - "1" - ], - "User-Agent": [ - "Custom User Agent String" - ], - "Via": [ - "1.1 08f32312345678a7af34d5feb414ce27.cloudfront.net (CloudFront)" - ], - "X-Amz-Cf-Id": [ - "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==" - ], - "X-Forwarded-For": [ - "127.0.0.1, 127.0.0.2" - ], - "X-Forwarded-Port": [ - "443" - ], - "X-Forwarded-Proto": [ - "https" - ] - }, - "requestContext": { - "accountId": "123456789012", - "resourceId": "123456", - "stage": "prod", - "requestId": "c6af9ac6-7b61-11e6-9a41-93e812345678", - "requestTime": "09/Apr/2015:12:34:56 +0000", - "requestTimeEpoch": 1428582896000, - "identity": { - "cognitoIdentityPoolId": null, - "accountId": null, - "cognitoIdentityId": null, - "caller": null, - "accessKey": null, - "sourceIp": "127.0.0.1", - "cognitoAuthenticationType": null, - "cognitoAuthenticationProvider": null, - "userArn": null, - "userAgent": "Custom User Agent String", - "user": null - }, - "path": "/prod/path/to/resource", - "resourcePath": "/{proxy+}", - "httpMethod": "POST", - "apiId": "1234567890", - "protocol": "HTTP/1.1" - } -} \ No newline at end of file diff --git a/tests/resources/events/aws/appsync-resolver.json b/tests/resources/events/aws/appsync-resolver.json deleted file mode 100644 index 94960dd1ed..0000000000 --- a/tests/resources/events/aws/appsync-resolver.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "arguments": { - "id": "my identifier" - }, - "identity": { - "claims": { - "sub": "192879fc-a240-4bf1-ab5a-d6a00f3063f9", - "email_verified": true, - "iss": "https://cognito-idp.us-west-2.amazonaws.com/us-west-xxxxxxxxxxx", - "phone_number_verified": false, - "cognito:username": "jdoe", - "aud": "7471s60os7h0uu77i1tk27sp9n", - "event_id": "bc334ed8-a938-4474-b644-9547e304e606", - "token_use": "id", - "auth_time": 1599154213, - "phone_number": "+19999999999", - "exp": 1599157813, - "iat": 1599154213, - "email": "jdoe@email.com" - }, - "defaultAuthStrategy": "ALLOW", - "groups": null, - "issuer": "https://cognito-idp.us-west-2.amazonaws.com/us-west-xxxxxxxxxxx", - "sourceIp": [ - "1.1.1.1" - ], - "sub": "192879fc-a240-4bf1-ab5a-d6a00f3063f9", - "username": "jdoe" - }, - "source": null, - "request": { - "headers": { - "x-forwarded-for": "1.1.1.1, 2.2.2.2", - "cloudfront-viewer-country": "US", - "cloudfront-is-tablet-viewer": "false", - "via": "2.0 xxxxxxxxxxxxxxxx.cloudfront.net (CloudFront)", - "cloudfront-forwarded-proto": "https", - "origin": "https://us-west-1.console.aws.amazon.com", - "content-length": "217", - "accept-language": "en-US,en;q=0.9", - "host": "xxxxxxxxxxxxxxxx.appsync-api.us-west-1.amazonaws.com", - "x-forwarded-proto": "https", - "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36", - "accept": "*/*", - "cloudfront-is-mobile-viewer": "false", - "cloudfront-is-smarttv-viewer": "false", - "accept-encoding": "gzip, deflate, br", - "referer": "https://us-west-1.console.aws.amazon.com/appsync/home?region=us-west-1", - "content-type": "application/json", - "sec-fetch-mode": "cors", - "x-amz-cf-id": "3aykhqlUwQeANU-HGY7E_guV5EkNeMMtwyOgiA==", - "x-amzn-trace-id": "Root=1-5f512f51-fac632066c5e848ae714", - "authorization": "eyJraWQiOiJScWFCSlJqYVJlM0hrSnBTUFpIcVRXazNOW...", - "sec-fetch-dest": "empty", - "x-amz-user-agent": "AWS-Console-AppSync/", - "cloudfront-is-desktop-viewer": "true", - "sec-fetch-site": "cross-site", - "x-forwarded-port": "443" - } - }, - "prev": null, - "info": { - "selectionSetList": [ - "id", - "field1", - "field2" - ], - "selectionSetGraphQL": "{\n id\n field1\n field2\n}", - "parentTypeName": "Mutation", - "fieldName": "createSomething", - "variables": {} - }, - "stash": {} -} \ No newline at end of file diff --git a/tests/resources/events/aws/batch-get-job.json b/tests/resources/events/aws/batch-get-job.json deleted file mode 100644 index cf95238fc4..0000000000 --- a/tests/resources/events/aws/batch-get-job.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "jobName": "hello_world", - "jobQueue": "default", - "jobDefinition": "hello_world" -} \ No newline at end of file diff --git a/tests/resources/events/aws/batch-submit-job.json b/tests/resources/events/aws/batch-submit-job.json deleted file mode 100644 index cf95238fc4..0000000000 --- a/tests/resources/events/aws/batch-submit-job.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "jobName": "hello_world", - "jobQueue": "default", - "jobDefinition": "hello_world" -} \ No newline at end of file diff --git a/tests/resources/events/aws/cloudformation-create-request.json b/tests/resources/events/aws/cloudformation-create-request.json deleted file mode 100644 index 461752bb16..0000000000 --- a/tests/resources/events/aws/cloudformation-create-request.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "RequestType": "Create", - "ResponseURL": "http://pre-signed-S3-url-for-response", - "StackId": "arn:aws:cloudformation:eu-central-1:123456789012:stack/MyStack/guid", - "RequestId": "unique id for this create request", - "ResourceType": "Custom::TestResource", - "LogicalResourceId": "MyTestResource", - "ResourceProperties": { - "StackName": "MyStack", - "List": [ - "1", - "2", - "3" - ] - } -} \ No newline at end of file diff --git a/tests/resources/events/aws/cloudfront-ab-test.json b/tests/resources/events/aws/cloudfront-ab-test.json deleted file mode 100644 index bdc6b76e2d..0000000000 --- a/tests/resources/events/aws/cloudfront-ab-test.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "Records": [ - { - "cf": { - "config": { - "distributionId": "EXAMPLE" - }, - "request": { - "uri": "/test", - "method": "GET", - "clientIp": "2001:cdba::3257:9652", - "headers": { - "user-agent": [ - { - "key": "User-Agent", - "value": "Test Agent" - } - ], - "host": [ - { - "key": "Host", - "value": "d123.cf.net" - } - ], - "cookie": [ - { - "key": "Cookie", - "value": "SomeCookie=1; AnotherOne=A; X-Experiment-Name=B" - } - ] - } - } - } - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/cloudfront-access-request-in-response.json b/tests/resources/events/aws/cloudfront-access-request-in-response.json deleted file mode 100644 index ecb1240549..0000000000 --- a/tests/resources/events/aws/cloudfront-access-request-in-response.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "Records": [ - { - "cf": { - "config": { - "distributionId": "EXAMPLE" - }, - "request": { - "headers": { - "host": [ - { - "key": "Host", - "value": "d123.cf.net" - } - ], - "user-name": [ - { - "key": "User-Name", - "value": "CloudFront" - } - ] - }, - "clientIp": "2001:cdba::3257:9652", - "uri": "/test", - "method": "GET" - }, - "response": { - "status": "200", - "statusDescription": "OK", - "headers": { - "x-cache": [ - { - "key": "X-Cache", - "value": "Hello from Cloudfront" - } - ] - } - } - } - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/cloudfront-http-direct.json b/tests/resources/events/aws/cloudfront-http-direct.json deleted file mode 100644 index 1f13e53dd1..0000000000 --- a/tests/resources/events/aws/cloudfront-http-direct.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "Records": [ - { - "cf": { - "config": { - "distributionId": "EXAMPLE" - }, - "request": { - "uri": "/test", - "method": "GET", - "clientIp": "2001:cdba::3257:9652", - "headers": { - "user-agent": [ - { - "key": "User-Agent", - "value": "test-agent" - } - ], - "host": [ - { - "key": "Host", - "value": "d123.cf.net" - } - ] - } - } - } - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/cloudfront-modify-querystring.json b/tests/resources/events/aws/cloudfront-modify-querystring.json deleted file mode 100644 index 7485310e94..0000000000 --- a/tests/resources/events/aws/cloudfront-modify-querystring.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "Records": [ - { - "cf": { - "config": { - "distributionId": "EXAMPLE" - }, - "request": { - "uri": "/test", - "querystring": "auth=test&foo=bar", - "method": "GET", - "clientIp": "2001:cdba::3257:9652", - "headers": { - "host": [ - { - "key": "Host", - "value": "d123.cf.net" - } - ], - "user-agent": [ - { - "key": "User-Agent", - "value": "Test Agent" - } - ], - "user-name": [ - { - "key": "User-Name", - "value": "aws-cloudfront" - } - ] - } - } - } - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/cloudfront-modify-response-header.json b/tests/resources/events/aws/cloudfront-modify-response-header.json deleted file mode 100644 index 8ac2b4bc0f..0000000000 --- a/tests/resources/events/aws/cloudfront-modify-response-header.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "Records": [ - { - "cf": { - "config": { - "distributionId": "EXAMPLE" - }, - "response": { - "status": "200", - "statusDescription": "OK", - "headers": { - "vary": [ - { - "key": "Vary", - "value": "*" - } - ], - "last-modified": [ - { - "key": "Last-Modified", - "value": "2016-11-25" - } - ], - "x-amz-meta-last-modified": [ - { - "key": "X-Amz-Meta-Last-Modified", - "value": "2016-01-01" - } - ] - } - } - } - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/cloudfront-multiple-remote-calls-aggregate-response.json b/tests/resources/events/aws/cloudfront-multiple-remote-calls-aggregate-response.json deleted file mode 100644 index 1360629d73..0000000000 --- a/tests/resources/events/aws/cloudfront-multiple-remote-calls-aggregate-response.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "Records": [ - { - "cf": { - "config": { - "distributionId": "EXAMPLE" - }, - "request": { - "uri": "/forecast/Seattle:NewYork:Chicago:SanFrancisco", - "method": "GET", - "clientIp": "2001:cdba::3257:9652", - "headers": { - "host": [ - { - "key": "Host", - "value": "d123.cf.net" - } - ], - "user-agent": [ - { - "key": "User-Agent", - "value": "Test Agent" - } - ] - } - } - } - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/cloudfront-normalize-querystring-to-improve-cache-hit.json b/tests/resources/events/aws/cloudfront-normalize-querystring-to-improve-cache-hit.json deleted file mode 100644 index 21100c997a..0000000000 --- a/tests/resources/events/aws/cloudfront-normalize-querystring-to-improve-cache-hit.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "Records": [ - { - "cf": { - "config": { - "distributionId": "EXAMPLE" - }, - "request": { - "uri": "/test", - "querystring": "size=LARGE&color=RED", - "method": "GET", - "clientIp": "2001:cdba::3257:9652", - "headers": { - "host": [ - { - "key": "Host", - "value": "d123.cf.net" - } - ], - "user-agent": [ - { - "key": "User-Agent", - "value": "Test Agent" - } - ], - "user-name": [ - { - "key": "User-Name", - "value": "aws-cloudfront" - } - ] - } - } - } - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/cloudfront-redirect-on-viewer-country.json b/tests/resources/events/aws/cloudfront-redirect-on-viewer-country.json deleted file mode 100644 index 2d24b0cb43..0000000000 --- a/tests/resources/events/aws/cloudfront-redirect-on-viewer-country.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "Records": [ - { - "cf": { - "config": { - "distributionId": "EXAMPLE" - }, - "request": { - "uri": "/test", - "method": "GET", - "clientIp": "2001:cdba::3257:9652", - "headers": { - "host": [ - { - "key": "Host", - "value": "d123.cf.net" - } - ], - "cloudfront-viewer-country": [ - { - "key": "CloudFront-Viewer-Country", - "value": "TW" - } - ] - } - } - } - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/cloudfront-redirect-unauthenticated-users.json b/tests/resources/events/aws/cloudfront-redirect-unauthenticated-users.json deleted file mode 100644 index 4d064cfd0c..0000000000 --- a/tests/resources/events/aws/cloudfront-redirect-unauthenticated-users.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "Records": [ - { - "cf": { - "config": { - "distributionId": "EXAMPLE" - }, - "request": { - "uri": "/test", - "method": "GET", - "querystring": "foo=bar", - "headers": { - "host": [ - { - "key": "Host", - "value": "d123.cf.net" - } - ] - } - } - } - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/cloudfront-response-generation.json b/tests/resources/events/aws/cloudfront-response-generation.json deleted file mode 100644 index 2bfde0f0d6..0000000000 --- a/tests/resources/events/aws/cloudfront-response-generation.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "Records": [ - { - "cf": { - "config": { - "distributionId": "EXAMPLE" - }, - "request": { - "uri": "/test", - "method": "GET", - "clientIp": "2001:cdba::3257:9652", - "headers": { - "host": [ - { - "key": "Host", - "value": "d123.cf.net" - } - ] - } - } - } - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/cloudfront-serve-object-on-viewer-device.json b/tests/resources/events/aws/cloudfront-serve-object-on-viewer-device.json deleted file mode 100644 index f1f50a8980..0000000000 --- a/tests/resources/events/aws/cloudfront-serve-object-on-viewer-device.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "Records": [ - { - "cf": { - "config": { - "distributionId": "EXAMPLE" - }, - "request": { - "uri": "/test", - "method": "GET", - "clientIp": "2001:cdba::3257:9652", - "headers": { - "host": [ - { - "key": "Host", - "value": "d123.cf.net" - } - ], - "cloudfront-is-desktop-viewer": [ - { - "key": "CloudFront-Is-Desktop-Viewer", - "value": "true" - } - ], - "cloudfront-is-mobile-viewer": [ - { - "key": "CloudFront-Is-Mobile-Viewer", - "value": "false" - } - ], - "cloudfront-is-smarttv-viewer": [ - { - "key": "CloudFront-Is-SmartTV-Viewer", - "value": "false" - } - ], - "cloudfront-is-tablet-viewer": [ - { - "key": "CloudFront-Is-Tablet-Viewer", - "value": "false" - } - ] - } - } - } - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/cloudfront-simple-remote-call.json b/tests/resources/events/aws/cloudfront-simple-remote-call.json deleted file mode 100644 index bf4625d06d..0000000000 --- a/tests/resources/events/aws/cloudfront-simple-remote-call.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "Records": [ - { - "cf": { - "config": { - "distributionId": "EXAMPLE" - }, - "request": { - "uri": "/test", - "method": "GET", - "clientIp": "2001:cdba::3257:9652", - "headers": { - "host": [ - { - "key": "Host", - "value": "d123.cf.net" - } - ], - "user-agent": [ - { - "key": "User-Agent", - "value": "Test Agent" - } - ], - "user-name": [ - { - "key": "User-Name", - "value": "aws-cloudfront" - } - ] - } - } - } - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/cloudwatch-logs.json b/tests/resources/events/aws/cloudwatch-logs.json deleted file mode 100644 index 2b455b9bc4..0000000000 --- a/tests/resources/events/aws/cloudwatch-logs.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "awslogs": { - "data": "H4sIAAAAAAAAAHWPwQqCQBCGX0Xm7EFtK+smZBEUgXoLCdMhFtKV3akI8d0bLYmibvPPN3wz00CJxmQnTO41whwWQRIctmEcB6sQbFC3CjW3XW8kxpOpP+OC22d1Wml1qZkQGtoMsScxaczKN3plG8zlaHIta5KqWsozoTYw3/djzwhpLwivWFGHGpAFe7DL68JlBUk+l7KSN7tCOEJ4M3/qOI49vMHj+zCKdlFqLaU2ZHV2a4Ct/an0/ivdX8oYc1UVX860fQDQiMdxRQEAAA==" - } -} \ No newline at end of file diff --git a/tests/resources/events/aws/cloudwatch-scheduled-event.json b/tests/resources/events/aws/cloudwatch-scheduled-event.json deleted file mode 100644 index dfa0a04b5a..0000000000 --- a/tests/resources/events/aws/cloudwatch-scheduled-event.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c", - "detail-type": "Scheduled Event", - "source": "aws.events", - "account": "123456789012", - "time": "1970-01-01T00:00:00Z", - "region": "eu-central-1", - "resources": [ - "arn:aws:events:eu-central-1:123456789012:rule/ExampleRule" - ], - "detail": {} -} \ No newline at end of file diff --git a/tests/resources/events/aws/codecommit-repository.json b/tests/resources/events/aws/codecommit-repository.json deleted file mode 100644 index b30c7c6faa..0000000000 --- a/tests/resources/events/aws/codecommit-repository.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "Records": [ - { - "awsRegion": "eu-central-1", - "codecommit": { - "references": [ - { - "commit": "5c4ef1049f1d2712345678f313e0730018be182b", - "ref": "refs/heads/master" - } - ] - }, - "customData": "this is custom data", - "eventId": "5a824061-17ca-46a9-bbf9-114e12345678", - "eventName": "TriggerEventTest", - "eventPartNumber": 1, - "eventSource": "aws:codecommit", - "eventSourceARN": "arn:aws:codecommit:eu-central-1:123456789012:my-repo", - "eventTime": "2016-01-01T23:59:59.000+0000", - "eventTotalParts": 1, - "eventTriggerConfigId": "5a824061-17ca-46a9-bbf9-114e12345678", - "eventTriggerName": "my-trigger", - "eventVersion": "1.0", - "userIdentityARN": "arn:aws:iam::123456789012:root" - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/codepipeline-job.json b/tests/resources/events/aws/codepipeline-job.json deleted file mode 100644 index 13763cfadf..0000000000 --- a/tests/resources/events/aws/codepipeline-job.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "CodePipeline.job": { - "data": { - "artifactCredentials": { - "secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "sessionToken": "token", - "accessKeyId": "AKIAIOSFODNN7EXAMPLE" - }, - "actionConfiguration": { - "configuration": { - "FunctionName": "my-function", - "UserParameters": "user-parameter-string" - } - }, - "inputArtifacts": [ - { - "revision": "ca2b123456787d1932acb4977e08c803295d9896", - "name": "input-artifact", - "location": { - "type": "S3", - "s3Location": { - "objectKey": "test/key", - "bucketName": "example-bucket" - } - } - } - ], - "outputArtifacts": [ - { - "revision": null, - "name": "output-artifact", - "location": { - "type": "S3", - "s3Location": { - "objectKey": "test/key2", - "bucketName": "example-bucket2" - } - } - } - ] - }, - "id": "c968ef10-6415-4127-80b1-42502218a8c7", - "accountId": "123456789012" - } -} \ No newline at end of file diff --git a/tests/resources/events/aws/cognito-sync-trigger.json b/tests/resources/events/aws/cognito-sync-trigger.json deleted file mode 100644 index e9c7a0ddd0..0000000000 --- a/tests/resources/events/aws/cognito-sync-trigger.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": 2, - "eventType": "SyncTrigger", - "region": "eu-central-1", - "identityPoolId": "identityPoolId", - "identityId": "identityId", - "datasetName": "datasetName", - "datasetRecords": { - "SampleKey1": { - "oldValue": "oldValue1", - "newValue": "newValue1", - "op": "replace" - }, - "SampleKey2": { - "oldValue": "oldValue2", - "newValue": "newValue2", - "op": "replace" - } - } -} \ No newline at end of file diff --git a/tests/resources/events/aws/config-oversized-item-change-notification.json b/tests/resources/events/aws/config-oversized-item-change-notification.json deleted file mode 100644 index fb20d98bb7..0000000000 --- a/tests/resources/events/aws/config-oversized-item-change-notification.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "invokingEvent": "{\"configurationItemSummary\": {\"changeType\": \"UPDATE\",\"configurationItemVersion\": \"1.2\",\"configurationItemCaptureTime\":\"2016-10-06T16:46:16.261Z\",\"configurationStateId\": 0,\"awsAccountId\":\"123456789012\",\"configurationItemStatus\": \"OK\",\"resourceType\": \"AWS::EC2::Instance\",\"resourceId\":\"i-00000000\",\"resourceName\":null,\"ARN\":\"arn:aws:ec2:eu-central-1:123456789012:instance/i-00000000\",\"awsRegion\": \"eu-central-1\",\"availabilityZone\":\"eu-central-1\",\"configurationStateMd5Hash\":\"8f1ee69b287895a0f8bc5753eca68e96\",\"resourceCreationTime\":\"2016-10-06T16:46:10.489Z\"},\"messageType\":\"OversizedConfigurationItemChangeNotification\"}", - "ruleParameters": "{\"\":\"\"}", - "resultToken": "myResultToken", - "eventLeftScope": false, - "executionRoleArn": "arn:aws:iam::123456789012:role/config-role", - "configRuleArn": "arn:aws:config:eu-central-1:123456789012:config-rule/config-rule-0123456", - "configRuleName": "change-triggered-config-rule", - "configRuleId": "config-rule-0123456", - "accountId": "123456789012", - "version": "1.0" -} \ No newline at end of file diff --git a/tests/resources/events/aws/config-periodic-rule.json b/tests/resources/events/aws/config-periodic-rule.json deleted file mode 100644 index 03322a8b12..0000000000 --- a/tests/resources/events/aws/config-periodic-rule.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "invokingEvent": "{\"awsAccountId\":\"123456789012\",\"notificationCreationTime\":\"1970-01-01T00:00:00.0Z\",\"messageType\":\"ScheduledNotification\",\"recordVersion\":\"1.0\"}", - "ruleParameters": "{\"myParameterKey\":\"myParameterValue\"}", - "resultToken": "myResultToken", - "eventLeftScope": false, - "executionRoleArn": "arn:aws:iam::123456789012:role/config-role", - "configRuleArn": "arn:aws:config:eu-central-1:123456789012:config-rule/config-rule-0123456", - "configRuleName": "periodic-config-rule", - "configRuleId": "config-rule-0123456", - "accountId": "123456789012", - "version": "1.0" -} \ No newline at end of file diff --git a/tests/resources/events/aws/confit-item-change-notification.json b/tests/resources/events/aws/confit-item-change-notification.json deleted file mode 100644 index cf79568736..0000000000 --- a/tests/resources/events/aws/confit-item-change-notification.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2016-10-06T16:46:16.261Z\",\"awsAccountId\":\"123456789012\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"i-00000000\",\"resourceName\":\"foo\",\"configurationStateMd5Hash\":\"8f1ee69b297895a0f8bc5753eca68e96\",\"resourceCreationTime\":\"2016-10-06T16:46:10.489Z\",\"configurationStateId\":0,\"configurationItemVersion\":\"1.2\",\"ARN\":\"arn:aws:ec2:eu-central-1:123456789012:instance/i-00000000\",\"awsRegion\":\"eu-central-1\",\"availabilityZone\":\"eu-central-1\",\"resourceType\":\"AWS::EC2::Instance\",\"tags\":{\"\":\"\"},\"relationships\":[{\"resourceId\":\"eipalloc-00000000\",\"resourceType\":\"AWS::EC2::EIP\",\"name\":\"Is attached to ElasticIp\"}],\"configuration\":{\"\":\"\"}},\"messageType\":\"ConfigurationItemChangeNotification\"}", - "ruleParameters": "{\"\":\"\"}", - "resultToken": "myResultToken", - "eventLeftScope": false, - "executionRoleArn": "arn:aws:iam::123456789012:role/config-role", - "configRuleArn": "arn:aws:config:eu-central-1:123456789012:config-rule/config-rule-0123456", - "configRuleName": "change-triggered-config-rule", - "configRuleId": "config-rule-0123456", - "accountId": "123456789012", - "version": "1.0" -} \ No newline at end of file diff --git a/tests/resources/events/aws/connect-contact-flow-event.json b/tests/resources/events/aws/connect-contact-flow-event.json deleted file mode 100644 index b192be3150..0000000000 --- a/tests/resources/events/aws/connect-contact-flow-event.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "Name": "ContactFlowEvent", - "Details": { - "ContactData": { - "Attributes": {}, - "Channel": "VOICE", - "ContactId": "5ca32fbd-8f92-46af-92a5-6b0f970f0efe", - "CustomerEndpoint": { - "Address": "+11234567890", - "Type": "TELEPHONE_NUMBER" - }, - "InitialContactId": "5ca32fbd-8f92-46af-92a5-6b0f970f0efe", - "InitiationMethod": "API", - "InstanceARN": "arn:aws:connect:eu-central-1:123456789012:instance/9308c2a1-9bc6-4cea-8290-6c0b4a6d38fa", - "MediaStreams": { - "Customer": { - "Audio": { - "StartFragmentNumber": "91343852333181432392682062622220590765191907586", - "StartTimestamp": "1565781909613", - "StreamARN": "arn:aws:kinesisvideo:eu-central-1:123456789012:stream/connect-contact-a3d73b84-ce0e-479a-a9dc-5637c9d30ac9/1565272947806" - } - } - }, - "PreviousContactId": "5ca32fbd-8f92-46af-92a5-6b0f970f0efe", - "Queue": null, - "SystemEndpoint": { - "Address": "+11234567890", - "Type": "TELEPHONE_NUMBER" - } - }, - "Parameters": {} - } -} \ No newline at end of file diff --git a/tests/resources/events/aws/dynamodb-update-2.json b/tests/resources/events/aws/dynamodb-update-2.json deleted file mode 100644 index c7f8ca2795..0000000000 --- a/tests/resources/events/aws/dynamodb-update-2.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "Records": [ - { - "eventID": "1", - "eventVersion": "1.0", - "dynamodb": { - "Keys": { - "Id": { - "N": "101" - } - }, - "NewImage": { - "Message": { - "S": "New item!" - }, - "Id": { - "N": "101" - } - }, - "StreamViewType": "NEW_AND_OLD_IMAGES", - "SequenceNumber": "111", - "SizeBytes": 26 - }, - "awsRegion": "eu-central-1", - "eventName": "INSERT", - "eventSourceARN": "arn:aws:dynamodb:eu-central-1:account-id:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899", - "eventSource": "aws:dynamodb" - }, - { - "eventID": "2", - "eventVersion": "1.0", - "dynamodb": { - "OldImage": { - "Message": { - "S": "New item!" - }, - "Id": { - "N": "101" - } - }, - "SequenceNumber": "222", - "Keys": { - "Id": { - "N": "101" - } - }, - "SizeBytes": 59, - "NewImage": { - "Message": { - "S": "This item has changed" - }, - "Id": { - "N": "101" - } - }, - "StreamViewType": "NEW_AND_OLD_IMAGES" - }, - "awsRegion": "eu-central-1", - "eventName": "MODIFY", - "eventSourceARN": "arn:aws:dynamodb:eu-central-1:account-id:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899", - "eventSource": "aws:dynamodb" - }, - { - "eventID": "3", - "eventVersion": "1.0", - "dynamodb": { - "Keys": { - "Id": { - "N": "101" - } - }, - "SizeBytes": 38, - "SequenceNumber": "333", - "OldImage": { - "Message": { - "S": "This item has changed" - }, - "Id": { - "N": "101" - } - }, - "StreamViewType": "NEW_AND_OLD_IMAGES" - }, - "awsRegion": "eu-central-1", - "eventName": "REMOVE", - "eventSourceARN": "arn:aws:dynamodb:eu-central-1:account-id:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899", - "eventSource": "aws:dynamodb" - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/dynamodb-update.json b/tests/resources/events/aws/dynamodb-update.json deleted file mode 100644 index f59d46c645..0000000000 --- a/tests/resources/events/aws/dynamodb-update.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "Records": [ - { - "eventID": "c4ca4238a0b923820dcc509a6f75849b", - "eventName": "INSERT", - "eventVersion": "1.1", - "eventSource": "aws:dynamodb", - "awsRegion": "eu-central-1", - "dynamodb": { - "Keys": { - "Id": { - "N": "101" - } - }, - "NewImage": { - "Message": { - "S": "New item!" - }, - "Id": { - "N": "101" - } - }, - "ApproximateCreationDateTime": 1428537600, - "SequenceNumber": "4421584500000000017450439091", - "SizeBytes": 26, - "StreamViewType": "NEW_AND_OLD_IMAGES" - }, - "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899" - }, - { - "eventID": "c81e728d9d4c2f636f067f89cc14862c", - "eventName": "MODIFY", - "eventVersion": "1.1", - "eventSource": "aws:dynamodb", - "awsRegion": "eu-central-1", - "dynamodb": { - "Keys": { - "Id": { - "N": "101" - } - }, - "NewImage": { - "Message": { - "S": "This item has changed" - }, - "Id": { - "N": "101" - } - }, - "OldImage": { - "Message": { - "S": "New item!" - }, - "Id": { - "N": "101" - } - }, - "ApproximateCreationDateTime": 1428537600, - "SequenceNumber": "4421584500000000017450439092", - "SizeBytes": 59, - "StreamViewType": "NEW_AND_OLD_IMAGES" - }, - "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899" - }, - { - "eventID": "eccbc87e4b5ce2fe28308fd9f2a7baf3", - "eventName": "REMOVE", - "eventVersion": "1.1", - "eventSource": "aws:dynamodb", - "awsRegion": "eu-central-1", - "dynamodb": { - "Keys": { - "Id": { - "N": "101" - } - }, - "OldImage": { - "Message": { - "S": "This item has changed" - }, - "Id": { - "N": "101" - } - }, - "ApproximateCreationDateTime": 1428537600, - "SequenceNumber": "4421584500000000017450439093", - "SizeBytes": 38, - "StreamViewType": "NEW_AND_OLD_IMAGES" - }, - "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899" - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/kinesis-analytics-compressed.json b/tests/resources/events/aws/kinesis-analytics-compressed.json deleted file mode 100644 index b6c3a539d5..0000000000 --- a/tests/resources/events/aws/kinesis-analytics-compressed.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "invocationId": "invocationIdExample", - "applicationArn": "arn:aws:kinesisanalytics:eu-central-1:123456789012:application/example-application", - "streamArn": "arn:aws:kinesis:eu-central-1:123456789012:stream/example-stream", - "records": [ - { - "recordId": "49571347871967966406409637155186850213682522142927749122", - "data": "H4sIAAAAAAAA/6vmUspLzE1VslLKTsxNzFHS4VJKTEkpSi0uBgol5SRmKHHVAgDd1tysJAAAAA==" - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/kinesis-analytics-dynamodb.json b/tests/resources/events/aws/kinesis-analytics-dynamodb.json deleted file mode 100644 index 8d45307cac..0000000000 --- a/tests/resources/events/aws/kinesis-analytics-dynamodb.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "invocationId": "invocationIdExample", - "applicationArn": "arn:aws:kinesisanalytics:eu-central-1:123456789012:application/example-application", - "streamArn": "arn:aws:kinesis:eu-central-1:123456789012:stream/example-stream", - "records": [ - { - "recordId": "49571347871967966406409637155186850213682522142927749122", - "data": "eyJST1dUSU1FX1RJTUVTVEFNUCI6IjIwMTctMTItMTUgMDE6MDk6NTAuMDAwIiwiVkVISUNMRUlEIjoiNSIsIlZFSElDTEVDT1VOVCI6MTh9" - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/kinesis-analytics-kpl.json b/tests/resources/events/aws/kinesis-analytics-kpl.json deleted file mode 100644 index 807467ead1..0000000000 --- a/tests/resources/events/aws/kinesis-analytics-kpl.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "invocationId": "invocationIdExample", - "applicationArn": "arn:aws:kinesisanalytics:eu-central-1:123456789012:application/example-application", - "streamArn": "arn:aws:kinesis:eu-central-1:123456789012:stream/example-stream", - "records": [ - { - "recordId": "49571347871967966406409637155186850213682522142927749122", - "kinesisStreamRecordMetadata": { - "shardId": "shardId-000000000001", - "partitionKey": "partitionKey-01", - "approximateArrivalTimestamp": 1505169657117, - "sequenceNumber": "49571347871967966406409637155186850213682522142927749122" - }, - "data": "84mawgoNMTUwNTE2OTY0NTI1MBIlODYwOTcwOTY1NTE0NjQwMzU1MjIwNTI5MjgyNDk2MzU3MTUzNxImNzk1MTM3MTQxMzM4MTE5NTU1OTExMzA1ODMxNDEyOTg2NTM2OTgaiAEIABAAGoEBNDAsYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWEKGogBCAAQARqBATQxLGFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhCkU/elbPye5g1wGkAmxGCNg=" - }, - { - "recordId": "49571347871967966406409637141563465152445265354688561154", - "kinesisStreamRecordMetadata": { - "shardId": "shardId-000000000002", - "partitionKey": "partitionKey-02", - "approximateArrivalTimestamp": 1505169647777, - "sequenceNumber": "49571347871967966406409637141563465152445265354688561154" - }, - "data": "NCxhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWEK" - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/kinesis-analytics.json b/tests/resources/events/aws/kinesis-analytics.json deleted file mode 100644 index 7f1db565b1..0000000000 --- a/tests/resources/events/aws/kinesis-analytics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "invocationId": "invocationIdExample", - "applicationArn": "arn:aws:kinesisanalytics:eu-central-1:123456789012:application/example-application", - "streamArn": "arn:aws:kinesis:eu-central-1:123456789012:stream/example-stream", - "records": [ - { - "recordId": "49571347871967966406409637155186850213682522142927749122", - "data": "VGhpcyBpcyBhIHRlc3QgZnJvbSBLaW5lc2lzIEFuYWx5dGljcw==" - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/kinesis-apachelog.json b/tests/resources/events/aws/kinesis-apachelog.json deleted file mode 100644 index 3f917a1613..0000000000 --- a/tests/resources/events/aws/kinesis-apachelog.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "invocationId": "invocationIdExample", - "region": "eu-central-1", - "records": [ - { - "recordId": "49546986683135544286507457936321625675700192471156785154", - "approximateArrivalTimestamp": 1495072949453, - "data": "NjQuMjQyLjg4LjEwIC0gLSBbMDcvTWFyLzIwMDQ6MTY6MTA6MDIgLTA4MDBdICJHRVQgL21haWxtYW4vbGlzdGluZm8vaHNkaXZpc2lvbiBIVFRQLzEuMSIgMjAwIDYyOTE==" - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/kinesis-cloudwatch-logs-processor.json b/tests/resources/events/aws/kinesis-cloudwatch-logs-processor.json deleted file mode 100644 index c7978af868..0000000000 --- a/tests/resources/events/aws/kinesis-cloudwatch-logs-processor.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "records": [ - { - "recordId": "49578734086442259037497492980620233840400173390482112514000000", - "data": "H4sIAAAAAAAAADWO0QqCMBiFX2XsWiJFi7wLUW8sIYUuQmLpnxvpJttMQnz3Ztrlxzmc8424BaVIDfmnA+zjID3nlzS5n8IsO8YhtrAYOMg5aURfDUSXNBG1MkEj6liKvjPZQpmWQNoFVf9QpWSdZoJHrNEgFfZvxa8XvoHrGUfMqqWumdHQpDVjtmdvHc91dwdn71p/vVngmqBVD616PgoolC/Ga0SBNJoi8USVWWKczM8oYhKoULDBUzF9Aeua5yHsAAAA", - "approximateArrivalTimestamp": 1510254469499 - }, - { - "recordId": "49578734086442259037497492980621442766219788363254202370000000", - "data": "H4sIAAAAAAAAAJWRTWsbMRCG/8ueLZjRjL5yc9NNLnZDapemlFAkrTYstb3Lep0Qgv97x00KgTSHnAQzmkeP3nmqtmW/j3dl/TiU6qz6PF/Pfy3r1Wp+WVezqn/YlVHK2pK3Hr0Jxkt5099djv1hkE7uh0eVHzZqE7epiarb3fe/ixzDYVJoELRhssYQqsXLlEJ3jd8//biy4QYWz7jVNJa4/TDveQwV+qsada0v/HnthLg/pH0eu2Hq+t1Ft5nKuK/Ofn4EvnpDUAu7Xi6/LL9en3/z1e1f7fq+7KYT+qnqGrEnsi54AGS2wbHWxjCjoWAYGawmzawByIG3Dp0JzjOxsaI8dbKJKW4l1BcTdgg+zP5tSPCeQ/Bso/I+o+I2kUptjgrRlQyasslUHWdvZRwGJ4+HYJGCtiKgQTYKSJ4gODLgAkpFk3f0rkyA1zLGSsvoVsVCRTFakUkNqKxt1IyFc8T/y0gEmoHZo5a/W9HhU0TeWHMyIJaoQC6zDvC+DL6WSW3MqZSkiolJcWoalWybJSNIJTXcRgjV8fb4BwwLrNzwAgAA", - "approximateArrivalTimestamp": 1510254473773 - }, - { - "recordId": "49578734086442259037497492980622651692039402992428908546000000", - "data": "H4sIAAAAAAAAAJWRbWsbMQyA/8t9jkGSJdnut2zLCiXZyJKyZaMM352vHEty4e7SUkr++9yXwUbXD8Vgg2w9eizdF7s0DPE6re8OqTgrPkzX05+L2Wo1PZ8Vk6K73ac+h0mtV49egvgc3nbX5313POSbqjvcmep2a7ZxV9bRtPub7lfKx+E4GhQEErYqYtHMn7MMuiV+fbf5rOEbzJ9wq7FPcfdm3lOaNReXyws/3cw2fvk9A4djOVR9exjbbv+x3Y6pH4qzH29hr14QzFzXi8WnxZfl+0tfXD1az27SfnxA3xdtneWtVRc8ADJrcEwkwoxigzAyiBNxzkJuIxGrei+g3gbgrDy2eRBj3OWePpuwQ/Bh8mdAGR+J69pJMFXKihwTGJ+aYJArpkjYQB2K0+SljMPgyFIIijaQgs2BAMEyexbns1NeoqpsCV+VCfCPTOVLLgUMU4h5S5UpE4BRm6ROqCEF/r8MExBDro3ED0XBMigFVM0iQlkRvZLml9a/LoN/yzSYKoIKTOmVTf6VNTHZxkjTIElkqlCL09XpN5PgkxrvAgAA", - "approximateArrivalTimestamp": 1510254474027 - }, - { - "recordId": "49578734086442259037497492980623860617859017621603614722000000", - "data": "H4sIAAAAAAAAAJWRW28aQQyF/8s+M9J47LHHeaMtzUOhEQXSVlVUDctstCqwCJZEUcR/r3OpFCnNQ17mcjw+8+n4vtqUwyFfl/ndrlRn1afhfPh7MprNhuejalB1t9uyNzkwJk6QosZk8rq7Pt93x51V6m535+rbtVvnzXKVXbu96f4U23bH3kEEHyIhx4jgxs9dDmQK3z/8vGD94cdPdrN+X/Lm3X5PbcHp5QLkYrqYLC6/mOHhuDzU+3bXt932c7vuy/5Qnf16j/fslYMb83wy+Tr5Nv24SNXVI/Xopmz7B+v7ql0ZPCKLJu+BiFUohBiJIKJGAvIkSTgpsU8aVBNangymsCH3rQ2izxvL9JmEBOzh4N+AzL6gX3JD7CLn4kg8OiVduahNkIa0BtbqNHgNI6AS0P5kQA3sUcA4IDCElCBKwgdgiCoI+CaM+pcwbAVfN8F5r2owGV0OdpWkS8kp52a1/D8MBR8sDUoQKDIbDnqlhAgQLTMWz8YbRQT92zDwEkbIQ10YHUZbKGfvUmrAIWodih2btKpOV6e/zXGIX+8CAAA=", - "approximateArrivalTimestamp": 1510254474388 - } - ], - "region": "eu-central-1", - "deliveryStreamArn": "arn:aws:firehose:eu-central-1:123456789012:deliverystream/copy-cwl-lambda-invoke-input-151025436553-Firehose-8KILJ01Q5OBN", - "invocationId": "a7234216-12b6-4bc0-96d7-82606c0e80cf" -} \ No newline at end of file diff --git a/tests/resources/events/aws/kinesis-get-records.json b/tests/resources/events/aws/kinesis-get-records.json deleted file mode 100644 index 12abdabd3c..0000000000 --- a/tests/resources/events/aws/kinesis-get-records.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "Records": [ - { - "kinesis": { - "partitionKey": "partitionKey-03", - "kinesisSchemaVersion": "1.0", - "data": "SGVsbG8sIHRoaXMgaXMgYSB0ZXN0IDEyMy4=", - "sequenceNumber": "49545115243490985018280067714973144582180062593244200961", - "approximateArrivalTimestamp": 1428537600 - }, - "eventSource": "aws:kinesis", - "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961", - "invokeIdentityArn": "arn:aws:iam::EXAMPLE", - "eventVersion": "1.0", - "eventName": "aws:kinesis:record", - "eventSourceARN": "arn:aws:kinesis:EXAMPLE", - "awsRegion": "eu-central-1" - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/kinesis-kinesis-firehose.json b/tests/resources/events/aws/kinesis-kinesis-firehose.json deleted file mode 100644 index df23583934..0000000000 --- a/tests/resources/events/aws/kinesis-kinesis-firehose.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "invocationId": "invocationIdExample", - "deliveryStreamArn": "arn:aws:kinesis:EXAMPLE", - "region": "eu-central-1", - "records": [ - { - "recordId": "49546986683135544286507457936321625675700192471156785154", - "approximateArrivalTimestamp": 1495072949453, - "data": "SGVsbG8sIHRoaXMgaXMgYSB0ZXN0IDEyMy4=" - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/kinesis-streams-source.json b/tests/resources/events/aws/kinesis-streams-source.json deleted file mode 100644 index a462dc7e07..0000000000 --- a/tests/resources/events/aws/kinesis-streams-source.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "invocationId": "invocationIdExample", - "deliverySteamArn": "arn:aws:kinesis:EXAMPLE", - "region": "eu-central-1", - "records": [ - { - "recordId": "49546986683135544286507457936321625675700192471156785154", - "approximateArrivalTimestamp": 1495072949453, - "kinesisRecordMetadata": { - "sequenceNumber": "49545115243490985018280067714973144582180062593244200961", - "subsequenceNumber": "123456", - "partitionKey": "partitionKey-03", - "shardId": "shardId-000000000000", - "approximateArrivalTimestamp": 1495072949453 - }, - "data": "SGVsbG8sIHRoaXMgaXMgYSB0ZXN0IDEyMy4=" - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/kinesis-syslog.json b/tests/resources/events/aws/kinesis-syslog.json deleted file mode 100644 index bac8ff5567..0000000000 --- a/tests/resources/events/aws/kinesis-syslog.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "invocationId": "fir", - "region": "eu-central-1", - "records": [ - { - "recordId": "49546986683135544286507457936321625675700192471156785154", - "approximateArrivalTimestamp": 1495072949453, - "data": "SGVsbG8sIHRoaXMgaXMgYSB0ZXN0IDEyMy4=" - }, - { - "recordId": "49546986683135544286507457936321625675700192471156785154", - "approximateArrivalTimestamp": "2012-04-23T18:25:43.511Z", - "data": "SGVsbG8sIHRoaXMgaXMgYSB0ZXN0IDEyMy4=" - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/rekognition-s3-request.json b/tests/resources/events/aws/rekognition-s3-request.json deleted file mode 100644 index 89e0bd312c..0000000000 --- a/tests/resources/events/aws/rekognition-s3-request.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "Records": [ - { - "eventVersion": "2.0", - "eventSource": "aws:s3", - "awsRegion": "eu-central-1", - "eventTime": "1970-01-01T00:00:00.000Z", - "eventName": "ObjectCreated:Put", - "userIdentity": { - "principalId": "EXAMPLE" - }, - "requestParameters": { - "sourceIPAddress": "127.0.0.1" - }, - "responseElements": { - "x-amz-request-id": "EXAMPLE123456789", - "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH" - }, - "s3": { - "s3SchemaVersion": "1.0", - "configurationId": "testConfigRule", - "bucket": { - "name": "example-bucket", - "ownerIdentity": { - "principalId": "EXAMPLE" - }, - "arn": "arn:aws:s3:::example-bucket" - }, - "object": { - "key": "test/key", - "size": 1024, - "eTag": "0123456789abcdef0123456789abcdef", - "sequencer": "0A1B2C3D4E5F678901" - } - } - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/s3-delete.json b/tests/resources/events/aws/s3-delete.json deleted file mode 100644 index fd30611694..0000000000 --- a/tests/resources/events/aws/s3-delete.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "Records": [ - { - "eventVersion": "2.0", - "eventSource": "aws:s3", - "awsRegion": "eu-central-1", - "eventTime": "1970-01-01T00:00:00.000Z", - "eventName": "ObjectRemoved:Delete", - "userIdentity": { - "principalId": "EXAMPLE" - }, - "requestParameters": { - "sourceIPAddress": "127.0.0.1" - }, - "responseElements": { - "x-amz-request-id": "EXAMPLE123456789", - "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH" - }, - "s3": { - "s3SchemaVersion": "1.0", - "configurationId": "testConfigRule", - "bucket": { - "name": "example-bucket", - "ownerIdentity": { - "principalId": "EXAMPLE" - }, - "arn": "arn:aws:s3:::example-bucket" - }, - "object": { - "key": "test/key", - "sequencer": "0A1B2C3D4E5F678901" - } - } - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/s3-put.json b/tests/resources/events/aws/s3-put.json deleted file mode 100644 index 89e0bd312c..0000000000 --- a/tests/resources/events/aws/s3-put.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "Records": [ - { - "eventVersion": "2.0", - "eventSource": "aws:s3", - "awsRegion": "eu-central-1", - "eventTime": "1970-01-01T00:00:00.000Z", - "eventName": "ObjectCreated:Put", - "userIdentity": { - "principalId": "EXAMPLE" - }, - "requestParameters": { - "sourceIPAddress": "127.0.0.1" - }, - "responseElements": { - "x-amz-request-id": "EXAMPLE123456789", - "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH" - }, - "s3": { - "s3SchemaVersion": "1.0", - "configurationId": "testConfigRule", - "bucket": { - "name": "example-bucket", - "ownerIdentity": { - "principalId": "EXAMPLE" - }, - "arn": "arn:aws:s3:::example-bucket" - }, - "object": { - "key": "test/key", - "size": 1024, - "eTag": "0123456789abcdef0123456789abcdef", - "sequencer": "0A1B2C3D4E5F678901" - } - } - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/sagemaker-ground-truth-annotation-consolidation.json b/tests/resources/events/aws/sagemaker-ground-truth-annotation-consolidation.json deleted file mode 100644 index 2aeb4fbbce..0000000000 --- a/tests/resources/events/aws/sagemaker-ground-truth-annotation-consolidation.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "version": "2018-10-16", - "labelingJobArn": "arn:aws:sagemaker:eu-central-1:123456789012:labeling-job/example-job", - "labelAttributeName": "example-attribute", - "roleArn": "aws:aws:iam::{{account_id}}:role/sagemaker-role", - "payload": { - "s3Uri": "s3://sagemakerexample/output/example-job/annotations/worker_response/iteration-1/0/2019-09-06_18:35:03.json" - } -} \ No newline at end of file diff --git a/tests/resources/events/aws/sagemaker-ground-truth-pre-human.json b/tests/resources/events/aws/sagemaker-ground-truth-pre-human.json deleted file mode 100644 index 26c9755d2c..0000000000 --- a/tests/resources/events/aws/sagemaker-ground-truth-pre-human.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": "2018-10-16", - "labelingJobArn": "arn:aws:sagemaker:eu-central-1:123456789012:labeling-job/example-job", - "dataObject": { - "source-ref": "s3://sagemakerexample/object_to_annotate.jpg" - } -} \ No newline at end of file diff --git a/tests/resources/events/aws/ses-email-receiving.json b/tests/resources/events/aws/ses-email-receiving.json deleted file mode 100644 index 3a80dfe175..0000000000 --- a/tests/resources/events/aws/ses-email-receiving.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "Records": [ - { - "eventSource": "aws:ses", - "eventVersion": "1.0", - "ses": { - "mail": { - "commonHeaders": { - "date": "Wed, 7 Oct 2015 12:34:56 -0700", - "from": [ - "Jane Doe " - ], - "messageId": "<0123456789example.com>", - "returnPath": "janedoe@example.com", - "subject": "Test Subject", - "to": [ - "johndoe@example.com" - ] - }, - "destination": [ - "johndoe@example.com" - ], - "headers": [ - { - "name": "Return-Path", - "value": "" - }, - { - "name": "Received", - "value": "from mailer.example.com (mailer.example.com [203.0.113.1]) by inbound-smtp.eu-central-1.amazonaws.com with SMTP id o3vrnil0e2ic28trm7dfhrc2v0cnbeccl4nbp0g1 for johndoe@example.com; Wed, 07 Oct 2015 12:34:56 +0000 (UTC)" - }, - { - "name": "DKIM-Signature", - "value": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; s=example; h=mime-version:from:date:message-id:subject:to:content-type; bh=jX3F0bCAI7sIbkHyy3mLYO28ieDQz2R0P8HwQkklFj4=; b=sQwJ+LMe9RjkesGu+vqU56asvMhrLRRYrWCbVt6WJulueecwfEwRf9JVWgkBTKiL6m2hr70xDbPWDhtLdLO+jB3hzjVnXwK3pYIOHw3vxG6NtJ6o61XSUwjEsp9tdyxQjZf2HNYee873832l3K1EeSXKzxYk9Pwqcpi3dMC74ct9GukjIevf1H46hm1L2d9VYTL0LGZGHOAyMnHmEGB8ZExWbI+k6khpurTQQ4sp4PZPRlgHtnj3Zzv7nmpTo7dtPG5z5S9J+L+Ba7dixT0jn3HuhaJ9b+VThboo4YfsX9PMNhWWxGjVksSFOcGluPO7QutCPyoY4gbxtwkN9W69HA==" - }, - { - "name": "MIME-Version", - "value": "1.0" - }, - { - "name": "From", - "value": "Jane Doe " - }, - { - "name": "Date", - "value": "Wed, 7 Oct 2015 12:34:56 -0700" - }, - { - "name": "Message-ID", - "value": "<0123456789example.com>" - }, - { - "name": "Subject", - "value": "Test Subject" - }, - { - "name": "To", - "value": "johndoe@example.com" - }, - { - "name": "Content-Type", - "value": "text/plain; charset=UTF-8" - } - ], - "headersTruncated": false, - "messageId": "o3vrnil0e2ic28trm7dfhrc2v0clambda4nbp0g1", - "source": "janedoe@example.com", - "timestamp": "1970-01-01T00:00:00.000Z" - }, - "receipt": { - "action": { - "functionArn": "arn:aws:lambda:eu-central-1:123456789012:function:Example", - "invocationType": "Event", - "type": "Lambda" - }, - "dkimVerdict": { - "status": "PASS" - }, - "processingTimeMillis": 574, - "recipients": [ - "johndoe@example.com" - ], - "spamVerdict": { - "status": "PASS" - }, - "spfVerdict": { - "status": "PASS" - }, - "timestamp": "1970-01-01T00:00:00.000Z", - "virusVerdict": { - "status": "PASS" - } - } - } - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/sns-notification.json b/tests/resources/events/aws/sns-notification.json deleted file mode 100644 index 4bb79dee1e..0000000000 --- a/tests/resources/events/aws/sns-notification.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "Records": [ - { - "EventSource": "aws:sns", - "EventVersion": "1.0", - "EventSubscriptionArn": "arn:aws:sns:eu-central-1:{{{accountId}}}:ExampleTopic", - "Sns": { - "Type": "Notification", - "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e", - "TopicArn": "arn:aws:sns:eu-central-1:123456789012:ExampleTopic", - "Subject": "example subject", - "Message": "example message", - "Timestamp": "1970-01-01T00:00:00.000Z", - "SignatureVersion": "1", - "Signature": "EXAMPLE", - "SigningCertUrl": "EXAMPLE", - "UnsubscribeUrl": "EXAMPLE", - "MessageAttributes": { - "Test": { - "Type": "String", - "Value": "TestString" - }, - "TestBinary": { - "Type": "Binary", - "Value": "TestBinary" - } - } - } - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/sqs-receive-message.json b/tests/resources/events/aws/sqs-receive-message.json deleted file mode 100644 index a24697a237..0000000000 --- a/tests/resources/events/aws/sqs-receive-message.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "Records": [ - { - "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78", - "receiptHandle": "MessageReceiptHandle", - "body": "Hello from SQS!", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1523232000000", - "SenderId": "123456789012", - "ApproximateFirstReceiveTimestamp": "1523232000001" - }, - "messageAttributes": {}, - "md5OfBody": "{{{md5_of_body}}}", - "eventSource": "aws:sqs", - "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:MyQueue", - "awsRegion": "eu-central-1" - } - ] -} \ No newline at end of file diff --git a/tests/resources/events/aws/stepfunctions-error.json b/tests/resources/events/aws/stepfunctions-error.json deleted file mode 100644 index 9e26dfeeb6..0000000000 --- a/tests/resources/events/aws/stepfunctions-error.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/tests/resources/events/custom/hello-world.json b/tests/resources/events/custom/hello-world.json deleted file mode 100644 index 4ccb6ff485..0000000000 --- a/tests/resources/events/custom/hello-world.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "key1": "value1", - "key2": "value2", - "key3": "value3" -} \ No newline at end of file From c37932d432bf8d26b48c991a089a2ec794e7f667 Mon Sep 17 00:00:00 2001 From: Connor Kirkpatrick Date: Tue, 15 Nov 2022 15:09:03 +0000 Subject: [PATCH 07/15] docs: update environment variable tables for utilities (#1153) * chore(docs): update environment variable table * Correct the default value for the POWERTOOLS_METRICS_NAMESPACE env var * Refer reader to utility page for further info on env vars * chore(docs): add default, allowed and example values to utility docs * Extract default and allowed values from description to their own cols * Add allowed values, example and default columns * Correct default value for metrics namespace * refactor: set default service name in utility class Previously the Metric and Tracer classes did not set a service name if one was not provided. There was no default service name. This commit sets a default service name in the Utility class, and updates the Tracer, Metric and Logger classes to use it. * test: fix Logger tests Add missing `defaultServiceName` property to expected Logger object Correct typo in test names * test(tracer): remove invalid test Remove a test that is no longer valid. As there is a default service name, the tracer should not be enabled even if there is a service name Update the `addServiceAnnotation` function to remove redundant check * docs(metrics): correct default value * test(metrics): update metric tests to account for default service dim The `service_name` is now set by default. This means it is always present in metric dimensions. This commit updates the tests to account for this change * test(tracer): add test for default service name * test(utility): add test for new method Add test for getDefaultServiceName method * test(utility): fix getdefaultservicename test * refactor: extract isvalidservicename method to utility class This commit extracts the isValidServiceName method from the Tracer class to the Utility class. * test(utility): fix test description * refactor: change utility class methods from public to protected * test(utility): refactor tests to allow for protected methods * docs(index): remove quotation marks from default values * test(metrics): add comments to explain magic numbers * doc(tracer): add explanation for definite assignment assertion operator * test(tracer): refactor test to match project norms --- docs/core/logger.md | 12 +++--- docs/core/metrics.md | 8 ++-- docs/core/tracer.md | 14 +++---- docs/index.md | 6 ++- packages/commons/src/Utility.ts | 15 ++++++- packages/commons/tests/unit/Utility.test.ts | 45 +++++++++++++++++++++ packages/logger/src/Logger.ts | 4 +- packages/logger/tests/unit/Logger.test.ts | 5 +++ packages/logger/tests/unit/helpers.test.ts | 22 +++++----- packages/metrics/src/Metrics.ts | 2 +- packages/metrics/tests/unit/Metrics.test.ts | 27 ++++++++----- packages/tracer/src/Tracer.ts | 22 ++++------ packages/tracer/tests/unit/Tracer.test.ts | 5 ++- 13 files changed, 128 insertions(+), 59 deletions(-) diff --git a/docs/core/logger.md b/docs/core/logger.md index 1153edfb3d..02d95a794f 100644 --- a/docs/core/logger.md +++ b/docs/core/logger.md @@ -52,12 +52,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 | 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` | `serviceName` | -| **Logging level** | Sets how verbose Logger should be (INFO, by default). Supported values are: `DEBUG`, `INFO`, `WARN`, `ERROR` | `LOG_LEVEL` | `logLevel` | -| **Log incoming event** | Whether to log or not the incoming event when using the decorator or middleware. Supported values are: `true`, or `false`, disabled by default | `POWERTOOLS_LOGGER_LOG_EVENT` | `logEvent` | -| **Debug log sampling** | Probability that a Lambda invocation will print all the log items regardless of the log level setting. Supported values range from `0.0` to `1` | `POWERTOOLS_LOGGER_SAMPLE_RATE` | `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 | `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` | #### Example using AWS Serverless Application Model (SAM) diff --git a/docs/core/metrics.md b/docs/core/metrics.md index 3b2d4baa3f..661cc78de6 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -66,10 +66,10 @@ The library requires two settings. You can set them as environment variables, or These settings will be used across all metrics emitted: -| Setting | Description | Environment variable | Constructor parameter | -|----------------------|---------------------------------------------------------------------------------|--------------------------------|-----------------------| -| **Service** | Optionally, sets **service** metric dimension across all metrics e.g. `payment` | `POWERTOOLS_SERVICE_NAME` | `serviceName` | -| **Metric namespace** | Logical container where all metrics will be placed e.g. `serverlessAirline` | `POWERTOOLS_METRICS_NAMESPACE` | `namespace` | +| Setting | Description | Environment variable | Default | Allowed Values | Example | Constructor parameter | +|----------------------|-----------------------------------------------------------------|-------------------------------|--------------------|----------------|--------------------|-----------------------| +| **Service** | Optionally, sets **service** metric dimension across all metrics| `POWERTOOLS_SERVICE_NAME` | `service_undefined`| Any string | `serverlessAirline`| `serviceName` | +| **Metric namespace** | Logical container where all metrics will be placed | `POWERTOOLS_METRICS_NAMESPACE`| `default_namespace`| Any string | `serverlessAirline`| `default_namespace` | !!! tip Use your application name or main service as the metric namespace to easily group all metrics diff --git a/docs/core/tracer.md b/docs/core/tracer.md index ec90e4977a..c51e76a21f 100644 --- a/docs/core/tracer.md +++ b/docs/core/tracer.md @@ -50,13 +50,13 @@ The `Tracer` utility must always be instantiated outside of the Lambda handler. The library has three optional settings. You can set them as environment variables, or pass them in the constructor: -| Setting | Description | Environment variable | Constructor parameter | -|----------------------------|-----------------------------------------------------------------------------------------------------------------| -------------------------------------------|------------------------| -| **Service name** | Sets an annotation with the **name of the service** across all traces e.g. `serverlessAirline` | `POWERTOOLS_SERVICE_NAME` | `serviceName` | -| **Tracing enabled** | Enables or disables tracing. By default tracing is enabled when running in AWS Lambda | `POWERTOOLS_TRACE_ENABLED` | `enabled` | -| **Capture HTTPs Requests** | Defines whether HTTPs requests will be traced or not, enabled by default when tracing is also enabled | `POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS` | `captureHTTPsRequests` | -| **Capture Response** | Defines whether functions responses are serialized as metadata, enabled by default when tracing is also enabled | `POWERTOOLS_TRACER_CAPTURE_RESPONSE` | `captureResult` | -| **Capture Errors** | Defines whether functions errors are serialized as metadata, enabled by default when tracing is also enabled | `POWERTOOLS_TRACER_CAPTURE_ERROR` | N/A | +| Setting | Description | Environment variable | Default | Allowed Values | Example | Constructor parameter | +|----------------------------|-----------------------------------------------------------------------| -------------------------------------------|--------------------|------------------|--------------------|------------------------| +| **Service name** | Sets an annotation with the **name of the service** across all traces | `POWERTOOLS_SERVICE_NAME` | `service_undefined`| Any string | `serverlessAirline`| `serviceName` | +| **Tracing enabled** | Enables or disables tracing. | `POWERTOOLS_TRACE_ENABLED` | `true `| `true` or `false`| `false` | `enabled` | +| **Capture HTTPs Requests** | Defines whether HTTPs requests will be traced or not | `POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS` | `true` | `true` or `false`| `false` | `captureHTTPsRequests` | +| **Capture Response** | Defines whether functions responses are serialized as metadata | `POWERTOOLS_TRACER_CAPTURE_RESPONSE` | `true` | `true` or `false`| `false` | `captureResult` | +| **Capture Errors** | Defines whether functions errors are serialized as metadata | `POWERTOOLS_TRACER_CAPTURE_ERROR` | `true` | `true` or `false`| `false` | N/A | !!! note Before your use this utility, your AWS Lambda function must have [Active Tracing enabled](https://docs.aws.amazon.com/lambda/latest/dg/services-xray.html) as well as [have permissions](https://docs.aws.amazon.com/lambda/latest/dg/services-xray.html#services-xray-permissions) to send traces to AWS X-Ray diff --git a/docs/index.md b/docs/index.md index 8a96d71be8..a85ff5f441 100644 --- a/docs/index.md +++ b/docs/index.md @@ -285,8 +285,8 @@ Core utilities such as Tracing, Logging, and Metrics will be available across al | Environment variable | Description | Utility | Default | |----------------------------------------------|----------------------------------------------------------------------------------------|---------------------------|-----------------------| -| **POWERTOOLS_SERVICE_NAME** | Sets service name used for tracing namespace, metrics dimension and structured logging | All | `"service_undefined"` | -| **POWERTOOLS_METRICS_NAMESPACE** | Sets namespace used for metrics | [Metrics](./core/metrics) | `None` | +| **POWERTOOLS_SERVICE_NAME** | Sets service name used for tracing namespace, metrics dimension and structured logging | All | `service_undefined` | +| **POWERTOOLS_METRICS_NAMESPACE** | Sets namespace used for metrics | [Metrics](./core/metrics) | `default_namespace` | | **POWERTOOLS_TRACE_ENABLED** | Explicitly disables tracing | [Tracer](./core/tracer) | `true` | | **POWERTOOLS_TRACER_CAPTURE_RESPONSE** | Captures Lambda or method return as metadata. | [Tracer](./core/tracer) | `true` | | **POWERTOOLS_TRACER_CAPTURE_ERROR** | Captures Lambda or method exception as metadata. | [Tracer](./core/tracer) | `true` | @@ -296,6 +296,8 @@ Core utilities such as Tracing, Logging, and Metrics will be available across al | **POWERTOOLS_DEV** | Increase JSON indentation to ease debugging when running functions locally or in a non-production environment | [Logger](./core/logger) | `false` | | **LOG_LEVEL** | Sets logging level | [Logger](./core/logger) | `INFO` | +Each Utility page provides information on example values and allowed values + ## Tenets These are our core principles to guide our decision making. diff --git a/packages/commons/src/Utility.ts b/packages/commons/src/Utility.ts index 9991f18cef..c1c2a0fc05 100644 --- a/packages/commons/src/Utility.ts +++ b/packages/commons/src/Utility.ts @@ -56,8 +56,8 @@ * ``` */ export class Utility { - private coldStart: boolean = true; + private readonly defaultServiceName: string = 'service_undefined'; public getColdStart(): boolean { if (this.coldStart) { @@ -73,4 +73,17 @@ export class Utility { return this.getColdStart(); } + protected getDefaultServiceName(): string { + return this.defaultServiceName; + } + + /** + * Validate that the service name provided is valid. + * Used internally during initialization. + * + * @param serviceName - Service name to validate + */ + protected isValidServiceName(serviceName?: string): boolean { + return typeof serviceName === 'string' && serviceName.trim().length > 0; + } } \ No newline at end of file diff --git a/packages/commons/tests/unit/Utility.test.ts b/packages/commons/tests/unit/Utility.test.ts index 42db79d7af..b8a89f4af7 100644 --- a/packages/commons/tests/unit/Utility.test.ts +++ b/packages/commons/tests/unit/Utility.test.ts @@ -12,6 +12,24 @@ describe('Class: Utility', () => { jest.resetModules(); }); + describe('Method: getDefaultServiceName', ()=> { + test('it should return the default service name', ()=> { + class PowerTool extends Utility { + public constructor() { + super(); + } + + public dummyMethod(): string { + return this.getDefaultServiceName(); + } + } + + const powertool = new PowerTool(); + const result = powertool.dummyMethod(); + expect(result).toBe('service_undefined'); + }); + }); + describe('Method: getColdStart', () => { test('when called multiple times on the parent class, it returns true the first time, then false afterwards', () => { @@ -142,4 +160,31 @@ describe('Class: Utility', () => { }); + describe('Method: isValidServiceName', () => { + class PowerTool extends Utility { + public constructor() { + super(); + } + + public dummyMethod(name:string): boolean { + return this.isValidServiceName(name); + } + } + test('it should allow valid strings', ()=> { + const powertool = new PowerTool(); + const goodName = 'serverlessAirline'; + + const result = powertool.dummyMethod(goodName); + + expect(result).toBe(true); + }); + + test('it should not allow empty strings', ()=> { + const tooShort = ''; + const powertool = new PowerTool(); + const result = powertool.dummyMethod(tooShort); + + expect(result).toBe(false); + }); + }); }); \ No newline at end of file diff --git a/packages/logger/src/Logger.ts b/packages/logger/src/Logger.ts index e2198153ae..1e6cac40b9 100644 --- a/packages/logger/src/Logger.ts +++ b/packages/logger/src/Logger.ts @@ -119,8 +119,6 @@ class Logger extends Utility implements ClassThatLogs { private static readonly defaultLogLevel: LogLevel = 'INFO'; - private static readonly defaultServiceName: string = 'service_undefined'; - // envVarsService is always initialized in the constructor in setOptions() private envVarsService!: EnvironmentVariablesService; @@ -803,7 +801,7 @@ class Logger extends Utility implements ClassThatLogs { this.getEnvVarsService().getCurrentEnvironment(), sampleRateValue: this.getSampleRateValue(), serviceName: - serviceName || this.getCustomConfigService()?.getServiceName() || this.getEnvVarsService().getServiceName() || Logger.defaultServiceName, + serviceName || this.getCustomConfigService()?.getServiceName() || this.getEnvVarsService().getServiceName() || this.getDefaultServiceName(), }, persistentLogAttributes, ); diff --git a/packages/logger/tests/unit/Logger.test.ts b/packages/logger/tests/unit/Logger.test.ts index 42c1d99e72..aead56b637 100644 --- a/packages/logger/tests/unit/Logger.test.ts +++ b/packages/logger/tests/unit/Logger.test.ts @@ -566,6 +566,7 @@ describe('Class: Logger', () => { console: expect.any(Console), coldStart: false, // This is now false because the `coldStart` attribute has been already accessed once by the `addContext` method customConfigService: undefined, + defaultServiceName: 'service_undefined', envVarsService: expect.any(EnvironmentVariablesService), logEvent: false, logIndentation: 0, @@ -1265,6 +1266,7 @@ describe('Class: Logger', () => { console: expect.any(Console), coldStart: true, customConfigService: undefined, + defaultServiceName: 'service_undefined', envVarsService: expect.any(EnvironmentVariablesService), logEvent: false, logIndentation: 0, @@ -1290,6 +1292,7 @@ describe('Class: Logger', () => { console: expect.any(Console), coldStart: true, customConfigService: undefined, + defaultServiceName: 'service_undefined', envVarsService: expect.any(EnvironmentVariablesService), logEvent: false, logIndentation: 0, @@ -1317,6 +1320,7 @@ describe('Class: Logger', () => { console: expect.any(Console), coldStart: true, customConfigService: undefined, + defaultServiceName: 'service_undefined', envVarsService: expect.any(EnvironmentVariablesService), logEvent: false, logIndentation: 0, @@ -1342,6 +1346,7 @@ describe('Class: Logger', () => { console: expect.any(Console), coldStart: true, customConfigService: undefined, + defaultServiceName: 'service_undefined', envVarsService: expect.any(EnvironmentVariablesService), logEvent: false, logIndentation: 0, diff --git a/packages/logger/tests/unit/helpers.test.ts b/packages/logger/tests/unit/helpers.test.ts index ab49edd114..a185e980d8 100644 --- a/packages/logger/tests/unit/helpers.test.ts +++ b/packages/logger/tests/unit/helpers.test.ts @@ -46,13 +46,14 @@ describe('Helper: createLogger function', () => { }, envVarsService: expect.any(EnvironmentVariablesService), customConfigService: undefined, + defaultServiceName: 'service_undefined', logLevel: 'DEBUG', logFormatter: expect.any(PowertoolLogFormatter), })); }); - test('when no parameters are set, returns a Logger instance with the correct proprieties', () => { + test('when no parameters are set, returns a Logger instance with the correct properties', () => { // Prepare const loggerOptions = { @@ -74,6 +75,7 @@ describe('Helper: createLogger function', () => { expect(logger).toBeInstanceOf(Logger); expect(logger).toEqual({ coldStart: true, + defaultServiceName: 'service_undefined', customConfigService: expect.any(EnvironmentVariablesService), envVarsService: expect.any(EnvironmentVariablesService), logEvent: false, @@ -101,7 +103,7 @@ describe('Helper: createLogger function', () => { }); - test('when no constructor parameters and no environment variables are set, returns a Logger instance with the default proprieties', () => { + test('when no constructor parameters and no environment variables are set, returns a Logger instance with the default properties', () => { // Prepare const loggerOptions = undefined; @@ -116,6 +118,7 @@ describe('Helper: createLogger function', () => { expect(logger).toEqual({ coldStart: true, customConfigService: undefined, + defaultServiceName: 'service_undefined', envVarsService: expect.any(EnvironmentVariablesService), logEvent: false, logIndentation: 0, @@ -140,7 +143,7 @@ describe('Helper: createLogger function', () => { }); - test('when a custom logFormatter is passed, returns a Logger instance with the correct proprieties', () => { + test('when a custom logFormatter is passed, returns a Logger instance with the correct properties', () => { // Prepare const loggerOptions:ConstructorOptions = { @@ -168,7 +171,7 @@ describe('Helper: createLogger function', () => { })); }); - test('when a custom serviceName is passed, returns a Logger instance with the correct proprieties', () => { + test('when a custom serviceName is passed, returns a Logger instance with the correct properties', () => { // Prepare const loggerOptions:ConstructorOptions = { @@ -196,7 +199,7 @@ describe('Helper: createLogger function', () => { })); }); - test('when a custom logLevel is passed, returns a Logger instance with the correct proprieties', () => { + test('when a custom logLevel is passed, returns a Logger instance with the correct properties', () => { // Prepare const loggerOptions:ConstructorOptions = { @@ -238,6 +241,7 @@ describe('Helper: createLogger function', () => { expect(logger).toEqual({ coldStart: true, customConfigService: undefined, + defaultServiceName: 'service_undefined', envVarsService: expect.any(EnvironmentVariablesService), logEvent: false, logIndentation: 0, @@ -261,7 +265,7 @@ describe('Helper: createLogger function', () => { }); }); - test('when a custom sampleRateValue is passed, returns a Logger instance with the correct proprieties', () => { + test('when a custom sampleRateValue is passed, returns a Logger instance with the correct properties', () => { // Prepare const loggerOptions:ConstructorOptions = { @@ -289,7 +293,7 @@ describe('Helper: createLogger function', () => { })); }); - test('when a custom customConfigService is passed, returns a Logger instance with the correct proprieties', () => { + test('when a custom customConfigService is passed, returns a Logger instance with the correct properties', () => { const configService: ConfigServiceInterface = { get(name: string): string { @@ -344,7 +348,7 @@ describe('Helper: createLogger function', () => { })); }); - test('when custom persistentLogAttributes is passed, returns a Logger instance with the correct proprieties', () => { + test('when custom persistentLogAttributes is passed, returns a Logger instance with the correct properties', () => { // Prepare const loggerOptions:ConstructorOptions = { @@ -386,7 +390,7 @@ describe('Helper: createLogger function', () => { })); }); - test('when A custom environment is passed, returns a Logger instance with the correct proprieties', () => { + test('when a custom environment is passed, returns a Logger instance with the correct properties', () => { // Prepare const loggerOptions:ConstructorOptions = { diff --git a/packages/metrics/src/Metrics.ts b/packages/metrics/src/Metrics.ts index 10e500b5f7..dbde8e0438 100644 --- a/packages/metrics/src/Metrics.ts +++ b/packages/metrics/src/Metrics.ts @@ -473,7 +473,7 @@ class Metrics extends Utility implements MetricsInterface { private setService(service: string | undefined): void { const targetService = (service || this.getCustomConfigService()?.getServiceName() || - this.getEnvVarsService().getServiceName()) as string; + this.getEnvVarsService().getServiceName()) as string || this.getDefaultServiceName(); if (targetService.length > 0) { this.setDefaultDimensions({ service: targetService }); } diff --git a/packages/metrics/tests/unit/Metrics.test.ts b/packages/metrics/tests/unit/Metrics.test.ts index e6a0e6045a..ef34f6b6b9 100644 --- a/packages/metrics/tests/unit/Metrics.test.ts +++ b/packages/metrics/tests/unit/Metrics.test.ts @@ -69,7 +69,8 @@ describe('Class: Metrics', () => { metrics.addDimension(additionalDimension.name, additionalDimension.value); const loggedData = metrics.serializeMetrics(); - expect(loggedData._aws.CloudWatchMetrics[0].Dimensions[0].length).toEqual(1); + // Expect the additional dimension, and the service dimension + expect(loggedData._aws.CloudWatchMetrics[0].Dimensions[0].length).toEqual(2); expect(loggedData[additionalDimension.name]).toEqual(additionalDimension.value); }); @@ -97,15 +98,18 @@ describe('Class: Metrics', () => { expect(console.log).toBeCalledTimes(2); expect(loggedData[0][dimensionItem.name]).toEqual(dimensionItem.value); - expect(loggedData[0]._aws.CloudWatchMetrics[0].Dimensions[0].length).toEqual(1); + // Expect the additional dimension, and the service dimension + expect(loggedData[0]._aws.CloudWatchMetrics[0].Dimensions[0].length).toEqual(2); expect(loggedData[1][dimensionItem.name]).toBeUndefined(); - expect(loggedData[1]._aws.CloudWatchMetrics[0].Dimensions[0].length).toEqual(0); + // Expect just the service dimension + expect(loggedData[1]._aws.CloudWatchMetrics[0].Dimensions[0].length).toEqual(1); }); test('Adding more than max dimensions should throw error', () => { expect.assertions(1); const metrics = new Metrics(); - for (let x = 0; x < MAX_DIMENSION_COUNT; x++) { + // The service dimension is already set, so start from 1 + for (let x = 1; x < MAX_DIMENSION_COUNT; x++) { metrics.addDimension(`Dimension-${x}`, `value-${x}`); } try { @@ -123,7 +127,8 @@ describe('Class: Metrics', () => { metrics.addDimensions(additionalDimensions); const loggedData = metrics.serializeMetrics(); - expect(loggedData._aws.CloudWatchMetrics[0].Dimensions[0].length).toEqual(2); + // Expect the additional dimensions, and the service dimension + expect(loggedData._aws.CloudWatchMetrics[0].Dimensions[0].length).toEqual(3); Object.keys(additionalDimensions).forEach((key) => { expect(loggedData[key]).toEqual(additionalDimensions[key]); }); @@ -242,7 +247,8 @@ describe('Class: Metrics', () => { metrics.addMetric('test_name', MetricUnits.Seconds, 10); metrics.addDimension(additionalDimension.name, additionalDimension.value); const loggedData = metrics.serializeMetrics(); - expect(loggedData._aws.CloudWatchMetrics[0].Dimensions[0].length).toEqual(2); + // Expect the additional dimensions, and the service dimension + expect(loggedData._aws.CloudWatchMetrics[0].Dimensions[0].length).toEqual(3); expect(loggedData[additionalDimension.name]).toEqual(additionalDimension.value); metrics.clearDimensions(); } @@ -252,7 +258,8 @@ describe('Class: Metrics', () => { const loggedData = JSON.parse(consoleSpy.mock.calls[0][0]); expect(console.log).toBeCalledTimes(1); - expect(loggedData._aws.CloudWatchMetrics[0].Dimensions[0].length).toEqual(1); + // Expect the additional dimension, and the service dimension + expect(loggedData._aws.CloudWatchMetrics[0].Dimensions[0].length).toEqual(2); expect(loggedData._aws.CloudWatchMetrics[0].Dimensions[0]).toContain('default'); expect(loggedData.default).toContain('defaultValue'); }); @@ -654,7 +661,8 @@ describe('Class: Metrics', () => { metrics.addMetric('test_name', MetricUnits.Seconds, 10); metrics.addDimension(additionalDimension.name, additionalDimension.value); const loggedData = metrics.serializeMetrics(); - expect(loggedData._aws.CloudWatchMetrics[0].Dimensions[0].length).toEqual(2); + // Expect the additional dimensions, and the service dimension + expect(loggedData._aws.CloudWatchMetrics[0].Dimensions[0].length).toEqual(3); expect(loggedData[additionalDimension.name]).toEqual(additionalDimension.value); metrics.clearDimensions(); @@ -666,7 +674,8 @@ describe('Class: Metrics', () => { const loggedData = JSON.parse(consoleSpy.mock.calls[0][0]); expect(console.log).toBeCalledTimes(1); - expect(loggedData._aws.CloudWatchMetrics[0].Dimensions[0].length).toEqual(1); + // Expect the additional dimension, and the service dimension + expect(loggedData._aws.CloudWatchMetrics[0].Dimensions[0].length).toEqual(2); expect(loggedData._aws.CloudWatchMetrics[0].Dimensions[0]).toContain('default'); expect(loggedData.default).toContain('defaultValue'); }); diff --git a/packages/tracer/src/Tracer.ts b/packages/tracer/src/Tracer.ts index 072e237e4c..6d846b371f 100644 --- a/packages/tracer/src/Tracer.ts +++ b/packages/tracer/src/Tracer.ts @@ -128,7 +128,8 @@ class Tracer extends Utility implements TracerInterface { // envVarsService is always initialized in the constructor in setOptions() private envVarsService!: EnvironmentVariablesService; - private serviceName?: string; + // serviceName is always initialized in the constructor in setOptions() + private serviceName!: string; private tracingEnabled: boolean = true; @@ -189,7 +190,7 @@ class Tracer extends Utility implements TracerInterface { * */ public addServiceNameAnnotation(): void { - if (!this.isTracingEnabled() || this.serviceName === undefined) { + if (!this.isTracingEnabled()) { return; } this.putAnnotation('Service', this.serviceName); @@ -684,16 +685,6 @@ class Tracer extends Utility implements TracerInterface { return this.getEnvVarsService().getSamLocal() !== ''; } - /** - * Validate that the service name provided is valid. - * Used internally during initialization. - * - * @param serviceName - Service name to validate - */ - private static isValidServiceName(serviceName?: string): boolean { - return typeof serviceName === 'string' && serviceName.trim().length > 0; - } - /** * Setter for `captureError` based on configuration passed and environment variables. * Used internally during initialization. @@ -817,25 +808,26 @@ class Tracer extends Utility implements TracerInterface { * @param serviceName - Name of the service to use */ private setServiceName(serviceName?: string): void { - if (serviceName !== undefined && Tracer.isValidServiceName(serviceName)) { + if (serviceName !== undefined && this.isValidServiceName(serviceName)) { this.serviceName = serviceName; return; } const customConfigValue = this.getCustomConfigService()?.getServiceName(); - if (customConfigValue !== undefined && Tracer.isValidServiceName(customConfigValue)) { + if (customConfigValue !== undefined && this.isValidServiceName(customConfigValue)) { this.serviceName = customConfigValue; return; } const envVarsValue = this.getEnvVarsService().getServiceName(); - if (envVarsValue !== undefined && Tracer.isValidServiceName(envVarsValue)) { + if (envVarsValue !== undefined && this.isValidServiceName(envVarsValue)) { this.serviceName = envVarsValue; return; } + this.serviceName = this.getDefaultServiceName(); } /** diff --git a/packages/tracer/tests/unit/Tracer.test.ts b/packages/tracer/tests/unit/Tracer.test.ts index 6a71c93bd0..d46db1164b 100644 --- a/packages/tracer/tests/unit/Tracer.test.ts +++ b/packages/tracer/tests/unit/Tracer.test.ts @@ -114,7 +114,7 @@ describe('Class: Tracer', () => { }); - test('when called while a serviceName has not been set, it does nothing', () => { + test('when called when a serviceName has not been set in the constructor or environment variables, it adds the default service name as an annotation', () => { // Prepare delete process.env.POWERTOOLS_SERVICE_NAME; @@ -125,7 +125,8 @@ describe('Class: Tracer', () => { tracer.addServiceNameAnnotation(); // Assess - expect(putAnnotation).toBeCalledTimes(0); + expect(putAnnotation).toBeCalledTimes(1); + expect(putAnnotation).toBeCalledWith('Service', 'service_undefined'); }); From 6f0c30728f31d60433b3afb6983c64110c28d27e Mon Sep 17 00:00:00 2001 From: Sergei Cherniaev Date: Tue, 15 Nov 2022 19:19:59 +0400 Subject: [PATCH 08/15] feat(logger): disable logs while testing with `jest --silent` in dev env (#1165) * feat(logger): disable logs while testing with jest --silent in dev env * refactor(logger): rename method * docs(logger): add testing section with jest --silent option, which is also suppresses logs (#1165) * Update docs/core/logger.md Co-authored-by: Andrea Amorosi * test(logger): add test for setConsole() method * test(logger): add comment for assertion * docs(logger): add an example of using POWERTOOLS_DEV with jest --silent * Update packages/logger/src/Logger.ts Co-authored-by: Andrea Amorosi Co-authored-by: Andrea Amorosi --- docs/core/logger.md | 8 ++++ packages/logger/src/Logger.ts | 24 ++++++++++-- .../src/config/ConfigServiceInterface.ts | 14 +++---- .../src/config/EnvironmentVariablesService.ts | 22 +++++------ packages/logger/tests/unit/Logger.test.ts | 39 ++++++++++++++++--- .../EnvironmentVariablesService.test.ts | 10 ++--- packages/logger/tests/unit/helpers.test.ts | 2 +- .../tests/unit/middleware/middy.test.ts | 2 +- 8 files changed, 87 insertions(+), 34 deletions(-) diff --git a/docs/core/logger.md b/docs/core/logger.md index 02d95a794f..4ec1d28dda 100644 --- a/docs/core/logger.md +++ b/docs/core/logger.md @@ -971,3 +971,11 @@ This is a Jest sample that provides the minimum information necessary for Logger !!! tip If you don't want to declare your own dummy Lambda Context, you can use [`ContextExamples.helloworldContext`](https://github.com/awslabs/aws-lambda-powertools-typescript/blob/main/packages/commons/src/samples/resources/contexts/hello-world.ts#L3-L16) from [`@aws-lambda-powertools/commons`](https://www.npmjs.com/package/@aws-lambda-powertools/commons). + +### Suppress logs with Jest + +When unit testing your code with [Jest](https://jestjs.io) you can use the `POWERTOOLS_DEV` environment variable in conjunction with the Jest `--silent` CLI option to suppress logs from Logger. + +```bash title="Disabling logs while testing with Jest" +export POWERTOOLS_DEV=true && npx jest --silent +``` diff --git a/packages/logger/src/Logger.ts b/packages/logger/src/Logger.ts index 1e6cac40b9..57b9ba1a57 100644 --- a/packages/logger/src/Logger.ts +++ b/packages/logger/src/Logger.ts @@ -113,7 +113,8 @@ import type { */ class Logger extends Utility implements ClassThatLogs { - private console = new Console({ stdout: process.stdout, stderr: process.stderr }); + // console is initialized in the constructor in setOptions() + private console!: Console; private customConfigService?: ConfigServiceInterface; @@ -572,7 +573,7 @@ class Logger extends Utility implements ClassThatLogs { return this.powertoolLogData.sampleRateValue; } - + /** * It returns true if the provided log level is valid. * @@ -640,6 +641,21 @@ class Logger extends Utility implements ClassThatLogs { }; } + /** + * It initializes console property as an instance of the internal version of Console() class (PR #748) + * or as the global node console if the `POWERTOOLS_DEV' env variable is set and has truthy value. + * + * @private + * @returns {void} + */ + private setConsole(): void { + if (!this.getEnvVarsService().isDevMode()) { + this.console = new Console({ stdout: process.stdout, stderr: process.stderr }); + } else { + this.console = console; + } + } + /** * Sets the Logger's customer config service instance, which will be used * to fetch environment variables. @@ -697,7 +713,7 @@ class Logger extends Utility implements ClassThatLogs { * @returns {void} */ private setLogIndentation(): void { - if (this.getEnvVarsService().getDevMode()) { + if (this.getEnvVarsService().isDevMode()) { this.logIndentation = LogJsonIndent.PRETTY; } } @@ -764,6 +780,8 @@ class Logger extends Utility implements ClassThatLogs { } = options; this.setEnvVarsService(); + // order is important, it uses EnvVarsService() + this.setConsole(); this.setCustomConfigService(customConfigService); this.setLogLevel(logLevel); this.setSampleRateValue(sampleRateValue); diff --git a/packages/logger/src/config/ConfigServiceInterface.ts b/packages/logger/src/config/ConfigServiceInterface.ts index 43f25d3649..d66d4e9f85 100644 --- a/packages/logger/src/config/ConfigServiceInterface.ts +++ b/packages/logger/src/config/ConfigServiceInterface.ts @@ -22,13 +22,6 @@ interface ConfigServiceInterface { */ getCurrentEnvironment(): string - /** - * It returns the value of the POWERTOOLS_DEV environment variable. - * - * @returns {boolean} - */ - getDevMode(): boolean - /** * It returns the value of the POWERTOOLS_LOGGER_LOG_EVENT environment variable. * @@ -57,6 +50,13 @@ interface ConfigServiceInterface { */ getServiceName(): string + /** + * It returns the value of the POWERTOOLS_DEV environment variable. + * + * @returns {boolean} + */ + isDevMode(): boolean + /** * It returns true if the string value represents a boolean true value. * diff --git a/packages/logger/src/config/EnvironmentVariablesService.ts b/packages/logger/src/config/EnvironmentVariablesService.ts index fab7ec9fc8..52fe910784 100644 --- a/packages/logger/src/config/EnvironmentVariablesService.ts +++ b/packages/logger/src/config/EnvironmentVariablesService.ts @@ -46,17 +46,6 @@ class EnvironmentVariablesService extends CommonEnvironmentVariablesService impl return this.get(this.currentEnvironmentVariable); } - /** - * It returns the value of the POWERTOOLS_DEV environment variable. - * - * @returns {boolean} - */ - public getDevMode(): boolean { - const value = this.get(this.devModeVariable); - - return this.isValueTrue(value); - } - /** * It returns the value of the AWS_LAMBDA_FUNCTION_MEMORY_SIZE environment variable. * @@ -117,6 +106,17 @@ class EnvironmentVariablesService extends CommonEnvironmentVariablesService impl return (value && value.length > 0) ? Number(value) : undefined; } + /** + * It returns true if the POWERTOOLS_DEV environment variable is set to truthy value. + * + * @returns {boolean} + */ + public isDevMode(): boolean { + const value = this.get(this.devModeVariable); + + return this.isValueTrue(value); + } + /** * It returns true if the string value represents a boolean true value. * diff --git a/packages/logger/tests/unit/Logger.test.ts b/packages/logger/tests/unit/Logger.test.ts index aead56b637..e5fe9b39b7 100644 --- a/packages/logger/tests/unit/Logger.test.ts +++ b/packages/logger/tests/unit/Logger.test.ts @@ -1239,6 +1239,7 @@ describe('Class: Logger', () => { test('when called, it returns a DISTINCT clone of the logger instance', () => { // Prepare + const INDENTATION = LogJsonIndent.COMPACT; const parentLogger = new Logger(); // Act @@ -1257,7 +1258,10 @@ describe('Class: Logger', () => { // Assess expect(parentLogger === childLogger).toBe(false); - expect(parentLogger).toEqual(childLogger); + expect(childLogger).toEqual({ + ...parentLogger, + console: expect.any(Console), + }); expect(parentLogger === childLoggerWithPermanentAttributes).toBe(false); expect(parentLogger === childLoggerWithSampleRateEnabled).toBe(false); expect(parentLogger === childLoggerWithErrorLogLevel).toBe(false); @@ -1269,7 +1273,7 @@ describe('Class: Logger', () => { defaultServiceName: 'service_undefined', envVarsService: expect.any(EnvironmentVariablesService), logEvent: false, - logIndentation: 0, + logIndentation: INDENTATION, logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'DEBUG', logLevelThresholds: { @@ -1295,7 +1299,7 @@ describe('Class: Logger', () => { defaultServiceName: 'service_undefined', envVarsService: expect.any(EnvironmentVariablesService), logEvent: false, - logIndentation: 0, + logIndentation: INDENTATION, logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'DEBUG', logLevelThresholds: { @@ -1323,7 +1327,7 @@ describe('Class: Logger', () => { defaultServiceName: 'service_undefined', envVarsService: expect.any(EnvironmentVariablesService), logEvent: false, - logIndentation: 0, + logIndentation: INDENTATION, logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'DEBUG', logLevelThresholds: { @@ -1349,7 +1353,7 @@ describe('Class: Logger', () => { defaultServiceName: 'service_undefined', envVarsService: expect.any(EnvironmentVariablesService), logEvent: false, - logIndentation: 0, + logIndentation: INDENTATION, logFormatter: expect.any(PowertoolLogFormatter), logLevel: 'ERROR', logLevelThresholds: { @@ -1461,4 +1465,27 @@ describe('Class: Logger', () => { }); }); -}); \ No newline at end of file + describe('Method: setConsole()', () => { + + test('When the `POWERTOOLS_DEV` env var is SET console object is set to the global node console otherwise to the instance of the internal version of console', () => { + + // Prepare + const logger = new Logger(); + process.env.POWERTOOLS_DEV = 'true'; + const devLogger = new Logger(); + + // Assess + expect(devLogger).toEqual({ + ...devLogger, + console: console, + }); + // since instances of a class are not equal objects, + // we assert the opposite – console is not the global node object + expect(logger).not.toEqual({ + ...logger, + console: console, + }); + }); + }); + +}); diff --git a/packages/logger/tests/unit/config/EnvironmentVariablesService.test.ts b/packages/logger/tests/unit/config/EnvironmentVariablesService.test.ts index ccaf0084bf..b9aa387614 100644 --- a/packages/logger/tests/unit/config/EnvironmentVariablesService.test.ts +++ b/packages/logger/tests/unit/config/EnvironmentVariablesService.test.ts @@ -181,7 +181,7 @@ describe('Class: EnvironmentVariablesService', () => { }); - describe('Method: getDevMode', () => { + describe('Method: isDevMode', () => { test('It returns true if the environment variable POWERTOOLS_DEV is "true"', () => { @@ -190,7 +190,7 @@ describe('Class: EnvironmentVariablesService', () => { const service = new EnvironmentVariablesService(); // Act - const value = service.getDevMode(); + const value = service.isDevMode(); // Assess expect(value).toEqual(true); @@ -203,7 +203,7 @@ describe('Class: EnvironmentVariablesService', () => { const service = new EnvironmentVariablesService(); // Act - const value = service.getDevMode(); + const value = service.isDevMode(); // Assess expect(value).toEqual(false); @@ -216,7 +216,7 @@ describe('Class: EnvironmentVariablesService', () => { const service = new EnvironmentVariablesService(); // Act - const value = service.getDevMode(); + const value = service.isDevMode(); // Assess expect(value).toEqual(false); @@ -229,7 +229,7 @@ describe('Class: EnvironmentVariablesService', () => { const service = new EnvironmentVariablesService(); // Act - const value = service.getDevMode(); + const value = service.isDevMode(); // Assess expect(value).toEqual(false); diff --git a/packages/logger/tests/unit/helpers.test.ts b/packages/logger/tests/unit/helpers.test.ts index a185e980d8..3a4298421b 100644 --- a/packages/logger/tests/unit/helpers.test.ts +++ b/packages/logger/tests/unit/helpers.test.ts @@ -314,7 +314,7 @@ describe('Helper: createLogger function', () => { getServiceName(): string { return 'my-backend-service'; }, - getDevMode(): boolean { + isDevMode(): boolean { return false; }, isValueTrue(): boolean { diff --git a/packages/logger/tests/unit/middleware/middy.test.ts b/packages/logger/tests/unit/middleware/middy.test.ts index 88e37b9fce..8ec29fb957 100644 --- a/packages/logger/tests/unit/middleware/middy.test.ts +++ b/packages/logger/tests/unit/middleware/middy.test.ts @@ -336,7 +336,7 @@ describe('Middy middleware', () => { getServiceName(): string { return 'my-backend-service'; }, - getDevMode(): boolean { + isDevMode(): boolean { return false; }, isValueTrue(): boolean { From 36caa4ea4dd3de4f401553c14cd4c7234389bc49 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Sat, 19 Nov 2022 13:12:11 +0100 Subject: [PATCH 09/15] docs: add maintainers and public roadmap (#1167) * docs: added maintainers handbook page * docs: added maintainers handbook page * docs: update mkdocs & nav * docs: updated toc * chore: update gh-issue templates * docs: update notes about languages * docs: update label * chore: update stalebot + removed autoassign bot * chore: update labels in scripts * docs: updates to maintainers & labels * fix: label name * fix: label name * Update .github/stale.yml --- .github/ISSUE_TEMPLATE/bug_report.yml | 6 +- .../documentation_improvements.yml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- .github/ISSUE_TEMPLATE/maintenance.yml | 3 +- .github/ISSUE_TEMPLATE/rfc.yml | 2 +- .github/PULL_REQUEST_TEMPLATE.md | 4 +- .github/auto_assign-issues.yml | 6 - .github/release-drafter.yml | 19 +- .github/scripts/constants.js | 5 +- .github/scripts/label_pr_based_on_title.js | 33 +- .github/scripts/label_related_issue.js | 2 +- .github/scripts/post_release.js | 113 +++++++ .github/stale.yml | 9 +- CONTRIBUTING.md | 38 ++- MAINTAINERS.md | 299 ++++++++++++++++++ README.md | 12 +- docs/requirements.txt | 5 +- docs/roadmap.md | 102 ++++++ mkdocs.yml | 13 +- packages/commons/README.md | 55 +++- packages/logger/README.md | 13 +- packages/metrics/README.md | 10 +- packages/tracer/README.md | 10 +- 23 files changed, 652 insertions(+), 111 deletions(-) delete mode 100644 .github/auto_assign-issues.yml create mode 100644 .github/scripts/post_release.js create mode 100644 MAINTAINERS.md create mode 100644 docs/roadmap.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 173e45e7df..452ca49707 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,7 +1,7 @@ name: Bug report description: Report a reproducible bug to help us improve title: "Bug: TITLE" -labels: ["bug", "triage"] +labels: ["type/bug", "triage"] body: - type: markdown attributes: @@ -76,8 +76,8 @@ body: - type: textarea id: logs attributes: - label: Debugging logs - description: If available, please share [debugging logs](https://awslabs.github.io/aws-lambda-powertools-python/#debug-mode) + label: Execution logs + description: If available, please share some logs making sure to remove any sensitive data render: Shell validations: required: false diff --git a/.github/ISSUE_TEMPLATE/documentation_improvements.yml b/.github/ISSUE_TEMPLATE/documentation_improvements.yml index d52df5b994..93b47d75fc 100644 --- a/.github/ISSUE_TEMPLATE/documentation_improvements.yml +++ b/.github/ISSUE_TEMPLATE/documentation_improvements.yml @@ -1,7 +1,7 @@ name: Documentation improvements description: Suggest a documentation update to improve everyone's experience title: "Docs: TITLE" -labels: ["documentation", "triage"] +labels: ["area/documentation", "triage"] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 5abeb0a7f4..964a4cc897 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,7 +1,7 @@ name: Feature request description: Suggest an idea for Lambda Powertools title: "Feature request: TITLE" -labels: ["feature-request", "triage"] +labels: ["type/feature-request", "triage"] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/maintenance.yml b/.github/ISSUE_TEMPLATE/maintenance.yml index b07bfc8fb7..cfd98ca876 100644 --- a/.github/ISSUE_TEMPLATE/maintenance.yml +++ b/.github/ISSUE_TEMPLATE/maintenance.yml @@ -1,7 +1,7 @@ name: Maintenance description: Suggest an activity to help address tech debt, governance, and anything internal title: "Maintenance: TITLE" -labels: ["internal", "triage"] +labels: ["type/internal", "triage"] body: - type: markdown attributes: @@ -32,7 +32,6 @@ body: - Automation - Governance - Tests - - Types - Tracer - Logger - Metrics diff --git a/.github/ISSUE_TEMPLATE/rfc.yml b/.github/ISSUE_TEMPLATE/rfc.yml index b5b1b5c20f..8f6c075476 100644 --- a/.github/ISSUE_TEMPLATE/rfc.yml +++ b/.github/ISSUE_TEMPLATE/rfc.yml @@ -1,7 +1,7 @@ name: Request for Comments (RFC) description: Feature design and detailed proposals title: "RFC: TITLE" -labels: ["RFC", "triage"] +labels: ["type/RFC", "triage"] body: - type: markdown attributes: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e431ff05dc..02304c842c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -32,7 +32,7 @@ ## Checklist -- [ ] [My changes meet the tenets criteria](https://awslabs.github.io/aws-lambda-powertools-python/#tenets) +- [ ] [My changes meet the tenets criteria](https://awslabs.github.io/aws-lambda-powertools-typescript/#tenets) - [ ] I have performed a *self-review* of my own code - [ ] I have *commented* my code where necessary, particularly in areas that should be flagged with a TODO, or hard-to-understand areas - [ ] I have made corresponding changes to the *documentation* @@ -41,7 +41,7 @@ - [ ] The *code coverage* hasn't decreased - [ ] I have *added tests* that prove my change is effective and works - [ ] New and existing *unit tests pass* locally and in Github Actions -- [ ] Any *dependent changes have been merged and published* in downstream module +- [ ] Any *dependent changes have been merged and published* - [ ] The PR title follows the [conventional commit semantics](https://github.com/awslabs/aws-lambda-powertools-typescript/blob/main/.github/semantic.yml#L2) ### Breaking change checklist diff --git a/.github/auto_assign-issues.yml b/.github/auto_assign-issues.yml deleted file mode 100644 index 7104232a46..0000000000 --- a/.github/auto_assign-issues.yml +++ /dev/null @@ -1,6 +0,0 @@ -addAssignees: false - -# The list of users to assign to new issues. -# If empty or not provided, the repository owner is assigned -# If addAssignees above is false the below is ignored -assignees: ~ \ No newline at end of file diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 44ad5a6177..a0ae73b1d8 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -2,28 +2,27 @@ change-template: "* $TITLE (#$NUMBER) by @$AUTHOR" categories: - title: '⚡ Breaking Changes' labels: - - 'breaking-change' + - 'type/breaking-change' - title: '🌟New features and non-breaking changes' labels: - - 'major' - - 'feature' + - 'type/feature' - title: '🌟 Minor Changes' labels: - - 'enhancement' + - 'type/enhancement' - title: '📜 Documentation updates' labels: - - 'documentation' + - 'area/documentation' - title: '🐛 Bug and hot fixes' labels: - - 'bug' - - 'fix' + - 'type/bug' + - 'type/fix' - title: '🚒 Deprecations' labels: - - 'deprecated' + - 'type/deprecation' - title: '🔧 Maintenance' labels: - - 'internal' - - 'dependencies' + - 'type/internal' + - 'type/dependencies' exclude-labels: - 'skip-changelog' tag-template: 'v$NEXT_PATCH_VERSION' diff --git a/.github/scripts/constants.js b/.github/scripts/constants.js index e7dd21d8ca..2e3cb78f91 100644 --- a/.github/scripts/constants.js +++ b/.github/scripts/constants.js @@ -25,7 +25,10 @@ module.exports = Object.freeze({ "LABEL_BLOCK_REASON": "need-issue", /** @type {string} */ - "LABEL_PENDING_RELEASE": "pending-release", + "LABEL_PENDING_RELEASE": "status/pending-release", + + /** @type {string} */ + "LABEL_RELEASED": "status/completed", /** @type {string} */ "HANDLE_MAINTAINERS_TEAM": "@awslabs/aws-lambda-powertools-typescript", diff --git a/.github/scripts/label_pr_based_on_title.js b/.github/scripts/label_pr_based_on_title.js index 3f118af489..12465eab1a 100644 --- a/.github/scripts/label_pr_based_on_title.js +++ b/.github/scripts/label_pr_based_on_title.js @@ -1,21 +1,19 @@ -const { PR_NUMBER, PR_TITLE, AREAS } = require("./constants"); +const { PR_NUMBER, PR_TITLE } = require("./constants"); module.exports = async ({github, context, core}) => { - const FEAT_REGEX = /feat(\((.+)\))?(:.+)/ const BUG_REGEX = /(fix|bug)(\((.+)\))?(:.+)/ + const FEAT_REFACTOR_REGEX = /(feat|refactor)(\((.+)\))?(:.+)/ const DOCS_REGEX = /(docs|doc)(\((.+)\))?(:.+)/ const CHORE_REGEX = /(chore)(\((.+)\))?(:.+)/ const DEPRECATED_REGEX = /(deprecated)(\((.+)\))?(:.+)/ - const REFACTOR_REGEX = /(refactor)(\((.+)\))?(:.+)/ - + const labels = { - "feature": FEAT_REGEX, - "bug": BUG_REGEX, - "documentation": DOCS_REGEX, - "internal": CHORE_REGEX, - "enhancement": REFACTOR_REGEX, - "deprecated": DEPRECATED_REGEX, - }; + "type/feature": FEAT_REFACTOR_REGEX, + "type/bug": BUG_REGEX, + "area/documentation": DOCS_REGEX, + "type/internal": CHORE_REGEX, + "type/deprecation": DEPRECATED_REGEX, + } // Maintenance: We should keep track of modified PRs in case their titles change let miss = 0; @@ -33,19 +31,6 @@ module.exports = async ({github, context, core}) => { labels: [label] }); - const area = matches[2]; // second capture group contains the area - if (AREAS.indexOf(area) > -1) { - core.info(`Auto-labeling PR ${PR_NUMBER} with area ${area}`); - await github.rest.issues.addLabels({ - issue_number: PR_NUMBER, - owner: context.repo.owner, - repo: context.repo.repo, - labels: [`area/${area}`], - }); - } else { - core.debug(`'${PR_TITLE}' didn't match any known area.`); - } - return; } else { core.debug(`'${PR_TITLE}' didn't match '${label}' semantic.`); diff --git a/.github/scripts/label_related_issue.js b/.github/scripts/label_related_issue.js index 94ec9c7998..6cfebbdf9b 100644 --- a/.github/scripts/label_related_issue.js +++ b/.github/scripts/label_related_issue.js @@ -6,7 +6,7 @@ const { LABEL_PENDING_RELEASE, HANDLE_MAINTAINERS_TEAM, PR_IS_MERGED, -} = require("./constants") +} = require("./constants"); module.exports = async ({github, context, core}) => { if (IGNORE_AUTHORS.includes(PR_AUTHOR)) { diff --git a/.github/scripts/post_release.js b/.github/scripts/post_release.js new file mode 100644 index 0000000000..5b6781c475 --- /dev/null +++ b/.github/scripts/post_release.js @@ -0,0 +1,113 @@ +const { LABEL_PENDING_RELEASE, LABEL_RELEASED } = require("./constants"); + +/** + * Fetch issues using GitHub REST API + * + * @param {object} gh_client - Pre-authenticated REST client (Octokit) + * @param {string} org - GitHub Organization + * @param {string} repository - GitHub repository + * @param {string} state - GitHub issue state (open, closed) + * @param {string} label - Comma-separated issue labels to fetch + * @return {Object[]} issues - Array of issues matching params + * @see {@link https://octokit.github.io/rest.js/v18#usage|Octokit client} + */ + +const fetchIssues = async ({ + gh_client, + org, + repository, + state = "all", + label = LABEL_PENDING_RELEASE, +}) => { + try { + const { data: issues } = await gh_client.rest.issues.listForRepo({ + owner: org, + repo: repository, + state: state, + labels: label, + }); + + return issues; + } catch (error) { + console.error(error); + throw new Error("Failed to fetch issues"); + } + +}; + +/** + * Notify new release and close staged GitHub issue + * + * @param {object} gh_client - Pre-authenticated REST client (Octokit) + * @param {string} owner - GitHub Organization + * @param {string} repository - GitHub repository + * @param {string} release_version - GitHub Release version + * @see {@link https://octokit.github.io/rest.js/v18#usage|Octokit client} + */ + +const notifyRelease = async ({ + gh_client, + owner, + repository, + release_version, +}) => { + const release_url = `https://github.com/${owner}/${repository}/releases/tag/v${release_version}`; + + const issues = await fetchIssues({ + gh_client: gh_client, + org: owner, + repository: repository, + }); + + issues.forEach(async (issue) => { + console.info(`Updating issue number ${issue.number}`); + + const comment = `This is now released under [${release_version}](${release_url}) version!`; + try { + await gh_client.rest.issues.createComment({ + owner: owner, + repo: repository, + body: comment, + issue_number: issue.number, + }); + } catch (error) { + console.error(error); + throw new Error(`Failed to update issue ${issue.number} about ${release_version} release`) + } + + + // Close issue and remove staged label; keep existing ones + const labels = issue.labels + .filter((label) => label.name != LABEL_PENDING_RELEASE) + .map((label) => label.name); + + try { + await gh_client.rest.issues.update({ + repo: repository, + owner: owner, + issue_number: issue.number, + state: "closed", + labels: [...labels, LABEL_RELEASED], + }); + } catch (error) { + console.error(error); + throw new Error("Failed to close issue") + } + + console.info(`Issue number ${issue.number} closed and updated`); + }); +}; + +// context: https://github.com/actions/toolkit/blob/main/packages/github/src/context.ts + +module.exports = async ({ github, context }) => { + const { RELEASE_VERSION } = process.env; + console.log(`Running post-release script for ${RELEASE_VERSION} version`); + + await notifyRelease({ + gh_client: github, + owner: context.repo.owner, + repository: context.repo.repo, + release_version: RELEASE_VERSION, + }); +}; \ No newline at end of file diff --git a/.github/stale.yml b/.github/stale.yml index 1bb42057ef..d6bdd4c29c 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -2,11 +2,10 @@ only: issues daysUntilStale: 30 daysUntilClose: 7 exemptLabels: - - bug - - documentation - - enhancement - - feature-request - - RFC + - type/bug + - type/bug-upstream + - type/feature-request + - type/RFC staleLabel: pending-close-response-required markComment: > This issue has been automatically marked as stale because it has not had diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ef11f898cd..01b305d816 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing Guidelines +# Contributing Guidelines Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional documentation, we greatly value feedback and contributions from our community. @@ -6,29 +6,35 @@ documentation, we greatly value feedback and contributions from our community. Please read through this document before submitting any issues or pull requests to ensure we have all the necessary information to effectively respond to your bug report or contribution. -## Table of contents +## Table of contents - [Security issue notifications](#security-issue-notifications) - [Code of Conduct](#code-of-conduct) - [Reporting Bugs/Feature Requests](#reporting-bugsfeature-requests) -- [Contributing via Pull Requests](#contributing-via-pull-requests) - - [Summary](#summary) - - [Step 1: Find something to work on](#step-1-find-something-to-work-on) - - [Step 2: Design](#step-2-design) - - [Step 3: Work your Magic](#step-3-work-your-magic) - - [Step 4: Pull Request](#step-4-pull-request) - - [Step 5: Merge](#step-5-merge) - [Getting Started](#getting-started) - [Setup](#setup) - [Repo Layout](#repo-layout) - [Tests](#tests) - [Unit tests](#unit-tests) + - [Write](#write) + - [Run](#run) - [e2e tests](#e2e-tests) + - [Set up](#set-up) + - [Write](#write-1) + - [Run](#run-1) + - [Automate](#automate) - [Examples](#examples) - [Local documentation](#local-documentation) - [API reference](#api-reference) - [Docs website](#docs-website) - [Conventions](#conventions) +- [Contributing via Pull Requests](#contributing-via-pull-requests) + - [Summary](#summary) + - [Step 1: Find something to work on](#step-1-find-something-to-work-on) + - [Step 2: Design](#step-2-design) + - [Step 3: Work your Magic](#step-3-work-your-magic) + - [Step 4: Pull Request](#step-4-pull-request) + - [Step 5: Merge](#step-5-merge) - [Licensing](#licensing) ## Security issue notifications @@ -252,13 +258,13 @@ Alternatively you can run these two commands: ### Conventions -Category | Convention -------------------------------------------------- | --------------------------------------------------------------------------------- -**Docstring** | We use [JSDoc](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html) annotations to provide type information and create API references. -**Style guide** | We use `eslint` to keep our code consistent in terms of style and reducing defects. -**Test coverage** | We use [Jest](https://jestjs.io/) to test our code and [Codecov](https://codecov.io/) to report test coverage. We aim to have 100% test coverage in our unit tests. -**Git commits** | We follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/). These are not enforced as we squash and merge PRs, but PR titles are enforced during CI. -**Documentation** | API reference docs are generated from docstrings which should have an Examples section to allow developers to have what they need within their own IDE. Documentation website covers the wider usage, tips, and strives to be concise. +| Category | Convention | +| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Docstring** | We use [JSDoc](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html) annotations to provide type information and create API references. | +| **Style guide** | We use `eslint` to keep our code consistent in terms of style and reducing defects. | +| **Test coverage** | We use [Jest](https://jestjs.io/) to test our code and [Codecov](https://codecov.io/) to report test coverage. We aim to have 100% test coverage in our unit tests. | +| **Git commits** | We follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/). These are not enforced as we squash and merge PRs, but PR titles are enforced during CI. | +| **Documentation** | API reference docs are generated from docstrings which should have an Examples section to allow developers to have what they need within their own IDE. Documentation website covers the wider usage, tips, and strives to be concise. | ## Contributing via Pull Requests diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 0000000000..6792654c1a --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1,299 @@ +## Table of contents + +- [Overview](#overview) +- [Current Maintainers](#current-maintainers) +- [Emeritus](#emeritus) +- [Labels](#labels) +- [Maintainer Responsibilities](#maintainer-responsibilities) + - [Uphold Code of Conduct](#uphold-code-of-conduct) + - [Prioritize Security](#prioritize-security) + - [Review Pull Requests](#review-pull-requests) + - [Merging Pull Requests](#merging-pull-requests) + - [Triage New Issues](#triage-new-issues) + - [Triage Bug Reports](#triage-bug-reports) + - [Triage RFCs](#triage-rfcs) + - [Releasing a new version](#releasing-a-new-version) + - [Drafting release notes](#drafting-release-notes) + - [Run end to end tests](#run-end-to-end-tests) + - [Releasing a documentation hotfix](#releasing-a-documentation-hotfix) + - [Maintain Overall Health of the Repo](#maintain-overall-health-of-the-repo) + - [Manage Roadmap](#manage-roadmap) + - [Add Continuous Integration Checks](#add-continuous-integration-checks) + - [Negative Impact on the Project](#negative-impact-on-the-project) + - [Becoming a maintainer](#becoming-a-maintainer) +- [Common scenarios](#common-scenarios) + - [Contribution is stuck](#contribution-is-stuck) + - [Insufficient feedback or information](#insufficient-feedback-or-information) + - [Crediting contributions](#crediting-contributions) + - [Is that a bug?](#is-that-a-bug) + - [Mentoring contributions](#mentoring-contributions) + - [Long running issues or PRs](#long-running-issues-or-prs) +- [E2E framework](#e2e-framework) +- [Automation](#automation) + +## Overview + +> **Please treat this content as a living document.** + +This is document explains who the maintainers are (see below), what they do in this repo, and how they should be doing it. If you're interested in contributing, see [CONTRIBUTING](CONTRIBUTING.md). + +## Current Maintainers + +| Maintainer | GitHub ID | Affiliation | +| -------------------------- | ------------------------------------------- | ----------- | +| Andrea Amorosi | [dreamorosi](https://github.com/dreamorosi) | Amazon | +| Sara Gerion | [saragerion](https://github.com/saragerion) | Amazon | +| Florian Chazal | [flochaz](https://github.com/flochaz) | Amazon | +| Chadchapol Vittavutkarnvej | [ijemmy](https://github.com/ijemmy) | Amazon | + +## Emeritus + +Previous active maintainers who contributed to this project. + +| Maintainer | GitHub ID | Affiliation | +| ----------------- | --------------------------------------------- | ----------- | +| Alan Churley | [alan-churley](alan-churley) | CloudCall | +| Nicolas Moutschen | [bahrmichael](https://github.com/bahrmichael) | Stedi | + +## Labels + +These are the most common labels used by maintainers to triage issues, pull requests (PR), and for project management: + +| Label | Usage | Notes | +| -------------------------------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------- | +| triage | New issues that require maintainers review | Issue template | +| area/documentation | Improvements or additions to documentation | Examples/Readme files; Doc additions, fixes, etc.; | +| area/logger | Items related to the Logger Utility | PR automation | +| area/metrics | Items related to the Metrics Utility | PR automation | +| area/tracer | Items related to the Tracer Utility | PR automation | +| area/idempotency | Items related to the Idempotency Utility | PR automation | +| area/parameters | Items related to the Parameters Utility | PR automation | +| area/automation | Items related to automation like GitHub workflows or CI/CD | PR automation | +| size/XS | PRs between 0-9 LOC | PR automation | +| size/S | PRs between 10-29 LOC | PR automation | +| size/M | PRs between 30-99 LOC | PR automation | +| size/L | PRs between 100-499 LOC | PR automation | +| size/XL | PRs between 500-999 LOC, often PRs that grown with feedback | PR automation | +| size/XXL | PRs with 1K+ LOC, largely documentation related | PR automation | +| customer-reference | Authorization to use company name in our documentation | Public Relations | +| community-content | Suggested content to feature in our documentation | Public Relations | +| do-not-merge | PRs that are blocked for varying reasons | Timeline is uncertain | +| type/bug | Unexpected, reproducible and unintended software behavior | PR/Release automation; Doc snippets are excluded; | +| type/bug-upstream | Bug caused by upstream dependency | | +| type/not-a-bug | New and existing bug reports incorrectly submitted as bug | Analytics | +| type/deprecation | This item contains code deprecation | | +| type/duplicate | This issue is a duplicate of an existing one | Analytics | +| type/feature-request | Issue requesting new or enhancements to existing features | Issue template | +| type/feature | PRs that introduce new features | PR automation | +| type/enhancement | PRs that introduce minor changes, usually to existing features | PR automation | +| type/RFC | Technical design documents related to a feature request | | +| type/internal | PRs that introduce changes in governance, tech debt and chores (linting setup, baseline, etc.) | PR automation | +| type/tests | PRs that add or change tests | PR automation | +| type/dependencies | Changes that touch dependencies, e.g. Dependabot, etc. | Issues/PR automation | +| type/breaking-change | Changes that will cause customer impact and need careful triage | | +| status/blocked | Items which progress is blocked by external dependency or reason | | +| status/confirmed | Items with clear scope and that are ready for implementation | | +| status/discussing | Items that need to be discussed, elaborated, or refined | | +| status/on-hold | Items that are on hold and will be revisited in the future | | +| status/pending-release | Merged changes that will be available soon | Release automation auto-closes/notifies it | +| status/completed | Items that are complete and have been merged and/or shipped | | +| status/rejected | This is something we will not be working on. At least, not in the measurable future | | +| status/pending-close-response-required | This issue will be closed soon unless the discussion moves forward | Stale Automation | +| good-first-issue | Something that is suitable for those who want to start contributing | | +| help-wanted | Tasks you want help from anyone to move forward | Bandwidth, complex topics, etc. | +| need-customer-feedback | Tasks that need more feedback before proceeding | 80/20% rule, uncertain, etc. | +| need-more-information | Missing information before making any calls | | +| need-issue | PR is missing a related issue for tracking change | | + +## Maintainer Responsibilities + +Maintainers are active and visible members of the community, and have [maintain-level permissions on a repository](https://docs.github.com/en/organizations/managing-access-to-your-organizations-repositories/repository-permission-levels-for-an-organization). Use those privileges to serve the community and evolve code as follows. + +Be aware of recurring ambiguous situations and [document them](#common-scenarios) to help your fellow maintainers. + +### Uphold Code of Conduct + +Model the behavior set forward by the [Code of Conduct](CODE_OF_CONDUCT.md) and raise any violations to other maintainers and admins. There could be unusual circumstances where inappropriate behavior does not immediately fall within the [Code of Conduct](CODE_OF_CONDUCT.md). + +These might be nuanced and should be handled with extra care - when in doubt, do not engage and reach out to other maintainers and admins. + +### Prioritize Security + +Security is your number one priority. Maintainer's Github keys must be password protected securely and any reported security vulnerabilities are addressed before features or bugs. + +Note that this repository is monitored and supported 24/7 by Amazon Security, see [Reporting a Vulnerability](SECURITY.md) for details. + +### Review Pull Requests + +Review pull requests regularly, comment, suggest, reject, merge and close. Accept only high quality pull-requests. Provide code reviews and guidance on incoming pull requests. + +PRs are [labeled](#labels) based on file changes and semantic title. Pay attention to whether labels reflect the current state of the PR and correct accordingly. + +Use and enforce [semantic versioning](https://semver.org/) pull request titles, as these will be used for [CHANGELOG](CHANGELOG.md) and [Release notes](https://github.com/awslabs/aws-lambda-powertools-typescript/releases) - make sure they communicate their intent at the human level. + +For issues linked to a PR, our automation should apply the `pending-release` label. Make sure the label is always applied when merging. [Upon release](#releasing-a-new-version), these issues will be notified which release version contains their change. + +See [Common scenarios](#common-scenarios) section for additional guidance. + +### Merging Pull Requests + +Before merging a PR make sure that the title reflects the changes being introduced. + +This project uses the [squash and merge](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges#squash-and-merge-your-commits) strategy which means commits are squashed into a single commit. Instead of seeing all of a contributor's individual commits from a topic branch, the commits are combined into one commit and merged into the default branch. + +This allows you to have control over the commit message although it should match the PR title most of the time. Use and enforce [semantic versioning](https://semver.org/), as these will be used for versioning the next release. + +### Triage New Issues + +Manage [labels](#labels), review issues regularly, and create new labels as needed by the project. Remove `triage` label when you're able to confirm the validity of a request, a bug can be reproduced, etc. Give priority to the original author for implementation, unless it is a sensitive task that is best handled by maintainers. + +Make sure issues are assigned to our [board of activities](https://github.com/orgs/awslabs/projects/76/) and have the right [status](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/roadmap/#roadmap-status-definition). + +Use our [labels](#labels) to signal good first issues to new community members, and to set expectation that this might need additional feedback from the author, other customers, experienced community members and/or maintainers. + +Be aware of [casual contributors](https://opensource.com/article/17/10/managing-casual-contributors) and recurring contributors. Provide the experience and attention you wish you had if you were starting in open source. + +See [Common scenarios](#common-scenarios) section for additional guidance. + +### Triage Bug Reports + +Be familiar with [our definition of bug](#is-that-a-bug). If it's not a bug, you can close it or adjust its title and labels - always communicate the reason accordingly. + +For bugs caused by upstream dependencies, replace `bug` with `bug-upstream` label. Ask the author whether they'd like to raise the issue upstream or if they prefer us to do so. + +Assess the impact and make the call on whether we need an emergency release. Contact other [maintainers](#current-maintainers) when in doubt. + +See [Common scenarios](#common-scenarios) section for additional guidance. + +### Triage RFCs + +RFC is a collaborative process to help us get to the most optimal solution given the context. Their purpose is to ensure everyone understands what this context is, their trade-offs, and alternative solutions that were part of the research before implementation begins. + +Make sure you ask these questions in mind when reviewing: + +- Does it use our [RFC template](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/new?assignees=&labels=RFC%2Ctriage&template=rfc.yml&title=RFC%3A+TITLE)? +- Does the match our [Tenets](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/#tenets)? +- Does the proposal address the use case? If so, is the recommended usage explicit? +- Does it focus on the mechanics to solve the use case over fine-grained implementation details? +- Can anyone familiar with the code base implement it? +- If approved, are they interested in contributing? Do they need any guidance? +- Does this significantly increase the overall project maintenance? Do we have the skills to maintain it? +- If we can't take this use case, are there alternative projects we could recommend? Or does it call for a new project altogether? + +When necessary, be upfront that the time to review, approve, and implement a RFC can vary - see [Contribution is stuck](#contribution-is-stuck). Some RFCs may be further updated after implementation, as certain areas become clearer. + +An example of a successful RFC: [#447](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/447) + +### Releasing a new version + +🚧 WIP 🚧 + +#### Drafting release notes + +Visit the [Releases page](https://github.com/awslabs/aws-lambda-powertools-typescript/releases) and choose the edit pencil button. + +Make sure the `tag` field reflects the new version you're releasing, the target branch field is set to `main`, and `release title` matches your tag e.g., `v1.4.1`. + +You'll notice we group all changes based on their [labels](#labels) like `type/feature`, `type/bug`, `type/documentation`, etc. + +**I spotted a typo or incorrect grouping - how do I fix it?** + +Edit the respective PR title and update their [labels](#labels). Then run the [Release Drafter workflow](https://github.com/awslabs/aws-lambda-powertools-typescript/actions/workflows/release-drafter.yml) to update the Draft release. + +**All looking good, what's next?** + +The best part comes now. Replace the placeholder `[Human readable summary of changes]` with what you'd like to communicate to customers what this release is all about. Rule of thumb: always put yourself in the customers shoes. + +These are some questions to keep in mind when drafting your first or future release notes: + +- Can customers understand at a high level what changed in this release? +- Is there a link to the documentation where they can read more about each main change? +- Are there any graphics or [code snippets](carbon.now.sh/) that can enhance readability? +- Are we calling out any key contributor(s) to this release? + - All contributors are automatically credited, use this as an exceptional case to feature them + +Once you're happy, hit `Publish release` 🎉🎉🎉. + +This will kick off the [Publish docs on release](https://github.com/awslabs/aws-lambda-powertools-typescript/blob/main/.github/workflows/publish-docs-on-release.yml) workflow and within a few minutes you should see the latest version in PyPi, and all issues labeled as `pending-release` will be closed and notified. + +### Run end to end tests + +E2E tests should be ran before every merge to `main` or manually via [run-e2e-tests workflow](https://github.com/awslabs/aws-lambda-powertools-typescript/actions/workflows/run-e2e-tests.yml) before making a release. + +To run locally, you need [AWS CDK CLI](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html#getting_started_prerequisites) and an [account bootstrapped](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html) (`cdk bootstrap`). With a default AWS CLI profile configured, or `AWS_PROFILE` environment variable set, run `npm run test:e2e -ws`. + +### Releasing a documentation hotfix + +You can rebuild the latest documentation without a full release via this [GitHub Actions Workflow](https://github.com/awslabs/aws-lambda-powertools-typescript/blob/main/.github/workflows/publish-docs-on-release.yml). Choose `Run workflow`, keep `main` as the branch, and input the latest Powertools version available i.e. `v1.4.1`. + +This workflow will update both user guide and API documentation. + +### Maintain Overall Health of the Repo + +Keep the `main` branch at production quality at all times. If a PR introduces code changes you should make sure that linting and tests are passing before merging. + +### Manage Roadmap + +See [Roadmap section](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/roadmap/) + +Ensure the repo highlights features that should be elevated to the project roadmap. Be clear about the feature’s status, priority, target version, and whether or not it should be elevated to the roadmap. + +### Add Continuous Integration Checks + +Add integration checks that validate pull requests and pushes to ease the burden on Pull Request reviewers. Continuously revisit areas of improvement to reduce operational burden in all parties involved. + +### Negative Impact on the Project + +Actions that negatively impact the project will be handled by the admins, in coordination with other maintainers, in balance with the urgency of the issue. Examples would be [Code of Conduct](CODE_OF_CONDUCT.md) violations, deliberate harmful or malicious actions, spam, monopolization, and security risks. + +### Becoming a maintainer + +In late 2023, we will revisit this. We need to improve our understanding of how other projects are doing, their mechanisms to promote key contributors, and how they interact daily. + +We suspect this process might look similar to the [OpenSearch project](https://github.com/opensearch-project/.github/blob/main/MAINTAINERS.md#becoming-a-maintainer). + +## Common scenarios + +These are recurring ambiguous situations that new and existing maintainers may encounter. They serve as guidance. It is up to each maintainer to follow, adjust, or handle in a different manner as long as [our conduct is consistent](#uphold-code-of-conduct) + +### Contribution is stuck + +A contribution can get stuck often due to lack of bandwidth and language barrier. For bandwidth issues, check whether the author needs help. Make sure you get their permission before pushing code into their existing PR - do not create a new PR unless strictly necessary. + +For language barrier and others, offer a 1:1 chat to get them unblocked. Often times, English might not be their primary language, and writing in public might put them off, or come across not the way they intended to be. + +In other cases, you may have constrained capacity. Use `help=wanted` label when you want to signal other maintainers and external contributors that you could use a hand to move it forward. + +### Insufficient feedback or information + +When in doubt, use `need-more-information` or `need-customer-feedback` labels to signal more context and feedback are necessary before proceeding. You can also use `status/on-hold` label when you expect it might take a while to gather enough information before you can decide. + +### Crediting contributions + +We credit all contributions as part of each [release note](https://github.com/awslabs/aws-lambda-powertools-typescript/releases) as an automated process. If you find contributors are missing from the release note you're producing, please add them manually. + +### Is that a bug? + +A bug produces incorrect or unexpected results at runtime that differ from its intended behavior. Bugs must be reproducible. They directly affect customers experience at runtime despite following its recommended usage. + +Documentation snippets, examples, use of internal components, or unadvertised functionalities are not considered bugs. + +### Mentoring contributions + +Always favor mentoring issue authors to contribute, unless they're not interested or the implementation is sensitive (_e.g., complexity, time to release, etc._). + +Make use of `help-wanted` and `good-first-issue` to signal additional contributions the community can help. + +### Long running issues or PRs + +Try offering a 1:1 call in the attempt to get to a mutual understanding and clarify areas that maintainers could help. + +In the rare cases where both parties don't have the bandwidth or expertise to continue, it's best to use the `on-hold` label. By then, see if it's possible to break the PR or issue in smaller chunks, and eventually close if there is no progress. + +## E2E framework + +🚧 WIP 🚧 + +## Automation + +🚧 WIP 🚧 \ No newline at end of file diff --git a/README.md b/README.md index c7dd0c0a7d..d97b378def 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# AWS Lambda Powertools for TypeScript +# AWS Lambda Powertools for TypeScript [![Join our Discord](https://dcbadge.vercel.app/api/server/B8zZKbbyET)](https://discord.gg/B8zZKbbyET) @@ -6,17 +6,19 @@ A suite of utilities for AWS Lambda functions to ease the adoption of best pract You can use the library in both TypeScript and JavaScript code bases. -AWS Lambda Powertools for [Python](https://github.com/awslabs/aws-lambda-powertools-python) and AWS Lambda Powertools for [Java](https://github.com/awslabs/aws-lambda-powertools-java) are also available. +> 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://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)** +**[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 +## Table of contents - [Features](#features) - [Getting started](#getting-started) - [Installation](#installation) + - [Lambda layers](#lambda-layers) + - [NPM modules](#npm-modules) - [Examples](#examples) - - [Serverless TypeScript Demo](#serverless-typescript-demo-application) + - [Serverless TypeScript Demo application](#serverless-typescript-demo-application) - [Contribute](#contribute) - [Roadmap](#roadmap) - [Connect](#connect) diff --git a/docs/requirements.txt b/docs/requirements.txt index 33e6747563..8ab5fb126c 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,3 @@ mike==1.1.2 -mkdocs-material==8.3.6 -mkdocs-git-revision-date-plugin==0.3.2 -mkdocs-glightbox==0.1.6 \ No newline at end of file +mkdocs-material==8.5.9 +mkdocs-git-revision-date-plugin==0.3.2 \ No newline at end of file diff --git a/docs/roadmap.md b/docs/roadmap.md new file mode 100644 index 0000000000..f449979f2e --- /dev/null +++ b/docs/roadmap.md @@ -0,0 +1,102 @@ +## Overview + +This is our public roadmap that outlines the high level direction we are working towards, namely [Themes](#themes). We update this document when our priorities change: security and stability is our top priority. + +[See our latest list of activities »](https://github.com/orgs/awslabs/projects/76/views/2){target="_blank"} + +## Themes + +!!! info "Operational Excellence is priority number 1." + +Themes are key activities maintainers are focusing on, besides bug reports. These are updated periodically and you can get an idea of the overall progress in the [Milestones section](https://github.com/awslabs/aws-lambda-powertools-typescript/milestones){target="_blank"}. + +### New utilities + +After going GA, we want to start working on new utilities, specifically but not limited to the most commonly asked: **(1)** [Idempotency](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/447) and **(2)** [Parameters](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/846). + +### Lambda Layers + +We want to publish and maintain public Lambda Layers. + +Work on this area is mostly complete and layers are already available in all commercial regions, however we still have some [work to do around automation](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1124). + +### ES Modules support + +Another one of the most requested features is [ES Modules support](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/521). This topic requires some in-depth investigation to understand how to move forward and how to deal with some of our upstream dependencies. + +When the time comes, we might open a RFC to discuss options. + +### Improve operational excellence + +We continue to work on increasing operational excellence to remove as much undifferentiated heavylifting for maintainers, so that we can focus on delivering features that help you. + +This means improving our automation workflows, and project management, and test coverage. + +## Roadmap status definition + +
+```mermaid +graph LR + Ideas --> Backlog --> Work["Working on it"] --> Merged["Coming soon"] --> Shipped +``` +Visual representation +
+ +Within our [public board](https://github.com/orgs/awslabs/projects/76/views/3){target="_blank"}, you'll see the following values in the `Status` column: + +* **Ideas**. Incoming and existing feature requests that are not being actively considered yet. These will be reviewed when bandwidth permits and based on demand. +* **Backlog**. Accepted feature requests or enhancements that we want to work on. +* **Working on it**. Features or enhancements we're currently either researching or implementing it. +* **Coming soon**. Any feature, enhancement, or bug fixes that have been merged and are coming in the next release. +* **Shipped**. Features or enhancements that are now available in the most recent release. +* **On hold**. Features or items that are currently blocked until further notice. +* **Pending review**. Features which implementation is mostly completed, but need review and some additional iterations. + +> Tasks or issues with empty `Status` will be categorized in upcoming review cycles. + +## Process + +
+```mermaid +graph LR + PFR[Feature request] --> Triage{Need RFC?} + Triage --> |Complex/major change or new utility?| RFC[Ask or write RFC] --> Approval{Approved?} + Triage --> |Minor feature or enhancement?| NoRFC[No RFC required] --> Approval + Approval --> |Yes| Backlog + Approval --> |No | Reject["Inform next steps"] + Backlog --> |Prioritized| Implementation + Backlog --> |Defer| WelcomeContributions["help-wanted label"] +``` +Visual representation +
+ +Our end-to-end mechanism follows four major steps: + +* **Feature Request**. Ideas start with a [feature request](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/new?assignees=&labels=type/feature-request%2Ctriage&template=feature_request.yml&title=Feature+request%3A+TITLE){target="_blank"} to outline their use case at a high level. For complex use cases, maintainers might ask for/write a RFC. + * Maintainers review requests based on [project tenets](index.md#tenets){target="_blank"}, customers reaction (👍), and use cases. +* **Request-for-comments (RFC)**. Design proposals use our [RFC issue template](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/new?assignees=&labels=type/RFC%2Ctriage&template=rfc.yml&title=RFC%3A+TITLE){target="_blank"} to describe its implementation, challenges, developer experience, dependencies, and alternative solutions. + * This helps refine the initial idea with community feedback before a decision is made. +* **Decision**. After carefully reviewing and discussing them, maintainers make a final decision on whether to start implementation, defer or reject it, and update everyone with the next steps. +* **Implementation**. For approved features, maintainers give priority to the original authors for implementation unless it is a sensitive task that is best handled by maintainers. + +??? info "See [Maintainers](https://github.com/awslabs/aws-lambda-powertools-typescript/blob/develop/MAINTAINERS.md) document to understand how we triage issues and pull requests, labels and governance." + +## Disclaimer + +The AWS Lambda Powertools team values feedback and guidance from its community of users, although final decisions on inclusion into the project will be made by AWS. + +We determine the high-level direction for our open roadmap based on customer feedback and popularity (👍🏽 and comments), security and operational impacts, and business value. Where features don’t meet our goals and longer-term strategy, we will communicate that clearly and openly as quickly as possible with an explanation of why the decision was made. + +## FAQs + +**Q: Why did you build this?** + +A: We know that our customers are making decisions and plans based on what we are developing, and we want to provide our customers the insights they need to plan. + +**Q: Why are there no dates on your roadmap?** + +A: Because job zero is security and operational stability, we can't provide specific target dates for features. The roadmap is subject to change at any time, and roadmap issues in this repository do not guarantee a feature will be launched as proposed. + +**Q: How can I provide feedback or ask for more information?** + +A: For existing features, you can directly comment on issues. For anything else, please open an issue. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 6b429558d9..d6b85211d4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,12 +2,12 @@ site_name: AWS Lambda Powertools for TypeScript site_description: AWS Lambda Powertools for TypeScript site_author: Amazon Web Services repo_url: https://github.com/awslabs/aws-lambda-powertools-typescript -edit_uri: edit/develop/docs +edit_uri: edit/main/docs nav: - Homepage: index.md - Changelog: changelog.md - - Roadmap: https://github.com/awslabs/aws-lambda-powertools-roadmap/projects/1" target="_blank + - Roadmap: roadmap.md - API reference: api/" target="_blank - Core utilities: - core/tracer.md @@ -31,14 +31,15 @@ theme: icon: material/toggle-switch name: Switch to light mode features: + - header.autohide - navigation.sections - navigation.expand - navigation.top - - toc.follow - navigation.instant - navigation.indexes - navigation.tracking - content.code.annotate + - toc.follow - toc.integrate icon: repo: fontawesome/brands/github @@ -52,7 +53,6 @@ markdown_extensions: alternate_style: true - pymdownx.highlight: linenums: true - - pymdownx.superfences - pymdownx.details - pymdownx.snippets: base_path: "." @@ -62,7 +62,9 @@ markdown_extensions: permalink: true toc_depth: 4 - attr_list - - pymdownx.emoji + - pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg - pymdownx.inlinehilite - pymdownx.superfences: custom_fences: @@ -75,7 +77,6 @@ copyright: Copyright © 2022 Amazon Web Services plugins: - git-revision-date - search - - glightbox extra_css: - stylesheets/extra.css diff --git a/packages/commons/README.md b/packages/commons/README.md index 63228c23f0..184439883a 100644 --- a/packages/commons/README.md +++ b/packages/commons/README.md @@ -1,10 +1,26 @@ -# AWS Lambda Powertools for TypeScript +# AWS Lambda Powertools for TypeScript -A suite of TypeScript utilities for AWS Lambda functions to ease adopting best practices such as tracing, structured logging, custom metrics, and more. (AWS Lambda Powertools for [Python](https://github.com/awslabs/aws-lambda-powertools-python) and AWS Lambda Powertools for [Java](https://github.com/awslabs/aws-lambda-powertools-java) are also available). +A suite of utilities for AWS Lambda functions to ease the adoption of best practices such as tracing, structured logging, custom metrics, and more. -**[📜 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)** +You can use the library in both TypeScript and JavaScript code bases. -> **An AWS Developer Acceleration (DevAx) initiative by Specialist Solution Architects | aws-devax-open-source@amazon.com** +> 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)** + +## Table of contents + +- [Table of contents](#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 @@ -18,8 +34,17 @@ Find the complete project's [documentation here](https://awslabs.github.io/aws-l ### 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. +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) @@ -32,15 +57,29 @@ Each TypeScript utility is installed as standalone NPM package. * [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) -## Credits +### Serverless TypeScript Demo application -* 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/). +The [Serverless TypeScript Demo](https://github.com/aws-samples/serverless-typescript-demo) shows how to use Lambda Powertools for TypeScript. +You can find instructions on how to deploy and load test this application in the [repository](https://github.com/aws-samples/serverless-typescript-demo). + +## Contribute + +If you are interested in contributing to this project, please refer to our [Contributing Guidelines](https://github.com/awslabs/aws-lambda-powertools-typescript/blob/main/CONTRIBUTING.md). + +## Roadmap + +The roadmap of Powertools is driven by customers’ demand. +Help us prioritize upcoming functionalities or utilities by [upvoting existing RFCs and feature requests](https://github.com/awslabs/aws-lambda-powertools-typescript/issues), or [creating new ones](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/new/choose), in this GitHub repository. ## Connect * **AWS Lambda Powertools on Discord**: `#typescript` - **[Invite link](https://discord.gg/B8zZKbbyET)** * **Email**: aws-lambda-powertools-feedback@amazon.com +## Credits + +Credits for the Lambda Powertools idea go to [DAZN](https://github.com/getndazn) and their [DAZN Lambda Powertools](https://github.com/getndazn/dazn-lambda-powertools/). + ## License This library is licensed under the MIT-0 License. See the LICENSE file. \ No newline at end of file diff --git a/packages/logger/README.md b/packages/logger/README.md index aab76edadc..80f5122041 100644 --- a/packages/logger/README.md +++ b/packages/logger/README.md @@ -1,20 +1,20 @@ -# AWS Lambda Powertools for TypeScript +# AWS Lambda Powertools for TypeScript A suite of utilities for AWS Lambda functions to ease the adoption of best practices such as tracing, structured logging, custom metrics, and more. You can use the library in both TypeScript and JavaScript code bases. -AWS Lambda Powertools for [Python](https://github.com/awslabs/aws-lambda-powertools-python) and AWS Lambda Powertools for [Java](https://github.com/awslabs/aws-lambda-powertools-java) are also available. +> 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://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)** +**[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 +## 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) @@ -34,7 +34,8 @@ Find the complete project's [documentation here](https://awslabs.github.io/aws-l ### 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. + +Each TypeScript utility is installed as standalone npm package. Install all three core utilities at once with this single command: diff --git a/packages/metrics/README.md b/packages/metrics/README.md index 297dfde1a4..a148d17c99 100644 --- a/packages/metrics/README.md +++ b/packages/metrics/README.md @@ -1,20 +1,20 @@ -# AWS Lambda Powertools for TypeScript +# AWS Lambda Powertools for TypeScript A suite of utilities for AWS Lambda functions to ease the adoption of best practices such as tracing, structured logging, custom metrics, and more. You can use the library in both TypeScript and JavaScript code bases. -AWS Lambda Powertools for [Python](https://github.com/awslabs/aws-lambda-powertools-python) and AWS Lambda Powertools for [Java](https://github.com/awslabs/aws-lambda-powertools-java) are also available. +> 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://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)** +**[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 +## 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) diff --git a/packages/tracer/README.md b/packages/tracer/README.md index 297dfde1a4..a148d17c99 100644 --- a/packages/tracer/README.md +++ b/packages/tracer/README.md @@ -1,20 +1,20 @@ -# AWS Lambda Powertools for TypeScript +# AWS Lambda Powertools for TypeScript A suite of utilities for AWS Lambda functions to ease the adoption of best practices such as tracing, structured logging, custom metrics, and more. You can use the library in both TypeScript and JavaScript code bases. -AWS Lambda Powertools for [Python](https://github.com/awslabs/aws-lambda-powertools-python) and AWS Lambda Powertools for [Java](https://github.com/awslabs/aws-lambda-powertools-java) are also available. +> 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://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)** +**[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 +## 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) From d717a26bba086be4c01f1458422662f8bfba09a9 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Mon, 21 Nov 2022 11:07:36 +0100 Subject: [PATCH 10/15] feat(parameters): added `BaseProvider` class (#1168) * feat: added baseprovider class * tests: added unit tests * chore: added package to unit tests run * chore: added comments * chore: break down classes in files * chore: remove redundant store init value * refactor: moved constants in separate file * chore: made store protected * fix: removed cache pruning * feat: added baseprovider class --- ...sable-run-linting-check-and-unit-tests.yml | 6 +- package.json | 3 +- packages/parameters/jest.config.js | 45 ++ packages/parameters/package.json | 54 +++ packages/parameters/src/BaseProvider.ts | 170 +++++++ packages/parameters/src/Exceptions.ts | 14 + packages/parameters/src/ExpirableValue.ts | 20 + packages/parameters/src/GetMultipleOptions.ts | 18 + packages/parameters/src/GetOptions.ts | 17 + packages/parameters/src/constants.ts | 3 + packages/parameters/src/index.ts | 2 + packages/parameters/src/types/BaseProvider.ts | 34 ++ packages/parameters/src/types/index.ts | 1 + .../helpers/populateEnvironmentVariables.ts | 9 + .../tests/unit/BaseProvider.test.ts | 436 ++++++++++++++++++ packages/parameters/tsconfig-dev.json | 30 ++ packages/parameters/tsconfig.es.json | 30 ++ packages/parameters/tsconfig.json | 30 ++ 18 files changed, 918 insertions(+), 4 deletions(-) create mode 100644 packages/parameters/jest.config.js create mode 100644 packages/parameters/package.json create mode 100644 packages/parameters/src/BaseProvider.ts create mode 100644 packages/parameters/src/Exceptions.ts create mode 100644 packages/parameters/src/ExpirableValue.ts create mode 100644 packages/parameters/src/GetMultipleOptions.ts create mode 100644 packages/parameters/src/GetOptions.ts create mode 100644 packages/parameters/src/constants.ts create mode 100644 packages/parameters/src/index.ts create mode 100644 packages/parameters/src/types/BaseProvider.ts create mode 100644 packages/parameters/src/types/index.ts create mode 100644 packages/parameters/tests/helpers/populateEnvironmentVariables.ts create mode 100644 packages/parameters/tests/unit/BaseProvider.test.ts create mode 100644 packages/parameters/tsconfig-dev.json create mode 100644 packages/parameters/tsconfig.es.json create mode 100644 packages/parameters/tsconfig.json diff --git a/.github/workflows/reusable-run-linting-check-and-unit-tests.yml b/.github/workflows/reusable-run-linting-check-and-unit-tests.yml index 991260cb76..ce7c0e1996 100644 --- a/.github/workflows/reusable-run-linting-check-and-unit-tests.yml +++ b/.github/workflows/reusable-run-linting-check-and-unit-tests.yml @@ -42,11 +42,11 @@ jobs: if: steps.cache-node-modules.outputs.cache-hit == 'true' run: | npm run build -w packages/commons - npm run build -w packages/logger & npm run build -w packages/tracer & npm run build -w packages/metrics + npm run build -w packages/logger & npm run build -w packages/tracer & npm run build -w packages/metrics -w packages/parameters - name: Run linting - run: npm run lint -w packages/commons -w packages/logger -w packages/tracer -w packages/metrics + run: npm run lint -w packages/commons -w packages/logger -w packages/tracer -w packages/metrics -w packages/parameters - name: Run unit tests - run: npm t -w packages/commons -w packages/logger -w packages/tracer -w packages/metrics + run: npm t -w packages/commons -w packages/logger -w packages/tracer -w packages/metrics -w packages/parameters check-examples: runs-on: ubuntu-latest env: diff --git a/package.json b/package.json index c077b3c592..778f80ffa1 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "packages/commons", "packages/logger", "packages/metrics", - "packages/tracer" + "packages/tracer", + "packages/parameters" ], "scripts": { "init-environment": "husky install", diff --git a/packages/parameters/jest.config.js b/packages/parameters/jest.config.js new file mode 100644 index 0000000000..6c267d005e --- /dev/null +++ b/packages/parameters/jest.config.js @@ -0,0 +1,45 @@ +module.exports = { + displayName: { + name: 'AWS Lambda Powertools utility: PARAMETERS', + color: 'magenta', + }, + 'runner': 'groups', + 'preset': 'ts-jest', + 'transform': { + '^.+\\.ts?$': 'ts-jest', + }, + moduleFileExtensions: [ 'js', 'ts' ], + 'collectCoverageFrom': [ + '**/src/**/*.ts', + '!**/node_modules/**', + ], + 'testMatch': ['**/?(*.)+(spec|test).ts'], + 'roots': [ + '/src', + '/tests', + ], + 'testPathIgnorePatterns': [ + '/node_modules/', + ], + 'testEnvironment': 'node', + 'coveragePathIgnorePatterns': [ + '/node_modules/', + '/types/', + ], + 'coverageThreshold': { + 'global': { + 'statements': 100, + 'branches': 100, + 'functions': 100, + 'lines': 100, + }, + }, + 'coverageReporters': [ + 'json-summary', + 'text', + 'lcov' + ], + 'setupFiles': [ + '/tests/helpers/populateEnvironmentVariables.ts' + ] +}; \ No newline at end of file diff --git a/packages/parameters/package.json b/packages/parameters/package.json new file mode 100644 index 0000000000..fffbdbc373 --- /dev/null +++ b/packages/parameters/package.json @@ -0,0 +1,54 @@ +{ + "name": "@aws-lambda-powertools/parameters", + "version": "1.4.1", + "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" + }, + "scripts": { + "commit": "commit", + "test": "npm run test:unit", + "test:unit": "jest --group=unit --detectOpenHandles --coverage --verbose", + "test:e2e:nodejs12x": "echo \"Not implemented\"", + "test:e2e:nodejs14x": "echo \"Not implemented\"", + "test:e2e:nodejs16x": "echo \"Not implemented\"", + "test:e2e": "echo \"Not implemented\"", + "watch": "jest --watch", + "build": "tsc", + "lint": "eslint --ext .ts --no-error-on-unmatched-pattern src tests", + "lint-fix": "eslint --fix --ext .ts --no-error-on-unmatched-pattern src tests", + "package": "mkdir -p dist/ && npm pack && mv *.tgz dist/", + "package-bundle": "../../package-bundler.sh parameters-bundle ./dist", + "prepare": "npm run build", + "postversion": "git push --tags" + }, + "homepage": "https://github.com/awslabs/aws-lambda-powertools-typescript/tree/main/packages/parameters#readme", + "license": "MIT-0", + "main": "./lib/index.js", + "types": "./lib/index.d.ts", + "devDependencies": {}, + "files": [ + "lib" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/awslabs/aws-lambda-powertools-typescript.git" + }, + "bugs": { + "url": "https://github.com/awslabs/aws-lambda-powertools-typescript/issues" + }, + "dependencies": {}, + "keywords": [ + "aws", + "lambda", + "powertools", + "ssm", + "secrets", + "serverless", + "nodejs" + ] +} \ No newline at end of file diff --git a/packages/parameters/src/BaseProvider.ts b/packages/parameters/src/BaseProvider.ts new file mode 100644 index 0000000000..9a081cc7f5 --- /dev/null +++ b/packages/parameters/src/BaseProvider.ts @@ -0,0 +1,170 @@ +import { fromBase64 } from '@aws-sdk/util-base64-node'; +import { GetOptions } from './GetOptions'; +import { GetMultipleOptions } from './GetMultipleOptions'; +import { ExpirableValue } from './ExpirableValue'; +import { TRANSFORM_METHOD_BINARY, TRANSFORM_METHOD_JSON } from './constants'; +import { GetParameterError, TransformParameterError } from './Exceptions'; +import type { BaseProviderInterface, GetMultipleOptionsInterface, GetOptionsInterface, TransformOptions } from './types'; + +abstract class BaseProvider implements BaseProviderInterface { + protected store: Map; + + public constructor () { + this.store = new Map(); + } + + public addToCache(key: string, value: string | Record, maxAge: number): void { + if (maxAge <= 0) return; + + this.store.set(key, new ExpirableValue(value, maxAge)); + } + + 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. + * + * @param {string} name - Parameter name + * @param {GetOptionsInterface} options - Options to configure maximum age, trasformation, AWS SDK options, or force fetch + */ + public async get(name: string, options?: GetOptionsInterface): Promise> { + const configs = new GetOptions(options); + const key = [ name, configs.transform ].toString(); + + if (!configs.forceFetch && !this.hasKeyExpiredInCache(key)) { + // If the code enters in this block, then the key must exist & not have been expired + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return this.store.get(key)!.value; + } + + let value; + try { + value = await this._get(name, options?.sdkOptions); + } catch (error) { + throw new GetParameterError((error as Error).message); + } + + if (value && configs.transform) { + value = transformValue(value, configs.transform, true); + } + + if (value) { + this.addToCache(key, value, configs.maxAge); + } + + // TODO: revisit return type once providers are implemented, it might be missing binary when not transformed + return value; + } + + public async getMultiple(path: string, options?: GetMultipleOptionsInterface): Promise> { + const configs = new GetMultipleOptions(options || {}); + const key = [ path, configs.transform ].toString(); + + if (!configs.forceFetch && !this.hasKeyExpiredInCache(key)) { + // If the code enters in this block, then the key must exist & not have been expired + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return this.store.get(key)!.value as Record; + } + + let values: Record = {}; + try { + values = await this._getMultiple(path, options?.sdkOptions); + } catch (error) { + throw new GetParameterError((error as Error).message); + } + + if (Object.keys(values) && configs.transform) { + values = transformValues(values, configs.transform, configs.throwOnTransformError); + } + + if (Array.from(Object.keys(values)).length !== 0) { + this.addToCache(key, values, configs.maxAge); + } + + // TODO: revisit return type once providers are implemented, it might be missing something + return values; + } + + /** + * Retrieve parameter value from the underlying parameter store + * + * @param {string} name - Parameter name + * @param {unknown} sdkOptions - Options to pass to the underlying AWS SDK + */ + protected abstract _get(name: string, sdkOptions?: unknown): Promise; + + protected abstract _getMultiple(path: string, sdkOptions?: unknown): Promise>; + + /** + * 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. + * + * @param {string} key - Stringified representation of the key to retrieve + */ + private hasKeyExpiredInCache(key: string): boolean { + const value = this.store.get(key); + if (value) return value.isExpired(); + + return true; + } + +} + +// TODO: revisit `value` type once we are clearer on the types returned by the various SDKs +const transformValue = (value: unknown, transform: TransformOptions, throwOnTransformError: boolean, key: string = ''): string | Record | undefined => { + try { + const normalizedTransform = transform.toLowerCase(); + if ( + (normalizedTransform === TRANSFORM_METHOD_JSON || + (normalizedTransform === 'auto' && key.toLowerCase().endsWith(`.${TRANSFORM_METHOD_JSON}`))) && + typeof value === 'string' + ) { + return JSON.parse(value) as Record; + } else if ( + (normalizedTransform === TRANSFORM_METHOD_BINARY || + (normalizedTransform === 'auto' && key.toLowerCase().endsWith(`.${TRANSFORM_METHOD_BINARY}`))) && + typeof value === 'string' + ) { + return new TextDecoder('utf-8').decode(fromBase64(value)); + } else { + // TODO: revisit this type once we are clearer on types returned by SDKs + return value as string; + } + } catch (error) { + if (throwOnTransformError) + throw new TransformParameterError(transform, (error as Error).message); + + return; + } +}; + +const transformValues = (value: Record, transform: TransformOptions, throwOnTransformError: boolean): Record => { + const transformedValues: Record = {}; + for (const [ entryKey, entryValue ] of Object.entries(value)) { + try { + transformedValues[entryKey] = transformValue(entryValue, transform, throwOnTransformError, entryKey); + } catch (error) { + if (throwOnTransformError) + throw new TransformParameterError(transform, (error as Error).message); + } + } + + return transformedValues; +}; + +export { + BaseProvider, + ExpirableValue, + transformValue, +}; \ No newline at end of file diff --git a/packages/parameters/src/Exceptions.ts b/packages/parameters/src/Exceptions.ts new file mode 100644 index 0000000000..845577e453 --- /dev/null +++ b/packages/parameters/src/Exceptions.ts @@ -0,0 +1,14 @@ +class GetParameterError extends Error {} + +class TransformParameterError extends Error { + public constructor(transform: string, message: string) { + super(message); + + this.message = `Unable to transform value using '${transform}' transform: ${message}`; + } +} + +export { + GetParameterError, + TransformParameterError, +}; \ No newline at end of file diff --git a/packages/parameters/src/ExpirableValue.ts b/packages/parameters/src/ExpirableValue.ts new file mode 100644 index 0000000000..d4b3b2bda4 --- /dev/null +++ b/packages/parameters/src/ExpirableValue.ts @@ -0,0 +1,20 @@ +import type { ExpirableValueInterface } from './types'; + +class ExpirableValue implements ExpirableValueInterface { + public ttl: number; + public value: string | Record; + + public constructor(value: string | Record, maxAge: number) { + this.value = value; + const timeNow = new Date(); + this.ttl = timeNow.setSeconds(timeNow.getSeconds() + maxAge); + } + + public isExpired(): boolean { + return this.ttl < Date.now(); + } +} + +export { + ExpirableValue +}; \ No newline at end of file diff --git a/packages/parameters/src/GetMultipleOptions.ts b/packages/parameters/src/GetMultipleOptions.ts new file mode 100644 index 0000000000..5f03a0ee3e --- /dev/null +++ b/packages/parameters/src/GetMultipleOptions.ts @@ -0,0 +1,18 @@ +import { DEFAULT_MAX_AGE_SECS } from './constants'; +import type { GetMultipleOptionsInterface, TransformOptions } from './types'; + +class GetMultipleOptions implements GetMultipleOptionsInterface { + public forceFetch: boolean = false; + public maxAge: number = DEFAULT_MAX_AGE_SECS; + public sdkOptions?: unknown; + public throwOnTransformError: boolean = false; + public transform?: TransformOptions; + + public constructor(options: GetMultipleOptionsInterface) { + Object.assign(this, options); + } +} + +export { + GetMultipleOptions +}; \ No newline at end of file diff --git a/packages/parameters/src/GetOptions.ts b/packages/parameters/src/GetOptions.ts new file mode 100644 index 0000000000..807962a5aa --- /dev/null +++ b/packages/parameters/src/GetOptions.ts @@ -0,0 +1,17 @@ +import { DEFAULT_MAX_AGE_SECS } from './constants'; +import type { GetOptionsInterface, TransformOptions } from './types'; + +class GetOptions implements GetOptionsInterface { + public forceFetch: boolean = false; + public maxAge: number = DEFAULT_MAX_AGE_SECS; + public sdkOptions?: unknown; + public transform?: TransformOptions; + + public constructor(options: GetOptionsInterface = {}) { + Object.assign(this, options); + } +} + +export { + GetOptions +}; \ No newline at end of file diff --git a/packages/parameters/src/constants.ts b/packages/parameters/src/constants.ts new file mode 100644 index 0000000000..b9cd5beeb3 --- /dev/null +++ b/packages/parameters/src/constants.ts @@ -0,0 +1,3 @@ +export const DEFAULT_MAX_AGE_SECS = 5; +export const TRANSFORM_METHOD_JSON = 'json'; +export const TRANSFORM_METHOD_BINARY = 'binary'; \ No newline at end of file diff --git a/packages/parameters/src/index.ts b/packages/parameters/src/index.ts new file mode 100644 index 0000000000..4fdc288aba --- /dev/null +++ b/packages/parameters/src/index.ts @@ -0,0 +1,2 @@ +export * from './BaseProvider'; +export * from './Exceptions'; diff --git a/packages/parameters/src/types/BaseProvider.ts b/packages/parameters/src/types/BaseProvider.ts new file mode 100644 index 0000000000..8f6754d2d3 --- /dev/null +++ b/packages/parameters/src/types/BaseProvider.ts @@ -0,0 +1,34 @@ +type TransformOptions = 'auto' | 'binary' | 'json'; + +interface GetOptionsInterface { + maxAge?: number + forceFetch?: boolean + sdkOptions?: unknown + transform?: TransformOptions +} + +interface GetMultipleOptionsInterface { + maxAge?: number + forceFetch?: boolean + sdkOptions?: unknown + transform?: string + throwOnTransformError?: boolean +} + +interface ExpirableValueInterface { + value: string | Record + ttl: number +} + +interface BaseProviderInterface { + get(name: string, options?: GetOptionsInterface): Promise> + getMultiple(path: string, options?: GetMultipleOptionsInterface): Promise> +} + +export { + GetOptionsInterface, + GetMultipleOptionsInterface, + BaseProviderInterface, + ExpirableValueInterface, + TransformOptions, +}; \ No newline at end of file diff --git a/packages/parameters/src/types/index.ts b/packages/parameters/src/types/index.ts new file mode 100644 index 0000000000..73eb846a94 --- /dev/null +++ b/packages/parameters/src/types/index.ts @@ -0,0 +1 @@ +export * from './BaseProvider'; \ No newline at end of file diff --git a/packages/parameters/tests/helpers/populateEnvironmentVariables.ts b/packages/parameters/tests/helpers/populateEnvironmentVariables.ts new file mode 100644 index 0000000000..e808ac84b9 --- /dev/null +++ b/packages/parameters/tests/helpers/populateEnvironmentVariables.ts @@ -0,0 +1,9 @@ +// Reserved variables +process.env._X_AMZN_TRACE_ID = 'Root=1-5759e988-bd862e3fe1be46a994272793;Parent=557abcec3ee5a047;Sampled=1'; +process.env.AWS_LAMBDA_FUNCTION_NAME = 'my-lambda-function'; +process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE = '128'; +process.env.AWS_REGION = 'eu-west-1'; + +// Powertools variables +process.env.LOG_LEVEL = 'DEBUG'; +process.env.POWERTOOLS_SERVICE_NAME = 'hello-world'; diff --git a/packages/parameters/tests/unit/BaseProvider.test.ts b/packages/parameters/tests/unit/BaseProvider.test.ts new file mode 100644 index 0000000000..8b7e0e3f78 --- /dev/null +++ b/packages/parameters/tests/unit/BaseProvider.test.ts @@ -0,0 +1,436 @@ +/** + * Test BaseProvider class + * + * @group unit/parameters/baseProvider/class + */ + +import { BaseProvider, ExpirableValue, GetParameterError, TransformParameterError } from '../../src'; +import { toBase64 } from '@aws-sdk/util-base64-node'; + +const encoder = new TextEncoder(); + +describe('Class: BaseProvider', () => { + + afterEach(() => { + jest.clearAllMocks(); + }); + + class TestProvider extends BaseProvider { + public _add(key: string, value: ExpirableValue): void { + this.store.set(key, value); + } + + public _get(_name: string): Promise { + throw Error('Not implemented.'); + } + + public _getKeyTest(key: string): ExpirableValue | undefined { + return this.store.get(key); + } + + public _getMultiple(_path: string): Promise> { + throw Error('Not implemented.'); + } + + public _getStoreSize(): number { + return this.store.size; + } + } + + describe('Method: addToCache', () => { + + test('when called with a value and maxAge equal to 0, it skips the cache entirely', () => { + + // Prepare + const provider = new TestProvider(); + + // Act + provider.addToCache('my-key', 'value', 0); + + // Assess + expect(provider._getKeyTest('my-key')).toBeUndefined(); + + }); + + test('when called with a value and maxAge, it places the value in the cache', () => { + + // Prepare + const provider = new TestProvider(); + + // Act + provider.addToCache('my-key', 'my-value', 5000); + + // Assess + expect(provider._getKeyTest('my-key')).toEqual(expect.objectContaining({ + value: 'my-value' + })); + + }); + + }); + + describe('Method: get', () => { + + test('when the underlying _get method throws an error, it throws a GetParameterError', async () => { + + // Prepare + const provider = new TestProvider(); + + // Act / Assess + await expect(provider.get('my-parameter')).rejects.toThrowError(GetParameterError); + + }); + + test('when called and a cached value is available, it returns an the cached value', async () => { + + // Prepare + const provider = new TestProvider(); + provider._add([ 'my-parameter', undefined ].toString(), new ExpirableValue('my-value', 5000)); + + // Act + const values = await provider.get('my-parameter'); + + // Assess + expect(values).toEqual('my-value'); + + }); + + test('when called with forceFetch, even whith cached value available, it returns the remote value', async () => { + + // Prepare + const mockData = 'my-remote-value'; + const provider = new TestProvider(); + jest.spyOn(provider, '_get').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + provider._add([ 'my-parameter', undefined ].toString(), new ExpirableValue('my-value', 5000)); + + // Act + const values = await provider.get('my-parameter', { forceFetch: true }); + + // Assess + expect(values).toEqual('my-remote-value'); + + }); + + test('when called and values cached are expired, it returns the remote values', async () => { + + // Prepare + const mockData = 'my-remote-value'; + const provider = new TestProvider(); + jest.spyOn(provider, '_get').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + const expirableValue = new ExpirableValue('my-other-value', 0); + jest.spyOn(expirableValue, 'isExpired').mockImplementation(() => true); + provider._add([ 'my-path', undefined ].toString(), expirableValue); + + // Act + const values = await provider.get('my-parameter'); + + // Assess + expect(values).toEqual('my-remote-value'); + + }); + + test('when called with a json transform, and the value is a valid string representation of a JSON, it returns an object', async () => { + + // Prepare + const mockData = JSON.stringify({ foo: 'bar' }); + const provider = new TestProvider(); + jest.spyOn(provider, '_get').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + + // Act + const value = await provider.get('my-parameter', { transform: 'json' }); + + // Assess + expect(typeof value).toBe('object'); + expect(value).toMatchObject({ + 'foo': 'bar' + }); + + }); + + test('when called with a json transform, and the value is NOT a valid string representation of a JSON, it throws', async () => { + + // Prepare + const mockData = `${JSON.stringify({ foo: 'bar' })}{`; + const provider = new TestProvider(); + jest.spyOn(provider, '_get').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + + // Act / Assess + await expect(provider.get('my-parameter', { transform: 'json' })).rejects.toThrowError(TransformParameterError); + + }); + + test('when called with a binary transform, and the value is a valid string representation of a binary, it returns the decoded value', async () => { + // Prepare + const mockData = toBase64(encoder.encode('my-value')); + const provider = new TestProvider(); + jest.spyOn(provider, '_get').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + + // Act + const value = await provider.get('my-parameter', { transform: 'binary' }); + + // Assess + expect(typeof value).toBe('string'); + expect(value).toEqual('my-value'); + + }); + + test('when called with a binary transform, and the value is NOT a valid string representation of a binary, it throws', async () => { + + // Prepare + const mockData = 'qw'; + const provider = new TestProvider(); + jest.spyOn(provider, '_get').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + + // Act / Assess + await expect(provider.get('my-parameter', { transform: 'binary' })).rejects.toThrowError(TransformParameterError); + + }); + + }); + + describe('Method: getMultiple', () => { + test('when the underlying _getMultiple throws an error, it throws a GetParameterError', async () => { + + // Prepare + const provider = new TestProvider(); + jest.spyOn(provider, '_getMultiple').mockImplementation(() => new Promise((_resolve, reject) => reject(new Error('Some error.')))); + + // Act / Assess + await expect(provider.getMultiple('my-parameter')).rejects.toThrowError(GetParameterError); + + }); + + test('when called with a json transform, and all the values are a valid string representation of a JSON, it returns an object with all the values', async () => { + + // Prepare + const mockData = { 'A': JSON.stringify({ foo: 'bar' }) }; + const provider = new TestProvider(); + jest.spyOn(provider, '_getMultiple').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + + // Act + const values = await provider.getMultiple('my-path', { transform: 'json' }); + + // Assess + expect(typeof values).toBe('object'); + expect(values).toMatchObject({ + 'A': { + 'foo': 'bar' + } + }); + + }); + + test('when called, it returns an object with the values', async () => { + + // Prepare + const mockData = { 'A': 'foo', 'B': 'bar' }; + const provider = new TestProvider(); + jest.spyOn(provider, '_getMultiple').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + + // Act + const values = await provider.getMultiple('my-path'); + + // Assess + expect(typeof values).toBe('object'); + expect(values).toMatchObject({ + 'A': 'foo', + 'B': 'bar' + }); + + }); + + test('when called with a json transform, and one of the values is NOT a valid string representation of a JSON, it returns an object with partial failures', async () => { + + // Prepare + const mockData = { 'A': JSON.stringify({ foo: 'bar' }), 'B': `${JSON.stringify({ foo: 'bar' })}{` }; + const provider = new TestProvider(); + jest.spyOn(provider, '_getMultiple').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + + // Act + const values = await provider.getMultiple('my-path', { transform: 'json' }); + + // Assess + expect(typeof values).toBe('object'); + expect(values).toMatchObject({ + 'A': { + 'foo': 'bar' + }, + 'B': undefined + }); + + }); + + test('when called with a json transform and throwOnTransformError equal to TRUE, and at least ONE the values is NOT a valid string representation of a JSON, it throws', async () => { + + // Prepare + const mockData = { 'A': `${JSON.stringify({ foo: 'bar' })}{` }; + const provider = new TestProvider(); + jest.spyOn(provider, '_getMultiple').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + + // Act / Assess + await expect(provider.getMultiple('my-path', { transform: 'json', throwOnTransformError: true })).rejects.toThrowError(TransformParameterError); + + }); + + test('when called with a binary transform, and all the values are a valid string representation of a binary, it returns an object with all the values', async () => { + + // Prepare + const mockData = { 'A': toBase64(encoder.encode('my-value')).toString() }; + const provider = new TestProvider(); + jest.spyOn(provider, '_getMultiple').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + + // Act + const values = await provider.getMultiple('my-path', { transform: 'binary' }); + + // Assess + expect(typeof values).toBe('object'); + expect(values).toMatchObject({ + 'A': 'my-value' + }); + + }); + + test('when called with a binary transform, and one of the values is NOT a valid string representation of a binary, it returns an object with partial failures', async () => { + + // Prepare + const mockData = { 'A': toBase64(encoder.encode('my-value')), 'B': 'qw' }; + const provider = new TestProvider(); + jest.spyOn(provider, '_getMultiple').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + + // Act + const values = await provider.getMultiple('my-path', { transform: 'binary' }); + + // Assess + expect(typeof values).toBe('object'); + expect(values).toMatchObject({ + 'A': 'my-value', + 'B': undefined + }); + + }); + + test('when called with a binary transform and throwOnTransformError equal to TRUE, and at least ONE the values is NOT a valid string representation of a binary, it throws', async () => { + + // Prepare + const mockData = { 'A': 'qw' }; + const provider = new TestProvider(); + jest.spyOn(provider, '_getMultiple').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + + // Act / Assess + await expect(provider.getMultiple('my-path', { transform: 'binary', throwOnTransformError: true })).rejects.toThrowError(TransformParameterError); + + }); + + test('when called with auto transform and the key of the parameter ends with `.binary`, and all the values are a valid string representation of a binary, it returns an object with all the transformed values', async () => { + + // Prepare + const mockData = { 'A.binary': toBase64(encoder.encode('my-value')) }; + const provider = new TestProvider(); + jest.spyOn(provider, '_getMultiple').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + + // Act + const values = await provider.getMultiple('my-path', { transform: 'auto' }); + + // Assess + expect(typeof values).toBe('object'); + expect(values).toMatchObject({ + 'A.binary': 'my-value' + }); + + }); + + test('when called with auto transform and the key of the parameter DOES NOT end with `.binary` or `.json`, it returns an object with all the values NOT transformed', async () => { + + // Prepare + const mockBinary = toBase64(encoder.encode('my-value')); + const mockData = { 'A.foo': mockBinary }; + const provider = new TestProvider(); + jest.spyOn(provider, '_getMultiple').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + + // Act + const values = await provider.getMultiple('my-path', { transform: 'auto' }); + + // Assess + expect(typeof values).toBe('object'); + expect(values).toMatchObject({ + 'A.foo': mockBinary + }); + + }); + + test('when called with a binary transform, and at least ONE the values is undefined, it returns an object with one of the values undefined', async () => { + + // Prepare + const mockData = { 'A': undefined }; + const provider = new TestProvider(); + jest.spyOn(provider, '_getMultiple').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + + // Act + const values = await provider.getMultiple('my-path', { transform: 'auto' }); + + expect(typeof values).toBe('object'); + expect(values).toMatchObject({ + 'A': undefined + }); + + }); + + test('when called and values cached are available, it returns an object with the cached values', async () => { + + // Prepare + const provider = new TestProvider(); + provider._add([ 'my-path', undefined ].toString(), new ExpirableValue({ 'A': 'my-value' }, 60000)); + + // Act + const values = await provider.getMultiple('my-path'); + + // Assess + expect(typeof values).toBe('object'); + expect(values).toMatchObject({ + 'A': 'my-value', + }); + + }); + + test('when called and values cached are expired, it returns an object with the remote values', async () => { + + // Prepare + const mockData = { 'A': 'my-value' }; + const provider = new TestProvider(); + jest.spyOn(provider, '_getMultiple').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + const expirableValue = new ExpirableValue({ 'B': 'my-other-value' }, 0); + jest.spyOn(expirableValue, 'isExpired').mockImplementation(() => true); + provider._add([ 'my-path', undefined ].toString(), expirableValue); + + // Act + const values = await provider.getMultiple('my-path'); + + // Assess + expect(typeof values).toBe('object'); + expect(values).toMatchObject({ + 'A': 'my-value', + }); + + }); + + }); + + describe('Method: clearCache', () => { + + test('when called, it clears the store', () => { + + // Prepare + const provider = new TestProvider(); + provider._add([ 'my-path', undefined ].toString(), new ExpirableValue({ 'B': 'my-other-value' }, 0)); + + // Act + provider.clearCache(); + + // Assess + expect(provider._getStoreSize()).toBe(0); + + }); + + }); + +}); \ No newline at end of file diff --git a/packages/parameters/tsconfig-dev.json b/packages/parameters/tsconfig-dev.json new file mode 100644 index 0000000000..7c6046c8bc --- /dev/null +++ b/packages/parameters/tsconfig-dev.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "experimentalDecorators": true, + "noImplicitAny": true, + "target": "ES2019", + "module": "commonjs", + "declaration": true, + "declarationMap": true, + "outDir": "lib", + "strict": true, + "inlineSourceMap": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "pretty": true, + "baseUrl": "src/", + "rootDirs": [ "src/" ] + }, + "include": [ "src/**/*", "examples/**/*", "**/tests/**/*" ], + "exclude": [ "./node_modules"], + "watchOptions": { + "watchFile": "useFsEvents", + "watchDirectory": "useFsEvents", + "fallbackPolling": "dynamicPriority" + }, + "lib": [ "es2019" ], + "types": [ + "jest", + "node" + ] +} \ No newline at end of file diff --git a/packages/parameters/tsconfig.es.json b/packages/parameters/tsconfig.es.json new file mode 100644 index 0000000000..7c6046c8bc --- /dev/null +++ b/packages/parameters/tsconfig.es.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "experimentalDecorators": true, + "noImplicitAny": true, + "target": "ES2019", + "module": "commonjs", + "declaration": true, + "declarationMap": true, + "outDir": "lib", + "strict": true, + "inlineSourceMap": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "pretty": true, + "baseUrl": "src/", + "rootDirs": [ "src/" ] + }, + "include": [ "src/**/*", "examples/**/*", "**/tests/**/*" ], + "exclude": [ "./node_modules"], + "watchOptions": { + "watchFile": "useFsEvents", + "watchDirectory": "useFsEvents", + "fallbackPolling": "dynamicPriority" + }, + "lib": [ "es2019" ], + "types": [ + "jest", + "node" + ] +} \ No newline at end of file diff --git a/packages/parameters/tsconfig.json b/packages/parameters/tsconfig.json new file mode 100644 index 0000000000..3d7d8b8b05 --- /dev/null +++ b/packages/parameters/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "experimentalDecorators": true, + "noImplicitAny": true, + "target": "ES2019", + "module": "commonjs", + "declaration": true, + "outDir": "lib", + "strict": true, + "inlineSourceMap": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "pretty": true, + "baseUrl": "src/", + "rootDirs": [ "src/" ], + "esModuleInterop": true + }, + "include": [ "src/**/*" ], + "exclude": [ "./node_modules"], + "watchOptions": { + "watchFile": "useFsEvents", + "watchDirectory": "useFsEvents", + "fallbackPolling": "dynamicPriority" + }, + "lib": [ "es2019" ], + "types": [ + "jest", + "node" + ] +} \ No newline at end of file From cb9eaf963c496e48dd47fe04e34e12c4cff13eaa Mon Sep 17 00:00:00 2001 From: Florian Chazal Date: Wed, 23 Nov 2022 16:03:02 +0100 Subject: [PATCH 11/15] chore(layer): reduce size by 3 by removing @types packages (#1181) Co-authored-by: Florian Chazal --- layer-publisher/src/powertools-typescript-layer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/layer-publisher/src/powertools-typescript-layer.ts b/layer-publisher/src/powertools-typescript-layer.ts index 029925aa65..df0e5b01a2 100644 --- a/layer-publisher/src/powertools-typescript-layer.ts +++ b/layer-publisher/src/powertools-typescript-layer.ts @@ -30,6 +30,7 @@ export class PowerToolsTypeScriptLayer extends lambda.LayerVersion { @aws-lambda-powertools/logger@${version} \ @aws-lambda-powertools/metrics@${version} \ @aws-lambda-powertools/tracer@${version}`, + 'rm -rf node_modules/@types', 'rm package.json package-lock.json', ]; const commandJoined = commands.join(' && '); From cb9137436cc3a10d6c869506ddd07e35963ba8b2 Mon Sep 17 00:00:00 2001 From: Sergei Cherniaev Date: Wed, 23 Nov 2022 19:17:49 +0400 Subject: [PATCH 12/15] fix(logger): merge child logger options correctly (#1178) * bug(logger): fix bug when child logger with overwritten options clears all other options * test(logger): add tests for createChild method * refactor(logger): remove clonedeep dependency * refactor(logger): move initOptions initializer to setOptions * feat(logger): add addPersistentLogAttributes from parent to child logger * test(logger): add tests for createChild with persistent log attrs * feat(logger): added addContext support, remove redundant initOption * refactor(logger): add parent's context to child explicitly, without merging --- package-lock.json | 48 +--- packages/logger/package.json | 2 - packages/logger/src/Logger.ts | 14 +- packages/logger/tests/unit/Logger.test.ts | 294 ++++++++++++++++++++- packages/logger/tests/unit/helpers.test.ts | 2 +- 5 files changed, 312 insertions(+), 48 deletions(-) diff --git a/package-lock.json b/package-lock.json index e743677bb0..8413791a0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3915,15 +3915,6 @@ "integrity": "sha512-XOKXa1KIxtNXgASAnwj7cnttJxS4fksBRywK/9LzRV5YxrF80BXZIGeQSuoESQ/VkUj30Ae0+YcuHc15wJCB2g==", "dev": true }, - "node_modules/@types/lodash.clonedeep": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.7.tgz", - "integrity": "sha512-ccNqkPptFIXrpVqUECi60/DFxjNKsfoQxSQsgcBJCX/fuX1wgyQieojkcWH/KpE3xzLoWN/2k+ZeGqIN3paSvw==", - "dev": true, - "dependencies": { - "@types/lodash": "*" - } - }, "node_modules/@types/lodash.merge": { "version": "4.6.7", "resolved": "https://registry.npmjs.org/@types/lodash.merge/-/lodash.merge-4.6.7.tgz", @@ -11059,11 +11050,6 @@ "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", "dev": true }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" - }, "node_modules/lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -15542,28 +15528,30 @@ }, "packages/commons": { "name": "@aws-lambda-powertools/commons", - "version": "1.4.0", + "version": "1.4.1", "license": "MIT-0" }, "packages/logger": { "name": "@aws-lambda-powertools/logger", - "version": "1.4.0", + "version": "1.4.1", "license": "MIT", "dependencies": { - "lodash.clonedeep": "^4.5.0", + "@aws-lambda-powertools/commons": "^1.4.1", "lodash.merge": "^4.6.2", "lodash.pickby": "^4.6.0" }, "devDependencies": { - "@types/lodash.clonedeep": "^4.5.7", "@types/lodash.merge": "^4.6.7", "@types/lodash.pickby": "^4.6.7" } }, "packages/metrics": { "name": "@aws-lambda-powertools/metrics", - "version": "1.4.0", + "version": "1.4.1", "license": "MIT-0", + "dependencies": { + "@aws-lambda-powertools/commons": "^1.4.1" + }, "devDependencies": { "@types/promise-retry": "^1.1.3", "promise-retry": "^2.0.1" @@ -15571,9 +15559,10 @@ }, "packages/tracer": { "name": "@aws-lambda-powertools/tracer", - "version": "1.4.0", + "version": "1.4.1", "license": "MIT-0", "dependencies": { + "@aws-lambda-powertools/commons": "^1.4.1", "aws-xray-sdk-core": "^3.3.6" }, "devDependencies": { @@ -15800,10 +15789,9 @@ "@aws-lambda-powertools/logger": { "version": "file:packages/logger", "requires": { - "@types/lodash.clonedeep": "^4.5.7", + "@aws-lambda-powertools/commons": "^1.4.1", "@types/lodash.merge": "^4.6.7", "@types/lodash.pickby": "^4.6.7", - "lodash.clonedeep": "^4.5.0", "lodash.merge": "^4.6.2", "lodash.pickby": "^4.6.0" } @@ -15811,6 +15799,7 @@ "@aws-lambda-powertools/metrics": { "version": "file:packages/metrics", "requires": { + "@aws-lambda-powertools/commons": "^1.4.1", "@types/promise-retry": "^1.1.3", "promise-retry": "^2.0.1" } @@ -15818,6 +15807,7 @@ "@aws-lambda-powertools/tracer": { "version": "file:packages/tracer", "requires": { + "@aws-lambda-powertools/commons": "^1.4.1", "@aws-sdk/client-dynamodb": "^3.100.0", "@types/promise-retry": "^1.1.3", "aws-xray-sdk-core": "^3.3.6", @@ -18772,15 +18762,6 @@ "integrity": "sha512-XOKXa1KIxtNXgASAnwj7cnttJxS4fksBRywK/9LzRV5YxrF80BXZIGeQSuoESQ/VkUj30Ae0+YcuHc15wJCB2g==", "dev": true }, - "@types/lodash.clonedeep": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.7.tgz", - "integrity": "sha512-ccNqkPptFIXrpVqUECi60/DFxjNKsfoQxSQsgcBJCX/fuX1wgyQieojkcWH/KpE3xzLoWN/2k+ZeGqIN3paSvw==", - "dev": true, - "requires": { - "@types/lodash": "*" - } - }, "@types/lodash.merge": { "version": "4.6.7", "resolved": "https://registry.npmjs.org/@types/lodash.merge/-/lodash.merge-4.6.7.tgz", @@ -24326,11 +24307,6 @@ "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", "dev": true }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" - }, "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", diff --git a/packages/logger/package.json b/packages/logger/package.json index 84478e69b9..211969e096 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -32,7 +32,6 @@ "types": "./lib/index.d.ts", "typedocMain": "src/index.ts", "devDependencies": { - "@types/lodash.clonedeep": "^4.5.7", "@types/lodash.merge": "^4.6.7", "@types/lodash.pickby": "^4.6.7" }, @@ -48,7 +47,6 @@ }, "dependencies": { "@aws-lambda-powertools/commons": "^1.4.1", - "lodash.clonedeep": "^4.5.0", "lodash.merge": "^4.6.2", "lodash.pickby": "^4.6.0" }, diff --git a/packages/logger/src/Logger.ts b/packages/logger/src/Logger.ts index 57b9ba1a57..6a57f92eb4 100644 --- a/packages/logger/src/Logger.ts +++ b/packages/logger/src/Logger.ts @@ -3,7 +3,6 @@ import type { Context, Handler } from 'aws-lambda'; import { Utility } from '@aws-lambda-powertools/commons'; import { LogFormatterInterface, PowertoolLogFormatter } from './formatter'; import { LogItem } from './log'; -import cloneDeep from 'lodash.clonedeep'; import merge from 'lodash.merge'; import { ConfigServiceInterface, EnvironmentVariablesService } from './config'; import { LogJsonIndent } from './types'; @@ -151,7 +150,6 @@ class Logger extends Utility implements ClassThatLogs { */ public constructor(options: ConstructorOptions = {}) { super(); - this.setOptions(options); } @@ -205,7 +203,17 @@ class Logger extends Utility implements ClassThatLogs { * @returns {Logger} */ public createChild(options: ConstructorOptions = {}): Logger { - return cloneDeep(this).setOptions(options); + const parentsPowertoolsLogData = this.getPowertoolLogData(); + const childLogger = new Logger(merge({}, parentsPowertoolsLogData, options)); + + const parentsPersistentLogAttributes = this.getPersistentLogAttributes(); + childLogger.addPersistentLogAttributes(parentsPersistentLogAttributes); + + if (parentsPowertoolsLogData.lambdaContext) { + childLogger.addContext(parentsPowertoolsLogData.lambdaContext as Context); + } + + return childLogger; } /** diff --git a/packages/logger/tests/unit/Logger.test.ts b/packages/logger/tests/unit/Logger.test.ts index e5fe9b39b7..03d7c7ae9b 100644 --- a/packages/logger/tests/unit/Logger.test.ts +++ b/packages/logger/tests/unit/Logger.test.ts @@ -1236,6 +1236,107 @@ describe('Class: Logger', () => { describe('Method: createChild', () => { + test('Child and grandchild loggers should have all ancestor\'s options', () => { + // Prepare + const INDENTATION = LogJsonIndent.COMPACT; + const loggerOptions = { + serviceName: 'parent-service-name', + sampleRateValue: 0.01, + }; + const parentLogger = new Logger(loggerOptions); + + // Act + const childLoggerOptions = { sampleRateValue: 1 }; + const childLogger = parentLogger.createChild(childLoggerOptions); + + const grandchildLoggerOptions = { serviceName: 'grandchild-logger-name' }; + const grandchildLogger = childLogger.createChild(grandchildLoggerOptions); + + // Assess + expect(parentLogger === childLogger).toBe(false); + expect(childLogger === grandchildLogger).toBe(false); + expect(parentLogger === grandchildLogger).toBe(false); + + expect(parentLogger).toEqual({ + console: expect.any(Console), + coldStart: true, + customConfigService: undefined, + defaultServiceName: 'service_undefined', + envVarsService: expect.any(EnvironmentVariablesService), + logEvent: false, + logIndentation: INDENTATION, + logFormatter: expect.any(PowertoolLogFormatter), + logLevel: 'DEBUG', + logLevelThresholds: { + DEBUG: 8, + ERROR: 20, + INFO: 12, + WARN: 16, + }, + logsSampled: false, + persistentLogAttributes: {}, + powertoolLogData: { + awsRegion: 'eu-west-1', + environment: '', + sampleRateValue: 0.01, + serviceName: 'parent-service-name', + }, + }); + + expect(childLogger).toEqual({ + console: expect.any(Console), + coldStart: true, + customConfigService: undefined, + defaultServiceName: 'service_undefined', + envVarsService: expect.any(EnvironmentVariablesService), + logEvent: false, + logIndentation: INDENTATION, + logFormatter: expect.any(PowertoolLogFormatter), + logLevel: 'DEBUG', + logLevelThresholds: { + DEBUG: 8, + ERROR: 20, + INFO: 12, + WARN: 16, + }, + logsSampled: true, + persistentLogAttributes: {}, + powertoolLogData: { + awsRegion: 'eu-west-1', + environment: '', + sampleRateValue: 1, + serviceName: 'parent-service-name', + }, + }); + + expect(grandchildLogger).toEqual({ + console: expect.any(Console), + coldStart: true, + customConfigService: undefined, + defaultServiceName: 'service_undefined', + envVarsService: expect.any(EnvironmentVariablesService), + logEvent: false, + logIndentation: INDENTATION, + logFormatter: expect.any(PowertoolLogFormatter), + logLevel: 'DEBUG', + logLevelThresholds: { + DEBUG: 8, + ERROR: 20, + INFO: 12, + WARN: 16, + }, + logsSampled: true, + persistentLogAttributes: {}, + powertoolLogData: { + awsRegion: 'eu-west-1', + environment: '', + sampleRateValue: 1, + serviceName: 'grandchild-logger-name', + }, + }); + + }); + test('when called, it returns a DISTINCT clone of the logger instance', () => { // Prepare @@ -1244,17 +1345,23 @@ describe('Class: Logger', () => { // Act const childLogger = parentLogger.createChild(); - const childLoggerWithPermanentAttributes = parentLogger.createChild({ + + const optionsWithPermanentAttributes = { persistentLogAttributes: { extra: 'This is an attribute that will be logged only by the child logger', }, - }); - const childLoggerWithSampleRateEnabled = parentLogger.createChild({ + }; + const childLoggerWithPermanentAttributes = parentLogger.createChild(optionsWithPermanentAttributes); + + const optionsWithSampleRateEnabled = { sampleRateValue: 1, // 100% probability to make sure that the logs are sampled - }); - const childLoggerWithErrorLogLevel = parentLogger.createChild({ + }; + const childLoggerWithSampleRateEnabled = parentLogger.createChild(optionsWithSampleRateEnabled); + + const optionsWithErrorLogLevel = { logLevel: 'ERROR', - }); + }; + const childLoggerWithErrorLogLevel = parentLogger.createChild(optionsWithErrorLogLevel); // Assess expect(parentLogger === childLogger).toBe(false); @@ -1374,6 +1481,181 @@ describe('Class: Logger', () => { }); + test('child logger should have parent\'s keys in persistentLogAttributes', () => { + + // Prepare + const INDENTATION = LogJsonIndent.COMPACT; + const parentLogger = new Logger(); + const childLogger = parentLogger.createChild(); + + // Act + parentLogger.appendKeys({ + aws_account_id: '123456789012', + aws_region: 'eu-west-1', + logger: { + name: 'aws-lambda-powertool-typescript', + version: '0.2.4', + }, + test_key: 'key-for-test' + }); + const childLoggerWithKeys = parentLogger.createChild(); + childLoggerWithKeys.removeKeys(['test_key']); + + // Assess + expect(childLogger).toEqual({ + console: expect.any(Console), + coldStart: true, + customConfigService: undefined, + defaultServiceName: 'service_undefined', + envVarsService: expect.any(EnvironmentVariablesService), + logEvent: false, + logIndentation: INDENTATION, + logFormatter: expect.any(PowertoolLogFormatter), + logLevel: 'DEBUG', + logLevelThresholds: { + DEBUG: 8, + ERROR: 20, + INFO: 12, + WARN: 16, + }, + logsSampled: false, + persistentLogAttributes: {}, + powertoolLogData: { + awsRegion: 'eu-west-1', + environment: '', + sampleRateValue: undefined, + serviceName: 'hello-world', + }, + }); + + expect(childLoggerWithKeys).toEqual({ + console: expect.any(Console), + coldStart: true, + customConfigService: undefined, + defaultServiceName: 'service_undefined', + envVarsService: expect.any(EnvironmentVariablesService), + logEvent: false, + logIndentation: INDENTATION, + logFormatter: expect.any(PowertoolLogFormatter), + logLevel: 'DEBUG', + logLevelThresholds: { + DEBUG: 8, + ERROR: 20, + INFO: 12, + WARN: 16, + }, + logsSampled: false, + persistentLogAttributes: { + aws_account_id: '123456789012', + aws_region: 'eu-west-1', + logger: { + name: 'aws-lambda-powertool-typescript', + version: '0.2.4', + }, + }, + powertoolLogData: { + awsRegion: 'eu-west-1', + environment: '', + sampleRateValue: undefined, + serviceName: 'hello-world', + }, + }); + + expect(parentLogger).toEqual({ + console: expect.any(Console), + coldStart: true, + customConfigService: undefined, + defaultServiceName: 'service_undefined', + envVarsService: expect.any(EnvironmentVariablesService), + logEvent: false, + logIndentation: INDENTATION, + logFormatter: expect.any(PowertoolLogFormatter), + logLevel: 'DEBUG', + logLevelThresholds: { + DEBUG: 8, + ERROR: 20, + INFO: 12, + WARN: 16, + }, + logsSampled: false, + persistentLogAttributes: { + aws_account_id: '123456789012', + aws_region: 'eu-west-1', + logger: { + name: 'aws-lambda-powertool-typescript', + version: '0.2.4', + }, + test_key: 'key-for-test', + }, + powertoolLogData: { + awsRegion: 'eu-west-1', + environment: '', + sampleRateValue: undefined, + serviceName: 'hello-world', + }, + }); + }); + + const context = { + callbackWaitsForEmptyEventLoop: true, + functionVersion: '$LATEST', + functionName: 'foo-bar-function-with-cold-start', + memoryLimitInMB: '128', + logGroupName: '/aws/lambda/foo-bar-function-with-cold-start', + logStreamName: '2021/03/09/[$LATEST]1-5759e988-bd862e3fe1be46a994272793', + invokedFunctionArn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function-with-cold-start', + awsRequestId: 'c6af9ac6-7b61-11e6-9a41-93e812345678', + getRemainingTimeInMillis: () => 1234, + done: () => console.log('Done!'), + fail: () => console.log('Failed!'), + succeed: () => console.log('Succeeded!'), + }; + + test('child logger should have parent\'s context in PowertoolLogData', () => { + + // Prepare + const parentLogger = new Logger(); + + // Act + parentLogger.addContext(context); + const childLoggerWithContext = parentLogger.createChild(); + + // Assess + expect(childLoggerWithContext).toEqual({ + console: expect.any(Console), + coldStart: false, // This is now false because the `coldStart` attribute has been already accessed once by the `addContext` method + customConfigService: undefined, + defaultServiceName: 'service_undefined', + envVarsService: expect.any(EnvironmentVariablesService), + logEvent: false, + logIndentation: 0, + logFormatter: expect.any(PowertoolLogFormatter), + logLevel: 'DEBUG', + logLevelThresholds: { + DEBUG: 8, + ERROR: 20, + INFO: 12, + WARN: 16, + }, + logsSampled: false, + persistentLogAttributes: {}, + powertoolLogData: { + awsRegion: 'eu-west-1', + environment: '', + lambdaContext: { + awsRequestId: 'c6af9ac6-7b61-11e6-9a41-93e812345678', + coldStart: true, + functionName: 'foo-bar-function-with-cold-start', + functionVersion: '$LATEST', + invokedFunctionArn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function-with-cold-start', + memoryLimitInMB: 128, + }, + sampleRateValue: undefined, + serviceName: 'hello-world', + }, + }); + }); + }); describe('Method: logEventIfEnabled', () => { diff --git a/packages/logger/tests/unit/helpers.test.ts b/packages/logger/tests/unit/helpers.test.ts index 3a4298421b..4f953620fc 100644 --- a/packages/logger/tests/unit/helpers.test.ts +++ b/packages/logger/tests/unit/helpers.test.ts @@ -230,7 +230,7 @@ describe('Helper: createLogger function', () => { test('when no log level is set, returns a Logger instance with INFO level', () => { // Prepare - const loggerOptions:ConstructorOptions = {}; + const loggerOptions: ConstructorOptions = {}; delete process.env.LOG_LEVEL; // Act From 34ef180604d2df53694b416e8350fb579038227a Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 24 Nov 2022 10:43:10 +0100 Subject: [PATCH 13/15] tests(idempotency): add utility to workspace unit tests and CI (#1160) * chore: fix package.lock & package * ci: add idempotency to ci checks on PRs * refactor: minor styling changes to folder structure, exports, types * test: unit tests style + added missing * chore: temporarily excluded makeFunctionIdempotent from coverage report * refactor: rename npx DynamoDbPersistenceLayer to DynamoDBPersistenceLayer * refactor: rename npx DynamoDbPersistenceLayer to DynamoDBPersistenceLayer * refactor: rename npx DynamoDbPersistenceLayer to DynamoDBPersistenceLayer * chore: revert changes to PersistenceLayerInterface * Trigger Build * Update packages/idempotency/src/persistence/PersistenceLayer.ts Co-authored-by: ijemmy Co-authored-by: ijemmy --- ...sable-run-linting-check-and-unit-tests.yml | 6 +- package-lock.json | 1021 ++++++++++++++--- package.json | 5 +- packages/idempotency/jest.config.js | 6 +- packages/idempotency/package.json | 16 +- .../src/EnvironmentVariablesService.ts | 25 - .../src/config/ConfigServiceInterface.ts | 13 + .../src/config/EnvironmentVariablesService.ts | 36 + packages/idempotency/src/config/index.ts | 1 + .../idempotency/src/makeFunctionIdempotent.ts | 7 +- ...ceLayer.ts => DynamoDBPersistenceLayer.ts} | 5 +- .../src/persistence/PersistenceLayer.ts | 29 +- .../persistence/PersistenceLayerInterface.ts | 3 +- packages/idempotency/src/persistence/index.ts | 2 +- .../DynamoPersistenceConstructorOptions.ts | 2 +- .../src/{ => types}/IdempotencyOptions.ts | 2 +- .../idempotency/src/types/PersistenceLayer.ts | 7 + packages/idempotency/src/types/index.ts | 3 +- .../helpers/populateEnvironmentVariables.ts | 7 +- .../unit/EnvironmentVariableService.test.ts | 34 - .../config/EnvironmentVariableService.test.ts | 50 + .../DynamoDbPersistenceLayer.test.ts | 211 ++-- .../persistence/IdempotencyRecord.test.ts | 7 +- .../unit/persistence/PersistenceLayer.test.ts | 229 ++-- packages/parameters/package.json | 7 +- 25 files changed, 1290 insertions(+), 444 deletions(-) delete mode 100644 packages/idempotency/src/EnvironmentVariablesService.ts create mode 100644 packages/idempotency/src/config/ConfigServiceInterface.ts create mode 100644 packages/idempotency/src/config/EnvironmentVariablesService.ts create mode 100644 packages/idempotency/src/config/index.ts rename packages/idempotency/src/persistence/{DynamoDbPersistenceLayer.ts => DynamoDBPersistenceLayer.ts} (95%) rename packages/idempotency/src/{ => types}/IdempotencyOptions.ts (65%) create mode 100644 packages/idempotency/src/types/PersistenceLayer.ts delete mode 100644 packages/idempotency/tests/unit/EnvironmentVariableService.test.ts create mode 100644 packages/idempotency/tests/unit/config/EnvironmentVariableService.test.ts diff --git a/.github/workflows/reusable-run-linting-check-and-unit-tests.yml b/.github/workflows/reusable-run-linting-check-and-unit-tests.yml index ce7c0e1996..637b2d5d55 100644 --- a/.github/workflows/reusable-run-linting-check-and-unit-tests.yml +++ b/.github/workflows/reusable-run-linting-check-and-unit-tests.yml @@ -42,11 +42,11 @@ jobs: if: steps.cache-node-modules.outputs.cache-hit == 'true' run: | npm run build -w packages/commons - npm run build -w packages/logger & npm run build -w packages/tracer & npm run build -w packages/metrics -w packages/parameters + npm run build -w packages/logger & npm run build -w packages/tracer & npm run build -w packages/metrics & npm run build -w packages/parameters & npm run build -w packages/idempotency - name: Run linting - run: npm run lint -w packages/commons -w packages/logger -w packages/tracer -w packages/metrics -w packages/parameters + run: npm run lint -w packages/commons -w packages/logger -w packages/tracer -w packages/metrics -w packages/parameters -w packages/idempotency - name: Run unit tests - run: npm t -w packages/commons -w packages/logger -w packages/tracer -w packages/metrics -w packages/parameters + run: npm t -w packages/commons -w packages/logger -w packages/tracer -w packages/metrics -w packages/parameters -w packages/idempotency check-examples: runs-on: ubuntu-latest env: diff --git a/package-lock.json b/package-lock.json index 8413791a0d..efb29753bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,9 @@ "packages/commons", "packages/logger", "packages/metrics", - "packages/tracer" + "packages/tracer", + "packages/parameters", + "packages/idempotency" ], "dependencies": { "hosted-git-info": "^5.0.0" @@ -217,7 +219,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-2.0.0.tgz", "integrity": "sha512-pkVXf/dq6PITJ0jzYZ69VhL8VFOFoPZLZqtU/12SGnzYuJOOGNfF41q9GxdI1yqC8R13Rq3jOLKDFpUJFT5eTA==", - "dev": true, "dependencies": { "tslib": "^1.11.1" } @@ -225,14 +226,12 @@ "node_modules/@aws-crypto/ie11-detection/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@aws-crypto/sha256-browser": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-2.0.0.tgz", "integrity": "sha512-rYXOQ8BFOaqMEHJrLHul/25ckWH6GTJtdLSajhlqGMx0PmSueAuvboCuZCTqEKlxR8CQOwRarxYMZZSYlhRA1A==", - "dev": true, "dependencies": { "@aws-crypto/ie11-detection": "^2.0.0", "@aws-crypto/sha256-js": "^2.0.0", @@ -247,14 +246,12 @@ "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@aws-crypto/sha256-js": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-2.0.0.tgz", "integrity": "sha512-VZY+mCY4Nmrs5WGfitmNqXzaE873fcIZDu54cbaDaaamsaTOP1DBImV9F4pICc3EHjQXujyE8jig+PFCaew9ig==", - "dev": true, "dependencies": { "@aws-crypto/util": "^2.0.0", "@aws-sdk/types": "^3.1.0", @@ -264,14 +261,12 @@ "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@aws-crypto/supports-web-crypto": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-2.0.0.tgz", "integrity": "sha512-Ge7WQ3E0OC7FHYprsZV3h0QIcpdyJLvIeg+uTuHqRYm8D6qCFJoiC+edSzSyFiHtZf+NOQDJ1q46qxjtzIY2nA==", - "dev": true, "dependencies": { "tslib": "^1.11.1" } @@ -279,14 +274,12 @@ "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@aws-crypto/util": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-2.0.1.tgz", "integrity": "sha512-JJmFFwvbm08lULw4Nm5QOLg8+lAQeC8aCXK5xrtxntYzYXCGfHwUJ4Is3770Q7HmICsXthGQ+ZsDL7C2uH3yBQ==", - "dev": true, "dependencies": { "@aws-sdk/types": "^3.1.0", "@aws-sdk/util-utf8-browser": "^3.0.0", @@ -296,13 +289,16 @@ "node_modules/@aws-crypto/util/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@aws-lambda-powertools/commons": { "resolved": "packages/commons", "link": true }, + "node_modules/@aws-lambda-powertools/idempotency": { + "resolved": "packages/idempotency", + "link": true + }, "node_modules/@aws-lambda-powertools/logger": { "resolved": "packages/logger", "link": true @@ -311,6 +307,10 @@ "resolved": "packages/metrics", "link": true }, + "node_modules/@aws-lambda-powertools/parameters": { + "resolved": "packages/parameters", + "link": true + }, "node_modules/@aws-lambda-powertools/tracer": { "resolved": "packages/tracer", "link": true @@ -319,7 +319,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.110.0.tgz", "integrity": "sha512-zok/WEVuK7Jh6V9YeA56pNZtxUASon9LTkS7vE65A4UFmNkPGNBCNgoiBcbhWfxwrZ8wtXcQk6rtUut39831mA==", - "dev": true, "dependencies": { "@aws-sdk/types": "3.110.0", "tslib": "^2.3.1" @@ -332,7 +331,6 @@ "version": "3.113.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.113.0.tgz", "integrity": "sha512-tp0/smnSBqoPdSbZqRaH46S9cDkOMXsbG8Qph+gV3+SL+V3nJLeGFzA+3WPmF0l0Bnv0/9Tms4ySZB0MsBSy/A==", - "dev": true, "dependencies": { "@aws-crypto/sha256-browser": "2.0.0", "@aws-crypto/sha256-js": "2.0.0", @@ -380,7 +378,6 @@ "version": "3.112.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.112.0.tgz", "integrity": "sha512-FwFmiapxuVQiyMdDaBvCpajnJkVWEUHBdO+7rIpzgKHkODEPou5/AwboaGRPEFYULOyYeI0HiDFzpK0G6de+7Q==", - "dev": true, "dependencies": { "@aws-crypto/sha256-browser": "2.0.0", "@aws-crypto/sha256-js": "2.0.0", @@ -422,7 +419,6 @@ "version": "3.112.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.112.0.tgz", "integrity": "sha512-hSApRO2wg3jk9VRGM6SCZO3aFP7DKVSUqs6FrvlXlj+JU88ZKObjrGE61cCzXoD89Dh+b9t8A2T6W51Nzriaxw==", - "dev": true, "dependencies": { "@aws-crypto/sha256-browser": "2.0.0", "@aws-crypto/sha256-js": "2.0.0", @@ -469,7 +465,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.110.0.tgz", "integrity": "sha512-7VvtKy4CL63BAktQ2vgsjhWDSXpkXO5YdiI56LQnHztrvSuJBBaxJ7R1p/k0b2tEUhYKUziAIW8EKE/7EGPR4g==", - "dev": true, "dependencies": { "@aws-sdk/signature-v4": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -485,7 +480,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.110.0.tgz", "integrity": "sha512-oFU3IYk/Bl5tdsz1qigtm3I25a9cvXPqlE8VjYjxVDdLujF5zd/4HLbhP4GQWhpEwZmM1ijcSNfLcyywVevTZg==", - "dev": true, "dependencies": { "@aws-sdk/property-provider": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -499,7 +493,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.110.0.tgz", "integrity": "sha512-atl+7/dAB+8fG9XI2fYyCgXKYDbOzot65VAwis+14bOEUCVp7PCJifBEZ/L8GEq564p+Fa2p1IpV0wuQXxqFUQ==", - "dev": true, "dependencies": { "@aws-sdk/node-config-provider": "3.110.0", "@aws-sdk/property-provider": "3.110.0", @@ -515,7 +508,6 @@ "version": "3.112.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.112.0.tgz", "integrity": "sha512-ebgZ6/jZdTGHQ3zfq/ccmS+7YmLk6yUWHDmh69VK+B1Dd+S1jFwbD9EQ+pYWCp/gEl9F620NSwb6KghRylPWEQ==", - "dev": true, "dependencies": { "@aws-sdk/credential-provider-env": "3.110.0", "@aws-sdk/credential-provider-imds": "3.110.0", @@ -534,7 +526,6 @@ "version": "3.112.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.112.0.tgz", "integrity": "sha512-7txS7P3BAaU4cksFw/PnoVskVvO8h/TPvOl/BxFtCiUdwA6FRltLvBeMlN08fwUoqgM6z06q8areBdeDqCHOSw==", - "dev": true, "dependencies": { "@aws-sdk/credential-provider-env": "3.110.0", "@aws-sdk/credential-provider-imds": "3.110.0", @@ -555,7 +546,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.110.0.tgz", "integrity": "sha512-JJcZePvRTfQHYj/+EEY13yItnZH/e8exlARFUjN0L13UrgHpOJtDQBa+YBHXo6MbTFQh+re25z2kzc+zOYSMNQ==", - "dev": true, "dependencies": { "@aws-sdk/property-provider": "3.110.0", "@aws-sdk/shared-ini-file-loader": "3.110.0", @@ -570,7 +560,6 @@ "version": "3.112.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.112.0.tgz", "integrity": "sha512-b6rOrSXbNK3fGyPvNpyF5zdktmAoNOqHCTmFSUcxRxOipyRGb5JACsbjWthIQkpWkpNCT8GFNLEg9spXPFIdLA==", - "dev": true, "dependencies": { "@aws-sdk/client-sso": "3.112.0", "@aws-sdk/property-provider": "3.110.0", @@ -586,7 +575,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.110.0.tgz", "integrity": "sha512-e4e5u7v3fsUFZsMcFMhMy1NdJBQpunYcLwpYlszm3OEICwTTekQ+hVvnVRd134doHvzepE4yp9sAop0Cj+IRVQ==", - "dev": true, "dependencies": { "@aws-sdk/property-provider": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -600,7 +588,6 @@ "version": "3.55.0", "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.55.0.tgz", "integrity": "sha512-kxDoHFDuQwZEEUZRp+ZLOg68EXuKPzUN86DcpIZantDVcmu7MSPTbbQp9DZd8MnKVEKCP7Sop5f7zCqOPl3LXw==", - "dev": true, "dependencies": { "mnemonist": "0.38.3", "tslib": "^2.3.1" @@ -613,7 +600,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.110.0.tgz", "integrity": "sha512-vk+K4GeCZL2J2rtvKO+T0Q7i3MDpEGZBMg5K2tj9sMcEQwty0BF0aFnP7Eu2l4/Zif2z1mWuUFM2WcZI6DVnbw==", - "dev": true, "dependencies": { "@aws-sdk/protocol-http": "3.110.0", "@aws-sdk/querystring-builder": "3.110.0", @@ -626,7 +612,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.110.0.tgz", "integrity": "sha512-wakl+kP2O8wTGYiQ3InZy+CVfGrIpFfq9fo4zif9PZac0BbUbguUU1dkY34uZiaf+4o2/9MoDYrHU2HYeXKxWw==", - "dev": true, "dependencies": { "@aws-sdk/types": "3.110.0", "@aws-sdk/util-buffer-from": "3.55.0", @@ -640,7 +625,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.110.0.tgz", "integrity": "sha512-O8J1InmtJkoiUMbQDtxBfOzgigBp9iSVsNXQrhs2qHh3826cJOfE7NGT3u+NMw73Pk5j2cfmOh1+7k/76IqxOg==", - "dev": true, "dependencies": { "@aws-sdk/types": "3.110.0", "tslib": "^2.3.1" @@ -650,7 +634,6 @@ "version": "3.55.0", "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.55.0.tgz", "integrity": "sha512-NbiPHVYuPxdqdFd6FxzzN3H1BQn/iWA3ri3Ry7AyLeP/tGs1yzEWMwf8BN8TSMALI0GXT6Sh0GDWy3Ok5xB6DA==", - "dev": true, "dependencies": { "tslib": "^2.3.1" }, @@ -658,11 +641,27 @@ "node": ">= 12.0.0" } }, + "node_modules/@aws-sdk/lib-dynamodb": { + "version": "3.208.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.208.0.tgz", + "integrity": "sha512-Cr1eR7JqSe3wM3KQFrn0+ePwb5tjn9g+7q74zM19caBC3IvQUegmWnCV3gG0KlVo18U6veJiUSIDi3fZDAXSdQ==", + "dependencies": { + "@aws-sdk/util-dynamodb": "3.208.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.0.0", + "@aws-sdk/smithy-client": "^3.0.0", + "@aws-sdk/types": "^3.0.0" + } + }, "node_modules/@aws-sdk/middleware-content-length": { "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.110.0.tgz", "integrity": "sha512-hKU+zdqfAJQg22LXMVu/z35nNIHrVAKpVKPe9+WYVdL/Z7JKUPK7QymqKGOyDuDbzW6OxyulC1zKGEX12zGmdA==", - "dev": true, "dependencies": { "@aws-sdk/protocol-http": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -676,7 +675,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.110.0.tgz", "integrity": "sha512-GUnNcuEBqD21E8EgevTcaBXJGnH5dp7eDSfWceYJP+iSscFXLzFFOf1I6H0cwvfARVMHVvlFz0tQcojCSHUZig==", - "dev": true, "dependencies": { "@aws-sdk/config-resolver": "3.110.0", "@aws-sdk/endpoint-cache": "3.55.0", @@ -692,7 +690,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.110.0.tgz", "integrity": "sha512-/Cknn1vL2LTlclI0MX2RzmtdPlCJ5palCRXxm/mod1oHwg4oNTKRlUX3LUD+L8g7JuJ4h053Ch9KS/A0vanE5Q==", - "dev": true, "dependencies": { "@aws-sdk/protocol-http": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -706,7 +703,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.110.0.tgz", "integrity": "sha512-+pz+a+8dfTnzLj79nHrv3aONMp/N36/erMd+7JXeR84QEosVLrFBUwKA8x5x6O3s1iBbQzRKMYEIuja9xn1BPA==", - "dev": true, "dependencies": { "@aws-sdk/types": "3.110.0", "tslib": "^2.3.1" @@ -719,7 +715,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.110.0.tgz", "integrity": "sha512-Wav782zd7bcd1e6txRob76CDOdVOaUQ8HXoywiIm/uFrEEUZvhs2mgnXjVUVCMBUehdNgnL99z420aS13JeL/Q==", - "dev": true, "dependencies": { "@aws-sdk/protocol-http": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -733,7 +728,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.110.0.tgz", "integrity": "sha512-lwLAQQveCiUqymQvVYjCee6QOXw3Zqbc9yq+pxYdXbs1Cv1XMA6PeJeUU5r5KEVuSceBLyyrnl6E0R1l1om1MQ==", - "dev": true, "dependencies": { "@aws-sdk/protocol-http": "3.110.0", "@aws-sdk/service-error-classification": "3.110.0", @@ -750,7 +744,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.110.0.tgz", "integrity": "sha512-EjY/YFdlr5jECde6qIrTIyGBbn/34CKcQGKvmvRd31+3qaClIJLAwNuHfcVzWvCUGbAslsfvdbOpLju33pSQRA==", - "dev": true, "dependencies": { "@aws-sdk/middleware-signing": "3.110.0", "@aws-sdk/property-provider": "3.110.0", @@ -767,7 +760,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.110.0.tgz", "integrity": "sha512-brVupxgEAmcZ9cZvdHEH8zncjvGKIiud8pOe4fiimp5NpHmjBLew4jUbnOKNZNAjaidcKUtz//cxtutD6yXEww==", - "dev": true, "dependencies": { "@aws-sdk/types": "3.110.0", "tslib": "^2.3.1" @@ -780,7 +772,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.110.0.tgz", "integrity": "sha512-y6ZKrGYfgDlFMzWhZmoq5J1UctBgZOUvMmnU9sSeZ020IlEPiOxFMvR0Zu6TcYThp8uy3P0wyjQtGYeTl9Z/kA==", - "dev": true, "dependencies": { "@aws-sdk/property-provider": "3.110.0", "@aws-sdk/protocol-http": "3.110.0", @@ -796,7 +787,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.110.0.tgz", "integrity": "sha512-iaLHw6ctOuGa9UxNueU01Xes+15dR+mqioRpUOUZ9Zx+vhXVpD7C8lnNqhRnYeFXs10/rNIzASgsIrAHTlnlIQ==", - "dev": true, "dependencies": { "tslib": "^2.3.1" }, @@ -808,7 +798,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.110.0.tgz", "integrity": "sha512-Y6FgiZr99DilYq6AjeaaWcNwVlSQpNGKrILzvV4Tmz03OaBIspe4KL+8EZ2YA/sAu5Lpw80vItdezqDOwGAlnQ==", - "dev": true, "dependencies": { "@aws-sdk/protocol-http": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -822,7 +811,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.110.0.tgz", "integrity": "sha512-46p4dCPGYctuybTQTwLpjenA1QFHeyJw/OyggGbtUJUy+833+ldnAwcPVML2aXJKUKv3APGI8vq1kaloyNku3Q==", - "dev": true, "dependencies": { "@aws-sdk/property-provider": "3.110.0", "@aws-sdk/shared-ini-file-loader": "3.110.0", @@ -837,7 +825,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.110.0.tgz", "integrity": "sha512-/rP+hY516DpP8fZhwFW5xM/ElH0w6lxw/15VvZCoY5EnOLAF5XIsJdzscWPSEW2FHCylBM4SNrKhGar14BDXhA==", - "dev": true, "dependencies": { "@aws-sdk/abort-controller": "3.110.0", "@aws-sdk/protocol-http": "3.110.0", @@ -853,7 +840,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.110.0.tgz", "integrity": "sha512-7NkpmYeOkK3mhWBNU+/zSDqwzeaSPH1qrq4L//WV7WS/weYyE/jusQeZoOxVsuZQnQEXHt5O2hKVeUwShl12xA==", - "dev": true, "dependencies": { "@aws-sdk/types": "3.110.0", "tslib": "^2.3.1" @@ -866,7 +852,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.110.0.tgz", "integrity": "sha512-qdi2gCbJiyPyLn+afebPNp/5nVCRh1X7t7IRIFl3FHVEC+o54u/ojay/MLZ4M/+X9Fa4Zxsb0Wpp3T0xAHVDBg==", - "dev": true, "dependencies": { "@aws-sdk/types": "3.110.0", "tslib": "^2.3.1" @@ -879,7 +864,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.110.0.tgz", "integrity": "sha512-7V3CDXj519izmbBn9ZE68ymASwGriA+Aq+cb/yHSVtffnvXjPtvONNw7G/5iVblisGLSCUe2hSvpYtcaXozbHw==", - "dev": true, "dependencies": { "@aws-sdk/types": "3.110.0", "@aws-sdk/util-uri-escape": "3.55.0", @@ -893,7 +877,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.110.0.tgz", "integrity": "sha512-//pJHH7hrhdDMZGBPKXKymmC/tJM7gFT0w/qbu/yd3Wm4W2fMB+8gkmj6EZctx7jrsWlfRQuvFejKqEfapur/g==", - "dev": true, "dependencies": { "@aws-sdk/types": "3.110.0", "tslib": "^2.3.1" @@ -914,7 +897,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.110.0.tgz", "integrity": "sha512-E1ERoqEoG206XNBYWCKLgHkzCbTxdpDEGbsLET2DnvjFsT0s9p2dPvVux3bYl7JVAhyGduE+qcqWk7MzhFCBNQ==", - "dev": true, "dependencies": { "tslib": "^2.3.1" }, @@ -926,7 +908,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.110.0.tgz", "integrity": "sha512-utxxdllOnmQDhbpipnFAbuQ4c2pwefZ+2hi48jKvQRULQ2PO4nxLmdZm6B0FXaTijbKsyO7GrMik+EZ6mi3ARQ==", - "dev": true, "dependencies": { "@aws-sdk/is-array-buffer": "3.55.0", "@aws-sdk/types": "3.110.0", @@ -943,7 +924,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.110.0.tgz", "integrity": "sha512-gNLYrmdAe/1hVF2Nv2LF4OkL1A0a1o708pEMZHzql9xP164omRDaLrGDhz9tH7tsJEgLz+Bf4E8nTuISeDwvGg==", - "dev": true, "dependencies": { "@aws-sdk/middleware-stack": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -965,7 +945,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.110.0.tgz", "integrity": "sha512-tILFB8/Q73yzgO0dErJNnELmmBszd0E6FucwAnG3hfDefjqCBe09Q/1yhu2aARXyRmZa4AKp0sWcdwIWHc8dnA==", - "dev": true, "dependencies": { "@aws-sdk/querystring-parser": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -976,7 +955,6 @@ "version": "3.109.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64-browser/-/util-base64-browser-3.109.0.tgz", "integrity": "sha512-lAZ6fyDGiRLaIsKT9qh7P9FGuNyZ4gAbr1YOSQk/5mHtaTuUvxlPptZuInNM/0MPQm6lpcot00D8IWTucn4PbA==", - "dev": true, "dependencies": { "tslib": "^2.3.1" } @@ -985,7 +963,6 @@ "version": "3.55.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64-node/-/util-base64-node-3.55.0.tgz", "integrity": "sha512-UQ/ZuNoAc8CFMpSiRYmevaTsuRKzLwulZTnM8LNlIt9Wx1tpNvqp80cfvVj7yySKROtEi20wq29h31dZf1eYNQ==", - "dev": true, "dependencies": { "@aws-sdk/util-buffer-from": "3.55.0", "tslib": "^2.3.1" @@ -998,7 +975,6 @@ "version": "3.55.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.55.0.tgz", "integrity": "sha512-Ei2OCzXQw5N6ZkTMZbamUzc1z+z1R1Ja5tMEagz5BxuX4vWdBObT+uGlSzL8yvTbjoPjnxWA2aXyEqaUP3JS8Q==", - "dev": true, "dependencies": { "tslib": "^2.3.1" } @@ -1007,7 +983,6 @@ "version": "3.55.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.55.0.tgz", "integrity": "sha512-lU1d4I+9wJwydduXs0SxSfd+mHKjxeyd39VwOv6i2KSwWkPbji9UQqpflKLKw+r45jL7+xU/zfeTUg5Tt/3Gew==", - "dev": true, "dependencies": { "tslib": "^2.3.1" }, @@ -1019,7 +994,6 @@ "version": "3.55.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.55.0.tgz", "integrity": "sha512-uVzKG1UgvnV7XX2FPTylBujYMKBPBaq/qFBxfl0LVNfrty7YjpfieQxAe6yRLD+T0Kir/WDQwGvYC+tOYG3IGA==", - "dev": true, "dependencies": { "@aws-sdk/is-array-buffer": "3.55.0", "tslib": "^2.3.1" @@ -1032,7 +1006,6 @@ "version": "3.109.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.109.0.tgz", "integrity": "sha512-GrAZl/aBv0A28LkyNyq8SPJ5fmViCwz80fWLMeWx/6q5AbivuILogjlWwEZSvZ9zrlHOcFC0+AnCa5pQrjaslw==", - "dev": true, "dependencies": { "tslib": "^2.3.1" }, @@ -1044,7 +1017,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.110.0.tgz", "integrity": "sha512-Y2dcOOD20S3bv/IjUqpdKIiDt6995SXNG5Pu/LeSdXNyLCOIm9rX4gHTxl9fC1KK5M/gR9fGJ362f67WwqEEqw==", - "dev": true, "dependencies": { "@aws-sdk/property-provider": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -1059,7 +1031,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.110.0.tgz", "integrity": "sha512-Cr3Z5nyrw1KowjbW76xp8hkT/zJtYjAVZ9PS4l84KxIicbVvDOBpxG3yNddkuQcavmlH6G4wH9uM5DcnpKDncg==", - "dev": true, "dependencies": { "@aws-sdk/config-resolver": "3.110.0", "@aws-sdk/credential-provider-imds": "3.110.0", @@ -1072,11 +1043,21 @@ "node": ">= 10.0.0" } }, + "node_modules/@aws-sdk/util-dynamodb": { + "version": "3.208.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.208.0.tgz", + "integrity": "sha512-4tJNlsZ2wpaQVxQ+AwXGmUk3lLWBrqK4lpJYITJ1C6Y8VZF/yqjeH+mNGQifMzO3T4dzCuGBCUKJ1HUCmGApCw==", + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-sdk/util-hex-encoding": { "version": "3.109.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.109.0.tgz", "integrity": "sha512-s8CgTNrn3cLkrdiohfxLuOYPCanzvHn/aH5RW6DaMoeQiG5Hl9QUiP/WtdQ9QQx3xvpQFpmvxIaSBwSgFNLQxA==", - "dev": true, "dependencies": { "tslib": "^2.3.1" }, @@ -1088,7 +1069,6 @@ "version": "3.55.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.55.0.tgz", "integrity": "sha512-0sPmK2JaJE2BbTcnvybzob/VrFKCXKfN4CUKcvn0yGg/me7Bz+vtzQRB3Xp+YSx+7OtWxzv63wsvHoAnXvgxgg==", - "dev": true, "dependencies": { "tslib": "^2.3.1" }, @@ -1100,7 +1080,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.110.0.tgz", "integrity": "sha512-PTVWrI5fA9d5hHJs6RzX2dIS2jRQ3uW073Fm0BePpQeDdZrEk+S5KNwRhUtpN6sdSV45vm6S9rrjZUG51qwGmA==", - "dev": true, "dependencies": { "tslib": "^2.3.1" }, @@ -1112,7 +1091,6 @@ "version": "3.55.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.55.0.tgz", "integrity": "sha512-mmdDLUpFCN2nkfwlLdOM54lTD528GiGSPN1qb8XtGLgZsJUmg3uJSFIN2lPeSbEwJB3NFjVas/rnQC48i7mV8w==", - "dev": true, "dependencies": { "tslib": "^2.3.1" }, @@ -1124,7 +1102,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.110.0.tgz", "integrity": "sha512-rNdhmHDMV5dNJctqlBWimkZLJRB+x03DB+61pm+SKSFk6gPIVIvc1WNXqDFphkiswT4vA13ZUkGHzt+N4+noQQ==", - "dev": true, "dependencies": { "@aws-sdk/types": "3.110.0", "bowser": "^2.11.0", @@ -1135,7 +1112,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.110.0.tgz", "integrity": "sha512-OQ915TPCCBwZWz5Np8zkNWn7U6KvrTZfFoCOy/VIemK3dUqmnBZ7HqGpuZx8SwJ2R9JE1x+j0niYSJ5fWJZZKA==", - "dev": true, "dependencies": { "@aws-sdk/node-config-provider": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -1149,7 +1125,6 @@ "version": "3.109.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.109.0.tgz", "integrity": "sha512-FmcGSz0v7Bqpl1SE8G1Gc0CtDpug+rvqNCG/szn86JApD/f5x8oByjbEiAyTU2ZH2VevUntx6EW68ulHyH+x+w==", - "dev": true, "dependencies": { "tslib": "^2.3.1" } @@ -1158,7 +1133,6 @@ "version": "3.109.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-node/-/util-utf8-node-3.109.0.tgz", "integrity": "sha512-Ti/ZBdvz2eSTElsucjzNmzpyg2MwfD1rXmxD0hZuIF8bPON/0+sZYnWd5CbDw9kgmhy28dmKue086tbZ1G0iLQ==", - "dev": true, "dependencies": { "@aws-sdk/util-buffer-from": "3.55.0", "tslib": "^2.3.1" @@ -1171,7 +1145,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-waiter/-/util-waiter-3.110.0.tgz", "integrity": "sha512-8dE6W6XYfjk1gx/aeb8NeLfMMLkLFhlV1lmKpFSBJhY8msajU8aQahTuykq5JW8QT/wCGbqbu7dH35SdX7kO+A==", - "dev": true, "dependencies": { "@aws-sdk/abort-controller": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -2244,6 +2217,27 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, + "node_modules/@jest/expect-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", + "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", + "dev": true, + "dependencies": { + "jest-get-type": "^28.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/expect-utils/node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, "node_modules/@jest/fake-timers": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", @@ -2319,6 +2313,18 @@ } } }, + "node_modules/@jest/schemas": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.24.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, "node_modules/@jest/source-map": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz", @@ -3748,6 +3754,12 @@ "@octokit/openapi-types": "^11.2.0" } }, + "node_modules/@sinclair/typebox": { + "version": "0.24.51", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "dev": true + }, "node_modules/@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -3766,6 +3778,23 @@ "@sinonjs/commons": "^1.7.0" } }, + "node_modules/@sinonjs/samsam": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.1.3.tgz", + "integrity": "sha512-nhOb2dWPeb1sd3IQXL/dVPnKHDOAFfvichtBf4xV00/rU1QbPCQqKMbvIheIjqwVjh7qIgf2AHTHi391yMOMpQ==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -3983,6 +4012,21 @@ "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==", "dev": true }, + "node_modules/@types/sinon": { + "version": "10.0.13", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.13.tgz", + "integrity": "sha512-UVjDqJblVNQYvVNUsj0PuYYw0ELRmgt1Nt5Vk0pT5f16ROGfcKJY8o1HVuMOJOpD727RrGB9EGvoaTQE5tgxZQ==", + "dev": true, + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", + "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", + "dev": true + }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -4961,6 +5005,200 @@ "node": ">= 10.0.0" } }, + "node_modules/aws-sdk-client-mock": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aws-sdk-client-mock/-/aws-sdk-client-mock-2.0.0.tgz", + "integrity": "sha512-yjC39Ud78Cgwu9jLc7LoFILT8xtyvR9doCfd8tjeqaOI4oQ5IeTaIYDezWpWKndmrjXZzVLw/odWKP1hpuvsVQ==", + "dev": true, + "dependencies": { + "@types/sinon": "^10.0.10", + "sinon": "^11.1.1", + "tslib": "^2.1.0" + } + }, + "node_modules/aws-sdk-client-mock-jest": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aws-sdk-client-mock-jest/-/aws-sdk-client-mock-jest-2.0.0.tgz", + "integrity": "sha512-HwJeJThngXDUyyyd3Hk12Ailu3smhQzyox+nrF14TxE/LNSJsSmJ2rXahzj7Zdyxn9jDSe2h2ulrfUNYgbPYlw==", + "dev": true, + "dependencies": { + "@types/jest": "^28.1.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "aws-sdk-client-mock": "2.0.0" + } + }, + "node_modules/aws-sdk-client-mock-jest/node_modules/@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/aws-sdk-client-mock-jest/node_modules/@types/jest": { + "version": "28.1.8", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-28.1.8.tgz", + "integrity": "sha512-8TJkV++s7B6XqnDrzR1m/TT0A0h948Pnl/097veySPN67VRAgQ4gZ7n2KfJo2rVq6njQjdxU3GCCyDvAeuHoiw==", + "dev": true, + "dependencies": { + "expect": "^28.0.0", + "pretty-format": "^28.0.0" + } + }, + "node_modules/aws-sdk-client-mock-jest/node_modules/@types/yargs": { + "version": "17.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz", + "integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/aws-sdk-client-mock-jest/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aws-sdk-client-mock-jest/node_modules/diff-sequences": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/aws-sdk-client-mock-jest/node_modules/expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/aws-sdk-client-mock-jest/node_modules/jest-diff": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", + "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/aws-sdk-client-mock-jest/node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/aws-sdk-client-mock-jest/node_modules/jest-matcher-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", + "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/aws-sdk-client-mock-jest/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/aws-sdk-client-mock-jest/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/aws-sdk-client-mock-jest/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/aws-sdk-client-mock-jest/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "node_modules/aws-sdk/node_modules/uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", @@ -5205,8 +5443,7 @@ "node_modules/bowser": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", - "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", - "dev": true + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" }, "node_modules/brace-expansion": { "version": "1.1.11", @@ -7179,7 +7416,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } @@ -8271,7 +8507,6 @@ "version": "3.19.0", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-3.19.0.tgz", "integrity": "sha512-4pXwmBplsCPv8FOY1WRakF970TjNGnGnfbOnLqjlYvMiF1SR3yOHyxMR/YCXpPTOspNF5gwudqktIP4VsWkvBg==", - "dev": true, "bin": { "xml2js": "cli.js" }, @@ -10738,6 +10973,12 @@ "node": ">=0.6.0" } }, + "node_modules/just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -11068,6 +11309,12 @@ "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", "dev": true }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, "node_modules/lodash.ismatch": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", @@ -11640,7 +11887,6 @@ "version": "0.38.3", "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.3.tgz", "integrity": "sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==", - "dev": true, "dependencies": { "obliterator": "^1.6.1" } @@ -11724,6 +11970,46 @@ "node": ">= 0.4.0" } }, + "node_modules/nise": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.2.tgz", + "integrity": "sha512-+gQjFi8v+tkfCuSCxfURHLhRhniE/+IaYbIphxAN2JRR9SHKhY8hgXpaXiYfHdw+gcGe4buxgbprBQFab9FkhA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "@sinonjs/fake-timers": "^7.0.4", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, + "node_modules/nise/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/nise/node_modules/@sinonjs/fake-timers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/nise/node_modules/@sinonjs/fake-timers/node_modules/@sinonjs/commons": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.5.tgz", + "integrity": "sha512-rTpCA0wG1wUxglBSFdMMY0oTrKYvgf4fNgv/sXbfCVAdf+FnPBdKJR/7XbpTCwbCrvCbdPYnlWaUUYz4V2fPDA==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, "node_modules/node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -12256,8 +12542,7 @@ "node_modules/obliterator": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-1.6.1.tgz", - "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==", - "dev": true + "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==" }, "node_modules/once": { "version": "1.4.0", @@ -12682,6 +12967,21 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/path-to-regexp/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -13787,6 +14087,33 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/sinon": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-11.1.2.tgz", + "integrity": "sha512-59237HChms4kg7/sXhiRcUzdSkKuydDeTiamT/jesUVHshBgL8XAmhgFo0GfK6RruMDM/iRSij1EybmMog9cJw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": "^7.1.2", + "@sinonjs/samsam": "^6.0.2", + "diff": "^5.0.0", + "nise": "^5.1.0", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/@sinonjs/fake-timers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -14658,8 +14985,7 @@ "node_modules/tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "node_modules/tsutils": { "version": "3.21.0", @@ -14952,7 +15278,6 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, "bin": { "uuid": "dist/bin/uuid" } @@ -15531,6 +15856,19 @@ "version": "1.4.1", "license": "MIT-0" }, + "packages/idempotency": { + "name": "@aws-lambda-powertools/idempotency", + "version": "0.0.11", + "license": "MIT", + "dependencies": { + "@aws-lambda-powertools/commons": "^1.4.1", + "@aws-sdk/lib-dynamodb": "^3.170.0" + }, + "devDependencies": { + "aws-sdk-client-mock": "^2.0.0", + "aws-sdk-client-mock-jest": "^2.0.0" + } + }, "packages/logger": { "name": "@aws-lambda-powertools/logger", "version": "1.4.1", @@ -15557,6 +15895,51 @@ "promise-retry": "^2.0.1" } }, + "packages/parameters": { + "name": "@aws-lambda-powertools/parameters", + "version": "1.4.1", + "license": "MIT-0", + "dependencies": { + "@aws-sdk/util-base64-node": "^3.209.0" + }, + "devDependencies": {} + }, + "packages/parameters/node_modules/@aws-sdk/is-array-buffer": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.201.0.tgz", + "integrity": "sha512-UPez5qLh3dNgt0DYnPD/q0mVJY84rA17QE26hVNOW3fAji8W2wrwrxdacWOxyXvlxWsVRcKmr+lay1MDqpAMfg==", + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "packages/parameters/node_modules/@aws-sdk/util-base64-node": { + "version": "3.209.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64-node/-/util-base64-node-3.209.0.tgz", + "integrity": "sha512-U6pjb6uF/BameQLmzoSrqeiTxu5otwwGV7fO+TyE/3SJm/lyIsBaO+wr0qsoK0ae1VqggR+KCsUG13pWhdltpw==", + "deprecated": "The package @aws-sdk/util-base64-node has been renamed to @aws-sdk/util-base64. Please install the renamed package.", + "dependencies": { + "@aws-sdk/util-buffer-from": "3.208.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "packages/parameters/node_modules/@aws-sdk/util-buffer-from": { + "version": "3.208.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.208.0.tgz", + "integrity": "sha512-7L0XUixNEFcLUGPeBF35enCvB9Xl+K6SQsmbrPk1P3mlV9mguWSDQqbOBwY1Ir0OVbD6H/ZOQU7hI/9RtRI0Zw==", + "dependencies": { + "@aws-sdk/is-array-buffer": "3.201.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, "packages/tracer": { "name": "@aws-lambda-powertools/tracer", "version": "1.4.1", @@ -15691,7 +16074,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-2.0.0.tgz", "integrity": "sha512-pkVXf/dq6PITJ0jzYZ69VhL8VFOFoPZLZqtU/12SGnzYuJOOGNfF41q9GxdI1yqC8R13Rq3jOLKDFpUJFT5eTA==", - "dev": true, "requires": { "tslib": "^1.11.1" }, @@ -15699,8 +16081,7 @@ "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" } } }, @@ -15708,7 +16089,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-2.0.0.tgz", "integrity": "sha512-rYXOQ8BFOaqMEHJrLHul/25ckWH6GTJtdLSajhlqGMx0PmSueAuvboCuZCTqEKlxR8CQOwRarxYMZZSYlhRA1A==", - "dev": true, "requires": { "@aws-crypto/ie11-detection": "^2.0.0", "@aws-crypto/sha256-js": "^2.0.0", @@ -15723,8 +16103,7 @@ "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" } } }, @@ -15732,7 +16111,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-2.0.0.tgz", "integrity": "sha512-VZY+mCY4Nmrs5WGfitmNqXzaE873fcIZDu54cbaDaaamsaTOP1DBImV9F4pICc3EHjQXujyE8jig+PFCaew9ig==", - "dev": true, "requires": { "@aws-crypto/util": "^2.0.0", "@aws-sdk/types": "^3.1.0", @@ -15742,8 +16120,7 @@ "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" } } }, @@ -15751,7 +16128,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-2.0.0.tgz", "integrity": "sha512-Ge7WQ3E0OC7FHYprsZV3h0QIcpdyJLvIeg+uTuHqRYm8D6qCFJoiC+edSzSyFiHtZf+NOQDJ1q46qxjtzIY2nA==", - "dev": true, "requires": { "tslib": "^1.11.1" }, @@ -15759,8 +16135,7 @@ "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" } } }, @@ -15768,7 +16143,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-2.0.1.tgz", "integrity": "sha512-JJmFFwvbm08lULw4Nm5QOLg8+lAQeC8aCXK5xrtxntYzYXCGfHwUJ4Is3770Q7HmICsXthGQ+ZsDL7C2uH3yBQ==", - "dev": true, "requires": { "@aws-sdk/types": "^3.1.0", "@aws-sdk/util-utf8-browser": "^3.0.0", @@ -15778,14 +16152,22 @@ "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" } } }, "@aws-lambda-powertools/commons": { "version": "file:packages/commons" }, + "@aws-lambda-powertools/idempotency": { + "version": "file:packages/idempotency", + "requires": { + "@aws-lambda-powertools/commons": "^1.4.1", + "@aws-sdk/lib-dynamodb": "^3.170.0", + "aws-sdk-client-mock": "^2.0.0", + "aws-sdk-client-mock-jest": "^2.0.0" + } + }, "@aws-lambda-powertools/logger": { "version": "file:packages/logger", "requires": { @@ -15804,6 +16186,40 @@ "promise-retry": "^2.0.1" } }, + "@aws-lambda-powertools/parameters": { + "version": "file:packages/parameters", + "requires": { + "@aws-sdk/util-base64-node": "*" + }, + "dependencies": { + "@aws-sdk/is-array-buffer": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.201.0.tgz", + "integrity": "sha512-UPez5qLh3dNgt0DYnPD/q0mVJY84rA17QE26hVNOW3fAji8W2wrwrxdacWOxyXvlxWsVRcKmr+lay1MDqpAMfg==", + "requires": { + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-base64-node": { + "version": "3.209.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64-node/-/util-base64-node-3.209.0.tgz", + "integrity": "sha512-U6pjb6uF/BameQLmzoSrqeiTxu5otwwGV7fO+TyE/3SJm/lyIsBaO+wr0qsoK0ae1VqggR+KCsUG13pWhdltpw==", + "requires": { + "@aws-sdk/util-buffer-from": "3.208.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-buffer-from": { + "version": "3.208.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.208.0.tgz", + "integrity": "sha512-7L0XUixNEFcLUGPeBF35enCvB9Xl+K6SQsmbrPk1P3mlV9mguWSDQqbOBwY1Ir0OVbD6H/ZOQU7hI/9RtRI0Zw==", + "requires": { + "@aws-sdk/is-array-buffer": "3.201.0", + "tslib": "^2.3.1" + } + } + } + }, "@aws-lambda-powertools/tracer": { "version": "file:packages/tracer", "requires": { @@ -15819,7 +16235,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.110.0.tgz", "integrity": "sha512-zok/WEVuK7Jh6V9YeA56pNZtxUASon9LTkS7vE65A4UFmNkPGNBCNgoiBcbhWfxwrZ8wtXcQk6rtUut39831mA==", - "dev": true, "requires": { "@aws-sdk/types": "3.110.0", "tslib": "^2.3.1" @@ -15829,7 +16244,6 @@ "version": "3.113.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.113.0.tgz", "integrity": "sha512-tp0/smnSBqoPdSbZqRaH46S9cDkOMXsbG8Qph+gV3+SL+V3nJLeGFzA+3WPmF0l0Bnv0/9Tms4ySZB0MsBSy/A==", - "dev": true, "requires": { "@aws-crypto/sha256-browser": "2.0.0", "@aws-crypto/sha256-js": "2.0.0", @@ -15874,7 +16288,6 @@ "version": "3.112.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.112.0.tgz", "integrity": "sha512-FwFmiapxuVQiyMdDaBvCpajnJkVWEUHBdO+7rIpzgKHkODEPou5/AwboaGRPEFYULOyYeI0HiDFzpK0G6de+7Q==", - "dev": true, "requires": { "@aws-crypto/sha256-browser": "2.0.0", "@aws-crypto/sha256-js": "2.0.0", @@ -15913,7 +16326,6 @@ "version": "3.112.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.112.0.tgz", "integrity": "sha512-hSApRO2wg3jk9VRGM6SCZO3aFP7DKVSUqs6FrvlXlj+JU88ZKObjrGE61cCzXoD89Dh+b9t8A2T6W51Nzriaxw==", - "dev": true, "requires": { "@aws-crypto/sha256-browser": "2.0.0", "@aws-crypto/sha256-js": "2.0.0", @@ -15957,7 +16369,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.110.0.tgz", "integrity": "sha512-7VvtKy4CL63BAktQ2vgsjhWDSXpkXO5YdiI56LQnHztrvSuJBBaxJ7R1p/k0b2tEUhYKUziAIW8EKE/7EGPR4g==", - "dev": true, "requires": { "@aws-sdk/signature-v4": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -15970,7 +16381,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.110.0.tgz", "integrity": "sha512-oFU3IYk/Bl5tdsz1qigtm3I25a9cvXPqlE8VjYjxVDdLujF5zd/4HLbhP4GQWhpEwZmM1ijcSNfLcyywVevTZg==", - "dev": true, "requires": { "@aws-sdk/property-provider": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -15981,7 +16391,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.110.0.tgz", "integrity": "sha512-atl+7/dAB+8fG9XI2fYyCgXKYDbOzot65VAwis+14bOEUCVp7PCJifBEZ/L8GEq564p+Fa2p1IpV0wuQXxqFUQ==", - "dev": true, "requires": { "@aws-sdk/node-config-provider": "3.110.0", "@aws-sdk/property-provider": "3.110.0", @@ -15994,7 +16403,6 @@ "version": "3.112.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.112.0.tgz", "integrity": "sha512-ebgZ6/jZdTGHQ3zfq/ccmS+7YmLk6yUWHDmh69VK+B1Dd+S1jFwbD9EQ+pYWCp/gEl9F620NSwb6KghRylPWEQ==", - "dev": true, "requires": { "@aws-sdk/credential-provider-env": "3.110.0", "@aws-sdk/credential-provider-imds": "3.110.0", @@ -16010,7 +16418,6 @@ "version": "3.112.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.112.0.tgz", "integrity": "sha512-7txS7P3BAaU4cksFw/PnoVskVvO8h/TPvOl/BxFtCiUdwA6FRltLvBeMlN08fwUoqgM6z06q8areBdeDqCHOSw==", - "dev": true, "requires": { "@aws-sdk/credential-provider-env": "3.110.0", "@aws-sdk/credential-provider-imds": "3.110.0", @@ -16028,7 +16435,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.110.0.tgz", "integrity": "sha512-JJcZePvRTfQHYj/+EEY13yItnZH/e8exlARFUjN0L13UrgHpOJtDQBa+YBHXo6MbTFQh+re25z2kzc+zOYSMNQ==", - "dev": true, "requires": { "@aws-sdk/property-provider": "3.110.0", "@aws-sdk/shared-ini-file-loader": "3.110.0", @@ -16040,7 +16446,6 @@ "version": "3.112.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.112.0.tgz", "integrity": "sha512-b6rOrSXbNK3fGyPvNpyF5zdktmAoNOqHCTmFSUcxRxOipyRGb5JACsbjWthIQkpWkpNCT8GFNLEg9spXPFIdLA==", - "dev": true, "requires": { "@aws-sdk/client-sso": "3.112.0", "@aws-sdk/property-provider": "3.110.0", @@ -16053,7 +16458,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.110.0.tgz", "integrity": "sha512-e4e5u7v3fsUFZsMcFMhMy1NdJBQpunYcLwpYlszm3OEICwTTekQ+hVvnVRd134doHvzepE4yp9sAop0Cj+IRVQ==", - "dev": true, "requires": { "@aws-sdk/property-provider": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -16064,7 +16468,6 @@ "version": "3.55.0", "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.55.0.tgz", "integrity": "sha512-kxDoHFDuQwZEEUZRp+ZLOg68EXuKPzUN86DcpIZantDVcmu7MSPTbbQp9DZd8MnKVEKCP7Sop5f7zCqOPl3LXw==", - "dev": true, "requires": { "mnemonist": "0.38.3", "tslib": "^2.3.1" @@ -16074,7 +16477,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.110.0.tgz", "integrity": "sha512-vk+K4GeCZL2J2rtvKO+T0Q7i3MDpEGZBMg5K2tj9sMcEQwty0BF0aFnP7Eu2l4/Zif2z1mWuUFM2WcZI6DVnbw==", - "dev": true, "requires": { "@aws-sdk/protocol-http": "3.110.0", "@aws-sdk/querystring-builder": "3.110.0", @@ -16087,7 +16489,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.110.0.tgz", "integrity": "sha512-wakl+kP2O8wTGYiQ3InZy+CVfGrIpFfq9fo4zif9PZac0BbUbguUU1dkY34uZiaf+4o2/9MoDYrHU2HYeXKxWw==", - "dev": true, "requires": { "@aws-sdk/types": "3.110.0", "@aws-sdk/util-buffer-from": "3.55.0", @@ -16098,7 +16499,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.110.0.tgz", "integrity": "sha512-O8J1InmtJkoiUMbQDtxBfOzgigBp9iSVsNXQrhs2qHh3826cJOfE7NGT3u+NMw73Pk5j2cfmOh1+7k/76IqxOg==", - "dev": true, "requires": { "@aws-sdk/types": "3.110.0", "tslib": "^2.3.1" @@ -16108,16 +16508,23 @@ "version": "3.55.0", "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.55.0.tgz", "integrity": "sha512-NbiPHVYuPxdqdFd6FxzzN3H1BQn/iWA3ri3Ry7AyLeP/tGs1yzEWMwf8BN8TSMALI0GXT6Sh0GDWy3Ok5xB6DA==", - "dev": true, "requires": { "tslib": "^2.3.1" } }, + "@aws-sdk/lib-dynamodb": { + "version": "3.208.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.208.0.tgz", + "integrity": "sha512-Cr1eR7JqSe3wM3KQFrn0+ePwb5tjn9g+7q74zM19caBC3IvQUegmWnCV3gG0KlVo18U6veJiUSIDi3fZDAXSdQ==", + "requires": { + "@aws-sdk/util-dynamodb": "3.208.0", + "tslib": "^2.3.1" + } + }, "@aws-sdk/middleware-content-length": { "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.110.0.tgz", "integrity": "sha512-hKU+zdqfAJQg22LXMVu/z35nNIHrVAKpVKPe9+WYVdL/Z7JKUPK7QymqKGOyDuDbzW6OxyulC1zKGEX12zGmdA==", - "dev": true, "requires": { "@aws-sdk/protocol-http": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -16128,7 +16535,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.110.0.tgz", "integrity": "sha512-GUnNcuEBqD21E8EgevTcaBXJGnH5dp7eDSfWceYJP+iSscFXLzFFOf1I6H0cwvfARVMHVvlFz0tQcojCSHUZig==", - "dev": true, "requires": { "@aws-sdk/config-resolver": "3.110.0", "@aws-sdk/endpoint-cache": "3.55.0", @@ -16141,7 +16547,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.110.0.tgz", "integrity": "sha512-/Cknn1vL2LTlclI0MX2RzmtdPlCJ5palCRXxm/mod1oHwg4oNTKRlUX3LUD+L8g7JuJ4h053Ch9KS/A0vanE5Q==", - "dev": true, "requires": { "@aws-sdk/protocol-http": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -16152,7 +16557,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.110.0.tgz", "integrity": "sha512-+pz+a+8dfTnzLj79nHrv3aONMp/N36/erMd+7JXeR84QEosVLrFBUwKA8x5x6O3s1iBbQzRKMYEIuja9xn1BPA==", - "dev": true, "requires": { "@aws-sdk/types": "3.110.0", "tslib": "^2.3.1" @@ -16162,7 +16566,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.110.0.tgz", "integrity": "sha512-Wav782zd7bcd1e6txRob76CDOdVOaUQ8HXoywiIm/uFrEEUZvhs2mgnXjVUVCMBUehdNgnL99z420aS13JeL/Q==", - "dev": true, "requires": { "@aws-sdk/protocol-http": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -16173,7 +16576,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.110.0.tgz", "integrity": "sha512-lwLAQQveCiUqymQvVYjCee6QOXw3Zqbc9yq+pxYdXbs1Cv1XMA6PeJeUU5r5KEVuSceBLyyrnl6E0R1l1om1MQ==", - "dev": true, "requires": { "@aws-sdk/protocol-http": "3.110.0", "@aws-sdk/service-error-classification": "3.110.0", @@ -16187,7 +16589,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.110.0.tgz", "integrity": "sha512-EjY/YFdlr5jECde6qIrTIyGBbn/34CKcQGKvmvRd31+3qaClIJLAwNuHfcVzWvCUGbAslsfvdbOpLju33pSQRA==", - "dev": true, "requires": { "@aws-sdk/middleware-signing": "3.110.0", "@aws-sdk/property-provider": "3.110.0", @@ -16201,7 +16602,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.110.0.tgz", "integrity": "sha512-brVupxgEAmcZ9cZvdHEH8zncjvGKIiud8pOe4fiimp5NpHmjBLew4jUbnOKNZNAjaidcKUtz//cxtutD6yXEww==", - "dev": true, "requires": { "@aws-sdk/types": "3.110.0", "tslib": "^2.3.1" @@ -16211,7 +16611,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.110.0.tgz", "integrity": "sha512-y6ZKrGYfgDlFMzWhZmoq5J1UctBgZOUvMmnU9sSeZ020IlEPiOxFMvR0Zu6TcYThp8uy3P0wyjQtGYeTl9Z/kA==", - "dev": true, "requires": { "@aws-sdk/property-provider": "3.110.0", "@aws-sdk/protocol-http": "3.110.0", @@ -16224,7 +16623,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.110.0.tgz", "integrity": "sha512-iaLHw6ctOuGa9UxNueU01Xes+15dR+mqioRpUOUZ9Zx+vhXVpD7C8lnNqhRnYeFXs10/rNIzASgsIrAHTlnlIQ==", - "dev": true, "requires": { "tslib": "^2.3.1" } @@ -16233,7 +16631,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.110.0.tgz", "integrity": "sha512-Y6FgiZr99DilYq6AjeaaWcNwVlSQpNGKrILzvV4Tmz03OaBIspe4KL+8EZ2YA/sAu5Lpw80vItdezqDOwGAlnQ==", - "dev": true, "requires": { "@aws-sdk/protocol-http": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -16244,7 +16641,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.110.0.tgz", "integrity": "sha512-46p4dCPGYctuybTQTwLpjenA1QFHeyJw/OyggGbtUJUy+833+ldnAwcPVML2aXJKUKv3APGI8vq1kaloyNku3Q==", - "dev": true, "requires": { "@aws-sdk/property-provider": "3.110.0", "@aws-sdk/shared-ini-file-loader": "3.110.0", @@ -16256,7 +16652,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.110.0.tgz", "integrity": "sha512-/rP+hY516DpP8fZhwFW5xM/ElH0w6lxw/15VvZCoY5EnOLAF5XIsJdzscWPSEW2FHCylBM4SNrKhGar14BDXhA==", - "dev": true, "requires": { "@aws-sdk/abort-controller": "3.110.0", "@aws-sdk/protocol-http": "3.110.0", @@ -16269,7 +16664,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.110.0.tgz", "integrity": "sha512-7NkpmYeOkK3mhWBNU+/zSDqwzeaSPH1qrq4L//WV7WS/weYyE/jusQeZoOxVsuZQnQEXHt5O2hKVeUwShl12xA==", - "dev": true, "requires": { "@aws-sdk/types": "3.110.0", "tslib": "^2.3.1" @@ -16279,7 +16673,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.110.0.tgz", "integrity": "sha512-qdi2gCbJiyPyLn+afebPNp/5nVCRh1X7t7IRIFl3FHVEC+o54u/ojay/MLZ4M/+X9Fa4Zxsb0Wpp3T0xAHVDBg==", - "dev": true, "requires": { "@aws-sdk/types": "3.110.0", "tslib": "^2.3.1" @@ -16289,7 +16682,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.110.0.tgz", "integrity": "sha512-7V3CDXj519izmbBn9ZE68ymASwGriA+Aq+cb/yHSVtffnvXjPtvONNw7G/5iVblisGLSCUe2hSvpYtcaXozbHw==", - "dev": true, "requires": { "@aws-sdk/types": "3.110.0", "@aws-sdk/util-uri-escape": "3.55.0", @@ -16300,7 +16692,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.110.0.tgz", "integrity": "sha512-//pJHH7hrhdDMZGBPKXKymmC/tJM7gFT0w/qbu/yd3Wm4W2fMB+8gkmj6EZctx7jrsWlfRQuvFejKqEfapur/g==", - "dev": true, "requires": { "@aws-sdk/types": "3.110.0", "tslib": "^2.3.1" @@ -16315,7 +16706,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.110.0.tgz", "integrity": "sha512-E1ERoqEoG206XNBYWCKLgHkzCbTxdpDEGbsLET2DnvjFsT0s9p2dPvVux3bYl7JVAhyGduE+qcqWk7MzhFCBNQ==", - "dev": true, "requires": { "tslib": "^2.3.1" } @@ -16324,7 +16714,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.110.0.tgz", "integrity": "sha512-utxxdllOnmQDhbpipnFAbuQ4c2pwefZ+2hi48jKvQRULQ2PO4nxLmdZm6B0FXaTijbKsyO7GrMik+EZ6mi3ARQ==", - "dev": true, "requires": { "@aws-sdk/is-array-buffer": "3.55.0", "@aws-sdk/types": "3.110.0", @@ -16338,7 +16727,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.110.0.tgz", "integrity": "sha512-gNLYrmdAe/1hVF2Nv2LF4OkL1A0a1o708pEMZHzql9xP164omRDaLrGDhz9tH7tsJEgLz+Bf4E8nTuISeDwvGg==", - "dev": true, "requires": { "@aws-sdk/middleware-stack": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -16354,7 +16742,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.110.0.tgz", "integrity": "sha512-tILFB8/Q73yzgO0dErJNnELmmBszd0E6FucwAnG3hfDefjqCBe09Q/1yhu2aARXyRmZa4AKp0sWcdwIWHc8dnA==", - "dev": true, "requires": { "@aws-sdk/querystring-parser": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -16365,7 +16752,6 @@ "version": "3.109.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64-browser/-/util-base64-browser-3.109.0.tgz", "integrity": "sha512-lAZ6fyDGiRLaIsKT9qh7P9FGuNyZ4gAbr1YOSQk/5mHtaTuUvxlPptZuInNM/0MPQm6lpcot00D8IWTucn4PbA==", - "dev": true, "requires": { "tslib": "^2.3.1" } @@ -16374,7 +16760,6 @@ "version": "3.55.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64-node/-/util-base64-node-3.55.0.tgz", "integrity": "sha512-UQ/ZuNoAc8CFMpSiRYmevaTsuRKzLwulZTnM8LNlIt9Wx1tpNvqp80cfvVj7yySKROtEi20wq29h31dZf1eYNQ==", - "dev": true, "requires": { "@aws-sdk/util-buffer-from": "3.55.0", "tslib": "^2.3.1" @@ -16384,7 +16769,6 @@ "version": "3.55.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.55.0.tgz", "integrity": "sha512-Ei2OCzXQw5N6ZkTMZbamUzc1z+z1R1Ja5tMEagz5BxuX4vWdBObT+uGlSzL8yvTbjoPjnxWA2aXyEqaUP3JS8Q==", - "dev": true, "requires": { "tslib": "^2.3.1" } @@ -16393,7 +16777,6 @@ "version": "3.55.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.55.0.tgz", "integrity": "sha512-lU1d4I+9wJwydduXs0SxSfd+mHKjxeyd39VwOv6i2KSwWkPbji9UQqpflKLKw+r45jL7+xU/zfeTUg5Tt/3Gew==", - "dev": true, "requires": { "tslib": "^2.3.1" } @@ -16402,7 +16785,6 @@ "version": "3.55.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.55.0.tgz", "integrity": "sha512-uVzKG1UgvnV7XX2FPTylBujYMKBPBaq/qFBxfl0LVNfrty7YjpfieQxAe6yRLD+T0Kir/WDQwGvYC+tOYG3IGA==", - "dev": true, "requires": { "@aws-sdk/is-array-buffer": "3.55.0", "tslib": "^2.3.1" @@ -16412,7 +16794,6 @@ "version": "3.109.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.109.0.tgz", "integrity": "sha512-GrAZl/aBv0A28LkyNyq8SPJ5fmViCwz80fWLMeWx/6q5AbivuILogjlWwEZSvZ9zrlHOcFC0+AnCa5pQrjaslw==", - "dev": true, "requires": { "tslib": "^2.3.1" } @@ -16421,7 +16802,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.110.0.tgz", "integrity": "sha512-Y2dcOOD20S3bv/IjUqpdKIiDt6995SXNG5Pu/LeSdXNyLCOIm9rX4gHTxl9fC1KK5M/gR9fGJ362f67WwqEEqw==", - "dev": true, "requires": { "@aws-sdk/property-provider": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -16433,7 +16813,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.110.0.tgz", "integrity": "sha512-Cr3Z5nyrw1KowjbW76xp8hkT/zJtYjAVZ9PS4l84KxIicbVvDOBpxG3yNddkuQcavmlH6G4wH9uM5DcnpKDncg==", - "dev": true, "requires": { "@aws-sdk/config-resolver": "3.110.0", "@aws-sdk/credential-provider-imds": "3.110.0", @@ -16443,11 +16822,18 @@ "tslib": "^2.3.1" } }, + "@aws-sdk/util-dynamodb": { + "version": "3.208.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.208.0.tgz", + "integrity": "sha512-4tJNlsZ2wpaQVxQ+AwXGmUk3lLWBrqK4lpJYITJ1C6Y8VZF/yqjeH+mNGQifMzO3T4dzCuGBCUKJ1HUCmGApCw==", + "requires": { + "tslib": "^2.3.1" + } + }, "@aws-sdk/util-hex-encoding": { "version": "3.109.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.109.0.tgz", "integrity": "sha512-s8CgTNrn3cLkrdiohfxLuOYPCanzvHn/aH5RW6DaMoeQiG5Hl9QUiP/WtdQ9QQx3xvpQFpmvxIaSBwSgFNLQxA==", - "dev": true, "requires": { "tslib": "^2.3.1" } @@ -16456,7 +16842,6 @@ "version": "3.55.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.55.0.tgz", "integrity": "sha512-0sPmK2JaJE2BbTcnvybzob/VrFKCXKfN4CUKcvn0yGg/me7Bz+vtzQRB3Xp+YSx+7OtWxzv63wsvHoAnXvgxgg==", - "dev": true, "requires": { "tslib": "^2.3.1" } @@ -16465,7 +16850,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.110.0.tgz", "integrity": "sha512-PTVWrI5fA9d5hHJs6RzX2dIS2jRQ3uW073Fm0BePpQeDdZrEk+S5KNwRhUtpN6sdSV45vm6S9rrjZUG51qwGmA==", - "dev": true, "requires": { "tslib": "^2.3.1" } @@ -16474,7 +16858,6 @@ "version": "3.55.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.55.0.tgz", "integrity": "sha512-mmdDLUpFCN2nkfwlLdOM54lTD528GiGSPN1qb8XtGLgZsJUmg3uJSFIN2lPeSbEwJB3NFjVas/rnQC48i7mV8w==", - "dev": true, "requires": { "tslib": "^2.3.1" } @@ -16483,7 +16866,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.110.0.tgz", "integrity": "sha512-rNdhmHDMV5dNJctqlBWimkZLJRB+x03DB+61pm+SKSFk6gPIVIvc1WNXqDFphkiswT4vA13ZUkGHzt+N4+noQQ==", - "dev": true, "requires": { "@aws-sdk/types": "3.110.0", "bowser": "^2.11.0", @@ -16494,7 +16876,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.110.0.tgz", "integrity": "sha512-OQ915TPCCBwZWz5Np8zkNWn7U6KvrTZfFoCOy/VIemK3dUqmnBZ7HqGpuZx8SwJ2R9JE1x+j0niYSJ5fWJZZKA==", - "dev": true, "requires": { "@aws-sdk/node-config-provider": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -16505,7 +16886,6 @@ "version": "3.109.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.109.0.tgz", "integrity": "sha512-FmcGSz0v7Bqpl1SE8G1Gc0CtDpug+rvqNCG/szn86JApD/f5x8oByjbEiAyTU2ZH2VevUntx6EW68ulHyH+x+w==", - "dev": true, "requires": { "tslib": "^2.3.1" } @@ -16514,7 +16894,6 @@ "version": "3.109.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-node/-/util-utf8-node-3.109.0.tgz", "integrity": "sha512-Ti/ZBdvz2eSTElsucjzNmzpyg2MwfD1rXmxD0hZuIF8bPON/0+sZYnWd5CbDw9kgmhy28dmKue086tbZ1G0iLQ==", - "dev": true, "requires": { "@aws-sdk/util-buffer-from": "3.55.0", "tslib": "^2.3.1" @@ -16524,7 +16903,6 @@ "version": "3.110.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-waiter/-/util-waiter-3.110.0.tgz", "integrity": "sha512-8dE6W6XYfjk1gx/aeb8NeLfMMLkLFhlV1lmKpFSBJhY8msajU8aQahTuykq5JW8QT/wCGbqbu7dH35SdX7kO+A==", - "dev": true, "requires": { "@aws-sdk/abort-controller": "3.110.0", "@aws-sdk/types": "3.110.0", @@ -17353,6 +17731,23 @@ "jest-mock": "^27.5.1" } }, + "@jest/expect-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", + "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", + "dev": true, + "requires": { + "jest-get-type": "^28.0.2" + }, + "dependencies": { + "jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true + } + } + }, "@jest/fake-timers": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", @@ -17411,6 +17806,15 @@ "v8-to-istanbul": "^8.1.0" } }, + "@jest/schemas": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.24.1" + } + }, "@jest/source-map": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz", @@ -18598,6 +19002,12 @@ "@octokit/openapi-types": "^11.2.0" } }, + "@sinclair/typebox": { + "version": "0.24.51", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "dev": true + }, "@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -18616,6 +19026,23 @@ "@sinonjs/commons": "^1.7.0" } }, + "@sinonjs/samsam": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.1.3.tgz", + "integrity": "sha512-nhOb2dWPeb1sd3IQXL/dVPnKHDOAFfvichtBf4xV00/rU1QbPCQqKMbvIheIjqwVjh7qIgf2AHTHi391yMOMpQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -18830,6 +19257,21 @@ "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==", "dev": true }, + "@types/sinon": { + "version": "10.0.13", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.13.tgz", + "integrity": "sha512-UVjDqJblVNQYvVNUsj0PuYYw0ELRmgt1Nt5Vk0pT5f16ROGfcKJY8o1HVuMOJOpD727RrGB9EGvoaTQE5tgxZQ==", + "dev": true, + "requires": { + "@types/sinonjs__fake-timers": "*" + } + }, + "@types/sinonjs__fake-timers": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", + "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", + "dev": true + }, "@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -19528,6 +19970,166 @@ } } }, + "aws-sdk-client-mock": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aws-sdk-client-mock/-/aws-sdk-client-mock-2.0.0.tgz", + "integrity": "sha512-yjC39Ud78Cgwu9jLc7LoFILT8xtyvR9doCfd8tjeqaOI4oQ5IeTaIYDezWpWKndmrjXZzVLw/odWKP1hpuvsVQ==", + "dev": true, + "requires": { + "@types/sinon": "^10.0.10", + "sinon": "^11.1.1", + "tslib": "^2.1.0" + } + }, + "aws-sdk-client-mock-jest": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aws-sdk-client-mock-jest/-/aws-sdk-client-mock-jest-2.0.0.tgz", + "integrity": "sha512-HwJeJThngXDUyyyd3Hk12Ailu3smhQzyox+nrF14TxE/LNSJsSmJ2rXahzj7Zdyxn9jDSe2h2ulrfUNYgbPYlw==", + "dev": true, + "requires": { + "@types/jest": "^28.1.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "dev": true, + "requires": { + "@jest/schemas": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "@types/jest": { + "version": "28.1.8", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-28.1.8.tgz", + "integrity": "sha512-8TJkV++s7B6XqnDrzR1m/TT0A0h948Pnl/097veySPN67VRAgQ4gZ7n2KfJo2rVq6njQjdxU3GCCyDvAeuHoiw==", + "dev": true, + "requires": { + "expect": "^28.0.0", + "pretty-format": "^28.0.0" + } + }, + "@types/yargs": { + "version": "17.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz", + "integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "diff-sequences": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", + "dev": true + }, + "expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", + "dev": true, + "requires": { + "@jest/expect-utils": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3" + } + }, + "jest-diff": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", + "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + } + }, + "jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true + }, + "jest-matcher-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", + "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + } + }, + "jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, + "pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dev": true, + "requires": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + } + } + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -19707,8 +20309,7 @@ "bowser": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", - "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", - "dev": true + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" }, "brace-expansion": { "version": "1.1.11", @@ -21412,8 +22013,7 @@ "entities": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" }, "env-paths": { "version": "2.2.1", @@ -22150,8 +22750,7 @@ "fast-xml-parser": { "version": "3.19.0", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-3.19.0.tgz", - "integrity": "sha512-4pXwmBplsCPv8FOY1WRakF970TjNGnGnfbOnLqjlYvMiF1SR3yOHyxMR/YCXpPTOspNF5gwudqktIP4VsWkvBg==", - "dev": true + "integrity": "sha512-4pXwmBplsCPv8FOY1WRakF970TjNGnGnfbOnLqjlYvMiF1SR3yOHyxMR/YCXpPTOspNF5gwudqktIP4VsWkvBg==" }, "fastq": { "version": "1.13.0", @@ -24044,6 +24643,12 @@ "verror": "1.10.0" } }, + "just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -24325,6 +24930,12 @@ "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, "lodash.ismatch": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", @@ -24777,7 +25388,6 @@ "version": "0.38.3", "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.3.tgz", "integrity": "sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==", - "dev": true, "requires": { "obliterator": "^1.6.1" } @@ -24845,6 +25455,50 @@ "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", "dev": true }, + "nise": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.2.tgz", + "integrity": "sha512-+gQjFi8v+tkfCuSCxfURHLhRhniE/+IaYbIphxAN2JRR9SHKhY8hgXpaXiYfHdw+gcGe4buxgbprBQFab9FkhA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^2.0.0", + "@sinonjs/fake-timers": "^7.0.4", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.5.tgz", + "integrity": "sha512-rTpCA0wG1wUxglBSFdMMY0oTrKYvgf4fNgv/sXbfCVAdf+FnPBdKJR/7XbpTCwbCrvCbdPYnlWaUUYz4V2fPDA==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + } + } + } + } + }, "node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -25263,8 +25917,7 @@ "obliterator": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-1.6.1.tgz", - "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==", - "dev": true + "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==" }, "once": { "version": "1.4.0", @@ -25586,6 +26239,23 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + } + } + }, "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -26436,6 +27106,31 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "sinon": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-11.1.2.tgz", + "integrity": "sha512-59237HChms4kg7/sXhiRcUzdSkKuydDeTiamT/jesUVHshBgL8XAmhgFo0GfK6RruMDM/iRSij1EybmMog9cJw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": "^7.1.2", + "@sinonjs/samsam": "^6.0.2", + "diff": "^5.0.0", + "nise": "^5.1.0", + "supports-color": "^7.2.0" + }, + "dependencies": { + "@sinonjs/fake-timers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + } + } + }, "sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -27077,8 +27772,7 @@ "tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "tsutils": { "version": "3.21.0", @@ -27312,8 +28006,7 @@ "uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, "v8-compile-cache": { "version": "2.3.0", diff --git a/package.json b/package.json index 778f80ffa1..24809d929b 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "packages/logger", "packages/metrics", "packages/tracer", - "packages/parameters" + "packages/parameters", + "packages/idempotency" ], "scripts": { "init-environment": "husky install", @@ -84,4 +85,4 @@ "dependencies": { "hosted-git-info": "^5.0.0" } -} +} \ No newline at end of file diff --git a/packages/idempotency/jest.config.js b/packages/idempotency/jest.config.js index 8f1701110c..c33c7e13fc 100644 --- a/packages/idempotency/jest.config.js +++ b/packages/idempotency/jest.config.js @@ -1,7 +1,7 @@ module.exports = { displayName: { name: 'AWS Lambda Powertools utility: IDEMPOTENCY', - color: 'cyan', + color: 'blue', }, 'runner': 'groups', 'preset': 'ts-jest', @@ -25,6 +25,7 @@ module.exports = { 'coveragePathIgnorePatterns': [ '/node_modules/', '/types/', + 'src/makeFunctionIdempotent.ts', // TODO: remove this once makeFunctionIdempotent is implemented ], 'coverageThreshold': { 'global': { @@ -38,5 +39,8 @@ module.exports = { 'json-summary', 'text', 'lcov' + ], + 'setupFiles': [ + '/tests/helpers/populateEnvironmentVariables.ts' ] }; \ No newline at end of file diff --git a/packages/idempotency/package.json b/packages/idempotency/package.json index 23d75bafb6..8ea2117115 100644 --- a/packages/idempotency/package.json +++ b/packages/idempotency/package.json @@ -13,18 +13,18 @@ "commit": "commit", "test": "npm run test:unit", "test:unit": "jest --group=unit --detectOpenHandles --coverage --verbose", - "test:e2e:nodejs12x": "RUNTIME=nodejs12x jest --group=e2e", - "test:e2e:nodejs14x": "RUNTIME=nodejs14x jest --group=e2e", - "test:e2e:nodejs16x": "RUNTIME=nodejs16x jest --group=e2e", - "test:e2e": "jest --group=e2e", + "test:e2e:nodejs12x": "echo \"Not implemented\"", + "test:e2e:nodejs14x": "echo \"Not implemented\"", + "test:e2e:nodejs16x": "echo \"Not implemented\"", + "test:e2e": "echo \"Not implemented\"", "watch": "jest --watch --group=unit", "build": "tsc", "lint": "eslint --ext .ts --no-error-on-unmatched-pattern src tests", "lint-fix": "eslint --fix --ext .ts --no-error-on-unmatched-pattern src tests", "package": "mkdir -p dist/ && npm pack && mv *.tgz dist/", - "package-bundle": "../../package-bundler.sh logger-bundle ./dist", + "package-bundle": "../../package-bundler.sh idempotency-bundle ./dist", "prepare": "npm run build", - "postversion": "git push --tags" + "postversion": "echo \"Not implemented\"" }, "homepage": "https://github.com/awslabs/aws-lambda-powertools-typescript/tree/master/packages/idempotency#readme", "license": "MIT", @@ -42,7 +42,7 @@ "url": "https://github.com/awslabs/aws-lambda-powertools-typescript/issues" }, "dependencies": { - "@aws-lambda-powertools/commons": "^1.2.1", + "@aws-lambda-powertools/commons": "^1.4.1", "@aws-sdk/lib-dynamodb": "^3.170.0" }, "keywords": [ @@ -56,4 +56,4 @@ "aws-sdk-client-mock": "^2.0.0", "aws-sdk-client-mock-jest": "^2.0.0" } -} +} \ No newline at end of file diff --git a/packages/idempotency/src/EnvironmentVariablesService.ts b/packages/idempotency/src/EnvironmentVariablesService.ts deleted file mode 100644 index 4d9892e307..0000000000 --- a/packages/idempotency/src/EnvironmentVariablesService.ts +++ /dev/null @@ -1,25 +0,0 @@ -class EnvironmentVariablesService { - private lambdaFunctionNameVariable = 'AWS_LAMBDA_FUNCTION_NAME'; - - /** - * retrieve the value of an environment variable - * - * @param name the name of the environment variable - * @returns the value of the environment variable - */ - public get(name: string): string { - return process.env[name]?.trim() || ''; - } - /** - * retrieve the name of the Lambda function - * - * @returns the Lambda function name - */ - public getLambdaFunctionName(): string{ - return this.get(this.lambdaFunctionNameVariable); - } -} - -export { - EnvironmentVariablesService -}; \ No newline at end of file diff --git a/packages/idempotency/src/config/ConfigServiceInterface.ts b/packages/idempotency/src/config/ConfigServiceInterface.ts new file mode 100644 index 0000000000..f56dbc0d11 --- /dev/null +++ b/packages/idempotency/src/config/ConfigServiceInterface.ts @@ -0,0 +1,13 @@ +interface ConfigServiceInterface { + + get(name: string): string + + getServiceName(): string + + getFunctionName(): string + +} + +export { + ConfigServiceInterface +}; \ No newline at end of file diff --git a/packages/idempotency/src/config/EnvironmentVariablesService.ts b/packages/idempotency/src/config/EnvironmentVariablesService.ts new file mode 100644 index 0000000000..333faaca38 --- /dev/null +++ b/packages/idempotency/src/config/EnvironmentVariablesService.ts @@ -0,0 +1,36 @@ +import { ConfigServiceInterface } from './ConfigServiceInterface'; +import { EnvironmentVariablesService as CommonEnvironmentVariablesService } from '@aws-lambda-powertools/commons'; + +/** + * Class EnvironmentVariablesService + * + * This class is used to return environment variables that are available in the runtime of + * the current Lambda invocation. + * These variables can be a mix of runtime environment variables set by AWS and + * variables that can be set by the developer additionally. + * + * @class + * @extends {CommonEnvironmentVariablesService} + * @implements {ConfigServiceInterface} + * @see https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime + * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/#environment-variables + */ +class EnvironmentVariablesService extends CommonEnvironmentVariablesService implements ConfigServiceInterface { + + // Reserved environment variables + private functionNameVariable = 'AWS_LAMBDA_FUNCTION_NAME'; + + /** + * It returns the value of the AWS_LAMBDA_FUNCTION_NAME environment variable. + * + * @returns {string} + */ + public getFunctionName(): string { + return this.get(this.functionNameVariable); + } + +} + +export { + EnvironmentVariablesService +}; \ No newline at end of file diff --git a/packages/idempotency/src/config/index.ts b/packages/idempotency/src/config/index.ts new file mode 100644 index 0000000000..b55646be54 --- /dev/null +++ b/packages/idempotency/src/config/index.ts @@ -0,0 +1 @@ +export * from './EnvironmentVariablesService'; \ No newline at end of file diff --git a/packages/idempotency/src/makeFunctionIdempotent.ts b/packages/idempotency/src/makeFunctionIdempotent.ts index dc851396d6..40b6e52acf 100644 --- a/packages/idempotency/src/makeFunctionIdempotent.ts +++ b/packages/idempotency/src/makeFunctionIdempotent.ts @@ -1,10 +1,11 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { AnyFunction } from './types/AnyFunction'; -import { IdempotencyOptions } from './IdempotencyOptions'; +import type { AnyFunction } from './types/AnyFunction'; +import type { IdempotencyOptions } from './types/IdempotencyOptions'; const makeFunctionIdempotent = ( fn: AnyFunction, _options: IdempotencyOptions + // TODO: revisit this with a more specific type if possible + /* eslint-disable @typescript-eslint/no-explicit-any */ ): (...args: Array) => Promise => (...args) => fn(...args); export { makeFunctionIdempotent }; diff --git a/packages/idempotency/src/persistence/DynamoDbPersistenceLayer.ts b/packages/idempotency/src/persistence/DynamoDBPersistenceLayer.ts similarity index 95% rename from packages/idempotency/src/persistence/DynamoDbPersistenceLayer.ts rename to packages/idempotency/src/persistence/DynamoDBPersistenceLayer.ts index 97cc752533..a0b9fe775e 100644 --- a/packages/idempotency/src/persistence/DynamoDbPersistenceLayer.ts +++ b/packages/idempotency/src/persistence/DynamoDBPersistenceLayer.ts @@ -1,5 +1,6 @@ import { DynamoDB, DynamoDBServiceException } from '@aws-sdk/client-dynamodb'; -import { DynamoDBDocument, GetCommandOutput } from '@aws-sdk/lib-dynamodb'; +import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb'; +import type { GetCommandOutput } from '@aws-sdk/lib-dynamodb'; import { DynamoPersistenceConstructorOptions } from '../types/DynamoPersistenceConstructorOptions'; import { IdempotencyItemAlreadyExistsError, IdempotencyItemNotFoundError } from '../Exceptions'; import { IdempotencyRecordStatus } from '../types/IdempotencyRecordStatus'; @@ -23,7 +24,7 @@ class DynamoDBPersistenceLayer extends PersistenceLayer { this.statusAttr = constructorOptions.statusAttr ?? 'status'; this.expiryAttr = constructorOptions.expiryAttr ?? 'expiration'; this.inProgressExpiryAttr = constructorOptions.inProgressExpiryAttr ?? 'in_progress_expiry_attr'; - this.dataAttr = constructorOptions.data_attr ?? 'data'; + this.dataAttr = constructorOptions.dataAttr ?? 'data'; } protected async _deleteRecord(record: IdempotencyRecord): Promise { diff --git a/packages/idempotency/src/persistence/PersistenceLayer.ts b/packages/idempotency/src/persistence/PersistenceLayer.ts index d64c3d1546..9325723312 100644 --- a/packages/idempotency/src/persistence/PersistenceLayer.ts +++ b/packages/idempotency/src/persistence/PersistenceLayer.ts @@ -1,6 +1,7 @@ import { BinaryToTextEncoding, createHash, Hash } from 'crypto'; import { IdempotencyRecordStatus } from '../types/IdempotencyRecordStatus'; -import { EnvironmentVariablesService } from '../EnvironmentVariablesService'; +import type { PersistenceLayerConfigureOptions } from '../types/PersistenceLayer'; +import { EnvironmentVariablesService } from '../config'; import { IdempotencyRecord } from './IdempotencyRecord'; import { PersistenceLayerInterface } from './PersistenceLayerInterface'; @@ -10,22 +11,32 @@ abstract class PersistenceLayer implements PersistenceLayerInterface { private envVarsService!: EnvironmentVariablesService; private expiresAfterSeconds: number; - - private functionName: string = ''; - + private hashDigest: BinaryToTextEncoding; - + private hashFunction: string; + + private idempotencyKeyPrefix: string; public constructor() { this.setEnvVarsService(); this.expiresAfterSeconds = 60 * 60; //one hour is the default expiration this.hashFunction = 'md5'; this.hashDigest = 'base64'; - + this.idempotencyKeyPrefix = this.getEnvVarsService().getFunctionName(); + } - public configure(functionName: string = ''): void { - this.functionName = this.getEnvVarsService().getLambdaFunctionName() + '.' + functionName; + + /** + * Configures the persistence layer by passing the name of the idempotent function. This will be used + * in the prefix of the idempotency key + * + * @param {PersistenceLayerConfigureOptions} options - configuration object for the persistence layer + */ + public configure(options?: PersistenceLayerConfigureOptions): void { + if (options?.functionName && options.functionName.trim() !== '') { + this.idempotencyKeyPrefix = `${this.idempotencyKeyPrefix}.${options.functionName}`; + } } /** @@ -136,7 +147,7 @@ abstract class PersistenceLayer implements PersistenceLayerInterface { console.warn('No data found for idempotency key'); } - return this.functionName + '#' + this.generateHash(JSON.stringify(data)); + return `${this.idempotencyKeyPrefix}#${this.generateHash(JSON.stringify(data))}`; } /** diff --git a/packages/idempotency/src/persistence/PersistenceLayerInterface.ts b/packages/idempotency/src/persistence/PersistenceLayerInterface.ts index e6bce97fde..3eb5a9d0f0 100644 --- a/packages/idempotency/src/persistence/PersistenceLayerInterface.ts +++ b/packages/idempotency/src/persistence/PersistenceLayerInterface.ts @@ -1,7 +1,8 @@ import { IdempotencyRecord } from './IdempotencyRecord'; +import type { PersistenceLayerConfigureOptions } from '../types/PersistenceLayer'; interface PersistenceLayerInterface { - configure(functionName: string): void + configure(options?: PersistenceLayerConfigureOptions): void saveInProgress(data: unknown): Promise saveSuccess(data: unknown, result: unknown): Promise deleteRecord(data: unknown): Promise diff --git a/packages/idempotency/src/persistence/index.ts b/packages/idempotency/src/persistence/index.ts index 7256fa4e7b..01dfd9be77 100644 --- a/packages/idempotency/src/persistence/index.ts +++ b/packages/idempotency/src/persistence/index.ts @@ -1,4 +1,4 @@ -export * from './DynamoDbPersistenceLayer'; +export * from './DynamoDBPersistenceLayer'; export * from './PersistenceLayer'; export * from './PersistenceLayerInterface'; export * from './IdempotencyRecord'; \ No newline at end of file diff --git a/packages/idempotency/src/types/DynamoPersistenceConstructorOptions.ts b/packages/idempotency/src/types/DynamoPersistenceConstructorOptions.ts index c2308b362e..8ca7f7d4f3 100644 --- a/packages/idempotency/src/types/DynamoPersistenceConstructorOptions.ts +++ b/packages/idempotency/src/types/DynamoPersistenceConstructorOptions.ts @@ -4,7 +4,7 @@ type DynamoPersistenceConstructorOptions = { statusAttr?: string expiryAttr?: string inProgressExpiryAttr?: string - data_attr?: string + dataAttr?: string }; export { diff --git a/packages/idempotency/src/IdempotencyOptions.ts b/packages/idempotency/src/types/IdempotencyOptions.ts similarity index 65% rename from packages/idempotency/src/IdempotencyOptions.ts rename to packages/idempotency/src/types/IdempotencyOptions.ts index 53893cfe42..f7b445e65f 100644 --- a/packages/idempotency/src/IdempotencyOptions.ts +++ b/packages/idempotency/src/types/IdempotencyOptions.ts @@ -1,4 +1,4 @@ -import { PersistenceLayer } from './persistence/PersistenceLayer'; +import { PersistenceLayer } from '../persistence/PersistenceLayer'; type IdempotencyOptions = { dataKeywordArgument: string diff --git a/packages/idempotency/src/types/PersistenceLayer.ts b/packages/idempotency/src/types/PersistenceLayer.ts new file mode 100644 index 0000000000..3654648846 --- /dev/null +++ b/packages/idempotency/src/types/PersistenceLayer.ts @@ -0,0 +1,7 @@ +type PersistenceLayerConfigureOptions = { + functionName?: string +}; + +export { + PersistenceLayerConfigureOptions +}; \ No newline at end of file diff --git a/packages/idempotency/src/types/index.ts b/packages/idempotency/src/types/index.ts index 850ccbd19c..307c6bc093 100644 --- a/packages/idempotency/src/types/index.ts +++ b/packages/idempotency/src/types/index.ts @@ -1,2 +1,3 @@ export * from './AnyFunction'; -export * from './IdempotencyRecordStatus'; \ No newline at end of file +export * from './IdempotencyRecordStatus'; +export * from './PersistenceLayer'; \ No newline at end of file diff --git a/packages/idempotency/tests/helpers/populateEnvironmentVariables.ts b/packages/idempotency/tests/helpers/populateEnvironmentVariables.ts index 42b8feeb63..d38651b714 100644 --- a/packages/idempotency/tests/helpers/populateEnvironmentVariables.ts +++ b/packages/idempotency/tests/helpers/populateEnvironmentVariables.ts @@ -1,2 +1,7 @@ // Reserved variables -process.env.AWS_REGION = 'us-east-1'; +process.env._X_AMZN_TRACE_ID = '1-abcdef12-3456abcdef123456abcdef12'; +process.env.AWS_LAMBDA_FUNCTION_NAME = 'my-lambda-function'; +process.env.AWS_EXECUTION_ENV = 'nodejs16.x'; +process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE = '128'; +process.env.AWS_REGION = 'eu-west-1'; +process.env._HANDLER = 'index.handler'; \ No newline at end of file diff --git a/packages/idempotency/tests/unit/EnvironmentVariableService.test.ts b/packages/idempotency/tests/unit/EnvironmentVariableService.test.ts deleted file mode 100644 index 2480c328cb..0000000000 --- a/packages/idempotency/tests/unit/EnvironmentVariableService.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Test EnvironmentVariableService class - * - * @group unit/idempotency/all - */ -import { EnvironmentVariablesService } from '../../src/EnvironmentVariablesService'; - -describe('Class: EnvironmentVariableService', ()=> { - describe('Method: getLambdaFunctionName', ()=> { - beforeEach(() => { - process.env.AWS_LAMBDA_FUNCTION_NAME = 'testFunction'; - }); - - afterEach(()=> { - delete process.env.AWS_LAMBDA_FUNCTION_NAME; - }); - - test('When called, it gets the Lambda function name from the environment variable', ()=> { - const expectedName = process.env.AWS_LAMBDA_FUNCTION_NAME; - - const lambdaName = new EnvironmentVariablesService().getLambdaFunctionName(); - - expect(lambdaName).toEqual(expectedName); - }); - - test('When called without the environment variable set, it returns an empty string', ()=> { - delete process.env.AWS_LAMBDA_FUNCTION_NAME; - - const lambdaName = new EnvironmentVariablesService().getLambdaFunctionName(); - - expect(lambdaName).toEqual(''); - }); - }); -}); \ No newline at end of file diff --git a/packages/idempotency/tests/unit/config/EnvironmentVariableService.test.ts b/packages/idempotency/tests/unit/config/EnvironmentVariableService.test.ts new file mode 100644 index 0000000000..f4e44333e9 --- /dev/null +++ b/packages/idempotency/tests/unit/config/EnvironmentVariableService.test.ts @@ -0,0 +1,50 @@ +/** + * Test EnvironmentVariableService class + * + * @group unit/idempotency/all + */ +import { EnvironmentVariablesService } from '../../../src/config'; + +describe('Class: EnvironmentVariableService', () => { + + const ENVIRONMENT_VARIABLES = process.env; + + beforeEach(() => { + jest.resetModules(); + process.env = { ...ENVIRONMENT_VARIABLES }; + }); + + afterEach(() => { + process.env = ENVIRONMENT_VARIABLES; + }); + + describe('Method: getFunctionName', () => { + + test('When called, it gets the Lambda function name from the environment variable', () => { + + // Prepare + const expectedName = process.env.AWS_LAMBDA_FUNCTION_NAME; + + // Act + const lambdaName = new EnvironmentVariablesService().getFunctionName(); + + // Assess + expect(lambdaName).toEqual(expectedName); + + }); + + test('When called without the environment variable set, it returns an empty string', () => { + + // Prepare + delete process.env.AWS_LAMBDA_FUNCTION_NAME; + + // Act + const lambdaName = new EnvironmentVariablesService().getFunctionName(); + + // Assess + expect(lambdaName).toEqual(''); + + }); + + }); +}); \ No newline at end of file diff --git a/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts b/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts index 8ca16818dd..46d3b3e23d 100644 --- a/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts +++ b/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts @@ -1,19 +1,24 @@ +/** + * Test DynamoDBPersistenceLayer class + * + * @group unit/idempotency/all + */ import { DeleteCommand, DynamoDBDocument, GetCommand, PutCommand, UpdateCommand } from '@aws-sdk/lib-dynamodb'; import { mockClient } from 'aws-sdk-client-mock'; import 'aws-sdk-client-mock-jest'; import { IdempotencyItemAlreadyExistsError, IdempotencyItemNotFoundError } from '../../../src/Exceptions'; -import { DynamoDBPersistenceLayer } from '../../../src/persistence/DynamoDbPersistenceLayer'; +import { DynamoDBPersistenceLayer } from '../../../src/persistence/DynamoDBPersistenceLayer'; import { IdempotencyRecord } from '../../../src/persistence/IdempotencyRecord'; +import { DynamoPersistenceConstructorOptions } from '../../../src/types/DynamoPersistenceConstructorOptions'; import { IdempotencyRecordStatus } from '../../../src/types/IdempotencyRecordStatus'; -/** - * Test DynamoDBPersistenceLayer class - * - * @group unit/idempotency/all - */ +describe('Class: DynamoDBPersistenceLayer', () => { + + const dummyTableName = 'someTable'; + const dummyKey = 'someKey'; + + class TestDynamoDBPersistenceLayer extends DynamoDBPersistenceLayer { -describe('Class: DynamoDbPersistenceLayer', () => { - class TestDynamoPersistenceLayer extends DynamoDBPersistenceLayer { public _deleteRecord(record: IdempotencyRecord): Promise { return super._deleteRecord(record); } @@ -29,13 +34,61 @@ describe('Class: DynamoDbPersistenceLayer', () => { public _updateRecord(record: IdempotencyRecord): Promise { return super._updateRecord(record); } + } beforeEach(() => { jest.clearAllMocks(); }); + describe('Method: constructor', () => { + + test('when instantiated with minimum options it creates an instance with default values', () => { + + // Prepare + const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName }); + + // Act / Assess + expect(persistenceLayer).toEqual(expect.objectContaining({ + tableName: dummyTableName, + keyAttr: 'id', + statusAttr: 'status', + expiryAttr: 'expiration', + inProgressExpiryAttr: 'in_progress_expiry_attr', + dataAttr: 'data' + })); + + }); + + test('when instantiated with specific options it creates an instance with correct values', () => { + + // Prepare + const testDynamoDBPersistenceLayerOptions: DynamoPersistenceConstructorOptions = { + tableName: dummyTableName, + keyAttr: dummyKey, + statusAttr: 'someStatusAttr', + expiryAttr: 'someExpiryAttr', + inProgressExpiryAttr: 'someInProgressExpiryAttr', + dataAttr: 'someDataAttr' + }; + const persistenceLayer = new TestDynamoDBPersistenceLayer(testDynamoDBPersistenceLayerOptions); + + // Act / Assess + expect(persistenceLayer).toEqual(expect.objectContaining({ + tableName: dummyTableName, + keyAttr: dummyKey, + statusAttr: testDynamoDBPersistenceLayerOptions.statusAttr, + expiryAttr: testDynamoDBPersistenceLayerOptions.expiryAttr, + inProgressExpiryAttr: testDynamoDBPersistenceLayerOptions.inProgressExpiryAttr, + dataAttr: testDynamoDBPersistenceLayerOptions.dataAttr + })); + + }); + + }); + describe('Method: _putRecord', () => { + const currentDateInMilliseconds = 1000; const currentDateInSeconds = 1; @@ -43,17 +96,15 @@ describe('Class: DynamoDbPersistenceLayer', () => { jest.spyOn(Date, 'now').mockReturnValue(currentDateInMilliseconds); }); - test('when called with a record that meets conditions, it puts record in dynamo table', async () => { - // Prepare - const tableName = 'tableName'; - const persistenceLayer = new TestDynamoPersistenceLayer({ tableName }); + test('when called with a record that meets conditions, it puts record in DynamoDB table', async () => { - const key = 'key'; + // Prepare + const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName }); const status = IdempotencyRecordStatus.EXPIRED; const expiryTimestamp = 0; const inProgressExpiryTimestamp = 0; const record = new IdempotencyRecord({ - idempotencyKey: key, + idempotencyKey: dummyKey, status, expiryTimestamp, inProgressExpiryTimestamp @@ -65,104 +116,84 @@ describe('Class: DynamoDbPersistenceLayer', () => { // Assess expect(dynamoClient).toReceiveCommandWith(PutCommand, { - TableName: tableName, - Item: { 'id': key, 'expiration': expiryTimestamp, status: status }, + TableName: dummyTableName, + Item: { 'id': dummyKey, 'expiration': expiryTimestamp, status: status }, ExpressionAttributeNames: { '#id': 'id', '#expiry': 'expiration', '#status': 'status' }, ExpressionAttributeValues: { ':now': currentDateInSeconds, ':inprogress': IdempotencyRecordStatus.INPROGRESS }, ConditionExpression: 'attribute_not_exists(#id) OR #expiry < :now OR NOT #status = :inprogress' }); + }); test('when called with a record that fails any condition, it throws IdempotencyItemAlreadyExistsError', async () => { + // Prepare - const tableName = 'tableName'; - const persistenceLayer = new TestDynamoPersistenceLayer({ tableName }); - - const key = 'key'; + const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName }); const status = IdempotencyRecordStatus.EXPIRED; const expiryTimestamp = 0; const inProgressExpiryTimestamp = 0; const record = new IdempotencyRecord({ - idempotencyKey: key, + idempotencyKey: dummyKey, status, expiryTimestamp, inProgressExpiryTimestamp }); - const dynamoClient = mockClient(DynamoDBDocument).on(PutCommand).rejects({ name: 'ConditionalCheckFailedException' }); - // Act - let error: unknown; - try { - await persistenceLayer._putRecord(record); - } catch (e){ - error = e; - } - - // Assess + // Act / Assess + await expect(persistenceLayer._putRecord(record)).rejects.toThrowError(IdempotencyItemAlreadyExistsError); expect(dynamoClient).toReceiveCommandWith(PutCommand, { - TableName: tableName, - Item: { 'id': key, 'expiration': expiryTimestamp, status: status }, + TableName: dummyTableName, + Item: { 'id': dummyKey, 'expiration': expiryTimestamp, status: status }, ExpressionAttributeNames: { '#id': 'id', '#expiry': 'expiration', '#status': 'status' }, ExpressionAttributeValues: { ':now': currentDateInSeconds, ':inprogress': IdempotencyRecordStatus.INPROGRESS }, ConditionExpression: 'attribute_not_exists(#id) OR #expiry < :now OR NOT #status = :inprogress' }); - expect(error).toBeInstanceOf(IdempotencyItemAlreadyExistsError); + }); test('when encountering an unknown error, it throws the causing error', async () => { - // Prepare - const tableName = 'tableName'; - const persistenceLayer = new TestDynamoPersistenceLayer({ tableName }); - const key = 'key'; + // Prepare + const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName }); const status = IdempotencyRecordStatus.EXPIRED; const expiryTimestamp = 0; const inProgressExpiryTimestamp = 0; const record = new IdempotencyRecord({ - idempotencyKey: key, + idempotencyKey: dummyKey, status, expiryTimestamp, inProgressExpiryTimestamp }); - const dynamoClient = mockClient(DynamoDBDocument).on(PutCommand).rejects(new Error()); - // Act - let error: unknown; - try { - await persistenceLayer._putRecord(record); - } catch (e){ - error = e; - } - - // Assess + // Act / Assess + await expect(persistenceLayer._putRecord(record)).rejects.toThrow(); expect(dynamoClient).toReceiveCommandWith(PutCommand, { - TableName: tableName, - Item: { 'id': key, 'expiration': expiryTimestamp, status: status }, + TableName: dummyTableName, + Item: { 'id': dummyKey, 'expiration': expiryTimestamp, status: status }, ExpressionAttributeNames: { '#id': 'id', '#expiry': 'expiration', '#status': 'status' }, ExpressionAttributeValues: { ':now': currentDateInSeconds, ':inprogress': IdempotencyRecordStatus.INPROGRESS }, ConditionExpression: 'attribute_not_exists(#id) OR #expiry < :now OR NOT #status = :inprogress' }); - expect(error).toBe(error); + }); + }); describe('Method: _getRecord', () => { - test('when called with a record whose key exists, it gets the correct record', async () => { - // Prepare - const tableName = 'tableName'; - const persistenceLayer = new TestDynamoPersistenceLayer({ tableName }); - const key = 'key'; + test('when called with a record whose key exists, it gets the correct record', async () => { + // Prepare + const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName }); const status = IdempotencyRecordStatus.INPROGRESS; const expiryTimestamp = 10; const inProgressExpiryTimestamp = 10; const responseData = {}; const dynamoClient = mockClient(DynamoDBDocument).on(GetCommand).resolves({ Item: { - id: key, + id: dummyKey, status, 'expiration': expiryTimestamp, 'in_progress_expiry_attr': inProgressExpiryTimestamp, @@ -172,65 +203,55 @@ describe('Class: DynamoDbPersistenceLayer', () => { jest.spyOn(Date, 'now').mockReturnValue(0); // Act - const record: IdempotencyRecord = await persistenceLayer._getRecord(key); + const record: IdempotencyRecord = await persistenceLayer._getRecord(dummyKey); // Assess expect(dynamoClient).toReceiveCommandWith(GetCommand, { - TableName: tableName, + TableName: dummyTableName, Key: { - id: key + id: dummyKey }, ConsistentRead: true }); expect(record.getStatus()).toEqual(IdempotencyRecordStatus.INPROGRESS); - expect(record.idempotencyKey).toEqual(key); + expect(record.idempotencyKey).toEqual(dummyKey); expect(record.inProgressExpiryTimestamp).toEqual(inProgressExpiryTimestamp); expect(record.responseData).toEqual(responseData); expect(record.expiryTimestamp).toEqual(expiryTimestamp); }); test('when called with a record whose key does not exist, it throws IdempotencyItemNotFoundError', async () => { - // Prepare - const tableName = 'tableName'; - const persistenceLayer = new TestDynamoPersistenceLayer({ tableName }); - - const key = 'key'; + // Prepare + const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName }); const dynamoClient = mockClient(DynamoDBDocument).on(GetCommand).resolves({ Item: undefined }); jest.spyOn(Date, 'now').mockReturnValue(0); - // Act - let error: unknown; - try { - await persistenceLayer._getRecord(key); - } catch (e){ - error = e; - } - - // Assess + // Act / Assess + await expect(persistenceLayer._getRecord(dummyKey)).rejects.toThrowError(IdempotencyItemNotFoundError); expect(dynamoClient).toReceiveCommandWith(GetCommand, { - TableName: tableName, + TableName: dummyTableName, Key: { - id: key + id: dummyKey }, ConsistentRead: true }); - expect(error).toBeInstanceOf(IdempotencyItemNotFoundError); + }); + }); describe('Method: _updateRecord', () => { + test('when called to update a record, it resolves successfully', async () => { - // Prepare - const tableName = 'tableName'; - const persistenceLayer = new TestDynamoPersistenceLayer({ tableName }); - const key = 'key'; + // Prepare + const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName }); const status = IdempotencyRecordStatus.EXPIRED; const expiryTimestamp = 0; const inProgressExpiryTimestamp = 0; const record = new IdempotencyRecord({ - idempotencyKey: key, + idempotencyKey: dummyKey, status, expiryTimestamp, inProgressExpiryTimestamp @@ -242,27 +263,28 @@ describe('Class: DynamoDbPersistenceLayer', () => { // Assess expect(dynamoClient).toReceiveCommandWith(UpdateCommand, { - TableName: tableName, - Key: { id: key }, + TableName: dummyTableName, + Key: { id: dummyKey }, UpdateExpression: 'SET #status = :status, #expiry = :expiry', ExpressionAttributeNames: { '#status': 'status', '#expiry': 'expiration' }, ExpressionAttributeValues: { ':status': IdempotencyRecordStatus.EXPIRED,':expiry': expiryTimestamp }, }); + }); + }); describe('Method: _deleteRecord', () => { + test('when called with a valid record, record is deleted', async () => { - // Prepare - const tableName = 'tableName'; - const persistenceLayer = new TestDynamoPersistenceLayer({ tableName }); - const key = 'key'; + // Prepare + const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName }); const status = IdempotencyRecordStatus.EXPIRED; const expiryTimestamp = 0; const inProgressExpiryTimestamp = 0; const record = new IdempotencyRecord({ - idempotencyKey: key, + idempotencyKey: dummyKey, status, expiryTimestamp, inProgressExpiryTimestamp @@ -274,9 +296,12 @@ describe('Class: DynamoDbPersistenceLayer', () => { // Assess expect(dynamoClient).toReceiveCommandWith(DeleteCommand, { - TableName: tableName, - Key: { id: key } + TableName: dummyTableName, + Key: { id: dummyKey } }); + }); + }); + }); \ No newline at end of file diff --git a/packages/idempotency/tests/unit/persistence/IdempotencyRecord.test.ts b/packages/idempotency/tests/unit/persistence/IdempotencyRecord.test.ts index 7f0c00db04..3d42ed2059 100644 --- a/packages/idempotency/tests/unit/persistence/IdempotencyRecord.test.ts +++ b/packages/idempotency/tests/unit/persistence/IdempotencyRecord.test.ts @@ -1,11 +1,12 @@ -import { IdempotencyInvalidStatusError } from '../../../src/Exceptions'; -import { IdempotencyRecord } from '../../../src/persistence/IdempotencyRecord'; -import { IdempotencyRecordStatus } from '../../../src/types/IdempotencyRecordStatus'; /** * Test IdempotencyRecord class * * @group unit/idempotency/all */ +import { IdempotencyInvalidStatusError } from '../../../src/Exceptions'; +import { IdempotencyRecord } from '../../../src/persistence/IdempotencyRecord'; +import { IdempotencyRecordStatus } from '../../../src/types/IdempotencyRecordStatus'; + const mockIdempotencyKey = '123'; const mockData = undefined; const mockInProgressExpiry = 123; diff --git a/packages/idempotency/tests/unit/persistence/PersistenceLayer.test.ts b/packages/idempotency/tests/unit/persistence/PersistenceLayer.test.ts index ee899c3342..5fe3c42cf5 100644 --- a/packages/idempotency/tests/unit/persistence/PersistenceLayer.test.ts +++ b/packages/idempotency/tests/unit/persistence/PersistenceLayer.test.ts @@ -4,7 +4,6 @@ * @group unit/idempotency/all */ import { createHash, Hash } from 'crypto'; -import { EnvironmentVariablesService } from '../../../src/EnvironmentVariablesService'; import { IdempotencyRecord, PersistenceLayer } from '../../../src/persistence'; import { IdempotencyRecordStatus } from '../../../src/types/IdempotencyRecordStatus'; @@ -16,7 +15,10 @@ const cryptoUpdateMock = jest.fn(); const cryptoDigestMock = jest.fn(); const mockDigest = 'hashDigest'; -describe('Class: Persistence Layer', ()=> { +describe('Class: PersistenceLayer', () => { + + const dummyData = 'someData'; + const idempotentFunctionName = 'foo'; const deleteRecord = jest.fn(); const getRecord = jest.fn(); @@ -34,102 +36,146 @@ describe('Class: Persistence Layer', ()=> { protected _updateRecord = updateRecord; } - describe('Method: saveInProgress', ()=> { - beforeEach(()=> { - putRecord.mockClear(); - (createHash as jest.MockedFunction).mockReturnValue( - { - update: cryptoUpdateMock, - digest: cryptoDigestMock.mockReturnValue(mockDigest) - } as unknown as Hash - ); + describe('Method: configure', () => { + + test('when called without options it maintains the default value for the key prefix', () => { + + // Prepare + const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); + persistenceLayer.configure(); + + expect(persistenceLayer).toEqual(expect.objectContaining({ + idempotencyKeyPrefix: process.env.AWS_LAMBDA_FUNCTION_NAME, + })); + }); - test('When called, it saves an IN_PROGRESS idempotency record via _putRecord()', async ()=> { - const data = 'someData'; + test('when called with an empty option object it maintains the default value for the key prefix', () => { + + // Prepare const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); + persistenceLayer.configure({}); - await persistenceLayer.saveInProgress(data); + expect(persistenceLayer).toEqual(expect.objectContaining({ + idempotencyKeyPrefix: process.env.AWS_LAMBDA_FUNCTION_NAME, + })); - const savedIdempotencyRecord: IdempotencyRecord = putRecord.mock.calls[0][0]; - expect(savedIdempotencyRecord.getStatus()).toBe(IdempotencyRecordStatus.INPROGRESS); }); - test('When called, it creates an idempotency key from the function name and a digest of the md5 hash of the data', async ()=> { - const data = 'someData'; - const lambdaFunctionName = 'LambdaName'; - jest.spyOn(EnvironmentVariablesService.prototype, 'getLambdaFunctionName').mockReturnValue(lambdaFunctionName); + test('when called with an empty string as functionName it maintains the default value for the key prefix', () => { - const functionName = 'functionName'; + // Prepare + const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); + persistenceLayer.configure({ functionName: '' }); - const expectedIdempotencyKey = lambdaFunctionName + '.' + functionName + '#' + mockDigest; + expect(persistenceLayer).toEqual(expect.objectContaining({ + idempotencyKeyPrefix: process.env.AWS_LAMBDA_FUNCTION_NAME, + })); + + }); + + test('when called with a valid functionName it concatenates the key prefix correctly', () => { + + // Prepare + const expectedIdempotencyKeyPrefix = `${process.env.AWS_LAMBDA_FUNCTION_NAME}.${idempotentFunctionName}`; const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); - persistenceLayer.configure(functionName); + persistenceLayer.configure({ functionName: idempotentFunctionName }); - await persistenceLayer.saveInProgress(data); + expect(persistenceLayer).toEqual(expect.objectContaining({ + idempotencyKeyPrefix: expectedIdempotencyKeyPrefix + })); - const savedIdempotencyRecord: IdempotencyRecord = putRecord.mock.calls[0][0]; + }); - expect(createHash).toHaveBeenCalledWith( - expect.stringMatching('md5'), - ); - expect(cryptoUpdateMock).toHaveBeenCalledWith(expect.stringMatching(data)); - expect(cryptoDigestMock).toHaveBeenCalledWith( - expect.stringMatching('base64') + }); + + describe('Method: saveInProgress', () => { + + beforeEach(() => { + putRecord.mockClear(); + (createHash as jest.MockedFunction).mockReturnValue( + { + update: cryptoUpdateMock, + digest: cryptoDigestMock.mockReturnValue(mockDigest) + } as unknown as Hash ); - expect(savedIdempotencyRecord.idempotencyKey).toEqual(expectedIdempotencyKey); + }); - test('When called without a function name, it creates an idempotency key from the Lambda name only and a digest of the md5 hash of the data', async ()=> { - const data = 'someData'; - const lambdaFunctionName = 'LambdaName'; - jest.spyOn(EnvironmentVariablesService.prototype, 'getLambdaFunctionName').mockReturnValue(lambdaFunctionName); + test('when called, it saves an IN_PROGRESS idempotency record via _putRecord()', async () => { - const expectedIdempotencyKey = lambdaFunctionName + '.' + '#' + mockDigest; + // Prepare const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); - persistenceLayer.configure(); - await persistenceLayer.saveInProgress(data); + // Act + await persistenceLayer.saveInProgress(dummyData); + // Assess const savedIdempotencyRecord: IdempotencyRecord = putRecord.mock.calls[0][0]; + expect(savedIdempotencyRecord.getStatus()).toBe(IdempotencyRecordStatus.INPROGRESS); + + }); + test('when called, it creates an idempotency key from the function name and a digest of the md5 hash of the data', async () => { + + // Prepare + const expectedIdempotencyKey = `${process.env.AWS_LAMBDA_FUNCTION_NAME}.${idempotentFunctionName}#${mockDigest}`; + const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); + persistenceLayer.configure({ functionName: idempotentFunctionName }); + + // Act + await persistenceLayer.saveInProgress(dummyData); + + // Assess + const savedIdempotencyRecord: IdempotencyRecord = putRecord.mock.calls[0][0]; expect(createHash).toHaveBeenCalledWith( expect.stringMatching('md5'), ); - expect(cryptoUpdateMock).toHaveBeenCalledWith(expect.stringMatching(data)); + expect(cryptoUpdateMock).toHaveBeenCalledWith(expect.stringMatching(dummyData)); expect(cryptoDigestMock).toHaveBeenCalledWith( expect.stringMatching('base64') ); expect(savedIdempotencyRecord.idempotencyKey).toEqual(expectedIdempotencyKey); + }); - test('When called, it sets the expiry timestamp to one hour in the future', async ()=> { + test('when called, it sets the expiry timestamp to one hour in the future', async () => { + + // Prepare const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); - const data = 'someData'; const currentMillisTime = 3000; const currentSeconds = currentMillisTime / 1000; const oneHourSeconds = 60 * 60; jest.spyOn(Date, 'now').mockReturnValue(currentMillisTime); - await persistenceLayer.saveInProgress(data); + // Act + await persistenceLayer.saveInProgress(dummyData); + // Assess const savedIdempotencyRecord: IdempotencyRecord = putRecord.mock.calls[0][0]; expect(savedIdempotencyRecord.expiryTimestamp).toEqual(currentSeconds + oneHourSeconds); }); - test('When called without data, it logs a warning', async ()=> { - const consoleSpy = jest.spyOn(console, 'warn'); + test('when called without data, it logs a warning', async () => { + + // Prepare + const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => ({})); const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); + // Act await persistenceLayer.saveInProgress(''); + + // Assess expect(consoleSpy).toHaveBeenCalled(); + }); }); - describe('Method: saveSuccess', ()=> { - beforeEach(()=> { + describe('Method: saveSuccess', () => { + + beforeEach(() => { updateRecord.mockClear(); (createHash as jest.MockedFunction).mockReturnValue( { @@ -139,55 +185,59 @@ describe('Class: Persistence Layer', ()=> { ); }); - test('When called, it updates the idempotency record status to COMPLETED', async () => { - const data = 'someData'; + test('when called, it updates the idempotency record status to COMPLETED', async () => { + + // Prepare const result = {}; const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); - await persistenceLayer.saveSuccess(data, result); + // Act + await persistenceLayer.saveSuccess(dummyData, result); + // Assess const savedIdempotencyRecord: IdempotencyRecord = updateRecord.mock.calls[0][0]; expect(savedIdempotencyRecord.getStatus()).toBe(IdempotencyRecordStatus.COMPLETED); }); - test('When called, it generates the idempotency key from the function name and a digest of the md5 hash of the data', async ()=> { - const data = 'someData'; - const result = {}; - const lambdaFunctionName = 'LambdaName'; - jest.spyOn(EnvironmentVariablesService.prototype, 'getLambdaFunctionName').mockReturnValue(lambdaFunctionName); - - const functionName = 'functionName'; + test('when called, it generates the idempotency key from the function name and a digest of the md5 hash of the data', async () => { - const expectedIdempotencyKey = lambdaFunctionName + '.' + functionName + '#' + mockDigest; + // Prepare + const result = {}; + const expectedIdempotencyKey = `${process.env.AWS_LAMBDA_FUNCTION_NAME}.${idempotentFunctionName}#${mockDigest}`; const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); - persistenceLayer.configure(functionName); + persistenceLayer.configure({ functionName: idempotentFunctionName }); - await persistenceLayer.saveSuccess(data, result); + // Act + await persistenceLayer.saveSuccess(dummyData, result); + // Assess const savedIdempotencyRecord: IdempotencyRecord = updateRecord.mock.calls[0][0]; - expect(createHash).toHaveBeenCalledWith( expect.stringMatching('md5'), ); - expect(cryptoUpdateMock).toHaveBeenCalledWith(expect.stringMatching(data)); + expect(cryptoUpdateMock).toHaveBeenCalledWith(expect.stringMatching(dummyData)); expect(cryptoDigestMock).toHaveBeenCalledWith( expect.stringMatching('base64') ); expect(savedIdempotencyRecord.idempotencyKey).toEqual(expectedIdempotencyKey); + }); - test('When called, it sets the expiry timestamp to one hour in the future', async ()=> { + test('when called, it sets the expiry timestamp to one hour in the future', async () => { + + // Prepare const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); - const data = 'someData'; const result = {}; const currentMillisTime = 3000; const currentSeconds = currentMillisTime / 1000; const oneHourSeconds = 60 * 60; jest.spyOn(Date, 'now').mockReturnValue(currentMillisTime); - await persistenceLayer.saveSuccess(data, result); + // Act + await persistenceLayer.saveSuccess(dummyData, result); + // Assess const savedIdempotencyRecord: IdempotencyRecord = updateRecord.mock.calls[0][0]; expect(savedIdempotencyRecord.expiryTimestamp).toEqual(currentSeconds + oneHourSeconds); @@ -195,8 +245,9 @@ describe('Class: Persistence Layer', ()=> { }); - describe('Method: getRecord', ()=> { - beforeEach(()=> { + describe('Method: getRecord', () => { + + beforeEach(() => { putRecord.mockClear(); (createHash as jest.MockedFunction).mockReturnValue( { @@ -205,24 +256,26 @@ describe('Class: Persistence Layer', ()=> { } as unknown as Hash ); }); - test('When called, it gets the record for the idempotency key for the data passed in', ()=> { - const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); - const data = 'someData'; - const lambdaFunctionName = 'LambdaName'; - jest.spyOn(EnvironmentVariablesService.prototype, 'getLambdaFunctionName').mockReturnValue(lambdaFunctionName); - const functionName = 'functionName'; - const expectedIdempotencyKey = lambdaFunctionName + '.' + functionName + '#' + mockDigest; - persistenceLayer.configure(functionName); + test('when called, it gets the record for the idempotency key for the data passed in', () => { - persistenceLayer.getRecord(data); + // Prepare + const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); + const expectedIdempotencyKey = `${process.env.AWS_LAMBDA_FUNCTION_NAME}.${idempotentFunctionName}#${mockDigest}`; + persistenceLayer.configure({ functionName: idempotentFunctionName }); + + // Act + persistenceLayer.getRecord(dummyData); + // Assess expect(getRecord).toHaveBeenCalledWith(expectedIdempotencyKey); + }); }); - describe('Method: deleteRecord', ()=> { - beforeEach(()=> { + describe('Method: deleteRecord', () => { + + beforeEach(() => { putRecord.mockClear(); (createHash as jest.MockedFunction).mockReturnValue( { @@ -232,20 +285,20 @@ describe('Class: Persistence Layer', ()=> { ); }); - test('When called, it deletes the record with the idempotency key for the data passed in', ()=> { - const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); - const data = 'someData'; - const lambdaFunctionName = 'LambdaName'; - jest.spyOn(EnvironmentVariablesService.prototype, 'getLambdaFunctionName').mockReturnValue(lambdaFunctionName); + test('when called, it deletes the record with the idempotency key for the data passed in', () => { - const functionName = 'functionName'; - const expectedIdempotencyKey = lambdaFunctionName + '.' + functionName + '#' + mockDigest; - persistenceLayer.configure(functionName); + // Prepare + const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); + const expectedIdempotencyKey = `${process.env.AWS_LAMBDA_FUNCTION_NAME}.${idempotentFunctionName}#${mockDigest}`; + persistenceLayer.configure({ functionName: idempotentFunctionName }); - persistenceLayer.deleteRecord(data); + // Act + persistenceLayer.deleteRecord(dummyData); + + // Assess const deletedIdempotencyRecord: IdempotencyRecord = deleteRecord.mock.calls[0][0]; - expect(deletedIdempotencyRecord.idempotencyKey).toEqual(expectedIdempotencyKey); + }); }); }); \ No newline at end of file diff --git a/packages/parameters/package.json b/packages/parameters/package.json index fffbdbc373..2bdaceb398 100644 --- a/packages/parameters/package.json +++ b/packages/parameters/package.json @@ -30,7 +30,6 @@ "license": "MIT-0", "main": "./lib/index.js", "types": "./lib/index.d.ts", - "devDependencies": {}, "files": [ "lib" ], @@ -41,7 +40,9 @@ "bugs": { "url": "https://github.com/awslabs/aws-lambda-powertools-typescript/issues" }, - "dependencies": {}, + "dependencies": { + "@aws-sdk/util-base64-node": "^3.209.0" + }, "keywords": [ "aws", "lambda", @@ -51,4 +52,4 @@ "serverless", "nodejs" ] -} \ No newline at end of file +} From 16529fc777c1210947a12908a07225c6984218ef Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 24 Nov 2022 17:55:10 +0100 Subject: [PATCH 14/15] chore: add install step in docs workflow (#1183) * fix: add build on cache miss * chore: excluded unreleased utils from api docs --- .github/workflows/reusable-publish-docs.yml | 11 +++++++++-- packages/idempotency/package.json | 2 +- packages/parameters/package.json | 3 ++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/reusable-publish-docs.yml b/.github/workflows/reusable-publish-docs.yml index 14d9e5deb7..bfa14eb6aa 100644 --- a/.github/workflows/reusable-publish-docs.yml +++ b/.github/workflows/reusable-publish-docs.yml @@ -48,9 +48,16 @@ jobs: with: path: "./node_modules" key: 16-cache-utils-node-modules-${{ hashFiles('./package-lock.json') }} - # Here we assume that there will always be a cache hit because this workflow can be triggered - # only after tests have already happened on this same code + - name: Install dependencies + # We can skip the installation if there was a cache hit + if: steps.cache-node-modules.outputs.cache-hit != 'true' + # See https://github.com/npm/cli/issues/4475 to see why --foreground-scripts + run: npm ci --foreground-scripts - name: Build packages + # If there's a cache hit we still need to manually build the packages + # this would otherwise have been done automatically as a part of the + # post-install npm hook + if: steps.cache-node-modules.outputs.cache-hit == 'true' run: | npm run build -w packages/commons npm run build -w packages/logger & npm run build -w packages/tracer & npm run build -w packages/metrics diff --git a/packages/idempotency/package.json b/packages/idempotency/package.json index 8ea2117115..8448e48872 100644 --- a/packages/idempotency/package.json +++ b/packages/idempotency/package.json @@ -30,7 +30,7 @@ "license": "MIT", "main": "./lib/index.js", "types": "./lib/index.d.ts", - "typedocMain": "src/index.ts", + "typedocMain": "src/file_that_does_not_exist_so_its_ignored_from_api_docs.ts", "files": [ "lib" ], diff --git a/packages/parameters/package.json b/packages/parameters/package.json index 2bdaceb398..c66f4d71a9 100644 --- a/packages/parameters/package.json +++ b/packages/parameters/package.json @@ -30,6 +30,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", "files": [ "lib" ], @@ -52,4 +53,4 @@ "serverless", "nodejs" ] -} +} \ No newline at end of file From 3da04d7ee3a38cf3da3553a9b94fc63eb54eac31 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 25 Nov 2022 10:33:42 +0000 Subject: [PATCH 15/15] chore(release): v1.5.0 [skip ci] --- CHANGELOG.md | 19 +++++++++ examples/cdk/CHANGELOG.md | 8 ++++ examples/cdk/package-lock.json | 70 +++++++++++++++---------------- examples/cdk/package.json | 8 ++-- examples/sam/CHANGELOG.md | 8 ++++ examples/sam/package-lock.json | 70 +++++++++++++++---------------- examples/sam/package.json | 8 ++-- layer-publisher/CHANGELOG.md | 8 ++++ layer-publisher/package-lock.json | 2 +- layer-publisher/package.json | 2 +- lerna.json | 2 +- packages/commons/CHANGELOG.md | 8 ++++ packages/commons/package.json | 2 +- packages/logger/CHANGELOG.md | 17 ++++++++ packages/logger/package.json | 4 +- packages/metrics/CHANGELOG.md | 8 ++++ packages/metrics/package.json | 4 +- packages/tracer/CHANGELOG.md | 8 ++++ packages/tracer/package.json | 4 +- 19 files changed, 172 insertions(+), 88 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd87fcf97e..47b4d40f3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.5.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.4.1...v1.5.0) (2022-11-25) + + +### Bug Fixes + +* **logger:** merge child logger options correctly ([#1178](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1178)) ([cb91374](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/cb9137436cc3a10d6c869506ddd07e35963ba8b2)) + + +### Features + +* **idempotency:** Add persistence layer and DynamoDB implementation ([#1110](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1110)) ([0a6676a](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/0a6676ac24abdadaaff2d95fc8d75d3a7137a00b)) +* **logger:** disable logs while testing with `jest --silent` in dev env ([#1165](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1165)) ([6f0c307](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/6f0c30728f31d60433b3afb6983c64110c28d27e)) +* **logger:** pretty printing logs in local and non-prod environment ([#1141](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1141)) ([8d52660](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/8d52660eb6b8324e284421c2484c45d9a0839346)) +* **parameters:** added `BaseProvider` class ([#1168](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1168)) ([d717a26](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/d717a26bba086be4c01f1458422662f8bfba09a9)) + + + + + ## [1.4.1](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.4.0...v1.4.1) (2022-11-09) diff --git a/examples/cdk/CHANGELOG.md b/examples/cdk/CHANGELOG.md index 7a4c92f84f..6bdbdea263 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.5.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.4.1...v1.5.0) (2022-11-25) + +**Note:** Version bump only for package cdk-sample + + + + + ## [1.4.1](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.4.0...v1.4.1) (2022-11-09) **Note:** Version bump only for package cdk-sample diff --git a/examples/cdk/package-lock.json b/examples/cdk/package-lock.json index 691e390894..d6a1e08c92 100644 --- a/examples/cdk/package-lock.json +++ b/examples/cdk/package-lock.json @@ -1,17 +1,17 @@ { "name": "cdk-sample", - "version": "1.4.1", + "version": "1.5.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cdk-sample", - "version": "1.4.1", + "version": "1.5.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/logger": "^1.4.0", - "@aws-lambda-powertools/metrics": "^1.4.0", - "@aws-lambda-powertools/tracer": "^1.4.0", + "@aws-lambda-powertools/logger": "^1.4.1", + "@aws-lambda-powertools/metrics": "^1.4.1", + "@aws-lambda-powertools/tracer": "^1.4.1", "@aws-sdk/client-sts": "^3.53.0", "@middy/core": "^2.5.6", "@types/aws-lambda": "^8.10.86", @@ -128,35 +128,35 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@aws-lambda-powertools/commons": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.4.0.tgz", - "integrity": "sha512-8Y473kxNI8LIY/uhnk+2nsHQXgjrEL3WjZLk9L++UZ+nFRE2bJaSIWVW5V+IJJD2uPSvdhvdD1zvoLRsNT0+0w==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.4.1.tgz", + "integrity": "sha512-fGmCIQTG0vC+hllu3Ne1B2fRC3Hl2A0Q7Db7JeCXVsAEUIqzUp7blm1WVmqZsBzJ2T5XvZ3B/xMgc0ci8FGOjQ==" }, "node_modules/@aws-lambda-powertools/logger": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.4.0.tgz", - "integrity": "sha512-BhtRMWNYRGpK5FsF/c4jX3EtGylRV6SR9RAM0O7K+rQidFcKWLYPqJhVMm1CJxMk++qPawpr2kelHVEPzpG8tQ==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.4.1.tgz", + "integrity": "sha512-T0/bCUFybGSdFxaZEFKm5npz3jexorbPsNyet1WSY0sRubz3JS0o0ppJE3xrVs6t2Lz9LfzFJkFTF/aJKt3McQ==", "dependencies": { - "@aws-lambda-powertools/commons": "^1.4.0", + "@aws-lambda-powertools/commons": "^1.4.1", "lodash.clonedeep": "^4.5.0", "lodash.merge": "^4.6.2", "lodash.pickby": "^4.6.0" } }, "node_modules/@aws-lambda-powertools/metrics": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.4.0.tgz", - "integrity": "sha512-gHH2rCo922wgZS7N+Rm+tHPfKevGvUC/BY3I1sP9FscZEdpR6WnF10JF7vM+CKsAp0LczEFDhxj0eC7cK562YA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.4.1.tgz", + "integrity": "sha512-ZPfS7C/lnwETX8RhNSSPHOagNpeXniuGOUMi7cO0v8/ztB5vYJ5hjUtZ5j/Ht5alU9SLTKbehVyPdrTVfaZl0w==", "dependencies": { - "@aws-lambda-powertools/commons": "^1.4.0" + "@aws-lambda-powertools/commons": "^1.4.1" } }, "node_modules/@aws-lambda-powertools/tracer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.4.0.tgz", - "integrity": "sha512-nfhY58bLdvAdcRtsh37Ww9IoHwcQ5/08fX/t9RmOsUGFvDCY20QHGh5gKkJMpKxIMxbM4aXBfA7mFK8sOkD5uw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.4.1.tgz", + "integrity": "sha512-n+YSWg5wnbgd37XcZQ11U1hD77A7zPLvDTBUYLrbIDKwp01wNXdW2oDVF6GTP/doqoeVaJaRu8MILBoKw0h+Ow==", "dependencies": { - "@aws-lambda-powertools/commons": "^1.4.0", + "@aws-lambda-powertools/commons": "^1.4.1", "aws-xray-sdk-core": "^3.3.6" } }, @@ -8596,35 +8596,35 @@ } }, "@aws-lambda-powertools/commons": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.4.0.tgz", - "integrity": "sha512-8Y473kxNI8LIY/uhnk+2nsHQXgjrEL3WjZLk9L++UZ+nFRE2bJaSIWVW5V+IJJD2uPSvdhvdD1zvoLRsNT0+0w==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.4.1.tgz", + "integrity": "sha512-fGmCIQTG0vC+hllu3Ne1B2fRC3Hl2A0Q7Db7JeCXVsAEUIqzUp7blm1WVmqZsBzJ2T5XvZ3B/xMgc0ci8FGOjQ==" }, "@aws-lambda-powertools/logger": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.4.0.tgz", - "integrity": "sha512-BhtRMWNYRGpK5FsF/c4jX3EtGylRV6SR9RAM0O7K+rQidFcKWLYPqJhVMm1CJxMk++qPawpr2kelHVEPzpG8tQ==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.4.1.tgz", + "integrity": "sha512-T0/bCUFybGSdFxaZEFKm5npz3jexorbPsNyet1WSY0sRubz3JS0o0ppJE3xrVs6t2Lz9LfzFJkFTF/aJKt3McQ==", "requires": { - "@aws-lambda-powertools/commons": "^1.4.0", + "@aws-lambda-powertools/commons": "^1.4.1", "lodash.clonedeep": "^4.5.0", "lodash.merge": "^4.6.2", "lodash.pickby": "^4.6.0" } }, "@aws-lambda-powertools/metrics": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.4.0.tgz", - "integrity": "sha512-gHH2rCo922wgZS7N+Rm+tHPfKevGvUC/BY3I1sP9FscZEdpR6WnF10JF7vM+CKsAp0LczEFDhxj0eC7cK562YA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.4.1.tgz", + "integrity": "sha512-ZPfS7C/lnwETX8RhNSSPHOagNpeXniuGOUMi7cO0v8/ztB5vYJ5hjUtZ5j/Ht5alU9SLTKbehVyPdrTVfaZl0w==", "requires": { - "@aws-lambda-powertools/commons": "^1.4.0" + "@aws-lambda-powertools/commons": "^1.4.1" } }, "@aws-lambda-powertools/tracer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.4.0.tgz", - "integrity": "sha512-nfhY58bLdvAdcRtsh37Ww9IoHwcQ5/08fX/t9RmOsUGFvDCY20QHGh5gKkJMpKxIMxbM4aXBfA7mFK8sOkD5uw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.4.1.tgz", + "integrity": "sha512-n+YSWg5wnbgd37XcZQ11U1hD77A7zPLvDTBUYLrbIDKwp01wNXdW2oDVF6GTP/doqoeVaJaRu8MILBoKw0h+Ow==", "requires": { - "@aws-lambda-powertools/commons": "^1.4.0", + "@aws-lambda-powertools/commons": "^1.4.1", "aws-xray-sdk-core": "^3.3.6" } }, diff --git a/examples/cdk/package.json b/examples/cdk/package.json index 998e9bec55..d8abb1762a 100644 --- a/examples/cdk/package.json +++ b/examples/cdk/package.json @@ -1,6 +1,6 @@ { "name": "cdk-sample", - "version": "1.4.1", + "version": "1.5.0", "author": { "name": "Amazon Web Services", "url": "https://aws.amazon.com" @@ -39,9 +39,9 @@ "typescript": "^4.1.3" }, "dependencies": { - "@aws-lambda-powertools/logger": "^1.4.0", - "@aws-lambda-powertools/metrics": "^1.4.0", - "@aws-lambda-powertools/tracer": "^1.4.0", + "@aws-lambda-powertools/logger": "^1.4.1", + "@aws-lambda-powertools/metrics": "^1.4.1", + "@aws-lambda-powertools/tracer": "^1.4.1", "@aws-sdk/client-sts": "^3.53.0", "@middy/core": "^2.5.6", "@types/aws-lambda": "^8.10.86", diff --git a/examples/sam/CHANGELOG.md b/examples/sam/CHANGELOG.md index f7d6a1082a..4ae3f0ce2d 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.5.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.4.1...v1.5.0) (2022-11-25) + +**Note:** Version bump only for package sam-example + + + + + ## [1.4.1](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.4.0...v1.4.1) (2022-11-09) **Note:** Version bump only for package sam-example diff --git a/examples/sam/package-lock.json b/examples/sam/package-lock.json index f2883d1f83..8f347295ae 100644 --- a/examples/sam/package-lock.json +++ b/examples/sam/package-lock.json @@ -1,17 +1,17 @@ { "name": "sam-example", - "version": "1.4.1", + "version": "1.5.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "sam-example", - "version": "1.4.1", + "version": "1.5.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/logger": "^1.4.0", - "@aws-lambda-powertools/metrics": "^1.4.0", - "@aws-lambda-powertools/tracer": "^1.4.0", + "@aws-lambda-powertools/logger": "^1.4.1", + "@aws-lambda-powertools/metrics": "^1.4.1", + "@aws-lambda-powertools/tracer": "^1.4.1", "aws-sdk": "^2.1122.0" }, "devDependencies": { @@ -45,35 +45,35 @@ } }, "node_modules/@aws-lambda-powertools/commons": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.4.0.tgz", - "integrity": "sha512-8Y473kxNI8LIY/uhnk+2nsHQXgjrEL3WjZLk9L++UZ+nFRE2bJaSIWVW5V+IJJD2uPSvdhvdD1zvoLRsNT0+0w==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.4.1.tgz", + "integrity": "sha512-fGmCIQTG0vC+hllu3Ne1B2fRC3Hl2A0Q7Db7JeCXVsAEUIqzUp7blm1WVmqZsBzJ2T5XvZ3B/xMgc0ci8FGOjQ==" }, "node_modules/@aws-lambda-powertools/logger": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.4.0.tgz", - "integrity": "sha512-BhtRMWNYRGpK5FsF/c4jX3EtGylRV6SR9RAM0O7K+rQidFcKWLYPqJhVMm1CJxMk++qPawpr2kelHVEPzpG8tQ==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.4.1.tgz", + "integrity": "sha512-T0/bCUFybGSdFxaZEFKm5npz3jexorbPsNyet1WSY0sRubz3JS0o0ppJE3xrVs6t2Lz9LfzFJkFTF/aJKt3McQ==", "dependencies": { - "@aws-lambda-powertools/commons": "^1.4.0", + "@aws-lambda-powertools/commons": "^1.4.1", "lodash.clonedeep": "^4.5.0", "lodash.merge": "^4.6.2", "lodash.pickby": "^4.6.0" } }, "node_modules/@aws-lambda-powertools/metrics": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.4.0.tgz", - "integrity": "sha512-gHH2rCo922wgZS7N+Rm+tHPfKevGvUC/BY3I1sP9FscZEdpR6WnF10JF7vM+CKsAp0LczEFDhxj0eC7cK562YA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.4.1.tgz", + "integrity": "sha512-ZPfS7C/lnwETX8RhNSSPHOagNpeXniuGOUMi7cO0v8/ztB5vYJ5hjUtZ5j/Ht5alU9SLTKbehVyPdrTVfaZl0w==", "dependencies": { - "@aws-lambda-powertools/commons": "^1.4.0" + "@aws-lambda-powertools/commons": "^1.4.1" } }, "node_modules/@aws-lambda-powertools/tracer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.4.0.tgz", - "integrity": "sha512-nfhY58bLdvAdcRtsh37Ww9IoHwcQ5/08fX/t9RmOsUGFvDCY20QHGh5gKkJMpKxIMxbM4aXBfA7mFK8sOkD5uw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.4.1.tgz", + "integrity": "sha512-n+YSWg5wnbgd37XcZQ11U1hD77A7zPLvDTBUYLrbIDKwp01wNXdW2oDVF6GTP/doqoeVaJaRu8MILBoKw0h+Ow==", "dependencies": { - "@aws-lambda-powertools/commons": "^1.4.0", + "@aws-lambda-powertools/commons": "^1.4.1", "aws-xray-sdk-core": "^3.3.6" } }, @@ -6714,35 +6714,35 @@ } }, "@aws-lambda-powertools/commons": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.4.0.tgz", - "integrity": "sha512-8Y473kxNI8LIY/uhnk+2nsHQXgjrEL3WjZLk9L++UZ+nFRE2bJaSIWVW5V+IJJD2uPSvdhvdD1zvoLRsNT0+0w==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-1.4.1.tgz", + "integrity": "sha512-fGmCIQTG0vC+hllu3Ne1B2fRC3Hl2A0Q7Db7JeCXVsAEUIqzUp7blm1WVmqZsBzJ2T5XvZ3B/xMgc0ci8FGOjQ==" }, "@aws-lambda-powertools/logger": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.4.0.tgz", - "integrity": "sha512-BhtRMWNYRGpK5FsF/c4jX3EtGylRV6SR9RAM0O7K+rQidFcKWLYPqJhVMm1CJxMk++qPawpr2kelHVEPzpG8tQ==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-1.4.1.tgz", + "integrity": "sha512-T0/bCUFybGSdFxaZEFKm5npz3jexorbPsNyet1WSY0sRubz3JS0o0ppJE3xrVs6t2Lz9LfzFJkFTF/aJKt3McQ==", "requires": { - "@aws-lambda-powertools/commons": "^1.4.0", + "@aws-lambda-powertools/commons": "^1.4.1", "lodash.clonedeep": "^4.5.0", "lodash.merge": "^4.6.2", "lodash.pickby": "^4.6.0" } }, "@aws-lambda-powertools/metrics": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.4.0.tgz", - "integrity": "sha512-gHH2rCo922wgZS7N+Rm+tHPfKevGvUC/BY3I1sP9FscZEdpR6WnF10JF7vM+CKsAp0LczEFDhxj0eC7cK562YA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/metrics/-/metrics-1.4.1.tgz", + "integrity": "sha512-ZPfS7C/lnwETX8RhNSSPHOagNpeXniuGOUMi7cO0v8/ztB5vYJ5hjUtZ5j/Ht5alU9SLTKbehVyPdrTVfaZl0w==", "requires": { - "@aws-lambda-powertools/commons": "^1.4.0" + "@aws-lambda-powertools/commons": "^1.4.1" } }, "@aws-lambda-powertools/tracer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.4.0.tgz", - "integrity": "sha512-nfhY58bLdvAdcRtsh37Ww9IoHwcQ5/08fX/t9RmOsUGFvDCY20QHGh5gKkJMpKxIMxbM4aXBfA7mFK8sOkD5uw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/tracer/-/tracer-1.4.1.tgz", + "integrity": "sha512-n+YSWg5wnbgd37XcZQ11U1hD77A7zPLvDTBUYLrbIDKwp01wNXdW2oDVF6GTP/doqoeVaJaRu8MILBoKw0h+Ow==", "requires": { - "@aws-lambda-powertools/commons": "^1.4.0", + "@aws-lambda-powertools/commons": "^1.4.1", "aws-xray-sdk-core": "^3.3.6" } }, diff --git a/examples/sam/package.json b/examples/sam/package.json index 03bffbc598..214b2056e1 100644 --- a/examples/sam/package.json +++ b/examples/sam/package.json @@ -1,6 +1,6 @@ { "name": "sam-example", - "version": "1.4.1", + "version": "1.5.0", "author": { "name": "Amazon Web Services", "url": "https://aws.amazon.com" @@ -35,9 +35,9 @@ "typescript": "^4.1.3" }, "dependencies": { - "@aws-lambda-powertools/logger": "^1.4.0", - "@aws-lambda-powertools/metrics": "^1.4.0", - "@aws-lambda-powertools/tracer": "^1.4.0", + "@aws-lambda-powertools/logger": "^1.4.1", + "@aws-lambda-powertools/metrics": "^1.4.1", + "@aws-lambda-powertools/tracer": "^1.4.1", "aws-sdk": "^2.1122.0" } } diff --git a/layer-publisher/CHANGELOG.md b/layer-publisher/CHANGELOG.md index c224dfb18d..6b73417334 100644 --- a/layer-publisher/CHANGELOG.md +++ b/layer-publisher/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.5.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.4.1...v1.5.0) (2022-11-25) + +**Note:** Version bump only for package layer-publisher + + + + + ## [1.4.1](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.4.0...v1.4.1) (2022-11-09) **Note:** Version bump only for package layer-publisher diff --git a/layer-publisher/package-lock.json b/layer-publisher/package-lock.json index 1233e62f1f..e934867e4e 100644 --- a/layer-publisher/package-lock.json +++ b/layer-publisher/package-lock.json @@ -1,6 +1,6 @@ { "name": "layer-publisher", - "version": "1.4.1", + "version": "1.5.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/layer-publisher/package.json b/layer-publisher/package.json index caa1cab99f..cc4a1faaed 100644 --- a/layer-publisher/package.json +++ b/layer-publisher/package.json @@ -1,6 +1,6 @@ { "name": "layer-publisher", - "version": "1.4.1", + "version": "1.5.0", "bin": { "layer-publisher": "bin/layer-publisher.js" }, diff --git a/lerna.json b/lerna.json index e28c8bec18..ac715a242d 100644 --- a/lerna.json +++ b/lerna.json @@ -8,7 +8,7 @@ "examples/sam", "layer-publisher" ], - "version": "1.4.1", + "version": "1.5.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 e45b4ab5b1..eed16d6cf1 100644 --- a/packages/commons/CHANGELOG.md +++ b/packages/commons/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.5.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.4.1...v1.5.0) (2022-11-25) + +**Note:** Version bump only for package @aws-lambda-powertools/commons + + + + + ## [1.4.1](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.4.0...v1.4.1) (2022-11-09) **Note:** Version bump only for package @aws-lambda-powertools/commons diff --git a/packages/commons/package.json b/packages/commons/package.json index d1084e389f..0a5f963499 100644 --- a/packages/commons/package.json +++ b/packages/commons/package.json @@ -1,6 +1,6 @@ { "name": "@aws-lambda-powertools/commons", - "version": "1.4.1", + "version": "1.5.0", "description": "A shared utility package for AWS Lambda Powertools for TypeScript libraries", "author": { "name": "Amazon Web Services", diff --git a/packages/logger/CHANGELOG.md b/packages/logger/CHANGELOG.md index ebeab3277c..1e9be9b2e0 100644 --- a/packages/logger/CHANGELOG.md +++ b/packages/logger/CHANGELOG.md @@ -3,6 +3,23 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.5.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.4.1...v1.5.0) (2022-11-25) + + +### Bug Fixes + +* **logger:** merge child logger options correctly ([#1178](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1178)) ([cb91374](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/cb9137436cc3a10d6c869506ddd07e35963ba8b2)) + + +### Features + +* **logger:** disable logs while testing with `jest --silent` in dev env ([#1165](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1165)) ([6f0c307](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/6f0c30728f31d60433b3afb6983c64110c28d27e)) +* **logger:** pretty printing logs in local and non-prod environment ([#1141](https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1141)) ([8d52660](https://github.com/awslabs/aws-lambda-powertools-typescript/commit/8d52660eb6b8324e284421c2484c45d9a0839346)) + + + + + ## [1.4.1](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.4.0...v1.4.1) (2022-11-09) **Note:** Version bump only for package @aws-lambda-powertools/logger diff --git a/packages/logger/package.json b/packages/logger/package.json index 211969e096..1952251bc1 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -1,6 +1,6 @@ { "name": "@aws-lambda-powertools/logger", - "version": "1.4.1", + "version": "1.5.0", "description": "The logging package for the AWS Lambda Powertools for TypeScript library", "author": { "name": "Amazon Web Services", @@ -46,7 +46,7 @@ "url": "https://github.com/awslabs/aws-lambda-powertools-typescript/issues" }, "dependencies": { - "@aws-lambda-powertools/commons": "^1.4.1", + "@aws-lambda-powertools/commons": "^1.5.0", "lodash.merge": "^4.6.2", "lodash.pickby": "^4.6.0" }, diff --git a/packages/metrics/CHANGELOG.md b/packages/metrics/CHANGELOG.md index 0ddbf89dde..b1cc8d41f8 100644 --- a/packages/metrics/CHANGELOG.md +++ b/packages/metrics/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.5.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.4.1...v1.5.0) (2022-11-25) + +**Note:** Version bump only for package @aws-lambda-powertools/metrics + + + + + ## [1.4.1](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.4.0...v1.4.1) (2022-11-09) diff --git a/packages/metrics/package.json b/packages/metrics/package.json index 2d1019dd6f..616169db00 100644 --- a/packages/metrics/package.json +++ b/packages/metrics/package.json @@ -1,6 +1,6 @@ { "name": "@aws-lambda-powertools/metrics", - "version": "1.4.1", + "version": "1.5.0", "description": "The metrics package for the AWS Lambda Powertools for TypeScript library", "author": { "name": "Amazon Web Services", @@ -46,7 +46,7 @@ "url": "https://github.com/awslabs/aws-lambda-powertools-typescript/issues" }, "dependencies": { - "@aws-lambda-powertools/commons": "^1.4.1" + "@aws-lambda-powertools/commons": "^1.5.0" }, "keywords": [ "aws", diff --git a/packages/tracer/CHANGELOG.md b/packages/tracer/CHANGELOG.md index 4ff49fe64a..d31842749e 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.5.0](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.4.1...v1.5.0) (2022-11-25) + +**Note:** Version bump only for package @aws-lambda-powertools/tracer + + + + + ## [1.4.1](https://github.com/awslabs/aws-lambda-powertools-typescript/compare/v1.4.0...v1.4.1) (2022-11-09) **Note:** Version bump only for package @aws-lambda-powertools/tracer diff --git a/packages/tracer/package.json b/packages/tracer/package.json index d42950886c..545d7ac177 100644 --- a/packages/tracer/package.json +++ b/packages/tracer/package.json @@ -1,6 +1,6 @@ { "name": "@aws-lambda-powertools/tracer", - "version": "1.4.1", + "version": "1.5.0", "description": "The tracer package for the AWS Lambda Powertools for TypeScript library", "author": { "name": "Amazon Web Services", @@ -47,7 +47,7 @@ "url": "https://github.com/awslabs/aws-lambda-powertools-typescript/issues" }, "dependencies": { - "@aws-lambda-powertools/commons": "^1.4.1", + "@aws-lambda-powertools/commons": "^1.5.0", "aws-xray-sdk-core": "^3.3.6" }, "keywords": [