diff --git a/contentlake-shared.code-workspace b/contentlake-shared.code-workspace index 1da94de..5a5563f 100644 --- a/contentlake-shared.code-workspace +++ b/contentlake-shared.code-workspace @@ -15,6 +15,9 @@ { "path": "packages/functions" }, + { + "path": "packages/queue-client" + }, { "path": "packages/request-handler" }, diff --git a/package-lock.json b/package-lock.json index 28dcf0f..16007bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,11 @@ "workspaces": [ "./packages/*" ], + "dependencies": { + "@adobe/contentlake-shared-blob-storage": "^1.2.1", + "@adobe/contentlake-shared-queue-client": "^1.2.0", + "@adobe/contentlake-shared-rest-error": "^1.0.0" + }, "devDependencies": { "@adobe/eslint-config-helix": "2.0.2", "@semantic-release/changelog": "6.0.3", @@ -92,6 +97,10 @@ "resolved": "packages/logger", "link": true }, + "node_modules/@adobe/contentlake-shared-queue-client": { + "resolved": "packages/queue-client", + "link": true + }, "node_modules/@adobe/contentlake-shared-request-handler": { "resolved": "packages/request-handler", "link": true @@ -168,8 +177,7 @@ }, "node_modules/@adobe/helix-universal": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@adobe/helix-universal/-/helix-universal-4.3.0.tgz", - "integrity": "sha512-f13i/cL82R/hGZ7Ibs7w2RCcITBJt55OQF5/lg0mQDhleALXYsrDlDeLCbnLb2naeYj9dyd1Xn3aoetG0ZjfZQ==", + "license": "Apache-2.0", "dependencies": { "@adobe/fetch": "4.0.13", "aws4": "1.12.0" @@ -2491,201 +2499,1009 @@ "version": "3.370.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-signing": "3.370.0", - "@aws-sdk/types": "3.370.0", - "@smithy/types": "^1.1.0", + "@aws-sdk/middleware-signing": "3.370.0", + "@aws-sdk/types": "3.370.0", + "@smithy/types": "^1.1.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-signing": { + "version": "3.370.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.370.0", + "@smithy/property-provider": "^1.0.1", + "@smithy/protocol-http": "^1.1.0", + "@smithy/signature-v4": "^1.0.1", + "@smithy/types": "^1.1.0", + "@smithy/util-middleware": "^1.0.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.370.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.370.0", + "@aws-sdk/util-endpoints": "3.370.0", + "@smithy/protocol-http": "^1.1.0", + "@smithy/types": "^1.1.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/token-providers": { + "version": "3.370.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso-oidc": "3.370.0", + "@aws-sdk/types": "3.370.0", + "@smithy/property-provider": "^1.0.1", + "@smithy/shared-ini-file-loader": "^1.0.1", + "@smithy/types": "^1.1.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/types": { + "version": "3.370.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^1.1.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-endpoints": { + "version": "3.370.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.370.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.370.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.370.0", + "@smithy/types": "^1.1.0", + "bowser": "^2.11.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.370.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.370.0", + "@smithy/node-config-provider": "^1.0.1", + "@smithy/types": "^1.1.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/client-secrets-manager": { + "version": "3.360.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.360.0", + "@aws-sdk/config-resolver": "3.357.0", + "@aws-sdk/credential-provider-node": "3.360.0", + "@aws-sdk/fetch-http-handler": "3.357.0", + "@aws-sdk/hash-node": "3.357.0", + "@aws-sdk/invalid-dependency": "3.357.0", + "@aws-sdk/middleware-content-length": "3.357.0", + "@aws-sdk/middleware-endpoint": "3.357.0", + "@aws-sdk/middleware-host-header": "3.357.0", + "@aws-sdk/middleware-logger": "3.357.0", + "@aws-sdk/middleware-recursion-detection": "3.357.0", + "@aws-sdk/middleware-retry": "3.357.0", + "@aws-sdk/middleware-serde": "3.357.0", + "@aws-sdk/middleware-signing": "3.357.0", + "@aws-sdk/middleware-stack": "3.357.0", + "@aws-sdk/middleware-user-agent": "3.357.0", + "@aws-sdk/node-config-provider": "3.357.0", + "@aws-sdk/node-http-handler": "3.360.0", + "@aws-sdk/smithy-client": "3.360.0", + "@aws-sdk/types": "3.357.0", + "@aws-sdk/url-parser": "3.357.0", + "@aws-sdk/util-base64": "3.310.0", + "@aws-sdk/util-body-length-browser": "3.310.0", + "@aws-sdk/util-body-length-node": "3.310.0", + "@aws-sdk/util-defaults-mode-browser": "3.360.0", + "@aws-sdk/util-defaults-mode-node": "3.360.0", + "@aws-sdk/util-endpoints": "3.357.0", + "@aws-sdk/util-retry": "3.357.0", + "@aws-sdk/util-user-agent-browser": "3.357.0", + "@aws-sdk/util-user-agent-node": "3.357.0", + "@aws-sdk/util-utf8": "3.310.0", + "@smithy/protocol-http": "^1.0.1", + "@smithy/types": "^1.0.0", + "tslib": "^2.5.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs": { + "version": "3.391.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.391.0", + "@aws-sdk/credential-provider-node": "3.391.0", + "@aws-sdk/middleware-host-header": "3.391.0", + "@aws-sdk/middleware-logger": "3.391.0", + "@aws-sdk/middleware-recursion-detection": "3.391.0", + "@aws-sdk/middleware-sdk-sqs": "3.391.0", + "@aws-sdk/middleware-signing": "3.391.0", + "@aws-sdk/middleware-user-agent": "3.391.0", + "@aws-sdk/types": "3.391.0", + "@aws-sdk/util-endpoints": "3.391.0", + "@aws-sdk/util-user-agent-browser": "3.391.0", + "@aws-sdk/util-user-agent-node": "3.391.0", + "@smithy/config-resolver": "^2.0.3", + "@smithy/fetch-http-handler": "^2.0.3", + "@smithy/hash-node": "^2.0.3", + "@smithy/invalid-dependency": "^2.0.3", + "@smithy/md5-js": "^2.0.3", + "@smithy/middleware-content-length": "^2.0.3", + "@smithy/middleware-endpoint": "^2.0.3", + "@smithy/middleware-retry": "^2.0.3", + "@smithy/middleware-serde": "^2.0.3", + "@smithy/middleware-stack": "^2.0.0", + "@smithy/node-config-provider": "^2.0.3", + "@smithy/node-http-handler": "^2.0.3", + "@smithy/protocol-http": "^2.0.3", + "@smithy/smithy-client": "^2.0.3", + "@smithy/types": "^2.2.0", + "@smithy/url-parser": "^2.0.3", + "@smithy/util-base64": "^2.0.0", + "@smithy/util-body-length-browser": "^2.0.0", + "@smithy/util-body-length-node": "^2.0.0", + "@smithy/util-defaults-mode-browser": "^2.0.3", + "@smithy/util-defaults-mode-node": "^2.0.3", + "@smithy/util-retry": "^2.0.0", + "@smithy/util-utf8": "^2.0.0", + "fast-xml-parser": "4.2.5", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/client-sso": { + "version": "3.391.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/middleware-host-header": "3.391.0", + "@aws-sdk/middleware-logger": "3.391.0", + "@aws-sdk/middleware-recursion-detection": "3.391.0", + "@aws-sdk/middleware-user-agent": "3.391.0", + "@aws-sdk/types": "3.391.0", + "@aws-sdk/util-endpoints": "3.391.0", + "@aws-sdk/util-user-agent-browser": "3.391.0", + "@aws-sdk/util-user-agent-node": "3.391.0", + "@smithy/config-resolver": "^2.0.3", + "@smithy/fetch-http-handler": "^2.0.3", + "@smithy/hash-node": "^2.0.3", + "@smithy/invalid-dependency": "^2.0.3", + "@smithy/middleware-content-length": "^2.0.3", + "@smithy/middleware-endpoint": "^2.0.3", + "@smithy/middleware-retry": "^2.0.3", + "@smithy/middleware-serde": "^2.0.3", + "@smithy/middleware-stack": "^2.0.0", + "@smithy/node-config-provider": "^2.0.3", + "@smithy/node-http-handler": "^2.0.3", + "@smithy/protocol-http": "^2.0.3", + "@smithy/smithy-client": "^2.0.3", + "@smithy/types": "^2.2.0", + "@smithy/url-parser": "^2.0.3", + "@smithy/util-base64": "^2.0.0", + "@smithy/util-body-length-browser": "^2.0.0", + "@smithy/util-body-length-node": "^2.0.0", + "@smithy/util-defaults-mode-browser": "^2.0.3", + "@smithy/util-defaults-mode-node": "^2.0.3", + "@smithy/util-retry": "^2.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/client-sts": { + "version": "3.391.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/credential-provider-node": "3.391.0", + "@aws-sdk/middleware-host-header": "3.391.0", + "@aws-sdk/middleware-logger": "3.391.0", + "@aws-sdk/middleware-recursion-detection": "3.391.0", + "@aws-sdk/middleware-sdk-sts": "3.391.0", + "@aws-sdk/middleware-signing": "3.391.0", + "@aws-sdk/middleware-user-agent": "3.391.0", + "@aws-sdk/types": "3.391.0", + "@aws-sdk/util-endpoints": "3.391.0", + "@aws-sdk/util-user-agent-browser": "3.391.0", + "@aws-sdk/util-user-agent-node": "3.391.0", + "@smithy/config-resolver": "^2.0.3", + "@smithy/fetch-http-handler": "^2.0.3", + "@smithy/hash-node": "^2.0.3", + "@smithy/invalid-dependency": "^2.0.3", + "@smithy/middleware-content-length": "^2.0.3", + "@smithy/middleware-endpoint": "^2.0.3", + "@smithy/middleware-retry": "^2.0.3", + "@smithy/middleware-serde": "^2.0.3", + "@smithy/middleware-stack": "^2.0.0", + "@smithy/node-config-provider": "^2.0.3", + "@smithy/node-http-handler": "^2.0.3", + "@smithy/protocol-http": "^2.0.3", + "@smithy/smithy-client": "^2.0.3", + "@smithy/types": "^2.2.0", + "@smithy/url-parser": "^2.0.3", + "@smithy/util-base64": "^2.0.0", + "@smithy/util-body-length-browser": "^2.0.0", + "@smithy/util-body-length-node": "^2.0.0", + "@smithy/util-defaults-mode-browser": "^2.0.3", + "@smithy/util-defaults-mode-node": "^2.0.3", + "@smithy/util-retry": "^2.0.0", + "@smithy/util-utf8": "^2.0.0", + "fast-xml-parser": "4.2.5", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.391.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.391.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.391.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.391.0", + "@aws-sdk/credential-provider-process": "3.391.0", + "@aws-sdk/credential-provider-sso": "3.391.0", + "@aws-sdk/credential-provider-web-identity": "3.391.0", + "@aws-sdk/types": "3.391.0", + "@smithy/credential-provider-imds": "^2.0.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.0", + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.391.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.391.0", + "@aws-sdk/credential-provider-ini": "3.391.0", + "@aws-sdk/credential-provider-process": "3.391.0", + "@aws-sdk/credential-provider-sso": "3.391.0", + "@aws-sdk/credential-provider-web-identity": "3.391.0", + "@aws-sdk/types": "3.391.0", + "@smithy/credential-provider-imds": "^2.0.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.0", + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.391.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.391.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.0", + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.391.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.391.0", + "@aws-sdk/token-providers": "3.391.0", + "@aws-sdk/types": "3.391.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.0", + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.391.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.391.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.391.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.391.0", + "@smithy/protocol-http": "^2.0.3", + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/middleware-logger": { + "version": "3.391.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.391.0", + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.391.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.391.0", + "@smithy/protocol-http": "^2.0.3", + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/middleware-sdk-sts": { + "version": "3.391.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-signing": "3.391.0", + "@aws-sdk/types": "3.391.0", + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/middleware-signing": { + "version": "3.391.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.391.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/protocol-http": "^2.0.3", + "@smithy/signature-v4": "^2.0.0", + "@smithy/types": "^2.2.0", + "@smithy/util-middleware": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.391.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.391.0", + "@aws-sdk/util-endpoints": "3.391.0", + "@smithy/protocol-http": "^2.0.3", + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/token-providers": { + "version": "3.391.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/middleware-host-header": "3.391.0", + "@aws-sdk/middleware-logger": "3.391.0", + "@aws-sdk/middleware-recursion-detection": "3.391.0", + "@aws-sdk/middleware-user-agent": "3.391.0", + "@aws-sdk/types": "3.391.0", + "@aws-sdk/util-endpoints": "3.391.0", + "@aws-sdk/util-user-agent-browser": "3.391.0", + "@aws-sdk/util-user-agent-node": "3.391.0", + "@smithy/config-resolver": "^2.0.3", + "@smithy/fetch-http-handler": "^2.0.3", + "@smithy/hash-node": "^2.0.3", + "@smithy/invalid-dependency": "^2.0.3", + "@smithy/middleware-content-length": "^2.0.3", + "@smithy/middleware-endpoint": "^2.0.3", + "@smithy/middleware-retry": "^2.0.3", + "@smithy/middleware-serde": "^2.0.3", + "@smithy/middleware-stack": "^2.0.0", + "@smithy/node-config-provider": "^2.0.3", + "@smithy/node-http-handler": "^2.0.3", + "@smithy/property-provider": "^2.0.0", + "@smithy/protocol-http": "^2.0.3", + "@smithy/shared-ini-file-loader": "^2.0.0", + "@smithy/smithy-client": "^2.0.3", + "@smithy/types": "^2.2.0", + "@smithy/url-parser": "^2.0.3", + "@smithy/util-base64": "^2.0.0", + "@smithy/util-body-length-browser": "^2.0.0", + "@smithy/util-body-length-node": "^2.0.0", + "@smithy/util-defaults-mode-browser": "^2.0.3", + "@smithy/util-defaults-mode-node": "^2.0.3", + "@smithy/util-retry": "^2.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/types": { + "version": "3.391.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/util-endpoints": { + "version": "3.391.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.391.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.391.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.391.0", + "@smithy/types": "^2.2.0", + "bowser": "^2.11.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.391.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.391.0", + "@smithy/node-config-provider": "^2.0.3", + "@smithy/types": "^2.2.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/client-sqs/node_modules/@smithy/abort-controller": { + "version": "2.0.4", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/config-resolver": { + "version": "2.0.4", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.2.1", + "@smithy/util-config-provider": "^2.0.0", + "@smithy/util-middleware": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/credential-provider-imds": { + "version": "2.0.4", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^2.0.4", + "@smithy/property-provider": "^2.0.4", + "@smithy/types": "^2.2.1", + "@smithy/url-parser": "^2.0.4", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/eventstream-codec": { + "version": "2.0.4", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "3.0.0", + "@smithy/types": "^2.2.1", + "@smithy/util-hex-encoding": "^2.0.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/fetch-http-handler": { + "version": "2.0.4", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^2.0.4", + "@smithy/querystring-builder": "^2.0.4", + "@smithy/types": "^2.2.1", + "@smithy/util-base64": "^2.0.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/hash-node": { + "version": "2.0.4", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.2.1", + "@smithy/util-buffer-from": "^2.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/invalid-dependency": { + "version": "2.0.4", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/is-array-buffer": { + "version": "2.0.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/md5-js": { + "version": "2.0.4", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.2.1", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/middleware-content-length": { + "version": "2.0.4", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^2.0.4", + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/middleware-endpoint": { + "version": "2.0.4", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^2.0.4", + "@smithy/types": "^2.2.1", + "@smithy/url-parser": "^2.0.4", + "@smithy/util-middleware": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/middleware-retry": { + "version": "2.0.4", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^2.0.4", + "@smithy/service-error-classification": "^2.0.0", + "@smithy/types": "^2.2.1", + "@smithy/util-middleware": "^2.0.0", + "@smithy/util-retry": "^2.0.0", + "tslib": "^2.5.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/middleware-serde": { + "version": "2.0.4", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/middleware-stack": { + "version": "2.0.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/node-config-provider": { + "version": "2.0.4", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^2.0.4", + "@smithy/shared-ini-file-loader": "^2.0.4", + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/node-http-handler": { + "version": "2.0.4", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^2.0.4", + "@smithy/protocol-http": "^2.0.4", + "@smithy/querystring-builder": "^2.0.4", + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/property-provider": { + "version": "2.0.4", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/protocol-http": { + "version": "2.0.4", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/querystring-builder": { + "version": "2.0.4", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.2.1", + "@smithy/util-uri-escape": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/querystring-parser": { + "version": "2.0.4", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/service-error-classification": { + "version": "2.0.0", + "license": "Apache-2.0", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/shared-ini-file-loader": { + "version": "2.0.4", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/signature-v4": { + "version": "2.0.4", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^2.0.4", + "@smithy/is-array-buffer": "^2.0.0", + "@smithy/types": "^2.2.1", + "@smithy/util-hex-encoding": "^2.0.0", + "@smithy/util-middleware": "^2.0.0", + "@smithy/util-uri-escape": "^2.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/smithy-client": { + "version": "2.0.4", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-stack": "^2.0.0", + "@smithy/types": "^2.2.1", + "@smithy/util-stream": "^2.0.4", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/types": { + "version": "2.2.1", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/url-parser": { + "version": "2.0.4", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^2.0.4", + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/util-base64": { + "version": "2.0.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/util-body-length-browser": { + "version": "2.0.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/util-body-length-node": { + "version": "2.0.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/util-buffer-from": { + "version": "2.0.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.0.0", "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-signing": { - "version": "3.370.0", + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/util-config-provider": { + "version": "2.0.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.370.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/protocol-http": "^1.1.0", - "@smithy/signature-v4": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/util-middleware": "^1.0.1", "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.370.0", + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/util-defaults-mode-browser": { + "version": "2.0.4", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.370.0", - "@aws-sdk/util-endpoints": "3.370.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", + "@smithy/property-provider": "^2.0.4", + "@smithy/types": "^2.2.1", + "bowser": "^2.11.0", "tslib": "^2.5.0" }, "engines": { - "node": ">=14.0.0" + "node": ">= 10.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/token-providers": { - "version": "3.370.0", + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/util-defaults-mode-node": { + "version": "2.0.4", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso-oidc": "3.370.0", - "@aws-sdk/types": "3.370.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", + "@smithy/config-resolver": "^2.0.4", + "@smithy/credential-provider-imds": "^2.0.4", + "@smithy/node-config-provider": "^2.0.4", + "@smithy/property-provider": "^2.0.4", + "@smithy/types": "^2.2.1", "tslib": "^2.5.0" }, "engines": { - "node": ">=14.0.0" + "node": ">= 10.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/types": { - "version": "3.370.0", + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/util-hex-encoding": { + "version": "2.0.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^1.1.0", "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-endpoints": { - "version": "3.370.0", + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/util-middleware": { + "version": "2.0.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.370.0", "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.370.0", + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/util-retry": { + "version": "2.0.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.370.0", - "@smithy/types": "^1.1.0", - "bowser": "^2.11.0", + "@smithy/service-error-classification": "^2.0.0", "tslib": "^2.5.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.370.0", + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/util-stream": { + "version": "2.0.4", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.370.0", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/types": "^1.1.0", + "@smithy/fetch-http-handler": "^2.0.4", + "@smithy/node-http-handler": "^2.0.4", + "@smithy/types": "^2.2.1", + "@smithy/util-base64": "^2.0.0", + "@smithy/util-buffer-from": "^2.0.0", + "@smithy/util-hex-encoding": "^2.0.0", + "@smithy/util-utf8": "^2.0.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/client-secrets-manager": { - "version": "3.360.0", + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/util-uri-escape": { + "version": "2.0.0", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.360.0", - "@aws-sdk/config-resolver": "3.357.0", - "@aws-sdk/credential-provider-node": "3.360.0", - "@aws-sdk/fetch-http-handler": "3.357.0", - "@aws-sdk/hash-node": "3.357.0", - "@aws-sdk/invalid-dependency": "3.357.0", - "@aws-sdk/middleware-content-length": "3.357.0", - "@aws-sdk/middleware-endpoint": "3.357.0", - "@aws-sdk/middleware-host-header": "3.357.0", - "@aws-sdk/middleware-logger": "3.357.0", - "@aws-sdk/middleware-recursion-detection": "3.357.0", - "@aws-sdk/middleware-retry": "3.357.0", - "@aws-sdk/middleware-serde": "3.357.0", - "@aws-sdk/middleware-signing": "3.357.0", - "@aws-sdk/middleware-stack": "3.357.0", - "@aws-sdk/middleware-user-agent": "3.357.0", - "@aws-sdk/node-config-provider": "3.357.0", - "@aws-sdk/node-http-handler": "3.360.0", - "@aws-sdk/smithy-client": "3.360.0", - "@aws-sdk/types": "3.357.0", - "@aws-sdk/url-parser": "3.357.0", - "@aws-sdk/util-base64": "3.310.0", - "@aws-sdk/util-body-length-browser": "3.310.0", - "@aws-sdk/util-body-length-node": "3.310.0", - "@aws-sdk/util-defaults-mode-browser": "3.360.0", - "@aws-sdk/util-defaults-mode-node": "3.360.0", - "@aws-sdk/util-endpoints": "3.357.0", - "@aws-sdk/util-retry": "3.357.0", - "@aws-sdk/util-user-agent-browser": "3.357.0", - "@aws-sdk/util-user-agent-node": "3.357.0", - "@aws-sdk/util-utf8": "3.310.0", - "@smithy/protocol-http": "^1.0.1", - "@smithy/types": "^1.0.0", - "tslib": "^2.5.0", - "uuid": "^8.3.2" + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-sqs": { - "version": "3.360.0", + "node_modules/@aws-sdk/client-sqs/node_modules/@smithy/util-utf8": { + "version": "2.0.0", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.360.0", - "@aws-sdk/config-resolver": "3.357.0", - "@aws-sdk/credential-provider-node": "3.360.0", - "@aws-sdk/fetch-http-handler": "3.357.0", - "@aws-sdk/hash-node": "3.357.0", - "@aws-sdk/invalid-dependency": "3.357.0", - "@aws-sdk/md5-js": "3.357.0", - "@aws-sdk/middleware-content-length": "3.357.0", - "@aws-sdk/middleware-endpoint": "3.357.0", - "@aws-sdk/middleware-host-header": "3.357.0", - "@aws-sdk/middleware-logger": "3.357.0", - "@aws-sdk/middleware-recursion-detection": "3.357.0", - "@aws-sdk/middleware-retry": "3.357.0", - "@aws-sdk/middleware-sdk-sqs": "3.357.0", - "@aws-sdk/middleware-serde": "3.357.0", - "@aws-sdk/middleware-signing": "3.357.0", - "@aws-sdk/middleware-stack": "3.357.0", - "@aws-sdk/middleware-user-agent": "3.357.0", - "@aws-sdk/node-config-provider": "3.357.0", - "@aws-sdk/node-http-handler": "3.360.0", - "@aws-sdk/smithy-client": "3.360.0", - "@aws-sdk/types": "3.357.0", - "@aws-sdk/url-parser": "3.357.0", - "@aws-sdk/util-base64": "3.310.0", - "@aws-sdk/util-body-length-browser": "3.310.0", - "@aws-sdk/util-body-length-node": "3.310.0", - "@aws-sdk/util-defaults-mode-browser": "3.360.0", - "@aws-sdk/util-defaults-mode-node": "3.360.0", - "@aws-sdk/util-endpoints": "3.357.0", - "@aws-sdk/util-retry": "3.357.0", - "@aws-sdk/util-user-agent-browser": "3.357.0", - "@aws-sdk/util-user-agent-node": "3.357.0", - "@aws-sdk/util-utf8": "3.310.0", - "@smithy/protocol-http": "^1.0.1", - "@smithy/types": "^1.0.0", - "fast-xml-parser": "4.2.5", + "@smithy/util-buffer-from": "^2.0.0", "tslib": "^2.5.0" }, "engines": { @@ -3012,15 +3828,6 @@ "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/md5-js": { - "version": "3.357.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-utf8": "3.310.0", - "tslib": "^2.5.0" - } - }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { "version": "3.370.0", "license": "Apache-2.0", @@ -3271,12 +4078,76 @@ } }, "node_modules/@aws-sdk/middleware-sdk-sqs": { - "version": "3.357.0", + "version": "3.391.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.391.0", + "@smithy/types": "^2.2.0", + "@smithy/util-hex-encoding": "^2.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-sqs/node_modules/@aws-sdk/types": { + "version": "3.391.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-sqs/node_modules/@smithy/is-array-buffer": { + "version": "2.0.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-sqs/node_modules/@smithy/types": { + "version": "2.2.1", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-hex-encoding": "3.310.0", - "@aws-sdk/util-utf8": "3.310.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-sqs/node_modules/@smithy/util-buffer-from": { + "version": "2.0.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-sqs/node_modules/@smithy/util-hex-encoding": { + "version": "2.0.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-sqs/node_modules/@smithy/util-utf8": { + "version": "2.0.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.0.0", "tslib": "^2.5.0" }, "engines": { @@ -4282,9 +5153,8 @@ }, "node_modules/@eslint/eslintrc": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -4305,9 +5175,8 @@ }, "node_modules/@eslint/eslintrc/node_modules/ajv": { "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4321,15 +5190,13 @@ }, "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@eslint/js": { "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.47.0.tgz", - "integrity": "sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==", "dev": true, + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -7980,9 +8847,8 @@ }, "node_modules/acorn": { "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -7992,9 +8858,8 @@ }, "node_modules/acorn-jsx": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -8276,9 +9141,8 @@ }, "node_modules/aws-sdk-client-mock": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aws-sdk-client-mock/-/aws-sdk-client-mock-3.0.0.tgz", - "integrity": "sha512-4mBiWhuLYLZe1+K/iB8eYy5SAZyW2se+Keyh5u9QouMt6/qJ5SRZhss68xvUX5g3ApzROJ06QPRziYHP6buuvQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/sinon": "^10.0.10", "sinon": "^14.0.2", @@ -9361,9 +10225,8 @@ }, "node_modules/eslint": { "version": "8.47.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.47.0.tgz", - "integrity": "sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -9573,9 +10436,8 @@ }, "node_modules/eslint-visitor-keys": { "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -9605,9 +10467,8 @@ }, "node_modules/espree": { "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -10178,9 +11039,8 @@ }, "node_modules/globals": { "version": "13.21.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", - "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -14234,9 +15094,8 @@ }, "node_modules/type-fest": { "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -14669,7 +15528,7 @@ }, "packages/blob-storage": { "name": "@adobe/contentlake-shared-blob-storage", - "version": "1.2.0", + "version": "1.2.1", "license": "Apache-2.0", "dependencies": { "@aws-sdk/client-s3": "^3.373.0", @@ -14680,19 +15539,6 @@ "dotenv": "^16.3.1" } }, - "packages/custom-secrets": { - "name": "@adobe/contentlake-shared-custom-secrets", - "version": "1.0.0", - "extraneous": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-secrets-manager": "^3.290.0" - }, - "devDependencies": { - "aws-sdk-client-mock": "^3.0.0", - "dotenv": "^16.3.1" - } - }, "packages/extractor-helpers": { "name": "@adobe/contentlake-shared-extractor-helpers", "version": "1.4.0", @@ -14746,12 +15592,27 @@ "@adobe/helix-shared-wrap": "^2.0.0" } }, + "packages/queue-client": { + "name": "@adobe/contentlake-shared-queue-client", + "version": "1.2.0", + "license": "Apache-2.0", + "dependencies": { + "@adobe/contentlake-shared-blob-storage": "^1.2.0", + "@aws-sdk/client-sqs": "^3.391.0" + }, + "devDependencies": { + "aws-sdk-client-mock": "^3.0.0", + "dotenv": "^16.3.1" + } + }, "packages/request-handler": { "name": "@adobe/contentlake-shared-request-handler", "version": "1.0.0", "license": "Apache-2.0", "dependencies": { - "@adobe/content-lake-commons": "^1.8.1", + "@adobe/contentlake-shared-blob-storage": "^1.2.1", + "@adobe/contentlake-shared-queue-client": "^1.2.0", + "@adobe/contentlake-shared-rest-error": "^1.0.0", "@adobe/helix-shared-wrap": "^2.0.0", "@adobe/helix-status": "^10.0.9", "@adobe/helix-universal-logger": "^3.0.10", diff --git a/packages/queue-client/.nycrc.json b/packages/queue-client/.nycrc.json new file mode 100644 index 0000000..ecafcea --- /dev/null +++ b/packages/queue-client/.nycrc.json @@ -0,0 +1,11 @@ +{ + "reporter": [ + "lcov", + "text" + ], + "exclude": ["it/*", "test/*", "src/mock*", "scripts/*"], + "check-coverage": true, + "lines": 80, + "branches": 80, + "statements": 80 +} diff --git a/packages/queue-client/.prettierrc b/packages/queue-client/.prettierrc new file mode 100644 index 0000000..56d4876 --- /dev/null +++ b/packages/queue-client/.prettierrc @@ -0,0 +1,6 @@ +{ + "tabWidth": 2, + "useTabs": false, + "singleQuote": true, + "trailingComma": "all" +} \ No newline at end of file diff --git a/packages/queue-client/LICENSE.txt b/packages/queue-client/LICENSE.txt new file mode 100644 index 0000000..883ab09 --- /dev/null +++ b/packages/queue-client/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/queue-client/README.md b/packages/queue-client/README.md new file mode 100644 index 0000000..3783633 --- /dev/null +++ b/packages/queue-client/README.md @@ -0,0 +1,26 @@ +# Content Lake Shared - Queue Client + +> Queue library for Asset Catalog + +This is one of the [Content Lake Shared](https://github.com/adobe/contentlake-shared) libraries. + +## Status + +[![GitHub license](https://img.shields.io/github/license/adobe/contentlake-shared.svg)](https://github.com/adobe/contentlake-shared/blob/main/LICENSE.txt) + +## Usage + +Install using: + +``` +npm install @adobe/contentlake-shared-queue-client +``` + +``` +import { QueueClient } from '@adobe/contentlake-shared-queue-client' + +const queueClient = new QueueClient({ + queueUrl: 'https://somequeue.com' +}); +await queueClient.sendMessage({ message: 'Hello World' }); +``` diff --git a/packages/queue-client/it/index.test.js b/packages/queue-client/it/index.test.js new file mode 100644 index 0000000..d4ccd42 --- /dev/null +++ b/packages/queue-client/it/index.test.js @@ -0,0 +1,141 @@ +/* + * 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 assert from 'assert'; +import dotenv from 'dotenv'; +import { + PurgeQueueCommand, + ReceiveMessageCommand, + SendMessageCommand, +} from '@aws-sdk/client-sqs'; +import util from 'util'; +import { BlobStorage } from '@adobe/contentlake-shared-blob-storage'; +import { QueueClient } from '../src/index.js'; + +dotenv.config(); + +const sleep = util.promisify(setTimeout); + +const DEFAULT_CONFIG = { + log: console, + queueUrl: process.env.QUEUE_URL, +}; + +const SLOW_TEST_TIMEOUT = 5000; + +function generateLargeMessage() { + const message = []; + for (let i = 0; i < 4086; i += 1) { + message.push( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse nec nisl massa. Morbi at erat elementum, dignissim quam at.', + ); + } + return message; +} + +describe('Queue Integration Tests', () => { + it('will use default logger', async () => { + const queueClient = new QueueClient({ + ...DEFAULT_CONFIG, + log: undefined, + }); + const id = await queueClient.sendMessage({ message: 'Hello World' }); + assert.ok(id); + }); + + describe('send message', () => { + it('can send message', async () => { + const queueClient = new QueueClient(DEFAULT_CONFIG); + const id = await queueClient.sendMessage({ message: 'Hello World' }); + assert.ok(id); + }); + + it('will fail with invalid queue', async () => { + const queueClient = new QueueClient({ + ...DEFAULT_CONFIG, + queueUrl: 'http://notaqueue.com', + }); + await assert.rejects(() => queueClient.sendMessage({ message: 'Hello World' })); + }); + + it('will fail on oversized message with no blob storage', async () => { + const queueClient = new QueueClient(DEFAULT_CONFIG); + await assert.rejects(() => queueClient.sendMessage(generateLargeMessage())); + }).timeout(SLOW_TEST_TIMEOUT); + + it('can send oversized message with blob storage', async () => { + const queueClient = new QueueClient({ + ...DEFAULT_CONFIG, + blobStorage: new BlobStorage({ + ...DEFAULT_CONFIG, + bucket: 'cl-commons-it-files', + }), + }); + const messageId = await queueClient.sendMessage(generateLargeMessage()); + assert.ok(messageId); + }).timeout(SLOW_TEST_TIMEOUT); + + after(async () => { + const queueClient = new QueueClient(DEFAULT_CONFIG); + await queueClient.client + .send( + new PurgeQueueCommand({ + QueueUrl: process.env.QUEUE_URL, + }), + ) + .catch(() => {}); + }); + }); + + describe('remove message', () => { + it('will fail on invalid recieptHandle', async () => { + const queueClient = new QueueClient(DEFAULT_CONFIG); + await assert.rejects(() => queueClient.removeMessage('notvalid')); + }); + + it('can remove message by recieptHandle', async () => { + const queueClient = new QueueClient(DEFAULT_CONFIG); + await queueClient.client.send( + new SendMessageCommand({ + QueueUrl: process.env.QUEUE_URL, + MessageBody: JSON.stringify({ message: 'Hello world' }), + }), + ); + await sleep(2000); + const messages = await queueClient.client.send( + new ReceiveMessageCommand({ + QueueUrl: process.env.QUEUE_URL, + }), + ); + const { ReceiptHandle } = messages.Messages[0]; + assert.ok(ReceiptHandle); + await queueClient.removeMessage(ReceiptHandle); + }).timeout(SLOW_TEST_TIMEOUT); + }); + + after(async () => { + const queueClient = new QueueClient(DEFAULT_CONFIG); + const messages = await queueClient.client.send( + new ReceiveMessageCommand({ + QueueUrl: process.env.QUEUE_URL, + }), + ); + Promise.all( + messages.Messages?.map(async (msg) => { + const { ReceiptHandle } = msg; + assert.ok(ReceiptHandle); + await queueClient.removeMessage(ReceiptHandle); + }), + ); + }); +}); diff --git a/packages/queue-client/package.json b/packages/queue-client/package.json new file mode 100644 index 0000000..c637de7 --- /dev/null +++ b/packages/queue-client/package.json @@ -0,0 +1,38 @@ +{ + "name": "@adobe/contentlake-shared-queue-client", + "version": "1.2.0", + "description": "Queue library for Asset Catalog", + "main": "src/index.js", + "type": "module", + "scripts": { + "test": "c8 mocha test", + "test:integration": "mocha it", + "lint": "eslint .", + "clean": "rm -rf package-lock.json node_modules" + }, + "mocha": { + "reporter": "mocha-multi-reporters", + "reporter-options": "configFile=../../.mocha-multi.json" + }, + "repository": { + "type": "git", + "url": "https://github.com/adobe/contentlake-shared" + }, + "author": "", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/adobe/contentlake-shared/issues" + }, + "homepage": "https://github.com/adobe/contentlake-shared/tree/main/packages/queue-client#readme", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@adobe/contentlake-shared-blob-storage": "^1.2.0", + "@aws-sdk/client-sqs": "^3.391.0" + }, + "devDependencies": { + "aws-sdk-client-mock": "^3.0.0", + "dotenv": "^16.3.1" + } +} diff --git a/packages/queue-client/src/index.js b/packages/queue-client/src/index.js new file mode 100644 index 0000000..c9b0dbb --- /dev/null +++ b/packages/queue-client/src/index.js @@ -0,0 +1,209 @@ +/* + * 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 { + DeleteMessageCommand, + SQSClient, + SendMessageCommand, +} from '@aws-sdk/client-sqs'; +import { randomUUID } from 'crypto'; + +export * from './mock.js'; + +/** + * @typedef QueueConfig + * @property {import('@adobe/contentlake-shared-blob-storage').BlobStorage} [blobStorage] + * @property {SQSClient} [client] + * @property {*} [log] + * @property {string} queueUrl + */ + +/** + * @typedef {Object} QueueRecord + * @property {string} messageId + * @property {string} receiptHandle + * @property {string} body + * @property {Record} attributes + * @property {Record} messageAttributes + * @property {string} eventSource + */ + +const MAX_MESSAGE_LEN = 127000; // 127 KB + +export class QueueClient { + /** + * @type {import('@adobe/contentlake-shared-blob-storage').BlobStorage} + */ + #blobStorage; + + /** + * @type {SQSClient} + */ + client; + + #log; + + /** + * @type {string} + */ + #queueUrl; + + /** + * Check whether or not the context contains Records from a queue + * @param {import('@adobe/helix-universal').UniversalContext} context + * @returns {boolean} + */ + static isQueueRequest(context) { + return typeof context.invocation?.event?.Records !== 'undefined'; + } + + /** + * Gets the queue records from the context + * @param {import('@adobe/helix-universal').UniversalContext} context + * @returns {Array} the queue records + */ + static extractQueueRecords(context) { + return context.invocation?.event?.Records || []; + } + + /** + * @param {QueueConfig} config + */ + constructor(config) { + this.#log = config.log || console; + this.client = config.client || new SQSClient(); + this.#blobStorage = config.blobStorage; + this.#queueUrl = config.queueUrl; + } + + /** + * Serializes the specified message to a string, saving to blob storage + * if the message exceeds the max size + * @param {Object} message the message to serialize + * @returns {Promise} the message serialized as a string + */ + async #serializeMessage(message) { + const json = JSON.stringify(message); + const size = Buffer.byteLength(json); + if (size < MAX_MESSAGE_LEN) { + this.#log.debug('Message size is below max size, returning JSON', { + size, + }); + return json; + } else if (this.#blobStorage) { + const key = randomUUID(); + this.#log.debug( + 'Message size is above max size, saving to blob storage', + { + size, + key, + }, + ); + await this.#blobStorage.save(key, Buffer.from(json)); + return JSON.stringify({ blobStorage: true, key }); + } else { + this.#log.error( + 'Message size exceeds maximum and no blob storage provided', + { size }, + ); + throw new Error( + `Message size ${size} exceeds maximum and no blob storage provided`, + ); + } + } + + /** + * Reads the message, reading from blob storage if required + * @param {string} messageBody + * @returns {Promise} + */ + async readMessageBody(messageBody) { + const json = JSON.parse(messageBody); + if (!json.blobStorage) { + this.#log.debug('Message does not use blob storage'); + return json; + } else if (this.#blobStorage) { + const { key } = json; + this.#log.debug('Retrieving message from blob storage', { key }); + return JSON.parse(await this.#blobStorage.getString(key)); + } else { + throw new Error( + 'Message stored in blob storage, but no blob storage client provided', + ); + } + } + + /** + * Sends the specified message to the queue + * @param {Object} message the message to send + * @returns {Promise} the message id + */ + async sendMessage(message) { + try { + this.#log.debug('Enqueuing message', { + message, + queueUrl: this.#queueUrl, + }); + const messageBody = await this.#serializeMessage(message); + const res = await this.client.send( + new SendMessageCommand({ + QueueUrl: this.#queueUrl, + MessageBody: messageBody, + }), + ); + const { MessageId } = res; + this.#log.debug('Message enqueued successfully', { + message, + queueUrl: this.#queueUrl, + MessageId, + }); + return MessageId; + } catch (err) { + this.#log.error('Failed to queue message', { + err, + message, + queueUrl: this.#queueUrl, + }); + throw err; + } + } + + /** + * Removes a message by it's receipt handle + * @param {string} receiptHandle + */ + async removeMessage(receiptHandle) { + const queueUrl = this.#queueUrl; + try { + this.#log.debug('Removing message', { + receiptHandle, + queueUrl, + }); + await this.client.send( + new DeleteMessageCommand({ + QueueUrl: this.#queueUrl, + ReceiptHandle: receiptHandle, + }), + ); + this.#log.debug('Message removed successfully', { + receiptHandle, + queueUrl, + }); + } catch (err) { + this.#log.error('Failed to remove message', { + err, + receiptHandle, + queueUrl, + }); + throw err; + } + } +} diff --git a/packages/queue-client/src/mock.js b/packages/queue-client/src/mock.js new file mode 100644 index 0000000..e8097d2 --- /dev/null +++ b/packages/queue-client/src/mock.js @@ -0,0 +1,40 @@ +/* + * 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. + */ +export class MockQueueClient { + messages = []; + + removed = []; + + async sendMessage(message) { + const id = this.messages.push(message); + return id.toString(); + } + + /** + * Reads the message, reading from blob storage if required + * @param {string} messageBody + * @returns {Promise} + */ + // eslint-disable-next-line class-methods-use-this + async readMessageBody(messageBody) { + return JSON.parse(messageBody); + } + + async removeMessage(receiptHandle) { + this.removed.push(receiptHandle); + } + + reset() { + this.messages = []; + this.removed = []; + } +} diff --git a/packages/queue-client/test/index.test.js b/packages/queue-client/test/index.test.js new file mode 100644 index 0000000..93c6073 --- /dev/null +++ b/packages/queue-client/test/index.test.js @@ -0,0 +1,239 @@ +/* + * 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-env mocha */ +import assert from 'assert'; +import { mockClient } from 'aws-sdk-client-mock'; +import { + DeleteMessageCommand, + SQSClient, + SendMessageCommand, +} from '@aws-sdk/client-sqs'; +import { QueueClient } from '../src/index.js'; + +const mockSqsClient = mockClient(SQSClient); + +const LARGE_MESSAGE = { + // generates a 128KB string by joining the alphabet 5120 times + msg: [...Array(5120)].map(() => 'abcdefghijklmnopqrstuvwxyz').join(''), +}; + +describe('QueueClient Unit Tests', () => { + afterEach(() => mockSqsClient.reset()); + + it('can get client', () => { + const client = new QueueClient({}); + assert.ok(client); + }); + + it('can send message', async () => { + mockSqsClient.on(SendMessageCommand).callsFake((input) => { + assert.deepStrictEqual(input, { + MessageBody: '{"message":"Hello World!"}', + QueueUrl: 'http://www.queue.com', + }); + return { + MessageId: 'test-id', + }; + }); + const client = new QueueClient({ + client: mockSqsClient, + queueUrl: 'http://www.queue.com', + }); + const resp = await client.sendMessage({ message: 'Hello World!' }); + assert.strictEqual(resp, 'test-id'); + }); + + it('throws on create message failure', async () => { + mockSqsClient.on(SendMessageCommand).rejects(); + const client = new QueueClient({ + client: mockSqsClient, + queueUrl: 'http://www.queue.com', + }); + let caught; + try { + await client.sendMessage({ message: 'Hello World!' }); + } catch (err) { + caught = err; + } + assert.ok(caught); + }); + + it('can remove message', async () => { + mockSqsClient.on(DeleteMessageCommand).callsFake((input) => { + assert.strictEqual(input.ReceiptHandle, 'test-handle'); + }); + const client = new QueueClient({ + client: mockSqsClient, + queueUrl: 'http://www.queue.com', + }); + await client.removeMessage('test-handle'); + }); + + it('remove handles throws', async () => { + mockSqsClient.on(DeleteMessageCommand).rejects(); + const client = new QueueClient({ + client: mockSqsClient, + queueUrl: 'http://www.queue.com', + }); + await assert.rejects(() => client.removeMessage('test-handle')); + }); + + it('fails on large message if no blob storage provided', async () => { + const client = new QueueClient({ + client: mockSqsClient, + queueUrl: 'http://www.queue.com', + }); + + await assert.rejects(() => client.sendMessage(LARGE_MESSAGE)); + }); + + it('can send large messages', async () => { + const blobs = {}; + const mockBlobStorage = { + save: (key, buf) => { + blobs[key] = buf.toString('utf-8'); + }, + }; + mockSqsClient.on(SendMessageCommand).callsFake((input) => { + const { blobStorage, key } = JSON.parse(input.MessageBody); + assert.strictEqual(blobStorage, true); + assert.strictEqual(blobs[key].message, LARGE_MESSAGE.message); + assert.ok(blobs[key]); + return { + MessageId: 'test-message-id', + }; + }); + + const client = new QueueClient({ + client: mockSqsClient, + queueUrl: 'http://www.queue.com', + blobStorage: mockBlobStorage, + }); + + const id = await client.sendMessage(LARGE_MESSAGE); + assert.strictEqual(id, 'test-message-id'); + }); + + it('can read large messages', async () => { + const blobs = { test: JSON.stringify(LARGE_MESSAGE) }; + const mockBlobStorage = { + getString: (key) => blobs[key], + }; + const client = new QueueClient({ + queueUrl: 'http://www.queue.com', + blobStorage: mockBlobStorage, + }); + + const messageBody = await client.readMessageBody( + JSON.stringify({ + blobStorage: true, + key: 'test', + }), + ); + assert.strictEqual(messageBody.message, LARGE_MESSAGE.message); + }); + + it('can read normal sized messages', async () => { + const client = new QueueClient({ + queueUrl: 'http://www.queue.com', + }); + + const messageBody = await client.readMessageBody( + JSON.stringify({ + message: 'Hello World!', + }), + ); + assert.strictEqual(messageBody.message, 'Hello World!'); + }); + + it('will fail to read large message if no blob storage provided', async () => { + const client = new QueueClient({ + client: mockSqsClient, + queueUrl: 'http://www.queue.com', + }); + + await assert.rejects(() => client.readMessageBody( + JSON.stringify({ + blobStorage: true, + key: 'test', + }), + )); + }); + + describe('isQueueRequest', () => { + it('will not fail if undefined', () => { + assert.ok(!QueueClient.isQueueRequest({})); + }); + + it('will not fail if not present', () => { + assert.ok( + !QueueClient.isQueueRequest({ + invocation: { event: {} }, + }), + ); + }); + + it('will return true on empty records array', () => { + assert.ok( + QueueClient.isQueueRequest({ + invocation: { + event: { + Records: [], + }, + }, + }), + ); + }); + }); + + describe('get sqs records', () => { + it('will not fail if not present', () => { + assert.ok(QueueClient.extractQueueRecords({})); + }); + + it('will extract records', () => { + const res = QueueClient.extractQueueRecords({ + invocation: { + event: { + Records: [ + { + messageId: 'af88e691-c3a6-4b46-b4d2-1c897b41b600', + receiptHandle: + 'AQEBJCLTpWgDm+oaeBAlSKWumzIoFRHeJglHCwWEfJANgc7GSWQBcYTiLPfbO1IuxAIkJagUIEkqgmszqnj2a7hLZjoIcv0AWCQfL0tmje/hhnDWYKdQmrUmfITdPDIg49XI+n+Ub/gKjXEy3VvunLsp0bxuF33OCsR8+N0Skff+U+zan+42GcHtn8lacm6ZQIF9msoFxszourA+zpJ/DJ1DTMlEpr9cSPxa6nsbg7JHOOwBzWknn7d3Zkimuo/J3shMyb+4fBYFRNpzXt9o9l8rfQpi9JZDwGIFRqDYFvpI0Emqv9ke1V2uBAJPiiGS0h1MIKO6dZZ/ejfWAR0Rug3zMEH9SEa6N+hT4gF5Pu2IN6WmcRhE4sh0jW/ImAAunuIo/OZ1FhNjqp+keK3AvBiPiQ==', + body: '{"message":"Hello World"}', + attributes: { + ApproximateReceiveCount: '1', + SentTimestamp: '1678764328689', + SenderId: 'AIDAXXYBVS2FJDJXJ56HK', + ApproximateFirstReceiveTimestamp: '1678764328690', + }, + messageAttributes: {}, + md5OfBody: 'd7e5fb40d1b43e304158449c3ecd6e5c', + eventSource: 'aws:sqs', + eventSourceARN: + 'arn:aws:sqs:us-east-1:532042585738:content-lake-it', + awsRegion: 'us-east-1', + }, + ], + }, + }, + }); + assert.strictEqual(res.length, 1); + assert.strictEqual( + res[0].messageId, + 'af88e691-c3a6-4b46-b4d2-1c897b41b600', + ); + }); + }); +}); diff --git a/packages/request-handler/package.json b/packages/request-handler/package.json index 3e0e28c..6e9fb1e 100644 --- a/packages/request-handler/package.json +++ b/packages/request-handler/package.json @@ -26,7 +26,9 @@ "access": "public" }, "dependencies": { - "@adobe/content-lake-commons": "^1.8.1", + "@adobe/contentlake-shared-blob-storage": "^1.2.1", + "@adobe/contentlake-shared-queue-client": "^1.2.0", + "@adobe/contentlake-shared-rest-error": "^1.0.0", "@adobe/helix-shared-wrap": "^2.0.0", "@adobe/helix-status": "^10.0.9", "@adobe/helix-universal-logger": "^3.0.10", diff --git a/packages/request-handler/src/index.js b/packages/request-handler/src/index.js index e076333..a32c5de 100644 --- a/packages/request-handler/src/index.js +++ b/packages/request-handler/src/index.js @@ -10,21 +10,16 @@ * governing permissions and limitations under the License. */ -import { - BlobStorage, - ContextHelper, - QueueClient, - RestError, -} from '@adobe/content-lake-commons'; +import { RestError } from '@adobe/contentlake-shared-rest-error'; import wrap from '@adobe/helix-shared-wrap'; import { helixStatus } from '@adobe/helix-status'; import { logger } from '@adobe/helix-universal-logger'; -import { Response } from 'node-fetch'; +import { RequestHandlerInternal } from './internal.js'; /** * @callback HandlerFn * @param {Record} event the event to handle - * @param {import('@adobe/content-lake-commons').contextHelper.UniversalishContext} context + * @param {import('@adobe/helix-universal').UniversalContext} context * the current context * @returns {Promise} the response from handling the request */ @@ -41,7 +36,7 @@ export class RequestHandler { #wrapFn; /** - * @type {HandlerFn} + * @type {Record} */ #handlers = {}; @@ -61,30 +56,7 @@ export class RequestHandler { * @returns {function(Request,UniversalContext):Promise} the main function */ getMain() { - let main = wrap(async (request, context) => { - const helper = new ContextHelper(context); - const log = helper.getLog(); - let res; - const { method, url } = request; - const loggableRequest = { - method, - url, - headers: Object.fromEntries(request.headers), - invocation: context?.invocation, - event: helper.extractOriginalEvent(), - }; - const start = Date.now(); - try { - log.debug(`> ${method} ${url}`); - log.debug('Handling request', loggableRequest); - res = await this.handleRequest(context); - } catch (err) { - log.warn('Exception handling request', { ...loggableRequest, err }); - res = RestError.toProblemResponse(err); - } - log.info(`< ${method} ${res.status} ${url} ${Date.now() - start}ms`); - return res; - }); + let main = wrap(async (request, context) => this.main(request, context)); if (this.#wrapFn) { main = main.with(this.#wrapFn); } @@ -92,126 +64,35 @@ export class RequestHandler { } /** - * Get the queue client for the specified request - * @param {any} context Context for the current execution is running. - * @returns {QueueClient} the queue client + * @param {Request} request + * @param {import('@adobe/helix-universal').UniversalContext} context + * @returns {Promise} */ - // eslint-disable-next-line class-methods-use-this - getQueueClient(context) { - const helper = new ContextHelper(context); - const queueUrl = context.env.QUEUE_URL; - if (!queueUrl) { - throw new Error('Missing ENV variable QUEUE_URL'); - } - let blobStorage; - const queueStorageBucket = context.env.QUEUE_STORAGE_BUCKET; - if (queueStorageBucket) { - blobStorage = new BlobStorage({ - ...helper.extractAwsConfig(context), - bucket: queueStorageBucket, - }); - } - return new QueueClient({ - ...helper.extractAwsConfig(context), - queueUrl, - blobStorage, - }); - } - - /** - * Handles a request that comes into an extractor's HTTP service. - * @param {any} context Context for the current execution is running. - * @returns {Promise} Resolves with the response that the service - * will provide - */ - async handleRequest(context) { - const helper = new ContextHelper(context); - const log = helper.getLog(); - - if (helper.isQueueRequest()) { - const queueClient = this.getQueueClient(context); - const records = helper.extractQueueRecords(); - log.debug('Handing queue records', { count: records.length }); - - const results = await Promise.allSettled( - records.map((qr) => this.handleQueueRecord(context, qr, queueClient, log)), - ); - - const batchItemFailures = results - .filter((r) => r.status === 'rejected') - .map((fail) => fail.reason) - .map((itemIdentifier) => ({ itemIdentifier })); - if (batchItemFailures.length === 0) { - log.debug('All batch items processed successfully'); - return new Response(); - } else { - log.info('Failed to process batch items', { - items: batchItemFailures, - count: batchItemFailures.length, - }); - return new Response( - JSON.stringify({ - batchItemFailures, - }), - { status: 206 }, - ); - } - } else { - log.debug('Handing POST payload'); - const event = helper.extractOriginalEvent(); - return this.handleEvent(event, context); - } - } - - /** - * Handles a single event - * @param {Record} event the event to handle - * @param {import('@adobe/content-lake-commons').contextHelper.UniversalishContext} context - * the current context - * @returns {Promise} the response from handling the event - */ - async handleEvent(event, context) { - const { action } = event || {}; - if (!action) { - throw new RestError(400, 'Missing parameter [action]'); - } - if (!this.#handlers[action]) { - throw new RestError(400, `Invalid action [${action}]`); - } - return this.#handlers[action](event, context); - } - - /** - * Handles a queue event record - * @param {import('@adobe/content-lake-commons').contextHelper.UniversalishContext} context - * @param {import('@adobe/content-lake-commons').QueueRecord} record - * @param {QueueClient} queueClient - * @param {import('@adobe/content-lake-commons').contextHelper.Logger} log - * @returns {Promise} - */ - async handleQueueRecord(context, record, queueClient, log) { + async main(request, context) { + const log = context.log || console; + let res; + const { method, url } = request; + const loggableRequest = { + method, + url, + headers: Object.fromEntries(request.headers), + invocation: context?.invocation, + }; + const start = Date.now(); try { - log.debug('Handling queue record', { - record, - }); - const event = await queueClient.readMessageBody(record.body); - const res = await this.handleEvent(event, context); - if (res.ok) { - log.debug('Record handled successfully, removing from queue', { - record, - }); - await queueClient.removeMessage(record.receiptHandle); - } else { - log.warn('Record handling unsuccessful', { - record, - res, - }); - throw record.messageId; - } + log.debug(`> ${method} ${url}`); + log.debug('Handling request', loggableRequest); + const internalHandler = new RequestHandlerInternal( + context, + this.#handlers, + ); + res = await internalHandler.handle(); } catch (err) { - log.warn('Failed to handle queue record', { record, err }); - throw record.messageId; + log.warn('Exception handling request', { ...loggableRequest, err }); + res = RestError.toProblemResponse(err, context); } + log.info(`< ${method} ${res.status} ${url} ${Date.now() - start}ms`); + return res; } /** diff --git a/packages/request-handler/src/internal.js b/packages/request-handler/src/internal.js new file mode 100644 index 0000000..a370cd3 --- /dev/null +++ b/packages/request-handler/src/internal.js @@ -0,0 +1,169 @@ +/* + * 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 { RestError } from '@adobe/contentlake-shared-rest-error'; +import { BlobStorage } from '@adobe/contentlake-shared-blob-storage'; +import { QueueClient } from '@adobe/contentlake-shared-queue-client'; +import { Response } from 'node-fetch'; + +/** + * Internal class for handling requests + */ +export class RequestHandlerInternal { + #context; + + #log; + + /** + * @type {Record} + */ + #handlers = {}; + + /** + * @type {QueueClient} + */ + #queueClient; + + /** + * @param {import('@adobe/helix-universal').UniversalContext} context + * @param {Record} handlers + */ + constructor(context, handlers) { + this.#context = context; + this.#handlers = handlers; + this.#log = context.log || console; + + if (context.queueClient) { + this.#queueClient = context.queueClient; + } else { + const queueUrl = context.env.QUEUE_URL; + let blobStorage; + const queueStorageBucket = context.env.QUEUE_STORAGE_BUCKET; + if (queueStorageBucket) { + blobStorage = new BlobStorage({ + bucket: queueStorageBucket, + }); + } + this.queueClient = new QueueClient({ + queueUrl, + blobStorage, + }); + } + } + + /** + * @param {Response} res + */ + async #serializeResponse(res) { + let body; + try { + body = await res.text(); + } catch (err) { + this.#log.warn('Failed to retrieve body', err); + } + return { + body, + status: res.status, + headers: Object.fromEntries(res.headers), + }; + } + + /** + * Handles a single event + * @param {Record} event the event to handle + * @returns {Promise} the response from handling the event + */ + async #handleEvent(event) { + const { action } = event || {}; + if (!action) { + throw new RestError(400, 'Missing parameter [action]'); + } + if (!this.#handlers[action]) { + throw new RestError(400, `Invalid action [${action}]`); + } + return this.#handlers[action](event, this.#context); + } + + /** + * Handles queue records + * @returns {Promise} + */ + async #handleQueueRecords() { + const records = QueueClient.extractQueueRecords(this.#context); + this.#log.debug('Handing queue records', { count: records.length }); + + const results = await Promise.allSettled( + records.map(async (record) => { + let res; + try { + this.#log.debug('Handling queue record', { + record, + }); + const event = await this.#queueClient.readMessageBody(record.body); + res = await this.#handleEvent(event, this.#context); + } catch (err) { + this.#log.warn('Failed to handle queue record', { record, err }); + throw record.messageId; + } + if (res.ok) { + this.#log.debug('Record handled successfully, removing from queue', { + record, + }); + await this.#queueClient.removeMessage(record.receiptHandle); + } else { + const serialized = this.#serializeResponse(res); + this.#log.warn('Record handling unsuccessful', { + record, + response: serialized, + }); + throw record.messageId; + } + }), + ); + + const batchItemFailures = results + .filter((r) => r.status === 'rejected') + .map((fail) => fail.reason) + .map((itemIdentifier) => ({ itemIdentifier })); + if (batchItemFailures.length === 0) { + this.#log.debug('All batch items processed successfully'); + return new Response(); + } else { + this.#log.info('Failed to process batch items', { + total: results.length, + failures: batchItemFailures, + failure_count: batchItemFailures.length, + }); + return new Response( + JSON.stringify({ + batchItemFailures, + }), + { status: 206 }, + ); + } + } + + /** + * Handle the request + * @returns {Promise} + */ + async handle() { + const { event } = this.#context.invocation; + this.#log.debug('Handling event', event); + if (QueueClient.isQueueRequest(this.#context)) { + return this.#handleQueueRecords(); + } else { + this.#log.debug('Handing POST payload'); + return this.#handleEvent(event); + } + } +} diff --git a/packages/request-handler/test/index.test.js b/packages/request-handler/test/index.test.js index dda1b6d..c4b87a7 100644 --- a/packages/request-handler/test/index.test.js +++ b/packages/request-handler/test/index.test.js @@ -14,9 +14,10 @@ /* eslint-disable class-methods-use-this */ import { createHash, randomUUID } from 'crypto'; import assert from 'assert'; -import { stub } from 'sinon'; +import { MockQueueClient } from '@adobe/contentlake-shared-queue-client'; import { RequestHandler } from '../src/index.js'; +const mockQueueClient = new MockQueueClient(); const MOCK_REQUEST = new Request('http://localhost', { method: 'POST' }); function mockContext(event) { return { @@ -27,6 +28,7 @@ function mockContext(event) { QUEUE_URL: 'http://testqueue.com', QUEUE_STORAGE_BUCKET: 'queue-test', }, + queueClient: mockQueueClient, }; } @@ -48,6 +50,7 @@ function mockSqsRecord(action) { } describe('Request Handler Tests', () => { + afterEach(() => mockQueueClient.reset()); describe('main', () => { it('can get main', () => { const requestHandler = new RequestHandler(); @@ -57,9 +60,9 @@ describe('Request Handler Tests', () => { it('main requires context / request', async () => { const requestHandler = new RequestHandler(); - const main = requestHandler.getMain(); let caught; try { + const main = requestHandler.getMain(); await main(undefined, undefined); } catch (err) { caught = err; @@ -72,7 +75,10 @@ describe('Request Handler Tests', () => { throw new Error('Surprise!'); }); const main = requestHandler.getMain(); - const res = await main(MOCK_REQUEST, mockContext({ action: 'throw' })); + const res = await main(MOCK_REQUEST, { + ...mockContext({ action: 'throw' }), + queueClient: undefined, + }); assert.strictEqual(res.status, 500); }); @@ -94,19 +100,13 @@ describe('Request Handler Tests', () => { 'test', () => new Response(), ); - let caught; - try { - await requestHandler.handleEvent( - { - payload: 'Hello World', - }, - mockContext(), - ); - } catch (err) { - caught = err; - } - assert.ok(caught); - assert.strictEqual(caught.status, 400); + const res = await requestHandler.main( + MOCK_REQUEST, + mockContext({ + payload: 'Hello World', + }), + ); + assert.strictEqual(res.status, 400); }); it('will throw on unknown action', async () => { @@ -114,14 +114,13 @@ describe('Request Handler Tests', () => { 'test', () => new Response(), ); - let caught; - try { - await requestHandler.handleEvent({ action: 'test2' }, mockContext()); - } catch (err) { - caught = err; - } - assert.ok(caught); - assert.strictEqual(caught.status, 400); + const res = await requestHandler.main( + MOCK_REQUEST, + mockContext({ + action: 'test2', + }), + ); + assert.strictEqual(res.status, 400); }); it('will not fail to destructure on undefined event', async () => { @@ -129,14 +128,13 @@ describe('Request Handler Tests', () => { 'test', () => new Response(), ); - let caught; - try { - await requestHandler.handleEvent(undefined, mockContext()); - } catch (err) { - caught = err; - } - assert.ok(caught); - assert.strictEqual(caught.status, 400); + const res = await requestHandler.main(MOCK_REQUEST, { + ...mockContext(), + invocation: { + event: undefined, + }, + }); + assert.strictEqual(res.status, 400); }); it('can add/invoke actions', async () => { @@ -162,11 +160,6 @@ describe('Request Handler Tests', () => { return new Response(); }); - const removed = []; - stub(requestHandler, 'getQueueClient').returns({ - removeMessage: (handle) => removed.push(handle), - readMessageBody: (msgBody) => JSON.parse(msgBody), - }); const main = requestHandler.getMain(); const res = await main( MOCK_REQUEST, @@ -177,7 +170,7 @@ describe('Request Handler Tests', () => { assert.ok(res.ok); assert.strictEqual(res.status, 200); assert.strictEqual(events.length, 2); - assert.strictEqual(removed.length, 2); + assert.strictEqual(mockQueueClient.removed.length, 2); }); it('can handle SQS record(s) with failures', async () => { @@ -196,57 +189,52 @@ describe('Request Handler Tests', () => { throw new Error('bad news'); }); - const removed = []; - stub(requestHandler, 'getQueueClient').returns({ - removeMessage: (handle) => removed.push(handle), - readMessageBody: (msgBody) => JSON.parse(msgBody), - }); const main = requestHandler.getMain(); - const res = await main( - MOCK_REQUEST, - mockContext({ + const res = await main(MOCK_REQUEST, { + ...mockContext({ Records: [ mockSqsRecord('test'), mockSqsRecord('fail'), mockSqsRecord('throw'), ], }), - ); + log: console, + }); assert.ok(res.ok); assert.strictEqual(res.status, 206); const body = await res.json(); assert.strictEqual(body.batchItemFailures.length, 2); assert.strictEqual(events.length, 3); - assert.strictEqual(removed.length, 1); - }); - }); - - describe('getQueueClient', () => { - it('can get queue client', async () => { - const requestHandler = new RequestHandler(); - const queueClient = requestHandler.getQueueClient(mockContext()); - assert.ok(queueClient); + assert.strictEqual(mockQueueClient.removed.length, 1); }); - it('can get queue client without bucket', async () => { - const requestHandler = new RequestHandler(); - const queueClient = requestHandler.getQueueClient({ - env: { - QUEUE_URL: 'http://test.queue.com', + it('handles failures reading response body', async () => { + const events = []; + const requestHandler = new RequestHandler().withHandler( + 'bad-body', + (evt) => { + events.push(evt); + return { + text() { + throw Error(); + }, + }; }, - }); - assert.ok(queueClient); - }); + ); - it('getQueueClient requires queue url', async () => { - const requestHandler = new RequestHandler(); - let caught; - try { - requestHandler.getQueueClient({ env: {} }); - } catch (err) { - caught = err; - } - assert.ok(caught); + const main = requestHandler.getMain(); + const res = await main( + MOCK_REQUEST, + mockContext({ + Records: [mockSqsRecord('bad-body')], + }), + ); + assert.ok(res.ok); + assert.strictEqual(res.status, 206); + const body = await res.json(); + assert.strictEqual(body.batchItemFailures.length, 1); + assert.strictEqual(events.length, 1); + assert.strictEqual(mockQueueClient.removed.length, 0); }); }); });