diff --git a/package-lock.json b/package-lock.json index 49f9ce936e..1c4ee68683 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4570,6 +4570,12 @@ "pretty-format": "^29.0.0" } }, + "node_modules/@types/jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@types/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha512-uaht4XcYSq5ZrPriQW8C+g5DhptewRd1E84ph7L167sCyzLObz+U3JTpmYq/CNkvjNsz2mtyQoHPNEYQYTzWmg==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -11348,7 +11354,6 @@ "version": "0.16.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", - "dev": true, "engines": { "node": ">= 0.6.0" } @@ -16728,9 +16733,11 @@ "license": "MIT", "dependencies": { "@aws-lambda-powertools/commons": "^1.5.0", - "@aws-sdk/lib-dynamodb": "^3.231.0" + "@aws-sdk/lib-dynamodb": "^3.231.0", + "jmespath": "^0.16.0" }, "devDependencies": { + "@types/jmespath": "^0.15.0", "aws-sdk-client-mock": "^2.0.1", "aws-sdk-client-mock-jest": "^2.0.1" } @@ -17033,8 +17040,10 @@ "requires": { "@aws-lambda-powertools/commons": "^1.5.0", "@aws-sdk/lib-dynamodb": "^3.231.0", + "@types/jmespath": "*", "aws-sdk-client-mock": "^2.0.1", - "aws-sdk-client-mock-jest": "^2.0.1" + "aws-sdk-client-mock-jest": "^2.0.1", + "jmespath": "^0.16.0" } }, "@aws-lambda-powertools/logger": { @@ -17056,7 +17065,7 @@ "@aws-lambda-powertools/parameters": { "version": "file:packages/parameters", "requires": { - "@aws-lambda-powertools/commons": "*", + "@aws-lambda-powertools/commons": "^1.7.0", "@aws-sdk/client-appconfigdata": "^3.241.0", "@aws-sdk/client-dynamodb": "^3.245.0", "@aws-sdk/client-secrets-manager": "^3.238.0", @@ -20392,6 +20401,12 @@ "pretty-format": "^29.0.0" } }, + "@types/jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@types/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha512-uaht4XcYSq5ZrPriQW8C+g5DhptewRd1E84ph7L167sCyzLObz+U3JTpmYq/CNkvjNsz2mtyQoHPNEYQYTzWmg==", + "dev": true + }, "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -23242,8 +23257,8 @@ "@aws-sdk/client-secrets-manager": "^3.250.0", "@aws-sdk/client-ssm": "^3.245.0", "@aws-sdk/util-dynamodb": "^3.245.0", - "aws-sdk-client-mock": "2.0.1", - "aws-sdk-client-mock-jest": "2.0.1", + "aws-sdk-client-mock": "^2.0.1", + "aws-sdk-client-mock-jest": "^2.0.1", "axios": "^1.2.4" }, "dependencies": { @@ -25775,8 +25790,7 @@ "jmespath": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", - "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", - "dev": true + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==" }, "js-sdsl": { "version": "4.2.0", diff --git a/packages/idempotency/package-lock.json b/packages/idempotency/package-lock.json deleted file mode 100644 index 29fef4816a..0000000000 --- a/packages/idempotency/package-lock.json +++ /dev/null @@ -1,3294 +0,0 @@ -{ - "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 cc83d4a229..a57aafd8e0 100644 --- a/packages/idempotency/package.json +++ b/packages/idempotency/package.json @@ -46,7 +46,8 @@ }, "dependencies": { "@aws-lambda-powertools/commons": "^1.5.0", - "@aws-sdk/lib-dynamodb": "^3.231.0" + "@aws-sdk/lib-dynamodb": "^3.231.0", + "jmespath": "^0.16.0" }, "keywords": [ "aws", @@ -56,7 +57,8 @@ "nodejs" ], "devDependencies": { + "@types/jmespath": "^0.15.0", "aws-sdk-client-mock": "^2.0.1", "aws-sdk-client-mock-jest": "^2.0.1" } -} \ No newline at end of file +} diff --git a/packages/idempotency/src/Exceptions.ts b/packages/idempotency/src/Exceptions.ts index d39a01b37e..43fa7e8ab7 100644 --- a/packages/idempotency/src/Exceptions.ts +++ b/packages/idempotency/src/Exceptions.ts @@ -1,32 +1,50 @@ -class IdempotencyItemNotFoundError extends Error { - -} - -class IdempotencyItemAlreadyExistsError extends Error{ - -} - -class IdempotencyInvalidStatusError extends Error { - -} - -class IdempotencyInconsistentStateError extends Error { - -} - -class IdempotencyAlreadyInProgressError extends Error { - -} - -class IdempotencyPersistenceLayerError extends Error { - -} +/** + * Item attempting to be inserted into persistence store already exists and is not expired + */ +class IdempotencyItemAlreadyExistsError extends Error {} + +/** + * Item does not exist in persistence store + */ +class IdempotencyItemNotFoundError extends Error {} + +/** + * Execution with idempotency key is already in progress + */ +class IdempotencyAlreadyInProgressError extends Error {} + +/** + * An invalid status was provided + */ +class IdempotencyInvalidStatusError extends Error {} + +/** + * Payload does not match stored idempotency record + */ +class IdempotencyValidationError extends Error {} + +/** + * State is inconsistent across multiple requests to persistence store + */ +class IdempotencyInconsistentStateError extends Error {} + +/** + * Unrecoverable error from the data store + */ +class IdempotencyPersistenceLayerError extends Error {} + +/** + * Payload does not contain an idempotent key + */ +class IdempotencyKeyError extends Error {} export { - IdempotencyItemNotFoundError, IdempotencyItemAlreadyExistsError, + IdempotencyItemNotFoundError, + IdempotencyAlreadyInProgressError, IdempotencyInvalidStatusError, + IdempotencyValidationError, IdempotencyInconsistentStateError, - IdempotencyAlreadyInProgressError, - IdempotencyPersistenceLayerError + IdempotencyPersistenceLayerError, + IdempotencyKeyError, }; \ No newline at end of file diff --git a/packages/idempotency/src/IdempotencyConfig.ts b/packages/idempotency/src/IdempotencyConfig.ts new file mode 100644 index 0000000000..db154da116 --- /dev/null +++ b/packages/idempotency/src/IdempotencyConfig.ts @@ -0,0 +1,30 @@ +import type { Context } from 'aws-lambda'; +import type { IdempotencyConfigOptions } from './types'; + +class IdempotencyConfig { + public eventKeyJmesPath: string; + public expiresAfterSeconds: number; + public hashFunction: string; + public lambdaContext?: Context; + public payloadValidationJmesPath?: string; + public throwOnNoIdempotencyKey: boolean; + public useLocalCache: boolean; + + public constructor(config: IdempotencyConfigOptions) { + this.eventKeyJmesPath = config.eventKeyJmesPath ?? ''; + this.payloadValidationJmesPath = config.payloadValidationJmesPath; + this.throwOnNoIdempotencyKey = config.throwOnNoIdempotencyKey ?? false; + this.expiresAfterSeconds = config.expiresAfterSeconds ?? 3600; // 1 hour default + this.useLocalCache = config.useLocalCache ?? false; + this.hashFunction = config.hashFunction ?? 'md5'; + this.lambdaContext = config.lambdaContext; + } + + public registerLambdaContext(context: Context): void { + this.lambdaContext = context; + } +} + +export { + IdempotencyConfig, +}; \ No newline at end of file diff --git a/packages/idempotency/src/IdempotencyHandler.ts b/packages/idempotency/src/IdempotencyHandler.ts index e629fa917b..bc34c048d7 100644 --- a/packages/idempotency/src/IdempotencyHandler.ts +++ b/packages/idempotency/src/IdempotencyHandler.ts @@ -1,16 +1,25 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -import { AnyFunctionWithRecord, IdempotencyRecordStatus } from './types'; -import { IdempotencyOptions } from './types/IdempotencyOptions'; -import { IdempotencyRecord } from 'persistence'; -import { IdempotencyInconsistentStateError, IdempotencyItemAlreadyExistsError, IdempotencyAlreadyInProgressError, IdempotencyPersistenceLayerError } from './Exceptions'; +import { IdempotencyRecordStatus } from './types'; +import type { + AnyFunctionWithRecord, + IdempotencyOptions, +} from './types'; +import { + IdempotencyInconsistentStateError, + IdempotencyItemAlreadyExistsError, + IdempotencyAlreadyInProgressError, + IdempotencyPersistenceLayerError +} from './Exceptions'; +import { IdempotencyRecord } from './persistence/IdempotencyRecord'; export class IdempotencyHandler { + public constructor( + private functionToMakeIdempotent: AnyFunctionWithRecord, + private functionPayloadToBeHashed: Record, + private idempotencyOptions: IdempotencyOptions, + private fullFunctionPayload: Record + ) {} - public constructor(private functionToMakeIdempotent: AnyFunctionWithRecord, private functionPayloadToBeHashed: unknown, - private idempotencyOptions: IdempotencyOptions, private fullFunctionPayload: Record) {} - - public determineResultFromIdempotencyRecord(idempotencyRecord: IdempotencyRecord): Promise | U{ + public determineResultFromIdempotencyRecord(idempotencyRecord: IdempotencyRecord): Promise | U { if (idempotencyRecord.getStatus() === IdempotencyRecordStatus.EXPIRED) { throw new IdempotencyInconsistentStateError('Item has expired during processing and may not longer be valid.'); } else if (idempotencyRecord.getStatus() === IdempotencyRecordStatus.INPROGRESS){ diff --git a/packages/idempotency/src/config/EnvironmentVariablesService.ts b/packages/idempotency/src/config/EnvironmentVariablesService.ts index 333faaca38..a487f0db8a 100644 --- a/packages/idempotency/src/config/EnvironmentVariablesService.ts +++ b/packages/idempotency/src/config/EnvironmentVariablesService.ts @@ -1,5 +1,7 @@ import { ConfigServiceInterface } from './ConfigServiceInterface'; -import { EnvironmentVariablesService as CommonEnvironmentVariablesService } from '@aws-lambda-powertools/commons'; +import { + EnvironmentVariablesService as CommonEnvironmentVariablesService +} from '@aws-lambda-powertools/commons'; /** * Class EnvironmentVariablesService diff --git a/packages/idempotency/src/idempotentDecorator.ts b/packages/idempotency/src/idempotentDecorator.ts index 51e387b428..4db95a86b0 100644 --- a/packages/idempotency/src/idempotentDecorator.ts +++ b/packages/idempotency/src/idempotentDecorator.ts @@ -1,13 +1,16 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { IdempotencyOptions } from './types/IdempotencyOptions'; +import { + GenericTempRecord, + IdempotencyOptions, +} from './types'; import { IdempotencyHandler } from './IdempotencyHandler'; const idempotent = function (options: IdempotencyOptions) { - return function (_target: any, _propertyKey: string, descriptor: PropertyDescriptor) { + return function (_target: unknown, _propertyKey: string, descriptor: PropertyDescriptor) { const childFunction = descriptor.value; - descriptor.value = function(record: Record){ - const idempotencyHandler: IdempotencyHandler = new IdempotencyHandler(childFunction, record[options.dataKeywordArgument], options, record); - + // TODO: sort out the type for this + descriptor.value = function(record: GenericTempRecord){ + const idempotencyHandler = new IdempotencyHandler(childFunction, record[options.dataKeywordArgument], options, record); + return idempotencyHandler.processIdempotency(); }; diff --git a/packages/idempotency/src/makeFunctionIdempotent.ts b/packages/idempotency/src/makeFunctionIdempotent.ts index fd416ccfc5..75c44b47c3 100644 --- a/packages/idempotency/src/makeFunctionIdempotent.ts +++ b/packages/idempotency/src/makeFunctionIdempotent.ts @@ -1,13 +1,16 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { AnyFunctionWithRecord, AnyIdempotentFunction } from './types/AnyFunction'; -import { IdempotencyOptions } from './types/IdempotencyOptions'; +import type { + GenericTempRecord, + IdempotencyOptions, + AnyFunctionWithRecord, + AnyIdempotentFunction, +} from './types'; import { IdempotencyHandler } from './IdempotencyHandler'; const makeFunctionIdempotent = function ( fn: AnyFunctionWithRecord, options: IdempotencyOptions ): AnyIdempotentFunction { - const wrappedFn: AnyIdempotentFunction = function (record: Record): Promise { + const wrappedFn: AnyIdempotentFunction = function (record: GenericTempRecord): Promise { const idempotencyHandler: IdempotencyHandler = new IdempotencyHandler(fn, record[options.dataKeywordArgument], options, record); return idempotencyHandler.processIdempotency(); diff --git a/packages/idempotency/src/persistence/BasePersistenceLayer.ts b/packages/idempotency/src/persistence/BasePersistenceLayer.ts new file mode 100644 index 0000000000..d8c68bb5d4 --- /dev/null +++ b/packages/idempotency/src/persistence/BasePersistenceLayer.ts @@ -0,0 +1,239 @@ +import { + createHash, + Hash +} from 'node:crypto'; +import { search } from 'jmespath'; +import { IdempotencyRecordStatus } from '../types'; +import type { + BasePersistenceLayerOptions +} from '../types'; +import { EnvironmentVariablesService } from '../config'; +import { IdempotencyRecord } from './IdempotencyRecord'; +import { BasePersistenceLayerInterface } from './BasePersistenceLayerInterface'; +import { IdempotencyValidationError } from '../Exceptions'; + +abstract class BasePersistenceLayer implements BasePersistenceLayerInterface { + public idempotencyKeyPrefix: string; + private configured: boolean = false; + // envVarsService is always initialized in the constructor + private envVarsService!: EnvironmentVariablesService; + private eventKeyJmesPath?: string; + private expiresAfterSeconds: number = 60 * 60; // 1 hour default + private hashFunction: string = 'md5'; + private payloadValidationEnabled: boolean = false; + private throwOnNoIdempotencyKey: boolean = false; + private useLocalCache: boolean = false; + private validationKeyJmesPath?: string; + + public constructor() { + this.envVarsService = new EnvironmentVariablesService(); + this.idempotencyKeyPrefix = this.getEnvVarsService().getFunctionName(); + } + + /** + * Initialize the base persistence layer from the configuration settings + * + * @param {BasePersistenceLayerConfigureOptions} config - configuration object for the persistence layer + */ + public configure(config: BasePersistenceLayerOptions): void { + // Extracting the idempotency config from the config object for easier access + const { config: idempotencyConfig } = config; + + if (config?.functionName && config.functionName.trim() !== '') { + this.idempotencyKeyPrefix = `${this.idempotencyKeyPrefix}.${config.functionName}`; + } + + // Prevent reconfiguration + if (this.configured) { + return; + } + this.configured = true; + + this.eventKeyJmesPath = idempotencyConfig?.eventKeyJmesPath; + this.validationKeyJmesPath = idempotencyConfig?.payloadValidationJmesPath; + this.payloadValidationEnabled = this.validationKeyJmesPath !== undefined || false; + this.throwOnNoIdempotencyKey = idempotencyConfig?.throwOnNoIdempotencyKey || false; + this.eventKeyJmesPath = idempotencyConfig.eventKeyJmesPath; + this.expiresAfterSeconds = idempotencyConfig.expiresAfterSeconds; // 1 hour default + // TODO: Add support for local cache + this.hashFunction = idempotencyConfig.hashFunction; + } + + /** + * 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: Record): Promise { + const idempotencyRecord = new IdempotencyRecord({ + idempotencyKey: this.getHashedIdempotencyKey(data), + status: IdempotencyRecordStatus.EXPIRED + }); + + await 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: Record): Promise { + const idempotencyKey = this.getHashedIdempotencyKey(data); + + const record = await this._getRecord(idempotencyKey); + this.validatePayload(data, record); + + return record; + } + + public isPayloadValidationEnabled(): boolean { + return this.payloadValidationEnabled; + } + + /** + * 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 + * @param remainingTimeInMillis - the remaining time left in the lambda execution context + */ + public async saveInProgress(data: Record, remainingTimeInMillis?: number): Promise { + const idempotencyRecord = new IdempotencyRecord({ + idempotencyKey: this.getHashedIdempotencyKey(data), + status: IdempotencyRecordStatus.INPROGRESS, + expiryTimestamp: this.getExpiryTimestamp(), + payloadHash: this.generateHash(JSON.stringify(data)), + }); + + if (remainingTimeInMillis) { + idempotencyRecord.inProgressExpiryTimestamp = new Date().getTime() + remainingTimeInMillis; + } else { + console.warn( + 'Could not determine remaining time left. Did you call registerLambdaContext on IdempotencyConfig?' + ); + } + + await 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: Record, result: Record): Promise { + const idempotencyRecord = new IdempotencyRecord({ + idempotencyKey: this.getHashedIdempotencyKey(data), + status: IdempotencyRecordStatus.COMPLETED, + expiryTimestamp: this.getExpiryTimestamp(), + responseData: result, + payloadHash: this.generateHash(JSON.stringify(data)), + }); + + await this._updateRecord(idempotencyRecord); + } + + protected abstract _deleteRecord(record: IdempotencyRecord): Promise; + protected abstract _getRecord(idempotencyKey: string): Promise; + protected abstract _putRecord(record: IdempotencyRecord): 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('base64'); + } + + /** + * 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: Record): string { + if (this.eventKeyJmesPath) { + data = search(data, this.eventKeyJmesPath); + } + + if (BasePersistenceLayer.isMissingIdempotencyKey(data)) { + if (this.throwOnNoIdempotencyKey) { + throw new Error('No data found to create a hashed idempotency_key'); + } + console.warn(`No value found for idempotency_key. jmespath: ${this.eventKeyJmesPath}`); + } + + return `${this.idempotencyKeyPrefix}#${this.generateHash(JSON.stringify(data))}`; + } + + /** + * Extract payload using validation key jmespath and return a hashed representation + * + * @param data payload + */ + private getHashedPayload(data: Record): string { + // This method is only called when payload validation is enabled. + // For payload validation to be enabled, the validation key jmespath must be set. + // Therefore, the assertion is safe. + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + data = search(data, this.validationKeyJmesPath!); + + return this.generateHash(JSON.stringify(data)); + } + + private static isMissingIdempotencyKey(data: Record): boolean { + if (Array.isArray(data) || typeof data === 'object') { + if (data === null) return true; + for (const value of Object.values(data)) { + if (value) { + return false; + } + } + + return true; + } + + return !data; + } + + private validatePayload(data: Record, record: IdempotencyRecord): void { + if (this.payloadValidationEnabled) { + const hashedPayload: string = this.getHashedPayload(data); + if (hashedPayload !== record.payloadHash) { + throw new IdempotencyValidationError('Payload does not match stored record for this event key'); + } + } + } + +} + +export { + BasePersistenceLayer +}; \ No newline at end of file diff --git a/packages/idempotency/src/persistence/PersistenceLayerInterface.ts b/packages/idempotency/src/persistence/BasePersistenceLayerInterface.ts similarity index 50% rename from packages/idempotency/src/persistence/PersistenceLayerInterface.ts rename to packages/idempotency/src/persistence/BasePersistenceLayerInterface.ts index 3eb5a9d0f0..0e4325ec81 100644 --- a/packages/idempotency/src/persistence/PersistenceLayerInterface.ts +++ b/packages/idempotency/src/persistence/BasePersistenceLayerInterface.ts @@ -1,12 +1,13 @@ import { IdempotencyRecord } from './IdempotencyRecord'; -import type { PersistenceLayerConfigureOptions } from '../types/PersistenceLayer'; +import type { BasePersistenceLayerOptions } from '../types/BasePersistenceLayer'; -interface PersistenceLayerInterface { - configure(options?: PersistenceLayerConfigureOptions): void +interface BasePersistenceLayerInterface { + configure(options?: BasePersistenceLayerOptions): void + isPayloadValidationEnabled(): boolean saveInProgress(data: unknown): Promise saveSuccess(data: unknown, result: unknown): Promise deleteRecord(data: unknown): Promise getRecord(data: unknown): Promise } -export { PersistenceLayerInterface }; +export { BasePersistenceLayerInterface }; diff --git a/packages/idempotency/src/persistence/DynamoDBPersistenceLayer.ts b/packages/idempotency/src/persistence/DynamoDBPersistenceLayer.ts index a0b9fe775e..97d40f6fb6 100644 --- a/packages/idempotency/src/persistence/DynamoDBPersistenceLayer.ts +++ b/packages/idempotency/src/persistence/DynamoDBPersistenceLayer.ts @@ -1,127 +1,250 @@ -import { DynamoDB, DynamoDBServiceException } from '@aws-sdk/client-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'; +import { + IdempotencyItemAlreadyExistsError, + IdempotencyItemNotFoundError +} from '../Exceptions'; +import { IdempotencyRecordStatus } from '../types'; +import type { + DynamoPersistenceOptions +} from '../types'; +import { + DynamoDBClient, + DynamoDBClientConfig, + DynamoDBServiceException, + DeleteItemCommand, + GetItemCommand, + PutItemCommand, + UpdateItemCommand, + AttributeValue, +} from '@aws-sdk/client-dynamodb'; +import { + marshall, + unmarshall, +} from '@aws-sdk/util-dynamodb'; import { IdempotencyRecord } from './IdempotencyRecord'; -import { PersistenceLayer } from './PersistenceLayer'; +import { BasePersistenceLayer } from './BasePersistenceLayer'; -class DynamoDBPersistenceLayer extends PersistenceLayer { +class DynamoDBPersistenceLayer extends BasePersistenceLayer { + private client?: DynamoDBClient; + private clientConfig: DynamoDBClientConfig = {}; private dataAttr: string; private expiryAttr: string; private inProgressExpiryAttr: string; private keyAttr: string; + private sortKeyAttr?: string; + private staticPkValue: string; private statusAttr: string; - private table: DynamoDBDocument | undefined; private tableName: string; + private validationKeyAttr: string; - public constructor(constructorOptions: DynamoPersistenceConstructorOptions) { + public constructor(config: DynamoPersistenceOptions) { 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.dataAttr ?? 'data'; + this.tableName = config.tableName; + this.keyAttr = config.keyAttr ?? 'id'; + this.statusAttr = config.statusAttr ?? 'status'; + this.expiryAttr = config.expiryAttr ?? 'expiration'; + this.inProgressExpiryAttr = config.inProgressExpiryAttr ?? 'in_progress_expiry_attr'; + this.dataAttr = config.dataAttr ?? 'data'; + this.validationKeyAttr = config.validationKeyAttr ?? 'validation'; + if (config.sortKeyAttr === this.keyAttr) { + throw new Error( + `keyAttr [${this.keyAttr}] and sortKeyAttr [${config.sortKeyAttr}] cannot be the same!` + ); + } + this.sortKeyAttr = config.sortKeyAttr; + this.staticPkValue = config.staticPkValue ?? `idempotency#${this.idempotencyKeyPrefix}`; + + if (config?.awsSdkV3Client) { + if (config?.awsSdkV3Client instanceof DynamoDBClient) { + this.client = config.awsSdkV3Client; + } else { + console.warn('Invalid AWS SDK V3 client passed to DynamoDBPersistenceLayer. Using default client.'); + } + } else { + this.clientConfig = config?.clientConfig ?? {}; + } } protected async _deleteRecord(record: IdempotencyRecord): Promise { - const table: DynamoDBDocument = this.getTable(); - await table.delete({ - TableName: this.tableName, Key: { [this.keyAttr]: record.idempotencyKey } - }); + const client = this.getClient(); + await client.send(new DeleteItemCommand({ + TableName: this.tableName, + Key: this.getKey(record.idempotencyKey), + })); } protected async _getRecord(idempotencyKey: string): Promise { - const table: DynamoDBDocument = this.getTable(); - const output: GetCommandOutput = await table.get( + const client = this.getClient(); + const result = await client.send(new GetItemCommand( { - TableName: this.tableName, Key: { - [this.keyAttr]: idempotencyKey - }, + TableName: this.tableName, + Key: this.getKey(idempotencyKey), ConsistentRead: true } + ) ); - if (!output.Item) { + if (!result.Item) { throw new IdempotencyItemNotFoundError(); } + const item = unmarshall(result.Item); 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] + idempotencyKey: item[this.keyAttr], + status: item[this.statusAttr], + expiryTimestamp: item[this.expiryAttr], + inProgressExpiryTimestamp: item[this.inProgressExpiryAttr], + responseData: item[this.dataAttr] }); } - protected async _putRecord(_record: IdempotencyRecord): Promise { - const table: DynamoDBDocument = this.getTable(); + protected async _putRecord(record: IdempotencyRecord): Promise { + const client = this.getClient(); - const item = { - [this.keyAttr]: _record.idempotencyKey, - [this.expiryAttr]: _record.expiryTimestamp, - [this.statusAttr]: _record.getStatus() + const item = { + ...this.getKey(record.idempotencyKey), + ...marshall({ + [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}`; + if (record.inProgressExpiryTimestamp !== undefined) { + item[this.inProgressExpiryAttr] = { + N: record.inProgressExpiryTimestamp.toString() + }; + } + + if (this.isPayloadValidationEnabled() && record.payloadHash !== undefined) { + item[this.validationKeyAttr] = { + S: record.payloadHash as string + }; + } + try { - await table.put({ + /** + * | LOCKED | RETRY if status = "INPROGRESS" | RETRY + * |----------------|-------------------------------------------------------|-------------> .... (time) + * | Lambda Idempotency Record + * | Timeout Timeout + * | (in_progress_expiry) (expiry) + * + * Conditions to successfully save a record: + * * The idempotency key does not exist: + * - first time that this invocation key is used + * - previous invocation with the same key was deleted due to TTL + */ + const idempotencyKeyDoesNotExist = 'attribute_not_exists(#id)'; + // * The idempotency key exists but it is expired + const idempotencyKeyExpired = '#expiry < :now'; + // * The status of the record is "INPROGRESS", there is an in-progress expiry timestamp, but it's expired + const inProgressExpiryExpired = [ + '#status = :inprogress', + 'attribute_exists(#in_progress_expiry)', + '#in_progress_expiry < :now_in_millis', + ].join(' AND '); + + const conditionExpression = [ + idempotencyKeyDoesNotExist, + idempotencyKeyExpired, + `(${inProgressExpiryExpired})`, + ].join(' OR '); + + const now = Date.now(); + await client.send(new PutItemCommand({ TableName: this.tableName, Item: item, ExpressionAttributeNames: { '#id': this.keyAttr, '#expiry': this.expiryAttr, + '#in_progress_expiry': this.inProgressExpiryAttr, '#status': this.statusAttr }, - ExpressionAttributeValues: { - ':now': Date.now() / 1000, - ':inprogress': IdempotencyRecordStatus.INPROGRESS - }, - ConditionExpression: conditionalExpression - } - ); - } catch (e){ - if ((e as DynamoDBServiceException).name === 'ConditionalCheckFailedException'){ - throw new IdempotencyItemAlreadyExistsError(); + ExpressionAttributeValues: marshall({ + ':now': now / 1000, + ':now_in_millis': now, + ':inprogress': IdempotencyRecordStatus.INPROGRESS + }), + ConditionExpression: conditionExpression + })); + } catch (error){ + if (error instanceof DynamoDBServiceException) { + if (error.name === 'ConditionalCheckFailedException'){ + throw new IdempotencyItemAlreadyExistsError( + `Failed to put record for already existing idempotency key: ${record.idempotencyKey}` + ); + } } - - throw e; + + throw error; } } 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 + const client = this.getClient(); + + const updateExpressionFields: string[] = [ + '#response_data = :response_data', + '#expiry = :expiry', + '#status = :status', + ]; + const expressionAttributeNames: Record = { + '#response_data': this.dataAttr, + '#expiry': this.expiryAttr, + '#status': this.statusAttr, + }; + const expressionAttributeValues: Record = { + ':response_data': record.responseData, + ':expiry': record.expiryTimestamp, + ':status': record.getStatus(), + }; + + if (this.isPayloadValidationEnabled()) { + updateExpressionFields.push('#validation_key = :validation_key'); + expressionAttributeNames['#validation_key'] = this.validationKeyAttr; + expressionAttributeValues[':validation_key'] = record.payloadHash; + } + + await client.send( + new UpdateItemCommand( + { + TableName: this.tableName, + Key: this.getKey(record.idempotencyKey), + UpdateExpression: `SET ${updateExpressionFields.join(', ')}`, + ExpressionAttributeNames: expressionAttributeNames, + ExpressionAttributeValues: marshall(expressionAttributeValues), } - } + ) ); } - private getTable(): DynamoDBDocument { - if (!this.table) - this.table = DynamoDBDocument.from(new DynamoDB({}), { marshallOptions: { removeUndefinedValues: true } }); + private getClient(): DynamoDBClient { + if (!this.client) { + this.client = new DynamoDBClient(this.clientConfig); + } + + return this.client; + } - return this.table; + /** + * Build primary key attribute simple or composite based on params. + * + * When sortKeyAttr is set, we must return a composite key with staticPkValue, + * otherwise we use the idempotency key given. + * + * @param idempotencyKey + */ + private getKey(idempotencyKey: string): Record { + if (this.sortKeyAttr) { + return marshall({ + [this.keyAttr]: this.staticPkValue, + [this.sortKeyAttr]: idempotencyKey + }); + } + + return marshall({ + [this.keyAttr]: idempotencyKey + }); } } diff --git a/packages/idempotency/src/persistence/IdempotencyRecord.ts b/packages/idempotency/src/persistence/IdempotencyRecord.ts index b512cc5786..f59839273c 100644 --- a/packages/idempotency/src/persistence/IdempotencyRecord.ts +++ b/packages/idempotency/src/persistence/IdempotencyRecord.ts @@ -1,22 +1,27 @@ -import { IdempotencyRecordOptions } from 'types/IdempotencyRecordOptions'; +import { IdempotencyRecordStatus } from '../types'; +import type { + IdempotencyRecordOptions +} from '../types'; import { IdempotencyInvalidStatusError } from '../Exceptions'; -import { IdempotencyRecordStatus } from '../types/IdempotencyRecordStatus'; +/** + * Class representing an idempotency record + */ class IdempotencyRecord { - public expiryTimestamp: number | undefined; + public expiryTimestamp?: number; public idempotencyKey: string; - public inProgressExpiryTimestamp: number | undefined; - public payloadHash: string | undefined; - public responseData: Record | undefined; + public inProgressExpiryTimestamp?: number; + public payloadHash?: string; + public responseData?: Record; 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 constructor(config: IdempotencyRecordOptions) { + this.idempotencyKey = config.idempotencyKey; + this.expiryTimestamp = config.expiryTimestamp; + this.inProgressExpiryTimestamp = config.inProgressExpiryTimestamp; + this.responseData = config.responseData; + this.payloadHash = config.payloadHash; + this.status = config.status; } public getResponse(): Record | undefined { @@ -29,7 +34,7 @@ class IdempotencyRecord { } else if (Object.values(IdempotencyRecordStatus).includes(this.status)) { return this.status; } else { - throw new IdempotencyInvalidStatusError(); + throw new IdempotencyInvalidStatusError(this.status); } } diff --git a/packages/idempotency/src/persistence/PersistenceLayer.ts b/packages/idempotency/src/persistence/PersistenceLayer.ts deleted file mode 100644 index 9325723312..0000000000 --- a/packages/idempotency/src/persistence/PersistenceLayer.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { BinaryToTextEncoding, createHash, Hash } from 'crypto'; -import { IdempotencyRecordStatus } from '../types/IdempotencyRecordStatus'; -import type { PersistenceLayerConfigureOptions } from '../types/PersistenceLayer'; -import { EnvironmentVariablesService } from '../config'; -import { IdempotencyRecord } from './IdempotencyRecord'; -import { PersistenceLayerInterface } from './PersistenceLayerInterface'; - -abstract class PersistenceLayer implements PersistenceLayerInterface { - - // envVarsService is always initialized in the constructor - private envVarsService!: EnvironmentVariablesService; - - private expiresAfterSeconds: number; - - 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(); - - } - - /** - * 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}`; - } - } - - /** - * 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 - }); - - this._updateRecord(idempotencyRecord); - - } - - protected abstract _deleteRecord(record: IdempotencyRecord): Promise; - protected abstract _getRecord(idempotencyKey: string): Promise; - protected abstract _putRecord(record: IdempotencyRecord): 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.idempotencyKeyPrefix}#${this.generateHash(JSON.stringify(data))}`; - } - - /** - * Setter and initializer for `envVarsService`. - * Used internally during initialization. - */ - private setEnvVarsService(): void { - this.envVarsService = new EnvironmentVariablesService(); - } - -} - -export { - PersistenceLayer -}; \ No newline at end of file diff --git a/packages/idempotency/src/persistence/index.ts b/packages/idempotency/src/persistence/index.ts index 01dfd9be77..c531f87edb 100644 --- a/packages/idempotency/src/persistence/index.ts +++ b/packages/idempotency/src/persistence/index.ts @@ -1,4 +1,4 @@ export * from './DynamoDBPersistenceLayer'; -export * from './PersistenceLayer'; -export * from './PersistenceLayerInterface'; +export * from './BasePersistenceLayer'; +export * from './BasePersistenceLayerInterface'; export * from './IdempotencyRecord'; \ No newline at end of file diff --git a/packages/idempotency/src/types/AnyFunction.ts b/packages/idempotency/src/types/AnyFunction.ts index 4b2a894a13..a7e9b2f388 100644 --- a/packages/idempotency/src/types/AnyFunction.ts +++ b/packages/idempotency/src/types/AnyFunction.ts @@ -1,11 +1,13 @@ +// TODO: Find a better way to type this // eslint-disable-next-line @typescript-eslint/no-explicit-any -type AnyFunctionWithRecord = (record: Record) => Promise | U; +type GenericTempRecord = Record; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type AnyIdempotentFunction = (record: Record) => Promise; +type AnyFunctionWithRecord = (record: GenericTempRecord) => Promise | U; + +type AnyIdempotentFunction = (record: GenericTempRecord) => Promise; export { - // AnyFunction, + GenericTempRecord, AnyFunctionWithRecord, - AnyIdempotentFunction + AnyIdempotentFunction, }; \ No newline at end of file diff --git a/packages/idempotency/src/types/BasePersistenceLayer.ts b/packages/idempotency/src/types/BasePersistenceLayer.ts new file mode 100644 index 0000000000..b5456f805e --- /dev/null +++ b/packages/idempotency/src/types/BasePersistenceLayer.ts @@ -0,0 +1,10 @@ +import { IdempotencyConfig } from '../IdempotencyConfig'; + +type BasePersistenceLayerOptions = { + config: IdempotencyConfig + functionName?: string +}; + +export { + BasePersistenceLayerOptions, +}; \ No newline at end of file diff --git a/packages/idempotency/src/types/DynamoDBPersistence.ts b/packages/idempotency/src/types/DynamoDBPersistence.ts new file mode 100644 index 0000000000..61db0a5f46 --- /dev/null +++ b/packages/idempotency/src/types/DynamoDBPersistence.ts @@ -0,0 +1,78 @@ +import type { + DynamoDBClient, + DynamoDBClientConfig, +} from '@aws-sdk/client-dynamodb'; + +/** + * Base interface for DynamoPersistenceOptions. + * + * @interface + * @property {string} tableName - The DynamoDB table name. + * @property {string} [keyAttr] - The DynamoDB table key attribute name. Defaults to 'id'. + * @property {string} [expiryAttr] - The DynamoDB table expiry attribute name. Defaults to 'expiration'. + * @property {string} [inProgressExpiryAttr] - The DynamoDB table in progress expiry attribute name. Defaults to 'in_progress_expiry_attr'. + * @property {string} [statusAttr] - The DynamoDB table status attribute name. Defaults to 'status'. + * @property {string} [dataAttr] - The DynamoDB table data attribute name. Defaults to 'data'. + * @property {string} [validationKeyAttr] - The DynamoDB table validation key attribute name. Defaults to 'validation'. + * @property {string} [sortKeyAttr] - The DynamoDB table sort key attribute name, use only when table has one. Defaults to undefined. + * @property {string} [staticPkValue] - The DynamoDB table static partition key value, use only with sortKeyAttr. Defaults to `idempotency#{LAMBDA_FUNCTION_NAME}`. + */ +interface DynamoPersistenceOptionsBaseInterface { + tableName: string + keyAttr?: string + expiryAttr?: string + inProgressExpiryAttr?: string + statusAttr?: string + dataAttr?: string + validationKeyAttr?: string + sortKeyAttr?: string + staticPkValue?: string +} + +/** + * Interface for DynamoPersistenceOptions with clientConfig property. + * + * @interface + * @extends DynamoPersistenceOptionsBaseInterface + * @property {DynamoDBClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. + * @property {never} [awsSdkV3Client] - This property should never be passed. + */ +interface DynamoPersistenceOptionsWithClientConfig extends DynamoPersistenceOptionsBaseInterface { + clientConfig?: DynamoDBClientConfig + awsSdkV3Client?: never +} + +/** + * Interface for DynamoPersistenceOptions with awsSdkV3Client property. + * + * @interface + * @extends DynamoPersistenceOptionsBaseInterface + * @property {DynamoDBClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during AppConfigProvider class instantiation + * @property {never} [clientConfig] - This property should never be passed. + */ +interface DynamoPersistenceOptionsWithClientInstance extends DynamoPersistenceOptionsBaseInterface { + awsSdkV3Client?: DynamoDBClient + clientConfig?: never +} + +/** + * Options for the AppConfigProvider class constructor. + * + * @type AppConfigProviderOptions + * @property {string} tableName - The DynamoDB table name. + * @property {string} [keyAttr] - The DynamoDB table key attribute name. Defaults to 'id'. + * @property {string} [expiryAttr] - The DynamoDB table expiry attribute name. Defaults to 'expiration'. + * @property {string} [inProgressExpiryAttr] - The DynamoDB table in progress expiry attribute name. Defaults to 'in_progress_expiry_attr'. + * @property {string} [statusAttr] - The DynamoDB table status attribute name. Defaults to 'status'. + * @property {string} [dataAttr] - The DynamoDB table data attribute name. Defaults to 'data'. + * @property {string} [validationKeyAttr] - The DynamoDB table validation key attribute name. Defaults to 'validation'. + * @property {string} [sortKeyAttr] - The DynamoDB table sort key attribute name, use only when table has one. Defaults to undefined. + * @property {string} [staticPkValue] - The DynamoDB table static partition key value, use only with sortKeyAttr. Defaults to `idempotency#{LAMBDA_FUNCTION_NAME}`. + * @property {DynamoDBClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. Mutually exclusive with awsSdkV3Client. + * @property {DynamoDBClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during DynamoDBProvider class instantiation. Mutually exclusive with clientConfig. + */ +type DynamoPersistenceOptions = DynamoPersistenceOptionsWithClientConfig | DynamoPersistenceOptionsWithClientInstance; + +export type { + DynamoPersistenceOptions, +}; \ No newline at end of file diff --git a/packages/idempotency/src/types/DynamoPersistenceConstructorOptions.ts b/packages/idempotency/src/types/DynamoPersistenceConstructorOptions.ts deleted file mode 100644 index 8ca7f7d4f3..0000000000 --- a/packages/idempotency/src/types/DynamoPersistenceConstructorOptions.ts +++ /dev/null @@ -1,12 +0,0 @@ -type DynamoPersistenceConstructorOptions = { - tableName: string - keyAttr?: string - statusAttr?: string - expiryAttr?: string - inProgressExpiryAttr?: string - dataAttr?: string -}; - -export { - DynamoPersistenceConstructorOptions -}; \ No newline at end of file diff --git a/packages/idempotency/src/types/IdempotencyOptions.ts b/packages/idempotency/src/types/IdempotencyOptions.ts index f7b445e65f..cc468aff7a 100644 --- a/packages/idempotency/src/types/IdempotencyOptions.ts +++ b/packages/idempotency/src/types/IdempotencyOptions.ts @@ -1,8 +1,46 @@ -import { PersistenceLayer } from '../persistence/PersistenceLayer'; +import type { Context } from 'aws-lambda'; +import { BasePersistenceLayer } from '../persistence/BasePersistenceLayer'; type IdempotencyOptions = { dataKeywordArgument: string - persistenceStore: PersistenceLayer + persistenceStore: BasePersistenceLayer }; -export { IdempotencyOptions }; +/** + * Idempotency configuration options + */ +type IdempotencyConfigOptions = { + /** + * An optional JMESPath expression to extract the idempotency key from the event record + */ + eventKeyJmesPath?: string + /** + * An optional JMESPath expression to extract the payload to be validated from the event record + */ + payloadValidationJmesPath?: string + /** + * Throw an error if no idempotency key was found in the request, defaults to false + */ + throwOnNoIdempotencyKey?: boolean + /** + * The number of seconds to wait before a record is expired, defaults to 3600 (1 hour) + */ + expiresAfterSeconds?: number + /** + * Wheter to locally cache idempotency results, defaults to false + */ + useLocalCache?: boolean + /** + * Function to use for calculating hashes, defaults to md5 + */ + hashFunction?: string + /** + * AWS Lambda Context object containing information about the current invocation, function, and execution environment + */ + lambdaContext?: Context +}; + +export { + IdempotencyOptions, + IdempotencyConfigOptions +}; diff --git a/packages/idempotency/src/types/IdempotencyRecordOptions.ts b/packages/idempotency/src/types/IdempotencyRecord.ts similarity index 51% rename from packages/idempotency/src/types/IdempotencyRecordOptions.ts rename to packages/idempotency/src/types/IdempotencyRecord.ts index 07c9b91bb3..5e111d5bb7 100644 --- a/packages/idempotency/src/types/IdempotencyRecordOptions.ts +++ b/packages/idempotency/src/types/IdempotencyRecord.ts @@ -1,4 +1,10 @@ -import { IdempotencyRecordStatus } from './IdempotencyRecordStatus'; +const IdempotencyRecordStatus = { + INPROGRESS: 'INPROGRESS', + COMPLETED: 'COMPLETED', + EXPIRED: 'EXPIRED' +} as const; + +type IdempotencyRecordStatus = typeof IdempotencyRecordStatus[keyof typeof IdempotencyRecordStatus]; type IdempotencyRecordOptions = { idempotencyKey: string @@ -10,5 +16,6 @@ type IdempotencyRecordOptions = { }; export { + IdempotencyRecordStatus, IdempotencyRecordOptions }; \ No newline at end of file diff --git a/packages/idempotency/src/types/IdempotencyRecordStatus.ts b/packages/idempotency/src/types/IdempotencyRecordStatus.ts deleted file mode 100644 index 95fdac0637..0000000000 --- a/packages/idempotency/src/types/IdempotencyRecordStatus.ts +++ /dev/null @@ -1,9 +0,0 @@ -enum IdempotencyRecordStatus { - INPROGRESS = 'INPROGRESS', - COMPLETED = 'COMPLETED', - EXPIRED = 'EXPIRED' -} - -export { - IdempotencyRecordStatus -}; \ No newline at end of file diff --git a/packages/idempotency/src/types/PersistenceLayer.ts b/packages/idempotency/src/types/PersistenceLayer.ts deleted file mode 100644 index 3654648846..0000000000 --- a/packages/idempotency/src/types/PersistenceLayer.ts +++ /dev/null @@ -1,7 +0,0 @@ -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 c725c5a6ba..e643466963 100644 --- a/packages/idempotency/src/types/index.ts +++ b/packages/idempotency/src/types/index.ts @@ -1,4 +1,5 @@ export * from './AnyFunction'; -export * from './IdempotencyRecordStatus'; -export * from './IdempotencyRecordOptions'; -export * from './PersistenceLayer'; +export * from './IdempotencyRecord'; +export * from './BasePersistenceLayer'; +export * from './IdempotencyOptions'; +export * from './DynamoDBPersistence'; \ No newline at end of file diff --git a/packages/idempotency/tests/unit/IdempotencyConfig.test.ts b/packages/idempotency/tests/unit/IdempotencyConfig.test.ts new file mode 100644 index 0000000000..6155787883 --- /dev/null +++ b/packages/idempotency/tests/unit/IdempotencyConfig.test.ts @@ -0,0 +1,104 @@ +/** + * Test IdempotencyConfig class + * + * @group unit/idempotency/config + */ +import { + ContextExamples as dummyContext +} from '@aws-lambda-powertools/commons'; +import { + IdempotencyConfig, +} from '../../src/IdempotencyConfig'; +import type { + IdempotencyConfigOptions, +} from '../../src/types'; + +describe('Class: IdempotencyConfig', () => { + + const ENVIRONMENT_VARIABLES = process.env; + const context = dummyContext.helloworldContext; + + beforeEach(() => { + jest.clearAllMocks(); + jest.resetModules(); + process.env = { ...ENVIRONMENT_VARIABLES }; + }); + + afterAll(() => { + process.env = ENVIRONMENT_VARIABLES; + }); + + describe('Method: configure', () => { + + test('when configured with an empty config object, it initializes the config with default values', () => { + + // Prepare + const configOptions = {}; + + // Act + const config = new IdempotencyConfig(configOptions); + + // Assess + expect(config).toEqual(expect.objectContaining({ + eventKeyJmesPath: '', + payloadValidationJmesPath: undefined, + throwOnNoIdempotencyKey: false, + expiresAfterSeconds: 3600, + useLocalCache: false, + hashFunction: 'md5', + lambdaContext: undefined, + })); + + }); + + test('when configured with a config object, it initializes the config with the provided configs', () => { + + // Prepare + const configOptions: IdempotencyConfigOptions = { + eventKeyJmesPath: 'eventKeyJmesPath', + payloadValidationJmesPath: 'payloadValidationJmesPath', + throwOnNoIdempotencyKey: true, + expiresAfterSeconds: 100, + useLocalCache: true, + hashFunction: 'hashFunction', + lambdaContext: context, + }; + + // Act + const config = new IdempotencyConfig(configOptions); + + // Assess + expect(config).toEqual(expect.objectContaining({ + eventKeyJmesPath: 'eventKeyJmesPath', + payloadValidationJmesPath: 'payloadValidationJmesPath', + throwOnNoIdempotencyKey: true, + expiresAfterSeconds: 100, + useLocalCache: true, + hashFunction: 'hashFunction', + lambdaContext: context, + })); + + }); + + }); + + describe('Method: registerLambdaContext', () => { + + test('when called, it stores the provided context', async () => { + + // Prepare + const config = new IdempotencyConfig({}); + + // Act + config.registerLambdaContext(context); + + // Assess + expect(config).toEqual(expect.objectContaining({ + lambdaContext: context + })); + + }); + + }); + +}); \ 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 index f4e44333e9..85f0c8b116 100644 --- a/packages/idempotency/tests/unit/config/EnvironmentVariableService.test.ts +++ b/packages/idempotency/tests/unit/config/EnvironmentVariableService.test.ts @@ -1,7 +1,7 @@ /** * Test EnvironmentVariableService class * - * @group unit/idempotency/all + * @group unit/idempotency/environment-variables-service */ import { EnvironmentVariablesService } from '../../../src/config'; diff --git a/packages/idempotency/tests/unit/idempotentDecorator.test.ts b/packages/idempotency/tests/unit/idempotentDecorator.test.ts index 3df2689116..699413456b 100644 --- a/packages/idempotency/tests/unit/idempotentDecorator.test.ts +++ b/packages/idempotency/tests/unit/idempotentDecorator.test.ts @@ -1,19 +1,20 @@ /** * Test Function Wrapper * - * @group unit/idempotency/all + * @group unit/idempotency/decorator */ import { IdempotencyOptions } from '../../src/types/IdempotencyOptions'; -import { PersistenceLayer, IdempotencyRecord } from '../../src/persistence'; +import { BasePersistenceLayer, IdempotencyRecord } from '../../src/persistence'; import { idempotent } from '../../src/idempotentDecorator'; -import { IdempotencyRecordStatus, IdempotencyRecordOptions } from '../../src/types'; +import { IdempotencyRecordStatus } from '../../src/types'; +import type { IdempotencyRecordOptions } from '../../src/types'; import { IdempotencyItemAlreadyExistsError, IdempotencyAlreadyInProgressError, IdempotencyInconsistentStateError, IdempotencyPersistenceLayerError } from '../../src/Exceptions'; -const mockSaveInProgress = jest.spyOn(PersistenceLayer.prototype, 'saveInProgress').mockImplementation(); -const mockGetRecord = jest.spyOn(PersistenceLayer.prototype, 'getRecord').mockImplementation(); +const mockSaveInProgress = jest.spyOn(BasePersistenceLayer.prototype, 'saveInProgress').mockImplementation(); +const mockGetRecord = jest.spyOn(BasePersistenceLayer.prototype, 'getRecord').mockImplementation(); -class PersistenceLayerTestClass extends PersistenceLayer { +class PersistenceLayerTestClass extends BasePersistenceLayer { protected _deleteRecord = jest.fn(); protected _getRecord = jest.fn(); protected _putRecord = jest.fn(); @@ -25,6 +26,8 @@ const functionalityToDecorate = jest.fn(); class TestingClass { @idempotent(options) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore public testing(record: Record): string { functionalityToDecorate(record); diff --git a/packages/idempotency/tests/unit/makeFunctionIdempotent.test.ts b/packages/idempotency/tests/unit/makeFunctionIdempotent.test.ts index 95fc50b068..cb531bae23 100644 --- a/packages/idempotency/tests/unit/makeFunctionIdempotent.test.ts +++ b/packages/idempotency/tests/unit/makeFunctionIdempotent.test.ts @@ -1,19 +1,27 @@ /** * Test Function Wrapper * - * @group unit/idempotency/all + * @group unit/idempotency/makeFunctionIdempotent */ - import { IdempotencyOptions } from '../../src/types/IdempotencyOptions'; -import { IdempotencyRecord, PersistenceLayer } from '../../src/persistence'; +import { IdempotencyRecord, BasePersistenceLayer } from '../../src/persistence'; import { makeFunctionIdempotent } from '../../src/makeFunctionIdempotent'; -import { AnyIdempotentFunction, IdempotencyRecordStatus, IdempotencyRecordOptions } from '../../src/types'; -import { IdempotencyItemAlreadyExistsError, IdempotencyAlreadyInProgressError, IdempotencyInconsistentStateError, IdempotencyPersistenceLayerError } from '../../src/Exceptions'; +import { IdempotencyRecordStatus } from '../../src/types'; +import type { + AnyIdempotentFunction, + IdempotencyRecordOptions +} from '../../src/types'; +import { + IdempotencyItemAlreadyExistsError, + IdempotencyAlreadyInProgressError, + IdempotencyInconsistentStateError, + IdempotencyPersistenceLayerError +} from '../../src/Exceptions'; -const mockSaveInProgress = jest.spyOn(PersistenceLayer.prototype, 'saveInProgress').mockImplementation(); -const mockGetRecord = jest.spyOn(PersistenceLayer.prototype, 'getRecord').mockImplementation(); +const mockSaveInProgress = jest.spyOn(BasePersistenceLayer.prototype, 'saveInProgress').mockImplementation(); +const mockGetRecord = jest.spyOn(BasePersistenceLayer.prototype, 'getRecord').mockImplementation(); -class PersistenceLayerTestClass extends PersistenceLayer { +class PersistenceLayerTestClass extends BasePersistenceLayer { protected _deleteRecord = jest.fn(); protected _getRecord = jest.fn(); protected _putRecord = jest.fn(); diff --git a/packages/idempotency/tests/unit/persistence/BasePersistenceLayer.test.ts b/packages/idempotency/tests/unit/persistence/BasePersistenceLayer.test.ts new file mode 100644 index 0000000000..fecf1c3914 --- /dev/null +++ b/packages/idempotency/tests/unit/persistence/BasePersistenceLayer.test.ts @@ -0,0 +1,373 @@ +/** + * Test BasePersistenceLayer class + * + * @group unit/idempotency/persistence/base + */ +import { + ContextExamples as dummyContext +} from '@aws-lambda-powertools/commons'; +import { + IdempotencyConfig, +} from '../../../src/IdempotencyConfig'; +import { + IdempotencyRecord, + BasePersistenceLayer +} from '../../../src/persistence'; +import { + IdempotencyValidationError, +} from '../../../src/Exceptions'; +import type { + IdempotencyConfigOptions +} from '../../../src/types'; +import { + IdempotencyRecordStatus +} from '../../../src/types'; + +jest.mock('node:crypto', () => ({ + createHash: jest.fn().mockReturnValue( + { + update: jest.fn(), + digest: jest.fn().mockReturnValue('mocked-hash') + } + ), +})); + +describe('Class: BasePersistenceLayer', () => { + + const ENVIRONMENT_VARIABLES = process.env; + const context = dummyContext.helloworldContext; + + class PersistenceLayerTestClass extends BasePersistenceLayer { + + public _deleteRecord = jest.fn(); + public _getRecord = jest.fn(); + public _putRecord = jest.fn(); + public _updateRecord = jest.fn(); + } + + beforeAll(() => { + jest + .useFakeTimers() + .setSystemTime(new Date()); + }); + + beforeEach(() => { + jest.clearAllMocks(); + jest.resetModules(); + process.env = { ...ENVIRONMENT_VARIABLES }; + }); + + afterAll(() => { + process.env = ENVIRONMENT_VARIABLES; + jest.useRealTimers(); + }); + + describe('Method: constructor', () => { + + test('when initialized with no arguments, it initializes with default values', () => { + + // Prepare & Act + const persistenceLayer = new PersistenceLayerTestClass(); + + // Assess + expect(persistenceLayer.idempotencyKeyPrefix).toBe('my-lambda-function'); + expect(persistenceLayer).toEqual(expect.objectContaining({ + configured: false, + expiresAfterSeconds: 3600, + hashFunction: 'md5', + payloadValidationEnabled: false, + throwOnNoIdempotencyKey: false, + useLocalCache: false, + })); + + }); + + }); + + describe('Method: configure', () => { + + test('when configured with a function name, it appends the function name to the idempotency key prefix', () => { + + // Prepare + const config = new IdempotencyConfig({}); + const persistenceLayer = new PersistenceLayerTestClass(); + + // Act + persistenceLayer.configure({ config, functionName: 'my-function' }); + + // Assess + expect(persistenceLayer.idempotencyKeyPrefix).toBe('my-lambda-function.my-function'); + + }); + + test('when configured with an empty config object, it initializes the persistence layer with default configs', () => { + + // Prepare + const config = new IdempotencyConfig({}); + const persistenceLayer = new PersistenceLayerTestClass(); + + // Act + persistenceLayer.configure({ config }); + + // Assess + expect(persistenceLayer).toEqual(expect.objectContaining({ + configured: true, + validationKeyJmesPath: undefined, + payloadValidationEnabled: false, + expiresAfterSeconds: 3600, + throwOnNoIdempotencyKey: false, + eventKeyJmesPath: '', + useLocalCache: false, + hashFunction: 'md5', + })); + + }); + + test('when configured with a config object, it initializes the persistence layer with the provided configs', () => { + + // Prepare + const configOptions: IdempotencyConfigOptions = { + eventKeyJmesPath: 'eventKeyJmesPath', + payloadValidationJmesPath: 'payloadValidationJmesPath', + throwOnNoIdempotencyKey: true, + expiresAfterSeconds: 100, + useLocalCache: true, + hashFunction: 'hashFunction', + lambdaContext: context, + }; + const config = new IdempotencyConfig(configOptions); + const persistenceLayer = new PersistenceLayerTestClass(); + + // Act + persistenceLayer.configure({ config }); + + // Assess + expect(persistenceLayer).toEqual(expect.objectContaining({ + configured: true, + eventKeyJmesPath: 'eventKeyJmesPath', + validationKeyJmesPath: 'payloadValidationJmesPath', + payloadValidationEnabled: true, + throwOnNoIdempotencyKey: true, + expiresAfterSeconds: 100, + useLocalCache: false, + hashFunction: 'hashFunction', + })); + + }); + + test('when called twice, it returns without reconfiguring the persistence layer', () => { + + // Prepare + const config = new IdempotencyConfig({ + eventKeyJmesPath: 'eventKeyJmesPath', + }); + const secondConfig = new IdempotencyConfig({ + eventKeyJmesPath: 'secondEventKeyJmesPath', + }); + const persistenceLayer = new PersistenceLayerTestClass(); + + // Act + persistenceLayer.configure({ config }); + persistenceLayer.configure({ config: secondConfig }); + + // Assess + expect(persistenceLayer).toEqual(expect.objectContaining({ + configured: true, + eventKeyJmesPath: 'eventKeyJmesPath', + })); + + }); + + }); + + describe('Method: deleteRecord', () => { + + test('when called, it calls the _deleteRecord method with the correct arguments', async () => { + + // Prepare + const persistenceLayer = new PersistenceLayerTestClass(); + const baseIdempotencyRecord = new IdempotencyRecord({ + idempotencyKey: 'idempotencyKey', + status: IdempotencyRecordStatus.EXPIRED, + }); + const deleteRecordSpy = jest.spyOn(persistenceLayer, '_deleteRecord'); + + // Act + await persistenceLayer.deleteRecord({ foo: 'bar' }); + + // Assess + expect(deleteRecordSpy).toHaveBeenCalledWith( + expect.objectContaining({ + ...baseIdempotencyRecord, + idempotencyKey: 'my-lambda-function#mocked-hash', + status: IdempotencyRecordStatus.EXPIRED + }), + ); + + }); + + }); + + describe('Method: getRecord', () => { + + test('when called, it calls the _getRecord method with the correct arguments', async () => { + + // Prepare + const persistenceLayer = new PersistenceLayerTestClass(); + persistenceLayer.configure({ + config: new IdempotencyConfig({ + eventKeyJmesPath: 'foo', + }), + }); + const getRecordSpy = jest.spyOn(persistenceLayer, '_getRecord'); + + // Act + await persistenceLayer.getRecord({ foo: 'bar' }); + + // Assess + expect(getRecordSpy).toHaveBeenCalledWith('my-lambda-function#mocked-hash'); + + }); + + test('when called and payload validation fails due to hash mismatch, it throws an IdempotencyValidationError', async () => { + + // Prepare + const persistenceLayer = new PersistenceLayerTestClass(); + persistenceLayer.configure({ + config: new IdempotencyConfig({ + payloadValidationJmesPath: 'foo', + }), + }); + jest.spyOn(persistenceLayer, '_getRecord').mockReturnValue(new IdempotencyRecord({ + idempotencyKey: 'my-lambda-function#mocked-hash', + status: IdempotencyRecordStatus.INPROGRESS, + payloadHash: 'different-hash', + })); + + // Act & Assess + await expect(persistenceLayer.getRecord({ foo: 'bar' })).rejects.toThrow( + new IdempotencyValidationError( + 'Payload does not match stored record for this event key', + ) + ); + + }); + + test('when called and the hash generation fails, and throwOnNoIdempotencyKey is disabled, it logs a warning', async () => { + + // Prepare + const persistenceLayer = new PersistenceLayerTestClass(); + persistenceLayer.configure({ + config: new IdempotencyConfig({ + // This will cause the hash generation to fail because the event does not have a bar property + eventKeyJmesPath: 'bar', + }), + }); + const logWarningSpy = jest.spyOn(console, 'warn').mockImplementation(); + + // Act + await persistenceLayer.getRecord({ foo: 'bar' }); + + // Assess + expect(logWarningSpy).toHaveBeenCalledWith( + 'No value found for idempotency_key. jmespath: bar' + ); + + }); + + test('when called and the hash generation fails, and throwOnNoIdempotencyKey is enabled, it throws', async () => { + + // Prepare + const persistenceLayer = new PersistenceLayerTestClass(); + persistenceLayer.configure({ + config: new IdempotencyConfig({ + throwOnNoIdempotencyKey: true, + // This will cause the hash generation to fail because the JMESPath expression will return an empty array + eventKeyJmesPath: 'foo.bar', + }), + }); + + // Act & Assess + await expect(persistenceLayer.getRecord({ foo: { bar: [] } })).rejects.toThrow( + new Error('No data found to create a hashed idempotency_key') + ); + + }); + + }); + + describe('Method: saveInProgress', () => { + + test('when called, it calls the _putRecord method with the correct arguments', async () => { + + // Prepare + const persistenceLayer = new PersistenceLayerTestClass(); + const putRecordSpy = jest.spyOn(persistenceLayer, '_putRecord'); + const remainingTimeInMs = 2000; + + // Act + await persistenceLayer.saveInProgress({ foo: 'bar' }, remainingTimeInMs); + + // Assess + expect(putRecordSpy).toHaveBeenCalledWith( + expect.objectContaining({ + idempotencyKey: 'my-lambda-function#mocked-hash', + status: IdempotencyRecordStatus.INPROGRESS, + expiryTimestamp: Date.now() / 1000 + 3600, + payloadHash: 'mocked-hash', + inProgressExpiryTimestamp: Date.now() + remainingTimeInMs, + responseData: undefined, + }), + ); + + }); + + test('when called without remainingTimeInMillis, it logs a warning and then calls the _putRecord method', async () => { + + // Prepare + const persistenceLayer = new PersistenceLayerTestClass(); + const putRecordSpy = jest.spyOn(persistenceLayer, '_putRecord'); + const logWarningSpy = jest.spyOn(console, 'warn').mockImplementation(() => ({})); + + // Act + await persistenceLayer.saveInProgress({ foo: 'bar' }); + + // Assess + expect(putRecordSpy).toHaveBeenCalledTimes(1); + expect(logWarningSpy).toHaveBeenCalledWith( + 'Could not determine remaining time left. Did you call registerLambdaContext on IdempotencyConfig?', + ); + + }); + + }); + + describe('Method: saveSuccess', () => { + + test('when called, it calls the _updateRecord method with the correct arguments', async () => { + + // Prepare + const persistenceLayer = new PersistenceLayerTestClass(); + const updateRecordSpy = jest.spyOn(persistenceLayer, '_updateRecord'); + const result = { bar: 'baz' }; + + // Act + await persistenceLayer.saveSuccess({ foo: 'bar' }, result); + + // Assess + expect(updateRecordSpy).toHaveBeenCalledWith( + expect.objectContaining({ + idempotencyKey: 'my-lambda-function#mocked-hash', + status: IdempotencyRecordStatus.COMPLETED, + expiryTimestamp: Date.now() / 1000 + 3600, + payloadHash: 'mocked-hash', + inProgressExpiryTimestamp: undefined, + responseData: result, + }), + ); + + }); + + }); + +}); \ 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 8d95de55e2..3968f1f51e 100644 --- a/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts +++ b/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts @@ -1,19 +1,44 @@ /** - * Test DynamoDBPersistenceLayer class + * Test DynamoDBPersistenceLayer class * - * @group unit/idempotency/all + * @group unit/idempotency/persistence/dynamodb */ -import { DeleteCommand, DynamoDBDocument, GetCommand, PutCommand, UpdateCommand } from '@aws-sdk/lib-dynamodb'; +import { + DynamoDBPersistenceLayer +} from '../../../src/persistence/DynamoDBPersistenceLayer'; +import { + IdempotencyItemAlreadyExistsError, + IdempotencyItemNotFoundError +} from '../../../src/Exceptions'; +import { + IdempotencyRecord +} from '../../../src/persistence/IdempotencyRecord'; +import type { + DynamoPersistenceOptions +} from '../../../src/types'; +import { + IdempotencyRecordStatus +} from '../../../src/types'; +import { + DynamoDBClient, + DynamoDBServiceException, + PutItemCommand, + GetItemCommand, + UpdateItemCommand, + DeleteItemCommand, +} from '@aws-sdk/client-dynamodb'; +import { + marshall, +} from '@aws-sdk/util-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 { DynamoPersistenceConstructorOptions } from '../../../src/types/DynamoPersistenceConstructorOptions'; -import { IdempotencyRecordStatus } from '../../../src/types/IdempotencyRecordStatus'; + +const getFutureTimestamp = (seconds: number): number => new Date().getTime() + (seconds * 1000); describe('Class: DynamoDBPersistenceLayer', () => { + const ENVIRONMENT_VARIABLES = process.env; + const client = mockClient(DynamoDBClient); const dummyTableName = 'someTable'; const dummyKey = 'someKey'; @@ -36,25 +61,43 @@ describe('Class: DynamoDBPersistenceLayer', () => { } } + beforeAll(() => { + jest + .useFakeTimers() + .setSystemTime(new Date()); + }); + beforeEach(() => { jest.clearAllMocks(); + process.env = { ...ENVIRONMENT_VARIABLES }; + }); + + afterEach(() => { + client.reset(); + }); + + afterAll(() => { + process.env = ENVIRONMENT_VARIABLES; + jest.useRealTimers(); }); describe('Method: constructor', () => { test('when instantiated with minimum options it creates an instance with default values', () => { - // Prepare + // Prepare & Act const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName }); - // Act / Assess + // Assess expect(persistenceLayer).toEqual(expect.objectContaining({ tableName: dummyTableName, keyAttr: 'id', statusAttr: 'status', expiryAttr: 'expiration', inProgressExpiryAttr: 'in_progress_expiry_attr', - dataAttr: 'data' + dataAttr: 'data', + validationKeyAttr: 'validation', + staticPkValue: 'idempotency#my-lambda-function', })); }); @@ -62,95 +105,309 @@ describe('Class: DynamoDBPersistenceLayer', () => { test('when instantiated with specific options it creates an instance with correct values', () => { // Prepare - const testDynamoDBPersistenceLayerOptions: DynamoPersistenceConstructorOptions = { + const testDynamoDBPersistenceLayerOptions: DynamoPersistenceOptions = { tableName: dummyTableName, keyAttr: dummyKey, statusAttr: 'someStatusAttr', expiryAttr: 'someExpiryAttr', inProgressExpiryAttr: 'someInProgressExpiryAttr', - dataAttr: 'someDataAttr' + dataAttr: 'someDataAttr', + validationKeyAttr: 'someValidationKeyAttr', + staticPkValue: 'someStaticPkValue', + sortKeyAttr: 'someSortKeyAttr', }; + + // Act const persistenceLayer = new TestDynamoDBPersistenceLayer(testDynamoDBPersistenceLayerOptions); - // Act / Assess + // Assess expect(persistenceLayer).toEqual(expect.objectContaining({ tableName: dummyTableName, keyAttr: dummyKey, statusAttr: testDynamoDBPersistenceLayerOptions.statusAttr, expiryAttr: testDynamoDBPersistenceLayerOptions.expiryAttr, inProgressExpiryAttr: testDynamoDBPersistenceLayerOptions.inProgressExpiryAttr, - dataAttr: testDynamoDBPersistenceLayerOptions.dataAttr + dataAttr: testDynamoDBPersistenceLayerOptions.dataAttr, + validationKeyAttr: testDynamoDBPersistenceLayerOptions.validationKeyAttr, + staticPkValue: testDynamoDBPersistenceLayerOptions.staticPkValue, + sortKeyAttr: testDynamoDBPersistenceLayerOptions.sortKeyAttr, })); }); - }); + test('when instantiated with a sortKeyAttr that has same value of keyAttr, it throws', () => { - describe('Method: _putRecord', () => { + // Prepare + const testDynamoDBPersistenceLayerOptions: DynamoPersistenceOptions = { + tableName: dummyTableName, + keyAttr: dummyKey, + sortKeyAttr: dummyKey, + }; + + // Act & Assess + expect(() => new TestDynamoDBPersistenceLayer(testDynamoDBPersistenceLayerOptions)).toThrowError( + `keyAttr [${dummyKey}] and sortKeyAttr [${dummyKey}] cannot be the same!` + ); + + }); + + test('when instantiated with a custom AWS SDK client it uses that client', () => { + + // Prepare + const awsSdkV3Client = new DynamoDBClient({}); - const currentDateInMilliseconds = 1000; - const currentDateInSeconds = 1; + // Act + const persistenceLayer = new TestDynamoDBPersistenceLayer({ + tableName: dummyTableName, + awsSdkV3Client + }); + + // Assess + expect(persistenceLayer).toEqual(expect.objectContaining({ + tableName: dummyTableName, + client: awsSdkV3Client + })); + + }); + + test('when passed an invalid AWS SDK client it logs a warning', () => { + + // Prepare + const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); + + // Act + new TestDynamoDBPersistenceLayer({ + tableName: dummyTableName, + awsSdkV3Client: {} as DynamoDBClient + }); + + // Assess + expect(consoleWarnSpy).toHaveBeenCalledWith( + 'Invalid AWS SDK V3 client passed to DynamoDBPersistenceLayer. Using default client.' + ); - beforeEach(() => { - jest.spyOn(Date, 'now').mockReturnValue(currentDateInMilliseconds); }); + test('when passed a client config it stores it for later use', () => { + + // Prepare + const clientConfig = { + region: 'someRegion' + }; + + // Act + const persistenceLayer = new TestDynamoDBPersistenceLayer({ + tableName: dummyTableName, + clientConfig + }); + + // Assess + expect(persistenceLayer).toEqual(expect.objectContaining({ + tableName: dummyTableName, + clientConfig + })); + + }); + + }); + + describe('Method: _putRecord', () => { + test('when called with a record that meets conditions, it puts record in DynamoDB table', async () => { // Prepare const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName }); const status = IdempotencyRecordStatus.EXPIRED; const expiryTimestamp = 0; - const inProgressExpiryTimestamp = 0; const record = new IdempotencyRecord({ idempotencyKey: dummyKey, status, expiryTimestamp, - inProgressExpiryTimestamp }); - const dynamoClient = mockClient(DynamoDBDocument).on(PutCommand).resolves({}); // Act await persistenceLayer._putRecord(record); // Assess - expect(dynamoClient).toReceiveCommandWith(PutCommand, { + expect(client).toReceiveCommandWith(PutItemCommand, { 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' + Item: marshall({ + id: dummyKey, + expiration: expiryTimestamp, + status, + }), + ExpressionAttributeNames: { + '#id': 'id', + '#expiry': 'expiration', + '#status': 'status', + '#in_progress_expiry': 'in_progress_expiry_attr' + }, + ExpressionAttributeValues: marshall({ + ':now': Date.now() / 1000, + ':now_in_millis': Date.now(), + ':inprogress': IdempotencyRecordStatus.INPROGRESS + }), + ConditionExpression: 'attribute_not_exists(#id) OR #expiry < :now OR (#status = :inprogress AND attribute_exists(#in_progress_expiry) AND #in_progress_expiry < :now_in_millis)' }); }); - test('when called with a record that fails any condition, it throws IdempotencyItemAlreadyExistsError', async () => { - + test('when called with a record that uses composite key, it puts record in DynamoDB table', async () => { + // Prepare - const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName }); + const persistenceLayer = new TestDynamoDBPersistenceLayer({ + tableName: dummyTableName, + staticPkValue: 'idempotency#my-lambda-function', + sortKeyAttr: 'sortKey' + }); const status = IdempotencyRecordStatus.EXPIRED; const expiryTimestamp = 0; - const inProgressExpiryTimestamp = 0; - const record = new IdempotencyRecord({ + const record = new IdempotencyRecord({ idempotencyKey: dummyKey, status, expiryTimestamp, + }); + + // Act + await persistenceLayer._putRecord(record); + + // Assess + expect(client).toReceiveCommandWith(PutItemCommand, { + TableName: dummyTableName, + Item: marshall({ + id: 'idempotency#my-lambda-function', + sortKey: dummyKey, + expiration: expiryTimestamp, + status, + }), + ExpressionAttributeNames: { + '#id': 'id', + '#expiry': 'expiration', + '#status': 'status', + '#in_progress_expiry': 'in_progress_expiry_attr' + }, + ExpressionAttributeValues: marshall({ + ':now': Date.now() / 1000, + ':now_in_millis': Date.now(), + ':inprogress': IdempotencyRecordStatus.INPROGRESS + }), + ConditionExpression: 'attribute_not_exists(#id) OR #expiry < :now OR (#status = :inprogress AND attribute_exists(#in_progress_expiry) AND #in_progress_expiry < :now_in_millis)' + }); + + }); + + test('when called with a record that has inProgressExpiryTimestamp, it puts record in DynamoDB table', async () => { + + // Prepare + const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName }); + const status = IdempotencyRecordStatus.INPROGRESS; + const expiryTimestamp = getFutureTimestamp(10); + const inProgressExpiryTimestamp = getFutureTimestamp(5); + const record = new IdempotencyRecord({ + idempotencyKey: dummyKey, + status, + expiryTimestamp, inProgressExpiryTimestamp }); - const dynamoClient = mockClient(DynamoDBDocument).on(PutCommand).rejects({ name: 'ConditionalCheckFailedException' }); - // Act / Assess - await expect(persistenceLayer._putRecord(record)).rejects.toThrowError(IdempotencyItemAlreadyExistsError); - expect(dynamoClient).toReceiveCommandWith(PutCommand, { + // Act + await persistenceLayer._putRecord(record); + + // Assess + expect(client).toReceiveCommandWith(PutItemCommand, { 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' + Item: marshall({ + id: dummyKey, + expiration: expiryTimestamp, + status, + in_progress_expiry_attr: inProgressExpiryTimestamp + }), + ExpressionAttributeNames: { + '#id': 'id', + '#expiry': 'expiration', + '#status': 'status', + '#in_progress_expiry': 'in_progress_expiry_attr' + }, + ExpressionAttributeValues: marshall({ + ':now': Date.now() / 1000, + ':now_in_millis': Date.now(), + ':inprogress': IdempotencyRecordStatus.INPROGRESS + }), + ConditionExpression: 'attribute_not_exists(#id) OR #expiry < :now OR (#status = :inprogress AND attribute_exists(#in_progress_expiry) AND #in_progress_expiry < :now_in_millis)' }); }); + test('when called and and payload validation is enabled it puts record in DynamoDB table', async () => { + + // Prepare + const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName }); + jest.spyOn(persistenceLayer, 'isPayloadValidationEnabled').mockReturnValue(true); + const status = IdempotencyRecordStatus.EXPIRED; + const expiryTimestamp = 0; + const record = new IdempotencyRecord({ + idempotencyKey: dummyKey, + status, + expiryTimestamp, + payloadHash: 'someHash', + }); + + // Act + await persistenceLayer._putRecord(record); + + // Assess + expect(client).toReceiveCommandWith(PutItemCommand, { + TableName: dummyTableName, + Item: marshall({ + id: dummyKey, + expiration: expiryTimestamp, + status, + validation: record.payloadHash + }), + ExpressionAttributeNames: { + '#id': 'id', + '#expiry': 'expiration', + '#status': 'status', + '#in_progress_expiry': 'in_progress_expiry_attr' + }, + ExpressionAttributeValues: marshall({ + ':now': Date.now() / 1000, + ':now_in_millis': Date.now(), + ':inprogress': IdempotencyRecordStatus.INPROGRESS + }), + ConditionExpression: 'attribute_not_exists(#id) OR #expiry < :now OR (#status = :inprogress AND attribute_exists(#in_progress_expiry) AND #in_progress_expiry < :now_in_millis)' + }); + + }); + + test('when called with a record that fails any condition, it throws IdempotencyItemAlreadyExistsError', async () => { + + // Prepare + const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName }); + + const record = new IdempotencyRecord({ + idempotencyKey: dummyKey, + status: IdempotencyRecordStatus.EXPIRED, + expiryTimestamp: 0, + }); + client.on(PutItemCommand).rejects( + new DynamoDBServiceException({ + $fault: 'client', + $metadata: { + httpStatusCode: 400, + requestId: 'someRequestId', + }, + name: 'ConditionalCheckFailedException' + }) + ); + + // Act & Assess + await expect( + persistenceLayer._putRecord(record) + ).rejects.toThrowError(IdempotencyItemAlreadyExistsError); + + }); + test('when encountering an unknown error, it throws the causing error', async () => { // Prepare @@ -164,17 +421,10 @@ describe('Class: DynamoDBPersistenceLayer', () => { expiryTimestamp, inProgressExpiryTimestamp }); - const dynamoClient = mockClient(DynamoDBDocument).on(PutCommand).rejects(new Error()); + client.on(PutItemCommand).rejects(new Error()); - // Act / Assess + // Act & Assess await expect(persistenceLayer._putRecord(record)).rejects.toThrow(); - expect(dynamoClient).toReceiveCommandWith(PutCommand, { - 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' - }); }); @@ -182,57 +432,103 @@ describe('Class: DynamoDBPersistenceLayer', () => { describe('Method: _getRecord', () => { + test('it calls DynamoDB with correct parameters', async () => { + + // Prepare + const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName }); + client.on(GetItemCommand).resolves({ + Item: marshall({ + id: dummyKey, + status: IdempotencyRecordStatus.INPROGRESS, + expiration: getFutureTimestamp(15), + in_progress_expiry_attr: getFutureTimestamp(10), + data: {} + }), + }); + + // Act + await persistenceLayer._getRecord(dummyKey); + + // Assess + expect(client).toReceiveCommandWith(GetItemCommand, { + TableName: dummyTableName, + Key: marshall({ + id: dummyKey + }), + ConsistentRead: true + }); + + }); + 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 expiryTimestamp = getFutureTimestamp(15); + const inProgressExpiryTimestamp = getFutureTimestamp(10); const responseData = {}; - const dynamoClient = mockClient(DynamoDBDocument).on(GetCommand).resolves({ - Item: { + client.on(GetItemCommand).resolves({ + Item: marshall({ id: dummyKey, status, - 'expiration': expiryTimestamp, - 'in_progress_expiry_attr': inProgressExpiryTimestamp, + expiration: expiryTimestamp, + in_progress_expiry_attr: inProgressExpiryTimestamp, data: responseData - } + }), }); - jest.spyOn(Date, 'now').mockReturnValue(0); // Act - const record: IdempotencyRecord = await persistenceLayer._getRecord(dummyKey); + const record = await persistenceLayer._getRecord(dummyKey); // Assess - expect(dynamoClient).toReceiveCommandWith(GetCommand, { - TableName: dummyTableName, - Key: { - id: dummyKey - }, - ConsistentRead: true - }); + expect(record).toBeInstanceOf(IdempotencyRecord); expect(record.getStatus()).toEqual(IdempotencyRecordStatus.INPROGRESS); 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 persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName }); - const dynamoClient = mockClient(DynamoDBDocument).on(GetCommand).resolves({ Item: undefined }); - jest.spyOn(Date, 'now').mockReturnValue(0); + client.on(GetItemCommand).resolves({ Item: undefined }); - // Act / Assess + // Act & Assess await expect(persistenceLayer._getRecord(dummyKey)).rejects.toThrowError(IdempotencyItemNotFoundError); - expect(dynamoClient).toReceiveCommandWith(GetCommand, { - TableName: dummyTableName, - Key: { - id: dummyKey - }, + + }); + + test('when called with a record in a table that use composite key, it builds the request correctly', async () => { + + // Prepare + const persistenceLayer = new TestDynamoDBPersistenceLayer({ + tableName: dummyTableName, + staticPkValue: 'idempotency#my-lambda-function', + sortKeyAttr: 'sortKey' + }); + client.on(GetItemCommand).resolves({ + Item: marshall({ + id: dummyKey, + status: IdempotencyRecordStatus.INPROGRESS, + expiration: getFutureTimestamp(15), + data: {}, + }), + }); + + // Act + await persistenceLayer._getRecord(dummyKey); + + // Assess + expect(client).toReceiveCommandWith(GetItemCommand, { + TableName: dummyTableName, + Key: marshall({ + id: 'idempotency#my-lambda-function', + sortKey: dummyKey + }), ConsistentRead: true }); @@ -242,31 +538,80 @@ describe('Class: DynamoDBPersistenceLayer', () => { describe('Method: _updateRecord', () => { - test('when called to update a record, it resolves successfully', async () => { + test('when called to update a record, it updates the item with the correct parameters', async () => { // Prepare const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName }); const status = IdempotencyRecordStatus.EXPIRED; - const expiryTimestamp = 0; - const inProgressExpiryTimestamp = 0; + const expiryTimestamp = Date.now(); const record = new IdempotencyRecord({ idempotencyKey: dummyKey, status, expiryTimestamp, - inProgressExpiryTimestamp + responseData: {} }); - const dynamoClient = mockClient(DynamoDBDocument).on(UpdateCommand).resolves({}); // Act await persistenceLayer._updateRecord(record); // Assess - expect(dynamoClient).toReceiveCommandWith(UpdateCommand, { + expect(client).toReceiveCommandWith(UpdateItemCommand, { TableName: dummyTableName, - Key: { id: dummyKey }, - UpdateExpression: 'SET #status = :status, #expiry = :expiry', - ExpressionAttributeNames: { '#status': 'status', '#expiry': 'expiration' }, - ExpressionAttributeValues: { ':status': IdempotencyRecordStatus.EXPIRED,':expiry': expiryTimestamp }, + Key: marshall({ + id: dummyKey + }), + UpdateExpression: 'SET #response_data = :response_data, #expiry = :expiry, #status = :status', + ExpressionAttributeNames: { + '#status': 'status', + '#expiry': 'expiration', + '#response_data': 'data', + }, + ExpressionAttributeValues: marshall({ + ':status': IdempotencyRecordStatus.EXPIRED, + ':expiry': expiryTimestamp, + ':response_data': {} + }), + }); + + }); + + test('when called to update a record and payload validation is enabled, it adds the payload hash to the update expression', async () => { + + // Prepare + const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName }); + jest.spyOn(persistenceLayer, 'isPayloadValidationEnabled').mockImplementation(() => true); + const status = IdempotencyRecordStatus.EXPIRED; + const expiryTimestamp = Date.now(); + const record = new IdempotencyRecord({ + idempotencyKey: dummyKey, + status, + expiryTimestamp, + responseData: {}, + payloadHash: 'someHash' + }); + + // Act + await persistenceLayer._updateRecord(record); + + // Assess + expect(client).toReceiveCommandWith(UpdateItemCommand, { + TableName: dummyTableName, + Key: marshall({ + id: dummyKey + }), + UpdateExpression: 'SET #response_data = :response_data, #expiry = :expiry, #status = :status, #validation_key = :validation_key', + ExpressionAttributeNames: { + '#status': 'status', + '#expiry': 'expiration', + '#response_data': 'data', + '#validation_key': 'validation' + }, + ExpressionAttributeValues: marshall({ + ':status': IdempotencyRecordStatus.EXPIRED, + ':expiry': expiryTimestamp, + ':response_data': {}, + ':validation_key': record.payloadHash + }), }); }); @@ -275,28 +620,25 @@ describe('Class: DynamoDBPersistenceLayer', () => { describe('Method: _deleteRecord', () => { - test('when called with a valid record, record is deleted', async () => { + test('when called with a valid record, it calls the delete operation with the correct parameters', async () => { // Prepare const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName }); const status = IdempotencyRecordStatus.EXPIRED; - const expiryTimestamp = 0; - const inProgressExpiryTimestamp = 0; + const expiryTimestamp = Date.now(); const record = new IdempotencyRecord({ idempotencyKey: dummyKey, status, - expiryTimestamp, - inProgressExpiryTimestamp + expiryTimestamp, }); - const dynamoClient = mockClient(DynamoDBDocument).on(DeleteCommand).resolves({}); // Act await persistenceLayer._deleteRecord(record); // Assess - expect(dynamoClient).toReceiveCommandWith(DeleteCommand, { + expect(client).toReceiveCommandWith(DeleteItemCommand, { TableName: dummyTableName, - Key: { id: dummyKey } + Key: marshall({ id: dummyKey }) }); }); diff --git a/packages/idempotency/tests/unit/persistence/IdempotencyRecord.test.ts b/packages/idempotency/tests/unit/persistence/IdempotencyRecord.test.ts index 3d42ed2059..5b1a7531b1 100644 --- a/packages/idempotency/tests/unit/persistence/IdempotencyRecord.test.ts +++ b/packages/idempotency/tests/unit/persistence/IdempotencyRecord.test.ts @@ -1,11 +1,11 @@ /** * Test IdempotencyRecord class * - * @group unit/idempotency/all + * @group unit/idempotency/persistence/idempotencyRecord */ import { IdempotencyInvalidStatusError } from '../../../src/Exceptions'; import { IdempotencyRecord } from '../../../src/persistence/IdempotencyRecord'; -import { IdempotencyRecordStatus } from '../../../src/types/IdempotencyRecordStatus'; +import { IdempotencyRecordStatus } from '../../../src/types'; const mockIdempotencyKey = '123'; const mockData = undefined; diff --git a/packages/idempotency/tests/unit/persistence/PersistenceLayer.test.ts b/packages/idempotency/tests/unit/persistence/PersistenceLayer.test.ts deleted file mode 100644 index 5fe3c42cf5..0000000000 --- a/packages/idempotency/tests/unit/persistence/PersistenceLayer.test.ts +++ /dev/null @@ -1,304 +0,0 @@ -/** - * Test PersistenceLayer class - * - * @group unit/idempotency/all - */ -import { createHash, Hash } from 'crypto'; -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: PersistenceLayer', () => { - - const dummyData = 'someData'; - const idempotentFunctionName = 'foo'; - - 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: 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 with an empty option object 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 with an empty string as functionName it maintains the default value for the key prefix', () => { - - // Prepare - const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); - persistenceLayer.configure({ functionName: '' }); - - 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: idempotentFunctionName }); - - expect(persistenceLayer).toEqual(expect.objectContaining({ - idempotencyKeyPrefix: expectedIdempotencyKeyPrefix - })); - - }); - - }); - - 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 () => { - - // Prepare - const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); - - // 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(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 () => { - - // Prepare - const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); - const currentMillisTime = 3000; - const currentSeconds = currentMillisTime / 1000; - const oneHourSeconds = 60 * 60; - jest.spyOn(Date, 'now').mockReturnValue(currentMillisTime); - - // 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 () => { - - // 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(() => { - 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 () => { - - // Prepare - const result = {}; - const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); - - // 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 () => { - - // Prepare - const result = {}; - const expectedIdempotencyKey = `${process.env.AWS_LAMBDA_FUNCTION_NAME}.${idempotentFunctionName}#${mockDigest}`; - const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); - persistenceLayer.configure({ functionName: idempotentFunctionName }); - - // 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(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 () => { - - // Prepare - const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); - const result = {}; - const currentMillisTime = 3000; - const currentSeconds = currentMillisTime / 1000; - const oneHourSeconds = 60 * 60; - jest.spyOn(Date, 'now').mockReturnValue(currentMillisTime); - - // Act - await persistenceLayer.saveSuccess(dummyData, result); - - // Assess - 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', () => { - - // 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(() => { - 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', () => { - - // Prepare - const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass(); - const expectedIdempotencyKey = `${process.env.AWS_LAMBDA_FUNCTION_NAME}.${idempotentFunctionName}#${mockDigest}`; - persistenceLayer.configure({ functionName: idempotentFunctionName }); - - // Act - persistenceLayer.deleteRecord(dummyData); - - // Assess - const deletedIdempotencyRecord: IdempotencyRecord = deleteRecord.mock.calls[0][0]; - expect(deletedIdempotencyRecord.idempotencyKey).toEqual(expectedIdempotencyKey); - - }); - }); -}); \ No newline at end of file