diff --git a/README.md b/README.md index ee81171..c6a02f3 100644 --- a/README.md +++ b/README.md @@ -158,10 +158,35 @@ The project supports multiple environments: Each environment has its own AWS account and configuration. +## Lambda Utils Project + +The `@leanstacks/lambda-utils` package is a TypeScript utility library for AWS Lambda functions. It provides pre-configured logging, API response formatting, configuration validation, and AWS SDK clients—reducing boilerplate and promoting best practices within Node.js Lambda functions. + +Several of the Lambda Utils are used in the Lambda Starter application. You are encouraged to use the Lambda Utils library in your project or, if you want to maintain the source code yourself, you may fork the repo or copy only the code you need into your project. + +Learn more about the Lambda Utils with these resources... + +- **[@leanstacks/lambda-utils package on NPM](https://www.npmjs.com/package/@leanstacks/lambda-utils)** +- **[lambda-utils repository on GitHub](https://github.com/leanstacks/lambda-utils)** + +## Serverless Microservice Patterns + +This project implements the **Simple Web Service** serverless microservice pattern. The [Serverless Microservice Patterns repository](https://github.com/leanstacks/serverless-microservice-patterns) provides a comprehensive collection of additional patterns and examples, including: + +- **Simple Web Service**: A basic serverless web service pattern using API Gateway, Lambda, and DynamoDB. +- **Gatekeeper**: Adds an Auth microservice to authenticate and authorize API requests. +- **Internal API**: Facilitates synchronous, internal microservice-to-microservice integration without API Gateway exposure. +- **Internal Handoff**: Enables asynchronous microservice-to-microservice communication. +- **Publish Subscribe**: Demonstrates event-driven architecture using SNS topics and SQS queues for loose coupling. +- **Queue-Based Load Leveling**: Uses a message queue as a buffer to decouple producers from consumers and smooth demand spikes. + +Each pattern is implemented as a standalone project with practical examples and reference implementations for building scalable, event-driven microservices on AWS. + ## License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. ## Further Reading -- [Project Documentation](./docs/README.md) +- [**Lambda Utils Project**](https://github.com/leanstacks/lambda-utils) +- [**Project Documentation**](./docs/README.md) diff --git a/docs/ConfigurationGuide.md b/docs/ConfigurationGuide.md index 2a2cd1d..ba756f0 100644 --- a/docs/ConfigurationGuide.md +++ b/docs/ConfigurationGuide.md @@ -10,14 +10,15 @@ The application configuration is managed through environment variables. These va The following environment variables are available for configuring the application: -| Variable | Type | Description | Default | Required | -| ------------------- | ------- | ------------------------------------------------ | ----------- | -------- | -| `TASKS_TABLE` | string | The name of the DynamoDB table for storing tasks | - | Yes | -| `AWS_REGION` | string | The AWS region where resources are deployed | `us-east-1` | No | -| `LOGGING_ENABLED` | boolean | Enable or disable application logging | `true` | No | -| `LOGGING_LEVEL` | enum | Logging level: `debug`, `info`, `warn`, `error` | `debug` | No | -| `LOGGING_FORMAT` | enum | Logging format: `text`, `json` | `json` | No | -| `CORS_ALLOW_ORIGIN` | string | CORS allow origin header value | `*` | No | +| Variable | Type | Description | Default | Required | +| ---------------------- | ------- | ------------------------------------------------ | ----------- | -------- | +| `TASKS_TABLE` | string | The name of the DynamoDB table for storing tasks | - | Yes | +| `TASK_EVENT_TOPIC_ARN` | string | The ARN of the SNS topic for task events | - | Yes | +| `AWS_REGION` | string | The AWS region where resources are deployed | `us-east-1` | No | +| `LOGGING_ENABLED` | boolean | Enable or disable application logging | `true` | No | +| `LOGGING_LEVEL` | enum | Logging level: `debug`, `info`, `warn`, `error` | `debug` | No | +| `LOGGING_FORMAT` | enum | Logging format: `text`, `json` | `json` | No | +| `CORS_ALLOW_ORIGIN` | string | CORS allow origin header value | `*` | No | ### Usage @@ -27,6 +28,7 @@ Application configuration is accessed through the `config` object exported from import { config } from './utils/config'; console.log(`Tasks table: ${config.TASKS_TABLE}`); +console.log(`Task event topic ARN: ${config.TASK_EVENT_TOPIC_ARN}`); console.log(`Logging enabled: ${config.LOGGING_ENABLED}`); ``` @@ -155,6 +157,7 @@ Infrastructure configuration variables are passed to Lambda functions with modif | `CDK_APP_LOGGING_FORMAT` | `LOGGING_FORMAT` | | `CDK_CORS_ALLOW_ORIGIN` | `CORS_ALLOW_ORIGIN` | | (DynamoDB table name) | `TASKS_TABLE` | +| (SNS topic ARN) | `TASK_EVENT_TOPIC_ARN` | | (AWS Region) | `AWS_REGION` | --- diff --git a/docs/InfrastructureGuide.md b/docs/InfrastructureGuide.md index 0a9c808..3bc8aeb 100644 --- a/docs/InfrastructureGuide.md +++ b/docs/InfrastructureGuide.md @@ -11,6 +11,7 @@ The infrastructure is organized into two main AWS CDK stacks: | Stack Name Pattern | Purpose | | ------------------------- | ------------------------------------------ | | `{app-name}-data-{env}` | Manages DynamoDB tables and data resources | +| `{app-name}-sns-{env}` | Manages SNS topics for messaging | | `{app-name}-lambda-{env}` | Manages Lambda functions and API Gateway | --- @@ -34,6 +35,24 @@ The infrastructure is organized into two main AWS CDK stacks: --- +## SNS Stack + +**Purpose:** Manages SNS topics for messaging and event publishing. + +**Key Resources:** + +| Resource | Name Pattern | Key Properties | +| --------- | ----------------------------- | ------------------------------------------------- | +| SNS Topic | `{app-name}-task-event-{env}` | Standard (non-FIFO) topic, AWS-managed encryption | + +**Outputs:** + +| Output Name | Export Name Pattern | Description | +| ------------------- | --------------------------------------- | ------------------------------- | +| `TaskEventTopicArn` | `{app-name}-task-event-topic-arn-{env}` | Task Event Topic ARN (exported) | + +--- + ## Lambda Stack **Purpose:** Manages Lambda functions, API Gateway, and application runtime resources. @@ -49,6 +68,19 @@ The infrastructure is organized into two main AWS CDK stacks: | Lambda Function | `{app-name}-delete-task-{env}` | Delete a task (DynamoDB DeleteItem) | | API Gateway | `{app-name}-api-{env}` | REST API for Lambda functions | +**Environment Variables Passed to Lambda Functions:** + +All Lambda functions receive the following environment variables from the CDK configuration: + +| Variable | Source | Purpose | +| ---------------------- | ------------------------- | ---------------------------------------- | +| `TASKS_TABLE` | Data Stack output | DynamoDB table name for tasks | +| `TASK_EVENT_TOPIC_ARN` | SNS Stack output | SNS topic ARN for publishing task events | +| `LOGGING_ENABLED` | `CDK_APP_LOGGING_ENABLED` | Enable/disable application logging | +| `LOGGING_LEVEL` | `CDK_APP_LOGGING_LEVEL` | Application logging level | +| `LOGGING_FORMAT` | `CDK_APP_LOGGING_FORMAT` | Application logging format | +| `CORS_ALLOW_ORIGIN` | `CDK_CORS_ALLOW_ORIGIN` | CORS allow origin header value | + **Outputs:** | Output Name | Export Name Pattern | Description | diff --git a/infrastructure/app.ts b/infrastructure/app.ts index c2f5e2d..4eef96d 100644 --- a/infrastructure/app.ts +++ b/infrastructure/app.ts @@ -4,6 +4,7 @@ import * as cdk from 'aws-cdk-lib'; import { getConfig, getEnvironmentConfig, getTags } from './utils/config'; import { DataStack } from './stacks/data-stack'; +import { SnsStack } from './stacks/sns-stack'; import { LambdaStack } from './stacks/lambda-stack'; // Load and validate configuration @@ -27,6 +28,15 @@ const dataStack = new DataStack(app, `${config.CDK_APP_NAME}-data-stack-${config ...(environmentConfig && { env: environmentConfig }), }); +// Create SNS Stack +const snsStack = new SnsStack(app, `${config.CDK_APP_NAME}-sns-stack-${config.CDK_ENV}`, { + appName: config.CDK_APP_NAME, + envName: config.CDK_ENV, + stackName: `${config.CDK_APP_NAME}-sns-${config.CDK_ENV}`, + description: `SNS resources for ${config.CDK_APP_NAME} (${config.CDK_ENV})`, + ...(environmentConfig && { env: environmentConfig }), +}); + // Create Lambda Stack new LambdaStack(app, `${config.CDK_APP_NAME}-lambda-stack-${config.CDK_ENV}`, { appName: config.CDK_APP_NAME, @@ -34,6 +44,7 @@ new LambdaStack(app, `${config.CDK_APP_NAME}-lambda-stack-${config.CDK_ENV}`, { stackName: `${config.CDK_APP_NAME}-lambda-${config.CDK_ENV}`, description: `Lambda functions and API Gateway for ${config.CDK_APP_NAME} (${config.CDK_ENV})`, taskTable: dataStack.taskTable, + taskEventTopic: snsStack.taskEventTopic, loggingEnabled: config.CDK_APP_LOGGING_ENABLED, loggingLevel: config.CDK_APP_LOGGING_LEVEL, loggingFormat: config.CDK_APP_LOGGING_FORMAT, diff --git a/infrastructure/package-lock.json b/infrastructure/package-lock.json index 0bceb28..f2d11ff 100644 --- a/infrastructure/package-lock.json +++ b/infrastructure/package-lock.json @@ -8,7 +8,7 @@ "name": "lambda-starter-infrastructure", "version": "0.2.0", "dependencies": { - "aws-cdk-lib": "2.232.2", + "aws-cdk-lib": "2.233.0", "constructs": "10.4.4", "dotenv": "17.2.3", "source-map-support": "0.5.21", @@ -2147,9 +2147,9 @@ } }, "node_modules/aws-cdk-lib": { - "version": "2.232.2", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.232.2.tgz", - "integrity": "sha512-jrAxZy5mvSM9YVuF1M++hP8IIGyNmPzW8+C5nnvOnx+eYuySRiCSAzKVKlvB+UNpA0VEVCDNyTxKiu0mFBeg/g==", + "version": "2.233.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.233.0.tgz", + "integrity": "sha512-rBOzIA8TGC5eB8TyVIvckAVlX7a0/gVPE634FguhSee9RFaovjgc5+IixGyyLJhu3lLsMSjqDoqTJg2ab+p8ng==", "bundleDependencies": [ "@balena/dockerignore", "case", diff --git a/infrastructure/package.json b/infrastructure/package.json index 9124ea8..17d0da0 100644 --- a/infrastructure/package.json +++ b/infrastructure/package.json @@ -31,7 +31,7 @@ "typescript": "5.9.3" }, "dependencies": { - "aws-cdk-lib": "2.232.2", + "aws-cdk-lib": "2.233.0", "constructs": "10.4.4", "dotenv": "17.2.3", "source-map-support": "0.5.21", diff --git a/infrastructure/stacks/lambda-stack.test.ts b/infrastructure/stacks/lambda-stack.test.ts index 8cb8e00..dab56c3 100644 --- a/infrastructure/stacks/lambda-stack.test.ts +++ b/infrastructure/stacks/lambda-stack.test.ts @@ -1,6 +1,7 @@ import * as cdk from 'aws-cdk-lib'; import { Match, Template } from 'aws-cdk-lib/assertions'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as sns from 'aws-cdk-lib/aws-sns'; import { LambdaStack } from './lambda-stack'; // Mock NodejsFunction to avoid Docker bundling during tests @@ -35,11 +36,15 @@ describe('LambdaStack', () => { type: dynamodb.AttributeType.STRING, }, }); + const testMockTopic = new sns.Topic(mockTestStack, 'MockTaskEventTopic', { + topicName: 'mock-task-event-dev', + }); const stack = new LambdaStack(testApp, 'TestLambdaStack', { appName: 'lambda-starter', envName: 'dev', taskTable: testMockTable, + taskEventTopic: testMockTopic, loggingEnabled: true, loggingLevel: 'debug', loggingFormat: 'json', @@ -103,6 +108,7 @@ describe('LambdaStack', () => { Environment: { Variables: { TASKS_TABLE: Match.anyValue(), + TASK_EVENT_TOPIC_ARN: Match.anyValue(), LOGGING_ENABLED: 'true', LOGGING_LEVEL: 'debug', LOGGING_FORMAT: 'json', @@ -218,6 +224,45 @@ describe('LambdaStack', () => { }); }); + it('should grant Lambda SNS publish permissions for create function', () => { + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: Match.arrayWith([ + Match.objectLike({ + Action: 'sns:Publish', + Resource: Match.anyValue(), + }), + ]), + }, + }); + }); + + it('should grant Lambda SNS publish permissions for update function', () => { + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: Match.arrayWith([ + Match.objectLike({ + Action: 'sns:Publish', + Resource: Match.anyValue(), + }), + ]), + }, + }); + }); + + it('should grant Lambda SNS publish permissions for delete function', () => { + template.hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: Match.arrayWith([ + Match.objectLike({ + Action: 'sns:Publish', + Resource: Match.anyValue(), + }), + ]), + }, + }); + }); + it('should export API URL', () => { template.hasOutput('ApiUrl', { Export: { @@ -311,11 +356,15 @@ describe('LambdaStack', () => { type: dynamodb.AttributeType.STRING, }, }); + const testMockTopic = new sns.Topic(mockTestStack, 'MockTaskEventTopic', { + topicName: 'mock-task-event-prd', + }); const stack = new LambdaStack(testApp, 'TestLambdaStack', { appName: 'lambda-starter', envName: 'prd', taskTable: testMockTable, + taskEventTopic: testMockTopic, loggingEnabled: true, loggingLevel: 'info', loggingFormat: 'json', @@ -367,11 +416,15 @@ describe('LambdaStack', () => { type: dynamodb.AttributeType.STRING, }, }); + const testMockTopic = new sns.Topic(mockTestStack, 'MockTaskEventTopic', { + topicName: 'mock-task-event-dev', + }); const stack = new LambdaStack(testApp, 'TestLambdaStack', { appName: 'lambda-starter', envName: 'dev', taskTable: testMockTable, + taskEventTopic: testMockTopic, loggingEnabled: true, loggingLevel: 'debug', loggingFormat: 'json', diff --git a/infrastructure/stacks/lambda-stack.ts b/infrastructure/stacks/lambda-stack.ts index 69346a0..3046231 100644 --- a/infrastructure/stacks/lambda-stack.ts +++ b/infrastructure/stacks/lambda-stack.ts @@ -4,6 +4,7 @@ import * as apigateway from 'aws-cdk-lib/aws-apigateway'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import * as logs from 'aws-cdk-lib/aws-logs'; +import * as sns from 'aws-cdk-lib/aws-sns'; import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'; import { Construct } from 'constructs'; @@ -26,6 +27,11 @@ export interface LambdaStackProps extends cdk.StackProps { */ taskTable: dynamodb.ITable; + /** + * Reference to the Task Event SNS topic. + */ + taskEventTopic: sns.ITopic; + /** * Whether to enable application logging. */ @@ -92,6 +98,7 @@ export class LambdaStack extends cdk.Stack { entry: path.join(__dirname, '../../src/handlers/list-tasks.ts'), environment: { TASKS_TABLE: props.taskTable.tableName, + TASK_EVENT_TOPIC_ARN: props.taskEventTopic.topicArn, LOGGING_ENABLED: props.loggingEnabled.toString(), LOGGING_LEVEL: props.loggingLevel, LOGGING_FORMAT: props.loggingFormat, @@ -124,6 +131,7 @@ export class LambdaStack extends cdk.Stack { entry: path.join(__dirname, '../../src/handlers/get-task.ts'), environment: { TASKS_TABLE: props.taskTable.tableName, + TASK_EVENT_TOPIC_ARN: props.taskEventTopic.topicArn, LOGGING_ENABLED: props.loggingEnabled.toString(), LOGGING_LEVEL: props.loggingLevel, LOGGING_FORMAT: props.loggingFormat, @@ -156,6 +164,7 @@ export class LambdaStack extends cdk.Stack { entry: path.join(__dirname, '../../src/handlers/create-task.ts'), environment: { TASKS_TABLE: props.taskTable.tableName, + TASK_EVENT_TOPIC_ARN: props.taskEventTopic.topicArn, LOGGING_ENABLED: props.loggingEnabled.toString(), LOGGING_LEVEL: props.loggingLevel, LOGGING_FORMAT: props.loggingFormat, @@ -180,6 +189,9 @@ export class LambdaStack extends cdk.Stack { // Grant the Lambda function write access to the DynamoDB table props.taskTable.grantWriteData(this.createTaskFunction); + // Grant the Lambda function permission to publish to the SNS topic + props.taskEventTopic.grantPublish(this.createTaskFunction); + // Create the update task Lambda function this.updateTaskFunction = new NodejsFunction(this, 'UpdateTaskFunction', { functionName: `${props.appName}-update-task-${props.envName}`, @@ -188,6 +200,7 @@ export class LambdaStack extends cdk.Stack { entry: path.join(__dirname, '../../src/handlers/update-task.ts'), environment: { TASKS_TABLE: props.taskTable.tableName, + TASK_EVENT_TOPIC_ARN: props.taskEventTopic.topicArn, LOGGING_ENABLED: props.loggingEnabled.toString(), LOGGING_LEVEL: props.loggingLevel, LOGGING_FORMAT: props.loggingFormat, @@ -212,6 +225,9 @@ export class LambdaStack extends cdk.Stack { // Grant the Lambda function read and write access to the DynamoDB table props.taskTable.grantReadWriteData(this.updateTaskFunction); + // Grant the Lambda function permission to publish to the SNS topic + props.taskEventTopic.grantPublish(this.updateTaskFunction); + // Create the delete task Lambda function this.deleteTaskFunction = new NodejsFunction(this, 'DeleteTaskFunction', { functionName: `${props.appName}-delete-task-${props.envName}`, @@ -220,6 +236,7 @@ export class LambdaStack extends cdk.Stack { entry: path.join(__dirname, '../../src/handlers/delete-task.ts'), environment: { TASKS_TABLE: props.taskTable.tableName, + TASK_EVENT_TOPIC_ARN: props.taskEventTopic.topicArn, LOGGING_ENABLED: props.loggingEnabled.toString(), LOGGING_LEVEL: props.loggingLevel, LOGGING_FORMAT: props.loggingFormat, @@ -244,6 +261,9 @@ export class LambdaStack extends cdk.Stack { // Grant the Lambda function read and write access to the DynamoDB table props.taskTable.grantReadWriteData(this.deleteTaskFunction); + // Grant the Lambda function permission to publish to the SNS topic + props.taskEventTopic.grantPublish(this.deleteTaskFunction); + // Create API Gateway REST API this.api = new apigateway.RestApi(this, 'LambdaStarterApi', { restApiName: `${props.appName}-api-${props.envName}`, diff --git a/infrastructure/stacks/sns-stack.test.ts b/infrastructure/stacks/sns-stack.test.ts new file mode 100644 index 0000000..648341c --- /dev/null +++ b/infrastructure/stacks/sns-stack.test.ts @@ -0,0 +1,114 @@ +import * as cdk from 'aws-cdk-lib'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import { Template } from 'aws-cdk-lib/assertions'; +import { SnsStack } from './sns-stack'; + +describe('SnsStack', () => { + let stack: SnsStack; + let template: Template; + + beforeEach(() => { + // Create a test CDK app + const app = new cdk.App(); + + // Instantiate the SnsStack + stack = new SnsStack(app, 'TestSnsStack', { + appName: 'test-app', + envName: 'test', + }); + + // Create a template from the stack for assertions + template = Template.fromStack(stack); + }); + + describe('SNS Topic', () => { + it('should create a SNS topic with correct properties', () => { + // Assert - verify SNS topic exists with correct properties + template.hasResourceProperties('AWS::SNS::Topic', { + TopicName: 'test-app-task-event-test', + DisplayName: 'Task Event Topic (test)', + }); + }); + + it('should expose the task event topic', () => { + // Assert + expect(stack.taskEventTopic).toBeDefined(); + expect(stack.taskEventTopic).toBeInstanceOf(sns.Topic); + }); + + it('should create topic with encryption enabled by default', () => { + // Assert - SNS topics have AWS-managed encryption by default + template.resourceCountIs('AWS::SNS::Topic', 1); + }); + }); + + describe('Stack Outputs', () => { + it('should export TaskEventTopicArn output', () => { + // Assert - verify the output exists + template.hasOutput('TaskEventTopicArn', { + Export: { + Name: 'test-app-task-event-topic-arn-test', + }, + }); + }); + + it('should output the correct topic ARN', () => { + // Assert - verify the topic ARN is exported + const output = stack.node.findChild('TaskEventTopicArn'); + expect(output).toBeDefined(); + }); + }); + + describe('Stack Configuration', () => { + it('should set topic name with correct naming convention', () => { + // Assert + template.hasResourceProperties('AWS::SNS::Topic', { + TopicName: 'test-app-task-event-test', + }); + }); + + it('should create stack with dev environment naming', () => { + // Arrange + const app = new cdk.App(); + const devStack = new SnsStack(app, 'DevSnsStack', { + appName: 'lambda-starter', + envName: 'dev', + }); + + const devTemplate = Template.fromStack(devStack); + + // Assert + devTemplate.hasResourceProperties('AWS::SNS::Topic', { + TopicName: 'lambda-starter-task-event-dev', + }); + }); + + it('should create stack with prod environment naming', () => { + // Arrange + const app = new cdk.App(); + const prodStack = new SnsStack(app, 'ProdSnsStack', { + appName: 'lambda-starter', + envName: 'prd', + }); + + const prodTemplate = Template.fromStack(prodStack); + + // Assert + prodTemplate.hasResourceProperties('AWS::SNS::Topic', { + TopicName: 'lambda-starter-task-event-prd', + }); + }); + }); + + describe('Topic Properties', () => { + it('should create standard (non-FIFO) SNS topic', () => { + // Assert - topic should not have FIFO properties + template.allResources('AWS::SNS::Topic', (resource: any) => { + // FIFO topic would have FifoTopic: true + if (resource.properties.FifoTopic) { + throw new Error('Topic should not be FIFO'); + } + }); + }); + }); +}); diff --git a/infrastructure/stacks/sns-stack.ts b/infrastructure/stacks/sns-stack.ts new file mode 100644 index 0000000..00c0c4e --- /dev/null +++ b/infrastructure/stacks/sns-stack.ts @@ -0,0 +1,46 @@ +import * as cdk from 'aws-cdk-lib'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import { Construct } from 'constructs'; + +/** + * Properties for the SnsStack. + */ +export interface SnsStackProps extends cdk.StackProps { + /** + * Application name. + */ + appName: string; + + /** + * Environment name (dev, qat, prd). + */ + envName: string; +} + +/** + * CDK Stack for SNS topics and messaging resources. + */ +export class SnsStack extends cdk.Stack { + /** + * The Task Event SNS topic. + */ + public readonly taskEventTopic: sns.ITopic; + + constructor(scope: Construct, id: string, props: SnsStackProps) { + super(scope, id, props); + + // Create Task Event Topic + this.taskEventTopic = new sns.Topic(this, 'TaskEventTopic', { + topicName: `${props.appName}-task-event-${props.envName}`, + displayName: `Task Event Topic (${props.envName})`, + fifo: false, + }); + + // Output the topic ARN + new cdk.CfnOutput(this, 'TaskEventTopicArn', { + value: this.taskEventTopic.topicArn, + description: 'The ARN of the Task Event SNS topic', + exportName: `${props.appName}-task-event-topic-arn-${props.envName}`, + }); + } +} diff --git a/package-lock.json b/package-lock.json index 21f6e34..043c6ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,20 +9,19 @@ "version": "0.2.0", "license": "MIT", "dependencies": { - "@aws-sdk/client-dynamodb": "3.953.0", - "@aws-sdk/client-lambda": "3.953.0", - "@aws-sdk/client-sns": "3.953.0", - "@aws-sdk/lib-dynamodb": "3.953.0", - "pino": "10.1.0", - "pino-lambda": "4.4.1", + "@aws-sdk/client-dynamodb": "3.958.0", + "@aws-sdk/client-lambda": "3.958.0", + "@aws-sdk/client-sns": "3.958.0", + "@aws-sdk/lib-dynamodb": "3.958.0", + "@leanstacks/lambda-utils": "0.4.0-alpha.1", "zod": "4.2.1" }, "devDependencies": { "@types/aws-lambda": "8.10.159", "@types/jest": "30.0.0", "@types/node": "25.0.3", - "@typescript-eslint/eslint-plugin": "8.50.0", - "@typescript-eslint/parser": "8.50.0", + "@typescript-eslint/eslint-plugin": "8.50.1", + "@typescript-eslint/parser": "8.50.1", "eslint": "9.39.2", "eslint-config-prettier": "10.1.8", "globals": "16.5.0", @@ -33,7 +32,7 @@ "ts-jest": "29.4.6", "ts-node": "10.9.2", "typescript": "5.9.3", - "typescript-eslint": "8.50.0" + "typescript-eslint": "8.50.1" } }, "node_modules/@aws-crypto/crc32": { @@ -176,53 +175,53 @@ } }, "node_modules/@aws-sdk/client-dynamodb": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.953.0.tgz", - "integrity": "sha512-QeSFxXgRjpr8M2wiLUsgg+mXEDtdhcuMnBWbXyjqUwca38pLEFJzJdFyOGul9RoQ2ICseuAy2/RZt0Ri1UgeZQ==", + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.958.0.tgz", + "integrity": "sha512-R3G5cxf3fsL0CEcTbY1VkSwU1FJtImrhA5I9Eepd8nEO6isZ6C99qVKZtDG9eG7qVNK6zTzUigXac/GFrn6hYA==", "license": "Apache-2.0", "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.953.0", - "@aws-sdk/credential-provider-node": "3.953.0", - "@aws-sdk/dynamodb-codec": "3.953.0", - "@aws-sdk/middleware-endpoint-discovery": "3.953.0", - "@aws-sdk/middleware-host-header": "3.953.0", - "@aws-sdk/middleware-logger": "3.953.0", - "@aws-sdk/middleware-recursion-detection": "3.953.0", - "@aws-sdk/middleware-user-agent": "3.953.0", - "@aws-sdk/region-config-resolver": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@aws-sdk/util-endpoints": "3.953.0", - "@aws-sdk/util-user-agent-browser": "3.953.0", - "@aws-sdk/util-user-agent-node": "3.953.0", - "@smithy/config-resolver": "^4.4.4", - "@smithy/core": "^3.19.0", - "@smithy/fetch-http-handler": "^5.3.7", - "@smithy/hash-node": "^4.2.6", - "@smithy/invalid-dependency": "^4.2.6", - "@smithy/middleware-content-length": "^4.2.6", - "@smithy/middleware-endpoint": "^4.3.15", - "@smithy/middleware-retry": "^4.4.15", - "@smithy/middleware-serde": "^4.2.7", - "@smithy/middleware-stack": "^4.2.6", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/node-http-handler": "^4.4.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/smithy-client": "^4.10.0", - "@smithy/types": "^4.10.0", - "@smithy/url-parser": "^4.2.6", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/credential-provider-node": "3.958.0", + "@aws-sdk/dynamodb-codec": "3.957.0", + "@aws-sdk/middleware-endpoint-discovery": "3.957.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.14", - "@smithy/util-defaults-mode-node": "^4.2.17", - "@smithy/util-endpoints": "^3.2.6", - "@smithy/util-middleware": "^4.2.6", - "@smithy/util-retry": "^4.2.6", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", "@smithy/util-utf8": "^4.2.0", - "@smithy/util-waiter": "^4.2.6", + "@smithy/util-waiter": "^4.2.7", "tslib": "^2.6.2" }, "engines": { @@ -230,54 +229,54 @@ } }, "node_modules/@aws-sdk/client-lambda": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.953.0.tgz", - "integrity": "sha512-Bsm5arLOjkrhGQzycmswnVWwJANd447h5e/TRqyYd07Zr6LIhylcACOeyJIoz7z1ffXTCTj8iLGE7Zac4g0mSQ==", + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.958.0.tgz", + "integrity": "sha512-gwEqpDkgPLbFfewQkRRgnqn9iCfnd5BUVFUZpUoyq8DxzPmNn/lEVMkBaNCqwIXx07jd46+qd1neWBrH2UYi2Q==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.953.0", - "@aws-sdk/credential-provider-node": "3.953.0", - "@aws-sdk/middleware-host-header": "3.953.0", - "@aws-sdk/middleware-logger": "3.953.0", - "@aws-sdk/middleware-recursion-detection": "3.953.0", - "@aws-sdk/middleware-user-agent": "3.953.0", - "@aws-sdk/region-config-resolver": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@aws-sdk/util-endpoints": "3.953.0", - "@aws-sdk/util-user-agent-browser": "3.953.0", - "@aws-sdk/util-user-agent-node": "3.953.0", - "@smithy/config-resolver": "^4.4.4", - "@smithy/core": "^3.19.0", - "@smithy/eventstream-serde-browser": "^4.2.6", - "@smithy/eventstream-serde-config-resolver": "^4.3.6", - "@smithy/eventstream-serde-node": "^4.2.6", - "@smithy/fetch-http-handler": "^5.3.7", - "@smithy/hash-node": "^4.2.6", - "@smithy/invalid-dependency": "^4.2.6", - "@smithy/middleware-content-length": "^4.2.6", - "@smithy/middleware-endpoint": "^4.3.15", - "@smithy/middleware-retry": "^4.4.15", - "@smithy/middleware-serde": "^4.2.7", - "@smithy/middleware-stack": "^4.2.6", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/node-http-handler": "^4.4.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/smithy-client": "^4.10.0", - "@smithy/types": "^4.10.0", - "@smithy/url-parser": "^4.2.6", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/credential-provider-node": "3.958.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/eventstream-serde-browser": "^4.2.7", + "@smithy/eventstream-serde-config-resolver": "^4.3.7", + "@smithy/eventstream-serde-node": "^4.2.7", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.14", - "@smithy/util-defaults-mode-node": "^4.2.17", - "@smithy/util-endpoints": "^3.2.6", - "@smithy/util-middleware": "^4.2.6", - "@smithy/util-retry": "^4.2.6", - "@smithy/util-stream": "^4.5.7", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-stream": "^4.5.8", "@smithy/util-utf8": "^4.2.0", - "@smithy/util-waiter": "^4.2.6", + "@smithy/util-waiter": "^4.2.7", "tslib": "^2.6.2" }, "engines": { @@ -285,48 +284,302 @@ } }, "node_modules/@aws-sdk/client-sns": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sns/-/client-sns-3.953.0.tgz", - "integrity": "sha512-tlmq3g9WxxMBZsOTak22gs8gXCXUCtKMZvl2CJrB6/kPm0CTqquKli/S7XmpWFj8BudUmf48R+Cc23AkRCvtPA==", + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sns/-/client-sns-3.958.0.tgz", + "integrity": "sha512-KSsfhNXt8npsadJL5tF3UhARkPtqcbszMt3bKwkF4j5KA5Hdf5XD6tzjwBGkJnvBPHZJbLZEquzIvt4Z3KGIrw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/credential-provider-node": "3.958.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sqs/-/client-sqs-3.957.0.tgz", + "integrity": "sha512-V5hoymq0nnS7eAoMedj5p4YSyAbxkS4bzC8xl1tKEOmKIXDA0+uO1Ink5QIgUVbB7DEsLbdrgHE9G77DqwvcQA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/credential-provider-node": "3.957.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-sdk-sqs": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/md5-js": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/client-sso": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.957.0.tgz", + "integrity": "sha512-iRdRjd+IpOogqRPt8iNRcg30J53z4rRfMviGwpKgsEa/fx3inCUPOuca3Ap7ZDES0atnEg3KGSJ3V/NQiEJ4BA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.957.0.tgz", + "integrity": "sha512-YuoZmIeE91YIeUfihh8SiSu546KtTvU+4rG5SaL30U9+nGq6P11GRRgqF0ANUyRseLC9ONHt+utar4gbO3++og==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/credential-provider-env": "3.957.0", + "@aws-sdk/credential-provider-http": "3.957.0", + "@aws-sdk/credential-provider-login": "3.957.0", + "@aws-sdk/credential-provider-process": "3.957.0", + "@aws-sdk/credential-provider-sso": "3.957.0", + "@aws-sdk/credential-provider-web-identity": "3.957.0", + "@aws-sdk/nested-clients": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/credential-provider-login": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.957.0.tgz", + "integrity": "sha512-XcD5NEQDWYk8B4gs89bkwf2d+DNF8oS2NR5RoHJEbX4l8KErVATUjpEYVn6/rAFEktungxlYTnQ5wh0cIQvP5w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/nested-clients": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.957.0.tgz", + "integrity": "sha512-b9FT/7BQcJ001w+3JbTiJXfxHrWvPb7zDvvC1i1FKcNOvyCt3BGu04n4nO/b71a3iBnbfBXI89hCIZQsuLcEgw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.957.0", + "@aws-sdk/credential-provider-http": "3.957.0", + "@aws-sdk/credential-provider-ini": "3.957.0", + "@aws-sdk/credential-provider-process": "3.957.0", + "@aws-sdk/credential-provider-sso": "3.957.0", + "@aws-sdk/credential-provider-web-identity": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.957.0.tgz", + "integrity": "sha512-gTLPJFOkGtn3tVGglRhCar2oOobK1YctZRAT8nfJr17uaSRoAP46zIIHNYBZZUMqImb0qAHD9Ugm+Zd9sIqxyA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.957.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/token-providers": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.957.0.tgz", + "integrity": "sha512-x17xMeD7c+rKEsWachGIMifACqkugskrETWz18QDWismFcrmUuOcZu5rUa8s9y1pnITLKUQ1xU/qDLPH52jLlA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/nested-clients": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/nested-clients": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.957.0.tgz", + "integrity": "sha512-PZUFtaUTSZWO+mbgQGWSiwz3EqedsuKNb7Xoxjzh5rfJE352DD4/jScQEhVPxvdLw62IK9b5UDu5kZlxzBs9Ow==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.953.0", - "@aws-sdk/credential-provider-node": "3.953.0", - "@aws-sdk/middleware-host-header": "3.953.0", - "@aws-sdk/middleware-logger": "3.953.0", - "@aws-sdk/middleware-recursion-detection": "3.953.0", - "@aws-sdk/middleware-user-agent": "3.953.0", - "@aws-sdk/region-config-resolver": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@aws-sdk/util-endpoints": "3.953.0", - "@aws-sdk/util-user-agent-browser": "3.953.0", - "@aws-sdk/util-user-agent-node": "3.953.0", - "@smithy/config-resolver": "^4.4.4", - "@smithy/core": "^3.19.0", - "@smithy/fetch-http-handler": "^5.3.7", - "@smithy/hash-node": "^4.2.6", - "@smithy/invalid-dependency": "^4.2.6", - "@smithy/middleware-content-length": "^4.2.6", - "@smithy/middleware-endpoint": "^4.3.15", - "@smithy/middleware-retry": "^4.4.15", - "@smithy/middleware-serde": "^4.2.7", - "@smithy/middleware-stack": "^4.2.6", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/node-http-handler": "^4.4.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/smithy-client": "^4.10.0", - "@smithy/types": "^4.10.0", - "@smithy/url-parser": "^4.2.6", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.14", - "@smithy/util-defaults-mode-node": "^4.2.17", - "@smithy/util-endpoints": "^3.2.6", - "@smithy/util-middleware": "^4.2.6", - "@smithy/util-retry": "^4.2.6", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -334,48 +587,66 @@ "node": ">=18.0.0" } }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/token-providers": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.957.0.tgz", + "integrity": "sha512-oSwo3BZ6gcvhjTg036V0UQmtENUeNwfCU35iDckX961CdI1alQ3TKRWLzKrwvXCbrOx+bZsuA1PHsTbNhI/+Fw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/nested-clients": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@aws-sdk/client-sso": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.953.0.tgz", - "integrity": "sha512-WU8XtORGnhy1JjTLflcGcNGU329pmwl/2claTVGp7vrgUKyreQV9Ke3BkMuUPYLOGO/Znstzc1VbUT9ORH3+WQ==", + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.958.0.tgz", + "integrity": "sha512-6qNCIeaMzKzfqasy2nNRuYnMuaMebCcCPP4J2CVGkA8QYMbIVKPlkn9bpB20Vxe6H/r3jtCCLQaOJjVTx/6dXg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.953.0", - "@aws-sdk/middleware-host-header": "3.953.0", - "@aws-sdk/middleware-logger": "3.953.0", - "@aws-sdk/middleware-recursion-detection": "3.953.0", - "@aws-sdk/middleware-user-agent": "3.953.0", - "@aws-sdk/region-config-resolver": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@aws-sdk/util-endpoints": "3.953.0", - "@aws-sdk/util-user-agent-browser": "3.953.0", - "@aws-sdk/util-user-agent-node": "3.953.0", - "@smithy/config-resolver": "^4.4.4", - "@smithy/core": "^3.19.0", - "@smithy/fetch-http-handler": "^5.3.7", - "@smithy/hash-node": "^4.2.6", - "@smithy/invalid-dependency": "^4.2.6", - "@smithy/middleware-content-length": "^4.2.6", - "@smithy/middleware-endpoint": "^4.3.15", - "@smithy/middleware-retry": "^4.4.15", - "@smithy/middleware-serde": "^4.2.7", - "@smithy/middleware-stack": "^4.2.6", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/node-http-handler": "^4.4.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/smithy-client": "^4.10.0", - "@smithy/types": "^4.10.0", - "@smithy/url-parser": "^4.2.6", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.14", - "@smithy/util-defaults-mode-node": "^4.2.17", - "@smithy/util-endpoints": "^3.2.6", - "@smithy/util-middleware": "^4.2.6", - "@smithy/util-retry": "^4.2.6", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -384,22 +655,22 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.953.0.tgz", - "integrity": "sha512-hnz4ukn+XupuotZcFAyCIX41QqEWSHc8zf4gN4l5qVP2M8YCyZkLLZ5t5LaWLJkUFbOUU2w4SuGpUp+5ddtSkw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.953.0", - "@aws-sdk/xml-builder": "3.953.0", - "@smithy/core": "^3.19.0", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/property-provider": "^4.2.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/signature-v4": "^5.3.6", - "@smithy/smithy-client": "^4.10.0", - "@smithy/types": "^4.10.0", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.957.0.tgz", + "integrity": "sha512-DrZgDnF1lQZv75a52nFWs6MExihJF2GZB6ETZRqr6jMwhrk2kbJPUtvgbifwcL7AYmVqHQDJBrR/MqkwwFCpiw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@aws-sdk/xml-builder": "3.957.0", + "@smithy/core": "^3.20.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.6", + "@smithy/util-middleware": "^4.2.7", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -408,15 +679,15 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.953.0.tgz", - "integrity": "sha512-ahOxDxvaKUf99LU9iRmRtPW2HLmsR+ix2WyzRGl1PpLltiRLMbKJHj5Tu2xyOsgLxs8tefK9D1j0SUgPk8GJ6g==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.957.0.tgz", + "integrity": "sha512-475mkhGaWCr+Z52fOOVb/q2VHuNvqEDixlYIkeaO6xJ6t9qR0wpLt4hOQaR6zR1wfZV0SlE7d8RErdYq/PByog==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@smithy/property-provider": "^4.2.6", - "@smithy/types": "^4.10.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -424,20 +695,20 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.953.0.tgz", - "integrity": "sha512-kYQEcJpJLiT+1ZdsQDMrIs2o3YydxdAIDdLUxbIwks1+WQz2sCr9b94ld19aMZ0rejosASO0J3dfY0lt3Tfl+Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@smithy/fetch-http-handler": "^5.3.7", - "@smithy/node-http-handler": "^4.4.6", - "@smithy/property-provider": "^4.2.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/smithy-client": "^4.10.0", - "@smithy/types": "^4.10.0", - "@smithy/util-stream": "^4.5.7", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.957.0.tgz", + "integrity": "sha512-8dS55QHRxXgJlHkEYaCGZIhieCs9NU1HU1BcqQ4RfUdSsfRdxxktqUKgCnBnOOn0oD3PPA8cQOCAVgIyRb3Rfw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/util-stream": "^4.5.8", "tslib": "^2.6.2" }, "engines": { @@ -445,24 +716,24 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.953.0.tgz", - "integrity": "sha512-5cy6b3VntBvhRCanwohRtR0wbKKUd6JfIsnuXImB4JcZa2iMW5tDW0LhNangYZ9wrrM7sJol6cKHtUW9LNemFQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.953.0", - "@aws-sdk/credential-provider-env": "3.953.0", - "@aws-sdk/credential-provider-http": "3.953.0", - "@aws-sdk/credential-provider-login": "3.953.0", - "@aws-sdk/credential-provider-process": "3.953.0", - "@aws-sdk/credential-provider-sso": "3.953.0", - "@aws-sdk/credential-provider-web-identity": "3.953.0", - "@aws-sdk/nested-clients": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@smithy/credential-provider-imds": "^4.2.6", - "@smithy/property-provider": "^4.2.6", - "@smithy/shared-ini-file-loader": "^4.4.1", - "@smithy/types": "^4.10.0", + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.958.0.tgz", + "integrity": "sha512-u7twvZa1/6GWmPBZs6DbjlegCoNzNjBsMS/6fvh5quByYrcJr/uLd8YEr7S3UIq4kR/gSnHqcae7y2nL2bqZdg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/credential-provider-env": "3.957.0", + "@aws-sdk/credential-provider-http": "3.957.0", + "@aws-sdk/credential-provider-login": "3.958.0", + "@aws-sdk/credential-provider-process": "3.957.0", + "@aws-sdk/credential-provider-sso": "3.958.0", + "@aws-sdk/credential-provider-web-identity": "3.958.0", + "@aws-sdk/nested-clients": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -470,18 +741,18 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.953.0.tgz", - "integrity": "sha512-Drf4v7uBKnU/nY/qgQD9ZA2zD5gjQEatxQajQHUN54erDSQ2rB5ApNuTnc2cyNwaxURnQvO1J72mwO6P5lIpsg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.953.0", - "@aws-sdk/nested-clients": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@smithy/property-provider": "^4.2.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/shared-ini-file-loader": "^4.4.1", - "@smithy/types": "^4.10.0", + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.958.0.tgz", + "integrity": "sha512-sDwtDnBSszUIbzbOORGh5gmXGl9aK25+BHb4gb1aVlqB+nNL2+IUEJA62+CE55lXSH8qXF90paivjK8tOHTwPA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/nested-clients": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -489,22 +760,22 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.953.0.tgz", - "integrity": "sha512-/3gUap/QwAV/U3RPStcSjdiksDboTdcz82qajfhURhtAe9iEdoKJy7vTYqg75eX7IjpgBwLGajt0URasUofrPg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.953.0", - "@aws-sdk/credential-provider-http": "3.953.0", - "@aws-sdk/credential-provider-ini": "3.953.0", - "@aws-sdk/credential-provider-process": "3.953.0", - "@aws-sdk/credential-provider-sso": "3.953.0", - "@aws-sdk/credential-provider-web-identity": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@smithy/credential-provider-imds": "^4.2.6", - "@smithy/property-provider": "^4.2.6", - "@smithy/shared-ini-file-loader": "^4.4.1", - "@smithy/types": "^4.10.0", + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.958.0.tgz", + "integrity": "sha512-vdoZbNG2dt66I7EpN3fKCzi6fp9xjIiwEA/vVVgqO4wXCGw8rKPIdDUus4e13VvTr330uQs2W0UNg/7AgtquEQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.957.0", + "@aws-sdk/credential-provider-http": "3.957.0", + "@aws-sdk/credential-provider-ini": "3.958.0", + "@aws-sdk/credential-provider-process": "3.957.0", + "@aws-sdk/credential-provider-sso": "3.958.0", + "@aws-sdk/credential-provider-web-identity": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -512,16 +783,16 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.953.0.tgz", - "integrity": "sha512-YeqzqxzBzXnqdzn4pBuoXeCmWrXOLIu5owAowUc4dOvHmtlpXxB3beJdQ9g/Ky6fG6iaAPKO1vrCEoHNjM85Dw==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.957.0.tgz", + "integrity": "sha512-/KIz9kadwbeLy6SKvT79W81Y+hb/8LMDyeloA2zhouE28hmne+hLn0wNCQXAAupFFlYOAtZR2NTBs7HBAReJlg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@smithy/property-provider": "^4.2.6", - "@smithy/shared-ini-file-loader": "^4.4.1", - "@smithy/types": "^4.10.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -529,18 +800,18 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.953.0.tgz", - "integrity": "sha512-Kp/49vAJweixMo3q85eVpq1JO9KgKc6A+f8/8WDkN5CkMsfKQcGJZbO5JtuJfzar4pPmRKbgOeOP1qQsOmfyfw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.953.0", - "@aws-sdk/core": "3.953.0", - "@aws-sdk/token-providers": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@smithy/property-provider": "^4.2.6", - "@smithy/shared-ini-file-loader": "^4.4.1", - "@smithy/types": "^4.10.0", + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.958.0.tgz", + "integrity": "sha512-CBYHJ5ufp8HC4q+o7IJejCUctJXWaksgpmoFpXerbjAso7/Fg7LLUu9inXVOxlHKLlvYekDXjIUBXDJS2WYdgg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.958.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/token-providers": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -548,17 +819,17 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.953.0.tgz", - "integrity": "sha512-V8SpGVtNjQZboHjb/GLM4L3/6O/AZJFtwJ8fiBaemKMTntl8haioZlQHDklWixR9s9zthOdFsCDs1+92ndUBRw==", + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.958.0.tgz", + "integrity": "sha512-dgnvwjMq5Y66WozzUzxNkCFap+umHUtqMMKlr8z/vl9NYMLem/WUbWNpFFOVFWquXikc+ewtpBMR4KEDXfZ+KA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.953.0", - "@aws-sdk/nested-clients": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@smithy/property-provider": "^4.2.6", - "@smithy/shared-ini-file-loader": "^4.4.1", - "@smithy/types": "^4.10.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/nested-clients": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -566,15 +837,15 @@ } }, "node_modules/@aws-sdk/dynamodb-codec": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/dynamodb-codec/-/dynamodb-codec-3.953.0.tgz", - "integrity": "sha512-VJDM2VvFb/VQxiRXmLw4sWCDqhAuYNzD5toANgwRazeZmCg0em6dtLdb97Q+4BT+MBXI4t1CS2xKaGtKOieNug==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/dynamodb-codec/-/dynamodb-codec-3.957.0.tgz", + "integrity": "sha512-xds1mkwEGzXrNy/gT6/ehaJ+cbYn/QM7AkdwNrO1NBlwJVLo3imO6hOnOQ/0KWG2ck1dbKv9H9f2hka67bAzEA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.953.0", - "@smithy/core": "^3.19.0", - "@smithy/smithy-client": "^4.10.0", - "@smithy/types": "^4.10.0", + "@aws-sdk/core": "3.957.0", + "@smithy/core": "^3.20.0", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" }, @@ -582,13 +853,13 @@ "node": ">=18.0.0" }, "peerDependencies": { - "@aws-sdk/client-dynamodb": "^3.953.0" + "@aws-sdk/client-dynamodb": "^3.957.0" } }, "node_modules/@aws-sdk/endpoint-cache": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.953.0.tgz", - "integrity": "sha512-pz67DoHk5WNmvMuyNDiomUS2xo0mq6Z3TdfLJZlWVbSKi3h8hYxVQchJ2kzgTr6wu6zt3UBbtKV9yY1IBhKMVA==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.957.0.tgz", + "integrity": "sha512-QxvFejXYYBZp/GBfT7B15gvmvuq+0f2U8RPHqArf5IqBi51ZyBqUD805tQ8TlsVrlLoi+Z4fEFw4HEM5pGvPUg==", "license": "Apache-2.0", "dependencies": { "mnemonist": "0.38.3", @@ -599,36 +870,36 @@ } }, "node_modules/@aws-sdk/lib-dynamodb": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.953.0.tgz", - "integrity": "sha512-oiCj2gZumRjhnv2RfI3oYgx0TFeollUt5JEzWjcCFbT2Ou9J2Ay1FEH9/xYGLxHu/NCGUVEAHdwuDsJcb09KHQ==", + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.958.0.tgz", + "integrity": "sha512-ojqEe4ojhk/MINaaEzqFLcZ9abBGP+zwUxTJh9x2mM2Y7Y4Gvqmwvs5aT790pI8yiPKPDkeF/E+DNEmSCUPtlA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.953.0", - "@aws-sdk/util-dynamodb": "3.953.0", - "@smithy/core": "^3.19.0", - "@smithy/smithy-client": "^4.10.0", - "@smithy/types": "^4.10.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/util-dynamodb": "3.958.0", + "@smithy/core": "^3.20.0", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" }, "peerDependencies": { - "@aws-sdk/client-dynamodb": "^3.953.0" + "@aws-sdk/client-dynamodb": "^3.958.0" } }, "node_modules/@aws-sdk/middleware-endpoint-discovery": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.953.0.tgz", - "integrity": "sha512-/YKB1/OiWr7TwOfmkqzv8x1xgOpU71yciQTfsq6erB3dTQhdukPADt/CMJOhWFKC6Q1D5cDN8381nsGmnNuBVg==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.957.0.tgz", + "integrity": "sha512-MJjlw4mVJNTyR5dW6wpzKLRzFPIYAMA8qUWqgG4hGscmm4GFHvWVJ9mhhdpDu7Ie4Uaikmzfy0C4xzZ+lkf1+w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/endpoint-cache": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", + "@aws-sdk/endpoint-cache": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -636,14 +907,14 @@ } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.953.0.tgz", - "integrity": "sha512-jTGhfkONav+r4E6HLOrl5SzBqDmPByUYCkyB/c/3TVb8jX3wAZx8/q9bphKpCh+G5ARi3IdbSisgkZrJYqQ19Q==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.957.0.tgz", + "integrity": "sha512-BBgKawVyfQZglEkNTuBBdC3azlyqNXsvvN4jPkWAiNYcY0x1BasaJFl+7u/HisfULstryweJq/dAvIZIxzlZaA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.953.0", - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", + "@aws-sdk/types": "3.957.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -651,13 +922,13 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.953.0.tgz", - "integrity": "sha512-PlWdVYgcuptkIC0ZKqVUhWNtSHXJSx7U9V8J7dJjRmsXC40X7zpEycvrkzDMJjeTDGcCceYbyYAg/4X1lkcIMw==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.957.0.tgz", + "integrity": "sha512-w1qfKrSKHf9b5a8O76yQ1t69u6NWuBjr5kBX+jRWFx/5mu6RLpqERXRpVJxfosbep7k3B+DSB5tZMZ82GKcJtQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.953.0", - "@smithy/types": "^4.10.0", + "@aws-sdk/types": "3.957.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -665,15 +936,32 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.953.0.tgz", - "integrity": "sha512-cmIJx0gWeesUKK4YwgE+VQL3mpACr3/J24fbwnc1Z5tntC86b+HQFzU5vsBDw6lLwyD46dBgWdsXFh1jL+ZaFw==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.957.0.tgz", + "integrity": "sha512-D2H/WoxhAZNYX+IjkKTdOhOkWQaK0jjJrDBj56hKjU5c9ltQiaX/1PqJ4dfjHntEshJfu0w+E6XJ+/6A6ILBBA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.953.0", + "@aws-sdk/types": "3.957.0", "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-sqs": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.957.0.tgz", + "integrity": "sha512-3A1V2oSV/NzWukwDBwnf/ng+n+8zU32jRml0lbYiP9PzBgc6D6Y4Z/RCbPp7g+PO8XrCRrZg6QKspO3cLpGnOw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -681,17 +969,17 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.953.0.tgz", - "integrity": "sha512-xZSU54cEHlIvRTUewyTAajb4WJPdbBd1mIaDYOzac07+vsfMuCREQ5CheQ3inoBfqske7QOX+mIjLpVV4azAnw==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.957.0.tgz", + "integrity": "sha512-50vcHu96XakQnIvlKJ1UoltrFODjsq2KvtTgHiPFteUS884lQnK5VC/8xd1Msz/1ONpLMzdCVproCQqhDTtMPQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@aws-sdk/util-endpoints": "3.953.0", - "@smithy/core": "^3.19.0", - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@smithy/core": "^3.20.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -699,47 +987,47 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.953.0.tgz", - "integrity": "sha512-YEBI0b/4ezNkw5W4+RBRUoueFzqEPPrnkh/3LBqeJ7RWZTymBhxlpoLo6Gfxw9LxtDgv2vZ5K5rgC4/6Ly8ADg==", + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.958.0.tgz", + "integrity": "sha512-/KuCcS8b5TpQXkYOrPLYytrgxBhv81+5pChkOlhegbeHttjM69pyUpQVJqyfDM/A7wPLnDrzCAnk4zaAOkY0Nw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.953.0", - "@aws-sdk/middleware-host-header": "3.953.0", - "@aws-sdk/middleware-logger": "3.953.0", - "@aws-sdk/middleware-recursion-detection": "3.953.0", - "@aws-sdk/middleware-user-agent": "3.953.0", - "@aws-sdk/region-config-resolver": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@aws-sdk/util-endpoints": "3.953.0", - "@aws-sdk/util-user-agent-browser": "3.953.0", - "@aws-sdk/util-user-agent-node": "3.953.0", - "@smithy/config-resolver": "^4.4.4", - "@smithy/core": "^3.19.0", - "@smithy/fetch-http-handler": "^5.3.7", - "@smithy/hash-node": "^4.2.6", - "@smithy/invalid-dependency": "^4.2.6", - "@smithy/middleware-content-length": "^4.2.6", - "@smithy/middleware-endpoint": "^4.3.15", - "@smithy/middleware-retry": "^4.4.15", - "@smithy/middleware-serde": "^4.2.7", - "@smithy/middleware-stack": "^4.2.6", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/node-http-handler": "^4.4.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/smithy-client": "^4.10.0", - "@smithy/types": "^4.10.0", - "@smithy/url-parser": "^4.2.6", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.14", - "@smithy/util-defaults-mode-node": "^4.2.17", - "@smithy/util-endpoints": "^3.2.6", - "@smithy/util-middleware": "^4.2.6", - "@smithy/util-retry": "^4.2.6", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -748,15 +1036,15 @@ } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.953.0.tgz", - "integrity": "sha512-5MJgnsc+HLO+le0EK1cy92yrC7kyhGZSpaq8PcQvKs9qtXCXT5Tb6tMdkr5Y07JxYsYOV1omWBynvL6PWh08tQ==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.957.0.tgz", + "integrity": "sha512-V8iY3blh8l2iaOqXWW88HbkY5jDoWjH56jonprG/cpyqqCnprvpMUZWPWYJoI8rHRf2bqzZeql1slxG6EnKI7A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.953.0", - "@smithy/config-resolver": "^4.4.4", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/types": "^4.10.0", + "@aws-sdk/types": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -764,17 +1052,17 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.953.0.tgz", - "integrity": "sha512-4FWWR3lDDuIftmCEWpGejfkJu2J1VG2MUktChQshADXABfVfM0fsT7OYlO7Sy106nOe9upE4uQTWXg9YT/6ssw==", + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.958.0.tgz", + "integrity": "sha512-UCj7lQXODduD1myNJQkV+LYcGYJ9iiMggR8ow8Hva1g3A/Na5imNXzz6O67k7DAee0TYpy+gkNw+SizC6min8Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.953.0", - "@aws-sdk/nested-clients": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@smithy/property-provider": "^4.2.6", - "@smithy/shared-ini-file-loader": "^4.4.1", - "@smithy/types": "^4.10.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/nested-clients": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -782,12 +1070,12 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.953.0.tgz", - "integrity": "sha512-M9Iwg9kTyqTErI0vOTVVpcnTHWzS3VplQppy8MuL02EE+mJ0BIwpWfsaAPQW+/XnVpdNpWZTsHcNE29f1+hR8g==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.957.0.tgz", + "integrity": "sha512-wzWC2Nrt859ABk6UCAVY/WYEbAd7FjkdrQL6m24+tfmWYDNRByTJ9uOgU/kw9zqLCAwb//CPvrJdhqjTznWXAg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -795,9 +1083,9 @@ } }, "node_modules/@aws-sdk/util-dynamodb": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.953.0.tgz", - "integrity": "sha512-WxDCCmpyJwAuBly+M7XYxdR9vLH69DjJT/8mWBeaxCcaZQO6W7l8PGGNMSHZQSMcWO5vhqOf0l29ZHdK3a63FA==", + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.958.0.tgz", + "integrity": "sha512-wNGCmCBaj+Om4e93+zkiWv7L+sPGlJuDKHWpndCLdHp3EyHt46KxQpAC5QOMTzbRS0obxl2LqXilX8fbQZxU6A==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -806,19 +1094,19 @@ "node": ">=18.0.0" }, "peerDependencies": { - "@aws-sdk/client-dynamodb": "^3.953.0" + "@aws-sdk/client-dynamodb": "^3.958.0" } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.953.0.tgz", - "integrity": "sha512-rjaS6jrFksopXvNg6YeN+D1lYwhcByORNlFuYesFvaQNtPOufbE5tJL4GJ3TMXyaY0uFR28N5BHHITPyWWfH/g==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.957.0.tgz", + "integrity": "sha512-xwF9K24mZSxcxKS3UKQFeX/dPYkEps9wF1b+MGON7EvnbcucrJGyQyK1v1xFPn1aqXkBTFi+SZaMRx5E5YCVFw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.953.0", - "@smithy/types": "^4.10.0", - "@smithy/url-parser": "^4.2.6", - "@smithy/util-endpoints": "^3.2.6", + "@aws-sdk/types": "3.957.0", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-endpoints": "^3.2.7", "tslib": "^2.6.2" }, "engines": { @@ -826,9 +1114,9 @@ } }, "node_modules/@aws-sdk/util-locate-window": { - "version": "3.893.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz", - "integrity": "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.957.0.tgz", + "integrity": "sha512-nhmgKHnNV9K+i9daumaIz8JTLsIIML9PE/HUks5liyrjUzenjW/aHoc7WJ9/Td/gPZtayxFnXQSJRb/fDlBuJw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -838,27 +1126,27 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.953.0.tgz", - "integrity": "sha512-UF5NeqYesWuFao+u7LJvpV1SJCaLml5BtFZKUdTnNNMeN6jvV+dW/eQoFGpXF94RCqguX0XESmRuRRPQp+/rzQ==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.957.0.tgz", + "integrity": "sha512-exueuwxef0lUJRnGaVkNSC674eAiWU07ORhxBnevFFZEKisln+09Qrtw823iyv5I1N8T+wKfh95xvtWQrNKNQw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.953.0", - "@smithy/types": "^4.10.0", + "@aws-sdk/types": "3.957.0", + "@smithy/types": "^4.11.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.953.0.tgz", - "integrity": "sha512-HjJ0Nq/57kg0QCjt8mwBAK6C34njKezy9ItUNcrWITzrBZv7ikQtyqM1pindAIzgLaBb7ly/0kbJqMY+NPbcJA==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.957.0.tgz", + "integrity": "sha512-ycbYCwqXk4gJGp0Oxkzf2KBeeGBdTxz559D41NJP8FlzSej1Gh7Rk40Zo6AyTfsNWkrl/kVi1t937OIzC5t+9Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/types": "^4.10.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -874,12 +1162,12 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.953.0.tgz", - "integrity": "sha512-Zmrj21jQ2OeOJGr9spPiN00aQvXa/WUqRXcTVENhrMt+OFoSOfDFpYhUj9NQ09QmQ8KMWFoWuWW6iKurNqLvAA==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.957.0.tgz", + "integrity": "sha512-Ai5iiQqS8kJ5PjzMhWcLKN0G2yasAkvpnPlq2EnqlIMdB48HsizElt62qcktdxp4neRMyGkFq4NzgmDbXnhRiA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" }, @@ -2275,6 +2563,436 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@leanstacks/lambda-utils": { + "version": "0.4.0-alpha.1", + "resolved": "https://registry.npmjs.org/@leanstacks/lambda-utils/-/lambda-utils-0.4.0-alpha.1.tgz", + "integrity": "sha512-q+xvGG56BWpRaODNOv+nO3uDGag81b/z+z8+TnUrohs40LDrsDBVvU6lLlYDBoSl8MpLZODuxSNT5cB0g9R6DA==", + "license": "MIT", + "dependencies": { + "@aws-sdk/client-dynamodb": "3.957.0", + "@aws-sdk/client-lambda": "3.957.0", + "@aws-sdk/client-sns": "3.957.0", + "@aws-sdk/client-sqs": "3.957.0", + "@aws-sdk/lib-dynamodb": "3.957.0", + "pino": "10.1.0", + "pino-lambda": "4.4.1", + "zod": "4.2.1" + } + }, + "node_modules/@leanstacks/lambda-utils/node_modules/@aws-sdk/client-dynamodb": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.957.0.tgz", + "integrity": "sha512-H/uHYgZTmFUq2qb4b/GTxT2F8Yyqyb3pMpI3mldGINZkPYQYN9pP246pqnf+OYOClPMxSMRchrbjZgZhADMi8Q==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/credential-provider-node": "3.957.0", + "@aws-sdk/dynamodb-codec": "3.957.0", + "@aws-sdk/middleware-endpoint-discovery": "3.957.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@leanstacks/lambda-utils/node_modules/@aws-sdk/client-lambda": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.957.0.tgz", + "integrity": "sha512-g7ZXBME+Um6ViY0y7HCGd8U1NlsbyZEQQe3dtw5jRcUl5zNAE76SxYKWkZo9GDdXzNqPJTFTjKlv18m4FyGJIA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/credential-provider-node": "3.957.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/eventstream-serde-browser": "^4.2.7", + "@smithy/eventstream-serde-config-resolver": "^4.3.7", + "@smithy/eventstream-serde-node": "^4.2.7", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-stream": "^4.5.8", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@leanstacks/lambda-utils/node_modules/@aws-sdk/client-sns": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sns/-/client-sns-3.957.0.tgz", + "integrity": "sha512-edRPKM7u8rB4LBe+bv5xWcOlUMvlCH9kNT+fIa7YcSBzLYGHedE6pbgAwACwTUnBOoaKKPGZZ9Fm9o4ZD8/JXQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/credential-provider-node": "3.957.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@leanstacks/lambda-utils/node_modules/@aws-sdk/client-sso": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.957.0.tgz", + "integrity": "sha512-iRdRjd+IpOogqRPt8iNRcg30J53z4rRfMviGwpKgsEa/fx3inCUPOuca3Ap7ZDES0atnEg3KGSJ3V/NQiEJ4BA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@leanstacks/lambda-utils/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.957.0.tgz", + "integrity": "sha512-YuoZmIeE91YIeUfihh8SiSu546KtTvU+4rG5SaL30U9+nGq6P11GRRgqF0ANUyRseLC9ONHt+utar4gbO3++og==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/credential-provider-env": "3.957.0", + "@aws-sdk/credential-provider-http": "3.957.0", + "@aws-sdk/credential-provider-login": "3.957.0", + "@aws-sdk/credential-provider-process": "3.957.0", + "@aws-sdk/credential-provider-sso": "3.957.0", + "@aws-sdk/credential-provider-web-identity": "3.957.0", + "@aws-sdk/nested-clients": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@leanstacks/lambda-utils/node_modules/@aws-sdk/credential-provider-login": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.957.0.tgz", + "integrity": "sha512-XcD5NEQDWYk8B4gs89bkwf2d+DNF8oS2NR5RoHJEbX4l8KErVATUjpEYVn6/rAFEktungxlYTnQ5wh0cIQvP5w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/nested-clients": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@leanstacks/lambda-utils/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.957.0.tgz", + "integrity": "sha512-b9FT/7BQcJ001w+3JbTiJXfxHrWvPb7zDvvC1i1FKcNOvyCt3BGu04n4nO/b71a3iBnbfBXI89hCIZQsuLcEgw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.957.0", + "@aws-sdk/credential-provider-http": "3.957.0", + "@aws-sdk/credential-provider-ini": "3.957.0", + "@aws-sdk/credential-provider-process": "3.957.0", + "@aws-sdk/credential-provider-sso": "3.957.0", + "@aws-sdk/credential-provider-web-identity": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@leanstacks/lambda-utils/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.957.0.tgz", + "integrity": "sha512-gTLPJFOkGtn3tVGglRhCar2oOobK1YctZRAT8nfJr17uaSRoAP46zIIHNYBZZUMqImb0qAHD9Ugm+Zd9sIqxyA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.957.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/token-providers": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@leanstacks/lambda-utils/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.957.0.tgz", + "integrity": "sha512-x17xMeD7c+rKEsWachGIMifACqkugskrETWz18QDWismFcrmUuOcZu5rUa8s9y1pnITLKUQ1xU/qDLPH52jLlA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/nested-clients": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@leanstacks/lambda-utils/node_modules/@aws-sdk/lib-dynamodb": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.957.0.tgz", + "integrity": "sha512-v/GnmDIFfqDQub4tEp/QtsWOMMvSilMbbhCv0l5rPV9sd1NUmCtrH8iBHqVKeEmwSsiCzEKNTakmxSUJszrEww==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/util-dynamodb": "3.957.0", + "@smithy/core": "^3.20.0", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.957.0" + } + }, + "node_modules/@leanstacks/lambda-utils/node_modules/@aws-sdk/nested-clients": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.957.0.tgz", + "integrity": "sha512-PZUFtaUTSZWO+mbgQGWSiwz3EqedsuKNb7Xoxjzh5rfJE352DD4/jScQEhVPxvdLw62IK9b5UDu5kZlxzBs9Ow==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@leanstacks/lambda-utils/node_modules/@aws-sdk/token-providers": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.957.0.tgz", + "integrity": "sha512-oSwo3BZ6gcvhjTg036V0UQmtENUeNwfCU35iDckX961CdI1alQ3TKRWLzKrwvXCbrOx+bZsuA1PHsTbNhI/+Fw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/nested-clients": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@leanstacks/lambda-utils/node_modules/@aws-sdk/util-dynamodb": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.957.0.tgz", + "integrity": "sha512-EqFfOkNZt4oJdyxFoP+PxO4JoEnAnTNOiwLKr57F0Hi+Qp5WYPJHdMFtHrzw6j2+atXQIfQUvtk1q6eQbcvMTw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.957.0" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", @@ -2319,9 +3037,9 @@ } }, "node_modules/@sinclair/typebox": { - "version": "0.34.41", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", - "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", + "version": "0.34.43", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.43.tgz", + "integrity": "sha512-zD2/pAP6nq+ypyNPxsDhc5as3BamstGwCvhOEFTvull2qog9fdb/sj4m/431mk6iuXXW+WE1PER94uBDqxkoIg==", "dev": true, "license": "MIT" }, @@ -2346,12 +3064,12 @@ } }, "node_modules/@smithy/abort-controller": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.6.tgz", - "integrity": "sha512-P7JD4J+wxHMpGxqIg6SHno2tPkZbBUBLbPpR5/T1DEUvw/mEaINBMaPFZNM7lA+ToSCZ36j6nMHa+5kej+fhGg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.7.tgz", + "integrity": "sha512-rzMY6CaKx2qxrbYbqjXWS0plqEy7LOdKHS0bg4ixJ6aoGDPNUcLWk/FRNuCILh7GKLG9TFUXYYeQQldMBBwuyw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2359,16 +3077,16 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.4.tgz", - "integrity": "sha512-s3U5ChS21DwU54kMmZ0UJumoS5cg0+rGVZvN6f5Lp6EbAVi0ZyP+qDSHdewfmXKUgNK1j3z45JyzulkDukrjAA==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.5.tgz", + "integrity": "sha512-HAGoUAFYsUkoSckuKbCPayECeMim8pOu+yLy1zOxt1sifzEbrsRpYa+mKcMdiHKMeiqOibyPG0sFJnmaV/OGEg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.6", - "@smithy/types": "^4.10.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-endpoints": "^3.2.6", - "@smithy/util-middleware": "^4.2.6", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", "tslib": "^2.6.2" }, "engines": { @@ -2376,18 +3094,18 @@ } }, "node_modules/@smithy/core": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.19.0.tgz", - "integrity": "sha512-Y9oHXpBcXQgYHOcAEmxjkDilUbSTkgKjoHYed3WaYUH8jngq8lPWDBSpjHblJ9uOgBdy5mh3pzebrScDdYr29w==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.20.0.tgz", + "integrity": "sha512-WsSHCPq/neD5G/MkK4csLI5Y5Pkd9c1NMfpYEKeghSGaD4Ja1qLIohRQf2D5c1Uy5aXp76DeKHkzWZ9KAlHroQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.2.7", - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-middleware": "^4.2.6", - "@smithy/util-stream": "^4.5.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-stream": "^4.5.8", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" @@ -2397,15 +3115,15 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.6.tgz", - "integrity": "sha512-xBmawExyTzOjbhzkZwg+vVm/khg28kG+rj2sbGlULjFd1jI70sv/cbpaR0Ev4Yfd6CpDUDRMe64cTqR//wAOyA==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.7.tgz", + "integrity": "sha512-CmduWdCiILCRNbQWFR0OcZlUPVtyE49Sr8yYL0rZQ4D/wKxiNzBNS/YHemvnbkIWj623fplgkexUd/c9CAKdoA==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.6", - "@smithy/property-provider": "^4.2.6", - "@smithy/types": "^4.10.0", - "@smithy/url-parser": "^4.2.6", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", "tslib": "^2.6.2" }, "engines": { @@ -2413,13 +3131,13 @@ } }, "node_modules/@smithy/eventstream-codec": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.6.tgz", - "integrity": "sha512-OZfsI+YRG26XZik/jKMMg37acnBSbUiK/8nETW3uM3mLj+0tMmFXdHQw1e5WEd/IHN8BGOh3te91SNDe2o4RHg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.7.tgz", + "integrity": "sha512-DrpkEoM3j9cBBWhufqBwnbbn+3nf1N9FP6xuVJ+e220jbactKuQgaZwjwP5CP1t+O94brm2JgVMD2atMGX3xIQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" }, @@ -2428,13 +3146,13 @@ } }, "node_modules/@smithy/eventstream-serde-browser": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.6.tgz", - "integrity": "sha512-6OiaAaEbLB6dEkRbQyNzFSJv5HDvly3Mc6q/qcPd2uS/g3szR8wAIkh7UndAFKfMypNSTuZ6eCBmgCLR5LacTg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.7.tgz", + "integrity": "sha512-ujzPk8seYoDBmABDE5YqlhQZAXLOrtxtJLrbhHMKjBoG5b4dK4i6/mEU+6/7yXIAkqOO8sJ6YxZl+h0QQ1IJ7g==", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.6", - "@smithy/types": "^4.10.0", + "@smithy/eventstream-serde-universal": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2442,12 +3160,12 @@ } }, "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.6.tgz", - "integrity": "sha512-xP5YXbOVRVN8A4pDnSUkEUsL9fYFU6VNhxo8tgr13YnMbf3Pn4xVr+hSyLVjS1Frfi1Uk03ET5Bwml4+0CeYEw==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.7.tgz", + "integrity": "sha512-x7BtAiIPSaNaWuzm24Q/mtSkv+BrISO/fmheiJ39PKRNH3RmH2Hph/bUKSOBOBC9unqfIYDhKTHwpyZycLGPVQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2455,13 +3173,13 @@ } }, "node_modules/@smithy/eventstream-serde-node": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.6.tgz", - "integrity": "sha512-jhH7nJuaOpnTFcuZpWK9dqb6Ge2yGi1okTo0W6wkJrfwAm2vwmO74tF1v07JmrSyHBcKLQATEexclJw9K1Vj7w==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.7.tgz", + "integrity": "sha512-roySCtHC5+pQq5lK4be1fZ/WR6s/AxnPaLfCODIPArtN2du8s5Ot4mKVK3pPtijL/L654ws592JHJ1PbZFF6+A==", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.6", - "@smithy/types": "^4.10.0", + "@smithy/eventstream-serde-universal": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2469,13 +3187,13 @@ } }, "node_modules/@smithy/eventstream-serde-universal": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.6.tgz", - "integrity": "sha512-olIfZ230B64TvPD6b0tPvrEp2eB0FkyL3KvDlqF4RVmIc/kn3orzXnV6DTQdOOW5UU+M5zKY3/BU47X420/oPw==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.7.tgz", + "integrity": "sha512-QVD+g3+icFkThoy4r8wVFZMsIP08taHVKjE6Jpmz8h5CgX/kk6pTODq5cht0OMtcapUx+xrPzUTQdA+TmO0m1g==", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-codec": "^4.2.6", - "@smithy/types": "^4.10.0", + "@smithy/eventstream-codec": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2483,14 +3201,14 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.7", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.7.tgz", - "integrity": "sha512-fcVap4QwqmzQwQK9QU3keeEpCzTjnP9NJ171vI7GnD7nbkAIcP9biZhDUx88uRH9BabSsQDS0unUps88uZvFIQ==", + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.8.tgz", + "integrity": "sha512-h/Fi+o7mti4n8wx1SR6UHWLaakwHRx29sizvp8OOm7iqwKGFneT06GCSFhml6Bha5BT6ot5pj3CYZnCHhGC2Rg==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.6", - "@smithy/querystring-builder": "^4.2.6", - "@smithy/types": "^4.10.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/querystring-builder": "^4.2.7", + "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" }, @@ -2499,12 +3217,12 @@ } }, "node_modules/@smithy/hash-node": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.6.tgz", - "integrity": "sha512-k3Dy9VNR37wfMh2/1RHkFf/e0rMyN0pjY0FdyY6ItJRjENYyVPRMwad6ZR1S9HFm6tTuIOd9pqKBmtJ4VHxvxg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.7.tgz", + "integrity": "sha512-PU/JWLTBCV1c8FtB8tEFnY4eV1tSfBc7bDBADHfn1K+uRbPgSJ9jnJp0hyjiFN2PMdPzxsf1Fdu0eo9fJ760Xw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" @@ -2514,12 +3232,12 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.6.tgz", - "integrity": "sha512-E4t/V/q2T46RY21fpfznd1iSLTvCXKNKo4zJ1QuEFN4SE9gKfu2vb6bgq35LpufkQ+SETWIC7ZAf2GGvTlBaMQ==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.7.tgz", + "integrity": "sha512-ncvgCr9a15nPlkhIUx3CU4d7E7WEuVJOV7fS7nnK2hLtPK9tYRBkMHQbhXU1VvvKeBm/O0x26OEoBq+ngFpOEQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2538,14 +3256,28 @@ "node": ">=18.0.0" } }, + "node_modules/@smithy/md5-js": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.7.tgz", + "integrity": "sha512-Wv6JcUxtOLTnxvNjDnAiATUsk8gvA6EeS8zzHig07dotpByYsLot+m0AaQEniUBjx97AC41MQR4hW0baraD1Xw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@smithy/middleware-content-length": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.6.tgz", - "integrity": "sha512-0cjqjyfj+Gls30ntq45SsBtqF3dfJQCeqQPyGz58Pk8OgrAr5YiB7ZvDzjCA94p4r6DCI4qLm7FKobqBjf515w==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.7.tgz", + "integrity": "sha512-GszfBfCcvt7kIbJ41LuNa5f0wvQCHhnGx/aDaZJCCT05Ld6x6U2s0xsc/0mBFONBZjQJp2U/0uSJ178OXOwbhg==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2553,18 +3285,18 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.0.tgz", - "integrity": "sha512-M6qWfUNny6NFNy8amrCGIb9TfOMUkHVtg9bHtEFGRgfH7A7AtPpn/fcrToGPjVDK1ECuMVvqGQOXcZxmu9K+7A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.19.0", - "@smithy/middleware-serde": "^4.2.7", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/shared-ini-file-loader": "^4.4.1", - "@smithy/types": "^4.10.0", - "@smithy/url-parser": "^4.2.6", - "@smithy/util-middleware": "^4.2.6", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.1.tgz", + "integrity": "sha512-gpLspUAoe6f1M6H0u4cVuFzxZBrsGZmjx2O9SigurTx4PbntYa4AJ+o0G0oGm1L2oSX6oBhcGHwrfJHup2JnJg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.20.0", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-middleware": "^4.2.7", "tslib": "^2.6.2" }, "engines": { @@ -2572,18 +3304,18 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.4.16", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.16.tgz", - "integrity": "sha512-XPpNhNRzm3vhYm7YCsyw3AtmWggJbg1wNGAoqb7NBYr5XA5isMRv14jgbYyUV6IvbTBFZQdf2QpeW43LrRdStQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/service-error-classification": "^4.2.6", - "@smithy/smithy-client": "^4.10.1", - "@smithy/types": "^4.10.0", - "@smithy/util-middleware": "^4.2.6", - "@smithy/util-retry": "^4.2.6", + "version": "4.4.17", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.17.tgz", + "integrity": "sha512-MqbXK6Y9uq17h+4r0ogu/sBT6V/rdV+5NvYL7ZV444BKfQygYe8wAhDrVXagVebN6w2RE0Fm245l69mOsPGZzg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/service-error-classification": "^4.2.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" }, @@ -2592,13 +3324,13 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "4.2.7", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.7.tgz", - "integrity": "sha512-PFMVHVPgtFECeu4iZ+4SX6VOQT0+dIpm4jSPLLL6JLSkp9RohGqKBKD0cbiXdeIFS08Forp0UHI6kc0gIHenSA==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.8.tgz", + "integrity": "sha512-8rDGYen5m5+NV9eHv9ry0sqm2gI6W7mc1VSFMtn6Igo25S507/HaOX9LTHAS2/J32VXD0xSzrY0H5FJtOMS4/w==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2606,12 +3338,12 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.6.tgz", - "integrity": "sha512-JSbALU3G+JS4kyBZPqnJ3hxIYwOVRV7r9GNQMS6j5VsQDo5+Es5nddLfr9TQlxZLNHPvKSh+XSB0OuWGfSWFcA==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.7.tgz", + "integrity": "sha512-bsOT0rJ+HHlZd9crHoS37mt8qRRN/h9jRve1SXUhVbkRzu0QaNYZp1i1jha4n098tsvROjcwfLlfvcFuJSXEsw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2619,14 +3351,14 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.6.tgz", - "integrity": "sha512-fYEyL59Qe82Ha1p97YQTMEQPJYmBS+ux76foqluaTVWoG9Px5J53w6NvXZNE3wP7lIicLDF7Vj1Em18XTX7fsA==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.7.tgz", + "integrity": "sha512-7r58wq8sdOcrwWe+klL9y3bc4GW1gnlfnFOuL7CXa7UzfhzhxKuzNdtqgzmTV+53lEp9NXh5hY/S4UgjLOzPfw==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.6", - "@smithy/shared-ini-file-loader": "^4.4.1", - "@smithy/types": "^4.10.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2634,15 +3366,15 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.6.tgz", - "integrity": "sha512-Gsb9jf4ido5BhPfani4ggyrKDd3ZK+vTFWmUaZeFg5G3E5nhFmqiTzAIbHqmPs1sARuJawDiGMGR/nY+Gw6+aQ==", + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.7.tgz", + "integrity": "sha512-NELpdmBOO6EpZtWgQiHjoShs1kmweaiNuETUpuup+cmm/xJYjT4eUjfhrXRP4jCOaAsS3c3yPsP3B+K+/fyPCQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.2.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/querystring-builder": "^4.2.6", - "@smithy/types": "^4.10.0", + "@smithy/abort-controller": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/querystring-builder": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2650,12 +3382,12 @@ } }, "node_modules/@smithy/property-provider": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.6.tgz", - "integrity": "sha512-a/tGSLPtaia2krbRdwR4xbZKO8lU67DjMk/jfY4QKt4PRlKML+2tL/gmAuhNdFDioO6wOq0sXkfnddNFH9mNUA==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.7.tgz", + "integrity": "sha512-jmNYKe9MGGPoSl/D7JDDs1C8b3dC8f/w78LbaVfoTtWy4xAd5dfjaFG9c9PWPihY4ggMQNQSMtzU77CNgAJwmA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2663,12 +3395,12 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.6.tgz", - "integrity": "sha512-qLRZzP2+PqhE3OSwvY2jpBbP0WKTZ9opTsn+6IWYI0SKVpbG+imcfNxXPq9fj5XeaUTr7odpsNpK6dmoiM1gJQ==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.7.tgz", + "integrity": "sha512-1r07pb994I20dD/c2seaZhoCuNYm0rWrvBxhCQ70brNh11M5Ml2ew6qJVo0lclB3jMIXirD4s2XRXRe7QEi0xA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2676,12 +3408,12 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.6.tgz", - "integrity": "sha512-MeM9fTAiD3HvoInK/aA8mgJaKQDvm8N0dKy6EiFaCfgpovQr4CaOkJC28XqlSRABM+sHdSQXbC8NZ0DShBMHqg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.7.tgz", + "integrity": "sha512-eKONSywHZxK4tBxe2lXEysh8wbBdvDWiA+RIuaxZSgCMmA0zMgoDpGLJhnyj+c0leOQprVnXOmcB4m+W9Rw7sg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" }, @@ -2690,12 +3422,12 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.6.tgz", - "integrity": "sha512-YmWxl32SQRw/kIRccSOxzS/Ib8/b5/f9ex0r5PR40jRJg8X1wgM3KrR2In+8zvOGVhRSXgvyQpw9yOSlmfmSnA==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.7.tgz", + "integrity": "sha512-3X5ZvzUHmlSTHAXFlswrS6EGt8fMSIxX/c3Rm1Pni3+wYWB6cjGocmRIoqcQF9nU5OgGmL0u7l9m44tSUpfj9w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2703,24 +3435,24 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.6.tgz", - "integrity": "sha512-Q73XBrzJlGTut2nf5RglSntHKgAG0+KiTJdO5QQblLfr4TdliGwIAha1iZIjwisc3rA5ulzqwwsYC6xrclxVQg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.7.tgz", + "integrity": "sha512-YB7oCbukqEb2Dlh3340/8g8vNGbs/QsNNRms+gv3N2AtZz9/1vSBx6/6tpwQpZMEJFs7Uq8h4mmOn48ZZ72MkA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0" + "@smithy/types": "^4.11.0" }, "engines": { "node": ">=18.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.1.tgz", - "integrity": "sha512-tph+oQYPbpN6NamF030hx1gb5YN2Plog+GLaRHpoEDwp8+ZPG26rIJvStG9hkWzN2HBn3HcWg0sHeB0tmkYzqA==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.2.tgz", + "integrity": "sha512-M7iUUff/KwfNunmrgtqBfvZSzh3bmFgv/j/t1Y1dQ+8dNo34br1cqVEqy6v0mYEgi0DkGO7Xig0AnuOaEGVlcg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2728,16 +3460,16 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.6.tgz", - "integrity": "sha512-P1TXDHuQMadTMTOBv4oElZMURU4uyEhxhHfn+qOc2iofW9Rd4sZtBGx58Lzk112rIGVEYZT8eUMK4NftpewpRA==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.7.tgz", + "integrity": "sha512-9oNUlqBlFZFOSdxgImA6X5GFuzE7V2H7VG/7E70cdLhidFbdtvxxt81EHgykGK5vq5D3FafH//X+Oy31j3CKOg==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^4.2.0", - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-middleware": "^4.2.6", + "@smithy/util-middleware": "^4.2.7", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" @@ -2747,17 +3479,17 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.1.tgz", - "integrity": "sha512-1ovWdxzYprhq+mWqiGZlt3kF69LJthuQcfY9BIyHx9MywTFKzFapluku1QXoaBB43GCsLDxNqS+1v30ure69AA==", + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.2.tgz", + "integrity": "sha512-D5z79xQWpgrGpAHb054Fn2CCTQZpog7JELbVQ6XAvXs5MNKWf28U9gzSBlJkOyMl9LA1TZEjRtwvGXfP0Sl90g==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.19.0", - "@smithy/middleware-endpoint": "^4.4.0", - "@smithy/middleware-stack": "^4.2.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", - "@smithy/util-stream": "^4.5.7", + "@smithy/core": "^3.20.0", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-stream": "^4.5.8", "tslib": "^2.6.2" }, "engines": { @@ -2765,9 +3497,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.10.0.tgz", - "integrity": "sha512-K9mY7V/f3Ul+/Gz4LJANZ3vJ/yiBIwCyxe0sPT4vNJK63Srvd+Yk1IzP0t+nE7XFSpIGtzR71yljtnqpUTYFlQ==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.11.0.tgz", + "integrity": "sha512-mlrmL0DRDVe3mNrjTcVcZEgkFmufITfUAPBEA+AHYiIeYyJebso/He1qLbP3PssRe22KUzLRpQSdBPbXdgZ2VA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -2777,13 +3509,13 @@ } }, "node_modules/@smithy/url-parser": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.6.tgz", - "integrity": "sha512-tVoyzJ2vXp4R3/aeV4EQjBDmCuWxRa8eo3KybL7Xv4wEM16nObYh7H1sNfcuLWHAAAzb0RVyxUz1S3sGj4X+Tg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.7.tgz", + "integrity": "sha512-/RLtVsRV4uY3qPWhBDsjwahAtt3x2IsMGnP5W1b2VZIe+qgCqkLxI1UOHDZp1Q1QSOrdOR32MF3Ph2JfWT1VHg==", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.2.6", - "@smithy/types": "^4.10.0", + "@smithy/querystring-parser": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2854,14 +3586,14 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.15", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.15.tgz", - "integrity": "sha512-LiZQVAg/oO8kueX4c+oMls5njaD2cRLXRfcjlTYjhIqmwHnCwkQO5B3dMQH0c5PACILxGAQf6Mxsq7CjlDc76A==", + "version": "4.3.16", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.16.tgz", + "integrity": "sha512-/eiSP3mzY3TsvUOYMeL4EqUX6fgUOj2eUOU4rMMgVbq67TiRLyxT7Xsjxq0bW3OwuzK009qOwF0L2OgJqperAQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.6", - "@smithy/smithy-client": "^4.10.1", - "@smithy/types": "^4.10.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2869,17 +3601,17 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.18", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.18.tgz", - "integrity": "sha512-Kw2J+KzYm9C9Z9nY6+W0tEnoZOofstVCMTshli9jhQbQCy64rueGfKzPfuFBnVUqZD9JobxTh2DzHmPkp/Va/Q==", + "version": "4.2.19", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.19.tgz", + "integrity": "sha512-3a4+4mhf6VycEJyHIQLypRbiwG6aJvbQAeRAVXydMmfweEPnLLabRbdyo/Pjw8Rew9vjsh5WCdhmDaHkQnhhhA==", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.4.4", - "@smithy/credential-provider-imds": "^4.2.6", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/property-provider": "^4.2.6", - "@smithy/smithy-client": "^4.10.1", - "@smithy/types": "^4.10.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2887,13 +3619,13 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.6.tgz", - "integrity": "sha512-v60VNM2+mPvgHCBXEfMCYrQ0RepP6u6xvbAkMenfe4Mi872CqNkJzgcnQL837e8NdeDxBgrWQRTluKq5Lqdhfg==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.7.tgz", + "integrity": "sha512-s4ILhyAvVqhMDYREeTS68R43B1V5aenV5q/V1QpRQJkCXib5BPRo4s7uNdzGtIKxaPHCfU/8YkvPAEvTpxgspg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.6", - "@smithy/types": "^4.10.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2913,12 +3645,12 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.6.tgz", - "integrity": "sha512-qrvXUkxBSAFomM3/OEMuDVwjh4wtqK8D2uDZPShzIqOylPst6gor2Cdp6+XrH4dyksAWq/bE2aSDYBTTnj0Rxg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.7.tgz", + "integrity": "sha512-i1IkpbOae6NvIKsEeLLM9/2q4X+M90KV3oCFgWQI4q0Qz+yUZvsr+gZPdAEAtFhWQhAHpTsJO8DRJPuwVyln+w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.10.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2926,13 +3658,13 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.6.tgz", - "integrity": "sha512-x7CeDQLPQ9cb6xN7fRJEjlP9NyGW/YeXWc4j/RUhg4I+H60F0PEeRc2c/z3rm9zmsdiMFzpV/rT+4UHW6KM1SA==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.7.tgz", + "integrity": "sha512-SvDdsQyF5CIASa4EYVT02LukPHVzAgUA4kMAuZ97QJc2BpAqZfA4PINB8/KOoCXEw9tsuv/jQjMeaHFvxdLNGg==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.2.6", - "@smithy/types": "^4.10.0", + "@smithy/service-error-classification": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2940,14 +3672,14 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.7.tgz", - "integrity": "sha512-Uuy4S5Aj4oF6k1z+i2OtIBJUns4mlg29Ph4S+CqjR+f4XXpSFVgTCYLzMszHJTicYDBxKFtwq2/QSEDSS5l02A==", + "version": "4.5.8", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.8.tgz", + "integrity": "sha512-ZnnBhTapjM0YPGUSmOs0Mcg/Gg87k503qG4zU2v/+Js2Gu+daKOJMeqcQns8ajepY8tgzzfYxl6kQyZKml6O2w==", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.3.7", - "@smithy/node-http-handler": "^4.4.6", - "@smithy/types": "^4.10.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", @@ -2984,13 +3716,13 @@ } }, "node_modules/@smithy/util-waiter": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.6.tgz", - "integrity": "sha512-xU9HwUSik9UUCJmm530yvBy0AwlQFICveKmqvaaTukKkXEAhyiBdHtSrhPrH3rH+uz0ykyaE3LdgsX86C6mDCQ==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.7.tgz", + "integrity": "sha512-vHJFXi9b7kUEpHWUCY3Twl+9NPOZvQ0SAi+Ewtn48mbiJk4JY9MZmKQjGB4SCvVb9WPiSphZJYY6RIbs+grrzw==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.2.6", - "@smithy/types": "^4.10.0", + "@smithy/abort-controller": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -3188,17 +3920,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.0.tgz", - "integrity": "sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==", + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.1.tgz", + "integrity": "sha512-PKhLGDq3JAg0Jk/aK890knnqduuI/Qj+udH7wCf0217IGi4gt+acgCyPVe79qoT+qKUvHMDQkwJeKW9fwl8Cyw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.50.0", - "@typescript-eslint/type-utils": "8.50.0", - "@typescript-eslint/utils": "8.50.0", - "@typescript-eslint/visitor-keys": "8.50.0", + "@typescript-eslint/scope-manager": "8.50.1", + "@typescript-eslint/type-utils": "8.50.1", + "@typescript-eslint/utils": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" @@ -3211,23 +3943,23 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.50.0", + "@typescript-eslint/parser": "^8.50.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.0.tgz", - "integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==", + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.1.tgz", + "integrity": "sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.50.0", - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/typescript-estree": "8.50.0", - "@typescript-eslint/visitor-keys": "8.50.0", + "@typescript-eslint/scope-manager": "8.50.1", + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/typescript-estree": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1", "debug": "^4.3.4" }, "engines": { @@ -3243,14 +3975,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.0.tgz", - "integrity": "sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==", + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.1.tgz", + "integrity": "sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.50.0", - "@typescript-eslint/types": "^8.50.0", + "@typescript-eslint/tsconfig-utils": "^8.50.1", + "@typescript-eslint/types": "^8.50.1", "debug": "^4.3.4" }, "engines": { @@ -3265,14 +3997,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.0.tgz", - "integrity": "sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==", + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.1.tgz", + "integrity": "sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/visitor-keys": "8.50.0" + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3283,9 +4015,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.0.tgz", - "integrity": "sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==", + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.1.tgz", + "integrity": "sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==", "dev": true, "license": "MIT", "engines": { @@ -3300,15 +4032,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.0.tgz", - "integrity": "sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==", + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.1.tgz", + "integrity": "sha512-7J3bf022QZE42tYMO6SL+6lTPKFk/WphhRPe9Tw/el+cEwzLz1Jjz2PX3GtGQVxooLDKeMVmMt7fWpYRdG5Etg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/typescript-estree": "8.50.0", - "@typescript-eslint/utils": "8.50.0", + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/typescript-estree": "8.50.1", + "@typescript-eslint/utils": "8.50.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -3325,9 +4057,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.0.tgz", - "integrity": "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==", + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.1.tgz", + "integrity": "sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==", "dev": true, "license": "MIT", "engines": { @@ -3339,16 +4071,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.0.tgz", - "integrity": "sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==", + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.1.tgz", + "integrity": "sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.50.0", - "@typescript-eslint/tsconfig-utils": "8.50.0", - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/visitor-keys": "8.50.0", + "@typescript-eslint/project-service": "8.50.1", + "@typescript-eslint/tsconfig-utils": "8.50.1", + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", @@ -3367,16 +4099,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.0.tgz", - "integrity": "sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==", + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.1.tgz", + "integrity": "sha512-lCLp8H1T9T7gPbEuJSnHwnSuO9mDf8mfK/Nion5mZmiEaQD9sWf9W4dfeFqRyqRjF06/kBuTmAqcs9sewM2NbQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.50.0", - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/typescript-estree": "8.50.0" + "@typescript-eslint/scope-manager": "8.50.1", + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/typescript-estree": "8.50.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3391,13 +4123,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.0.tgz", - "integrity": "sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==", + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.1.tgz", + "integrity": "sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.50.0", + "@typescript-eslint/types": "8.50.1", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -3940,9 +4672,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.9.8", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.8.tgz", - "integrity": "sha512-Y1fOuNDowLfgKOypdc9SPABfoWXuZHBOyCS4cD52IeZBhr4Md6CLLs6atcxVrzRmQ06E7hSlm5bHHApPKR/byA==", + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4064,9 +4796,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001760", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz", - "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==", + "version": "1.0.30001761", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001761.tgz", + "integrity": "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==", "dev": true, "funding": [ { @@ -4305,9 +5037,9 @@ } }, "node_modules/dedent": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", - "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", + "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -7472,16 +8204,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.50.0.tgz", - "integrity": "sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A==", + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.50.1.tgz", + "integrity": "sha512-ytTHO+SoYSbhAH9CrYnMhiLx8To6PSSvqnvXyPUgPETCvB6eBKmTI9w6XMPS3HsBRGkwTVBX+urA8dYQx6bHfQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.50.0", - "@typescript-eslint/parser": "8.50.0", - "@typescript-eslint/typescript-estree": "8.50.0", - "@typescript-eslint/utils": "8.50.0" + "@typescript-eslint/eslint-plugin": "8.50.1", + "@typescript-eslint/parser": "8.50.1", + "@typescript-eslint/typescript-estree": "8.50.1", + "@typescript-eslint/utils": "8.50.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7552,9 +8284,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", - "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { diff --git a/package.json b/package.json index e2a3703..a3c7553 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,8 @@ "@types/aws-lambda": "8.10.159", "@types/jest": "30.0.0", "@types/node": "25.0.3", - "@typescript-eslint/eslint-plugin": "8.50.0", - "@typescript-eslint/parser": "8.50.0", + "@typescript-eslint/eslint-plugin": "8.50.1", + "@typescript-eslint/parser": "8.50.1", "eslint": "9.39.2", "eslint-config-prettier": "10.1.8", "globals": "16.5.0", @@ -51,15 +51,14 @@ "ts-jest": "29.4.6", "ts-node": "10.9.2", "typescript": "5.9.3", - "typescript-eslint": "8.50.0" + "typescript-eslint": "8.50.1" }, "dependencies": { - "@aws-sdk/client-dynamodb": "3.953.0", - "@aws-sdk/client-lambda": "3.953.0", - "@aws-sdk/client-sns": "3.953.0", - "@aws-sdk/lib-dynamodb": "3.953.0", - "pino": "10.1.0", - "pino-lambda": "4.4.1", + "@aws-sdk/client-dynamodb": "3.958.0", + "@aws-sdk/client-lambda": "3.958.0", + "@aws-sdk/client-sns": "3.958.0", + "@aws-sdk/lib-dynamodb": "3.958.0", + "@leanstacks/lambda-utils": "0.4.0-alpha.1", "zod": "4.2.1" } } diff --git a/src/handlers/create-task.ts b/src/handlers/create-task.ts index fb5ebd0..e70dde6 100644 --- a/src/handlers/create-task.ts +++ b/src/handlers/create-task.ts @@ -1,17 +1,11 @@ import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; -import { lambdaRequestTracker } from 'pino-lambda'; import { ZodError } from 'zod'; +import { badRequest, created, internalServerError, withRequestTracking } from '@leanstacks/lambda-utils'; -import { CreateTaskDtoSchema } from '@/models/create-task-dto.js'; -import { createTask } from '@/services/task-service.js'; -import { badRequest, created, internalServerError } from '@/utils/apigateway-response.js'; -import { logger } from '@/utils/logger.js'; - -/** - * Lambda request tracker middleware for logging. - * @see https://www.npmjs.com/package/pino-lambda#best-practices - */ -const withRequestTracking = lambdaRequestTracker(); +import { defaultResponseHeaders } from '@/utils/constants'; +import { CreateTaskDtoSchema } from '@/models/create-task-dto'; +import { createTask } from '@/services/task-service'; +import { logger } from '@/utils/logger'; /** * Lambda handler for creating a new task @@ -28,7 +22,7 @@ export const handler = async (event: APIGatewayProxyEvent, context: Context): Pr // Parse and validate request body if (!event.body) { logger.warn('[CreateTaskHandler] < handler - missing request body'); - return badRequest('Request body is required'); + return badRequest('Request body is required', defaultResponseHeaders); } let requestBody: unknown; @@ -36,7 +30,7 @@ export const handler = async (event: APIGatewayProxyEvent, context: Context): Pr requestBody = JSON.parse(event.body); } catch (_error) { logger.warn('[CreateTaskHandler] < handler - invalid JSON in request body'); - return badRequest('Invalid JSON in request body'); + return badRequest('Invalid JSON in request body', defaultResponseHeaders); } // Validate request body against schema @@ -52,7 +46,7 @@ export const handler = async (event: APIGatewayProxyEvent, context: Context): Pr '[CreateTaskHandler] < handler - successfully created task', ); // Return created response with the new task - return created(task); + return created(task, defaultResponseHeaders); } catch (error) { if (error instanceof ZodError) { // Handle validation errors @@ -63,11 +57,11 @@ export const handler = async (event: APIGatewayProxyEvent, context: Context): Pr }, '[CreateTaskHandler] < handler - validation error', ); - return badRequest(`Validation failed: ${errorMessages}`); + return badRequest(`Validation failed: ${errorMessages}`, defaultResponseHeaders); } // Handle other errors logger.error({ error }, '[CreateTaskHandler] < handler - failed to create task'); - return internalServerError('Failed to create task'); + return internalServerError('Failed to create task', defaultResponseHeaders); } }; diff --git a/src/handlers/delete-task.ts b/src/handlers/delete-task.ts index 37f005f..9b702b0 100644 --- a/src/handlers/delete-task.ts +++ b/src/handlers/delete-task.ts @@ -1,15 +1,9 @@ import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; -import { lambdaRequestTracker } from 'pino-lambda'; +import { internalServerError, noContent, notFound, withRequestTracking } from '@leanstacks/lambda-utils'; -import { deleteTask } from '@/services/task-service.js'; -import { internalServerError, noContent, notFound } from '@/utils/apigateway-response.js'; -import { logger } from '@/utils/logger.js'; - -/** - * Lambda request tracker middleware for logging. - * @see https://www.npmjs.com/package/pino-lambda#best-practices - */ -const withRequestTracking = lambdaRequestTracker(); +import { defaultResponseHeaders } from '@/utils/constants'; +import { deleteTask } from '@/services/task-service'; +import { logger } from '@/utils/logger'; /** * Lambda handler for deleting a task by ID @@ -28,7 +22,7 @@ export const handler = async (event: APIGatewayProxyEvent, context: Context): Pr if (!taskId) { logger.warn('[DeleteTaskHandler] < handler - missing taskId path parameter'); - return notFound('Task not found'); + return notFound('Task not found', defaultResponseHeaders); } // Delete the task @@ -37,15 +31,15 @@ export const handler = async (event: APIGatewayProxyEvent, context: Context): Pr // Check if the task was found and deleted if (!deleted) { logger.info({ taskId }, '[DeleteTaskHandler] < handler - task not found'); - return notFound('Task not found'); + return notFound('Task not found', defaultResponseHeaders); } // Return no content response logger.info({ taskId }, '[DeleteTaskHandler] < handler - successfully deleted task'); - return noContent(); + return noContent(defaultResponseHeaders); } catch (error) { // Handle unexpected errors logger.error({ error }, '[DeleteTaskHandler] < handler - failed to delete task'); - return internalServerError('Failed to delete task'); + return internalServerError('Failed to delete task', defaultResponseHeaders); } }; diff --git a/src/handlers/get-task.ts b/src/handlers/get-task.ts index cb88292..04a894f 100644 --- a/src/handlers/get-task.ts +++ b/src/handlers/get-task.ts @@ -1,15 +1,9 @@ import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; -import { lambdaRequestTracker } from 'pino-lambda'; +import { internalServerError, notFound, ok, withRequestTracking } from '@leanstacks/lambda-utils'; -import { getTask } from '@/services/task-service.js'; -import { internalServerError, notFound, ok } from '@/utils/apigateway-response.js'; -import { logger } from '@/utils/logger.js'; - -/** - * Lambda request tracker middleware for logging. - * @see https://www.npmjs.com/package/pino-lambda#best-practices - */ -const withRequestTracking = lambdaRequestTracker(); +import { defaultResponseHeaders } from '@/utils/constants'; +import { getTask } from '@/services/task-service'; +import { logger } from '@/utils/logger'; /** * Lambda handler for retrieving a task by ID @@ -28,7 +22,7 @@ export const handler = async (event: APIGatewayProxyEvent, context: Context): Pr if (!taskId) { logger.warn('[GetTaskHandler] < handler - missing taskId path parameter'); - return notFound('Task not found'); + return notFound('Task not found', defaultResponseHeaders); } // Retrieve the task @@ -37,15 +31,15 @@ export const handler = async (event: APIGatewayProxyEvent, context: Context): Pr // Check if the task was found if (!task) { logger.info({ taskId }, '[GetTaskHandler] < handler - task not found'); - return notFound('Task not found'); + return notFound('Task not found', defaultResponseHeaders); } // Return ok response with the task logger.info({ taskId }, '[GetTaskHandler] < handler - successfully retrieved task'); - return ok(task); + return ok(task, defaultResponseHeaders); } catch (error) { // Handle unexpected errors logger.error({ error }, '[GetTaskHandler] < handler - failed to get task'); - return internalServerError('Failed to retrieve task'); + return internalServerError('Failed to retrieve task', defaultResponseHeaders); } }; diff --git a/src/handlers/list-tasks.ts b/src/handlers/list-tasks.ts index 81a7463..ede03c5 100644 --- a/src/handlers/list-tasks.ts +++ b/src/handlers/list-tasks.ts @@ -1,15 +1,9 @@ import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; -import { lambdaRequestTracker } from 'pino-lambda'; +import { withRequestTracking, internalServerError, ok } from '@leanstacks/lambda-utils'; -import { listTasks } from '@/services/task-service.js'; -import { internalServerError, ok } from '@/utils/apigateway-response.js'; -import { logger } from '@/utils/logger.js'; - -/** - * Lambda request tracker middleware for logging. - * @see https://www.npmjs.com/package/pino-lambda#best-practices - */ -const withRequestTracking = lambdaRequestTracker(); +import { logger } from '@/utils/logger'; +import { defaultResponseHeaders } from '@/utils/constants'; +import { listTasks } from '@/services/task-service'; /** * Lambda handler for listing all tasks @@ -28,10 +22,10 @@ export const handler = async (event: APIGatewayProxyEvent, context: Context): Pr // Return ok response with the list of tasks logger.info({ count: tasks.length }, '[ListTasksHandler] < handler - successfully retrieved tasks'); - return ok(tasks); + return ok(tasks, defaultResponseHeaders); } catch (error) { // Handle unexpected errors logger.error({ error }, '[ListTasksHandler] < handler - failed to list tasks'); - return internalServerError('Failed to retrieve tasks'); + return internalServerError('Failed to retrieve tasks', defaultResponseHeaders); } }; diff --git a/src/handlers/update-task.ts b/src/handlers/update-task.ts index f4f33f4..8a8f0d5 100644 --- a/src/handlers/update-task.ts +++ b/src/handlers/update-task.ts @@ -1,17 +1,11 @@ import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; -import { lambdaRequestTracker } from 'pino-lambda'; import { ZodError } from 'zod'; +import { badRequest, internalServerError, notFound, ok, withRequestTracking } from '@leanstacks/lambda-utils'; -import { UpdateTaskDtoSchema } from '@/models/update-task-dto.js'; -import { updateTask } from '@/services/task-service.js'; -import { badRequest, internalServerError, notFound, ok } from '@/utils/apigateway-response.js'; -import { logger } from '@/utils/logger.js'; - -/** - * Lambda request tracker middleware for logging. - * @see https://www.npmjs.com/package/pino-lambda#best-practices - */ -const withRequestTracking = lambdaRequestTracker(); +import { defaultResponseHeaders } from '@/utils/constants'; +import { UpdateTaskDtoSchema } from '@/models/update-task-dto'; +import { updateTask } from '@/services/task-service'; +import { logger } from '@/utils/logger'; /** * Lambda handler for updating an existing task @@ -29,13 +23,13 @@ export const handler = async (event: APIGatewayProxyEvent, context: Context): Pr const taskId = event.pathParameters?.taskId; if (!taskId) { logger.warn('[UpdateTaskHandler] < handler - missing taskId'); - return badRequest('Task ID is required'); + return badRequest('Task ID is required', defaultResponseHeaders); } // Parse and validate request body if (!event.body) { logger.warn('[UpdateTaskHandler] < handler - missing request body'); - return badRequest('Request body is required'); + return badRequest('Request body is required', defaultResponseHeaders); } let requestBody: unknown; @@ -43,7 +37,7 @@ export const handler = async (event: APIGatewayProxyEvent, context: Context): Pr requestBody = JSON.parse(event.body); } catch (_error) { logger.warn('[UpdateTaskHandler] < handler - invalid JSON in request body'); - return badRequest('Invalid JSON in request body'); + return badRequest('Invalid JSON in request body', defaultResponseHeaders); } // Validate request body against schema @@ -55,22 +49,22 @@ export const handler = async (event: APIGatewayProxyEvent, context: Context): Pr // Check if the task was found if (!task) { logger.info({ taskId }, '[UpdateTaskHandler] < handler - task not found'); - return notFound(); + return notFound('Task not found', defaultResponseHeaders); } // Return ok response with the updated task logger.info({ id: task.id }, '[UpdateTaskHandler] < handler - successfully updated task'); - return ok(task); + return ok(task, defaultResponseHeaders); } catch (error) { // Handle validation errors if (error instanceof ZodError) { const errorMessages = error.issues.map((err) => `${err.path.join('.')}: ${err.message}`).join(', '); logger.warn({ issues: error.issues }, '[UpdateTaskHandler] < handler - validation error'); - return badRequest(`Validation failed: ${errorMessages}`); + return badRequest(`Validation failed: ${errorMessages}`, defaultResponseHeaders); } // Handle other unexpected errors logger.error({ error }, '[UpdateTaskHandler] < handler - failed to update task'); - return internalServerError('Failed to update task'); + return internalServerError('Failed to update task', defaultResponseHeaders); } }; diff --git a/src/services/task-service.test.ts b/src/services/task-service.test.ts index 95782e3..bbf1baa 100644 --- a/src/services/task-service.test.ts +++ b/src/services/task-service.test.ts @@ -8,6 +8,7 @@ const mockLoggerDebug = jest.fn(); const mockLoggerInfo = jest.fn(); const mockLoggerError = jest.fn(); const mockRandomUUID = jest.fn(); +const mockPublishToTopic = jest.fn(); jest.mock('crypto', () => ({ randomUUID: mockRandomUUID, @@ -27,9 +28,14 @@ jest.mock('../utils/logger', () => ({ }, })); +jest.mock('@leanstacks/lambda-utils', () => ({ + publishToTopic: mockPublishToTopic, +})); + jest.mock('../utils/config', () => ({ config: { TASKS_TABLE: 'test-tasks-table', + TASK_EVENT_TOPIC_ARN: 'arn:aws:sns:us-east-1:123456789012:test-topic', }, })); @@ -44,6 +50,9 @@ describe('task-service', () => { // Clear all mocks jest.clearAllMocks(); + // Setup publishToTopic mock to return a message ID + mockPublishToTopic.mockResolvedValue('test-message-id-123'); + // Import the module after mocks are set up const taskService = require('./task-service'); listTasks = taskService.listTasks; @@ -475,6 +484,16 @@ describe('task-service', () => { isComplete: true, }; + const existingTaskItem: TaskItem = { + pk: 'TASK#123e4567-e89b-12d3-a456-426614174000', + id: taskId, + title: 'Old Task', + detail: 'Old detail', + isComplete: false, + createdAt: '2025-11-01T10:00:00.000Z', + updatedAt: '2025-11-01T10:00:00.000Z', + }; + const updatedTaskItem: TaskItem = { pk: 'TASK#123e4567-e89b-12d3-a456-426614174000', id: taskId, @@ -486,9 +505,14 @@ describe('task-service', () => { updatedAt: '2025-12-01T10:00:00.000Z', }; - mockSend.mockResolvedValue({ - Attributes: updatedTaskItem, - }); + // First call is getTask, second call is updateTask + mockSend + .mockResolvedValueOnce({ + Item: existingTaskItem, + }) + .mockResolvedValueOnce({ + Attributes: updatedTaskItem, + }); // Act const result = await updateTask(taskId, updateTaskDto); @@ -500,7 +524,8 @@ describe('task-service', () => { expect(result?.dueAt).toBe('2025-12-31T23:59:59.000Z'); expect(result?.isComplete).toBe(true); expect(result?.updatedAt).toBe('2025-12-01T10:00:00.000Z'); - expect(mockSend).toHaveBeenCalledTimes(1); + expect(mockSend).toHaveBeenCalledTimes(2); + expect(mockPublishToTopic).toHaveBeenCalledTimes(1); expect(mockLoggerInfo).toHaveBeenCalledWith( { id: taskId, @@ -517,6 +542,16 @@ describe('task-service', () => { isComplete: false, }; + const existingTaskItem: TaskItem = { + pk: 'TASK#123e4567-e89b-12d3-a456-426614174000', + id: taskId, + title: 'Old Task', + detail: 'Old detail', + isComplete: true, + createdAt: '2025-11-01T10:00:00.000Z', + updatedAt: '2025-11-01T10:00:00.000Z', + }; + const updatedTaskItem: TaskItem = { pk: 'TASK#123e4567-e89b-12d3-a456-426614174000', id: taskId, @@ -526,9 +561,14 @@ describe('task-service', () => { updatedAt: '2025-12-01T10:00:00.000Z', }; - mockSend.mockResolvedValue({ - Attributes: updatedTaskItem, - }); + // First call is getTask, second call is updateTask + mockSend + .mockResolvedValueOnce({ + Item: existingTaskItem, + }) + .mockResolvedValueOnce({ + Attributes: updatedTaskItem, + }); // Act const result = await updateTask(taskId, updateTaskDto); @@ -539,7 +579,8 @@ describe('task-service', () => { expect(result?.detail).toBeUndefined(); expect(result?.dueAt).toBeUndefined(); expect(result?.isComplete).toBe(false); - expect(mockSend).toHaveBeenCalledTimes(1); + expect(mockSend).toHaveBeenCalledTimes(2); + expect(mockPublishToTopic).toHaveBeenCalledTimes(1); }); it('should return null when task is not found', async () => { @@ -576,6 +617,15 @@ describe('task-service', () => { isComplete: false, }; + const existingTaskItem: TaskItem = { + pk: 'TASK#123e4567-e89b-12d3-a456-426614174000', + id: taskId, + title: 'Old Task', + isComplete: true, + createdAt: '2025-11-01T10:00:00.000Z', + updatedAt: '2025-11-01T10:00:00.000Z', + }; + const updatedTaskItem: TaskItem = { pk: 'TASK#123e4567-e89b-12d3-a456-426614174000', id: taskId, @@ -585,9 +635,14 @@ describe('task-service', () => { updatedAt: '2025-12-01T10:00:00.000Z', }; - mockSend.mockResolvedValue({ - Attributes: updatedTaskItem, - }); + // First call is getTask, second call is updateTask + mockSend + .mockResolvedValueOnce({ + Item: existingTaskItem, + }) + .mockResolvedValueOnce({ + Attributes: updatedTaskItem, + }); // Act const result = await updateTask(taskId, updateTaskDto); @@ -621,6 +676,15 @@ describe('task-service', () => { isComplete: false, }; + const existingTaskItem: TaskItem = { + pk: 'TASK#123e4567-e89b-12d3-a456-426614174000', + id: taskId, + title: 'Old Task', + isComplete: true, + createdAt: '2025-11-01T10:00:00.000Z', + updatedAt: '2025-11-01T10:00:00.000Z', + }; + const updatedTaskItem: TaskItem = { pk: 'TASK#123e4567-e89b-12d3-a456-426614174000', id: taskId, @@ -630,9 +694,14 @@ describe('task-service', () => { updatedAt: '2025-12-01T10:00:00.000Z', }; - mockSend.mockResolvedValue({ - Attributes: updatedTaskItem, - }); + // First call is getTask, second call is updateTask + mockSend + .mockResolvedValueOnce({ + Item: existingTaskItem, + }) + .mockResolvedValueOnce({ + Attributes: updatedTaskItem, + }); // Act const result = await updateTask(taskId, updateTaskDto); @@ -650,24 +719,40 @@ describe('task-service', () => { isComplete: false, }; - mockSend.mockResolvedValue({ - Attributes: { - pk: 'TASK#123e4567-e89b-12d3-a456-426614174000', - id: taskId, - title: 'Updated Task', - detail: 'Updated detail', - isComplete: false, - createdAt: '2025-11-01T10:00:00.000Z', - updatedAt: '2025-12-01T10:00:00.000Z', - }, - }); + const existingTaskItem: TaskItem = { + pk: 'TASK#123e4567-e89b-12d3-a456-426614174000', + id: taskId, + title: 'Old Task', + isComplete: true, + createdAt: '2025-11-01T10:00:00.000Z', + updatedAt: '2025-11-01T10:00:00.000Z', + }; + + const updatedTaskItem: TaskItem = { + pk: 'TASK#123e4567-e89b-12d3-a456-426614174000', + id: taskId, + title: 'Updated Task', + detail: 'Updated detail', + isComplete: false, + createdAt: '2025-11-01T10:00:00.000Z', + updatedAt: '2025-12-01T10:00:00.000Z', + }; + + // First call is getTask, second call is updateTask + mockSend + .mockResolvedValueOnce({ + Item: existingTaskItem, + }) + .mockResolvedValueOnce({ + Attributes: updatedTaskItem, + }); // Act await updateTask(taskId, updateTaskDto); // Assert - expect(mockSend).toHaveBeenCalledTimes(1); - const command = mockSend.mock.calls[0][0]; + expect(mockSend).toHaveBeenCalledTimes(2); + const command = mockSend.mock.calls[1][0]; expect(command.input.TableName).toBe('test-tasks-table'); expect(command.input.Key).toEqual({ pk: 'TASK#123e4567-e89b-12d3-a456-426614174000' }); expect(command.input.ConditionExpression).toBe('attribute_exists(pk)'); @@ -683,23 +768,39 @@ describe('task-service', () => { isComplete: false, }; - mockSend.mockResolvedValue({ - Attributes: { - pk: 'TASK#123e4567-e89b-12d3-a456-426614174000', - id: taskId, - title: 'Updated Task', - detail: 'New detail', - isComplete: false, - createdAt: '2025-11-01T10:00:00.000Z', - updatedAt: '2025-12-01T10:00:00.000Z', - }, - }); + const existingTaskItem: TaskItem = { + pk: 'TASK#123e4567-e89b-12d3-a456-426614174000', + id: taskId, + title: 'Old Task', + isComplete: true, + createdAt: '2025-11-01T10:00:00.000Z', + updatedAt: '2025-11-01T10:00:00.000Z', + }; + + const updatedTaskItem: TaskItem = { + pk: 'TASK#123e4567-e89b-12d3-a456-426614174000', + id: taskId, + title: 'Updated Task', + detail: 'New detail', + isComplete: false, + createdAt: '2025-11-01T10:00:00.000Z', + updatedAt: '2025-12-01T10:00:00.000Z', + }; + + // First call is getTask, second call is updateTask + mockSend + .mockResolvedValueOnce({ + Item: existingTaskItem, + }) + .mockResolvedValueOnce({ + Attributes: updatedTaskItem, + }); // Act await updateTask(taskId, updateTaskDto); // Assert - const command = mockSend.mock.calls[0][0]; + const command = mockSend.mock.calls[1][0]; expect(command.input.UpdateExpression).toContain('detail = :detail'); expect(command.input.ExpressionAttributeValues[':detail']).toBe('New detail'); }); @@ -713,23 +814,39 @@ describe('task-service', () => { isComplete: false, }; - mockSend.mockResolvedValue({ - Attributes: { - pk: 'TASK#123e4567-e89b-12d3-a456-426614174000', - id: taskId, - title: 'Updated Task', - dueAt: '2025-12-31T23:59:59.000Z', - isComplete: false, - createdAt: '2025-11-01T10:00:00.000Z', - updatedAt: '2025-12-01T10:00:00.000Z', - }, - }); + const existingTaskItem: TaskItem = { + pk: 'TASK#123e4567-e89b-12d3-a456-426614174000', + id: taskId, + title: 'Old Task', + isComplete: true, + createdAt: '2025-11-01T10:00:00.000Z', + updatedAt: '2025-11-01T10:00:00.000Z', + }; + + const updatedTaskItem: TaskItem = { + pk: 'TASK#123e4567-e89b-12d3-a456-426614174000', + id: taskId, + title: 'Updated Task', + dueAt: '2025-12-31T23:59:59.000Z', + isComplete: false, + createdAt: '2025-11-01T10:00:00.000Z', + updatedAt: '2025-12-01T10:00:00.000Z', + }; + + // First call is getTask, second call is updateTask + mockSend + .mockResolvedValueOnce({ + Item: existingTaskItem, + }) + .mockResolvedValueOnce({ + Attributes: updatedTaskItem, + }); // Act await updateTask(taskId, updateTaskDto); // Assert - const command = mockSend.mock.calls[0][0]; + const command = mockSend.mock.calls[1][0]; expect(command.input.UpdateExpression).toContain('dueAt = :dueAt'); expect(command.input.ExpressionAttributeValues[':dueAt']).toBe('2025-12-31T23:59:59.000Z'); }); diff --git a/src/services/task-service.ts b/src/services/task-service.ts index 2b91aab..cb3960e 100644 --- a/src/services/task-service.ts +++ b/src/services/task-service.ts @@ -1,12 +1,13 @@ import { randomUUID } from 'crypto'; import { DeleteCommand, GetCommand, PutCommand, ScanCommand, UpdateCommand } from '@aws-sdk/lib-dynamodb'; +import { publishToTopic } from '@leanstacks/lambda-utils'; import { CreateTaskDto } from '@/models/create-task-dto.js'; import { UpdateTaskDto } from '@/models/update-task-dto.js'; import { Task, TaskItem, TaskKeys, toTask } from '@/models/task.js'; import { config } from '@/utils/config.js'; +import { logger } from '@/utils/logger'; import { dynamoDocClient } from '@/utils/dynamodb-client.js'; -import { logger } from '@/utils/logger.js'; /** * Retrieves all tasks from the DynamoDB table @@ -123,9 +124,28 @@ export const createTask = async (createTaskDto: CreateTaskDto): Promise => // Map the created item to a Task object const task = toTask(taskItem); + logger.debug({ task }, '[TaskService] - createTask - put Task in DynamoDB'); - logger.info({ id: task.id }, '[TaskService] < createTask - successfully created task'); + // Publish task creation event to SNS + const messageId = await publishToTopic( + config.TASK_EVENT_TOPIC_ARN, + { + task, + }, + { + event: { + DataType: 'String', + StringValue: 'TaskCreated', + }, + }, + ); + logger.debug( + { taskId: task.id, topicArn: config.TASK_EVENT_TOPIC_ARN, messageId }, + '[TaskService] createTask - published TaskCreated event to SNS', + ); + // Return the created task + logger.info({ id: task.id }, '[TaskService] < createTask - successfully created task'); return task; } catch (error) { // Handle unexpected errors @@ -145,6 +165,13 @@ export const updateTask = async (id: string, updateTaskDto: UpdateTaskDto): Prom logger.info({ id }, '[TaskService] > updateTask'); try { + // Fetch existing task to ensure it exists + const existingTask = await getTask(id); + if (!existingTask) { + logger.info({ id }, '[TaskService] < updateTask - task not found'); + return null; + } + // Prepare the task item to be updated const now = new Date().toISOString(); @@ -195,7 +222,7 @@ export const updateTask = async (id: string, updateTaskDto: UpdateTaskDto): Prom ConditionExpression: 'attribute_exists(pk)', ReturnValues: 'ALL_NEW', }); - logger.debug({ input: command.input }, '[TaskService] updateTask - UpdateCommandInput'); + logger.debug({ input: command.input }, '[TaskService] updateTask - UpdateCommandInput'); // Execute the update command const response = await dynamoDocClient.send(command); @@ -208,9 +235,29 @@ export const updateTask = async (id: string, updateTaskDto: UpdateTaskDto): Prom // Map the updated item to a Task object const task = toTask(response.Attributes as TaskItem); + logger.info({ task }, '[TaskService] - updateTask - successfully updated task'); - logger.info({ id }, '[TaskService] < updateTask - successfully updated task'); + // Publish task update event to SNS + const messageId = await publishToTopic( + config.TASK_EVENT_TOPIC_ARN, + { + oldTask: existingTask, + newTask: task, + }, + { + event: { + DataType: 'String', + StringValue: 'TaskUpdated', + }, + }, + ); + logger.debug( + { taskId: id, topicArn: config.TASK_EVENT_TOPIC_ARN, messageId }, + '[TaskService] updateTask - published TaskUpdated event to SNS', + ); + // Return the updated task + logger.info({ id }, '[TaskService] < updateTask - successfully updated task'); return task; } catch (error) { // Handle conditional check failures (task not found) @@ -247,7 +294,27 @@ export const deleteTask = async (id: string): Promise => { // Execute the delete command await dynamoDocClient.send(command); + logger.info({ id }, '[TaskService] - deleteTask - deleted Task from DynamoDB'); + + // Publish task deletion event to SNS + const messageId = await publishToTopic( + config.TASK_EVENT_TOPIC_ARN, + { + taskId: id, + }, + { + event: { + DataType: 'String', + StringValue: 'TaskDeleted', + }, + }, + ); + logger.debug( + { taskId: id, topicArn: config.TASK_EVENT_TOPIC_ARN, messageId }, + '[TaskService] deleteTask - published TaskDeleted event to SNS', + ); + // Return true indicating successful deletion logger.info({ id }, '[TaskService] < deleteTask - successfully deleted task'); return true; } catch (error) { diff --git a/src/utils/apigateway-response.test.ts b/src/utils/apigateway-response.test.ts deleted file mode 100644 index 2931360..0000000 --- a/src/utils/apigateway-response.test.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { - createResponse, - ok, - created, - noContent, - badRequest, - notFound, - internalServerError, -} from './apigateway-response'; - -jest.mock('./config', () => ({ - config: { - CORS_ALLOW_ORIGIN: 'https://example.com', - TASKS_TABLE: 'mock-table', - }, -})); - -describe('apigateway-response', () => { - const corsOrigin = 'https://example.com'; - - beforeEach(() => { - // No setup needed for now, but placeholder for future cleanup/mocks - }); - - describe('createResponse', () => { - it('should return a valid API Gateway response with correct status and body', () => { - // Arrange - const status = 200; - const body = { foo: 'bar' }; - - // Act - const result = createResponse(status, body); - - // Assert - expect(result).toEqual({ - statusCode: status, - headers: { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': corsOrigin, - }, - body: JSON.stringify(body), - }); - }); - }); - - describe('ok', () => { - it('returns 200', () => { - // Arrange - const body = { hello: 'world' }; - - // Act - const result = ok(body); - - // Assert - expect(result.statusCode).toBe(200); - expect(result.body).toBe(JSON.stringify(body)); - }); - }); - - describe('created', () => { - it('returns 201', () => { - // Arrange - const body = { id: 1 }; - - // Act - const result = created(body); - - // Assert - expect(result.statusCode).toBe(201); - expect(result.body).toBe(JSON.stringify(body)); - }); - }); - - describe('noContent', () => { - it('returns 204 with empty object', () => { - // Act - const result = noContent(); - - // Assert - expect(result.statusCode).toBe(204); - expect(result.body).toBe(JSON.stringify({})); - }); - }); - - describe('badRequest', () => { - it('returns 400 with default message', () => { - // Act - const result = badRequest(); - - // Assert - expect(result.statusCode).toBe(400); - expect(JSON.parse(result.body)).toEqual({ message: 'Bad Request' }); - }); - - it('returns 400 with custom message', () => { - // Arrange - const message = 'Custom error'; - - // Act - const result = badRequest(message); - - // Assert - expect(result.statusCode).toBe(400); - expect(JSON.parse(result.body)).toEqual({ message }); - }); - }); - - describe('notFound', () => { - it('returns 404 with default message', () => { - // Act - const result = notFound(); - - // Assert - expect(result.statusCode).toBe(404); - expect(JSON.parse(result.body)).toEqual({ message: 'Not Found' }); - }); - - it('returns 404 with custom message', () => { - // Arrange - const message = 'Missing'; - - // Act - const result = notFound(message); - - // Assert - expect(result.statusCode).toBe(404); - expect(JSON.parse(result.body)).toEqual({ message }); - }); - }); - - describe('internalServerError', () => { - it('returns 500 with default message', () => { - // Act - const result = internalServerError(); - - // Assert - expect(result.statusCode).toBe(500); - expect(JSON.parse(result.body)).toEqual({ message: 'Internal Server Error' }); - }); - - it('returns 500 with custom message', () => { - // Arrange - const message = 'Oops'; - - // Act - const result = internalServerError(message); - - // Assert - expect(result.statusCode).toBe(500); - expect(JSON.parse(result.body)).toEqual({ message }); - }); - }); -}); diff --git a/src/utils/apigateway-response.ts b/src/utils/apigateway-response.ts deleted file mode 100644 index a2687cc..0000000 --- a/src/utils/apigateway-response.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { APIGatewayProxyResult } from 'aws-lambda'; - -import { config } from './config'; - -/** - * Creates a standardized API Gateway response with the given status code and body - * Includes CORS headers to allow cross-origin requests - */ -export const createResponse = (statusCode: number, body: unknown): APIGatewayProxyResult => { - return { - statusCode, - headers: { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': config.CORS_ALLOW_ORIGIN, - }, - body: JSON.stringify(body), - }; -}; - -// Common response patterns -export const ok = (body: unknown): APIGatewayProxyResult => createResponse(200, body); -export const created = (body: unknown): APIGatewayProxyResult => createResponse(201, body); -export const noContent = (): APIGatewayProxyResult => createResponse(204, {}); -export const badRequest = (message = 'Bad Request'): APIGatewayProxyResult => createResponse(400, { message }); -export const notFound = (message = 'Not Found'): APIGatewayProxyResult => createResponse(404, { message }); -export const internalServerError = (message = 'Internal Server Error'): APIGatewayProxyResult => - createResponse(500, { message }); diff --git a/src/utils/config.test.ts b/src/utils/config.test.ts index a1dda74..afb5699 100644 --- a/src/utils/config.test.ts +++ b/src/utils/config.test.ts @@ -1,22 +1,10 @@ describe('config', () => { - let config: typeof import('./config').config; - let refreshConfig: typeof import('./config').refreshConfig; const originalEnv = process.env; beforeEach(() => { // Reset modules to clear the config cache jest.resetModules(); - // Mock logger to avoid circular dependency issues during testing - jest.doMock('./logger', () => ({ - logger: { - debug: jest.fn(), - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - }, - })); - // Reset environment variables to a clean slate process.env = { ...originalEnv }; }); @@ -31,21 +19,24 @@ describe('config', () => { it('should validate and return config with required environment variables', () => { // Arrange process.env.TASKS_TABLE = 'my-tasks-table'; + process.env.TASK_EVENT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:my-topic'; // Act - config = require('./config').config; + const { config } = require('./config'); // Assert expect(config).toBeDefined(); expect(config.TASKS_TABLE).toBe('my-tasks-table'); + expect(config.TASK_EVENT_TOPIC_ARN).toBe('arn:aws:sns:us-east-1:123456789012:my-topic'); }); it('should apply default values for optional environment variables', () => { // Arrange process.env.TASKS_TABLE = 'my-tasks-table'; + process.env.TASK_EVENT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:my-topic'; // Act - config = require('./config').config; + const { config } = require('./config'); // Assert expect(config.AWS_REGION).toBe('us-east-1'); @@ -58,6 +49,7 @@ describe('config', () => { it('should use provided values instead of defaults', () => { // Arrange process.env.TASKS_TABLE = 'my-tasks-table'; + process.env.TASK_EVENT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:my-topic'; process.env.AWS_REGION = 'us-west-2'; process.env.LOGGING_ENABLED = 'false'; process.env.LOGGING_LEVEL = 'error'; @@ -65,7 +57,7 @@ describe('config', () => { process.env.CORS_ALLOW_ORIGIN = 'https://example.com'; // Act - config = require('./config').config; + const { config } = require('./config'); // Assert expect(config.AWS_REGION).toBe('us-west-2'); @@ -78,40 +70,59 @@ describe('config', () => { it('should throw error when required TASKS_TABLE is missing', () => { // Arrange delete process.env.TASKS_TABLE; + process.env.TASK_EVENT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:my-topic'; // Act & Assert expect(() => { const { config: testConfig } = require('./config'); return testConfig; - }).toThrow('Configuration validation failed'); + }).toThrow(); + }); + + it('should throw error when TASKS_TABLE is empty string', () => { + // Arrange + process.env.TASKS_TABLE = ''; + process.env.TASK_EVENT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:my-topic'; + + // Act & Assert expect(() => { const { config: testConfig } = require('./config'); return testConfig; - }).toThrow('TASKS_TABLE'); + }).toThrow(); }); - it('should throw error when TASKS_TABLE is empty string', () => { + it('should throw error when required TASK_EVENT_TOPIC_ARN is missing', () => { // Arrange - process.env.TASKS_TABLE = ''; + process.env.TASKS_TABLE = 'my-tasks-table'; + delete process.env.TASK_EVENT_TOPIC_ARN; // Act & Assert expect(() => { const { config: testConfig } = require('./config'); return testConfig; - }).toThrow('Configuration validation failed'); + }).toThrow(); + }); + + it('should throw error when TASK_EVENT_TOPIC_ARN is empty string', () => { + // Arrange + process.env.TASKS_TABLE = 'my-tasks-table'; + process.env.TASK_EVENT_TOPIC_ARN = ''; + + // Act & Assert expect(() => { const { config: testConfig } = require('./config'); return testConfig; - }).toThrow('TASKS_TABLE'); + }).toThrow(); }); it('should transform LOGGING_ENABLED string to boolean true', () => { // Arrange process.env.TASKS_TABLE = 'my-tasks-table'; + process.env.TASK_EVENT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:my-topic'; process.env.LOGGING_ENABLED = 'true'; // Act - config = require('./config').config; + const { config } = require('./config'); // Assert expect(config.LOGGING_ENABLED).toBe(true); @@ -121,10 +132,11 @@ describe('config', () => { it('should transform LOGGING_ENABLED string to boolean false', () => { // Arrange process.env.TASKS_TABLE = 'my-tasks-table'; + process.env.TASK_EVENT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:my-topic'; process.env.LOGGING_ENABLED = 'false'; // Act - config = require('./config').config; + const { config } = require('./config'); // Assert expect(config.LOGGING_ENABLED).toBe(false); @@ -134,13 +146,16 @@ describe('config', () => { it('should validate LOGGING_LEVEL enum values', () => { // Arrange process.env.TASKS_TABLE = 'my-tasks-table'; + process.env.TASK_EVENT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:my-topic'; // Act & Assert - valid values const validLogLevels = ['debug', 'info', 'warn', 'error']; validLogLevels.forEach((level) => { jest.resetModules(); + process.env.TASKS_TABLE = 'my-tasks-table'; + process.env.TASK_EVENT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:my-topic'; process.env.LOGGING_LEVEL = level; - config = require('./config').config; + const { config } = require('./config'); expect(config.LOGGING_LEVEL).toBe(level); }); }); @@ -148,37 +163,42 @@ describe('config', () => { it('should throw error for invalid LOGGING_LEVEL', () => { // Arrange process.env.TASKS_TABLE = 'my-tasks-table'; + process.env.TASK_EVENT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:my-topic'; process.env.LOGGING_LEVEL = 'invalid'; // Act & Assert expect(() => { const { config: testConfig } = require('./config'); return testConfig; - }).toThrow('Configuration validation failed'); + }).toThrow(); }); it('should throw error for invalid LOGGING_ENABLED value', () => { // Arrange process.env.TASKS_TABLE = 'my-tasks-table'; + process.env.TASK_EVENT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:my-topic'; process.env.LOGGING_ENABLED = 'yes'; // Act & Assert expect(() => { const { config: testConfig } = require('./config'); return testConfig; - }).toThrow('Configuration validation failed'); + }).toThrow(); }); it('should validate LOGGING_FORMAT enum values', () => { // Arrange process.env.TASKS_TABLE = 'my-tasks-table'; + process.env.TASK_EVENT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:my-topic'; // Act & Assert - valid values const validLogFormats = ['text', 'json']; validLogFormats.forEach((format) => { jest.resetModules(); + process.env.TASKS_TABLE = 'my-tasks-table'; + process.env.TASK_EVENT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:my-topic'; process.env.LOGGING_FORMAT = format; - config = require('./config').config; + const { config } = require('./config'); expect(config.LOGGING_FORMAT).toBe(format); }); }); @@ -186,69 +206,68 @@ describe('config', () => { it('should throw error for invalid LOGGING_FORMAT', () => { // Arrange process.env.TASKS_TABLE = 'my-tasks-table'; + process.env.TASK_EVENT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:my-topic'; process.env.LOGGING_FORMAT = 'xml'; // Act & Assert expect(() => { const { config: testConfig } = require('./config'); return testConfig; - }).toThrow('Configuration validation failed'); + }).toThrow(); }); }); - describe('refreshConfig', () => { + describe('refresh', () => { it('should refresh config when environment variables change', () => { // Arrange process.env.TASKS_TABLE = 'original-table'; + process.env.TASK_EVENT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:original-topic'; process.env.AWS_REGION = 'us-east-1'; - refreshConfig = require('./config').refreshConfig; - config = require('./config').config; + const { config, refresh } = require('./config'); expect(config.TASKS_TABLE).toBe('original-table'); + expect(config.TASK_EVENT_TOPIC_ARN).toBe('arn:aws:sns:us-east-1:123456789012:original-topic'); expect(config.AWS_REGION).toBe('us-east-1'); // Act - change environment and refresh process.env.TASKS_TABLE = 'updated-table'; + process.env.TASK_EVENT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:updated-topic'; process.env.AWS_REGION = 'eu-west-1'; - const refreshedConfig = refreshConfig(); + const refreshedConfig = refresh(); // Assert expect(refreshedConfig.TASKS_TABLE).toBe('updated-table'); + expect(refreshedConfig.TASK_EVENT_TOPIC_ARN).toBe('arn:aws:sns:us-east-1:123456789012:updated-topic'); expect(refreshedConfig.AWS_REGION).toBe('eu-west-1'); }); - it('should update cached config after refresh', () => { + it('should return updated config on refresh', () => { // Arrange process.env.TASKS_TABLE = 'original-table'; - refreshConfig = require('./config').refreshConfig; - const configModule = require('./config'); - const originalConfig = configModule.config; - - expect(originalConfig.TASKS_TABLE).toBe('original-table'); + process.env.TASK_EVENT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:original-topic'; + const { refresh } = require('./config'); // Act process.env.TASKS_TABLE = 'new-table'; - const refreshedConfig = refreshConfig(); + process.env.TASK_EVENT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:new-topic'; + const refreshedConfig = refresh(); - // Assert - refreshedConfig should have new value + // Assert expect(refreshedConfig.TASKS_TABLE).toBe('new-table'); - // The originally exported config constant won't change, but refreshConfig returns the new value - expect(originalConfig.TASKS_TABLE).toBe('original-table'); + expect(refreshedConfig.TASK_EVENT_TOPIC_ARN).toBe('arn:aws:sns:us-east-1:123456789012:new-topic'); }); it('should throw error on refresh if validation fails', () => { // Arrange process.env.TASKS_TABLE = 'valid-table'; - refreshConfig = require('./config').refreshConfig; - config = require('./config').config; - - expect(config.TASKS_TABLE).toBe('valid-table'); + process.env.TASK_EVENT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:valid-topic'; + const { refresh } = require('./config'); // Act - remove required variable and refresh delete process.env.TASKS_TABLE; // Assert - expect(() => refreshConfig()).toThrow('Configuration validation failed'); + expect(() => refresh()).toThrow(); }); }); @@ -256,6 +275,7 @@ describe('config', () => { it('should cache config after first validation', () => { // Arrange process.env.TASKS_TABLE = 'my-tasks-table'; + process.env.TASK_EVENT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:my-topic'; const configModule = require('./config'); // Act @@ -269,6 +289,7 @@ describe('config', () => { it('should return cached config on subsequent imports', () => { // Arrange process.env.TASKS_TABLE = 'cached-table'; + process.env.TASK_EVENT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:cached-topic'; process.env.AWS_REGION = 'us-west-1'; // Act @@ -289,18 +310,20 @@ describe('config', () => { it('should provide detailed error message for multiple validation failures', () => { // Arrange delete process.env.TASKS_TABLE; + delete process.env.TASK_EVENT_TOPIC_ARN; process.env.LOGGING_LEVEL = 'invalid'; // Act & Assert expect(() => { const { config: testConfig } = require('./config'); return testConfig; - }).toThrow('Configuration validation failed'); + }).toThrow(); }); it('should include field paths in error messages', () => { // Arrange delete process.env.TASKS_TABLE; + process.env.TASK_EVENT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:my-topic'; // Act & Assert try { @@ -310,8 +333,6 @@ describe('config', () => { fail('Should have thrown an error'); } catch (error) { expect(error).toBeInstanceOf(Error); - expect((error as Error).message).toContain('TASKS_TABLE'); - expect((error as Error).message).toContain('Configuration validation failed'); } }); }); @@ -320,16 +341,18 @@ describe('config', () => { it('should export Config type matching validated schema', () => { // Arrange process.env.TASKS_TABLE = 'my-tasks-table'; + process.env.TASK_EVENT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:my-topic'; process.env.AWS_REGION = 'us-east-1'; process.env.LOGGING_ENABLED = 'true'; process.env.LOGGING_LEVEL = 'info'; process.env.CORS_ALLOW_ORIGIN = 'https://example.com'; // Act - config = require('./config').config; + const { config } = require('./config'); // Assert - verify all expected properties exist and have correct types expect(typeof config.TASKS_TABLE).toBe('string'); + expect(typeof config.TASK_EVENT_TOPIC_ARN).toBe('string'); expect(typeof config.AWS_REGION).toBe('string'); expect(typeof config.LOGGING_ENABLED).toBe('boolean'); expect(['debug', 'info', 'warn', 'error']).toContain(config.LOGGING_LEVEL); diff --git a/src/utils/config.ts b/src/utils/config.ts index f7bfb39..c25e288 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { createConfigManager } from '@leanstacks/lambda-utils'; /** * Schema for validating environment variables @@ -6,6 +7,7 @@ import { z } from 'zod'; const envSchema = z.object({ // Required variables TASKS_TABLE: z.string().min(1, 'TASKS_TABLE environment variable is required'), + TASK_EVENT_TOPIC_ARN: z.string().min(1, 'TASK_EVENT_TOPIC_ARN environment variable is required'), // Optional variables with defaults AWS_REGION: z.string().default('us-east-1'), @@ -29,41 +31,20 @@ const envSchema = z.object({ */ export type Config = z.infer; -// Cache for the validated config -let configCache: Config | null = null; - /** - * Validates environment variables against schema and returns a validated config object - * @throws {Error} if validation fails + * The configuration manager responsible for validating and providing access to a singleton + * instance of the application's configuration */ -const _validateConfig = (): Config => { - try { - // Parse and validate environment variables - return envSchema.parse(process.env); - } catch (error) { - // Handle Zod validation errors - if (error instanceof z.ZodError) { - const errorMessage = error.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`).join('\n'); - - throw new Error(`Configuration validation failed:\n${errorMessage}`); - } - - // Re-throw other errors - throw error; - } -}; +const configManager = createConfigManager(envSchema); /** - * Refreshes the configuration by re-validating environment variables - * Useful in tests when environment variables are changed + * Validated configuration object + * Access environment variables through this object instead of process.env directly */ -export const refreshConfig = (): Config => { - configCache = _validateConfig(); - return configCache; -}; +export const config = configManager.get(); /** - * Validated configuration object - * Access environment variables through this object instead of process.env directly + * Refreshes the configuration by re-validating environment variables + * Useful in tests when environment variables are changed */ -export const config = configCache || refreshConfig(); +export const refresh = configManager.refresh; diff --git a/src/utils/constants.test.ts b/src/utils/constants.test.ts new file mode 100644 index 0000000..a732257 --- /dev/null +++ b/src/utils/constants.test.ts @@ -0,0 +1,60 @@ +jest.mock('@leanstacks/lambda-utils', () => ({ + httpHeaders: { + cors: jest.fn(() => ({ 'Access-Control-Allow-Origin': 'https://example.com' })), + json: { 'Content-Type': 'application/json' }, + }, +})); +jest.mock('./config', () => ({ + config: { + CORS_ALLOW_ORIGIN: 'https://example.com', + }, +})); + +// Import after mocking +import { httpHeaders } from '@leanstacks/lambda-utils'; +import { defaultResponseHeaders } from './constants'; + +describe('constants', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('defaultResponseHeaders', () => { + it('should be defined', () => { + // Assert + expect(defaultResponseHeaders).toBeDefined(); + }); + + it('should be of type object', () => { + // Assert + expect(typeof defaultResponseHeaders).toBe('object'); + expect(defaultResponseHeaders).not.toBeNull(); + }); + + it('should include content-type header from jsonHeaders', () => { + // Assert + expect(defaultResponseHeaders).toHaveProperty('Content-Type'); + expect(defaultResponseHeaders['Content-Type']).toBe('application/json'); + }); + + it('should pass CORS_ALLOW_ORIGIN from config to corsHeaders', () => { + // The corsHeaders function is called during module initialization + // Verify it exists and is a mock function + expect(typeof httpHeaders.cors).toBe('function'); + expect((httpHeaders.cors as jest.Mock).mock).toBeDefined(); + }); + + it('should include CORS headers from corsHeaders function', () => { + // Assert + expect(defaultResponseHeaders).toHaveProperty('Access-Control-Allow-Origin'); + expect(defaultResponseHeaders['Access-Control-Allow-Origin']).toBe('https://example.com'); + }); + + it('should combine properties from both jsonHeaders and corsHeaders', () => { + // Assert - verify the object contains properties from both sources + expect(Object.keys(defaultResponseHeaders).length).toBeGreaterThanOrEqual(2); + expect(defaultResponseHeaders).toHaveProperty('Content-Type'); + expect(defaultResponseHeaders).toHaveProperty('Access-Control-Allow-Origin'); + }); + }); +}); diff --git a/src/utils/constants.ts b/src/utils/constants.ts new file mode 100644 index 0000000..93e3159 --- /dev/null +++ b/src/utils/constants.ts @@ -0,0 +1,9 @@ +import { httpHeaders, Headers } from '@leanstacks/lambda-utils'; + +import { config } from './config'; + +/** + * Default API response headers. + * Includes Content-Type and CORS headers. + */ +export const defaultResponseHeaders: Headers = { ...httpHeaders.json, ...httpHeaders.cors(config.CORS_ALLOW_ORIGIN) }; diff --git a/src/utils/dynamodb-client.test.ts b/src/utils/dynamodb-client.test.ts index b2e86c6..5d1c063 100644 --- a/src/utils/dynamodb-client.test.ts +++ b/src/utils/dynamodb-client.test.ts @@ -2,11 +2,36 @@ describe('dynamodb-client', () => { let dynamoDocClient: typeof import('./dynamodb-client').dynamoDocClient; let dynamoClient: typeof import('./dynamodb-client').dynamoClient; let mockLoggerInfo: jest.Mock; + let mockInitializeDynamoDBClients: jest.Mock; + let mockGetDynamoDBClient: jest.Mock; + let mockGetDynamoDBDocumentClient: jest.Mock; + let mockDynamoClient: any; + let mockDocClient: any; beforeEach(() => { // Reset modules to clear any cached imports jest.resetModules(); + // Create mock client instances + mockDynamoClient = { + constructor: { name: 'DynamoDBClient' }, + config: { region: 'us-east-1' }, + }; + mockDocClient = { + constructor: { name: 'DynamoDBDocumentClient' }, + }; + + // Mock the lambda-utils module + mockInitializeDynamoDBClients = jest.fn(); + mockGetDynamoDBClient = jest.fn().mockReturnValue(mockDynamoClient); + mockGetDynamoDBDocumentClient = jest.fn().mockReturnValue(mockDocClient); + + jest.doMock('@leanstacks/lambda-utils', () => ({ + initializeDynamoDBClients: mockInitializeDynamoDBClients, + getDynamoDBClient: mockGetDynamoDBClient, + getDynamoDBDocumentClient: mockGetDynamoDBDocumentClient, + })); + // Mock the config module jest.doMock('./config', () => ({ config: { @@ -36,37 +61,53 @@ describe('dynamodb-client', () => { }); describe('dynamoDBClient', () => { - it('should create a DynamoDB client instance', () => { + it('should initialize lambda-utils with correct configuration', () => { // Arrange & Act - const module = require('./dynamodb-client'); - dynamoClient = module.dynamoClient; + require('./dynamodb-client'); // Assert - expect(dynamoClient).toBeDefined(); - expect(dynamoClient.constructor.name).toBe('DynamoDBClient'); + expect(mockInitializeDynamoDBClients).toHaveBeenCalledWith( + { region: 'us-east-1' }, + { + convertEmptyValues: false, + convertClassInstanceToMap: true, + removeUndefinedValues: true, + }, + { + wrapNumbers: false, + }, + ); }); - it('should configure DynamoDB client with AWS region from config', () => { + it('should get DynamoDB client from lambda-utils', () => { // Arrange & Act const module = require('./dynamodb-client'); dynamoClient = module.dynamoClient; // Assert expect(dynamoClient).toBeDefined(); - // Verify the client has the config property - expect(dynamoClient.config).toBeDefined(); + expect(dynamoClient.constructor.name).toBe('DynamoDBClient'); + expect(mockGetDynamoDBClient).toHaveBeenCalled(); }); - it('should log initialization with region information', () => { + it('should log initialization with configuration information', () => { // Arrange & Act require('./dynamodb-client'); // Assert expect(mockLoggerInfo).toHaveBeenCalledWith( expect.objectContaining({ - dynamoDBClientConfig: expect.objectContaining({ + dynamoDbClientConfig: expect.objectContaining({ region: 'us-east-1', }), + marshallConfig: expect.objectContaining({ + convertEmptyValues: false, + convertClassInstanceToMap: true, + removeUndefinedValues: true, + }), + unmarshallConfig: expect.objectContaining({ + wrapNumbers: false, + }), }), '[DynamoDBClient] - Initialized AWS DynamoDB client', ); @@ -74,7 +115,7 @@ describe('dynamodb-client', () => { }); describe('dynamoDocClient', () => { - it('should create a DynamoDB Document client instance', () => { + it('should get DynamoDB Document client from lambda-utils', () => { // Arrange & Act const module = require('./dynamodb-client'); dynamoDocClient = module.dynamoDocClient; @@ -82,9 +123,10 @@ describe('dynamodb-client', () => { // Assert expect(dynamoDocClient).toBeDefined(); expect(dynamoDocClient.constructor.name).toBe('DynamoDBDocumentClient'); + expect(mockGetDynamoDBDocumentClient).toHaveBeenCalled(); }); - it('should create Document client from base DynamoDB client', () => { + it('should create Document client after DynamoDB client initialization', () => { // Arrange & Act const module = require('./dynamodb-client'); dynamoDocClient = module.dynamoDocClient; @@ -94,6 +136,7 @@ describe('dynamodb-client', () => { expect(dynamoDocClient).toBeDefined(); expect(dynamoClient).toBeDefined(); expect(dynamoDocClient.constructor.name).toBe('DynamoDBDocumentClient'); + expect(mockGetDynamoDBDocumentClient).toHaveBeenCalled(); }); }); @@ -104,44 +147,36 @@ describe('dynamodb-client', () => { const module2 = require('./dynamodb-client'); // Assert - same instances should be returned (singleton pattern) - expect(module1.dynamoDBClient).toBe(module2.dynamoDBClient); + expect(module1.dynamoClient).toBe(module2.dynamoClient); expect(module1.dynamoDocClient).toBe(module2.dynamoDocClient); - // Logger should only be called once during initialization - expect(mockLoggerInfo).toHaveBeenCalledTimes(1); + // initializeDynamoDBClients should only be called once during initialization + expect(mockInitializeDynamoDBClients).toHaveBeenCalledTimes(1); }); - it('should use different region when config changes', () => { - // Arrange - jest.resetModules(); - jest.doMock('./config', () => ({ - config: { - AWS_REGION: 'eu-west-1', - TASKS_TABLE: 'test-table', - LOGGING_ENABLED: true, - LOGGING_LEVEL: 'info', - CORS_ALLOW_ORIGIN: '*', - }, - })); - jest.doMock('./logger', () => ({ - logger: { - info: mockLoggerInfo, - debug: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - }, - })); + it('should pass correct configuration to initializeDynamoDBClients', () => { + // Arrange & Act + require('./dynamodb-client'); + + // Assert + const callArgs = mockInitializeDynamoDBClients.mock.calls[0]; + expect(callArgs[0]).toEqual({ region: 'us-east-1' }); + expect(callArgs[1]).toEqual({ + convertEmptyValues: false, + convertClassInstanceToMap: true, + removeUndefinedValues: true, + }); + expect(callArgs[2]).toEqual({ wrapNumbers: false }); + }); - // Act + it('should use AWS region from config', () => { + // Arrange & Act require('./dynamodb-client'); // Assert - expect(mockLoggerInfo).toHaveBeenCalledWith( - expect.objectContaining({ - dynamoDBClientConfig: expect.objectContaining({ - region: 'eu-west-1', - }), - }), - '[DynamoDBClient] - Initialized AWS DynamoDB client', + expect(mockInitializeDynamoDBClients).toHaveBeenCalledWith( + expect.objectContaining({ region: 'us-east-1' }), + expect.any(Object), + expect.any(Object), ); }); }); diff --git a/src/utils/dynamodb-client.ts b/src/utils/dynamodb-client.ts index 454084b..89f36ce 100644 --- a/src/utils/dynamodb-client.ts +++ b/src/utils/dynamodb-client.ts @@ -1,24 +1,49 @@ -import { DynamoDBClient, DynamoDBClientConfig } from '@aws-sdk/client-dynamodb'; +import { initializeDynamoDBClients, getDynamoDBClient, getDynamoDBDocumentClient } from '@leanstacks/lambda-utils'; +import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb'; -import { config } from './config.js'; -import { logger } from './logger.js'; +import { config } from './config'; +import { logger } from './logger'; /** - * Configuration for the DynamoDB client + * DynamoDB client configuration */ -const clientConfig: DynamoDBClientConfig = { +const dynamoDbClientConfig = { region: config.AWS_REGION, }; +/** + * Configuration for marshalling JavaScript objects into DynamoDB items + */ +const marshallConfig = { + convertEmptyValues: false, + convertClassInstanceToMap: true, + removeUndefinedValues: true, +}; + +/** + * Configuration for unmarshalling DynamoDB items into JavaScript objects + */ +const unmarshallConfig = { + wrapNumbers: false, +}; + +/** + * Initialize the DynamoDB clients with the microservice configuration. + */ +initializeDynamoDBClients(dynamoDbClientConfig, marshallConfig, unmarshallConfig); + /** * Singleton DynamoDB client configured with the application's region */ -export const dynamoClient = new DynamoDBClient(clientConfig); +export const dynamoClient: DynamoDBClient = getDynamoDBClient(); /** * Singleton DynamoDB Document client for easier interaction with DynamoDB */ -export const dynamoDocClient = DynamoDBDocumentClient.from(dynamoClient); +export const dynamoDocClient: DynamoDBDocumentClient = getDynamoDBDocumentClient(); -logger.info({ dynamoDBClientConfig: clientConfig }, '[DynamoDBClient] - Initialized AWS DynamoDB client'); +logger.info( + { dynamoDbClientConfig, marshallConfig, unmarshallConfig }, + '[DynamoDBClient] - Initialized AWS DynamoDB client', +); diff --git a/src/utils/lambda-client.test.ts b/src/utils/lambda-client.test.ts deleted file mode 100644 index ae980b2..0000000 --- a/src/utils/lambda-client.test.ts +++ /dev/null @@ -1,685 +0,0 @@ -// Mock the logger -const mockLoggerInfo = jest.fn(); -const mockLoggerDebug = jest.fn(); -const mockLoggerError = jest.fn(); - -jest.mock('./logger.js', () => ({ - logger: { - info: mockLoggerInfo, - debug: mockLoggerDebug, - error: mockLoggerError, - }, -})); - -// Mock the config module -jest.mock('./config.js', () => ({ - config: { - AWS_REGION: 'us-east-1', - }, -})); - -// Mock the Lambda client -let mockSend: jest.Mock; -jest.mock('@aws-sdk/client-lambda', () => { - mockSend = jest.fn(); - return { - InvokeCommand: jest.fn(), - LambdaClient: jest.fn().mockImplementation(() => ({ - send: mockSend, - })), - }; -}); - -import { invokeLambdaSync, invokeLambdaAsync } from './lambda-client.js'; - -/** - * Test suite for Lambda client utility - */ -describe('lambda-client', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('invokeLambdaSync', () => { - it('should successfully invoke a Lambda function with RequestResponse invocation type', async () => { - // Arrange - const functionName = 'test-function'; - const payload = { test: 'data' }; - const mockResponse = { result: 'success' }; - - mockSend.mockResolvedValueOnce({ - StatusCode: 200, - FunctionError: undefined, - Payload: new TextEncoder().encode(JSON.stringify(mockResponse)), - }); - - // Act - const result = await invokeLambdaSync(functionName, payload); - - // Assert - expect(result).toEqual(mockResponse); - expect(mockLoggerInfo).toHaveBeenCalledWith( - expect.objectContaining({ functionName }), - '[LambdaClient] > invokeLambdaSync', - ); - expect(mockLoggerInfo).toHaveBeenCalledWith( - expect.objectContaining({ - functionName, - statusCode: 200, - }), - '[LambdaClient] < invokeLambdaSync - successfully invoked Lambda function', - ); - }); - - it('should serialize payload to JSON string', async () => { - // Arrange - const functionName = 'test-function'; - const payload = { httpMethod: 'GET', path: '/tasks', body: null }; - - mockSend.mockResolvedValueOnce({ - StatusCode: 200, - FunctionError: undefined, - Payload: new TextEncoder().encode(JSON.stringify([])), - }); - - // Act - await invokeLambdaSync(functionName, payload); - - // Assert - expect(mockSend).toHaveBeenCalled(); - }); - - it('should deserialize response payload from Uint8Array', async () => { - // Arrange - const functionName = 'test-function'; - const payload = {}; - const mockResponse = { - statusCode: 200, - body: JSON.stringify({ data: 'test' }), - headers: { 'Content-Type': 'application/json' }, - }; - - mockSend.mockResolvedValueOnce({ - StatusCode: 200, - FunctionError: undefined, - Payload: new TextEncoder().encode(JSON.stringify(mockResponse)), - }); - - // Act - const result = (await invokeLambdaSync(functionName, payload)) as Record; - - // Assert - expect(result).toEqual(mockResponse); - expect(typeof result).toBe('object'); - expect(result.statusCode).toBe(200); - }); - - it('should handle empty Payload from Lambda response', async () => { - // Arrange - const functionName = 'test-function'; - const payload = {}; - - mockSend.mockResolvedValueOnce({ - StatusCode: 200, - FunctionError: undefined, - Payload: undefined, - }); - - // Act - const result = await invokeLambdaSync(functionName, payload); - - // Assert - expect(result).toBeNull(); - }); - - it('should throw error when Lambda function returns FunctionError', async () => { - // Arrange - const functionName = 'test-function'; - const payload = {}; - const errorResponse = { message: 'Internal error' }; - - mockSend.mockResolvedValueOnce({ - StatusCode: 200, - FunctionError: 'Unhandled', - Payload: new TextEncoder().encode(JSON.stringify(errorResponse)), - }); - - // Act & Assert - await expect(invokeLambdaSync(functionName, payload)).rejects.toThrow('Lambda function error: Unhandled'); - expect(mockLoggerError).toHaveBeenCalledWith( - expect.objectContaining({ - functionName, - FunctionError: 'Unhandled', - responsePayload: expect.any(Object), - }), - '[LambdaClient] < invokeLambdaSync - Lambda function returned an error', - ); - }); - - it('should handle Lambda SDK invocation errors', async () => { - // Arrange - const functionName = 'non-existent-function'; - const payload = {}; - const error = new Error('Function not found'); - - mockSend.mockRejectedValueOnce(error); - - // Act & Assert - await expect(invokeLambdaSync(functionName, payload)).rejects.toThrow('Function not found'); - expect(mockLoggerError).toHaveBeenCalledWith( - expect.objectContaining({ - error, - functionName, - }), - '[LambdaClient] < invokeLambdaSync - failed to invoke Lambda function', - ); - }); - - it('should support generic type parameter for response', async () => { - // Arrange - interface TaskResponse { - tasks: { id: string; title: string }[]; - } - - const functionName = 'list-tasks'; - const payload = {}; - const mockResponse: TaskResponse = { - tasks: [{ id: '1', title: 'Test Task' }], - }; - - mockSend.mockResolvedValueOnce({ - StatusCode: 200, - FunctionError: undefined, - Payload: new TextEncoder().encode(JSON.stringify(mockResponse)), - }); - - // Act - const result = await invokeLambdaSync(functionName, payload); - - // Assert - expect(result).toEqual(mockResponse); - const typedResult = result as TaskResponse | null; - expect(typedResult?.tasks).toBeDefined(); - }); - - it('should log debug information during invocation', async () => { - // Arrange - const functionName = 'test-function'; - const payload = { test: 'data' }; - - mockSend.mockResolvedValueOnce({ - StatusCode: 200, - FunctionError: undefined, - Payload: new TextEncoder().encode(JSON.stringify({})), - }); - - // Act - await invokeLambdaSync(functionName, payload); - - // Assert - expect(mockLoggerDebug).toHaveBeenCalledWith( - expect.any(Object), - '[LambdaClient] invokeLambdaSync - InvokeCommand', - ); - }); - - it('should handle complex nested payloads', async () => { - // Arrange - const functionName = 'process-data'; - const complexPayload = { - event: { - httpMethod: 'POST', - path: '/api/v1/tasks', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ title: 'New Task', due: '2024-12-25' }), - requestContext: { - requestId: 'req-123', - accountId: '123456789012', - }, - }, - context: { - functionName: 'processor', - memoryLimit: 256, - }, - }; - - const mockResponse = { success: true, id: 'task-123' }; - - mockSend.mockResolvedValueOnce({ - StatusCode: 200, - FunctionError: undefined, - Payload: new TextEncoder().encode(JSON.stringify(mockResponse)), - }); - - // Act - const result = await invokeLambdaSync(functionName, complexPayload); - - // Assert - expect(result).toEqual(mockResponse); - }); - - it('should use RequestResponse invocation type', async () => { - // Arrange - const functionName = 'sync-function'; - const payload = {}; - - mockSend.mockResolvedValueOnce({ - StatusCode: 200, - FunctionError: undefined, - Payload: new TextEncoder().encode(JSON.stringify({ status: 'ok' })), - }); - - // Act - await invokeLambdaSync(functionName, payload); - - // Assert - expect(mockSend).toHaveBeenCalled(); - }); - - it('should handle Lambda response with non-JSON payload', async () => { - // Arrange - const functionName = 'text-function'; - const payload = {}; - - mockSend.mockResolvedValueOnce({ - StatusCode: 200, - FunctionError: undefined, - Payload: new TextEncoder().encode('plain text response'), - }); - - // Act & Assert - await expect(invokeLambdaSync(functionName, payload)).rejects.toThrow(); - }); - - it('should handle successful invocation with HTTP status codes other than 200', async () => { - // Arrange - const functionName = 'api-function'; - const payload = {}; - const mockResponse = { - statusCode: 404, - body: JSON.stringify({ message: 'Not Found' }), - }; - - mockSend.mockResolvedValueOnce({ - StatusCode: 202, - FunctionError: undefined, - Payload: new TextEncoder().encode(JSON.stringify(mockResponse)), - }); - - // Act - const result = (await invokeLambdaSync(functionName, payload)) as Record; - - // Assert - expect(result).toEqual(mockResponse); - expect(result.statusCode).toBe(404); - }); - - it('should include function name in log messages', async () => { - // Arrange - const functionName = 'my-special-function'; - const payload = {}; - - mockSend.mockResolvedValueOnce({ - StatusCode: 200, - FunctionError: undefined, - Payload: new TextEncoder().encode(JSON.stringify({})), - }); - - // Act - await invokeLambdaSync(functionName, payload); - - // Assert - expect(mockLoggerInfo).toHaveBeenCalledWith( - expect.objectContaining({ functionName }), - '[LambdaClient] > invokeLambdaSync', - ); - expect(mockLoggerDebug).toHaveBeenCalledWith( - expect.any(Object), - '[LambdaClient] invokeLambdaSync - InvokeCommand', - ); - expect(mockLoggerInfo).toHaveBeenCalledWith( - expect.objectContaining({ - functionName, - statusCode: 200, - }), - '[LambdaClient] < invokeLambdaSync - successfully invoked Lambda function', - ); - }); - }); - - describe('invokeLambdaAsync', () => { - it('should successfully invoke a Lambda function with Event invocation type', async () => { - // Arrange - const functionName = 'async-test-function'; - const payload = { test: 'data' }; - - mockSend.mockResolvedValueOnce({ - StatusCode: 202, - FunctionError: undefined, - Payload: new TextEncoder().encode(JSON.stringify({ result: 'accepted' })), - }); - - // Act - await invokeLambdaAsync(functionName, payload); - - // Assert - expect(mockLoggerInfo).toHaveBeenCalledWith( - expect.objectContaining({ functionName }), - '[LambdaClient] > invokeLambdaAsync', - ); - expect(mockLoggerInfo).toHaveBeenCalledWith( - expect.objectContaining({ - functionName, - statusCode: 202, - }), - '[LambdaClient] < invokeLambdaAsync - successfully invoked Lambda function', - ); - }); - - it('should serialize payload to JSON string for async invocation', async () => { - // Arrange - const functionName = 'async-test-function'; - const payload = { httpMethod: 'POST', path: '/tasks', body: { title: 'New Task' } }; - - mockSend.mockResolvedValueOnce({ - StatusCode: 202, - FunctionError: undefined, - Payload: new TextEncoder().encode(JSON.stringify({})), - }); - - // Act - await invokeLambdaAsync(functionName, payload); - - // Assert - expect(mockSend).toHaveBeenCalled(); - }); - - it('should deserialize response payload from Uint8Array for async invocation', async () => { - // Arrange - const functionName = 'async-test-function'; - const payload = {}; - - mockSend.mockResolvedValueOnce({ - StatusCode: 202, - FunctionError: undefined, - Payload: new TextEncoder().encode( - JSON.stringify({ - statusCode: 202, - body: JSON.stringify({ queued: true }), - headers: { 'Content-Type': 'application/json' }, - }), - ), - }); - - // Act - await invokeLambdaAsync(functionName, payload); - - // Assert - expect(mockSend).toHaveBeenCalled(); - expect(mockLoggerInfo).toHaveBeenCalledWith( - expect.objectContaining({ - functionName, - statusCode: 202, - }), - '[LambdaClient] < invokeLambdaAsync - successfully invoked Lambda function', - ); - }); - - it('should handle empty Payload from async Lambda response', async () => { - // Arrange - const functionName = 'async-test-function'; - const payload = {}; - - mockSend.mockResolvedValueOnce({ - StatusCode: 202, - FunctionError: undefined, - Payload: undefined, - }); - - // Act - await invokeLambdaAsync(functionName, payload); - - // Assert - expect(mockLoggerInfo).toHaveBeenCalledWith( - expect.objectContaining({ - functionName, - statusCode: 202, - }), - '[LambdaClient] < invokeLambdaAsync - successfully invoked Lambda function', - ); - }); - - it('should throw error when async Lambda function returns FunctionError', async () => { - // Arrange - const functionName = 'async-test-function'; - const payload = {}; - const errorResponse = { message: 'Internal error' }; - - mockSend.mockResolvedValueOnce({ - StatusCode: 202, - FunctionError: 'Unhandled', - Payload: new TextEncoder().encode(JSON.stringify(errorResponse)), - }); - - // Act & Assert - await expect(invokeLambdaAsync(functionName, payload)).rejects.toThrow('Lambda function error: Unhandled'); - expect(mockLoggerError).toHaveBeenCalledWith( - expect.objectContaining({ - functionName, - FunctionError: 'Unhandled', - response: expect.objectContaining({ - FunctionError: 'Unhandled', - }), - }), - '[LambdaClient] < invokeLambdaAsync - Lambda function returned an error', - ); - }); - - it('should handle Lambda SDK invocation errors for async invocation', async () => { - // Arrange - const functionName = 'non-existent-async-function'; - const payload = {}; - const error = new Error('Function not found'); - - mockSend.mockRejectedValueOnce(error); - - // Act & Assert - await expect(invokeLambdaAsync(functionName, payload)).rejects.toThrow('Function not found'); - expect(mockLoggerError).toHaveBeenCalledWith( - expect.objectContaining({ - error, - functionName, - }), - '[LambdaClient] < invokeLambdaAsync - failed to invoke Lambda function', - ); - }); - - it('should support generic type parameter for async response', async () => { - // Arrange - const functionName = 'create-task-async'; - const payload = {}; - - mockSend.mockResolvedValueOnce({ - StatusCode: 202, - FunctionError: undefined, - Payload: new TextEncoder().encode( - JSON.stringify({ - jobId: 'job-123', - status: 'queued', - }), - ), - }); - - // Act - await invokeLambdaAsync(functionName, payload); - - // Assert - invokeLambdaAsync returns void - expect(mockLoggerInfo).toHaveBeenCalledWith( - expect.objectContaining({ - functionName, - statusCode: 202, - }), - '[LambdaClient] < invokeLambdaAsync - successfully invoked Lambda function', - ); - }); - - it('should log debug information during async invocation', async () => { - // Arrange - const functionName = 'async-test-function'; - const payload = { test: 'data' }; - - mockSend.mockResolvedValueOnce({ - StatusCode: 202, - FunctionError: undefined, - Payload: new TextEncoder().encode(JSON.stringify({})), - }); - - // Act - await invokeLambdaAsync(functionName, payload); - - // Assert - expect(mockLoggerDebug).toHaveBeenCalledWith( - expect.any(Object), - '[LambdaClient] invokeLambdaAsync - InvokeCommand', - ); - }); - - it('should handle complex nested payloads for async invocation', async () => { - // Arrange - const functionName = 'process-data-async'; - const complexPayload = { - event: { - httpMethod: 'POST', - path: '/api/v1/tasks', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ title: 'Background Task', priority: 'high' }), - requestContext: { - requestId: 'req-456', - accountId: '123456789012', - }, - }, - context: { - functionName: 'async-processor', - memoryLimit: 512, - }, - }; - - mockSend.mockResolvedValueOnce({ - StatusCode: 202, - FunctionError: undefined, - Payload: new TextEncoder().encode(JSON.stringify({ jobId: 'async-job-456', queued: true })), - }); - - // Act - await invokeLambdaAsync(functionName, complexPayload); - - // Assert - expect(mockLoggerInfo).toHaveBeenCalledWith( - expect.objectContaining({ - functionName, - statusCode: 202, - }), - '[LambdaClient] < invokeLambdaAsync - successfully invoked Lambda function', - ); - }); - - it('should use Event invocation type for async invocation', async () => { - // Arrange - const functionName = 'event-function'; - const payload = {}; - - mockSend.mockResolvedValueOnce({ - StatusCode: 202, - FunctionError: undefined, - Payload: new TextEncoder().encode(JSON.stringify({ status: 'queued' })), - }); - - // Act - await invokeLambdaAsync(functionName, payload); - - // Assert - expect(mockSend).toHaveBeenCalled(); - }); - - it('should handle Lambda async response with non-JSON payload', async () => { - // Arrange - const functionName = 'text-async-function'; - const payload = {}; - - mockSend.mockResolvedValueOnce({ - StatusCode: 202, - FunctionError: undefined, - Payload: new TextEncoder().encode('plain text response'), - }); - - // Act & Assert - invokeLambdaAsync doesn't parse payloads for async - await invokeLambdaAsync(functionName, payload); - - expect(mockLoggerInfo).toHaveBeenCalledWith( - expect.objectContaining({ - functionName, - statusCode: 202, - }), - '[LambdaClient] < invokeLambdaAsync - successfully invoked Lambda function', - ); - }); - - it('should handle successful async invocation with various HTTP status codes', async () => { - // Arrange - const functionName = 'api-async-function'; - const payload = {}; - - mockSend.mockResolvedValueOnce({ - StatusCode: 202, - FunctionError: undefined, - Payload: new TextEncoder().encode( - JSON.stringify({ - statusCode: 202, - body: JSON.stringify({ message: 'Accepted' }), - }), - ), - }); - - // Act - await invokeLambdaAsync(functionName, payload); - - // Assert - expect(mockLoggerInfo).toHaveBeenCalledWith( - expect.objectContaining({ - functionName, - statusCode: 202, - }), - '[LambdaClient] < invokeLambdaAsync - successfully invoked Lambda function', - ); - }); - - it('should include function name in async log messages', async () => { - // Arrange - const functionName = 'my-async-special-function'; - const payload = {}; - - mockSend.mockResolvedValueOnce({ - StatusCode: 202, - FunctionError: undefined, - Payload: new TextEncoder().encode(JSON.stringify({})), - }); - - // Act - await invokeLambdaAsync(functionName, payload); - - // Assert - expect(mockLoggerInfo).toHaveBeenCalledWith({ functionName }, '[LambdaClient] > invokeLambdaAsync'); - expect(mockLoggerDebug).toHaveBeenCalledWith( - expect.any(Object), - '[LambdaClient] invokeLambdaAsync - InvokeCommand', - ); - expect(mockLoggerInfo).toHaveBeenCalledWith( - { - functionName, - statusCode: 202, - }, - '[LambdaClient] < invokeLambdaAsync - successfully invoked Lambda function', - ); - }); - }); -}); diff --git a/src/utils/lambda-client.ts b/src/utils/lambda-client.ts deleted file mode 100644 index 7184ed0..0000000 --- a/src/utils/lambda-client.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { InvokeCommand, LambdaClient } from '@aws-sdk/client-lambda'; - -import { config } from './config'; -import { logger } from './logger'; - -/** - * AWS Lambda service client - */ -const _lambdaClient = new LambdaClient({ region: config.AWS_REGION }); - -/** - * Type representing a JSON payload for Lambda invocation - */ -export type JsonPayload = Record | Array; - -/** - * Invokes a Lambda function synchronously (RequestResponse) - * @param functionName - The name or ARN of the Lambda function to invoke - * @param payload - The JSON payload to pass to the Lambda function - * @returns The response payload from the Lambda function - * @throws Error if the Lambda invocation fails - */ -export const invokeLambdaSync = async (functionName: string, payload: JsonPayload): Promise => { - logger.info({ functionName }, '[LambdaClient] > invokeLambdaSync'); - - try { - // Create the InvokeCommand with RequestResponse invocation type - const command = new InvokeCommand({ - FunctionName: functionName, - InvocationType: 'RequestResponse', - Payload: JSON.stringify(payload), - }); - - logger.debug({ command }, '[LambdaClient] invokeLambdaSync - InvokeCommand'); - - // Send the command to invoke the Lambda function - const response = await _lambdaClient.send(command); - - // Parse the response payload - const responsePayload = response.Payload ? JSON.parse(new TextDecoder().decode(response.Payload)) : null; - - // Check for function errors - if (response.FunctionError) { - logger.error( - { - functionName, - FunctionError: response.FunctionError, - responsePayload, - }, - '[LambdaClient] < invokeLambdaSync - Lambda function returned an error', - ); - throw new Error(`Lambda function error: ${response.FunctionError}`); - } - - logger.info( - { functionName, statusCode: response.StatusCode }, - '[LambdaClient] < invokeLambdaSync - successfully invoked Lambda function', - ); - return responsePayload as T; - } catch (error) { - // Handle invocation errors - logger.error( - { error: error as Error, functionName }, - '[LambdaClient] < invokeLambdaSync - failed to invoke Lambda function', - ); - throw error; - } -}; - -/** - * Invokes a Lambda function asynchronously (Event) - * @param functionName - The name or ARN of the Lambda function to invoke - * @param payload - The JSON payload to pass to the Lambda function - * @throws Error if the Lambda invocation fails - */ -export const invokeLambdaAsync = async (functionName: string, payload: JsonPayload): Promise => { - logger.info({ functionName }, '[LambdaClient] > invokeLambdaAsync'); - - try { - // Create the InvokeCommand with Event invocation type - const command = new InvokeCommand({ - FunctionName: functionName, - InvocationType: 'Event', - Payload: JSON.stringify(payload), - }); - - logger.debug({ command }, '[LambdaClient] invokeLambdaAsync - InvokeCommand'); - - // Send the command to invoke the Lambda function - const response = await _lambdaClient.send(command); - - // Check for function errors - if (response.FunctionError) { - logger.error( - { functionName, FunctionError: response.FunctionError, response }, - '[LambdaClient] < invokeLambdaAsync - Lambda function returned an error', - ); - throw new Error(`Lambda function error: ${response.FunctionError}`); - } - - logger.info( - { functionName, statusCode: response.StatusCode }, - '[LambdaClient] < invokeLambdaAsync - successfully invoked Lambda function', - ); - } catch (error) { - // Handle invocation errors - logger.error( - { error: error as Error, functionName }, - '[LambdaClient] < invokeLambdaAsync - failed to invoke Lambda function', - ); - throw error; - } -}; diff --git a/src/utils/logger.ts b/src/utils/logger.ts index d338eaf..34a8e00 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,23 +1,19 @@ -import pino from 'pino'; -import { CloudwatchLogFormatter, pinoLambdaDestination, StructuredLogFormatter } from 'pino-lambda'; +import { Logger } from '@leanstacks/lambda-utils'; import { config } from './config'; /** - * Initialize Pino Lambda destination - * @see https://www.npmjs.com/package/pino-lambda#best-practices + * Initializes and configures the logger instance with application settings. */ -const _lambdaDestination = pinoLambdaDestination({ - formatter: config.LOGGING_FORMAT === 'json' ? new StructuredLogFormatter() : new CloudwatchLogFormatter(), +const _logger = new Logger({ + enabled: config.LOGGING_ENABLED, + level: config.LOGGING_LEVEL, + format: config.LOGGING_FORMAT, }); /** - * Pino logger instance + * Exports the configured logger instance for use throughout the application. + * This singleton pattern promotes consistent logging behavior and prevents + * the creation of multiple logger instances, which can lead to performance issues. */ -export const logger = pino( - { - enabled: config.LOGGING_ENABLED, - level: config.LOGGING_LEVEL, - }, - _lambdaDestination, -); +export const logger = _logger.instance; diff --git a/src/utils/sns-client.test.ts b/src/utils/sns-client.test.ts deleted file mode 100644 index fdb4341..0000000 --- a/src/utils/sns-client.test.ts +++ /dev/null @@ -1,301 +0,0 @@ -import { SNSClient } from '@aws-sdk/client-sns'; - -// Mock the SNS client and logger before importing the module under test -const mockSend = jest.fn(); -const mockLoggerDebug = jest.fn(); -const mockLoggerError = jest.fn(); - -jest.mock('@aws-sdk/client-sns', () => { - const actualModule = jest.requireActual('@aws-sdk/client-sns'); - return { - ...actualModule, - SNSClient: jest.fn(), - }; -}); - -jest.mock('./logger.js', () => ({ - logger: { - debug: mockLoggerDebug, - info: jest.fn(), - warn: jest.fn(), - error: mockLoggerError, - }, -})); - -jest.mock('./config.js', () => ({ - config: { - TASKS_TABLE: 'test-tasks-table', - TASK_TOPIC_ARN: 'arn:aws:sns:us-east-1:123456789012:test-task-topic', - AWS_REGION: 'us-east-1', - LOGGING_ENABLED: true, - LOGGING_LEVEL: 'debug', - LOGGING_FORMAT: 'json', - CORS_ALLOW_ORIGIN: '*', - }, -})); - -describe('sns-client', () => { - let publishToTopic: typeof import('./sns-client').publishToTopic; - - beforeEach(() => { - jest.clearAllMocks(); - - // Mock SNSClient constructor - (SNSClient as jest.Mock).mockImplementation(() => ({ - send: mockSend, - })); - - // Import after mocks are set up - const snsClient = require('./sns-client'); - publishToTopic = snsClient.publishToTopic; - }); - - describe('publishToTopic', () => { - it('should publish a message to SNS topic successfully', async () => { - // Arrange - const topicArn = 'arn:aws:sns:us-east-1:123456789012:test-topic'; - const message = { action: 'task_created', payload: { taskId: '123' } }; - - mockSend.mockResolvedValue({ - MessageId: 'message-id-123', - }); - - // Act - const result = await publishToTopic(topicArn, message); - - // Assert - expect(result).toBe('message-id-123'); - expect(mockSend).toHaveBeenCalledTimes(1); - expect(mockLoggerDebug).toHaveBeenCalledWith( - expect.objectContaining({ topicArn }), - '[SnsClient] > publishToTopic', - ); - }); - - it('should convert message object to JSON string', async () => { - // Arrange - const topicArn = 'arn:aws:sns:us-east-1:123456789012:test-topic'; - const message = { action: 'task_updated', payload: { taskId: '456' } }; - - mockSend.mockResolvedValue({ - MessageId: 'message-id-456', - }); - - // Act - await publishToTopic(topicArn, message); - - // Assert - const publishCommand = mockSend.mock.calls[0][0]; - expect(publishCommand.input.Message).toBe(JSON.stringify(message)); - }); - - it('should include message attributes when provided', async () => { - // Arrange - const topicArn = 'arn:aws:sns:us-east-1:123456789012:test-topic'; - const message = { action: 'task_created' }; - const attributes = { - eventType: { - DataType: 'String' as const, - StringValue: 'task_created', - }, - }; - - mockSend.mockResolvedValue({ - MessageId: 'message-id-789', - }); - - // Act - await publishToTopic(topicArn, message, attributes); - - // Assert - const publishCommand = mockSend.mock.calls[0][0]; - expect(publishCommand.input.MessageAttributes).toEqual(attributes); - }); - - it('should return empty string when MessageId is undefined', async () => { - // Arrange - const topicArn = 'arn:aws:sns:us-east-1:123456789012:test-topic'; - const message = { action: 'test' }; - - mockSend.mockResolvedValue({ - MessageId: undefined, - }); - - // Act - const result = await publishToTopic(topicArn, message); - - // Assert - expect(result).toBe(''); - }); - - it('should log publish success with message details', async () => { - // Arrange - const topicArn = 'arn:aws:sns:us-east-1:123456789012:test-topic'; - const message = { action: 'task_deleted' }; - - mockSend.mockResolvedValue({ - MessageId: 'msg-delete-123', - }); - - // Act - await publishToTopic(topicArn, message); - - // Assert - expect(mockLoggerDebug).toHaveBeenCalledWith( - expect.objectContaining({ - topicArn, - messageId: 'msg-delete-123', - }), - '[SnsClient] < publishToTopic - successfully published message', - ); - }); - - it('should handle SNS publish errors and rethrow them', async () => { - // Arrange - const topicArn = 'arn:aws:sns:us-east-1:123456789012:test-topic'; - const message = { action: 'test' }; - const mockError = new Error('SNS publish failed'); - - mockSend.mockRejectedValue(mockError); - - // Act & Assert - await expect(publishToTopic(topicArn, message)).rejects.toThrow('SNS publish failed'); - expect(mockLoggerError).toHaveBeenCalledWith( - expect.objectContaining({ - error: mockError, - topicArn, - }), - '[SnsClient] < publishToTopic - failed to publish message to SNS', - ); - }); - - it('should handle complex message payloads', async () => { - // Arrange - const topicArn = 'arn:aws:sns:us-east-1:123456789012:test-topic'; - const message = { - action: 'task_created', - payload: { - task: { - id: 'task-123', - title: 'New Task', - detail: 'Task details', - isComplete: false, - createdAt: '2025-12-01T10:00:00.000Z', - updatedAt: '2025-12-01T10:00:00.000Z', - }, - }, - timestamp: '2025-12-01T10:00:00.000Z', - }; - - mockSend.mockResolvedValue({ - MessageId: 'complex-msg-id', - }); - - // Act - const result = await publishToTopic(topicArn, message); - - // Assert - expect(result).toBe('complex-msg-id'); - const publishCommand = mockSend.mock.calls[0][0]; - const publishedMessage = JSON.parse(publishCommand.input.Message); - expect(publishedMessage.action).toBe('task_created'); - expect(publishedMessage.payload.task.title).toBe('New Task'); - }); - - it('should handle messages with empty objects', async () => { - // Arrange - const topicArn = 'arn:aws:sns:us-east-1:123456789012:test-topic'; - const message = {}; - - mockSend.mockResolvedValue({ - MessageId: 'empty-msg-id', - }); - - // Act - const result = await publishToTopic(topicArn, message); - - // Assert - expect(result).toBe('empty-msg-id'); - const publishCommand = mockSend.mock.calls[0][0]; - expect(publishCommand.input.Message).toBe('{}'); - }); - - it('should not include message attributes when not provided', async () => { - // Arrange - const topicArn = 'arn:aws:sns:us-east-1:123456789012:test-topic'; - const message = { action: 'test' }; - - mockSend.mockResolvedValue({ - MessageId: 'msg-no-attrs', - }); - - // Act - await publishToTopic(topicArn, message); - - // Assert - const publishCommand = mockSend.mock.calls[0][0]; - expect(publishCommand.input.MessageAttributes).toBeUndefined(); - }); - - it('should use correct topic ARN in publish call', async () => { - // Arrange - const topicArn = 'arn:aws:sns:us-west-2:987654321098:custom-topic'; - const message = { action: 'test' }; - - mockSend.mockResolvedValue({ - MessageId: 'custom-topic-msg', - }); - - // Act - await publishToTopic(topicArn, message); - - // Assert - const publishCommand = mockSend.mock.calls[0][0]; - expect(publishCommand.input.TopicArn).toBe(topicArn); - }); - }); - - describe('MessageAttributes interface', () => { - it('should allow String type attributes', async () => { - // Arrange - const topicArn = 'arn:aws:sns:us-east-1:123456789012:test-topic'; - const message = { action: 'test' }; - const attributes = { - eventType: { - DataType: 'String' as const, - StringValue: 'task_event', - }, - }; - - mockSend.mockResolvedValue({ MessageId: 'test-msg' }); - - // Act - const result = await publishToTopic(topicArn, message, attributes); - - // Assert - expect(result).toBe('test-msg'); - const publishCommand = mockSend.mock.calls[0][0]; - expect(publishCommand.input.MessageAttributes.eventType.DataType).toBe('String'); - }); - - it('should allow Number type attributes', async () => { - // Arrange - const topicArn = 'arn:aws:sns:us-east-1:123456789012:test-topic'; - const message = { action: 'test' }; - const attributes = { - priority: { - DataType: 'Number' as const, - StringValue: '1', - }, - }; - - mockSend.mockResolvedValue({ MessageId: 'test-msg' }); - - // Act - const result = await publishToTopic(topicArn, message, attributes); - - // Assert - expect(result).toBe('test-msg'); - }); - }); -}); diff --git a/src/utils/sns-client.ts b/src/utils/sns-client.ts deleted file mode 100644 index 0c0ee24..0000000 --- a/src/utils/sns-client.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { MessageAttributeValue, PublishCommand, SNSClient } from '@aws-sdk/client-sns'; - -import { config } from './config.js'; -import { logger } from './logger.js'; - -/** - * SNS client instance for publishing messages to SNS topics. - */ -const _snsClient = new SNSClient({ - region: config.AWS_REGION, -}); - -/** - * Interface for SNS message attributes. - */ -export interface MessageAttributes { - [key: string]: MessageAttributeValue; -} - -/** - * Publishes a message to an SNS topic. - * @param topicArn - The ARN of the SNS topic to publish to - * @param message - The message content (will be converted to JSON string) - * @param attributes - Optional message attributes for filtering - * @returns Promise that resolves to the message ID - * @throws Error if the SNS publish operation fails - */ -export const publishToTopic = async ( - topicArn: string, - message: Record, - attributes?: MessageAttributes, -): Promise => { - logger.debug({ topicArn }, '[SnsClient] > publishToTopic'); - - try { - // Create the PublishCommand with the message and attributes - const command = new PublishCommand({ - TopicArn: topicArn, - Message: JSON.stringify(message), - MessageAttributes: attributes, - }); - - logger.debug({ command }, '[SnsClient] publishToTopic - PublishCommand'); - - // Send the publish command - const response = await _snsClient.send(command); - - logger.debug( - { topicArn, messageId: response.MessageId }, - '[SnsClient] < publishToTopic - successfully published message', - ); - - return response.MessageId ?? ''; - } catch (error) { - // Handle publish errors - logger.error( - { error: error as Error, topicArn }, - '[SnsClient] < publishToTopic - failed to publish message to SNS', - ); - throw error; - } -};