From 5f7aabbf7fda14e40bc0cfce462dd49f73742a92 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dominique=20J=C3=A4ggi?=
Date: Tue, 28 Nov 2023 08:39:28 +0100
Subject: [PATCH 01/10] feat: add dynamo and utils modules
---
.eslintrc.cjs | 1 +
docs/API.md | 292 ++++
package-lock.json | 1327 ++++++++++++++++-
package.json | 2 +-
packages/spacecat-shared-dynamo/.jsdoc.json | 17 +
.../spacecat-shared-dynamo/.mocha-multi.json | 6 +
packages/spacecat-shared-dynamo/.npmignore | 9 +
packages/spacecat-shared-dynamo/.nycrc.json | 10 +
packages/spacecat-shared-dynamo/CHANGELOG.md | 18 +
packages/spacecat-shared-dynamo/LICENSE.txt | 264 ++++
packages/spacecat-shared-dynamo/README.md | 15 +
packages/spacecat-shared-dynamo/package.json | 38 +
.../spacecat-shared-dynamo/src/index.d.ts | 27 +
packages/spacecat-shared-dynamo/src/index.js | 135 ++
.../spacecat-shared-dynamo/test/index.test.js | 129 ++
packages/spacecat-shared-utils/.jsdoc.json | 17 +
.../spacecat-shared-utils/.mocha-multi.json | 6 +
packages/spacecat-shared-utils/.npmignore | 9 +
packages/spacecat-shared-utils/.nycrc.json | 10 +
packages/spacecat-shared-utils/CHANGELOG.md | 18 +
packages/spacecat-shared-utils/LICENSE.txt | 264 ++++
packages/spacecat-shared-utils/README.md | 3 +
packages/spacecat-shared-utils/package.json | 34 +
.../spacecat-shared-utils/src/functions.js | 161 ++
.../test/functions.test.js | 222 +++
25 files changed, 3029 insertions(+), 5 deletions(-)
create mode 100644 docs/API.md
create mode 100644 packages/spacecat-shared-dynamo/.jsdoc.json
create mode 100644 packages/spacecat-shared-dynamo/.mocha-multi.json
create mode 100644 packages/spacecat-shared-dynamo/.npmignore
create mode 100644 packages/spacecat-shared-dynamo/.nycrc.json
create mode 100644 packages/spacecat-shared-dynamo/CHANGELOG.md
create mode 100644 packages/spacecat-shared-dynamo/LICENSE.txt
create mode 100644 packages/spacecat-shared-dynamo/README.md
create mode 100644 packages/spacecat-shared-dynamo/package.json
create mode 100644 packages/spacecat-shared-dynamo/src/index.d.ts
create mode 100644 packages/spacecat-shared-dynamo/src/index.js
create mode 100644 packages/spacecat-shared-dynamo/test/index.test.js
create mode 100644 packages/spacecat-shared-utils/.jsdoc.json
create mode 100644 packages/spacecat-shared-utils/.mocha-multi.json
create mode 100644 packages/spacecat-shared-utils/.npmignore
create mode 100644 packages/spacecat-shared-utils/.nycrc.json
create mode 100644 packages/spacecat-shared-utils/CHANGELOG.md
create mode 100644 packages/spacecat-shared-utils/LICENSE.txt
create mode 100644 packages/spacecat-shared-utils/README.md
create mode 100644 packages/spacecat-shared-utils/package.json
create mode 100644 packages/spacecat-shared-utils/src/functions.js
create mode 100644 packages/spacecat-shared-utils/test/functions.test.js
diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index 8c1ec0943..19305ccbc 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -13,4 +13,5 @@
module.exports = {
root: true,
extends: '@adobe/helix',
+ plugins: ['import'],
};
diff --git a/docs/API.md b/docs/API.md
new file mode 100644
index 000000000..c4d46ad90
--- /dev/null
+++ b/docs/API.md
@@ -0,0 +1,292 @@
+## Functions
+
+
+createClient(log, dbClient, docClient) ⇒ Object
+Creates a client object for interacting with DynamoDB.
+
+query(originalParams) ⇒ Promise.<Array>
+Queries DynamoDB and automatically handles pagination to retrieve all items.
+
+getItem(tableName, partitionKey, [sortKey]) ⇒ Promise.<Object>
+Retrieves an item from DynamoDB using a table name and key.
+
+putItem(tableName, item) ⇒ Promise.<Object>
+Inserts or updates an item in a DynamoDB table.
+
+removeItem(tableName, partitionKey, [sortKey]) ⇒ Promise.<Object>
+Removes an item from a DynamoDB table.
+
+isBoolean(value) ⇒ boolean
+Determines if the given value is a boolean or a string representation of a boolean.
+
+isInteger(value) ⇒ boolean
+Checks if the given value is an integer.
+
+isNumber(value) ⇒ boolean
+Determines if the given value is a number.
+
+isObject(obj) ⇒ boolean
+Checks if the given parameter is an object and not an array or null.
+
+isString(str) ⇒ boolean
+Determines if the given parameter is a string.
+
+hasText(str) ⇒ boolean
+Checks if the given string is not empty.
+
+isValidDate(obj) ⇒ boolean
+Checks whether the given object is a valid JavaScript Date.
+
+isIsoDate(str) ⇒ boolean
+Validates whether the given string is a JavaScript ISO date string in
+Zulu (UTC) timezone. Used for persisting system dates, which must be independent of any user timezone.
+
+isIsoTimeOffsetsDate(str) ⇒ boolean
+Validates whether the given string is a JavaScript ISO date string
+following UTC time offsets format.
+
+isValidUrl(urlString) ⇒ boolean
+Validates whether the given string is a valid URL with http or https protocol.
+
+toBoolean(value) ⇒ boolean
+Converts a given value to a boolean. Throws an error if the value is not a boolean.
+
+arrayEquals(a, b) ⇒ boolean
+Compares two arrays for equality.
+
+
+
+
+
+## createClient(log, dbClient, docClient) ⇒ Object
+Creates a client object for interacting with DynamoDB.
+
+**Kind**: global function
+**Returns**: Object - A client object with methods to interact with DynamoDB.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| log | Object | The logging object, defaults to console. |
+| dbClient | DynamoDB | The AWS SDK DynamoDB client instance. |
+| docClient | DynamoDBDocumentClient | The AWS SDK DynamoDB Document client instance. |
+
+
+
+## query(originalParams) ⇒ Promise.<Array>
+Queries DynamoDB and automatically handles pagination to retrieve all items.
+
+**Kind**: global function
+**Returns**: Promise.<Array> - A promise that resolves to an array of items retrieved from DynamoDB.
+**Throws**:
+
+- Error Throws an error if the DynamoDB query operation fails.
+
+
+| Param | Type | Description |
+| --- | --- | --- |
+| originalParams | Object | The parameters for the DynamoDB query. |
+
+
+
+## getItem(tableName, partitionKey, [sortKey]) ⇒ Promise.<Object>
+Retrieves an item from DynamoDB using a table name and key.
+
+**Kind**: global function
+**Returns**: Promise.<Object> - A promise that resolves to the retrieved item.
+**Throws**:
+
+- Error Throws an error if the DynamoDB get operation fails.
+
+
+| Param | Type | Description |
+| --- | --- | --- |
+| tableName | string | The name of the DynamoDB table. |
+| partitionKey | string | The partition key of the item to retrieve. |
+| [sortKey] | string | The sort key of the item to retrieve, if applicable. |
+
+
+
+## putItem(tableName, item) ⇒ Promise.<Object>
+Inserts or updates an item in a DynamoDB table.
+
+**Kind**: global function
+**Returns**: Promise.<Object> - A promise that resolves to a message indicating success.
+**Throws**:
+
+- Error Throws an error if the DynamoDB put operation fails.
+
+
+| Param | Type | Description |
+| --- | --- | --- |
+| tableName | string | The name of the DynamoDB table. |
+| item | Object | The item to insert or update in the table. |
+
+
+
+## removeItem(tableName, partitionKey, [sortKey]) ⇒ Promise.<Object>
+Removes an item from a DynamoDB table.
+
+**Kind**: global function
+**Returns**: Promise.<Object> - A promise that resolves to a message indicating successful removal.
+**Throws**:
+
+- Error Throws an error if the DynamoDB delete operation fails.
+
+
+| Param | Type | Description |
+| --- | --- | --- |
+| tableName | string | The name of the DynamoDB table. |
+| partitionKey | string | The partition key of the item to remove. |
+| [sortKey] | string | The sort key of the item to remove, if applicable. |
+
+
+
+## isBoolean(value) ⇒ boolean
+Determines if the given value is a boolean or a string representation of a boolean.
+
+**Kind**: global function
+**Returns**: boolean - True if the value is a boolean or a string representation of a boolean.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| value | \* | The value to check. |
+
+
+
+## isInteger(value) ⇒ boolean
+Checks if the given value is an integer.
+
+**Kind**: global function
+**Returns**: boolean - True if the value is an integer, false otherwise.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| value | \* | The value to check. |
+
+
+
+## isNumber(value) ⇒ boolean
+Determines if the given value is a number.
+
+**Kind**: global function
+**Returns**: boolean - True if the value is a finite number, false otherwise.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| value | \* | The value to check. |
+
+
+
+## isObject(obj) ⇒ boolean
+Checks if the given parameter is an object and not an array or null.
+
+**Kind**: global function
+**Returns**: boolean - True if the parameter is an object, false otherwise.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| obj | \* | The object to check. |
+
+
+
+## isString(str) ⇒ boolean
+Determines if the given parameter is a string.
+
+**Kind**: global function
+**Returns**: boolean - True if the parameter is a string, false otherwise.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| str | \* | The string to check. |
+
+
+
+## hasText(str) ⇒ boolean
+Checks if the given string is not empty.
+
+**Kind**: global function
+**Returns**: boolean - True if the string is not empty, false otherwise.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| str | \* | The string to check. |
+
+
+
+## isValidDate(obj) ⇒ boolean
+Checks whether the given object is a valid JavaScript Date.
+
+**Kind**: global function
+**Returns**: boolean - True if the given object is a valid Date object, false otherwise.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| obj | \* | The object to check. |
+
+
+
+## isIsoDate(str) ⇒ boolean
+Validates whether the given string is a JavaScript ISO date string in
+Zulu (UTC) timezone. Used for persisting system dates, which must be independent of any user timezone.
+
+**Kind**: global function
+**Returns**: boolean - True if the given string validates successfully.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| str | string | The string to validate. |
+
+
+
+## isIsoTimeOffsetsDate(str) ⇒ boolean
+Validates whether the given string is a JavaScript ISO date string
+following UTC time offsets format.
+
+**Kind**: global function
+**Returns**: boolean - True if the given string validates successfully.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| str | string | The string to validate. |
+
+
+
+## isValidUrl(urlString) ⇒ boolean
+Validates whether the given string is a valid URL with http or https protocol.
+
+**Kind**: global function
+**Returns**: boolean - True if the given string validates successfully.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| urlString | string | The string to validate. |
+
+
+
+## toBoolean(value) ⇒ boolean
+Converts a given value to a boolean. Throws an error if the value is not a boolean.
+
+**Kind**: global function
+**Returns**: boolean - The converted boolean value.
+**Throws**:
+
+- Error If the value is not a boolean or a boolean-like string.
+
+
+| Param | Type | Description |
+| --- | --- | --- |
+| value | \* | The value to convert. |
+
+
+
+## arrayEquals(a, b) ⇒ boolean
+Compares two arrays for equality.
+
+**Kind**: global function
+**Returns**: boolean - True if the arrays are equal, false otherwise.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| a | Array | The first array to compare. |
+| b | Array | The second array to compare. |
+
diff --git a/package-lock.json b/package-lock.json
index 37bb48d51..e6344d0ba 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -92,10 +92,653 @@
"aws4": "1.12.0"
}
},
+ "node_modules/@adobe/spacecat-shared-dynamo": {
+ "resolved": "packages/spacecat-shared-dynamo",
+ "link": true
+ },
"node_modules/@adobe/spacecat-shared-example": {
"resolved": "packages/spacecat-shared-example",
"link": true
},
+ "node_modules/@adobe/spacecat-shared-utils": {
+ "resolved": "packages/spacecat-shared-utils",
+ "link": true
+ },
+ "node_modules/@aws-crypto/crc32": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz",
+ "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==",
+ "dependencies": {
+ "@aws-crypto/util": "^3.0.0",
+ "@aws-sdk/types": "^3.222.0",
+ "tslib": "^1.11.1"
+ }
+ },
+ "node_modules/@aws-crypto/crc32/node_modules/tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
+ },
+ "node_modules/@aws-crypto/ie11-detection": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz",
+ "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==",
+ "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=="
+ },
+ "node_modules/@aws-crypto/sha256-browser": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz",
+ "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==",
+ "dependencies": {
+ "@aws-crypto/ie11-detection": "^3.0.0",
+ "@aws-crypto/sha256-js": "^3.0.0",
+ "@aws-crypto/supports-web-crypto": "^3.0.0",
+ "@aws-crypto/util": "^3.0.0",
+ "@aws-sdk/types": "^3.222.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=="
+ },
+ "node_modules/@aws-crypto/sha256-js": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz",
+ "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==",
+ "dependencies": {
+ "@aws-crypto/util": "^3.0.0",
+ "@aws-sdk/types": "^3.222.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=="
+ },
+ "node_modules/@aws-crypto/supports-web-crypto": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz",
+ "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==",
+ "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=="
+ },
+ "node_modules/@aws-crypto/util": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz",
+ "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==",
+ "dependencies": {
+ "@aws-sdk/types": "^3.222.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=="
+ },
+ "node_modules/@aws-sdk/client-dynamodb": {
+ "version": "3.454.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.454.0.tgz",
+ "integrity": "sha512-GztsAk/OwhksDYclc0JoqhoXGqowzfyqJ6405L6m1x3ydS0B0csul/1xnCmmyVEIr9aLJZK7dk0DgdfKsyj3KA==",
+ "dependencies": {
+ "@aws-crypto/sha256-browser": "3.0.0",
+ "@aws-crypto/sha256-js": "3.0.0",
+ "@aws-sdk/client-sts": "3.454.0",
+ "@aws-sdk/core": "3.451.0",
+ "@aws-sdk/credential-provider-node": "3.451.0",
+ "@aws-sdk/middleware-endpoint-discovery": "3.451.0",
+ "@aws-sdk/middleware-host-header": "3.451.0",
+ "@aws-sdk/middleware-logger": "3.451.0",
+ "@aws-sdk/middleware-recursion-detection": "3.451.0",
+ "@aws-sdk/middleware-signing": "3.451.0",
+ "@aws-sdk/middleware-user-agent": "3.451.0",
+ "@aws-sdk/region-config-resolver": "3.451.0",
+ "@aws-sdk/types": "3.451.0",
+ "@aws-sdk/util-endpoints": "3.451.0",
+ "@aws-sdk/util-user-agent-browser": "3.451.0",
+ "@aws-sdk/util-user-agent-node": "3.451.0",
+ "@smithy/config-resolver": "^2.0.18",
+ "@smithy/fetch-http-handler": "^2.2.6",
+ "@smithy/hash-node": "^2.0.15",
+ "@smithy/invalid-dependency": "^2.0.13",
+ "@smithy/middleware-content-length": "^2.0.15",
+ "@smithy/middleware-endpoint": "^2.2.0",
+ "@smithy/middleware-retry": "^2.0.20",
+ "@smithy/middleware-serde": "^2.0.13",
+ "@smithy/middleware-stack": "^2.0.7",
+ "@smithy/node-config-provider": "^2.1.5",
+ "@smithy/node-http-handler": "^2.1.9",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/smithy-client": "^2.1.15",
+ "@smithy/types": "^2.5.0",
+ "@smithy/url-parser": "^2.0.13",
+ "@smithy/util-base64": "^2.0.1",
+ "@smithy/util-body-length-browser": "^2.0.0",
+ "@smithy/util-body-length-node": "^2.1.0",
+ "@smithy/util-defaults-mode-browser": "^2.0.19",
+ "@smithy/util-defaults-mode-node": "^2.0.25",
+ "@smithy/util-endpoints": "^1.0.4",
+ "@smithy/util-retry": "^2.0.6",
+ "@smithy/util-utf8": "^2.0.2",
+ "@smithy/util-waiter": "^2.0.13",
+ "tslib": "^2.5.0",
+ "uuid": "^8.3.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/client-sso": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.451.0.tgz",
+ "integrity": "sha512-KkYSke3Pdv3MfVH/5fT528+MKjMyPKlcLcd4zQb0x6/7Bl7EHrPh1JZYjzPLHelb+UY5X0qN8+cb8iSu1eiwIQ==",
+ "dependencies": {
+ "@aws-crypto/sha256-browser": "3.0.0",
+ "@aws-crypto/sha256-js": "3.0.0",
+ "@aws-sdk/core": "3.451.0",
+ "@aws-sdk/middleware-host-header": "3.451.0",
+ "@aws-sdk/middleware-logger": "3.451.0",
+ "@aws-sdk/middleware-recursion-detection": "3.451.0",
+ "@aws-sdk/middleware-user-agent": "3.451.0",
+ "@aws-sdk/region-config-resolver": "3.451.0",
+ "@aws-sdk/types": "3.451.0",
+ "@aws-sdk/util-endpoints": "3.451.0",
+ "@aws-sdk/util-user-agent-browser": "3.451.0",
+ "@aws-sdk/util-user-agent-node": "3.451.0",
+ "@smithy/config-resolver": "^2.0.18",
+ "@smithy/fetch-http-handler": "^2.2.6",
+ "@smithy/hash-node": "^2.0.15",
+ "@smithy/invalid-dependency": "^2.0.13",
+ "@smithy/middleware-content-length": "^2.0.15",
+ "@smithy/middleware-endpoint": "^2.2.0",
+ "@smithy/middleware-retry": "^2.0.20",
+ "@smithy/middleware-serde": "^2.0.13",
+ "@smithy/middleware-stack": "^2.0.7",
+ "@smithy/node-config-provider": "^2.1.5",
+ "@smithy/node-http-handler": "^2.1.9",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/smithy-client": "^2.1.15",
+ "@smithy/types": "^2.5.0",
+ "@smithy/url-parser": "^2.0.13",
+ "@smithy/util-base64": "^2.0.1",
+ "@smithy/util-body-length-browser": "^2.0.0",
+ "@smithy/util-body-length-node": "^2.1.0",
+ "@smithy/util-defaults-mode-browser": "^2.0.19",
+ "@smithy/util-defaults-mode-node": "^2.0.25",
+ "@smithy/util-endpoints": "^1.0.4",
+ "@smithy/util-retry": "^2.0.6",
+ "@smithy/util-utf8": "^2.0.2",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/client-sts": {
+ "version": "3.454.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.454.0.tgz",
+ "integrity": "sha512-0fDvr8WeB6IYO8BUCzcivWmahgGl/zDbaYfakzGnt4mrl5ztYaXE875WI6b7+oFcKMRvN+KLvwu5TtyFuNY+GQ==",
+ "dependencies": {
+ "@aws-crypto/sha256-browser": "3.0.0",
+ "@aws-crypto/sha256-js": "3.0.0",
+ "@aws-sdk/core": "3.451.0",
+ "@aws-sdk/credential-provider-node": "3.451.0",
+ "@aws-sdk/middleware-host-header": "3.451.0",
+ "@aws-sdk/middleware-logger": "3.451.0",
+ "@aws-sdk/middleware-recursion-detection": "3.451.0",
+ "@aws-sdk/middleware-sdk-sts": "3.451.0",
+ "@aws-sdk/middleware-signing": "3.451.0",
+ "@aws-sdk/middleware-user-agent": "3.451.0",
+ "@aws-sdk/region-config-resolver": "3.451.0",
+ "@aws-sdk/types": "3.451.0",
+ "@aws-sdk/util-endpoints": "3.451.0",
+ "@aws-sdk/util-user-agent-browser": "3.451.0",
+ "@aws-sdk/util-user-agent-node": "3.451.0",
+ "@smithy/config-resolver": "^2.0.18",
+ "@smithy/fetch-http-handler": "^2.2.6",
+ "@smithy/hash-node": "^2.0.15",
+ "@smithy/invalid-dependency": "^2.0.13",
+ "@smithy/middleware-content-length": "^2.0.15",
+ "@smithy/middleware-endpoint": "^2.2.0",
+ "@smithy/middleware-retry": "^2.0.20",
+ "@smithy/middleware-serde": "^2.0.13",
+ "@smithy/middleware-stack": "^2.0.7",
+ "@smithy/node-config-provider": "^2.1.5",
+ "@smithy/node-http-handler": "^2.1.9",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/smithy-client": "^2.1.15",
+ "@smithy/types": "^2.5.0",
+ "@smithy/url-parser": "^2.0.13",
+ "@smithy/util-base64": "^2.0.1",
+ "@smithy/util-body-length-browser": "^2.0.0",
+ "@smithy/util-body-length-node": "^2.1.0",
+ "@smithy/util-defaults-mode-browser": "^2.0.19",
+ "@smithy/util-defaults-mode-node": "^2.0.25",
+ "@smithy/util-endpoints": "^1.0.4",
+ "@smithy/util-retry": "^2.0.6",
+ "@smithy/util-utf8": "^2.0.2",
+ "fast-xml-parser": "4.2.5",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/core": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.451.0.tgz",
+ "integrity": "sha512-SamWW2zHEf1ZKe3j1w0Piauryl8BQIlej0TBS18A4ACzhjhWXhCs13bO1S88LvPR5mBFXok3XOT6zPOnKDFktw==",
+ "dependencies": {
+ "@smithy/smithy-client": "^2.1.15",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-env": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.451.0.tgz",
+ "integrity": "sha512-9dAav7DcRgaF7xCJEQR5ER9ErXxnu/tdnVJ+UPmb1NPeIZdESv1A3lxFDEq1Fs8c4/lzAj9BpshGyJVIZwZDKg==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/property-provider": "^2.0.0",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-ini": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.451.0.tgz",
+ "integrity": "sha512-TySt64Ci5/ZbqFw1F9Z0FIGvYx5JSC9e6gqDnizIYd8eMnn8wFRUscRrD7pIHKfrhvVKN5h0GdYovmMO/FMCBw==",
+ "dependencies": {
+ "@aws-sdk/credential-provider-env": "3.451.0",
+ "@aws-sdk/credential-provider-process": "3.451.0",
+ "@aws-sdk/credential-provider-sso": "3.451.0",
+ "@aws-sdk/credential-provider-web-identity": "3.451.0",
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/credential-provider-imds": "^2.0.0",
+ "@smithy/property-provider": "^2.0.0",
+ "@smithy/shared-ini-file-loader": "^2.0.6",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-node": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.451.0.tgz",
+ "integrity": "sha512-AEwM1WPyxUdKrKyUsKyFqqRFGU70e4qlDyrtBxJnSU9NRLZI8tfEZ67bN7fHSxBUBODgDXpMSlSvJiBLh5/3pw==",
+ "dependencies": {
+ "@aws-sdk/credential-provider-env": "3.451.0",
+ "@aws-sdk/credential-provider-ini": "3.451.0",
+ "@aws-sdk/credential-provider-process": "3.451.0",
+ "@aws-sdk/credential-provider-sso": "3.451.0",
+ "@aws-sdk/credential-provider-web-identity": "3.451.0",
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/credential-provider-imds": "^2.0.0",
+ "@smithy/property-provider": "^2.0.0",
+ "@smithy/shared-ini-file-loader": "^2.0.6",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-process": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.451.0.tgz",
+ "integrity": "sha512-HQywSdKeD5PErcLLnZfSyCJO+6T+ZyzF+Lm/QgscSC+CbSUSIPi//s15qhBRVely/3KBV6AywxwNH+5eYgt4lQ==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/property-provider": "^2.0.0",
+ "@smithy/shared-ini-file-loader": "^2.0.6",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-sso": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.451.0.tgz",
+ "integrity": "sha512-Usm/N51+unOt8ID4HnQzxIjUJDrkAQ1vyTOC0gSEEJ7h64NSSPGD5yhN7il5WcErtRd3EEtT1a8/GTC5TdBctg==",
+ "dependencies": {
+ "@aws-sdk/client-sso": "3.451.0",
+ "@aws-sdk/token-providers": "3.451.0",
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/property-provider": "^2.0.0",
+ "@smithy/shared-ini-file-loader": "^2.0.6",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-web-identity": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.451.0.tgz",
+ "integrity": "sha512-Xtg3Qw65EfDjWNG7o2xD6sEmumPfsy3WDGjk2phEzVg8s7hcZGxf5wYwe6UY7RJvlEKrU0rFA+AMn6Hfj5oOzg==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/property-provider": "^2.0.0",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/endpoint-cache": {
+ "version": "3.310.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.310.0.tgz",
+ "integrity": "sha512-y3wipforet41EDTI0vnzxILqwAGll1KfI5qcdX9pXF/WF1f+3frcOtPiWtQEZQpy4czRogKm3BHo70QBYAZxlQ==",
+ "dependencies": {
+ "mnemonist": "0.38.3",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/lib-dynamodb": {
+ "version": "3.454.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.454.0.tgz",
+ "integrity": "sha512-qeu0ZhTBXrAGzP9fpZDN88u7tLWtoBdGebC0WvE9oMaQnGICu1mgLwb6u+u8TAS19cvPySHln9VmSVs52m8L1g==",
+ "dependencies": {
+ "@aws-sdk/util-dynamodb": "3.454.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "@aws-sdk/client-dynamodb": "^3.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-endpoint-discovery": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.451.0.tgz",
+ "integrity": "sha512-OvRb9M12JZaxjpdwPqTndAexjp/s4Ub/GumxED2uy7KDzo95f2A4CA3yRB03ExBERWtKtCShpJG87oTERkDiaQ==",
+ "dependencies": {
+ "@aws-sdk/endpoint-cache": "3.310.0",
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/node-config-provider": "^2.1.5",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-host-header": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.451.0.tgz",
+ "integrity": "sha512-j8a5jAfhWmsK99i2k8oR8zzQgXrsJtgrLxc3js6U+525mcZytoiDndkWTmD5fjJ1byU1U2E5TaPq+QJeDip05Q==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-logger": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.451.0.tgz",
+ "integrity": "sha512-0kHrYEyVeB2QBfP6TfbI240aRtatLZtcErJbhpiNUb+CQPgEL3crIjgVE8yYiJumZ7f0jyjo8HLPkwD1/2APaw==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-recursion-detection": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.451.0.tgz",
+ "integrity": "sha512-J6jL6gJ7orjHGM70KDRcCP7so/J2SnkN4vZ9YRLTeeZY6zvBuHDjX8GCIgSqPn/nXFXckZO8XSnA7u6+3TAT0w==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-sdk-sts": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.451.0.tgz",
+ "integrity": "sha512-UJ6UfVUEgp0KIztxpAeelPXI5MLj9wUtUCqYeIMP7C1ZhoEMNm3G39VLkGN43dNhBf1LqjsV9jkKMZbVfYXuwg==",
+ "dependencies": {
+ "@aws-sdk/middleware-signing": "3.451.0",
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-signing": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.451.0.tgz",
+ "integrity": "sha512-s5ZlcIoLNg1Huj4Qp06iKniE8nJt/Pj1B/fjhWc6cCPCM7XJYUCejCnRh6C5ZJoBEYodjuwZBejPc1Wh3j+znA==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/property-provider": "^2.0.0",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/signature-v4": "^2.0.0",
+ "@smithy/types": "^2.5.0",
+ "@smithy/util-middleware": "^2.0.6",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-user-agent": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.451.0.tgz",
+ "integrity": "sha512-8NM/0JiKLNvT9wtAQVl1DFW0cEO7OvZyLSUBLNLTHqyvOZxKaZ8YFk7d8PL6l76LeUKRxq4NMxfZQlUIRe0eSA==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@aws-sdk/util-endpoints": "3.451.0",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/region-config-resolver": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.451.0.tgz",
+ "integrity": "sha512-3iMf4OwzrFb4tAAmoROXaiORUk2FvSejnHIw/XHvf/jjR4EqGGF95NZP/n/MeFZMizJWVssrwS412GmoEyoqhg==",
+ "dependencies": {
+ "@smithy/node-config-provider": "^2.1.5",
+ "@smithy/types": "^2.5.0",
+ "@smithy/util-config-provider": "^2.0.0",
+ "@smithy/util-middleware": "^2.0.6",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/token-providers": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.451.0.tgz",
+ "integrity": "sha512-ij1L5iUbn6CwxVOT1PG4NFjsrsKN9c4N1YEM0lkl6DwmaNOscjLKGSNyj9M118vSWsOs1ZDbTwtj++h0O/BWrQ==",
+ "dependencies": {
+ "@aws-crypto/sha256-browser": "3.0.0",
+ "@aws-crypto/sha256-js": "3.0.0",
+ "@aws-sdk/middleware-host-header": "3.451.0",
+ "@aws-sdk/middleware-logger": "3.451.0",
+ "@aws-sdk/middleware-recursion-detection": "3.451.0",
+ "@aws-sdk/middleware-user-agent": "3.451.0",
+ "@aws-sdk/region-config-resolver": "3.451.0",
+ "@aws-sdk/types": "3.451.0",
+ "@aws-sdk/util-endpoints": "3.451.0",
+ "@aws-sdk/util-user-agent-browser": "3.451.0",
+ "@aws-sdk/util-user-agent-node": "3.451.0",
+ "@smithy/config-resolver": "^2.0.18",
+ "@smithy/fetch-http-handler": "^2.2.6",
+ "@smithy/hash-node": "^2.0.15",
+ "@smithy/invalid-dependency": "^2.0.13",
+ "@smithy/middleware-content-length": "^2.0.15",
+ "@smithy/middleware-endpoint": "^2.2.0",
+ "@smithy/middleware-retry": "^2.0.20",
+ "@smithy/middleware-serde": "^2.0.13",
+ "@smithy/middleware-stack": "^2.0.7",
+ "@smithy/node-config-provider": "^2.1.5",
+ "@smithy/node-http-handler": "^2.1.9",
+ "@smithy/property-provider": "^2.0.0",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/shared-ini-file-loader": "^2.0.6",
+ "@smithy/smithy-client": "^2.1.15",
+ "@smithy/types": "^2.5.0",
+ "@smithy/url-parser": "^2.0.13",
+ "@smithy/util-base64": "^2.0.1",
+ "@smithy/util-body-length-browser": "^2.0.0",
+ "@smithy/util-body-length-node": "^2.1.0",
+ "@smithy/util-defaults-mode-browser": "^2.0.19",
+ "@smithy/util-defaults-mode-node": "^2.0.25",
+ "@smithy/util-endpoints": "^1.0.4",
+ "@smithy/util-retry": "^2.0.6",
+ "@smithy/util-utf8": "^2.0.2",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/types": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.451.0.tgz",
+ "integrity": "sha512-rhK+qeYwCIs+laJfWCcrYEjay2FR/9VABZJ2NRM89jV/fKqGVQR52E5DQqrI+oEIL5JHMhhnr4N4fyECMS35lw==",
+ "dependencies": {
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/util-dynamodb": {
+ "version": "3.454.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.454.0.tgz",
+ "integrity": "sha512-gcxzlUzFHHLnnDB8Pc3BqRc2WmbVDf3A8Xre1H5zPNtF8bZihYj0xk3KMK9Sfnk/tkDTL2PfQ2qNf7R7RLTNpw==",
+ "dependencies": {
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "@aws-sdk/client-dynamodb": "^3.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/util-endpoints": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.451.0.tgz",
+ "integrity": "sha512-giqLGBTnRIcKkDqwU7+GQhKbtJ5Ku35cjGQIfMyOga6pwTBUbaK0xW1Sdd8sBQ1GhApscnChzI9o/R9x0368vw==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/util-endpoints": "^1.0.4",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/util-locate-window": {
+ "version": "3.310.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.310.0.tgz",
+ "integrity": "sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==",
+ "dependencies": {
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/util-user-agent-browser": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.451.0.tgz",
+ "integrity": "sha512-Ws5mG3J0TQifH7OTcMrCTexo7HeSAc3cBgjfhS/ofzPUzVCtsyg0G7I6T7wl7vJJETix2Kst2cpOsxygPgPD9w==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/types": "^2.5.0",
+ "bowser": "^2.11.0",
+ "tslib": "^2.5.0"
+ }
+ },
+ "node_modules/@aws-sdk/util-user-agent-node": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.451.0.tgz",
+ "integrity": "sha512-TBzm6P+ql4mkGFAjPlO1CI+w3yUT+NulaiALjl/jNX/nnUp6HsJsVxJf4nVFQTG5KRV0iqMypcs7I3KIhH+LmA==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/node-config-provider": "^2.1.5",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "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.259.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz",
+ "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==",
+ "dependencies": {
+ "tslib": "^2.3.1"
+ }
+ },
"node_modules/@babel/code-frame": {
"version": "7.23.4",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.4.tgz",
@@ -842,6 +1485,520 @@
"semantic-release": ">=18.0.0-beta.1"
}
},
+ "node_modules/@smithy/abort-controller": {
+ "version": "2.0.14",
+ "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.0.14.tgz",
+ "integrity": "sha512-zXtteuYLWbSXnzI3O6xq3FYvigYZFW8mdytGibfarLL2lxHto9L3ILtGVnVGmFZa7SDh62l39EnU5hesLN87Fw==",
+ "dependencies": {
+ "@smithy/types": "^2.6.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/config-resolver": {
+ "version": "2.0.19",
+ "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-2.0.19.tgz",
+ "integrity": "sha512-JsghnQ5zjWmjEVY8TFOulLdEOCj09SjRLugrHlkPZTIBBm7PQitCFVLThbsKPZQOP7N3ME1DU1nKUc1UaVnBog==",
+ "dependencies": {
+ "@smithy/node-config-provider": "^2.1.6",
+ "@smithy/types": "^2.6.0",
+ "@smithy/util-config-provider": "^2.0.0",
+ "@smithy/util-middleware": "^2.0.7",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/credential-provider-imds": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-2.1.2.tgz",
+ "integrity": "sha512-Y62jBWdoLPSYjr9fFvJf+KwTa1EunjVr6NryTEWCnwIY93OJxwV4t0qxjwdPl/XMsUkq79ppNJSEQN6Ohnhxjw==",
+ "dependencies": {
+ "@smithy/node-config-provider": "^2.1.6",
+ "@smithy/property-provider": "^2.0.15",
+ "@smithy/types": "^2.6.0",
+ "@smithy/url-parser": "^2.0.14",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/eventstream-codec": {
+ "version": "2.0.14",
+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-2.0.14.tgz",
+ "integrity": "sha512-g/OU/MeWGfHDygoXgMWfG/Xb0QqDnAGcM9t2FRrVAhleXYRddGOEnfanR5cmHgB9ue52MJsyorqFjckzXsylaA==",
+ "dependencies": {
+ "@aws-crypto/crc32": "3.0.0",
+ "@smithy/types": "^2.6.0",
+ "@smithy/util-hex-encoding": "^2.0.0",
+ "tslib": "^2.5.0"
+ }
+ },
+ "node_modules/@smithy/fetch-http-handler": {
+ "version": "2.2.7",
+ "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.2.7.tgz",
+ "integrity": "sha512-iSDBjxuH9TgrtMYAr7j5evjvkvgwLY3y+9D547uep+JNkZ1ZT+BaeU20j6I/bO/i26ilCWFImrlXTPsfQtZdIQ==",
+ "dependencies": {
+ "@smithy/protocol-http": "^3.0.10",
+ "@smithy/querystring-builder": "^2.0.14",
+ "@smithy/types": "^2.6.0",
+ "@smithy/util-base64": "^2.0.1",
+ "tslib": "^2.5.0"
+ }
+ },
+ "node_modules/@smithy/hash-node": {
+ "version": "2.0.16",
+ "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.0.16.tgz",
+ "integrity": "sha512-Wbi9A0PacMYUOwjAulQP90Wl3mQ6NDwnyrZQzFjDz+UzjXOSyQMgBrTkUBz+pVoYVlX3DUu24gWMZBcit+wOGg==",
+ "dependencies": {
+ "@smithy/types": "^2.6.0",
+ "@smithy/util-buffer-from": "^2.0.0",
+ "@smithy/util-utf8": "^2.0.2",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/invalid-dependency": {
+ "version": "2.0.14",
+ "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.0.14.tgz",
+ "integrity": "sha512-d8ohpwZo9RzTpGlAfsWtfm1SHBSU7+N4iuZ6MzR10xDTujJJWtmXYHK1uzcr7rggbpUTaWyHpPFgnf91q0EFqQ==",
+ "dependencies": {
+ "@smithy/types": "^2.6.0",
+ "tslib": "^2.5.0"
+ }
+ },
+ "node_modules/@smithy/is-array-buffer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.0.0.tgz",
+ "integrity": "sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==",
+ "dependencies": {
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/middleware-content-length": {
+ "version": "2.0.16",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-2.0.16.tgz",
+ "integrity": "sha512-9ddDia3pp1d3XzLXKcm7QebGxLq9iwKf+J1LapvlSOhpF8EM9SjMeSrMOOFgG+2TfW5K3+qz4IAJYYm7INYCng==",
+ "dependencies": {
+ "@smithy/protocol-http": "^3.0.10",
+ "@smithy/types": "^2.6.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/middleware-endpoint": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-2.2.1.tgz",
+ "integrity": "sha512-dVDS7HNJl/wb0lpByXor6whqDbb1YlLoaoWYoelyYzLHioXOE7y/0iDwJWtDcN36/tVCw9EPBFZ3aans84jLpg==",
+ "dependencies": {
+ "@smithy/middleware-serde": "^2.0.14",
+ "@smithy/node-config-provider": "^2.1.6",
+ "@smithy/shared-ini-file-loader": "^2.2.5",
+ "@smithy/types": "^2.6.0",
+ "@smithy/url-parser": "^2.0.14",
+ "@smithy/util-middleware": "^2.0.7",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/middleware-retry": {
+ "version": "2.0.21",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-2.0.21.tgz",
+ "integrity": "sha512-EZS1EXv1k6IJX6hyu/0yNQuPcPaXwG8SWljQHYueyRbOxmqYgoWMWPtfZj0xRRQ4YtLawQSpBgAeiJltq8/MPw==",
+ "dependencies": {
+ "@smithy/node-config-provider": "^2.1.6",
+ "@smithy/protocol-http": "^3.0.10",
+ "@smithy/service-error-classification": "^2.0.7",
+ "@smithy/types": "^2.6.0",
+ "@smithy/util-middleware": "^2.0.7",
+ "@smithy/util-retry": "^2.0.7",
+ "tslib": "^2.5.0",
+ "uuid": "^8.3.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/middleware-serde": {
+ "version": "2.0.14",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.0.14.tgz",
+ "integrity": "sha512-hFi3FqoYWDntCYA2IGY6gJ6FKjq2gye+1tfxF2HnIJB5uW8y2DhpRNBSUMoqP+qvYzRqZ6ntv4kgbG+o3pX57g==",
+ "dependencies": {
+ "@smithy/types": "^2.6.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/middleware-stack": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.0.8.tgz",
+ "integrity": "sha512-7/N59j0zWqVEKExJcA14MrLDZ/IeN+d6nbkN8ucs+eURyaDUXWYlZrQmMOd/TyptcQv0+RDlgag/zSTTV62y/Q==",
+ "dependencies": {
+ "@smithy/types": "^2.6.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/node-config-provider": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.1.6.tgz",
+ "integrity": "sha512-HLqTs6O78m3M3z1cPLFxddxhEPv5MkVatfPuxoVO3A+cHZanNd/H5I6btcdHy6N2CB1MJ/lihJC92h30SESsBA==",
+ "dependencies": {
+ "@smithy/property-provider": "^2.0.15",
+ "@smithy/shared-ini-file-loader": "^2.2.5",
+ "@smithy/types": "^2.6.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/node-http-handler": {
+ "version": "2.1.10",
+ "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.1.10.tgz",
+ "integrity": "sha512-lkALAwtN6odygIM4nB8aHDahINM6WXXjNrZmWQAh0RSossySRT2qa31cFv0ZBuAYVWeprskRk13AFvvLmf1WLw==",
+ "dependencies": {
+ "@smithy/abort-controller": "^2.0.14",
+ "@smithy/protocol-http": "^3.0.10",
+ "@smithy/querystring-builder": "^2.0.14",
+ "@smithy/types": "^2.6.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/property-provider": {
+ "version": "2.0.15",
+ "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.0.15.tgz",
+ "integrity": "sha512-YbRFBn8oiiC3o1Kn3a4KjGa6k47rCM9++5W9cWqYn9WnkyH+hBWgfJAckuxpyA2Hq6Ys4eFrWzXq6fqHEw7iew==",
+ "dependencies": {
+ "@smithy/types": "^2.6.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/protocol-http": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.0.10.tgz",
+ "integrity": "sha512-6+tjNk7rXW7YTeGo9qwxXj/2BFpJTe37kTj3EnZCoX/nH+NP/WLA7O83fz8XhkGqsaAhLUPo/bB12vvd47nsmg==",
+ "dependencies": {
+ "@smithy/types": "^2.6.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/querystring-builder": {
+ "version": "2.0.14",
+ "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.0.14.tgz",
+ "integrity": "sha512-lQ4pm9vTv9nIhl5jt6uVMPludr6syE2FyJmHsIJJuOD7QPIJnrf9HhUGf1iHh9KJ4CUv21tpOU3X6s0rB6uJ0g==",
+ "dependencies": {
+ "@smithy/types": "^2.6.0",
+ "@smithy/util-uri-escape": "^2.0.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/querystring-parser": {
+ "version": "2.0.14",
+ "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-2.0.14.tgz",
+ "integrity": "sha512-+cbtXWI9tNtQjlgQg3CA+pvL3zKTAxPnG3Pj6MP89CR3vi3QMmD0SOWoq84tqZDnJCxlsusbgIXk1ngMReXo+A==",
+ "dependencies": {
+ "@smithy/types": "^2.6.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/service-error-classification": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-2.0.7.tgz",
+ "integrity": "sha512-LLxgW12qGz8doYto15kZ4x1rHjtXl0BnCG6T6Wb8z2DI4PT9cJfOSvzbuLzy7+5I24PAepKgFeWHRd9GYy3Z9w==",
+ "dependencies": {
+ "@smithy/types": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/shared-ini-file-loader": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.2.5.tgz",
+ "integrity": "sha512-LHA68Iu7SmNwfAVe8egmjDCy648/7iJR/fK1UnVw+iAOUJoEYhX2DLgVd5pWllqdDiRbQQzgaHLcRokM+UFR1w==",
+ "dependencies": {
+ "@smithy/types": "^2.6.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/signature-v4": {
+ "version": "2.0.16",
+ "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.0.16.tgz",
+ "integrity": "sha512-ilLY85xS2kZZzTb83diQKYLIYALvart0KnBaKnIRnMBHAGEio5aHSlANQoxVn0VsonwmQ3CnWhnCT0sERD8uTg==",
+ "dependencies": {
+ "@smithy/eventstream-codec": "^2.0.14",
+ "@smithy/is-array-buffer": "^2.0.0",
+ "@smithy/types": "^2.6.0",
+ "@smithy/util-hex-encoding": "^2.0.0",
+ "@smithy/util-middleware": "^2.0.7",
+ "@smithy/util-uri-escape": "^2.0.0",
+ "@smithy/util-utf8": "^2.0.2",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/smithy-client": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.1.16.tgz",
+ "integrity": "sha512-Lw67+yQSpLl4YkDLUzI2KgS8TXclXmbzSeOJUmRFS4ueT56B4pw3RZRF/SRzvgyxM/HxgkUan8oSHXCujPDafQ==",
+ "dependencies": {
+ "@smithy/middleware-stack": "^2.0.8",
+ "@smithy/types": "^2.6.0",
+ "@smithy/util-stream": "^2.0.21",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/types": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.6.0.tgz",
+ "integrity": "sha512-PgqxJq2IcdMF9iAasxcqZqqoOXBHufEfmbEUdN1pmJrJltT42b0Sc8UiYSWWzKkciIp9/mZDpzYi4qYG1qqg6g==",
+ "dependencies": {
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/url-parser": {
+ "version": "2.0.14",
+ "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-2.0.14.tgz",
+ "integrity": "sha512-kbu17Y1AFXi5lNlySdDj7ZzmvupyWKCX/0jNZ8ffquRyGdbDZb+eBh0QnWqsSmnZa/ctyWaTf7n4l/pXLExrnw==",
+ "dependencies": {
+ "@smithy/querystring-parser": "^2.0.14",
+ "@smithy/types": "^2.6.0",
+ "tslib": "^2.5.0"
+ }
+ },
+ "node_modules/@smithy/util-base64": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.0.1.tgz",
+ "integrity": "sha512-DlI6XFYDMsIVN+GH9JtcRp3j02JEVuWIn/QOZisVzpIAprdsxGveFed0bjbMRCqmIFe8uetn5rxzNrBtIGrPIQ==",
+ "dependencies": {
+ "@smithy/util-buffer-from": "^2.0.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/util-body-length-browser": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-2.0.0.tgz",
+ "integrity": "sha512-JdDuS4ircJt+FDnaQj88TzZY3+njZ6O+D3uakS32f2VNnDo3vyEuNdBOh/oFd8Df1zSZOuH1HEChk2AOYDezZg==",
+ "dependencies": {
+ "tslib": "^2.5.0"
+ }
+ },
+ "node_modules/@smithy/util-body-length-node": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-2.1.0.tgz",
+ "integrity": "sha512-/li0/kj/y3fQ3vyzn36NTLGmUwAICb7Jbe/CsWCktW363gh1MOcpEcSO3mJ344Gv2dqz8YJCLQpb6hju/0qOWw==",
+ "dependencies": {
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/util-buffer-from": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.0.0.tgz",
+ "integrity": "sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==",
+ "dependencies": {
+ "@smithy/is-array-buffer": "^2.0.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/util-config-provider": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.0.0.tgz",
+ "integrity": "sha512-xCQ6UapcIWKxXHEU4Mcs2s7LcFQRiU3XEluM2WcCjjBtQkUN71Tb+ydGmJFPxMUrW/GWMgQEEGipLym4XG0jZg==",
+ "dependencies": {
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/util-defaults-mode-browser": {
+ "version": "2.0.20",
+ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.0.20.tgz",
+ "integrity": "sha512-QJtnbTIl0/BbEASkx1MUFf6EaoWqWW1/IM90N++8NNscePvPf77GheYfpoPis6CBQawUWq8QepTP2QUSAdrVkw==",
+ "dependencies": {
+ "@smithy/property-provider": "^2.0.15",
+ "@smithy/smithy-client": "^2.1.16",
+ "@smithy/types": "^2.6.0",
+ "bowser": "^2.11.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@smithy/util-defaults-mode-node": {
+ "version": "2.0.26",
+ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.0.26.tgz",
+ "integrity": "sha512-lGFPOFCHv1ql019oegYqa54BZH7HREw6EBqjDLbAr0wquMX0BDi2sg8TJ6Eq+JGLijkZbJB73m4+aK8OFAapMg==",
+ "dependencies": {
+ "@smithy/config-resolver": "^2.0.19",
+ "@smithy/credential-provider-imds": "^2.1.2",
+ "@smithy/node-config-provider": "^2.1.6",
+ "@smithy/property-provider": "^2.0.15",
+ "@smithy/smithy-client": "^2.1.16",
+ "@smithy/types": "^2.6.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@smithy/util-endpoints": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-1.0.5.tgz",
+ "integrity": "sha512-K7qNuCOD5K/90MjHvHm9kJldrfm40UxWYQxNEShMFxV/lCCCRIg8R4uu1PFAxRvPxNpIdcrh1uK6I1ISjDXZJw==",
+ "dependencies": {
+ "@smithy/node-config-provider": "^2.1.6",
+ "@smithy/types": "^2.6.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@smithy/util-hex-encoding": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.0.0.tgz",
+ "integrity": "sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA==",
+ "dependencies": {
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/util-middleware": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.0.7.tgz",
+ "integrity": "sha512-tRINOTlf1G9B0ECarFQAtTgMhpnrMPSa+5j4ZEwEawCLfTFTavk6757sxhE4RY5RMlD/I3x+DCS8ZUiR8ho9Pw==",
+ "dependencies": {
+ "@smithy/types": "^2.6.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/util-retry": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-2.0.7.tgz",
+ "integrity": "sha512-fIe5yARaF0+xVT1XKcrdnHKTJ1Vc4+3e3tLDjCuIcE9b6fkBzzGFY7AFiX4M+vj6yM98DrwkuZeHf7/hmtVp0Q==",
+ "dependencies": {
+ "@smithy/service-error-classification": "^2.0.7",
+ "@smithy/types": "^2.6.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@smithy/util-stream": {
+ "version": "2.0.21",
+ "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.0.21.tgz",
+ "integrity": "sha512-0BUE16d7n1x7pi1YluXJdB33jOTyBChT0j/BlOkFa9uxfg6YqXieHxjHNuCdJRARa7AZEj32LLLEPJ1fSa4inA==",
+ "dependencies": {
+ "@smithy/fetch-http-handler": "^2.2.7",
+ "@smithy/node-http-handler": "^2.1.10",
+ "@smithy/types": "^2.6.0",
+ "@smithy/util-base64": "^2.0.1",
+ "@smithy/util-buffer-from": "^2.0.0",
+ "@smithy/util-hex-encoding": "^2.0.0",
+ "@smithy/util-utf8": "^2.0.2",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/util-uri-escape": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.0.0.tgz",
+ "integrity": "sha512-ebkxsqinSdEooQduuk9CbKcI+wheijxEb3utGXkCoYQkJnwTnLbH1JXGimJtUkQwNQbsbuYwG2+aFVyZf5TLaw==",
+ "dependencies": {
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/util-utf8": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.0.2.tgz",
+ "integrity": "sha512-qOiVORSPm6Ce4/Yu6hbSgNHABLP2VMv8QOC3tTDNHHlWY19pPyc++fBTbZPtx6egPXi4HQxKDnMxVxpbtX2GoA==",
+ "dependencies": {
+ "@smithy/util-buffer-from": "^2.0.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/util-waiter": {
+ "version": "2.0.14",
+ "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-2.0.14.tgz",
+ "integrity": "sha512-Q6gSz4GUNjNGhrfNg+2Mjy+7K4pEI3r82x1b/+3dSc03MQqobMiUrRVN/YK/4nHVagvBELCoXsiHAFQJNQ5BeA==",
+ "dependencies": {
+ "@smithy/abort-controller": "^2.0.14",
+ "@smithy/types": "^2.6.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
@@ -1215,6 +2372,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/assertion-error": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/available-typed-arrays": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
@@ -1265,6 +2431,11 @@
"integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==",
"dev": true
},
+ "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=="
+ },
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -1416,6 +2587,24 @@
"node": ">= 10"
}
},
+ "node_modules/chai": {
+ "version": "4.3.10",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz",
+ "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==",
+ "dev": true,
+ "dependencies": {
+ "assertion-error": "^1.1.0",
+ "check-error": "^1.0.3",
+ "deep-eql": "^4.1.3",
+ "get-func-name": "^2.0.2",
+ "loupe": "^2.3.6",
+ "pathval": "^1.1.1",
+ "type-detect": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -1432,6 +2621,18 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/check-error": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
+ "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==",
+ "dev": true,
+ "dependencies": {
+ "get-func-name": "^2.0.2"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@@ -2005,6 +3206,18 @@
"node": ">=0.10.0"
}
},
+ "node_modules/deep-eql": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz",
+ "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==",
+ "dev": true,
+ "dependencies": {
+ "type-detect": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
@@ -2738,6 +3951,27 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true
},
+ "node_modules/fast-xml-parser": {
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz",
+ "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==",
+ "funding": [
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/naturalintelligence"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/NaturalIntelligence"
+ }
+ ],
+ "dependencies": {
+ "strnum": "^1.0.5"
+ },
+ "bin": {
+ "fxparser": "src/cli/cli.js"
+ }
+ },
"node_modules/fastq": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
@@ -2951,9 +4185,9 @@
]
},
"node_modules/fs-extra": {
- "version": "11.1.1",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz",
- "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==",
+ "version": "11.2.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
+ "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.2.0",
@@ -3038,6 +4272,15 @@
"node": "6.* || 8.* || >= 10.*"
}
},
+ "node_modules/get-func-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
+ "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/get-intrinsic": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
@@ -4569,6 +5812,15 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
+ "node_modules/loupe": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
+ "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==",
+ "dev": true,
+ "dependencies": {
+ "get-func-name": "^2.0.1"
+ }
+ },
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -4895,6 +6147,14 @@
"integrity": "sha512-xOE9xbICroUDmG1ye2h4bZ8WBie9EGmACaco8K8cx6RlkJJrxGIqjGqztAI+NMhexXBcdGbSEzI6N3EJPevxZw==",
"dev": true
},
+ "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==",
+ "dependencies": {
+ "obliterator": "^1.6.1"
+ }
+ },
"node_modules/mocha": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz",
@@ -7927,6 +9187,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "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=="
+ },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -8139,6 +9404,15 @@
"node": ">=8"
}
},
+ "node_modules/pathval": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
+ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
@@ -9792,6 +11066,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/strnum": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
+ "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA=="
+ },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -10034,6 +11313,11 @@
"strip-bom": "^3.0.0"
}
},
+ "node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -10046,6 +11330,15 @@
"node": ">= 0.8.0"
}
},
+ "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/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
@@ -10217,6 +11510,14 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true
},
+ "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==",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
"node_modules/v8-to-istanbul": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz",
@@ -10567,9 +11868,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "packages/spacecat-shared-dynamo": {
+ "name": "@adobe/spacecat-shared-dynamo",
+ "version": "1.0.0",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/client-dynamodb": "3.454.0",
+ "@aws-sdk/lib-dynamodb": "3.454.0"
+ },
+ "devDependencies": {}
+ },
"packages/spacecat-shared-example": {
"name": "@adobe/spacecat-shared-example",
- "version": "0.0.0-semantic-release",
+ "version": "1.0.0",
"license": "Apache-2.0",
"dependencies": {
"@adobe/fetch": "4.1.1",
@@ -10580,6 +11891,14 @@
"optionalDependencies": {
"@adobe/helix-universal": "4.4.1"
}
+ },
+ "packages/spacecat-shared-utils": {
+ "name": "@adobe/spacecat-shared-utils",
+ "version": "1.0.0",
+ "license": "Apache-2.0",
+ "devDependencies": {
+ "chai": "4.3.10"
+ }
}
}
}
diff --git a/package.json b/package.json
index 47171151f..585e1c46c 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
"type": "module",
"scripts": {
"docs:api": "jsdoc2md -c .jsdoc.json --files packages/*/src/*.js > docs/API.md",
- "docs": "npm run docs:schema && npm run docs:api",
+ "docs": "npm run docs:api",
"test": "npm test -ws",
"lint": "npm run lint -ws",
"semantic-release": "npx --no-install -ws semantic-release -e semantic-release-monorepo",
diff --git a/packages/spacecat-shared-dynamo/.jsdoc.json b/packages/spacecat-shared-dynamo/.jsdoc.json
new file mode 100644
index 000000000..405090f4b
--- /dev/null
+++ b/packages/spacecat-shared-dynamo/.jsdoc.json
@@ -0,0 +1,17 @@
+{
+ "plugins": [],
+ "recurseDepth": 10,
+ "source": {
+ "includePattern": ".+\\.js(doc|x)?$",
+ "excludePattern": "(^|\\/|\\\\)_"
+ },
+ "sourceType": "module",
+ "tags": {
+ "allowUnknownTags": true,
+ "dictionaries": ["jsdoc","closure"]
+ },
+ "templates": {
+ "cleverLinks": false,
+ "monospaceLinks": false
+ }
+}
\ No newline at end of file
diff --git a/packages/spacecat-shared-dynamo/.mocha-multi.json b/packages/spacecat-shared-dynamo/.mocha-multi.json
new file mode 100644
index 000000000..aa2be2a23
--- /dev/null
+++ b/packages/spacecat-shared-dynamo/.mocha-multi.json
@@ -0,0 +1,6 @@
+{
+ "reporterEnabled": "spec,xunit",
+ "xunitReporterOptions": {
+ "output": "junit/test-results.xml"
+ }
+}
diff --git a/packages/spacecat-shared-dynamo/.npmignore b/packages/spacecat-shared-dynamo/.npmignore
new file mode 100644
index 000000000..868317d21
--- /dev/null
+++ b/packages/spacecat-shared-dynamo/.npmignore
@@ -0,0 +1,9 @@
+coverage/
+node_modules/
+junit/
+test/
+docs/
+logs/
+test-results.xml
+renovate.json
+.*
diff --git a/packages/spacecat-shared-dynamo/.nycrc.json b/packages/spacecat-shared-dynamo/.nycrc.json
new file mode 100644
index 000000000..78e7a0b14
--- /dev/null
+++ b/packages/spacecat-shared-dynamo/.nycrc.json
@@ -0,0 +1,10 @@
+{
+ "reporter": [
+ "lcov",
+ "text"
+ ],
+ "check-coverage": true,
+ "lines": 100,
+ "branches": 97,
+ "statements": 100
+}
diff --git a/packages/spacecat-shared-dynamo/CHANGELOG.md b/packages/spacecat-shared-dynamo/CHANGELOG.md
new file mode 100644
index 000000000..93177fb1d
--- /dev/null
+++ b/packages/spacecat-shared-dynamo/CHANGELOG.md
@@ -0,0 +1,18 @@
+# @adobe/spacecat-shared-example-v1.0.0 (2023-11-27)
+
+
+### Bug Fixes
+
+* bogus change to force release ([e77e830](https://github.com/adobe-rnd/spacecat-shared/commit/e77e8305c96ac2504aeb22af44d45cb4acbcae56))
+* package name to "adobe" ([475434e](https://github.com/adobe-rnd/spacecat-shared/commit/475434e6619c2128ba1a8308a4eed23cf3d40691))
+* remove unneeded import (force release) ([49964f9](https://github.com/adobe-rnd/spacecat-shared/commit/49964f99906d5a0fafeb68b460cb19f98a51fc86))
+* repo reference ([881487d](https://github.com/adobe-rnd/spacecat-shared/commit/881487dce4c840ac2bf02a285f17cac511d39c88))
+* roll-back esm monorepo ([ab77518](https://github.com/adobe-rnd/spacecat-shared/commit/ab775187dc79a7f2cdb0ca4fa31198202be78143))
+* set package version to semantic, remove dev deps ([bf62c5f](https://github.com/adobe-rnd/spacecat-shared/commit/bf62c5fe013dbbb7425eb21cffa23e7f01322aee))
+* set to semantic-release versioning ([9209799](https://github.com/adobe-rnd/spacecat-shared/commit/92097995b657207996dd6a35277fdfcd3f3e8fb9))
+* variouss ([ea9d44f](https://github.com/adobe-rnd/spacecat-shared/commit/ea9d44f6071c1a53d2c31515966e7cdf44be7ae9))
+
+
+### Features
+
+* test ([bd3cc5d](https://github.com/adobe-rnd/spacecat-shared/commit/bd3cc5df89ccde75b86b000e8495893f4b099b5b))
diff --git a/packages/spacecat-shared-dynamo/LICENSE.txt b/packages/spacecat-shared-dynamo/LICENSE.txt
new file mode 100644
index 000000000..883ab098f
--- /dev/null
+++ b/packages/spacecat-shared-dynamo/LICENSE.txt
@@ -0,0 +1,264 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+APACHE JACKRABBIT SUBCOMPONENTS
+
+Apache Jackrabbit includes parts with separate copyright notices and license
+terms. Your use of these subcomponents is subject to the terms and conditions
+of the following licenses:
+
+ XPath 2.0/XQuery 1.0 Parser:
+ http://www.w3.org/2002/11/xquery-xpath-applets/xgrammar.zip
+
+ Copyright (C) 2002 World Wide Web Consortium, (Massachusetts Institute of
+ Technology, European Research Consortium for Informatics and Mathematics,
+ Keio University). All Rights Reserved.
+
+ This work is distributed under the W3C(R) Software License in the hope
+ that it will be useful, but WITHOUT ANY WARRANTY; without even the
+ implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ W3C(R) SOFTWARE NOTICE AND LICENSE
+ http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231
+
+ This work (and included software, documentation such as READMEs, or
+ other related items) is being provided by the copyright holders under
+ the following license. By obtaining, using and/or copying this work,
+ you (the licensee) agree that you have read, understood, and will comply
+ with the following terms and conditions.
+
+ Permission to copy, modify, and distribute this software and its
+ documentation, with or without modification, for any purpose and
+ without fee or royalty is hereby granted, provided that you include
+ the following on ALL copies of the software and documentation or
+ portions thereof, including modifications:
+
+ 1. The full text of this NOTICE in a location viewable to users
+ of the redistributed or derivative work.
+
+ 2. Any pre-existing intellectual property disclaimers, notices,
+ or terms and conditions. If none exist, the W3C Software Short
+ Notice should be included (hypertext is preferred, text is
+ permitted) within the body of any redistributed or derivative code.
+
+ 3. Notice of any changes or modifications to the files, including
+ the date changes were made. (We recommend you provide URIs to the
+ location from which the code is derived.)
+
+ THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT
+ HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED,
+ INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS
+ FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR
+ DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS,
+ TRADEMARKS OR OTHER RIGHTS.
+
+ COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL
+ OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR
+ DOCUMENTATION.
+
+ The name and trademarks of copyright holders may NOT be used in
+ advertising or publicity pertaining to the software without specific,
+ written prior permission. Title to copyright in this software and
+ any associated documentation will at all times remain with
+ copyright holders.
diff --git a/packages/spacecat-shared-dynamo/README.md b/packages/spacecat-shared-dynamo/README.md
new file mode 100644
index 000000000..d9047069e
--- /dev/null
+++ b/packages/spacecat-shared-dynamo/README.md
@@ -0,0 +1,15 @@
+# Spacecat Shared - DynamoDB Client
+
+TBD
+
+## Usage
+
+```js
+import { createClient } from '@adobe/spacecat-shared-dynamo';
+
+const dynamoDbClient = createClient();
+
+const result = await dynamoDbClient.query(...);
+...
+
+```
diff --git a/packages/spacecat-shared-dynamo/package.json b/packages/spacecat-shared-dynamo/package.json
new file mode 100644
index 000000000..d2c463c9b
--- /dev/null
+++ b/packages/spacecat-shared-dynamo/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "@adobe/spacecat-shared-dynamo",
+ "version": "1.0.0",
+ "description": "Shared modules of the Spacecat Services - DynamoDB client",
+ "type": "module",
+ "main": "src/indeex.js",
+ "types": "src/index.d.ts",
+ "scripts": {
+ "test": "c8 mocha",
+ "lint": "eslint .",
+ "clean": "rm -rf package-lock.json node_modules"
+ },
+ "mocha": {
+ "reporter": "mocha-multi-reporters",
+ "reporter-options": "configFile=.mocha-multi.json",
+ "spec": "test/*.test.js"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/adobe-rnd/spacecat-shared.git"
+ },
+ "author": "",
+ "license": "Apache-2.0",
+ "bugs": {
+ "url": "https://github.com/adobe-rnd/spacecat-shared/issues"
+ },
+ "homepage": "https://github.com/adobe-rnd/spacecat-shared#readme",
+ "publishConfig": {
+ "access": "public"
+ },
+ "dependencies": {
+ "@aws-sdk/client-dynamodb": "3.454.0",
+ "@aws-sdk/lib-dynamodb": "3.454.0"
+ },
+ "devDependencies": {
+ "chai": "4.3.10"
+ }
+}
diff --git a/packages/spacecat-shared-dynamo/src/index.d.ts b/packages/spacecat-shared-dynamo/src/index.d.ts
new file mode 100644
index 000000000..7581217b3
--- /dev/null
+++ b/packages/spacecat-shared-dynamo/src/index.d.ts
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2021 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+import { DynamoDB } from '@aws-sdk/client-dynamodb';
+import { DynamoDBDocumentClient, QueryCommandInput } from '@aws-sdk/lib-dynamodb';
+
+export declare interface Logger {
+ error(message: string, ...args: any[]): void;
+ // Add other logging methods as needed
+}
+
+export declare interface DynamoDbClient {
+ query(originalParams: QueryCommandInput): Promise;
+ getItem(tableName: string, partitionKey: string, sortKey?: string): Promise;
+ putItem(tableName: string, item: object): Promise;
+}
+
+export function createClient(logger: Logger, dbClient?: DynamoDB, docClient?: DynamoDBDocumentClient): DynamoDbClient;
diff --git a/packages/spacecat-shared-dynamo/src/index.js b/packages/spacecat-shared-dynamo/src/index.js
new file mode 100644
index 000000000..5b7ab47f3
--- /dev/null
+++ b/packages/spacecat-shared-dynamo/src/index.js
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2023 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+import { DynamoDB } from '@aws-sdk/client-dynamodb';
+import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
+
+/**
+ * Creates a client object for interacting with DynamoDB.
+ *
+ * @param {Object} log - The logging object, defaults to console.
+ * @param {DynamoDB} dbClient - The AWS SDK DynamoDB client instance.
+ * @param {DynamoDBDocumentClient} docClient - The AWS SDK DynamoDB Document client instance.
+ * @returns {Object} A client object with methods to interact with DynamoDB.
+ */
+const createClient = (
+ log = console,
+ dbClient = new DynamoDB(),
+ docClient = DynamoDBDocumentClient.from(dbClient),
+) => ({
+ /**
+ * Queries DynamoDB and automatically handles pagination to retrieve all items.
+ *
+ * @param {Object} originalParams - The parameters for the DynamoDB query.
+ * @returns {Promise} A promise that resolves to an array of items retrieved from DynamoDB.
+ * @throws {Error} Throws an error if the DynamoDB query operation fails.
+ */
+ async query(originalParams) {
+ let items = [];
+ const params = { ...originalParams };
+
+ try {
+ let data;
+ do {
+ /*
+ This is one of the scenarios where it's appropriate to disable
+ the ESLint rule for this specific case.
+ In this case, it's necessary because each query depends on the
+ result of the previous one (to get the LastEvaluatedKey).
+ */
+ // eslint-disable-next-line no-await-in-loop
+ data = await docClient.query(params);
+ items = items.concat(data.Items);
+ params.ExclusiveStartKey = data.LastEvaluatedKey;
+ } while (data.LastEvaluatedKey);
+ } catch (error) {
+ log.error('DB Query Error:', error);
+ throw error;
+ }
+ return items;
+ },
+
+ /**
+ * Retrieves an item from DynamoDB using a table name and key.
+ *
+ * @param {string} tableName - The name of the DynamoDB table.
+ * @param {string} partitionKey - The partition key of the item to retrieve.
+ * @param {string} [sortKey] - The sort key of the item to retrieve, if applicable.
+ * @returns {Promise} A promise that resolves to the retrieved item.
+ * @throws {Error} Throws an error if the DynamoDB get operation fails.
+ */
+ async getItem(tableName, partitionKey, sortKey) {
+ const key = sortKey ? { partitionKey, sortKey } : { partitionKey };
+ const params = {
+ TableName: tableName,
+ Key: key,
+ };
+
+ try {
+ const data = await docClient.get(params);
+ return data.Item;
+ } catch (error) {
+ log.error('DB Get Item Error:', error);
+ throw error;
+ }
+ },
+
+ /**
+ * Inserts or updates an item in a DynamoDB table.
+ *
+ * @param {string} tableName - The name of the DynamoDB table.
+ * @param {Object} item - The item to insert or update in the table.
+ * @returns {Promise} A promise that resolves to a message indicating success.
+ * @throws {Error} Throws an error if the DynamoDB put operation fails.
+ */
+ async putItem(tableName, item) {
+ const params = {
+ TableName: tableName,
+ Item: item,
+ };
+
+ try {
+ await docClient.put(params);
+ return { message: 'Item inserted/updated successfully.' };
+ } catch (error) {
+ log.error('DB Put Item Error:', error);
+ throw error;
+ }
+ },
+
+ /**
+ * Removes an item from a DynamoDB table.
+ *
+ * @param {string} tableName - The name of the DynamoDB table.
+ * @param {string} partitionKey - The partition key of the item to remove.
+ * @param {string} [sortKey] - The sort key of the item to remove, if applicable.
+ * @returns {Promise} A promise that resolves to a message indicating successful removal.
+ * @throws {Error} Throws an error if the DynamoDB delete operation fails.
+ */
+ async removeItem(tableName, partitionKey, sortKey) {
+ const key = sortKey ? { partitionKey, sortKey } : { partitionKey };
+ const params = {
+ TableName: tableName,
+ Key: key,
+ };
+
+ try {
+ await docClient.delete(params);
+ return { message: 'Item removed successfully.' };
+ } catch (error) {
+ log.error('DB Remove Item Error:', error);
+ throw error;
+ }
+ },
+});
+
+export { createClient };
diff --git a/packages/spacecat-shared-dynamo/test/index.test.js b/packages/spacecat-shared-dynamo/test/index.test.js
new file mode 100644
index 000000000..7a4fc7a73
--- /dev/null
+++ b/packages/spacecat-shared-dynamo/test/index.test.js
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2023 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+/* eslint-env mocha */
+/* eslint-disable no-unused-expressions */
+
+import { expect } from 'chai';
+import { createClient } from '../src/index.js';
+
+describe('DynamoDB Client', () => {
+ let dynamoDbClient;
+ let mockDocClient;
+
+ beforeEach(() => {
+ // Setup your mock DocumentClient
+ mockDocClient = {
+ query: async (params) => {
+ // Check if LastEvaluatedKey is provided and simulate pagination
+ if (params.ExclusiveStartKey === 'key2') {
+ return { Items: ['item3'], LastEvaluatedKey: undefined };
+ } else {
+ return { Items: ['item1', 'item2'], LastEvaluatedKey: 'key2' };
+ }
+ },
+ get: async () => ({ Item: {} }),
+ put: async () => ({}),
+ delete: async () => ({}),
+ };
+
+ dynamoDbClient = createClient(console, undefined, mockDocClient);
+ });
+
+ it('queries items from the database', async () => {
+ const result = await dynamoDbClient.query({ TableName: 'TestTable' });
+ expect(result).to.be.an('array');
+ });
+
+ it('queries items from the database with pagination', async () => {
+ const result = await dynamoDbClient.query({ TableName: 'TestTable' });
+ expect(result).to.have.lengthOf(3);
+ expect(result).to.deep.equal(['item1', 'item2', 'item3']);
+ });
+
+ it('gets an item from the database', async () => {
+ const result = await dynamoDbClient.getItem('TestTable', 'testPartitionKey');
+ expect(result).to.be.an('object');
+ });
+
+ it('gets an item from the database with sort key', async () => {
+ const result = await dynamoDbClient.getItem('TestTable', 'testPartitionKey', 'testSortKey');
+ expect(result).to.be.an('object');
+ });
+
+ it('puts an item into the database', async () => {
+ const result = await dynamoDbClient.putItem('TestTable', { someKey: 'someValue' });
+ expect(result).to.deep.equal({ message: 'Item inserted/updated successfully.' });
+ });
+
+ it('removes an item from the database', async () => {
+ const result = await dynamoDbClient.removeItem('TestTable', 'testPartitionKey');
+ expect(result).to.deep.equal({ message: 'Item removed successfully.' });
+ });
+
+ it('removes an item from the database with sort key', async () => {
+ const result = await dynamoDbClient.removeItem('TestTable', 'testPartitionKey', 'testSortKey');
+ expect(result).to.deep.equal({ message: 'Item removed successfully.' });
+ });
+
+ it('handles errors in query', async () => {
+ mockDocClient.query = async () => {
+ throw new Error('Query failed');
+ };
+
+ try {
+ await dynamoDbClient.query({ TableName: 'TestTable' });
+ expect.fail('queryDb did not throw as expected');
+ } catch (error) {
+ expect(error.message).to.equal('Query failed');
+ }
+ });
+
+ it('handles errors in getItem', async () => {
+ mockDocClient.get = async () => {
+ throw new Error('Get failed');
+ };
+
+ try {
+ await dynamoDbClient.getItem('TestTable', 'testPartitionKey');
+ expect.fail('getItem did not throw as expected');
+ } catch (error) {
+ expect(error.message).to.equal('Get failed');
+ }
+ });
+
+ it('handles errors in putItem', async () => {
+ mockDocClient.put = async () => {
+ throw new Error('Put failed');
+ };
+
+ try {
+ await dynamoDbClient.putItem('TestTable', { someKey: 'someValue' });
+ expect.fail('putItem did not throw as expected');
+ } catch (error) {
+ expect(error.message).to.equal('Put failed');
+ }
+ });
+
+ it('handles errors in removeItem', async () => {
+ mockDocClient.delete = async () => {
+ throw new Error('Remove failed');
+ };
+
+ try {
+ await dynamoDbClient.removeItem('TestTable', 'testPartitionKey');
+ expect.fail('removeItem did not throw as expected');
+ } catch (error) {
+ expect(error.message).to.equal('Remove failed');
+ }
+ });
+});
diff --git a/packages/spacecat-shared-utils/.jsdoc.json b/packages/spacecat-shared-utils/.jsdoc.json
new file mode 100644
index 000000000..405090f4b
--- /dev/null
+++ b/packages/spacecat-shared-utils/.jsdoc.json
@@ -0,0 +1,17 @@
+{
+ "plugins": [],
+ "recurseDepth": 10,
+ "source": {
+ "includePattern": ".+\\.js(doc|x)?$",
+ "excludePattern": "(^|\\/|\\\\)_"
+ },
+ "sourceType": "module",
+ "tags": {
+ "allowUnknownTags": true,
+ "dictionaries": ["jsdoc","closure"]
+ },
+ "templates": {
+ "cleverLinks": false,
+ "monospaceLinks": false
+ }
+}
\ No newline at end of file
diff --git a/packages/spacecat-shared-utils/.mocha-multi.json b/packages/spacecat-shared-utils/.mocha-multi.json
new file mode 100644
index 000000000..aa2be2a23
--- /dev/null
+++ b/packages/spacecat-shared-utils/.mocha-multi.json
@@ -0,0 +1,6 @@
+{
+ "reporterEnabled": "spec,xunit",
+ "xunitReporterOptions": {
+ "output": "junit/test-results.xml"
+ }
+}
diff --git a/packages/spacecat-shared-utils/.npmignore b/packages/spacecat-shared-utils/.npmignore
new file mode 100644
index 000000000..868317d21
--- /dev/null
+++ b/packages/spacecat-shared-utils/.npmignore
@@ -0,0 +1,9 @@
+coverage/
+node_modules/
+junit/
+test/
+docs/
+logs/
+test-results.xml
+renovate.json
+.*
diff --git a/packages/spacecat-shared-utils/.nycrc.json b/packages/spacecat-shared-utils/.nycrc.json
new file mode 100644
index 000000000..78e7a0b14
--- /dev/null
+++ b/packages/spacecat-shared-utils/.nycrc.json
@@ -0,0 +1,10 @@
+{
+ "reporter": [
+ "lcov",
+ "text"
+ ],
+ "check-coverage": true,
+ "lines": 100,
+ "branches": 97,
+ "statements": 100
+}
diff --git a/packages/spacecat-shared-utils/CHANGELOG.md b/packages/spacecat-shared-utils/CHANGELOG.md
new file mode 100644
index 000000000..93177fb1d
--- /dev/null
+++ b/packages/spacecat-shared-utils/CHANGELOG.md
@@ -0,0 +1,18 @@
+# @adobe/spacecat-shared-example-v1.0.0 (2023-11-27)
+
+
+### Bug Fixes
+
+* bogus change to force release ([e77e830](https://github.com/adobe-rnd/spacecat-shared/commit/e77e8305c96ac2504aeb22af44d45cb4acbcae56))
+* package name to "adobe" ([475434e](https://github.com/adobe-rnd/spacecat-shared/commit/475434e6619c2128ba1a8308a4eed23cf3d40691))
+* remove unneeded import (force release) ([49964f9](https://github.com/adobe-rnd/spacecat-shared/commit/49964f99906d5a0fafeb68b460cb19f98a51fc86))
+* repo reference ([881487d](https://github.com/adobe-rnd/spacecat-shared/commit/881487dce4c840ac2bf02a285f17cac511d39c88))
+* roll-back esm monorepo ([ab77518](https://github.com/adobe-rnd/spacecat-shared/commit/ab775187dc79a7f2cdb0ca4fa31198202be78143))
+* set package version to semantic, remove dev deps ([bf62c5f](https://github.com/adobe-rnd/spacecat-shared/commit/bf62c5fe013dbbb7425eb21cffa23e7f01322aee))
+* set to semantic-release versioning ([9209799](https://github.com/adobe-rnd/spacecat-shared/commit/92097995b657207996dd6a35277fdfcd3f3e8fb9))
+* variouss ([ea9d44f](https://github.com/adobe-rnd/spacecat-shared/commit/ea9d44f6071c1a53d2c31515966e7cdf44be7ae9))
+
+
+### Features
+
+* test ([bd3cc5d](https://github.com/adobe-rnd/spacecat-shared/commit/bd3cc5df89ccde75b86b000e8495893f4b099b5b))
diff --git a/packages/spacecat-shared-utils/LICENSE.txt b/packages/spacecat-shared-utils/LICENSE.txt
new file mode 100644
index 000000000..883ab098f
--- /dev/null
+++ b/packages/spacecat-shared-utils/LICENSE.txt
@@ -0,0 +1,264 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+APACHE JACKRABBIT SUBCOMPONENTS
+
+Apache Jackrabbit includes parts with separate copyright notices and license
+terms. Your use of these subcomponents is subject to the terms and conditions
+of the following licenses:
+
+ XPath 2.0/XQuery 1.0 Parser:
+ http://www.w3.org/2002/11/xquery-xpath-applets/xgrammar.zip
+
+ Copyright (C) 2002 World Wide Web Consortium, (Massachusetts Institute of
+ Technology, European Research Consortium for Informatics and Mathematics,
+ Keio University). All Rights Reserved.
+
+ This work is distributed under the W3C(R) Software License in the hope
+ that it will be useful, but WITHOUT ANY WARRANTY; without even the
+ implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ W3C(R) SOFTWARE NOTICE AND LICENSE
+ http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231
+
+ This work (and included software, documentation such as READMEs, or
+ other related items) is being provided by the copyright holders under
+ the following license. By obtaining, using and/or copying this work,
+ you (the licensee) agree that you have read, understood, and will comply
+ with the following terms and conditions.
+
+ Permission to copy, modify, and distribute this software and its
+ documentation, with or without modification, for any purpose and
+ without fee or royalty is hereby granted, provided that you include
+ the following on ALL copies of the software and documentation or
+ portions thereof, including modifications:
+
+ 1. The full text of this NOTICE in a location viewable to users
+ of the redistributed or derivative work.
+
+ 2. Any pre-existing intellectual property disclaimers, notices,
+ or terms and conditions. If none exist, the W3C Software Short
+ Notice should be included (hypertext is preferred, text is
+ permitted) within the body of any redistributed or derivative code.
+
+ 3. Notice of any changes or modifications to the files, including
+ the date changes were made. (We recommend you provide URIs to the
+ location from which the code is derived.)
+
+ THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT
+ HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED,
+ INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS
+ FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR
+ DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS,
+ TRADEMARKS OR OTHER RIGHTS.
+
+ COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL
+ OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR
+ DOCUMENTATION.
+
+ The name and trademarks of copyright holders may NOT be used in
+ advertising or publicity pertaining to the software without specific,
+ written prior permission. Title to copyright in this software and
+ any associated documentation will at all times remain with
+ copyright holders.
diff --git a/packages/spacecat-shared-utils/README.md b/packages/spacecat-shared-utils/README.md
new file mode 100644
index 000000000..219cd85ec
--- /dev/null
+++ b/packages/spacecat-shared-utils/README.md
@@ -0,0 +1,3 @@
+# Spacecat Shared - Utils
+
+A collection of utility functions.
diff --git a/packages/spacecat-shared-utils/package.json b/packages/spacecat-shared-utils/package.json
new file mode 100644
index 000000000..1dc38699d
--- /dev/null
+++ b/packages/spacecat-shared-utils/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "@adobe/spacecat-shared-utils",
+ "version": "1.0.0",
+ "description": "Shared modules of the Spacecat Services - utils",
+ "type": "module",
+ "main": "src/index.js",
+ "types": "src/index.d.ts",
+ "scripts": {
+ "test": "c8 mocha",
+ "lint": "eslint .",
+ "clean": "rm -rf package-lock.json node_modules"
+ },
+ "mocha": {
+ "reporter": "mocha-multi-reporters",
+ "reporter-options": "configFile=.mocha-multi.json",
+ "spec": "test/*.test.js"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/adobe-rnd/spacecat-shared.git"
+ },
+ "author": "",
+ "license": "Apache-2.0",
+ "bugs": {
+ "url": "https://github.com/adobe-rnd/spacecat-shared/issues"
+ },
+ "homepage": "https://github.com/adobe-rnd/spacecat-shared#readme",
+ "publishConfig": {
+ "access": "public"
+ },
+ "devDependencies": {
+ "chai": "4.3.10"
+ }
+}
diff --git a/packages/spacecat-shared-utils/src/functions.js b/packages/spacecat-shared-utils/src/functions.js
new file mode 100644
index 000000000..1f61373b1
--- /dev/null
+++ b/packages/spacecat-shared-utils/src/functions.js
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2023 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+/**
+ * Determines if the given value is a boolean or a string representation of a boolean.
+ *
+ * @param {*} value - The value to check.
+ * @returns {boolean} True if the value is a boolean or a string representation of a boolean.
+ */
+function isBoolean(value) {
+ return typeof value === 'boolean' || value === 'true' || value === 'false';
+}
+
+/**
+ * Checks if the given value is an integer.
+ *
+ * @param {*} value - The value to check.
+ * @returns {boolean} True if the value is an integer, false otherwise.
+ */
+function isInteger(value) {
+ return typeof value === 'number' && Number.isInteger(value);
+}
+
+/**
+ * Determines if the given value is a number.
+ *
+ * @param {*} value - The value to check.
+ * @returns {boolean} True if the value is a finite number, false otherwise.
+ */
+function isNumber(value) {
+ return typeof value === 'number' && Number.isFinite(value);
+}
+
+/**
+ * Checks if the given parameter is an object and not an array or null.
+ *
+ * @param {*} obj - The object to check.
+ * @returns {boolean} True if the parameter is an object, false otherwise.
+ */
+function isObject(obj) {
+ return !Array.isArray(obj) && obj !== null && typeof obj === 'object';
+}
+
+/**
+ * Determines if the given parameter is a string.
+ *
+ * @param {*} str - The string to check.
+ * @returns {boolean} True if the parameter is a string, false otherwise.
+ */
+function isString(str) {
+ return (!!str || str === '') && typeof str === 'string';
+}
+
+/**
+ * Checks if the given string is not empty.
+ *
+ * @param {*} str - The string to check.
+ * @returns {boolean} True if the string is not empty, false otherwise.
+ */
+function hasText(str) {
+ return !!str && typeof str === 'string';
+}
+
+/**
+ * Checks whether the given object is a valid JavaScript Date.
+ *
+ * @param {*} obj - The object to check.
+ * @returns {boolean} True if the given object is a valid Date object, false otherwise.
+ */
+function isValidDate(obj) {
+ return obj instanceof Date && !Number.isNaN(obj.getTime());
+}
+
+/**
+ * Validates whether the given string is a JavaScript ISO date string in
+ * Zulu (UTC) timezone. Used for persisting system dates, which must be
+ * independent of any user timezone.
+ *
+ * @param {string} str - The string to validate.
+ * @returns {boolean} True if the given string validates successfully.
+ */
+function isIsoDate(str) {
+ return /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/.test(str)
+ && new Date(str).toISOString() === str;
+}
+
+/**
+ * Validates whether the given string is a JavaScript ISO date string
+ * following UTC time offsets format.
+ *
+ * @param {string} str - The string to validate.
+ * @returns {boolean} True if the given string validates successfully.
+ */
+function isIsoTimeOffsetsDate(str) {
+ return /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}(Z|[+-]\d{2}:\d{2})/.test(str);
+}
+
+/**
+ * Validates whether the given string is a valid URL with http or https protocol.
+ *
+ * @param {string} urlString - The string to validate.
+ * @returns {boolean} True if the given string validates successfully.
+ */
+function isValidUrl(urlString) {
+ try {
+ const url = new URL(urlString);
+ return url.protocol === 'http:' || url.protocol === 'https:';
+ } catch (e) {
+ return false;
+ }
+}
+
+/**
+ * Converts a given value to a boolean. Throws an error if the value is not a boolean.
+ *
+ * @param {*} value - The value to convert.
+ * @returns {boolean} The converted boolean value.
+ * @throws {Error} If the value is not a boolean or a boolean-like string.
+ */
+function toBoolean(value) {
+ if (!isBoolean(value)) {
+ throw new Error('Not a boolean value');
+ }
+ return JSON.parse(value);
+}
+
+/**
+ * Compares two arrays for equality.
+ *
+ * @param {Array} a - The first array to compare.
+ * @param {Array} b - The second array to compare.
+ * @returns {boolean} True if the arrays are equal, false otherwise.
+ */
+const arrayEquals = (a, b) => Array.isArray(a)
+ && Array.isArray(b)
+ && a.length === b.length
+ && a.every((val, index) => val === b[index]);
+
+export {
+ arrayEquals,
+ hasText,
+ isBoolean,
+ isInteger,
+ isValidDate,
+ isIsoDate,
+ isIsoTimeOffsetsDate,
+ isNumber,
+ isObject,
+ isString,
+ toBoolean,
+ isValidUrl,
+};
diff --git a/packages/spacecat-shared-utils/test/functions.test.js b/packages/spacecat-shared-utils/test/functions.test.js
new file mode 100644
index 000000000..d3b8fe63b
--- /dev/null
+++ b/packages/spacecat-shared-utils/test/functions.test.js
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2023 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+/* eslint-env mocha */
+/* eslint-disable no-unused-expressions */
+
+import { expect } from 'chai';
+
+import {
+ hasText,
+ isBoolean,
+ isInteger,
+ isValidDate,
+ isIsoDate,
+ isIsoTimeOffsetsDate,
+ isNumber,
+ isObject,
+ isString,
+ toBoolean,
+ arrayEquals,
+ isValidUrl,
+} from '../src/functions.js';
+
+describe('Shared functions', () => {
+ describe('Commons', () => {
+ it('is iso date', () => {
+ expect(isIsoDate('')).to.be.false;
+ expect(isIsoDate('2011-10-05')).to.be.false;
+ expect(isIsoDate('2011-10-05T14:48:00.000')).to.be.false;
+ expect(isIsoDate('Wed Oct 05 2011 16:48:00 GMT+0200 (CEST)')).to.be.false;
+
+ expect(isIsoDate('2011-10-05T14:48:00.000Z')).to.be.true;
+ });
+
+ it('is iso date with time offset', () => {
+ expect(isIsoTimeOffsetsDate('')).to.be.false;
+ expect(isIsoTimeOffsetsDate('2019-11-15')).to.be.false;
+ expect(isIsoTimeOffsetsDate('2019-11-05T14:43:00.000')).to.be.false;
+ expect(isIsoTimeOffsetsDate('Wed Oct 11 2019 14:43:00 GMT+0200 (CEST)')).to.be.false;
+
+ expect(isIsoTimeOffsetsDate('2019-11-11T14:43:00.000Z')).to.be.true;
+ expect(isIsoTimeOffsetsDate('2019-11-11T14:43:00.000-00:00')).to.be.true;
+ expect(isIsoTimeOffsetsDate('2019-11-11T14:43:00.000+05:11')).to.be.true;
+ });
+
+ it('has text', () => {
+ expect(hasText()).to.be.false;
+ expect(hasText(null)).to.be.false;
+ expect(hasText(undefined)).to.be.false;
+ expect(hasText(123)).to.be.false;
+ expect(hasText({})).to.be.false;
+ expect(hasText([])).to.be.false;
+ expect(hasText(['asd'])).to.be.false;
+ expect(hasText({ asd: 'dsa' })).to.be.false;
+ expect(hasText('')).to.be.false;
+
+ expect(hasText('a')).to.be.true;
+ expect(hasText('1')).to.be.true;
+ expect(hasText('a12dsamklda')).to.be.true;
+ });
+
+ it('is boolean', () => {
+ expect(isBoolean()).to.be.false;
+ expect(isBoolean(null)).to.be.false;
+ expect(isBoolean(undefined)).to.be.false;
+ expect(isBoolean([])).to.be.false;
+ expect(isBoolean('foo')).to.be.false;
+ expect(isBoolean({})).to.be.false;
+ expect(isBoolean(NaN)).to.be.false;
+ expect(isBoolean(Infinity)).to.be.false;
+ expect(isBoolean(-Infinity)).to.be.false;
+ expect(isBoolean(-Infinity)).to.be.false;
+ expect(isBoolean(123)).to.be.false;
+
+ expect(isBoolean('true')).to.be.true;
+ expect(isBoolean('false')).to.be.true;
+ expect(isBoolean(true)).to.be.true;
+ expect(isBoolean(false)).to.be.true;
+ });
+
+ it('is number', () => {
+ expect(isNumber()).to.be.false;
+ expect(isNumber(null)).to.be.false;
+ expect(isNumber(undefined)).to.be.false;
+ expect(isNumber([])).to.be.false;
+ expect(isNumber(['dasd'])).to.be.false;
+ expect(isNumber({})).to.be.false;
+ expect(isNumber({ asd: 'dsa' })).to.be.false;
+ expect(isNumber('')).to.be.false;
+ expect(isNumber('dasd')).to.be.false;
+ expect(isNumber(NaN)).to.be.false;
+ expect(isNumber(Infinity)).to.be.false;
+ expect(isNumber(-Infinity)).to.be.false;
+
+ expect(isNumber(0)).to.be.true;
+ expect(isNumber(123)).to.be.true;
+ expect(isNumber(-123)).to.be.true;
+ expect(isNumber(12.3)).to.be.true;
+ });
+
+ it('is integer', () => {
+ expect(isInteger()).to.be.false;
+ expect(isInteger(null)).to.be.false;
+ expect(isInteger(undefined)).to.be.false;
+ expect(isInteger([])).to.be.false;
+ expect(isInteger(['dasd'])).to.be.false;
+ expect(isInteger({})).to.be.false;
+ expect(isInteger({ asd: 'dsa' })).to.be.false;
+ expect(isInteger('')).to.be.false;
+ expect(isInteger('dasd')).to.be.false;
+ expect(isInteger(NaN)).to.be.false;
+ expect(isInteger(Infinity)).to.be.false;
+ expect(isInteger(-Infinity)).to.be.false;
+ expect(isInteger(12.3)).to.be.false;
+
+ expect(isInteger(0)).to.be.true;
+ expect(isInteger(123)).to.be.true;
+ expect(isInteger(-123)).to.be.true;
+ });
+
+ it('is object', () => {
+ expect(isObject()).to.be.false;
+ expect(isObject(null)).to.be.false;
+ expect(isObject(undefined)).to.be.false;
+ expect(isObject(123)).to.be.false;
+ expect(isObject('dasd')).to.be.false;
+ expect(isObject([])).to.be.false;
+ expect(isObject(['dasd'])).to.be.false;
+
+ expect(isObject({})).to.be.true;
+ expect(isObject({ asd: 'dsa' })).to.be.true;
+ });
+
+ it('is string', () => {
+ expect(isString()).to.be.false;
+ expect(isString(null)).to.be.false;
+ expect(isString(undefined)).to.be.false;
+ expect(isString(123)).to.be.false;
+ expect(isString([])).to.be.false;
+ expect(isString(['dasd'])).to.be.false;
+ expect(isString({})).to.be.false;
+ expect(isString({ asd: 'dsa' })).to.be.false;
+
+ expect(isString('')).to.be.true;
+ expect(isString('dasd')).to.be.true;
+ });
+
+ it('toBoolean', () => {
+ expect(() => toBoolean()).to.throw('Not a boolean value');
+ expect(() => toBoolean(null)).to.throw('Not a boolean value');
+ expect(() => toBoolean(undefined)).to.throw('Not a boolean value');
+ expect(() => toBoolean([])).to.throw('Not a boolean value');
+ expect(() => toBoolean('foo')).to.throw('Not a boolean value');
+ expect(() => toBoolean({})).to.throw('Not a boolean value');
+ expect(() => toBoolean(NaN)).to.throw('Not a boolean value');
+ expect(() => toBoolean(Infinity)).to.throw('Not a boolean value');
+ expect(() => toBoolean(-Infinity)).to.throw('Not a boolean value');
+ expect(() => toBoolean(-Infinity)).to.throw('Not a boolean value');
+ expect(() => toBoolean(123)).to.throw('Not a boolean value');
+
+ expect(toBoolean('true')).to.be.true;
+ expect(toBoolean('false')).to.be.false;
+ expect(toBoolean(true)).to.be.true;
+ expect(toBoolean(false)).to.be.false;
+ });
+
+ it('array equals', () => {
+ expect(arrayEquals([], 1)).to.be.false;
+ expect(arrayEquals(1, [])).to.be.false;
+ expect(arrayEquals([1], [2, 3])).to.be.false;
+ expect(arrayEquals([1, 4], [2, 3])).to.be.false;
+ expect(arrayEquals([1, 2], [1, 2])).to.be.true;
+ });
+ });
+
+ describe('isValidUrl', () => {
+ it('returns false for invalid Url', async () => {
+ expect(isValidUrl(null)).to.be.false;
+ expect(isValidUrl(undefined)).to.be.false;
+ expect(isValidUrl('dummy')).to.be.false;
+ expect(isValidUrl(1234)).to.be.false;
+ expect(isValidUrl(true)).to.be.false;
+ expect(isValidUrl('example.com')).to.be.false;
+ expect(isValidUrl('www.example.com')).to.be.false;
+ expect(isValidUrl('255.255.255.256')).to.be.false;
+ expect(isValidUrl('ftp://abc.com')).to.be.false;
+ });
+
+ it('returns true for valid url', async () => {
+ expect(isValidUrl('http://abc.xyz')).to.be.true;
+ expect(isValidUrl('https://abc.xyz')).to.be.true;
+ });
+ });
+
+ describe('isValidDate', () => {
+ it('returns false for invalid date', async () => {
+ expect(isValidDate(null)).to.be.false;
+ expect(isValidDate(undefined)).to.be.false;
+ expect(isValidDate({})).to.be.false;
+ expect(isValidDate([])).to.be.false;
+ expect(isValidDate(1234)).to.be.false;
+ expect(isValidDate('1234')).to.be.false;
+ expect(isValidDate(new Date('2019-11-11T14:43:89.000-00:00'))).to.be.false;
+ expect(isValidDate(new Date('invalid date'))).to.be.false;
+ });
+
+ it('returns true for valid date', async () => {
+ expect(isValidDate(new Date())).to.be.true;
+ expect(isValidDate(new Date('2022-01-01T01:23:45.678-00:00'))).to.be.true;
+ expect(isValidDate(new Date('2022-01-01T01:23:45.678Z'))).to.be.true;
+ });
+ });
+});
From 4bf194d6570b2adda0f66076481bd8e3e7ba0cde Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dominique=20J=C3=A4ggi?=
Date: Tue, 28 Nov 2023 08:52:13 +0100
Subject: [PATCH 02/10] chore: update readme
---
README.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/README.md b/README.md
index 55efa7e0d..866b818ab 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,6 @@
# spacecat-shared
Shared modules for Spacecat Services
+
+## Modules
+- `spacecat-shared-dynamodb` - DynamoDB client
+- `spacecat-shared-utils` - Utility functions
From d34aa1a8cb8fa2fcdb4d589f1e2af420de8034b5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dominique=20J=C3=A4ggi?=
Date: Tue, 28 Nov 2023 09:26:52 +0100
Subject: [PATCH 03/10] chore: update readme
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 866b818ab..aa162c225 100644
--- a/README.md
+++ b/README.md
@@ -2,5 +2,5 @@
Shared modules for Spacecat Services
## Modules
-- `spacecat-shared-dynamodb` - DynamoDB client
+- `spacecat-shared-dynamodb` - DynamoDB client for basic access
- `spacecat-shared-utils` - Utility functions
From 532b464ae308cfd52bd53c279ddd2d586dacd91c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dominique=20J=C3=A4ggi?=
Date: Tue, 28 Nov 2023 10:36:24 +0100
Subject: [PATCH 04/10] feat: modularize and add basic input validation
---
.../spacecat-shared-dynamo/src/index.d.ts | 12 +-
packages/spacecat-shared-dynamo/src/index.js | 113 ++----------------
.../src/modules/getItem.js | 46 +++++++
.../src/modules/putItem.js | 42 +++++++
.../src/modules/query.js | 41 +++++++
.../src/modules/removeItem.js | 46 +++++++
.../spacecat-shared-dynamo/test/index.test.js | 64 ++++++++--
7 files changed, 250 insertions(+), 114 deletions(-)
create mode 100644 packages/spacecat-shared-dynamo/src/modules/getItem.js
create mode 100644 packages/spacecat-shared-dynamo/src/modules/putItem.js
create mode 100644 packages/spacecat-shared-dynamo/src/modules/query.js
create mode 100644 packages/spacecat-shared-dynamo/src/modules/removeItem.js
diff --git a/packages/spacecat-shared-dynamo/src/index.d.ts b/packages/spacecat-shared-dynamo/src/index.d.ts
index 7581217b3..78f759fe3 100644
--- a/packages/spacecat-shared-dynamo/src/index.d.ts
+++ b/packages/spacecat-shared-dynamo/src/index.d.ts
@@ -15,13 +15,19 @@ import { DynamoDBDocumentClient, QueryCommandInput } from '@aws-sdk/lib-dynamodb
export declare interface Logger {
error(message: string, ...args: any[]): void;
- // Add other logging methods as needed
+ info(message: string, ...args: any[]): void;
+}
+
+export declare interface DynamoDbKey {
+ partitionKey: string;
+ sortKey?: string;
}
export declare interface DynamoDbClient {
query(originalParams: QueryCommandInput): Promise;
- getItem(tableName: string, partitionKey: string, sortKey?: string): Promise;
- putItem(tableName: string, item: object): Promise;
+ getItem(tableName: string, key: DynamoDbKey): Promise;
+ putItem(tableName: string, item: object): Promise<{ message: string }>;
+ removeItem(tableName: string, key: DynamoDbKey): Promise<{ message: string }>;
}
export function createClient(logger: Logger, dbClient?: DynamoDB, docClient?: DynamoDBDocumentClient): DynamoDbClient;
diff --git a/packages/spacecat-shared-dynamo/src/index.js b/packages/spacecat-shared-dynamo/src/index.js
index 5b7ab47f3..6b09ed72e 100644
--- a/packages/spacecat-shared-dynamo/src/index.js
+++ b/packages/spacecat-shared-dynamo/src/index.js
@@ -13,6 +13,11 @@
import { DynamoDB } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
+import query from './modules/query.js';
+import getItem from './modules/getItem.js';
+import putItem from './modules/putItem.js';
+import removeItem from './modules/removeItem.js';
+
/**
* Creates a client object for interacting with DynamoDB.
*
@@ -26,110 +31,10 @@ const createClient = (
dbClient = new DynamoDB(),
docClient = DynamoDBDocumentClient.from(dbClient),
) => ({
- /**
- * Queries DynamoDB and automatically handles pagination to retrieve all items.
- *
- * @param {Object} originalParams - The parameters for the DynamoDB query.
- * @returns {Promise} A promise that resolves to an array of items retrieved from DynamoDB.
- * @throws {Error} Throws an error if the DynamoDB query operation fails.
- */
- async query(originalParams) {
- let items = [];
- const params = { ...originalParams };
-
- try {
- let data;
- do {
- /*
- This is one of the scenarios where it's appropriate to disable
- the ESLint rule for this specific case.
- In this case, it's necessary because each query depends on the
- result of the previous one (to get the LastEvaluatedKey).
- */
- // eslint-disable-next-line no-await-in-loop
- data = await docClient.query(params);
- items = items.concat(data.Items);
- params.ExclusiveStartKey = data.LastEvaluatedKey;
- } while (data.LastEvaluatedKey);
- } catch (error) {
- log.error('DB Query Error:', error);
- throw error;
- }
- return items;
- },
-
- /**
- * Retrieves an item from DynamoDB using a table name and key.
- *
- * @param {string} tableName - The name of the DynamoDB table.
- * @param {string} partitionKey - The partition key of the item to retrieve.
- * @param {string} [sortKey] - The sort key of the item to retrieve, if applicable.
- * @returns {Promise} A promise that resolves to the retrieved item.
- * @throws {Error} Throws an error if the DynamoDB get operation fails.
- */
- async getItem(tableName, partitionKey, sortKey) {
- const key = sortKey ? { partitionKey, sortKey } : { partitionKey };
- const params = {
- TableName: tableName,
- Key: key,
- };
-
- try {
- const data = await docClient.get(params);
- return data.Item;
- } catch (error) {
- log.error('DB Get Item Error:', error);
- throw error;
- }
- },
-
- /**
- * Inserts or updates an item in a DynamoDB table.
- *
- * @param {string} tableName - The name of the DynamoDB table.
- * @param {Object} item - The item to insert or update in the table.
- * @returns {Promise} A promise that resolves to a message indicating success.
- * @throws {Error} Throws an error if the DynamoDB put operation fails.
- */
- async putItem(tableName, item) {
- const params = {
- TableName: tableName,
- Item: item,
- };
-
- try {
- await docClient.put(params);
- return { message: 'Item inserted/updated successfully.' };
- } catch (error) {
- log.error('DB Put Item Error:', error);
- throw error;
- }
- },
-
- /**
- * Removes an item from a DynamoDB table.
- *
- * @param {string} tableName - The name of the DynamoDB table.
- * @param {string} partitionKey - The partition key of the item to remove.
- * @param {string} [sortKey] - The sort key of the item to remove, if applicable.
- * @returns {Promise} A promise that resolves to a message indicating successful removal.
- * @throws {Error} Throws an error if the DynamoDB delete operation fails.
- */
- async removeItem(tableName, partitionKey, sortKey) {
- const key = sortKey ? { partitionKey, sortKey } : { partitionKey };
- const params = {
- TableName: tableName,
- Key: key,
- };
-
- try {
- await docClient.delete(params);
- return { message: 'Item removed successfully.' };
- } catch (error) {
- log.error('DB Remove Item Error:', error);
- throw error;
- }
- },
+ query: (params) => query(docClient, params, log),
+ getItem: (tableName, key) => getItem(docClient, tableName, key, log),
+ putItem: (tableName, item) => putItem(docClient, tableName, item, log),
+ removeItem: (tableName, key) => removeItem(docClient, tableName, key, log),
});
export { createClient };
diff --git a/packages/spacecat-shared-dynamo/src/modules/getItem.js b/packages/spacecat-shared-dynamo/src/modules/getItem.js
new file mode 100644
index 000000000..4b017ff16
--- /dev/null
+++ b/packages/spacecat-shared-dynamo/src/modules/getItem.js
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2023 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+/**
+ * Retrieves an item from DynamoDB using a table name and key object.
+ *
+ * @param {DynamoDBDocumentClient} docClient - The AWS SDK DynamoDB Document client instance.
+ * @param {string} tableName - The name of the DynamoDB table.
+ * @param {DynamoDbKey} key - The key object containing partitionKey and optionally sortKey.
+ * @param {Logger} log - The logging object, defaults to console.
+ * @returns {Promise} A promise that resolves to the retrieved item.
+ * @throws {Error} Throws an error if the DynamoDB get operation fails or input validation fails.
+ */
+async function getItem(docClient, tableName, key, log = console) {
+ if (!tableName || typeof tableName !== 'string') {
+ throw new Error('Invalid tableName: must be a non-empty string.');
+ }
+
+ if (!key || typeof key !== 'object' || !key.partitionKey) {
+ throw new Error('Invalid key: must be an object with a partitionKey.');
+ }
+
+ const params = {
+ TableName: tableName,
+ Key: key,
+ };
+
+ try {
+ const data = await docClient.get(params);
+ return data.Item;
+ } catch (error) {
+ log.error('DB Get Item Error:', error);
+ throw error;
+ }
+}
+
+export default getItem;
diff --git a/packages/spacecat-shared-dynamo/src/modules/putItem.js b/packages/spacecat-shared-dynamo/src/modules/putItem.js
new file mode 100644
index 000000000..632abce16
--- /dev/null
+++ b/packages/spacecat-shared-dynamo/src/modules/putItem.js
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2023 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+/**
+ * Inserts or updates an item in a DynamoDB table.
+ *
+ * @param {DynamoDBDocumentClient} docClient - The AWS SDK DynamoDB Document client instance.
+ * @param {string} tableName - The name of the DynamoDB table.
+ * @param {Object} item - The item to insert or update in the table.
+ * @param {Logger} log - The logging object, defaults to console.
+ * @returns {Promise} A promise that resolves to a message indicating success.
+ * @throws {Error} Throws an error if the DynamoDB put operation fails.
+ */
+async function putItem(docClient, tableName, item, log = console) {
+ if (!tableName || typeof tableName !== 'string') {
+ throw new Error('Invalid tableName: must be a non-empty string.');
+ }
+
+ const params = {
+ TableName: tableName,
+ Item: item,
+ };
+
+ try {
+ await docClient.put(params);
+ return { message: 'Item inserted/updated successfully.' };
+ } catch (error) {
+ log.error('DB Put Item Error:', error);
+ throw error;
+ }
+}
+
+export default putItem;
diff --git a/packages/spacecat-shared-dynamo/src/modules/query.js b/packages/spacecat-shared-dynamo/src/modules/query.js
new file mode 100644
index 000000000..b3ed9aa62
--- /dev/null
+++ b/packages/spacecat-shared-dynamo/src/modules/query.js
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2023 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+/**
+ * Queries DynamoDB and automatically handles pagination to retrieve all items.
+ *
+ * @param {DynamoDBDocumentClient} docClient - The AWS SDK DynamoDB Document client instance.
+ * @param {Object} originalParams - The parameters for the DynamoDB query.
+ * @param {Logger} log - The logging object, defaults to console.
+ * @returns {Promise} A promise that resolves to an array of items retrieved from DynamoDB.
+ * @throws {Error} Throws an error if the DynamoDB query operation fails.
+ */
+async function query(docClient, originalParams, log = console) {
+ let items = [];
+ const params = { ...originalParams };
+
+ try {
+ let data;
+ do {
+ // eslint-disable-next-line no-await-in-loop
+ data = await docClient.query(params);
+ items = items.concat(data.Items);
+ params.ExclusiveStartKey = data.LastEvaluatedKey;
+ } while (data.LastEvaluatedKey);
+ } catch (error) {
+ log.error('DB Query Error:', error);
+ throw error;
+ }
+ return items;
+}
+
+export default query;
diff --git a/packages/spacecat-shared-dynamo/src/modules/removeItem.js b/packages/spacecat-shared-dynamo/src/modules/removeItem.js
new file mode 100644
index 000000000..53b785961
--- /dev/null
+++ b/packages/spacecat-shared-dynamo/src/modules/removeItem.js
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2023 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+/**
+ * Removes an item from a DynamoDB table.
+ *
+ * @param {DynamoDBDocumentClient} docClient - The AWS SDK DynamoDB Document client instance.
+ * @param {string} tableName - The name of the DynamoDB table.
+ * @param {DynamoDbKey} key - The key object containing partitionKey and optionally sortKey.
+ * @param {Logger} log - The logging object, defaults to console.
+ * @returns {Promise} A promise that resolves to a message indicating successful removal.
+ * @throws {Error} Throws an error if the DynamoDB delete operation fails or input validation fails.
+ */
+async function removeItem(docClient, tableName, key, log = console) {
+ if (!tableName || typeof tableName !== 'string') {
+ throw new Error('Invalid tableName: must be a non-empty string.');
+ }
+
+ if (!key || typeof key !== 'object' || !key.partitionKey) {
+ throw new Error('Invalid key: must be an object with a partitionKey.');
+ }
+
+ const params = {
+ TableName: tableName,
+ Key: key,
+ };
+
+ try {
+ await docClient.delete(params);
+ return { message: 'Item removed successfully.' };
+ } catch (error) {
+ log.error('DB Remove Item Error:', error);
+ throw error;
+ }
+}
+
+export default removeItem;
diff --git a/packages/spacecat-shared-dynamo/test/index.test.js b/packages/spacecat-shared-dynamo/test/index.test.js
index 7a4fc7a73..e16f38fff 100644
--- a/packages/spacecat-shared-dynamo/test/index.test.js
+++ b/packages/spacecat-shared-dynamo/test/index.test.js
@@ -21,7 +21,6 @@ describe('DynamoDB Client', () => {
let mockDocClient;
beforeEach(() => {
- // Setup your mock DocumentClient
mockDocClient = {
query: async (params) => {
// Check if LastEvaluatedKey is provided and simulate pagination
@@ -51,30 +50,81 @@ describe('DynamoDB Client', () => {
});
it('gets an item from the database', async () => {
- const result = await dynamoDbClient.getItem('TestTable', 'testPartitionKey');
+ const key = { partitionKey: 'testPartitionKey' };
+ const result = await dynamoDbClient.getItem('TestTable', key);
expect(result).to.be.an('object');
});
it('gets an item from the database with sort key', async () => {
- const result = await dynamoDbClient.getItem('TestTable', 'testPartitionKey', 'testSortKey');
+ const key = { partitionKey: 'testPartitionKey', sortKey: 'testSortKey' };
+ const result = await dynamoDbClient.getItem('TestTable', key);
expect(result).to.be.an('object');
});
+ it('throws an error for getItem with invalid tableName', async () => {
+ const key = { partitionKey: 'testPartitionKey' };
+ try {
+ await dynamoDbClient.getItem('', key);
+ expect.fail('getItem did not throw with empty tableName');
+ } catch (error) {
+ expect(error.message).to.equal('Invalid tableName: must be a non-empty string.');
+ }
+ });
+
+ it('throws an error for getItem with invalid key', async () => {
+ try {
+ await dynamoDbClient.getItem('TestTable', null);
+ expect.fail('getItem did not throw with invalid key');
+ } catch (error) {
+ expect(error.message).to.equal('Invalid key: must be an object with a partitionKey.');
+ }
+ });
+
it('puts an item into the database', async () => {
const result = await dynamoDbClient.putItem('TestTable', { someKey: 'someValue' });
expect(result).to.deep.equal({ message: 'Item inserted/updated successfully.' });
});
+ it('throws an error for putItem with invalid tableName', async () => {
+ try {
+ await dynamoDbClient.putItem('', { someKey: 'someValue' });
+ expect.fail('putItem did not throw with empty tableName');
+ } catch (error) {
+ expect(error.message).to.equal('Invalid tableName: must be a non-empty string.');
+ }
+ });
+
it('removes an item from the database', async () => {
- const result = await dynamoDbClient.removeItem('TestTable', 'testPartitionKey');
+ const key = { partitionKey: 'testPartitionKey' };
+ const result = await dynamoDbClient.removeItem('TestTable', key);
expect(result).to.deep.equal({ message: 'Item removed successfully.' });
});
it('removes an item from the database with sort key', async () => {
- const result = await dynamoDbClient.removeItem('TestTable', 'testPartitionKey', 'testSortKey');
+ const key = { partitionKey: 'testPartitionKey', sortKey: 'testSortKey' };
+ const result = await dynamoDbClient.removeItem('TestTable', key);
expect(result).to.deep.equal({ message: 'Item removed successfully.' });
});
+ it('throws an error for removeItem with invalid tableName', async () => {
+ const key = { partitionKey: 'testPartitionKey' };
+ try {
+ await dynamoDbClient.removeItem('', key);
+ expect.fail('removeItem did not throw with empty tableName');
+ } catch (error) {
+ expect(error.message).to.equal('Invalid tableName: must be a non-empty string.');
+ }
+ });
+
+ it('throws an error for removeItem with invalid key', async () => {
+ try {
+ await dynamoDbClient.removeItem('TestTable', null);
+ expect.fail('removeItem did not throw with invalid key');
+ } catch (error) {
+ expect(error.message).to.equal('Invalid key: must be an object with a partitionKey.');
+ }
+ });
+
it('handles errors in query', async () => {
mockDocClient.query = async () => {
throw new Error('Query failed');
@@ -94,7 +144,7 @@ describe('DynamoDB Client', () => {
};
try {
- await dynamoDbClient.getItem('TestTable', 'testPartitionKey');
+ await dynamoDbClient.getItem('TestTable', { partitionKey: 'testPartitionKey' });
expect.fail('getItem did not throw as expected');
} catch (error) {
expect(error.message).to.equal('Get failed');
@@ -120,7 +170,7 @@ describe('DynamoDB Client', () => {
};
try {
- await dynamoDbClient.removeItem('TestTable', 'testPartitionKey');
+ await dynamoDbClient.removeItem('TestTable', { partitionKey: 'testPartitionKey' });
expect.fail('removeItem did not throw as expected');
} catch (error) {
expect(error.message).to.equal('Remove failed');
From f31f451f048252cf42b986ecabad4e03c6d152f7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dominique=20J=C3=A4ggi?=
Date: Tue, 28 Nov 2023 11:02:33 +0100
Subject: [PATCH 05/10] chore: update doc
---
docs/API.md | 87 +++--------------------------------------------------
1 file changed, 4 insertions(+), 83 deletions(-)
diff --git a/docs/API.md b/docs/API.md
index c4d46ad90..6392f8287 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -4,18 +4,6 @@
createClient(log, dbClient, docClient) ⇒ Object
Creates a client object for interacting with DynamoDB.
-query(originalParams) ⇒ Promise.<Array>
-Queries DynamoDB and automatically handles pagination to retrieve all items.
-
-getItem(tableName, partitionKey, [sortKey]) ⇒ Promise.<Object>
-Retrieves an item from DynamoDB using a table name and key.
-
-putItem(tableName, item) ⇒ Promise.<Object>
-Inserts or updates an item in a DynamoDB table.
-
-removeItem(tableName, partitionKey, [sortKey]) ⇒ Promise.<Object>
-Removes an item from a DynamoDB table.
-
isBoolean(value) ⇒ boolean
Determines if the given value is a boolean or a string representation of a boolean.
@@ -39,7 +27,8 @@
isIsoDate(str) ⇒ boolean
Validates whether the given string is a JavaScript ISO date string in
-Zulu (UTC) timezone. Used for persisting system dates, which must be independent of any user timezone.
+Zulu (UTC) timezone. Used for persisting system dates, which must be
+independent of any user timezone.
isIsoTimeOffsetsDate(str) ⇒ boolean
Validates whether the given string is a JavaScript ISO date string
@@ -70,75 +59,6 @@ Creates a client object for interacting with DynamoDB.
| dbClient | DynamoDB | The AWS SDK DynamoDB client instance. |
| docClient | DynamoDBDocumentClient | The AWS SDK DynamoDB Document client instance. |
-
-
-## query(originalParams) ⇒ Promise.<Array>
-Queries DynamoDB and automatically handles pagination to retrieve all items.
-
-**Kind**: global function
-**Returns**: Promise.<Array> - A promise that resolves to an array of items retrieved from DynamoDB.
-**Throws**:
-
-- Error Throws an error if the DynamoDB query operation fails.
-
-
-| Param | Type | Description |
-| --- | --- | --- |
-| originalParams | Object | The parameters for the DynamoDB query. |
-
-
-
-## getItem(tableName, partitionKey, [sortKey]) ⇒ Promise.<Object>
-Retrieves an item from DynamoDB using a table name and key.
-
-**Kind**: global function
-**Returns**: Promise.<Object> - A promise that resolves to the retrieved item.
-**Throws**:
-
-- Error Throws an error if the DynamoDB get operation fails.
-
-
-| Param | Type | Description |
-| --- | --- | --- |
-| tableName | string | The name of the DynamoDB table. |
-| partitionKey | string | The partition key of the item to retrieve. |
-| [sortKey] | string | The sort key of the item to retrieve, if applicable. |
-
-
-
-## putItem(tableName, item) ⇒ Promise.<Object>
-Inserts or updates an item in a DynamoDB table.
-
-**Kind**: global function
-**Returns**: Promise.<Object> - A promise that resolves to a message indicating success.
-**Throws**:
-
-- Error Throws an error if the DynamoDB put operation fails.
-
-
-| Param | Type | Description |
-| --- | --- | --- |
-| tableName | string | The name of the DynamoDB table. |
-| item | Object | The item to insert or update in the table. |
-
-
-
-## removeItem(tableName, partitionKey, [sortKey]) ⇒ Promise.<Object>
-Removes an item from a DynamoDB table.
-
-**Kind**: global function
-**Returns**: Promise.<Object> - A promise that resolves to a message indicating successful removal.
-**Throws**:
-
-- Error Throws an error if the DynamoDB delete operation fails.
-
-
-| Param | Type | Description |
-| --- | --- | --- |
-| tableName | string | The name of the DynamoDB table. |
-| partitionKey | string | The partition key of the item to remove. |
-| [sortKey] | string | The sort key of the item to remove, if applicable. |
-
## isBoolean(value) ⇒ boolean
@@ -227,7 +147,8 @@ Checks whether the given object is a valid JavaScript Date.
## isIsoDate(str) ⇒ boolean
Validates whether the given string is a JavaScript ISO date string in
-Zulu (UTC) timezone. Used for persisting system dates, which must be independent of any user timezone.
+Zulu (UTC) timezone. Used for persisting system dates, which must be
+independent of any user timezone.
**Kind**: global function
**Returns**: boolean - True if the given string validates successfully.
From 7cc3b38d1c5021164feabbdbddf1197e84d4ecf2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dominique=20J=C3=A4ggi?=
Date: Tue, 28 Nov 2023 11:34:47 +0100
Subject: [PATCH 06/10] chore: modularize tests
---
packages/spacecat-shared-dynamo/package.json | 2 +-
.../spacecat-shared-dynamo/test/index.test.js | 175 +++---------------
.../test/modules/getItem.test.js | 73 ++++++++
.../test/modules/putItem.test.js | 56 ++++++
.../test/modules/query.test.js | 60 ++++++
.../test/modules/removeItem.test.js | 73 ++++++++
6 files changed, 289 insertions(+), 150 deletions(-)
create mode 100644 packages/spacecat-shared-dynamo/test/modules/getItem.test.js
create mode 100644 packages/spacecat-shared-dynamo/test/modules/putItem.test.js
create mode 100644 packages/spacecat-shared-dynamo/test/modules/query.test.js
create mode 100644 packages/spacecat-shared-dynamo/test/modules/removeItem.test.js
diff --git a/packages/spacecat-shared-dynamo/package.json b/packages/spacecat-shared-dynamo/package.json
index d2c463c9b..520ec7622 100644
--- a/packages/spacecat-shared-dynamo/package.json
+++ b/packages/spacecat-shared-dynamo/package.json
@@ -13,7 +13,7 @@
"mocha": {
"reporter": "mocha-multi-reporters",
"reporter-options": "configFile=.mocha-multi.json",
- "spec": "test/*.test.js"
+ "spec": "test/**/*.test.js"
},
"repository": {
"type": "git",
diff --git a/packages/spacecat-shared-dynamo/test/index.test.js b/packages/spacecat-shared-dynamo/test/index.test.js
index e16f38fff..6999524bc 100644
--- a/packages/spacecat-shared-dynamo/test/index.test.js
+++ b/packages/spacecat-shared-dynamo/test/index.test.js
@@ -11,169 +11,46 @@
*/
/* eslint-env mocha */
-/* eslint-disable no-unused-expressions */
-
import { expect } from 'chai';
+import { DynamoDB } from '@aws-sdk/client-dynamodb';
+import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
import { createClient } from '../src/index.js';
-describe('DynamoDB Client', () => {
- let dynamoDbClient;
- let mockDocClient;
+describe('createClient', () => {
+ let dbClient;
+ let docClient;
beforeEach(() => {
- mockDocClient = {
- query: async (params) => {
- // Check if LastEvaluatedKey is provided and simulate pagination
- if (params.ExclusiveStartKey === 'key2') {
- return { Items: ['item3'], LastEvaluatedKey: undefined };
- } else {
- return { Items: ['item1', 'item2'], LastEvaluatedKey: 'key2' };
- }
- },
- get: async () => ({ Item: {} }),
- put: async () => ({}),
- delete: async () => ({}),
- };
-
- dynamoDbClient = createClient(console, undefined, mockDocClient);
- });
-
- it('queries items from the database', async () => {
- const result = await dynamoDbClient.query({ TableName: 'TestTable' });
- expect(result).to.be.an('array');
- });
-
- it('queries items from the database with pagination', async () => {
- const result = await dynamoDbClient.query({ TableName: 'TestTable' });
- expect(result).to.have.lengthOf(3);
- expect(result).to.deep.equal(['item1', 'item2', 'item3']);
- });
-
- it('gets an item from the database', async () => {
- const key = { partitionKey: 'testPartitionKey' };
- const result = await dynamoDbClient.getItem('TestTable', key);
- expect(result).to.be.an('object');
- });
-
- it('gets an item from the database with sort key', async () => {
- const key = { partitionKey: 'testPartitionKey', sortKey: 'testSortKey' };
- const result = await dynamoDbClient.getItem('TestTable', key);
- expect(result).to.be.an('object');
- });
-
- it('throws an error for getItem with invalid tableName', async () => {
- const key = { partitionKey: 'testPartitionKey' };
- try {
- await dynamoDbClient.getItem('', key);
- expect.fail('getItem did not throw with empty tableName');
- } catch (error) {
- expect(error.message).to.equal('Invalid tableName: must be a non-empty string.');
- }
- });
-
- it('throws an error for getItem with invalid key', async () => {
- try {
- await dynamoDbClient.getItem('TestTable', null);
- expect.fail('getItem did not throw with invalid key');
- } catch (error) {
- expect(error.message).to.equal('Invalid key: must be an object with a partitionKey.');
- }
+ dbClient = new DynamoDB();
+ docClient = DynamoDBDocumentClient.from(dbClient);
});
- it('puts an item into the database', async () => {
- const result = await dynamoDbClient.putItem('TestTable', { someKey: 'someValue' });
- expect(result).to.deep.equal({ message: 'Item inserted/updated successfully.' });
+ it('should create a DynamoDB client with query method', () => {
+ const client = createClient(console, dbClient, docClient);
+ expect(client).to.have.property('query');
+ expect(client.query).to.be.a('function');
});
- it('throws an error for putItem with invalid tableName', async () => {
- try {
- await dynamoDbClient.putItem('', { someKey: 'someValue' });
- expect.fail('putItem did not throw with empty tableName');
- } catch (error) {
- expect(error.message).to.equal('Invalid tableName: must be a non-empty string.');
- }
+ it('should create a DynamoDB client with getItem method', () => {
+ const client = createClient(console, dbClient, docClient);
+ expect(client).to.have.property('getItem');
+ expect(client.getItem).to.be.a('function');
});
- it('removes an item from the database', async () => {
- const key = { partitionKey: 'testPartitionKey' };
- const result = await dynamoDbClient.removeItem('TestTable', key);
- expect(result).to.deep.equal({ message: 'Item removed successfully.' });
+ it('should create a DynamoDB client with putItem method', () => {
+ const client = createClient(console, dbClient, docClient);
+ expect(client).to.have.property('putItem');
+ expect(client.putItem).to.be.a('function');
});
- it('removes an item from the database with sort key', async () => {
- const key = { partitionKey: 'testPartitionKey', sortKey: 'testSortKey' };
- const result = await dynamoDbClient.removeItem('TestTable', key);
- expect(result).to.deep.equal({ message: 'Item removed successfully.' });
+ it('should create a DynamoDB client with removeItem method', () => {
+ const client = createClient(console, dbClient, docClient);
+ expect(client).to.have.property('removeItem');
+ expect(client.removeItem).to.be.a('function');
});
- it('throws an error for removeItem with invalid tableName', async () => {
- const key = { partitionKey: 'testPartitionKey' };
- try {
- await dynamoDbClient.removeItem('', key);
- expect.fail('removeItem did not throw with empty tableName');
- } catch (error) {
- expect(error.message).to.equal('Invalid tableName: must be a non-empty string.');
- }
- });
-
- it('throws an error for removeItem with invalid key', async () => {
- try {
- await dynamoDbClient.removeItem('TestTable', null);
- expect.fail('removeItem did not throw with invalid key');
- } catch (error) {
- expect(error.message).to.equal('Invalid key: must be an object with a partitionKey.');
- }
- });
-
- it('handles errors in query', async () => {
- mockDocClient.query = async () => {
- throw new Error('Query failed');
- };
-
- try {
- await dynamoDbClient.query({ TableName: 'TestTable' });
- expect.fail('queryDb did not throw as expected');
- } catch (error) {
- expect(error.message).to.equal('Query failed');
- }
- });
-
- it('handles errors in getItem', async () => {
- mockDocClient.get = async () => {
- throw new Error('Get failed');
- };
-
- try {
- await dynamoDbClient.getItem('TestTable', { partitionKey: 'testPartitionKey' });
- expect.fail('getItem did not throw as expected');
- } catch (error) {
- expect(error.message).to.equal('Get failed');
- }
- });
-
- it('handles errors in putItem', async () => {
- mockDocClient.put = async () => {
- throw new Error('Put failed');
- };
-
- try {
- await dynamoDbClient.putItem('TestTable', { someKey: 'someValue' });
- expect.fail('putItem did not throw as expected');
- } catch (error) {
- expect(error.message).to.equal('Put failed');
- }
- });
-
- it('handles errors in removeItem', async () => {
- mockDocClient.delete = async () => {
- throw new Error('Remove failed');
- };
-
- try {
- await dynamoDbClient.removeItem('TestTable', { partitionKey: 'testPartitionKey' });
- expect.fail('removeItem did not throw as expected');
- } catch (error) {
- expect(error.message).to.equal('Remove failed');
- }
+ it('should use default parameters if none are provided', () => {
+ const client = createClient();
+ expect(client).to.have.all.keys('query', 'getItem', 'putItem', 'removeItem');
});
});
diff --git a/packages/spacecat-shared-dynamo/test/modules/getItem.test.js b/packages/spacecat-shared-dynamo/test/modules/getItem.test.js
new file mode 100644
index 000000000..16fdbd285
--- /dev/null
+++ b/packages/spacecat-shared-dynamo/test/modules/getItem.test.js
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2023 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+/* eslint-env mocha */
+
+import { expect } from 'chai';
+import { createClient } from '../../src/index.js';
+
+describe('getItem', () => {
+ let dynamoDbClient;
+ let mockDocClient;
+
+ beforeEach(() => {
+ mockDocClient = {
+ get: async () => ({ Item: {} }),
+ };
+
+ dynamoDbClient = createClient(console, undefined, mockDocClient);
+ });
+
+ it('gets an item from the database', async () => {
+ const key = { partitionKey: 'testPartitionKey' };
+ const result = await dynamoDbClient.getItem('TestTable', key);
+ expect(result).to.be.an('object');
+ });
+
+ it('gets an item from the database with sort key', async () => {
+ const key = { partitionKey: 'testPartitionKey', sortKey: 'testSortKey' };
+ const result = await dynamoDbClient.getItem('TestTable', key);
+ expect(result).to.be.an('object');
+ });
+
+ it('throws an error for getItem with invalid tableName', async () => {
+ const key = { partitionKey: 'testPartitionKey' };
+ try {
+ await dynamoDbClient.getItem('', key);
+ expect.fail('getItem did not throw with empty tableName');
+ } catch (error) {
+ expect(error.message).to.equal('Invalid tableName: must be a non-empty string.');
+ }
+ });
+
+ it('throws an error for getItem with invalid key', async () => {
+ try {
+ await dynamoDbClient.getItem('TestTable', null);
+ expect.fail('getItem did not throw with invalid key');
+ } catch (error) {
+ expect(error.message).to.equal('Invalid key: must be an object with a partitionKey.');
+ }
+ });
+
+ it('handles errors in getItem', async () => {
+ mockDocClient.get = async () => {
+ throw new Error('Get failed');
+ };
+
+ try {
+ await dynamoDbClient.getItem('TestTable', { partitionKey: 'testPartitionKey' });
+ expect.fail('getItem did not throw as expected');
+ } catch (error) {
+ expect(error.message).to.equal('Get failed');
+ }
+ });
+});
diff --git a/packages/spacecat-shared-dynamo/test/modules/putItem.test.js b/packages/spacecat-shared-dynamo/test/modules/putItem.test.js
new file mode 100644
index 000000000..2c32df75a
--- /dev/null
+++ b/packages/spacecat-shared-dynamo/test/modules/putItem.test.js
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2023 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+/* eslint-env mocha */
+
+import { expect } from 'chai';
+import { createClient } from '../../src/index.js';
+
+describe('putItem', () => {
+ let dynamoDbClient;
+ let mockDocClient;
+
+ beforeEach(() => {
+ mockDocClient = {
+ put: async () => ({}),
+ };
+
+ dynamoDbClient = createClient(console, undefined, mockDocClient);
+ });
+
+ it('puts an item into the database', async () => {
+ const result = await dynamoDbClient.putItem('TestTable', { someKey: 'someValue' });
+ expect(result).to.deep.equal({ message: 'Item inserted/updated successfully.' });
+ });
+
+ it('throws an error for putItem with invalid tableName', async () => {
+ try {
+ await dynamoDbClient.putItem('', { someKey: 'someValue' });
+ expect.fail('putItem did not throw with empty tableName');
+ } catch (error) {
+ expect(error.message).to.equal('Invalid tableName: must be a non-empty string.');
+ }
+ });
+
+ it('handles errors in putItem', async () => {
+ mockDocClient.put = async () => {
+ throw new Error('Put failed');
+ };
+
+ try {
+ await dynamoDbClient.putItem('TestTable', { someKey: 'someValue' });
+ expect.fail('putItem did not throw as expected');
+ } catch (error) {
+ expect(error.message).to.equal('Put failed');
+ }
+ });
+});
diff --git a/packages/spacecat-shared-dynamo/test/modules/query.test.js b/packages/spacecat-shared-dynamo/test/modules/query.test.js
new file mode 100644
index 000000000..3424f47df
--- /dev/null
+++ b/packages/spacecat-shared-dynamo/test/modules/query.test.js
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2023 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+/* eslint-env mocha */
+
+import { expect } from 'chai';
+import { createClient } from '../../src/index.js';
+
+describe('query', () => {
+ let dynamoDbClient;
+ let mockDocClient;
+
+ beforeEach(() => {
+ mockDocClient = {
+ query: async (params) => {
+ // Check if LastEvaluatedKey is provided and simulate pagination
+ if (params.ExclusiveStartKey === 'key2') {
+ return { Items: ['item3'], LastEvaluatedKey: undefined };
+ } else {
+ return { Items: ['item1', 'item2'], LastEvaluatedKey: 'key2' };
+ }
+ },
+ };
+
+ dynamoDbClient = createClient(console, undefined, mockDocClient);
+ });
+
+ it('queries items from the database', async () => {
+ const result = await dynamoDbClient.query({ TableName: 'TestTable' });
+ expect(result).to.be.an('array');
+ });
+
+ it('queries items from the database with pagination', async () => {
+ const result = await dynamoDbClient.query({ TableName: 'TestTable' });
+ expect(result).to.have.lengthOf(3);
+ expect(result).to.deep.equal(['item1', 'item2', 'item3']);
+ });
+
+ it('handles errors in query', async () => {
+ mockDocClient.query = async () => {
+ throw new Error('Query failed');
+ };
+
+ try {
+ await dynamoDbClient.query({ TableName: 'TestTable' });
+ expect.fail('queryDb did not throw as expected');
+ } catch (error) {
+ expect(error.message).to.equal('Query failed');
+ }
+ });
+});
diff --git a/packages/spacecat-shared-dynamo/test/modules/removeItem.test.js b/packages/spacecat-shared-dynamo/test/modules/removeItem.test.js
new file mode 100644
index 000000000..f1c772f7d
--- /dev/null
+++ b/packages/spacecat-shared-dynamo/test/modules/removeItem.test.js
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2023 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+/* eslint-env mocha */
+
+import { expect } from 'chai';
+import { createClient } from '../../src/index.js';
+
+describe('removeItem', () => {
+ let dynamoDbClient;
+ let mockDocClient;
+
+ beforeEach(() => {
+ mockDocClient = {
+ delete: async () => ({}),
+ };
+
+ dynamoDbClient = createClient(console, undefined, mockDocClient);
+ });
+
+ it('removes an item from the database', async () => {
+ const key = { partitionKey: 'testPartitionKey' };
+ const result = await dynamoDbClient.removeItem('TestTable', key);
+ expect(result).to.deep.equal({ message: 'Item removed successfully.' });
+ });
+
+ it('removes an item from the database with sort key', async () => {
+ const key = { partitionKey: 'testPartitionKey', sortKey: 'testSortKey' };
+ const result = await dynamoDbClient.removeItem('TestTable', key);
+ expect(result).to.deep.equal({ message: 'Item removed successfully.' });
+ });
+
+ it('throws an error for removeItem with invalid tableName', async () => {
+ const key = { partitionKey: 'testPartitionKey' };
+ try {
+ await dynamoDbClient.removeItem('', key);
+ expect.fail('removeItem did not throw with empty tableName');
+ } catch (error) {
+ expect(error.message).to.equal('Invalid tableName: must be a non-empty string.');
+ }
+ });
+
+ it('throws an error for removeItem with invalid key', async () => {
+ try {
+ await dynamoDbClient.removeItem('TestTable', null);
+ expect.fail('removeItem did not throw with invalid key');
+ } catch (error) {
+ expect(error.message).to.equal('Invalid key: must be an object with a partitionKey.');
+ }
+ });
+
+ it('handles errors in removeItem', async () => {
+ mockDocClient.delete = async () => {
+ throw new Error('Remove failed');
+ };
+
+ try {
+ await dynamoDbClient.removeItem('TestTable', { partitionKey: 'testPartitionKey' });
+ expect.fail('removeItem did not throw as expected');
+ } catch (error) {
+ expect(error.message).to.equal('Remove failed');
+ }
+ });
+});
From f98203d53b6c02f91f4031245009d424a96cfbac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dominique=20J=C3=A4ggi?=
Date: Tue, 28 Nov 2023 11:36:11 +0100
Subject: [PATCH 07/10] chore: update license year
---
packages/spacecat-shared-dynamo/src/index.d.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/spacecat-shared-dynamo/src/index.d.ts b/packages/spacecat-shared-dynamo/src/index.d.ts
index 78f759fe3..98a79528c 100644
--- a/packages/spacecat-shared-dynamo/src/index.d.ts
+++ b/packages/spacecat-shared-dynamo/src/index.d.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 Adobe. All rights reserved.
+ * Copyright 2023 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
From 0a303dac1fae0496d7241df5c0b837f19f94edd8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dominique=20J=C3=A4ggi?=
Date: Tue, 28 Nov 2023 13:47:36 +0100
Subject: [PATCH 08/10] feat: add query timing
---
.../src/modules/getItem.js | 10 ++++++++
.../src/modules/putItem.js | 10 ++++++++
.../src/modules/query.js | 24 +++++++++++++++++++
.../src/modules/removeItem.js | 10 ++++++++
4 files changed, 54 insertions(+)
diff --git a/packages/spacecat-shared-dynamo/src/modules/getItem.js b/packages/spacecat-shared-dynamo/src/modules/getItem.js
index 4b017ff16..eaa64a3fd 100644
--- a/packages/spacecat-shared-dynamo/src/modules/getItem.js
+++ b/packages/spacecat-shared-dynamo/src/modules/getItem.js
@@ -10,6 +10,8 @@
* governing permissions and limitations under the License.
*/
+import { performance } from 'perf_hooks';
+
/**
* Retrieves an item from DynamoDB using a table name and key object.
*
@@ -35,7 +37,15 @@ async function getItem(docClient, tableName, key, log = console) {
};
try {
+ const startTime = performance.now();
+
const data = await docClient.get(params);
+
+ const endTime = performance.now();
+ const duration = endTime - startTime;
+
+ log.info(`GetItem execution time: ${duration.toFixed(2)} ms for query: ${JSON.stringify(params)}`);
+
return data.Item;
} catch (error) {
log.error('DB Get Item Error:', error);
diff --git a/packages/spacecat-shared-dynamo/src/modules/putItem.js b/packages/spacecat-shared-dynamo/src/modules/putItem.js
index 632abce16..39fb28ea9 100644
--- a/packages/spacecat-shared-dynamo/src/modules/putItem.js
+++ b/packages/spacecat-shared-dynamo/src/modules/putItem.js
@@ -10,6 +10,8 @@
* governing permissions and limitations under the License.
*/
+import { performance } from 'perf_hooks';
+
/**
* Inserts or updates an item in a DynamoDB table.
*
@@ -31,7 +33,15 @@ async function putItem(docClient, tableName, item, log = console) {
};
try {
+ const startTime = performance.now();
+
await docClient.put(params);
+
+ const endTime = performance.now();
+ const duration = endTime - startTime;
+
+ log.info(`PutItem execution time: ${duration.toFixed(2)} ms for query: ${JSON.stringify(params)}`);
+
return { message: 'Item inserted/updated successfully.' };
} catch (error) {
log.error('DB Put Item Error:', error);
diff --git a/packages/spacecat-shared-dynamo/src/modules/query.js b/packages/spacecat-shared-dynamo/src/modules/query.js
index b3ed9aa62..0bec8a76d 100644
--- a/packages/spacecat-shared-dynamo/src/modules/query.js
+++ b/packages/spacecat-shared-dynamo/src/modules/query.js
@@ -10,6 +10,8 @@
* governing permissions and limitations under the License.
*/
+import { performance } from 'perf_hooks';
+
/**
* Queries DynamoDB and automatically handles pagination to retrieve all items.
*
@@ -23,11 +25,30 @@ async function query(docClient, originalParams, log = console) {
let items = [];
const params = { ...originalParams };
+ let totalTime = 0;
+ let paginationCount = 0;
+
try {
let data;
do {
+ const startTime = performance.now();
+
+ /*
+ This is one of the scenarios where it's appropriate to disable
+ the ESLint rule for this specific case.
+ In this case, it's necessary because each query depends on the
+ result of the previous one (to get the LastEvaluatedKey).
+ */
// eslint-disable-next-line no-await-in-loop
data = await docClient.query(params);
+
+ const endTime = performance.now(); // End timing
+ const duration = endTime - startTime;
+ totalTime += duration;
+ paginationCount += 1;
+
+ log.info(`Pagination ${paginationCount} query time: ${duration.toFixed(2)} ms`);
+
items = items.concat(data.Items);
params.ExclusiveStartKey = data.LastEvaluatedKey;
} while (data.LastEvaluatedKey);
@@ -35,6 +56,9 @@ async function query(docClient, originalParams, log = console) {
log.error('DB Query Error:', error);
throw error;
}
+
+ log.info(`Total query time: ${totalTime.toFixed(2)} ms with ${paginationCount} paginations for query: ${JSON.stringify(params)}`);
+
return items;
}
diff --git a/packages/spacecat-shared-dynamo/src/modules/removeItem.js b/packages/spacecat-shared-dynamo/src/modules/removeItem.js
index 53b785961..a89a4ace2 100644
--- a/packages/spacecat-shared-dynamo/src/modules/removeItem.js
+++ b/packages/spacecat-shared-dynamo/src/modules/removeItem.js
@@ -10,6 +10,8 @@
* governing permissions and limitations under the License.
*/
+import { performance } from 'perf_hooks';
+
/**
* Removes an item from a DynamoDB table.
*
@@ -35,7 +37,15 @@ async function removeItem(docClient, tableName, key, log = console) {
};
try {
+ const startTime = performance.now();
+
await docClient.delete(params);
+
+ const endTime = performance.now();
+ const duration = endTime - startTime;
+
+ log.info(`RemoveItem execution time: ${duration.toFixed(2)} ms for query: ${JSON.stringify(params)}`);
+
return { message: 'Item removed successfully.' };
} catch (error) {
log.error('DB Remove Item Error:', error);
From 6e7977386b1b53eef18e395ce73b566237679133 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dominique=20J=C3=A4ggi?=
Date: Tue, 28 Nov 2023 14:26:58 +0100
Subject: [PATCH 09/10] chore: simplify tests
---
.../spacecat-shared-utils/src/functions.js | 14 +-
.../test/functions.test.js | 229 ++++++++++--------
2 files changed, 144 insertions(+), 99 deletions(-)
diff --git a/packages/spacecat-shared-utils/src/functions.js b/packages/spacecat-shared-utils/src/functions.js
index 1f61373b1..5cc79ea0e 100644
--- a/packages/spacecat-shared-utils/src/functions.js
+++ b/packages/spacecat-shared-utils/src/functions.js
@@ -10,6 +10,10 @@
* governing permissions and limitations under the License.
*/
+// Precompile regular expressions
+const REGEX_ISO_DATE = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/;
+const REGEX_TIME_OFFSET_DATE = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}(Z|[+-]\d{2}:\d{2})/;
+
/**
* Determines if the given value is a boolean or a string representation of a boolean.
*
@@ -89,8 +93,12 @@ function isValidDate(obj) {
* @returns {boolean} True if the given string validates successfully.
*/
function isIsoDate(str) {
- return /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/.test(str)
- && new Date(str).toISOString() === str;
+ if (!REGEX_ISO_DATE.test(str)) {
+ return false;
+ }
+
+ const date = new Date(str);
+ return isValidDate(date) && date.toISOString() === str;
}
/**
@@ -101,7 +109,7 @@ function isIsoDate(str) {
* @returns {boolean} True if the given string validates successfully.
*/
function isIsoTimeOffsetsDate(str) {
- return /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}(Z|[+-]\d{2}:\d{2})/.test(str);
+ return REGEX_TIME_OFFSET_DATE.test(str);
}
/**
diff --git a/packages/spacecat-shared-utils/test/functions.test.js b/packages/spacecat-shared-utils/test/functions.test.js
index d3b8fe63b..96fb5036d 100644
--- a/packages/spacecat-shared-utils/test/functions.test.js
+++ b/packages/spacecat-shared-utils/test/functions.test.js
@@ -33,19 +33,29 @@ import {
describe('Shared functions', () => {
describe('Commons', () => {
it('is iso date', () => {
- expect(isIsoDate('')).to.be.false;
- expect(isIsoDate('2011-10-05')).to.be.false;
- expect(isIsoDate('2011-10-05T14:48:00.000')).to.be.false;
- expect(isIsoDate('Wed Oct 05 2011 16:48:00 GMT+0200 (CEST)')).to.be.false;
+ const invalidDates = [
+ '',
+ '2011-10-05',
+ '2011-10-05T14:48:00.000',
+ 'Wed Oct 05 2011 16:48:00 GMT+0200 (CEST)',
+ '2011-13-01T14:48:00.000Z',
+ '2011-00-01T14:48:00.000Z',
+ ];
+
+ invalidDates.forEach((date) => expect(isIsoDate(date)).to.be.false);
expect(isIsoDate('2011-10-05T14:48:00.000Z')).to.be.true;
});
it('is iso date with time offset', () => {
- expect(isIsoTimeOffsetsDate('')).to.be.false;
- expect(isIsoTimeOffsetsDate('2019-11-15')).to.be.false;
- expect(isIsoTimeOffsetsDate('2019-11-05T14:43:00.000')).to.be.false;
- expect(isIsoTimeOffsetsDate('Wed Oct 11 2019 14:43:00 GMT+0200 (CEST)')).to.be.false;
+ const invalidOffsetDates = [
+ '',
+ '2019-11-15',
+ '2019-11-05T14:43:00.000',
+ 'Wed Oct 11 2019 14:43:00 GMT+0200 (CEST)',
+ ];
+
+ invalidOffsetDates.forEach((date) => expect(isIsoTimeOffsetsDate(date)).to.be.false);
expect(isIsoTimeOffsetsDate('2019-11-11T14:43:00.000Z')).to.be.true;
expect(isIsoTimeOffsetsDate('2019-11-11T14:43:00.000-00:00')).to.be.true;
@@ -53,15 +63,18 @@ describe('Shared functions', () => {
});
it('has text', () => {
- expect(hasText()).to.be.false;
- expect(hasText(null)).to.be.false;
- expect(hasText(undefined)).to.be.false;
- expect(hasText(123)).to.be.false;
- expect(hasText({})).to.be.false;
- expect(hasText([])).to.be.false;
- expect(hasText(['asd'])).to.be.false;
- expect(hasText({ asd: 'dsa' })).to.be.false;
- expect(hasText('')).to.be.false;
+ const invalidTexts = [
+ null,
+ undefined,
+ 123,
+ [],
+ ['dasd'],
+ {},
+ { asd: 'dsa' },
+ '',
+ ];
+
+ invalidTexts.forEach((value) => expect(hasText(value)).to.be.false);
expect(hasText('a')).to.be.true;
expect(hasText('1')).to.be.true;
@@ -69,17 +82,22 @@ describe('Shared functions', () => {
});
it('is boolean', () => {
- expect(isBoolean()).to.be.false;
- expect(isBoolean(null)).to.be.false;
- expect(isBoolean(undefined)).to.be.false;
- expect(isBoolean([])).to.be.false;
- expect(isBoolean('foo')).to.be.false;
- expect(isBoolean({})).to.be.false;
- expect(isBoolean(NaN)).to.be.false;
- expect(isBoolean(Infinity)).to.be.false;
- expect(isBoolean(-Infinity)).to.be.false;
- expect(isBoolean(-Infinity)).to.be.false;
- expect(isBoolean(123)).to.be.false;
+ const invalidBooleans = [
+ null,
+ undefined,
+ [],
+ ['dasd'],
+ {},
+ { asd: 'dsa' },
+ '',
+ 'dasd',
+ NaN,
+ Infinity,
+ -Infinity,
+ 123,
+ ];
+
+ invalidBooleans.forEach((value) => expect(isBoolean(value)).to.be.false);
expect(isBoolean('true')).to.be.true;
expect(isBoolean('false')).to.be.true;
@@ -88,18 +106,21 @@ describe('Shared functions', () => {
});
it('is number', () => {
- expect(isNumber()).to.be.false;
- expect(isNumber(null)).to.be.false;
- expect(isNumber(undefined)).to.be.false;
- expect(isNumber([])).to.be.false;
- expect(isNumber(['dasd'])).to.be.false;
- expect(isNumber({})).to.be.false;
- expect(isNumber({ asd: 'dsa' })).to.be.false;
- expect(isNumber('')).to.be.false;
- expect(isNumber('dasd')).to.be.false;
- expect(isNumber(NaN)).to.be.false;
- expect(isNumber(Infinity)).to.be.false;
- expect(isNumber(-Infinity)).to.be.false;
+ const invalidNumbers = [
+ null,
+ undefined,
+ [],
+ ['dasd'],
+ {},
+ { asd: 'dsa' },
+ '',
+ 'dasd',
+ NaN,
+ Infinity,
+ -Infinity,
+ ];
+
+ invalidNumbers.forEach((value) => expect(isNumber(value)).to.be.false);
expect(isNumber(0)).to.be.true;
expect(isNumber(123)).to.be.true;
@@ -108,19 +129,22 @@ describe('Shared functions', () => {
});
it('is integer', () => {
- expect(isInteger()).to.be.false;
- expect(isInteger(null)).to.be.false;
- expect(isInteger(undefined)).to.be.false;
- expect(isInteger([])).to.be.false;
- expect(isInteger(['dasd'])).to.be.false;
- expect(isInteger({})).to.be.false;
- expect(isInteger({ asd: 'dsa' })).to.be.false;
- expect(isInteger('')).to.be.false;
- expect(isInteger('dasd')).to.be.false;
- expect(isInteger(NaN)).to.be.false;
- expect(isInteger(Infinity)).to.be.false;
- expect(isInteger(-Infinity)).to.be.false;
- expect(isInteger(12.3)).to.be.false;
+ const invalidIntegers = [
+ null,
+ undefined,
+ [],
+ ['dasd'],
+ {},
+ { asd: 'dsa' },
+ '',
+ 'dasd',
+ NaN,
+ Infinity,
+ -Infinity,
+ 12.3,
+ ];
+
+ invalidIntegers.forEach((value) => expect(isInteger(value)).to.be.false);
expect(isInteger(0)).to.be.true;
expect(isInteger(123)).to.be.true;
@@ -128,44 +152,52 @@ describe('Shared functions', () => {
});
it('is object', () => {
- expect(isObject()).to.be.false;
- expect(isObject(null)).to.be.false;
- expect(isObject(undefined)).to.be.false;
- expect(isObject(123)).to.be.false;
- expect(isObject('dasd')).to.be.false;
- expect(isObject([])).to.be.false;
- expect(isObject(['dasd'])).to.be.false;
+ const invalidObjects = [
+ null,
+ undefined,
+ 123,
+ 'dasd',
+ [],
+ ['dasd'],
+ ];
+
+ invalidObjects.forEach((value) => expect(isObject(value)).to.be.false);
expect(isObject({})).to.be.true;
expect(isObject({ asd: 'dsa' })).to.be.true;
});
it('is string', () => {
- expect(isString()).to.be.false;
- expect(isString(null)).to.be.false;
- expect(isString(undefined)).to.be.false;
- expect(isString(123)).to.be.false;
- expect(isString([])).to.be.false;
- expect(isString(['dasd'])).to.be.false;
- expect(isString({})).to.be.false;
- expect(isString({ asd: 'dsa' })).to.be.false;
+ const invalidStrings = [
+ null,
+ undefined,
+ 123,
+ [],
+ ['dasd'],
+ {},
+ { asd: 'dsa' },
+ ];
+
+ invalidStrings.forEach((value) => expect(isString(value)).to.be.false);
expect(isString('')).to.be.true;
expect(isString('dasd')).to.be.true;
});
it('toBoolean', () => {
- expect(() => toBoolean()).to.throw('Not a boolean value');
- expect(() => toBoolean(null)).to.throw('Not a boolean value');
- expect(() => toBoolean(undefined)).to.throw('Not a boolean value');
- expect(() => toBoolean([])).to.throw('Not a boolean value');
- expect(() => toBoolean('foo')).to.throw('Not a boolean value');
- expect(() => toBoolean({})).to.throw('Not a boolean value');
- expect(() => toBoolean(NaN)).to.throw('Not a boolean value');
- expect(() => toBoolean(Infinity)).to.throw('Not a boolean value');
- expect(() => toBoolean(-Infinity)).to.throw('Not a boolean value');
- expect(() => toBoolean(-Infinity)).to.throw('Not a boolean value');
- expect(() => toBoolean(123)).to.throw('Not a boolean value');
+ const invalidBooleans = [
+ undefined,
+ null,
+ [],
+ 'foo',
+ {},
+ NaN,
+ Infinity,
+ -Infinity,
+ 123,
+ ];
+
+ invalidBooleans.forEach((value) => expect(() => toBoolean(value)).to.throw(Error, 'Not a boolean value'));
expect(toBoolean('true')).to.be.true;
expect(toBoolean('false')).to.be.false;
@@ -184,15 +216,18 @@ describe('Shared functions', () => {
describe('isValidUrl', () => {
it('returns false for invalid Url', async () => {
- expect(isValidUrl(null)).to.be.false;
- expect(isValidUrl(undefined)).to.be.false;
- expect(isValidUrl('dummy')).to.be.false;
- expect(isValidUrl(1234)).to.be.false;
- expect(isValidUrl(true)).to.be.false;
- expect(isValidUrl('example.com')).to.be.false;
- expect(isValidUrl('www.example.com')).to.be.false;
- expect(isValidUrl('255.255.255.256')).to.be.false;
- expect(isValidUrl('ftp://abc.com')).to.be.false;
+ const invalidUrls = [
+ null,
+ undefined,
+ 1234,
+ true,
+ 'example.com',
+ 'www.example.com',
+ '255.255.255.256',
+ 'ftp://abc.com',
+ ];
+
+ invalidUrls.forEach((url) => expect(isValidUrl(url)).to.be.false);
});
it('returns true for valid url', async () => {
@@ -203,14 +238,16 @@ describe('Shared functions', () => {
describe('isValidDate', () => {
it('returns false for invalid date', async () => {
- expect(isValidDate(null)).to.be.false;
- expect(isValidDate(undefined)).to.be.false;
- expect(isValidDate({})).to.be.false;
- expect(isValidDate([])).to.be.false;
- expect(isValidDate(1234)).to.be.false;
- expect(isValidDate('1234')).to.be.false;
- expect(isValidDate(new Date('2019-11-11T14:43:89.000-00:00'))).to.be.false;
- expect(isValidDate(new Date('invalid date'))).to.be.false;
+ const invalidDates = [
+ null,
+ undefined,
+ 1234,
+ true,
+ '2019-11-11T14:43:89.000-00:00',
+ 'invalid date',
+ ];
+
+ invalidDates.forEach((date) => expect(isValidDate(date)).to.be.false);
});
it('returns true for valid date', async () => {
From 82d58314fe67c113cf2d10b2b251ffd8a4b95226 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dominique=20J=C3=A4ggi?=
Date: Tue, 28 Nov 2023 15:37:02 +0100
Subject: [PATCH 10/10] fix: toBoolean / isBoolean
---
packages/spacecat-shared-utils/src/functions.js | 10 ++++++----
packages/spacecat-shared-utils/test/functions.test.js | 4 ++++
2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/packages/spacecat-shared-utils/src/functions.js b/packages/spacecat-shared-utils/src/functions.js
index 5cc79ea0e..4e877714b 100644
--- a/packages/spacecat-shared-utils/src/functions.js
+++ b/packages/spacecat-shared-utils/src/functions.js
@@ -15,13 +15,15 @@ const REGEX_ISO_DATE = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/;
const REGEX_TIME_OFFSET_DATE = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}(Z|[+-]\d{2}:\d{2})/;
/**
- * Determines if the given value is a boolean or a string representation of a boolean.
+ * Determines case-insensitively if the given value is a boolean or a string
+ * representation of a boolean.
*
* @param {*} value - The value to check.
* @returns {boolean} True if the value is a boolean or a string representation of a boolean.
*/
function isBoolean(value) {
- return typeof value === 'boolean' || value === 'true' || value === 'false';
+ const lowerCaseValue = String(value).toLowerCase();
+ return typeof value === 'boolean' || lowerCaseValue === 'true' || lowerCaseValue === 'false';
}
/**
@@ -138,11 +140,11 @@ function toBoolean(value) {
if (!isBoolean(value)) {
throw new Error('Not a boolean value');
}
- return JSON.parse(value);
+ return JSON.parse(String(value).toLowerCase());
}
/**
- * Compares two arrays for equality.
+ * Compares two arrays for equality. Supports primitive array item types only.
*
* @param {Array} a - The first array to compare.
* @param {Array} b - The second array to compare.
diff --git a/packages/spacecat-shared-utils/test/functions.test.js b/packages/spacecat-shared-utils/test/functions.test.js
index 96fb5036d..f346cf72e 100644
--- a/packages/spacecat-shared-utils/test/functions.test.js
+++ b/packages/spacecat-shared-utils/test/functions.test.js
@@ -100,7 +100,9 @@ describe('Shared functions', () => {
invalidBooleans.forEach((value) => expect(isBoolean(value)).to.be.false);
expect(isBoolean('true')).to.be.true;
+ expect(isBoolean('True')).to.be.true;
expect(isBoolean('false')).to.be.true;
+ expect(isBoolean('False')).to.be.true;
expect(isBoolean(true)).to.be.true;
expect(isBoolean(false)).to.be.true;
});
@@ -200,7 +202,9 @@ describe('Shared functions', () => {
invalidBooleans.forEach((value) => expect(() => toBoolean(value)).to.throw(Error, 'Not a boolean value'));
expect(toBoolean('true')).to.be.true;
+ expect(toBoolean('True')).to.be.true;
expect(toBoolean('false')).to.be.false;
+ expect(toBoolean('False')).to.be.false;
expect(toBoolean(true)).to.be.true;
expect(toBoolean(false)).to.be.false;
});