From 0ea417a1fa171049d2333cb8b9ed507fd316eb86 Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Sat, 31 May 2025 07:59:25 -0400 Subject: [PATCH 01/26] readme --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/README.md b/README.md index 9725228..8f3fc48 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,37 @@ The [official guide for GitHub Copilot in VS Code][vscode-copilot-docs] provides The VS Code YouTube channel has a playlist with many interesting videos and tutorials for [GitHub Copilot in VS Code](https://youtube.com/playlist?list=PLj6YeMhvp2S7rQaCLRrMnzRdkNdKnMVwg&si=KIRHyFKYyMcUssQ3). This official [tips and tricks guide](https://code.visualstudio.com/docs/copilot/copilot-tips-and-tricks) from VS Code provides an excellent summary of Copilot best practices. + +## Project structure + +The project structure follows the best practices for Copilot assistance. This project is ready to begin iterative development with a Copilot AI agent. + +Note that the application component does not exist, not yet anyway. This base project structure is primed for building an AWS Lambda component from the very beginning using Copilot agent mode with you in the driver's seat. + +``` +/.github + copilot-instructions.md # Copilot instructions + +.editorconfig # Editor configuration +.prettierrc # Prettier configuration +.gitignore # Git ignore +LICENSE # Software license +README.md # This document +``` + +## How to use + +### Update the instructions + +Add a section to the [Copilot Instructions](./.github/copilot-instructions.md) document immediately following the **Role** section. Provide an overview of the project like this: + +```md +--- + +## Project Overview + +- **Component:** Task Service **task-service** +- **Description:** This service provides a REST API for managing tasks, including creating, retrieving, updating, and deleting tasks. It uses AWS Lambda functions triggered by API Gateway events, with business logic encapsulated in service classes. The project follows best practices for TypeScript development, AWS CDK infrastructure management, and unit testing with Vitest. + +--- +``` From 3426e3c3a35d8b95ec652c1740ee525ae3a31536 Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Sat, 31 May 2025 07:59:32 -0400 Subject: [PATCH 02/26] instructions --- .github/copilot-instructions.md | 72 +++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 25 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 573ab3e..7853b6b 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -10,6 +10,13 @@ You are a **Senior TypeScript developer** working on an AWS Lambda REST API proj --- +## Project Overview + +- **Component:** Task Service **task-service** +- **Description:** This service provides a REST API for managing tasks, including creating, retrieving, updating, and deleting tasks. It uses AWS Lambda functions triggered by API Gateway events, with business logic encapsulated in service classes. The project follows best practices for TypeScript development, AWS CDK infrastructure management, and unit testing with Vitest. + +--- + ## Language & Stack - **Language:** TypeScript @@ -52,13 +59,13 @@ You are a **Senior TypeScript developer** working on an AWS Lambda REST API proj ``` /src /handlers - getUser.ts # Lambda handler - getUser.test.ts # Unit test for getUser + getTask.ts # Lambda handler + getTask.test.ts # Unit test for getTask /services - userService.ts # Business logic - userService.test.ts # Unit test for userService + taskService.ts # Business logic + taskService.test.ts # Unit test for taskService /models - User.ts + Task.ts /utils response.ts # Helper for formatting Lambda responses response.test.ts @@ -82,16 +89,16 @@ vitest.config.ts # Vitest config - Example: Basic Lambda + API Gateway route: ```ts -const getUserFunction = new NodejsFunction(this, 'GetUserFunction', { - entry: '../src/handlers/getUser.ts', - handler: 'getUser', +const getTaskFunction = new NodejsFunction(this, 'GetTaskFunction', { + entry: '../src/handlers/getTask.ts', + handler: 'getTask', environment: { - USERS_TABLE: usersTable.tableName, + TASKS_TABLE: tasksTable.tableName, }, }); -const api = new RestApi(this, 'UsersApi'); -api.root.addResource('users').addResource('{userId}').addMethod('GET', new LambdaIntegration(getUserFunction)); +const api = new RestApi(this, 'TasksApi'); +api.root.addResource('tasks').addResource('{taskId}').addMethod('GET', new LambdaIntegration(getTaskFunction)); ``` --- @@ -105,15 +112,30 @@ api.root.addResource('users').addResource('{userId}').addMethod('GET', new Lambd ### Handler Format Example ```ts -export const getUser = async (event: APIGatewayProxyEvent): Promise => { - const userId = event.pathParameters?.userId; - if (!userId) return badRequest('Missing userId'); +// Zod schema for request validation +const requestSchema = z.object({ + pathParameters: z.object({ + taskId: z.string().min(1, 'taskId path variable is required'), + }), +}); +type Request = z.infer; + +// Lambda handler function +export const getTask = async (event: APIGatewayProxyEvent): Promise => { + // Validate input + const result = requestSchema.safeParse(event); + if (!result.success) return badRequest('Invalid request'); + + // Extract validated data + const request: Request = result.data; + const { taskId } = request.pathParameters; try { - const user = await userService.getUserById(userId); - return user ? ok(user) : notFound('User not found'); + // Call service to get task + const task = await taskService.getTaskById(taskId); + return task ? ok(task) : notFound('Task not found'); } catch (err) { - console.error('Failed to get user:', err); + console.error('Failed to get task:', err); return internalServerError('Unexpected error'); } }; @@ -136,18 +158,18 @@ export const getUser = async (event: APIGatewayProxyEvent): Promise { +it('returns 400 if taskId is missing', async () => { // Arrange const event = { pathParameters: {} } as unknown as APIGatewayProxyEvent; // Act - const response = await getUser(event); + const response = await getTask(event); // Assert expect(response.statusCode).toBe(400); @@ -169,12 +191,12 @@ it('returns 400 if userId is missing', async () => { ```ts /** - * Lambda Handler: getUser + * Lambda Handler: getTask * * Copilot Instructions: - * - Parse userId from event.pathParameters - * - Call userService.getUserById(userId) - * - Return 200 with user object or 404 if not found + * - Parse taskId from event.pathParameters + * - Call taskService.getTaskById(taskId) + * - Return 200 with task object or 404 if not found * - Validate input; return 400 if invalid * - Catch unexpected errors; log and return 500 */ From 4fc007de8cd4af0ed7e8edd4ce856bbab887a3f2 Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Sat, 31 May 2025 08:34:09 -0400 Subject: [PATCH 03/26] initial reqs --- docs/requirements/01-create-task.md | 34 +++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 docs/requirements/01-create-task.md diff --git a/docs/requirements/01-create-task.md b/docs/requirements/01-create-task.md new file mode 100644 index 0000000..025effc --- /dev/null +++ b/docs/requirements/01-create-task.md @@ -0,0 +1,34 @@ +# Requirement: Create a Task + +This document describes the requirements for an AWS Lambda REST API that will create and persist a new **Task** in DynamoDB. + +--- + +## Description + +Create an AWS Lambda function which receives a REST API request containing the attributes of a new **Task** to be created. The handler should validate the create task object in the request. The task service should store the **Task** attributes in the task table in AWS DynamoDB. The task service should use the AWS SDK to interact with AWS DynamoDB. The task service should use the DynamoDB Document Client. The Lambda function should return the created **Task** in the response body if successful along with an appropriate HTTP status code. If an error occurs, the Lambda function should return an appropriate HTTP status code and the body should contain a meaningful message. + +Initialize the AWS CDK for this project. Ensure that the CDK configuration is updated to use the project directory structure specified in the instructions. Create appropriate AWS infrastructure using the AWS CDK including an REST API Gateway, the Lambda function, the DynamoDB table, and all associated AWS resources. + +Initialize Vitest for this project. Add unit tests for the Lambda function source members. + +Implement these requirements step by step. Follow all best practices and structure for this project. + +--- + +## Task object + +A **Task** object has the following attributes: + +- **id:** Primary key identifier of a task. String. Required. Maximum of 24 characters. +- **title:** The task title. String. Required. Maximum of 100 characters. +- **detail:** Additional information for the task. String. Optional. Maximum of 2000 characters. +- **isComplete:** Indicates if the task is complete. Boolean. Optional. Defaults to **false** if not provided. +- **dueAt:** The task due date. String. Optioanl. Use ISO-8601 date format. + +### Create Task Object + +The request body to create a new task, the **CreateTaskRequest**, is slightly different from the **Task** object in the following ways: + +- **id** The id is optional on the create task request. +- **isComplete** Defaults to **false** if not defined. From e751bd38c66e8fba2033f2a06c7bb9c980f09840 Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Mon, 2 Jun 2025 09:42:03 -0400 Subject: [PATCH 04/26] create a task --- .github/copilot-instructions.md | 46 +- .gitignore | 2 +- .nvmrc | 1 + README.md | 13 + cdk.json | 30 + docs/configuration.md | 68 + docs/requirements/01-create-task.md | 2 +- eslint.config.mjs | 32 + infrastructure/app.ts | 26 + infrastructure/stacks/ApiStack.ts | 74 + infrastructure/utils/config.ts | 56 + package-lock.json | 6033 +++++++++++++++++++++++++++ package.json | 44 + src/handlers/createTask.test.ts | 167 + src/handlers/createTask.ts | 69 + src/models/Task.ts | 35 + src/services/taskService.test.ts | 156 + src/services/taskService.ts | 52 + src/utils/awsClients.test.ts | 85 + src/utils/awsClients.ts | 23 + src/utils/config.test.ts | 180 + src/utils/config.ts | 65 + src/utils/logger.test.ts | 190 + src/utils/logger.ts | 71 + src/utils/response.test.ts | 98 + src/utils/response.ts | 23 + tsconfig.json | 32 + vitest.config.ts | 20 + 28 files changed, 7690 insertions(+), 3 deletions(-) create mode 100644 .nvmrc create mode 100644 cdk.json create mode 100644 docs/configuration.md create mode 100644 eslint.config.mjs create mode 100644 infrastructure/app.ts create mode 100644 infrastructure/stacks/ApiStack.ts create mode 100644 infrastructure/utils/config.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/handlers/createTask.test.ts create mode 100644 src/handlers/createTask.ts create mode 100644 src/models/Task.ts create mode 100644 src/services/taskService.test.ts create mode 100644 src/services/taskService.ts create mode 100644 src/utils/awsClients.test.ts create mode 100644 src/utils/awsClients.ts create mode 100644 src/utils/config.test.ts create mode 100644 src/utils/config.ts create mode 100644 src/utils/logger.test.ts create mode 100644 src/utils/logger.ts create mode 100644 src/utils/response.test.ts create mode 100644 src/utils/response.ts create mode 100644 tsconfig.json create mode 100644 vitest.config.ts diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 7853b6b..beb9b51 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -132,7 +132,7 @@ export const getTask = async (event: APIGatewayProxyEvent): Promise => { + // Generate a new ID + const taskId = uuidv4(); + + // Create the complete task object + const task: Task = { + id: taskId, + title: createTaskRequest.title, + detail: createTaskRequest.detail, + isComplete: createTaskRequest.isComplete ?? false, + dueAt: createTaskRequest.dueAt, + }; + + // Log the task creation + logger.info(`Creating task with ID: ${taskId}`, { task }); + + // Save to DynamoDB + await dynamoDocClient.send( + new PutCommand({ + TableName: config.TASKS_TABLE, + Item: task, + }), + ); + + return task; +}; + +// Define and export the TaskService with the methods to handle task operations +export const TaskService = { + createTask, +}; +``` + --- ## Co-located Testing Guidelines diff --git a/.gitignore b/.gitignore index 2251488..6543172 100644 --- a/.gitignore +++ b/.gitignore @@ -17,5 +17,5 @@ build dist # AWS CDK -cdk.output +cdk.out .cdk.staging \ No newline at end of file diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..156ca6d --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22.16.0 \ No newline at end of file diff --git a/README.md b/README.md index 8f3fc48..08c1acc 100644 --- a/README.md +++ b/README.md @@ -56,3 +56,16 @@ Add a section to the [Copilot Instructions](./.github/copilot-instructions.md) d --- ``` + +## Configuration + +The Task Service uses a centralized configuration system based on Zod validation to ensure all environment variables are properly validated and typed. This approach helps catch configuration errors early and provides TypeScript type safety for all configuration values. + +### Key Features + +- **Validation**: All environment variables are validated using Zod schemas +- **Type Safety**: TypeScript types are automatically derived from the validation schemas +- **Default Values**: Optional environment variables have sensible defaults +- **Environment-Specific Config**: Helper for environment-specific configuration + +For more details, see the [Configuration Documentation](docs/configuration.md). diff --git a/cdk.json b/cdk.json new file mode 100644 index 0000000..10e3415 --- /dev/null +++ b/cdk.json @@ -0,0 +1,30 @@ +{ + "app": "npx ts-node --prefer-ts-exts infrastructure/app.ts", + "watch": { + "include": ["infrastructure/**/*.ts", "src/**/*.ts"], + "exclude": ["README.md", "cdk*.json", "**/*.d.ts", "**/*.test.ts", "node_modules", "dist"] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": ["aws", "aws-cn"], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "environment": "dev" + } +} diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..b2e7729 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,68 @@ +# Configuration + +This document describes the configuration system used in the Task Service. + +## Overview + +The Task Service uses a centralized configuration system based on Zod validation to ensure all environment variables are properly validated and typed. This approach helps catch configuration errors early and provides TypeScript type safety for all configuration values. + +## Configuration Utility + +The configuration utility is located in `src/utils/config.ts`. It provides: + +1. Validation of all environment variables +2. Type-safe access to configuration values +3. Default values for optional configuration +4. Environment-specific configuration helper + +## Required Environment Variables + +The following environment variables are required: + +| Variable | Description | +| ----------- | -------------------------------------------------- | +| TASKS_TABLE | The name of the DynamoDB table used to store tasks | + +## Optional Environment Variables + +The following environment variables are optional and have default values: + +| Variable | Default | Description | +| -------------- | --------- | ------------------------------------------------------------------------- | +| AWS_REGION | us-east-1 | The AWS region to use for AWS SDK clients | +| ENABLE_LOGGING | true | Indicates if logging is enabled | +| LOG_LEVEL | info | The level of logging statements emitted. One of: debug, info, warn, error | + +## Usage + +### In Application Code + +Instead of using `process.env` directly, import and use the `config` object: + +```typescript +import { config } from '@/utils/config'; + +// Access configuration values +const tableName = config.TASKS_TABLE; +const region = config.AWS_REGION; +``` + +### In Tests + +For tests that need to modify environment variables, use the `refreshConfig` function: + +```typescript +import { config, refreshConfig } from '../utils/config'; + +// Set new environment variables +process.env.AWS_REGION = 'eu-west-1'; + +// Refresh the configuration +const updatedConfig = refreshConfig(); + +// Now config.AWS_REGION will be 'eu-west-1' +``` + +## CDK Infrastructure Configuration + +The CDK infrastructure uses a similar configuration system located in `@infrastructure/utils/config.ts`. It validates and provides type-safe access to CDK-specific environment variables like `CDK_DEFAULT_ACCOUNT` and `CDK_DEFAULT_REGION`. diff --git a/docs/requirements/01-create-task.md b/docs/requirements/01-create-task.md index 025effc..271e405 100644 --- a/docs/requirements/01-create-task.md +++ b/docs/requirements/01-create-task.md @@ -24,7 +24,7 @@ A **Task** object has the following attributes: - **title:** The task title. String. Required. Maximum of 100 characters. - **detail:** Additional information for the task. String. Optional. Maximum of 2000 characters. - **isComplete:** Indicates if the task is complete. Boolean. Optional. Defaults to **false** if not provided. -- **dueAt:** The task due date. String. Optioanl. Use ISO-8601 date format. +- **dueAt:** The task due date. String. Optioanl. Use ISO-8601 date format, YYYY-MM-DD. ### Create Task Object diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..1dd776e --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,32 @@ +// This file is used to configure ESLint for a TypeScript project. +// It extends the recommended rules from ESLint and TypeScript ESLint plugin, +// and integrates Prettier for code formatting. +import tseslint from 'typescript-eslint'; +import js from '@eslint/js'; +import globals from 'globals'; +import eslintConfigPrettier from 'eslint-config-prettier/flat'; + +export default tseslint.config( + { ignores: ['dist', 'coverage', 'cdk.out'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.ts'], + languageOptions: { + ecmaVersion: 'latest', + globals: globals.node, + }, + plugins: { + prettier: eslintConfigPrettier, + }, + rules: { + '@typescript-eslint/no-unused-vars': [ + 'warn', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + }, + ], + }, + }, +); diff --git a/infrastructure/app.ts b/infrastructure/app.ts new file mode 100644 index 0000000..2589d51 --- /dev/null +++ b/infrastructure/app.ts @@ -0,0 +1,26 @@ +#!/usr/bin/env node +import 'source-map-support/register'; +import * as cdk from 'aws-cdk-lib'; +import { ApiStack } from './stacks/ApiStack'; +import { cdkConfig } from './utils/config'; + +const app = new cdk.App(); + +// Define tags that will be applied to all resources +const tags = { + App: 'task-service', + Env: cdkConfig.ENV, + OU: 'leanstacks', + Owner: 'M Warman', +}; + +// Create the API stack +new ApiStack(app, 'TaskServiceApiStack', { + stackName: `task-service-api-${tags.Env}`, + description: 'Task Service API with Lambda, API Gateway, and DynamoDB', + tags: tags, + env: { + account: cdkConfig.CDK_DEFAULT_ACCOUNT, + region: cdkConfig.CDK_DEFAULT_REGION, + }, +}); diff --git a/infrastructure/stacks/ApiStack.ts b/infrastructure/stacks/ApiStack.ts new file mode 100644 index 0000000..f831209 --- /dev/null +++ b/infrastructure/stacks/ApiStack.ts @@ -0,0 +1,74 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as lambdaNodejs from 'aws-cdk-lib/aws-lambda-nodejs'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as path from 'path'; + +export class ApiStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + // Create DynamoDB table for tasks + const tasksTable = new dynamodb.Table(this, 'TasksTable', { + partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: cdk.RemovalPolicy.DESTROY, // Use RETAIN for production + }); + + // Create and configure CloudWatch Log Group for the Lambda function + const createTaskFunctionLogGroup = new logs.LogGroup(this, 'CreateTaskFunctionLogGroup', { + retention: logs.RetentionDays.ONE_WEEK, + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Create Lambda function for creating tasks + const createTaskFunction = new lambdaNodejs.NodejsFunction(this, 'CreateTaskFunction', { + runtime: lambda.Runtime.NODEJS_22_X, + entry: path.join(__dirname, '../../src/handlers/createTask.ts'), + handler: 'createTask', + environment: { + TASKS_TABLE: tasksTable.tableName, + }, + bundling: { + minify: true, + sourceMap: true, + }, + logGroup: createTaskFunctionLogGroup, + memorySize: 1024, + timeout: cdk.Duration.seconds(6), + }); + + // Grant the Lambda function permissions to write to the DynamoDB table + tasksTable.grantWriteData(createTaskFunction); + + // Create API Gateway + const api = new apigateway.RestApi(this, 'TasksApi', { + restApiName: 'Task Service API', + description: 'API for managing tasks', + deployOptions: { + stageName: 'api', + }, + }); + + // Create a tasks resource + const tasksResource = api.root.addResource('tasks'); + + // Add a POST method to create a new task + tasksResource.addMethod('POST', new apigateway.LambdaIntegration(createTaskFunction)); + + // Output the API URL + new cdk.CfnOutput(this, 'ApiUrl', { + value: api.url, + description: 'URL for the Task Service API', + }); + + // Output the DynamoDB table name + new cdk.CfnOutput(this, 'TasksTableName', { + value: tasksTable.tableName, + description: 'Name of the Tasks DynamoDB table', + }); + } +} diff --git a/infrastructure/utils/config.ts b/infrastructure/utils/config.ts new file mode 100644 index 0000000..56eec61 --- /dev/null +++ b/infrastructure/utils/config.ts @@ -0,0 +1,56 @@ +import { z } from 'zod'; + +/** + * Schema for validating CDK environment variables + */ +const cdkEnvSchema = z.object({ + // Required variables + CDK_DEFAULT_ACCOUNT: z.string().optional(), + + // Optional variables with defaults + CDK_DEFAULT_REGION: z.string().default('us-east-1'), + ENV: z.enum(['dev', 'qa', 'prod']).default('dev'), +}); + +/** + * Type representing our validated CDK config + */ +export type CdkConfig = z.infer; + +// Cache for the validated config +let configCache: CdkConfig | null = null; + +/** + * Validates environment variables against schema and returns a validated config object + * @throws {Error} if validation fails + */ +function validateCdkConfig(): CdkConfig { + try { + // Parse and validate environment variables + return cdkEnvSchema.parse(process.env); + } catch (error) { + // Handle Zod validation errors + if (error instanceof z.ZodError) { + const errorMessage = error.errors.map((err) => `${err.path.join('.')}: ${err.message}`).join('\n'); + + throw new Error(`CDK Configuration validation failed:\n${errorMessage}`); + } + + // Re-throw other errors + throw error; + } +} + +/** + * Refreshes the configuration by re-validating environment variables + * Useful in tests when environment variables are changed + */ +export function refreshCdkConfig(): CdkConfig { + configCache = validateCdkConfig(); + return configCache; +} + +/** + * Validated CDK configuration object + */ +export const cdkConfig = configCache || refreshCdkConfig(); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..d242367 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6033 @@ +{ + "name": "task-service", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "task-service", + "version": "1.0.0", + "dependencies": { + "@aws-sdk/client-dynamodb": "3.821.0", + "@aws-sdk/lib-dynamodb": "3.821.0", + "@aws-sdk/util-dynamodb": "3.821.0", + "aws-cdk-lib": "2.199.0", + "constructs": "10.4.2", + "uuid": "11.1.0", + "zod": "3.25.48" + }, + "devDependencies": { + "@types/aws-lambda": "8.10.149", + "@types/node": "22.15.29", + "@types/uuid": "10.0.0", + "@typescript-eslint/eslint-plugin": "8.33.0", + "@typescript-eslint/parser": "8.33.0", + "@vitest/coverage-v8": "3.2.0", + "aws-cdk": "2.1017.1", + "eslint": "9.28.0", + "eslint-config-prettier": "10.1.5", + "globals": "16.2.0", + "prettier": "3.5.3", + "rimraf": "6.0.1", + "ts-node": "10.9.2", + "typescript": "5.8.3", + "typescript-eslint": "8.33.0", + "vitest": "3.2.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@ampproject/remapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@aws-cdk/asset-awscli-v1": { + "version": "2.2.237", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.237.tgz", + "integrity": "sha512-OlXylbXI52lboFVJBFLae+WB99qWmI121x/wXQHEMj2RaVNVbWE+OAHcDk2Um1BitUQCaTf9ki57B0Fuqx0Rvw==", + "license": "Apache-2.0" + }, + "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz", + "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==", + "license": "Apache-2.0" + }, + "node_modules/@aws-cdk/cloud-assembly-schema": { + "version": "41.2.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-41.2.0.tgz", + "integrity": "sha512-JaulVS6z9y5+u4jNmoWbHZRs9uGOnmn/ktXygNWKNu1k6lF3ad4so3s18eRu15XCbUIomxN9WPYT6Ehh7hzONw==", + "bundleDependencies": [ + "jsonschema", + "semver" + ], + "license": "Apache-2.0", + "dependencies": { + "jsonschema": "~1.4.1", + "semver": "^7.7.1" + }, + "engines": { + "node": ">= 14.15.0" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { + "version": "1.4.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { + "version": "7.7.1", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-dynamodb": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.821.0.tgz", + "integrity": "sha512-7GyFMN0B7NW6rv1pT0PSWcAFPsziEYnxdZUnF/sMsgXz5U0peviCnuPGUL5jqYL6sf6HgXLYYooHVjqLnVMVVQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.821.0", + "@aws-sdk/credential-provider-node": "3.821.0", + "@aws-sdk/middleware-endpoint-discovery": "3.821.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.821.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.821.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.821.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.1", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.9", + "@smithy/middleware-retry": "^4.1.10", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.17", + "@smithy/util-defaults-mode-node": "^4.0.17", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "@smithy/util-utf8": "^4.0.0", + "@smithy/util-waiter": "^4.0.5", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-dynamodb/node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "license": "MIT" + }, + "node_modules/@aws-sdk/client-dynamodb/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.821.0.tgz", + "integrity": "sha512-aDEBZUKUd/+Tvudi0d9KQlqt2OW2P27LATZX0jkNC8yVk4145bAPS04EYoqdKLuyUn/U33DibEOgKUpxZB12jQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.821.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.821.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.821.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.821.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.1", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.9", + "@smithy/middleware-retry": "^4.1.10", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.17", + "@smithy/util-defaults-mode-node": "^4.0.17", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.821.0.tgz", + "integrity": "sha512-8eB3wKbmfciQFmxFq7hAjy7mXdUs7vBOR5SwT0ZtQBg0Txc18Lc9tMViqqdO6/KU7OukA6ib2IAVSjIJJEN7FQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/core": "^3.5.1", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.821.0.tgz", + "integrity": "sha512-C+s/A72pd7CXwEsJj9+Uq9T726iIfIF18hGRY8o82xcIEfOyakiPnlisku8zZOaAu+jm0CihbbYN4NyYNQ+HZQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.821.0.tgz", + "integrity": "sha512-gIRzTLnAsRfRSNarCag7G7rhcHagz4x5nNTWRihQs5cwTOghEExDy7Tj5m4TEkv3dcTAsNn+l4tnR4nZXo6R+Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.821.0.tgz", + "integrity": "sha512-VRTrmsca8kBHtY1tTek1ce+XkK/H0fzodBKcilM/qXjTyumMHPAzVAxKZfSvGC+28/pXyQzhOEyxZfw7giCiWA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.821.0", + "@aws-sdk/credential-provider-env": "3.821.0", + "@aws-sdk/credential-provider-http": "3.821.0", + "@aws-sdk/credential-provider-process": "3.821.0", + "@aws-sdk/credential-provider-sso": "3.821.0", + "@aws-sdk/credential-provider-web-identity": "3.821.0", + "@aws-sdk/nested-clients": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.821.0.tgz", + "integrity": "sha512-oBgbcgOXWMgknAfhIdTeHSSVIv+k2LXN9oTbxu1r++o4WWBWrEQ8mHU0Zo9dfr7Uaoqi3pezYZznsBkXnMLEOg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.821.0", + "@aws-sdk/credential-provider-http": "3.821.0", + "@aws-sdk/credential-provider-ini": "3.821.0", + "@aws-sdk/credential-provider-process": "3.821.0", + "@aws-sdk/credential-provider-sso": "3.821.0", + "@aws-sdk/credential-provider-web-identity": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.821.0.tgz", + "integrity": "sha512-e18ucfqKB3ICNj5RP/FEdvUfhVK6E9MALOsl8pKP13mwegug46p/1BsZWACD5n+Zf9ViiiHxIO7td03zQixfwA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.821.0.tgz", + "integrity": "sha512-Dt+pheBLom4O/egO4L75/72k9C1qtUOLl0F0h6lmqZe4Mvhz+wDtjoO/MdGC/P1q0kcIX/bBKr0NQ3cIvAH8pA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.821.0", + "@aws-sdk/core": "3.821.0", + "@aws-sdk/token-providers": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.821.0.tgz", + "integrity": "sha512-FF5wnRJkxSQaCVVvWNv53K1MhTMgH8d+O+MHTbkv51gVIgVATrtfFQMKBLcEAxzXrgAliIO3LiNv+1TqqBZ+BA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.821.0", + "@aws-sdk/nested-clients": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/endpoint-cache": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.804.0.tgz", + "integrity": "sha512-TQVDkA/lV6ua75ELZaichMzlp6x7tDa1bqdy/+0ZftmODPtKXuOOEcJxmdN7Ui/YRo1gkRz2D9txYy7IlNg1Og==", + "license": "Apache-2.0", + "dependencies": { + "mnemonist": "0.38.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/lib-dynamodb": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.821.0.tgz", + "integrity": "sha512-o87vY8SqlQtaI7WookHiWeTnPMzWrpv6H/IXuihj04AnNSwhTej2w4uL0kPTmWaA9DIRzmD8n2M9cjFnz6Pj6Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.821.0", + "@aws-sdk/util-dynamodb": "3.821.0", + "@smithy/core": "^3.5.1", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.821.0" + } + }, + "node_modules/@aws-sdk/middleware-endpoint-discovery": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.821.0.tgz", + "integrity": "sha512-8EguERzvpzTN2WrPaspK/F9GSkAzBQbecgIaCL49rJWKAso+ewmVVPnrXGzbeGVXTk4G0XuWSjt8wqUzZyt7wQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/endpoint-cache": "3.804.0", + "@aws-sdk/types": "3.821.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.821.0.tgz", + "integrity": "sha512-xSMR+sopSeWGx5/4pAGhhfMvGBHioVBbqGvDs6pG64xfNwM5vq5s5v6D04e2i+uSTj4qGa71dLUs5I0UzAK3sw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.821.0.tgz", + "integrity": "sha512-0cvI0ipf2tGx7fXYEEN5fBeZDz2RnHyb9xftSgUsEq7NBxjV0yTZfLJw6Za5rjE6snC80dRN8+bTNR1tuG89zA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.821.0.tgz", + "integrity": "sha512-efmaifbhBoqKG3bAoEfDdcM8hn1psF+4qa7ykWuYmfmah59JBeqHLfz5W9m9JoTwoKPkFcVLWZxnyZzAnVBOIg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.821.0.tgz", + "integrity": "sha512-rw8q3TxygMg3VrofN04QyWVCCyGwz3bVthYmBZZseENPWG3Krz1OCKcyqjkTcAxMQlEywOske+GIiOasGKnJ3w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.821.0", + "@smithy/core": "^3.5.1", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.821.0.tgz", + "integrity": "sha512-2IuHcUsWw44ftSEDYU4dvktTEqgyDvkOcfpoGC/UmT4Qo6TVCP3U5tWEGpNK9nN+7nLvekruxxG/jaMt5/oWVw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.821.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.821.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.821.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.821.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.1", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.9", + "@smithy/middleware-retry": "^4.1.10", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.17", + "@smithy/util-defaults-mode-node": "^4.0.17", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.821.0.tgz", + "integrity": "sha512-t8og+lRCIIy5nlId0bScNpCkif8sc0LhmtaKsbm0ZPm3sCa/WhCbSZibjbZ28FNjVCV+p0D9RYZx0VDDbtWyjw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.821.0.tgz", + "integrity": "sha512-qJ7wgKhdxGbPg718zWXbCYKDuSWZNU3TSw64hPRW6FtbZrIyZxObpiTKC6DKwfsVoZZhHEoP/imGykN1OdOTJA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.821.0", + "@aws-sdk/nested-clients": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.821.0.tgz", + "integrity": "sha512-Znroqdai1a90TlxGaJ+FK1lwC0fHpo97Xjsp5UKGR5JODYm7f9+/fF17ebO1KdoBr/Rm0UIFiF5VmI8ts9F1eA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-dynamodb": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.821.0.tgz", + "integrity": "sha512-ziVGOwqsqiZzTuSEKTxbGA6NH0MvSivmH0k6WRqoVhgl1uu65BZKI/z7musNDuNruwMzvL5ChlCq+npOo6E0kA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.821.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.821.0.tgz", + "integrity": "sha512-Uknt/zUZnLE76zaAAPEayOeF5/4IZ2puTFXvcSCWHsi9m3tqbb9UozlnlVqvCZLCRWfQryZQoG2W4XSS3qgk5A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "@smithy/util-endpoints": "^3.0.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.804.0.tgz", + "integrity": "sha512-zVoRfpmBVPodYlnMjgVjfGoEZagyRF5IPn3Uo6ZvOZp24chnW/FRstH7ESDHDDRga4z3V+ElUQHKpFDXWyBW5A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.821.0.tgz", + "integrity": "sha512-irWZHyM0Jr1xhC+38OuZ7JB6OXMLPZlj48thElpsO1ZSLRkLZx5+I7VV6k3sp2yZ7BYbKz/G2ojSv4wdm7XTLw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.821.0.tgz", + "integrity": "sha512-YwMXc9EvuzJgnLBTyiQly2juPujXwDgcMHB0iSN92tHe7Dd1jJ1feBmTgdClaaqCeHFUaFpw+3JU/ZUJ6LjR+A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.4.tgz", + "integrity": "sha512-BRmLHGwpUqLFR2jzx9orBuX/ABDkj2jLKOXrHDTN2aOKL+jFDDKaRNo9nyYsIl9h/UE/7lMKdDjKQQyxKKDZ7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz", + "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", + "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.28.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", + "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", + "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.14.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz", + "integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz", + "integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz", + "integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz", + "integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz", + "integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz", + "integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz", + "integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz", + "integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz", + "integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz", + "integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz", + "integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz", + "integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz", + "integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz", + "integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz", + "integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz", + "integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz", + "integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz", + "integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz", + "integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz", + "integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@smithy/abort-controller": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.4.tgz", + "integrity": "sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.5.1.tgz", + "integrity": "sha512-xSw7bZEFKwOKrm/iv8e2BLt2ur98YZdrRD6nII8ditQeUsY2Q1JmIQ0rpILOhaLKYxxG2ivnoOpokzr9qLyDWA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.0.8", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz", + "integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.4.tgz", + "integrity": "sha512-AMtBR5pHppYMVD7z7G+OlHHAcgAN7v0kVKEpHuTO4Gb199Gowh0taYi9oDStFeUhetkeP55JLSVlTW1n9rFtUw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.4.tgz", + "integrity": "sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.4.tgz", + "integrity": "sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.4.tgz", + "integrity": "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.9.tgz", + "integrity": "sha512-AjDgX4UjORLltD/LZCBQTwjQqEfyrx/GeDTHcYLzIgf87pIT70tMWnN87NQpJru1K4ITirY2htSOxNECZJCBOg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.5.1", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.10.tgz", + "integrity": "sha512-RyhcA3sZIIvAo6r48b2Nx2qfg0OnyohlaV0fw415xrQyx5HQ2bvHl9vs/WBiDXIP49mCfws5wX4308c9Pi/isw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/service-error-classification": "^4.0.5", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", + "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz", + "integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", + "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", + "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", + "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", + "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", + "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.5.tgz", + "integrity": "sha512-LvcfhrnCBvCmTee81pRlh1F39yTS/+kYleVeLCwNtkY8wtGg8V/ca9rbZZvYIl8OjlMtL6KIjaiL/lgVqHD2nA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.2.tgz", + "integrity": "sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.1.tgz", + "integrity": "sha512-XPbcHRfd0iwx8dY5XCBCGyI7uweMW0oezYezxXcG8ANgvZ5YPuC6Ylh+n0bTHpdU3SCMZOnhzgVklYz+p3fIhw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.5.1", + "@smithy/middleware-endpoint": "^4.1.9", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", + "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.17.tgz", + "integrity": "sha512-HXq5181qnXmIwB7VrwqwP8rsJybHMoYuJnNoXy4PROs2pfSI4sWDMASF2i+7Lo+u64Y6xowhegcdxczowgJtZg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.17.tgz", + "integrity": "sha512-RfU2A5LjFhEHw4Nwl1GZNitK4AUWu5jGtigAUDoQtfDUvYHpQxcuLw2QGAdKDtKRflIiHSZ8wXBDR36H9R2Ang==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.1.4", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.1", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", + "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", + "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.5.tgz", + "integrity": "sha512-V7MSjVDTlEt/plmOFBn1762Dyu5uqMrV2Pl2X0dYk4XvWfdWJNe9Bs5Bzb56wkCuiWjSfClVMGcsuKrGj7S/yg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.0.5", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.2.tgz", + "integrity": "sha512-aI+GLi7MJoVxg24/3J1ipwLoYzgkB4kUfogZfnslcYlynj3xsQ0e7vk4TnTro9hhsS5PvX1mwmkRqqHQjwcU7w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.0.5.tgz", + "integrity": "sha512-4QvC49HTteI1gfemu0I1syWovJgPvGn7CVUoN9ZFkdvr/cCFkrEL7qNCdx/2eICqDWEGnnr68oMdSIPCLAriSQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/aws-lambda": { + "version": "8.10.149", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.149.tgz", + "integrity": "sha512-NXSZIhfJjnXqJgtS7IwutqIF/SOy1Wz5Px4gUY1RWITp3AYTyuJS4xaXr/bIJY1v15XMzrJ5soGnPM+7uigZjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", + "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.15.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz", + "integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.0.tgz", + "integrity": "sha512-CACyQuqSHt7ma3Ns601xykeBK/rDeZa3w6IS6UtMQbixO5DWy+8TilKkviGDH6jtWCo8FGRKEK5cLLkPvEammQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.33.0", + "@typescript-eslint/type-utils": "8.33.0", + "@typescript-eslint/utils": "8.33.0", + "@typescript-eslint/visitor-keys": "8.33.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.33.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.0.tgz", + "integrity": "sha512-JaehZvf6m0yqYp34+RVnihBAChkqeH+tqqhS0GuX1qgPpwLvmTPheKEs6OeCK6hVJgXZHJ2vbjnC9j119auStQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.33.0", + "@typescript-eslint/types": "8.33.0", + "@typescript-eslint/typescript-estree": "8.33.0", + "@typescript-eslint/visitor-keys": "8.33.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.0.tgz", + "integrity": "sha512-d1hz0u9l6N+u/gcrk6s6gYdl7/+pp8yHheRTqP6X5hVDKALEaTn8WfGiit7G511yueBEL3OpOEpD+3/MBdoN+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.33.0", + "@typescript-eslint/types": "^8.33.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.0.tgz", + "integrity": "sha512-LMi/oqrzpqxyO72ltP+dBSP6V0xiUb4saY7WLtxSfiNEBI8m321LLVFU9/QDJxjDQG9/tjSqKz/E3380TEqSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.33.0", + "@typescript-eslint/visitor-keys": "8.33.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.0.tgz", + "integrity": "sha512-sTkETlbqhEoiFmGr1gsdq5HyVbSOF0145SYDJ/EQmXHtKViCaGvnyLqWFFHtEXoS0J1yU8Wyou2UGmgW88fEug==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.0.tgz", + "integrity": "sha512-lScnHNCBqL1QayuSrWeqAL5GmqNdVUQAAMTaCwdYEdWfIrSrOGzyLGRCHXcCixa5NK6i5l0AfSO2oBSjCjf4XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.33.0", + "@typescript-eslint/utils": "8.33.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.0.tgz", + "integrity": "sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.0.tgz", + "integrity": "sha512-vegY4FQoB6jL97Tu/lWRsAiUUp8qJTqzAmENH2k59SJhw0Th1oszb9Idq/FyyONLuNqT1OADJPXfyUNOR8SzAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.33.0", + "@typescript-eslint/tsconfig-utils": "8.33.0", + "@typescript-eslint/types": "8.33.0", + "@typescript-eslint/visitor-keys": "8.33.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.0.tgz", + "integrity": "sha512-lPFuQaLA9aSNa7D5u2EpRiqdAUhzShwGg/nhpBlc4GR6kcTABttCuyjFs8BcEZ8VWrjCBof/bePhP3Q3fS+Yrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.33.0", + "@typescript-eslint/types": "8.33.0", + "@typescript-eslint/typescript-estree": "8.33.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.0.tgz", + "integrity": "sha512-7RW7CMYoskiz5OOGAWjJFxgb7c5UNjTG292gYhWeOAcFmYCtVCSqjqSBj5zMhxbXo2JOW95YYrUWJfU0zrpaGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.33.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.0.tgz", + "integrity": "sha512-HjgvaokAiHxRMI5ioXl4WmgAi4zQtKtnltOOlmpzUqApdcTTZrZJAastbbRGydtiqwtYLFaIb6Jpo3PzowZ0cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^1.0.2", + "ast-v8-to-istanbul": "^0.3.3", + "debug": "^4.4.1", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "std-env": "^3.9.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "3.2.0", + "vitest": "3.2.0" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.0.tgz", + "integrity": "sha512-0v4YVbhDKX3SKoy0PHWXpKhj44w+3zZkIoVES9Ex2pq+u6+Bijijbi2ua5kE+h3qT6LBWFTNZSCOEU37H8Y5sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.0", + "@vitest/utils": "3.2.0", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.0.tgz", + "integrity": "sha512-HFcW0lAMx3eN9vQqis63H0Pscv0QcVMo1Kv8BNysZbxcmHu3ZUYv59DS6BGYiGQ8F5lUkmsfMMlPm4DJFJdf/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.0", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.0.tgz", + "integrity": "sha512-gUUhaUmPBHFkrqnOokmfMGRBMHhgpICud9nrz/xpNV3/4OXCn35oG+Pl8rYYsKaTNd/FAIrqRHnwpDpmYxCYZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.0.tgz", + "integrity": "sha512-bXdmnHxuB7fXJdh+8vvnlwi/m1zvu+I06i1dICVcDQFhyV4iKw2RExC/acavtDn93m/dRuawUObKsrNE1gJacA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.0", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.0.tgz", + "integrity": "sha512-z7P/EneBRMe7hdvWhcHoXjhA6at0Q4ipcoZo6SqgxLyQQ8KSMMCmvw1cSt7FHib3ozt0wnRHc37ivuUMbxzG/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.0", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.0.tgz", + "integrity": "sha512-s3+TkCNUIEOX99S0JwNDfsHRaZDDZZR/n8F0mop0PmsEbQGKZikCGpTGZ6JRiHuONKew3Fb5//EPwCP+pUX9cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.0.tgz", + "integrity": "sha512-gXXOe7Fj6toCsZKVQouTRLJftJwmvbhH5lKOBR6rlP950zUq9AitTUjnFoXS/CqjBC2aoejAztLPzzuva++XBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.0", + "loupe": "^3.1.3", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.3.tgz", + "integrity": "sha512-MuXMrSLVVoA6sYN/6Hke18vMzrT4TZNbZIj/hvh0fnYFpO+/kFXcLIaiPwXXWaQUPg4yJD8fj+lfJ7/1EBconw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "estree-walker": "^3.0.3", + "js-tokens": "^9.0.1" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/aws-cdk": { + "version": "2.1017.1", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1017.1.tgz", + "integrity": "sha512-KtDdkMhfVjDeexjpMrVoSlz2mTYI5BE/KotvJ7iFbZy1G0nkpW1ImZ54TdBefeeFmZ+8DAjU3I6nUFtymyOI1A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "cdk": "bin/cdk" + }, + "engines": { + "node": ">= 14.15.0" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/aws-cdk-lib": { + "version": "2.199.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.199.0.tgz", + "integrity": "sha512-hAZHdb7bPHepIGpuyg0jS/F3toY7VRvJDqxo4+C2cYY5zvktGP3lgcC9ukE2ehxYU1Pa9YOAehEDIxrita0Hvw==", + "bundleDependencies": [ + "@balena/dockerignore", + "case", + "fs-extra", + "ignore", + "jsonschema", + "minimatch", + "punycode", + "semver", + "table", + "yaml", + "mime-types" + ], + "license": "Apache-2.0", + "dependencies": { + "@aws-cdk/asset-awscli-v1": "2.2.237", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", + "@aws-cdk/cloud-assembly-schema": "^41.2.0", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.3.0", + "ignore": "^5.3.2", + "jsonschema": "^1.5.0", + "mime-types": "^2.1.35", + "minimatch": "^3.1.2", + "punycode": "^2.3.1", + "semver": "^7.7.2", + "table": "^6.9.0", + "yaml": "1.10.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "constructs": "^10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { + "version": "1.0.2", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/aws-cdk-lib/node_modules/ajv": { + "version": "8.17.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/astral-regex": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/balanced-match": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/brace-expansion": { + "version": "1.1.11", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/aws-cdk-lib/node_modules/case": { + "version": "1.6.3", + "inBundle": true, + "license": "(MIT OR GPL-3.0-or-later)", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/concat-map": { + "version": "0.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { + "version": "3.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-uri": { + "version": "3.0.6", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/aws-cdk-lib/node_modules/fs-extra": { + "version": "11.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/aws-cdk-lib/node_modules/graceful-fs": { + "version": "4.2.11", + "inBundle": true, + "license": "ISC" + }, + "node_modules/aws-cdk-lib/node_modules/ignore": { + "version": "5.3.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/jsonfile": { + "version": "6.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/jsonschema": { + "version": "1.5.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { + "version": "4.4.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/mime-db": { + "version": "1.52.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/mime-types": { + "version": "2.1.35", + "inBundle": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/minimatch": { + "version": "3.1.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/punycode": { + "version": "2.3.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/aws-cdk-lib/node_modules/require-from-string": { + "version": "2.0.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/semver": { + "version": "7.7.2", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/slice-ansi": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/table": { + "version": "6.9.0", + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/universalify": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/yaml": { + "version": "1.10.2", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/constructs": { + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.4.2.tgz", + "integrity": "sha512-wsNxBlAott2qg8Zv87q3eYZYgheb9lchtBfjHzzLHtXbttwSrHPs1NNQbBrmbb1YZvYg2+Vh0Dor76w4mFxJkA==", + "license": "Apache-2.0" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.28.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", + "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.14.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.28.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.5", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz", + "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", + "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz", + "integrity": "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mnemonist": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.3.tgz", + "integrity": "sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==", + "license": "MIT", + "dependencies": { + "obliterator": "^1.6.1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/obliterator": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-1.6.1.tgz", + "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==", + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", + "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.2.tgz", + "integrity": "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/lru-cache": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz", + "integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.41.1", + "@rollup/rollup-android-arm64": "4.41.1", + "@rollup/rollup-darwin-arm64": "4.41.1", + "@rollup/rollup-darwin-x64": "4.41.1", + "@rollup/rollup-freebsd-arm64": "4.41.1", + "@rollup/rollup-freebsd-x64": "4.41.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", + "@rollup/rollup-linux-arm-musleabihf": "4.41.1", + "@rollup/rollup-linux-arm64-gnu": "4.41.1", + "@rollup/rollup-linux-arm64-musl": "4.41.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", + "@rollup/rollup-linux-riscv64-gnu": "4.41.1", + "@rollup/rollup-linux-riscv64-musl": "4.41.1", + "@rollup/rollup-linux-s390x-gnu": "4.41.1", + "@rollup/rollup-linux-x64-gnu": "4.41.1", + "@rollup/rollup-linux-x64-musl": "4.41.1", + "@rollup/rollup-win32-arm64-msvc": "4.41.1", + "@rollup/rollup-win32-ia32-msvc": "4.41.1", + "@rollup/rollup-win32-x64-msvc": "4.41.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", + "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinypool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.0.tgz", + "integrity": "sha512-7CotroY9a8DKsKprEy/a14aCCm8jYVmR7aFy4fpkZM8sdpNJbKkixuNjgM50yCmip2ezc8z4N7k3oe2+rfRJCQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", + "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.33.0.tgz", + "integrity": "sha512-5YmNhF24ylCsvdNW2oJwMzTbaeO4bg90KeGtMjUw0AGtHksgEPLRTUil+coHwCfiu4QjVJFnjp94DmU6zV7DhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.33.0", + "@typescript-eslint/parser": "8.33.0", + "@typescript-eslint/utils": "8.33.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.0.tgz", + "integrity": "sha512-8Fc5Ko5Y4URIJkmMF/iFP1C0/OJyY+VGVe9Nw6WAdZyw4bTO+eVg9mwxWkQp/y8NnAoQY3o9KAvE1ZdA2v+Vmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", + "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.0.tgz", + "integrity": "sha512-P7Nvwuli8WBNmeMHHek7PnGW4oAZl9za1fddfRVidZar8wDZRi7hpznLKQePQ8JPLwSBEYDK11g+++j7uFJV8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.0", + "@vitest/mocker": "3.2.0", + "@vitest/pretty-format": "^3.2.0", + "@vitest/runner": "3.2.0", + "@vitest/snapshot": "3.2.0", + "@vitest/spy": "3.2.0", + "@vitest/utils": "3.2.0", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.0", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.0", + "@vitest/ui": "3.2.0", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.48", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.48.tgz", + "integrity": "sha512-0X1mz8FtgEIvaxGjdIImYpZEaZMrund9pGXm3M6vM7Reba0e2eI71KPjSCGXBfwKDPwPoywf6waUKc3/tFvX2Q==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..84e9ad7 --- /dev/null +++ b/package.json @@ -0,0 +1,44 @@ +{ + "name": "task-service", + "version": "1.0.0", + "description": "REST API service for managing tasks using AWS Lambda, API Gateway and DynamoDB", + "main": "index.js", + "scripts": { + "build": "tsc", + "clean": "rimraf cdk.out coverage dist", + "clean:all": "rimraf cdk.out coverage dist node_modules", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "lint": "eslint .", + "format": "prettier --write .", + "cdk": "cdk" + }, + "dependencies": { + "@aws-sdk/client-dynamodb": "3.821.0", + "@aws-sdk/lib-dynamodb": "3.821.0", + "@aws-sdk/util-dynamodb": "3.821.0", + "aws-cdk-lib": "2.199.0", + "constructs": "10.4.2", + "uuid": "11.1.0", + "zod": "3.25.48" + }, + "devDependencies": { + "@types/aws-lambda": "8.10.149", + "@types/node": "22.15.29", + "@types/uuid": "10.0.0", + "@typescript-eslint/eslint-plugin": "8.33.0", + "@typescript-eslint/parser": "8.33.0", + "@vitest/coverage-v8": "3.2.0", + "aws-cdk": "2.1017.1", + "eslint": "9.28.0", + "eslint-config-prettier": "10.1.5", + "globals": "16.2.0", + "prettier": "3.5.3", + "rimraf": "6.0.1", + "ts-node": "10.9.2", + "typescript": "5.8.3", + "typescript-eslint": "8.33.0", + "vitest": "3.2.0" + } +} diff --git a/src/handlers/createTask.test.ts b/src/handlers/createTask.test.ts new file mode 100644 index 0000000..7c04076 --- /dev/null +++ b/src/handlers/createTask.test.ts @@ -0,0 +1,167 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { APIGatewayProxyEvent } from 'aws-lambda'; +import { CreateTaskRequest } from '@/models/Task'; +import { createTask } from './createTask'; + +// Mock dependencies +vi.mock('../services/taskService.js', () => ({ + TaskService: { + createTask: vi.fn(), + }, +})); + +vi.mock('../utils/logger.js', () => ({ + logger: { + info: vi.fn(), + debug: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }, +})); + +describe('createTask handler', () => { + const mockRequestId = 'test-request-id'; + + // Create a helper function to build mock events + const createMockEvent = (body?: string): APIGatewayProxyEvent => + ({ + body, + requestContext: { + requestId: mockRequestId, + }, + }) as unknown as APIGatewayProxyEvent; + + beforeEach(async () => { + // Import the module fresh in each test to reset the mocks + vi.resetModules(); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('request validation', () => { + it('should return 400 when body is not valid JSON', async () => { + // Arrange + const mockEvent = createMockEvent('not-valid-json'); + const { logger } = await import('../utils/logger.js'); + + // Act + const response = await createTask(mockEvent); + + // Assert + expect(response.statusCode).toBe(400); + expect(JSON.parse(response.body)).toEqual({ + message: 'Invalid request format: The request body must be valid JSON', + }); + expect(logger.warn).toHaveBeenCalledWith( + 'Invalid request format', + expect.objectContaining({ + requestId: mockRequestId, + }), + ); + }); + + it('should return 400 when task data is invalid', async () => { + // Arrange + const invalidTaskData = { title: '' }; // Missing required title + const mockEvent = createMockEvent(JSON.stringify(invalidTaskData)); + const { logger } = await import('../utils/logger.js'); + + // Act + const response = await createTask(mockEvent); + + // Assert + expect(response.statusCode).toBe(400); + expect(JSON.parse(response.body).message).toContain('Invalid task data:'); + expect(logger.warn).toHaveBeenCalledWith( + 'Invalid task data', + expect.objectContaining({ + requestId: mockRequestId, + }), + ); + }); + }); + + describe('successful task creation', () => { + it('should create a task and return 201 with the created task', async () => { + // Arrange + const validTaskData: CreateTaskRequest = { + title: 'Test Task', + detail: 'This is a test task', + dueAt: '2025-06-30', + }; + const mockEvent = createMockEvent(JSON.stringify(validTaskData)); + + const createdTask = { + id: 'test-task-id', + ...validTaskData, + isComplete: false, + }; + + const { TaskService } = await import('../services/taskService.js'); + const { logger } = await import('../utils/logger.js'); + + // Mock the TaskService.createTask to return a task + vi.mocked(TaskService.createTask).mockResolvedValue(createdTask); + + // Act + const response = await createTask(mockEvent); + + // Assert + // Validate that the task service was called (we need to be less strict about the exact match) + expect(TaskService.createTask).toHaveBeenCalled(); + // Instead of checking exact match, verify the key properties + const createTaskCalls = vi.mocked(TaskService.createTask).mock.calls; + expect(createTaskCalls.length).toBe(1); + const taskArg = createTaskCalls[0][0]; + expect(taskArg.title).toEqual(validTaskData.title); + expect(taskArg.detail).toEqual(validTaskData.detail); + expect(taskArg.dueAt).toEqual(validTaskData.dueAt); + + expect(response.statusCode).toBe(201); + expect(JSON.parse(response.body)).toEqual(createdTask); + expect(logger.info).toHaveBeenCalledWith( + 'Task created successfully', + expect.objectContaining({ + requestId: mockRequestId, + taskId: createdTask.id, + }), + ); + }); + }); + + describe('error handling', () => { + it('should return 500 when an unexpected error occurs', async () => { + // Arrange + const validTaskData = { + title: 'Test Task', + detail: 'This is a test task', + }; + const mockEvent = createMockEvent(JSON.stringify(validTaskData)); + + const { TaskService } = await import('../services/taskService.js'); + const { logger } = await import('../utils/logger.js'); + + // Mock the TaskService.createTask to throw an error + const mockError = new Error('Test error'); + vi.mocked(TaskService.createTask).mockRejectedValue(mockError); + + // Act + const response = await createTask(mockEvent); + + // Assert + expect(response.statusCode).toBe(500); + expect(JSON.parse(response.body)).toEqual({ + message: 'An unexpected error occurred while creating the task', + }); + expect(logger.error).toHaveBeenCalledWith( + 'Failed to create task', + mockError, + expect.objectContaining({ + requestId: mockRequestId, + }), + ); + }); + }); +}); diff --git a/src/handlers/createTask.ts b/src/handlers/createTask.ts new file mode 100644 index 0000000..180f002 --- /dev/null +++ b/src/handlers/createTask.ts @@ -0,0 +1,69 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; +import { z } from 'zod'; +import { CreateTaskSchema } from '@/models/Task'; +import { badRequest, created, internalServerError } from '@/utils/response'; +import { TaskService } from '@/services/taskService'; +import { logger } from '@/utils/logger'; + +/** + * Schema for validating the request + */ +const requestSchema = z.object({ + body: z.string().transform((body, ctx) => { + try { + // Parse body as JSON + return JSON.parse(body); + } catch (_error) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Invalid JSON in request body', + }); + return z.NEVER; + } + }), +}); + +/** + * Lambda Handler: createTask + * + * Copilot Instructions: + * - Parse task data from event.body + * - Validate input against CreateTaskSchema + * - Call taskService.createTask with validated data + * - Return 201 with created task or appropriate error + * - Catch unexpected errors; log and return 500 + */ +export const createTask = async (event: APIGatewayProxyEvent): Promise => { + const requestId = event.requestContext?.requestId; + logger.info('Processing create task request', { requestId }); + + try { + // Parse and validate request + const parseResult = requestSchema.safeParse(event); + if (!parseResult.success) { + logger.warn('Invalid request format', { requestId, errors: parseResult.error.errors }); + return badRequest('Invalid request format: The request body must be valid JSON'); + } + + // Validate task data against schema + const taskValidation = CreateTaskSchema.safeParse(parseResult.data.body); + if (!taskValidation.success) { + const errors = taskValidation.error.errors.map((e) => e.message).join(', '); + logger.warn('Invalid task data', { requestId, errors }); + return badRequest(`Invalid task data: ${errors}`); + } + + logger.debug('Creating task', { requestId, taskData: taskValidation.data }); + + // Create task using the service + const task = await TaskService.createTask(taskValidation.data); + + logger.info('Task created successfully', { requestId, taskId: task.id }); + + // Return successful response + return created(task); + } catch (error) { + logger.error('Failed to create task', error as Error, { requestId }); + return internalServerError('An unexpected error occurred while creating the task'); + } +}; diff --git a/src/models/Task.ts b/src/models/Task.ts new file mode 100644 index 0000000..2953739 --- /dev/null +++ b/src/models/Task.ts @@ -0,0 +1,35 @@ +import { z } from 'zod'; + +/** + * Schema for a Task entity + * + * Represents a task with validation rules according to requirements: + * - id: Required string, max 24 characters + * - title: Required string, max 100 characters + * - detail: Optional string, max 2000 characters + * - isComplete: Boolean, defaults to false + * - dueAt: Optional ISO-8601 date string + */ +export const TaskSchema = z.object({ + id: z.string().min(1, 'ID is required').max(24, 'ID must be 24 characters or less'), + title: z.string().min(1, 'Title is required').max(100, 'Title must be 100 characters or less'), + detail: z.string().max(2000, 'Detail must be 2000 characters or less').optional(), + isComplete: z.boolean().default(false), + dueAt: z.string().date().optional(), +}); + +/** + * Schema for creating a new Task + * + * Different from the Task schema in that: + * - id is optional (will be generated if not provided) + * - isComplete defaults to false + */ +export const CreateTaskSchema = TaskSchema.omit({ id: true, isComplete: true }).extend({ + id: z.string().max(24, 'ID must be 24 characters or less').optional(), + isComplete: z.boolean().default(false).optional(), +}); + +// Type definitions derived from the schemas +export type Task = z.infer; +export type CreateTaskRequest = z.infer; diff --git a/src/services/taskService.test.ts b/src/services/taskService.test.ts new file mode 100644 index 0000000..f7d5949 --- /dev/null +++ b/src/services/taskService.test.ts @@ -0,0 +1,156 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { v4 as uuidv4 } from 'uuid'; +import { PutCommand } from '@aws-sdk/lib-dynamodb'; +import { CreateTaskRequest } from '@/models/Task.js'; + +// Mock dependencies +vi.mock('@aws-sdk/lib-dynamodb', () => ({ + PutCommand: vi.fn().mockImplementation((params) => ({ + ...params, + })), +})); + +vi.mock('uuid', () => ({ + v4: vi.fn().mockReturnValue('mocked-uuid'), +})); + +vi.mock('@/utils/awsClients.js', () => ({ + dynamoDocClient: { + send: vi.fn().mockResolvedValue({}), + }, +})); + +vi.mock('@/utils/logger.js', () => ({ + logger: { + info: vi.fn(), + debug: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }, +})); + +vi.mock('@/utils/config.js', () => ({ + config: { + TASKS_TABLE: 'tasks-table-test', + }, +})); + +describe('TaskService', () => { + let taskService: typeof import('./taskService.js').TaskService; + + beforeEach(async () => { + // Import the module fresh in each test to reset the mocks + vi.resetModules(); + taskService = (await import('./taskService.js')).TaskService; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('createTask', () => { + it('should create a task with the provided data', async () => { + // Arrange + const { dynamoDocClient } = await import('@/utils/awsClients.js'); + const { logger } = await import('@/utils/logger.js'); + const { config } = await import('@/utils/config.js'); + + const createTaskRequest: CreateTaskRequest = { + title: 'Test Task', + detail: 'This is a test task', + dueAt: '2025-06-30T00:00:00.000Z', + }; + + // Act + const result = await taskService.createTask(createTaskRequest); + + // Assert + // Check that UUID was generated + expect(uuidv4).toHaveBeenCalledTimes(1); + + // Check that the task object was created correctly + expect(result).toEqual({ + id: 'mocked-uuid', + title: 'Test Task', + detail: 'This is a test task', + isComplete: false, + dueAt: '2025-06-30T00:00:00.000Z', + }); + + // Check that PutCommand was called with the correct parameters + expect(PutCommand).toHaveBeenCalledWith({ + TableName: config.TASKS_TABLE, + Item: { + id: 'mocked-uuid', + title: 'Test Task', + detail: 'This is a test task', + isComplete: false, + dueAt: '2025-06-30T00:00:00.000Z', + }, + }); + + // Check that the document client's send method was called + expect(dynamoDocClient.send).toHaveBeenCalledTimes(1); + + // Check logging + expect(logger.debug).toHaveBeenCalledWith('Creating new task', { taskId: 'mocked-uuid' }); + expect(logger.debug).toHaveBeenCalledWith('Saving task to DynamoDB', { + tableName: 'tasks-table-test', + task: { + id: 'mocked-uuid', + title: 'Test Task', + detail: 'This is a test task', + isComplete: false, + dueAt: '2025-06-30T00:00:00.000Z', + }, + }); + expect(logger.info).toHaveBeenCalledWith('Task created successfully', { taskId: 'mocked-uuid' }); + }); + + it('should use default isComplete value if not provided', async () => { + // Arrange + const createTaskRequest: CreateTaskRequest = { + title: 'Test Task', + detail: 'This is a test task', + }; + + // Act + const result = await taskService.createTask(createTaskRequest); + + // Assert + expect(result.isComplete).toBe(false); + }); + + it('should respect provided isComplete value', async () => { + // Arrange + const createTaskRequest: CreateTaskRequest = { + title: 'Test Task', + detail: 'This is a test task', + isComplete: true, + }; + + // Act + const result = await taskService.createTask(createTaskRequest); + + // Assert + expect(result.isComplete).toBe(true); + }); + + it('should handle DynamoDB errors', async () => { + // Arrange + const { dynamoDocClient } = await import('@/utils/awsClients.js'); + const error = new Error('DynamoDB Error'); + + // Mock the DynamoDB send method to throw an error + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (dynamoDocClient.send as any).mockRejectedValueOnce(error); + + const createTaskRequest: CreateTaskRequest = { + title: 'Test Task', + }; + + // Act & Assert + await expect(taskService.createTask(createTaskRequest)).rejects.toThrow('DynamoDB Error'); + }); + }); +}); diff --git a/src/services/taskService.ts b/src/services/taskService.ts new file mode 100644 index 0000000..78662e1 --- /dev/null +++ b/src/services/taskService.ts @@ -0,0 +1,52 @@ +/** + * The TaskService module provides functionality to operate on tasks in DynamoDB. + * It includes methods to handle task creation, validation, and interaction with AWS services. + */ +import { PutCommand } from '@aws-sdk/lib-dynamodb'; +import { v4 as uuidv4 } from 'uuid'; +import { Task, CreateTaskRequest } from '@/models/Task.js'; +import { dynamoDocClient } from '@/utils/awsClients.js'; +import { logger } from '@/utils/logger.js'; +import { config } from '@/utils/config.js'; + +/** + * Creates a new task in DynamoDB + * + * @param createTaskRequest The task data to create + * @returns The created task with generated ID + */ +const createTask = async (createTaskRequest: CreateTaskRequest): Promise => { + // Generate a new ID + const taskId = uuidv4(); + logger.debug('Creating new task', { taskId }); + + // Create the complete task object + const task: Task = { + id: taskId, + title: createTaskRequest.title, + detail: createTaskRequest.detail, + isComplete: createTaskRequest.isComplete ?? false, + dueAt: createTaskRequest.dueAt, + }; + + logger.debug('Saving task to DynamoDB', { + tableName: config.TASKS_TABLE, + task, + }); + + // Save to DynamoDB + await dynamoDocClient.send( + new PutCommand({ + TableName: config.TASKS_TABLE, + Item: task, + }), + ); + + logger.info('Task created successfully', { taskId: task.id }); + return task; +}; + +// Define and export the TaskService with the methods to handle task operations +export const TaskService = { + createTask, +}; diff --git a/src/utils/awsClients.test.ts b/src/utils/awsClients.test.ts new file mode 100644 index 0000000..dd17eb1 --- /dev/null +++ b/src/utils/awsClients.test.ts @@ -0,0 +1,85 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; + +// Mock the AWS SDK clients +vi.mock('@aws-sdk/client-dynamodb', () => ({ + DynamoDBClient: vi.fn().mockImplementation(() => ({ + // Mock implementation of DynamoDBClient + config: { region: 'mocked-region' }, + })), +})); + +vi.mock('@aws-sdk/lib-dynamodb', () => ({ + DynamoDBDocumentClient: { + from: vi.fn().mockImplementation((client) => ({ + // Mock implementation of DynamoDBDocumentClient + send: vi.fn(), + client: client, + })), + }, +})); + +// Mock the config and logger +vi.mock('./config.js', () => ({ + config: { + AWS_REGION: 'test-region', + }, +})); + +vi.mock('./logger.js', () => ({ + logger: { + info: vi.fn(), + error: vi.fn(), + debug: vi.fn(), + warn: vi.fn(), + }, +})); + +describe('awsClients', () => { + // Clear module cache before each test to ensure we get a fresh instance + beforeEach(() => { + vi.resetModules(); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should create a DynamoDB client with the correct region', async () => { + // Import dynamically to get fresh instance with mocks applied + const { DynamoDBClient } = await import('@aws-sdk/client-dynamodb'); + const awsClients = await import('./awsClients.js'); + + // Verify DynamoDBClient was instantiated + expect(DynamoDBClient).toHaveBeenCalledTimes(1); + expect(DynamoDBClient).toHaveBeenCalledWith({ + region: 'test-region', + }); + + // Verify client was created + expect(awsClients.dynamoDBClient).toBeDefined(); + }); + + it('should create and export a DynamoDB Document client', async () => { + // Import dynamically to get fresh instance with mocks applied + const { DynamoDBDocumentClient } = await import('@aws-sdk/lib-dynamodb'); + const awsClients = await import('./awsClients.js'); + + // Verify DynamoDBDocumentClient.from was called with the right client + expect(DynamoDBDocumentClient.from).toHaveBeenCalledTimes(1); + expect(DynamoDBDocumentClient.from).toHaveBeenCalledWith(awsClients.dynamoDBClient); + + // Verify document client was created + expect(awsClients.dynamoDocClient).toBeDefined(); + }); + + it('should log initialization of the DynamoDB client', async () => { + // Import logger module directly to access the mock + const { logger } = await import('./logger.js'); + + // Re-import to trigger the log statement + await import('./awsClients.js'); + + // Verify the logger.info was called + expect(logger.info).toHaveBeenCalledWith('Initialized AWS DynamoDB client', { region: 'test-region' }); + }); +}); diff --git a/src/utils/awsClients.ts b/src/utils/awsClients.ts new file mode 100644 index 0000000..6e7e288 --- /dev/null +++ b/src/utils/awsClients.ts @@ -0,0 +1,23 @@ +import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb'; +import { config } from './config.js'; +import { logger } from './logger.js'; + +/** + * Singleton DynamoDB client configured with the application's region + */ +const dynamoClient = new DynamoDBClient({ + region: config.AWS_REGION, +}); + +logger.info('Initialized AWS DynamoDB client', { region: config.AWS_REGION }); + +/** + * DynamoDB Document client for easier interaction with DynamoDB + */ +export const dynamoDocClient = DynamoDBDocumentClient.from(dynamoClient); + +/** + * Export the base DynamoDB client for direct operations if needed + */ +export const dynamoDBClient = dynamoClient; diff --git a/src/utils/config.test.ts b/src/utils/config.test.ts new file mode 100644 index 0000000..70afd67 --- /dev/null +++ b/src/utils/config.test.ts @@ -0,0 +1,180 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import type { Config } from './config'; + +describe('config', () => { + // Store original env to restore after tests + const originalEnv = { ...process.env }; + + // Use a variable to hold the module we're testing + let configModule: { + config: Config; + refreshConfig: () => Config; + }; + + beforeEach(() => { + // Reset modules before each test + vi.resetModules(); + + // Make a fresh copy of the environment for each test + process.env = { ...originalEnv }; + + // Set required env vars to avoid validation errors when importing + process.env.TASKS_TABLE = 'default-table-for-tests'; + }); + + afterEach(() => { + // Restore original env + process.env = originalEnv; + }); + + // Helper function to get a fresh instance of the module + const getConfigModule = async () => { + // Re-import the module to get a fresh instance with current env vars + return import('./config.js'); + }; + + describe('validation', () => { + it('should validate required environment variables', async () => { + // Arrange + process.env.TASKS_TABLE = 'tasks-table-name'; + + // Act + configModule = await getConfigModule(); + const result = configModule.refreshConfig(); + + // Assert + expect(result.TASKS_TABLE).toBe('tasks-table-name'); + }); + + it('should throw error when required variables are missing', async () => { + // Arrange + delete process.env.TASKS_TABLE; + + // Act & Assert + await expect(getConfigModule()).rejects.toThrow('Configuration validation failed'); + }); + + it('should use default values for optional variables', async () => { + // Arrange + process.env.TASKS_TABLE = 'tasks-table-name'; + delete process.env.AWS_REGION; + delete process.env.ENABLE_LOGGING; + delete process.env.LOG_LEVEL; + + // Act + configModule = await getConfigModule(); + const result = configModule.refreshConfig(); + + // Assert + expect(result.AWS_REGION).toBe('us-east-1'); + expect(result.ENABLE_LOGGING).toBe(true); + expect(result.LOG_LEVEL).toBe('debug'); + }); + + it('should override default values when environment variables are provided', async () => { + // Arrange + process.env.TASKS_TABLE = 'tasks-table-name'; + process.env.AWS_REGION = 'eu-west-1'; + process.env.ENABLE_LOGGING = 'false'; + process.env.LOG_LEVEL = 'error'; + + // Act + configModule = await getConfigModule(); + const result = configModule.refreshConfig(); + + // Assert + expect(result.AWS_REGION).toBe('eu-west-1'); + expect(result.ENABLE_LOGGING).toBe(false); + expect(result.LOG_LEVEL).toBe('error'); + }); + + it('should transform ENABLE_LOGGING string to boolean', async () => { + // Arrange - First test with 'true' + process.env.TASKS_TABLE = 'tasks-table-name'; + process.env.ENABLE_LOGGING = 'true'; + + // Act + configModule = await getConfigModule(); + const resultTrue = configModule.refreshConfig(); + + // Assert + expect(resultTrue.ENABLE_LOGGING).toBe(true); + + // Reset modules and test with 'false' + vi.resetModules(); + process.env.ENABLE_LOGGING = 'false'; + + // Act again + configModule = await getConfigModule(); + const resultFalse = configModule.refreshConfig(); + + // Assert again + expect(resultFalse.ENABLE_LOGGING).toBe(false); + }); + + it('should validate LOG_LEVEL enum values', async () => { + // Valid values + const validLevels = ['debug', 'info', 'warn', 'error']; + + for (const level of validLevels) { + // Reset for each iteration + vi.resetModules(); + process.env.TASKS_TABLE = 'tasks-table-name'; + process.env.LOG_LEVEL = level; + + // Act + configModule = await getConfigModule(); + const result = configModule.refreshConfig(); + + // Assert + expect(result.LOG_LEVEL).toBe(level); + } + + // Invalid value test + vi.resetModules(); + process.env.TASKS_TABLE = 'tasks-table-name'; + process.env.LOG_LEVEL = 'invalid-level'; + + // The validation happens at import time, so we need to test the import itself + await expect(getConfigModule()).rejects.toThrow(); + }); + }); + + describe('refreshConfig', () => { + it('should refresh the configuration when called', async () => { + // Arrange + process.env.TASKS_TABLE = 'initial-table'; + + // Act - Get module with initial config + configModule = await getConfigModule(); + const initialConfig = configModule.refreshConfig(); + + // Assert initial value + expect(initialConfig.TASKS_TABLE).toBe('initial-table'); + + // Act - Change env and refresh + process.env.TASKS_TABLE = 'updated-table'; + const updatedConfig = configModule.refreshConfig(); + + // Assert updated value + expect(updatedConfig.TASKS_TABLE).toBe('updated-table'); + }); + }); + + describe('config singleton', () => { + it('should export a valid config object', async () => { + // Arrange + process.env.TASKS_TABLE = 'tasks-table-name'; + + // Act + configModule = await getConfigModule(); + + // Assert - Verify it has the expected shape + expect(configModule.config).toBeDefined(); + expect(configModule.config.TASKS_TABLE).toBeDefined(); + expect(configModule.config.AWS_REGION).toBeDefined(); + expect(configModule.config.ENABLE_LOGGING).toBeDefined(); + expect(configModule.config.LOG_LEVEL).toBeDefined(); + }); + }); +}); diff --git a/src/utils/config.ts b/src/utils/config.ts new file mode 100644 index 0000000..97a063b --- /dev/null +++ b/src/utils/config.ts @@ -0,0 +1,65 @@ +import { z } from 'zod'; + +/** + * Schema for validating environment variables + */ +const envSchema = z.object({ + // Required variables + TASKS_TABLE: z.string().min(1, 'TASKS_TABLE environment variable is required'), + + // Optional variables with defaults + AWS_REGION: z.string().default('us-east-1'), + + // Feature flags (optional) + ENABLE_LOGGING: z + .enum(['true', 'false']) + .transform((val) => val === 'true') + .default('true'), + LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('debug'), + + // Add more environment variables as needed +}); + +/** + * Type representing our validated config + */ +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 + */ +function 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.errors.map((err) => `${err.path.join('.')}: ${err.message}`).join('\n'); + + throw new Error(`Configuration validation failed:\n${errorMessage}`); + } + + // Re-throw other errors + throw error; + } +} + +/** + * Refreshes the configuration by re-validating environment variables + * Useful in tests when environment variables are changed + */ +export function refreshConfig(): Config { + configCache = validateConfig(); + return configCache; +} + +/** + * Validated configuration object + * Access environment variables through this object instead of process.env directly + */ +export const config = configCache || refreshConfig(); diff --git a/src/utils/logger.test.ts b/src/utils/logger.test.ts new file mode 100644 index 0000000..8e3b155 --- /dev/null +++ b/src/utils/logger.test.ts @@ -0,0 +1,190 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { logger } from './logger.js'; +import { config } from './config.js'; + +// Mock the config module +vi.mock('./config.js', () => ({ + config: { + ENABLE_LOGGING: true, + LOG_LEVEL: 'info', + }, +})); + +describe('logger', () => { + // Store original console methods + const originalConsole = { + debug: console.debug, + info: console.info, + warn: console.warn, + error: console.error, + }; + + beforeEach(() => { + // Mock console methods + console.debug = vi.fn(); + console.info = vi.fn(); + console.warn = vi.fn(); + console.error = vi.fn(); + }); + + afterEach(() => { + // Restore original console methods + console.debug = originalConsole.debug; + console.info = originalConsole.info; + console.warn = originalConsole.warn; + console.error = originalConsole.error; + + // Clear all mocks + vi.clearAllMocks(); + }); + + describe('log level filtering', () => { + it('should not log debug when level is info', () => { + // Arrange + vi.mocked(config).LOG_LEVEL = 'info'; + vi.mocked(config).ENABLE_LOGGING = true; + + // Act + logger.debug('Debug message'); + + // Assert + expect(console.debug).not.toHaveBeenCalled(); + }); + + it('should log info when level is info', () => { + // Arrange + vi.mocked(config).LOG_LEVEL = 'info'; + vi.mocked(config).ENABLE_LOGGING = true; + + // Act + logger.info('Info message'); + + // Assert + expect(console.info).toHaveBeenCalled(); + }); + + it('should log warn when level is info', () => { + // Arrange + vi.mocked(config).LOG_LEVEL = 'info'; + vi.mocked(config).ENABLE_LOGGING = true; + + // Act + logger.warn('Warning message'); + + // Assert + expect(console.warn).toHaveBeenCalled(); + }); + + it('should log error when level is info', () => { + // Arrange + vi.mocked(config).LOG_LEVEL = 'info'; + vi.mocked(config).ENABLE_LOGGING = true; + + // Act + logger.error('Error message'); + + // Assert + expect(console.error).toHaveBeenCalled(); + }); + + it('should not log anything when logging is disabled', () => { + // Arrange + vi.mocked(config).ENABLE_LOGGING = false; + + // Act + logger.debug('Debug message'); + logger.info('Info message'); + logger.warn('Warning message'); + logger.error('Error message'); + + // Assert + expect(console.debug).not.toHaveBeenCalled(); + expect(console.info).not.toHaveBeenCalled(); + expect(console.warn).not.toHaveBeenCalled(); + expect(console.error).not.toHaveBeenCalled(); + }); + }); + + describe('log message formatting', () => { + it('should format log messages with timestamp and level', () => { + // Arrange + vi.mocked(config).LOG_LEVEL = 'debug'; + vi.mocked(config).ENABLE_LOGGING = true; + + // Mock Date.toISOString to return a fixed timestamp + const mockDate = new Date('2025-06-02T12:00:00Z'); + vi.spyOn(global, 'Date').mockImplementation(() => mockDate); + + // Act + logger.info('Test message'); + + // Assert + expect(console.info).toHaveBeenCalledWith('[2025-06-02T12:00:00.000Z] [INFO] Test message'); + }); + + it('should include context in log message when provided', () => { + // Arrange + vi.mocked(config).LOG_LEVEL = 'debug'; + vi.mocked(config).ENABLE_LOGGING = true; + + // Mock Date.toISOString to return a fixed timestamp + const mockDate = new Date('2025-06-02T12:00:00Z'); + vi.spyOn(global, 'Date').mockImplementation(() => mockDate); + + const context = { userId: '123', action: 'login' }; + + // Act + logger.info('User action', context); + + // Assert + expect(console.info).toHaveBeenCalledWith( + '[2025-06-02T12:00:00.000Z] [INFO] User action {"userId":"123","action":"login"}', + ); + }); + }); + + describe('error logging', () => { + it('should log error message with error details', () => { + // Arrange + vi.mocked(config).LOG_LEVEL = 'error'; + vi.mocked(config).ENABLE_LOGGING = true; + + // Mock Date.toISOString to return a fixed timestamp + const mockDate = new Date('2025-06-02T12:00:00Z'); + vi.spyOn(global, 'Date').mockImplementation(() => mockDate); + + const error = new Error('Something went wrong'); + error.stack = 'Error: Something went wrong\n at test.js:1:1'; + + // Act + logger.error('Failed operation', error); + + // Assert + expect(console.error).toHaveBeenCalledWith( + '[2025-06-02T12:00:00.000Z] [ERROR] Failed operation {"errorMessage":"Something went wrong","stack":"Error: Something went wrong\\n at test.js:1:1"}', + ); + }); + + it('should merge error details with provided context', () => { + // Arrange + vi.mocked(config).LOG_LEVEL = 'error'; + vi.mocked(config).ENABLE_LOGGING = true; + + // Mock Date.toISOString to return a fixed timestamp + const mockDate = new Date('2025-06-02T12:00:00Z'); + vi.spyOn(global, 'Date').mockImplementation(() => mockDate); + + const error = new Error('Database connection failed'); + error.stack = 'Error: Database connection failed\n at db.js:42:10'; + const context = { operation: 'queryUsers', dbHost: 'localhost' }; + + // Act + logger.error('Database error', error, context); + + // Assert + expect(console.error).toHaveBeenCalledWith( + '[2025-06-02T12:00:00.000Z] [ERROR] Database error {"operation":"queryUsers","dbHost":"localhost","errorMessage":"Database connection failed","stack":"Error: Database connection failed\\n at db.js:42:10"}', + ); + }); + }); +}); diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 0000000..aa615ee --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,71 @@ +import { config } from './config.js'; + +/** + * Log levels with numeric values for comparison + */ +const LOG_LEVELS = { + debug: 0, + info: 1, + warn: 2, + error: 3, +} as const; + +type LogLevel = keyof typeof LOG_LEVELS; + +/** + * Check if a given log level should be logged based on configured level + */ +function shouldLog(level: LogLevel): boolean { + if (!config.ENABLE_LOGGING) return false; + + const configuredLevel = LOG_LEVELS[config.LOG_LEVEL as LogLevel]; + const requestedLevel = LOG_LEVELS[level]; + + return requestedLevel >= configuredLevel; +} + +/** + * Format a log message with timestamp and metadata + */ +function formatLogMessage(level: LogLevel, message: string, context?: Record): string { + const timestamp = new Date().toISOString(); + const contextStr = context ? ` ${JSON.stringify(context)}` : ''; + return `[${timestamp}] [${level.toUpperCase()}] ${message}${contextStr}`; +} + +/** + * Logger utility configured based on environment settings + */ +export const logger = { + debug(message: string, context?: Record): void { + if (shouldLog('debug')) { + console.debug(formatLogMessage('debug', message, context)); + } + }, + + info(message: string, context?: Record): void { + if (shouldLog('info')) { + console.info(formatLogMessage('info', message, context)); + } + }, + + warn(message: string, context?: Record): void { + if (shouldLog('warn')) { + console.warn(formatLogMessage('warn', message, context)); + } + }, + + error(message: string, error?: Error, context?: Record): void { + if (shouldLog('error')) { + const errorContext = error + ? { + ...context, + errorMessage: error.message, + stack: error.stack, + } + : context; + + console.error(formatLogMessage('error', message, errorContext)); + } + }, +}; diff --git a/src/utils/response.test.ts b/src/utils/response.test.ts new file mode 100644 index 0000000..391a89d --- /dev/null +++ b/src/utils/response.test.ts @@ -0,0 +1,98 @@ +import { describe, it, expect } from 'vitest'; +import { createResponse, ok, created, noContent, badRequest, notFound, internalServerError } from './response'; + +describe('Response Utils', () => { + describe('createResponse', () => { + it('should create a response with the given status code and body', () => { + // Arrange + const statusCode = 200; + const body = { data: 'test' }; + + // Act + const response = createResponse(statusCode, body); + + // Assert + expect(response.statusCode).toBe(200); + expect(response.body).toBe(JSON.stringify(body)); + expect(response.headers).toEqual({ 'Content-Type': 'application/json' }); + }); + }); + + describe('helper functions', () => { + it('should create a 200 OK response', () => { + // Arrange + const body = { data: 'test' }; + + // Act + const response = ok(body); + + // Assert + expect(response.statusCode).toBe(200); + expect(response.body).toBe(JSON.stringify(body)); + }); + + it('should create a 201 Created response', () => { + // Arrange + const body = { id: '123' }; + + // Act + const response = created(body); + + // Assert + expect(response.statusCode).toBe(201); + expect(response.body).toBe(JSON.stringify(body)); + }); + + it('should create a 204 No Content response', () => { + // Act + const response = noContent(); + + // Assert + expect(response.statusCode).toBe(204); + expect(response.body).toBe('{}'); + }); + + it('should create a 400 Bad Request response with custom message', () => { + // Arrange + const message = 'Invalid input'; + + // Act + const response = badRequest(message); + + // Assert + expect(response.statusCode).toBe(400); + expect(response.body).toBe(JSON.stringify({ message })); + }); + + it('should create a 404 Not Found response with custom message', () => { + // Arrange + const message = 'Task not found'; + + // Act + const response = notFound(message); + + // Assert + expect(response.statusCode).toBe(404); + expect(response.body).toBe(JSON.stringify({ message })); + }); + + it('should create a 500 Internal Server Error response with custom message', () => { + // Arrange + const message = 'Something went wrong'; + + // Act + const response = internalServerError(message); + + // Assert + expect(response.statusCode).toBe(500); + expect(response.body).toBe(JSON.stringify({ message })); + }); + + it('should use default messages when none provided', () => { + // Act & Assert + expect(JSON.parse(badRequest().body).message).toBe('Bad Request'); + expect(JSON.parse(notFound().body).message).toBe('Not Found'); + expect(JSON.parse(internalServerError().body).message).toBe('Internal Server Error'); + }); + }); +}); diff --git a/src/utils/response.ts b/src/utils/response.ts new file mode 100644 index 0000000..8e0e846 --- /dev/null +++ b/src/utils/response.ts @@ -0,0 +1,23 @@ +import { APIGatewayProxyResult } from 'aws-lambda'; + +/** + * Creates a standardized API Gateway response with the given status code and body + */ +export const createResponse = (statusCode: number, body: unknown): APIGatewayProxyResult => { + return { + statusCode, + headers: { + 'Content-Type': 'application/json', + }, + 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/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..b4c51f0 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022"], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "esModuleInterop": true, + "outDir": "dist", + "skipLibCheck": true, + "resolveJsonModule": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"], + "@infrastructure/*": ["./infrastructure/*"] + } + }, + "exclude": ["node_modules", "cdk.out", "dist", "coverage"] +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..2440359 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from 'vitest/config'; +import path from 'path'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['src/**/*.test.ts'], + clearMocks: true, + coverage: { + provider: 'v8', + reporter: ['text', 'html'], + include: ['src/**/*.ts'], + exclude: ['**/*.test.ts', 'src/models/**'], + }, + alias: { + '@': path.resolve(__dirname, 'src'), + }, + }, +}); From 5a1ff4eb58df2aa259eaaef42af2d198ffa3b4da Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Mon, 2 Jun 2025 09:47:55 -0400 Subject: [PATCH 05/26] initial list tasks requirements --- docs/requirements/02-list-tasks.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 docs/requirements/02-list-tasks.md diff --git a/docs/requirements/02-list-tasks.md b/docs/requirements/02-list-tasks.md new file mode 100644 index 0000000..8c96ecb --- /dev/null +++ b/docs/requirements/02-list-tasks.md @@ -0,0 +1,15 @@ +# Requirement: List all Tasks + +This document describes the requirements for an AWS Lambda REST API endpoint that will list all **Task** items found in DynamoDB. + +--- + +## Description + +Create an AWS Lambda function which handles a REST API request to retrieve all **Task** items from DynamoDB. The task service should fetch the **Task** items from the task table in AWS DynamoDB. The task service should use the AWS SDK to interact with AWS DynamoDB. The task service should use the DynamoDB Document Client. The Lambda function should return the collection of **Task** items in the response body if successful along with an appropriate HTTP status code. If an error occurs, the Lambda function should return an appropriate HTTP status code and the body should contain a meaningful message. + +Create appropriate AWS infrastructure for the new Lambda function using the AWS CDK. + +Add unit tests for the newly created and updated source members. + +Implement these requirements step by step. Follow all best practices and structure for this project. From 0093b41200419d0a199e9cdf54cd54b7ae80708e Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Mon, 2 Jun 2025 12:50:34 -0400 Subject: [PATCH 06/26] requirements --- docs/requirements/02-list-tasks.md | 2 +- docs/requirements/03-get-task.md | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 docs/requirements/03-get-task.md diff --git a/docs/requirements/02-list-tasks.md b/docs/requirements/02-list-tasks.md index 8c96ecb..ae98080 100644 --- a/docs/requirements/02-list-tasks.md +++ b/docs/requirements/02-list-tasks.md @@ -6,7 +6,7 @@ This document describes the requirements for an AWS Lambda REST API endpoint tha ## Description -Create an AWS Lambda function which handles a REST API request to retrieve all **Task** items from DynamoDB. The task service should fetch the **Task** items from the task table in AWS DynamoDB. The task service should use the AWS SDK to interact with AWS DynamoDB. The task service should use the DynamoDB Document Client. The Lambda function should return the collection of **Task** items in the response body if successful along with an appropriate HTTP status code. If an error occurs, the Lambda function should return an appropriate HTTP status code and the body should contain a meaningful message. +Create an AWS Lambda function which handles a REST API request to retrieve all **Task** items from DynamoDB. The task service should fetch the **Task** items from the task table in AWS DynamoDB. The task service should use the AWS SDK to interact with AWS DynamoDB. The task service should use the DynamoDB Document Client. The Lambda function should return the collection of **Task** items in the response body if successful along with an appropriate HTTP status code (200). If an error occurs, the Lambda function should return an appropriate HTTP status code (500) and the body should contain a meaningful message as JSON. Create appropriate AWS infrastructure for the new Lambda function using the AWS CDK. diff --git a/docs/requirements/03-get-task.md b/docs/requirements/03-get-task.md new file mode 100644 index 0000000..d4fd9df --- /dev/null +++ b/docs/requirements/03-get-task.md @@ -0,0 +1,17 @@ +# Requirement: Get a Task by Identifier + +This document describes the requirements for an AWS Lambda REST API endpoint that will retrieve a single **Task** item from DynamoDB by its identifier. + +--- + +## Description + +Create an AWS Lambda function which handles a REST API request to retrieve a specific **Task** item from DynamoDB using its unique identifier. The task identifier should be provided as a path parameter in the REST API request. The task service should fetch the **Task** item from the task table in AWS DynamoDB using the provided identifier. The task service should use the AWS SDK to interact with AWS DynamoDB. The task service should use the DynamoDB Document Client. + +The Lambda function should return the **Task** item in the response body along with an appropriate HTTP status code (200) if found. If the task is not found, the Lambda function should return an appropriate HTTP status code (404) and the body should contain a meaningful message as JSON. If an error occurs during processing, the Lambda function should return an appropriate HTTP status code (500) and the body should contain a meaningful message as JSON. + +Create appropriate AWS infrastructure for the new Lambda function using the AWS CDK, including updating the API Gateway to route requests to this Lambda function. + +Add unit tests for the newly created and updated source members, ensuring proper validation of the task identifier and handling of various response scenarios (success, not found, error). + +Implement these requirements step by step. Follow all best practices and structure for this project. From 1e4e3cf4a9dc410eb7f9a1d443c8bb4a1024cd4e Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Mon, 2 Jun 2025 13:14:50 -0400 Subject: [PATCH 07/26] requirements --- docs/requirements/04-update-task.md | 32 +++++++++++++++++++++++++++++ docs/requirements/05-delete-task.md | 19 +++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 docs/requirements/04-update-task.md create mode 100644 docs/requirements/05-delete-task.md diff --git a/docs/requirements/04-update-task.md b/docs/requirements/04-update-task.md new file mode 100644 index 0000000..8e8c260 --- /dev/null +++ b/docs/requirements/04-update-task.md @@ -0,0 +1,32 @@ +# Requirement: Update a Task by Identifier + +This document describes the requirements for an AWS Lambda REST API endpoint that will update a single **Task** item in DynamoDB by its identifier. + +--- + +## Description + +Create an AWS Lambda function which handles a REST API request to update a specific **Task** item in DynamoDB using its unique identifier. The task identifier should be provided as a path parameter in the REST API request, and the updated task attributes should be provided in the request body. The task service should first check if the **Task** item exists in the task table in AWS DynamoDB using the provided identifier. If found, the task service should update the **Task** item with the new attributes provided in the request. + +The Lambda function should validate the update task object in the request body. The task service should use the AWS SDK to interact with AWS DynamoDB, specifically using the DynamoDB Document Client to perform the update operation. + +The Lambda function should return the updated **Task** item in the response body along with an appropriate HTTP status code (200) if the update is successful. If the task is not found, the Lambda function should return an appropriate HTTP status code (404) and the body should contain a meaningful message as JSON. If the request body is invalid, the Lambda function should return an appropriate HTTP status code (400) and the body should contain validation error details as JSON. If an error occurs during processing, the Lambda function should return an appropriate HTTP status code (500) and the body should contain a meaningful message as JSON. + +Create appropriate AWS infrastructure for the new Lambda function using the AWS CDK, including updating the API Gateway to route update requests to this Lambda function. + +Add unit tests for the newly created and updated source members, ensuring proper validation of the task identifier, task attributes, and handling of various response scenarios (success, not found, validation error, system error). + +Implement these requirements step by step. Follow all best practices and structure for this project. + +--- + +## Update Task Object + +The request body to update an existing task, the **UpdateTaskRequest**, should include: + +- **title:** The task title. String. Optional. Maximum of 100 characters. +- **detail:** Additional information for the task. String. Optional. Maximum of 2000 characters. +- **isComplete:** Indicates if the task is complete. Boolean. Optional. +- **dueAt:** The task due date. String. Optional. Use ISO-8601 date format, YYYY-MM-DD. + +The update operation should only modify the attributes that are provided in the request. Attributes not included in the request should remain unchanged. diff --git a/docs/requirements/05-delete-task.md b/docs/requirements/05-delete-task.md new file mode 100644 index 0000000..9b7229e --- /dev/null +++ b/docs/requirements/05-delete-task.md @@ -0,0 +1,19 @@ +# Requirement: Delete a Task by Identifier + +This document describes the requirements for an AWS Lambda REST API endpoint that will delete a **Task** item from DynamoDB by its identifier. + +--- + +## Description + +Create an AWS Lambda function which handles a REST API request to delete a specific **Task** item from DynamoDB using its unique identifier. The task identifier should be provided as a path parameter in the REST API request. The task service should first check if the **Task** item exists in the task table in AWS DynamoDB using the provided identifier. If found, the task service should delete the **Task** item from the database. + +The Lambda function should validate the task identifier in the request. The task service should use the AWS SDK to interact with AWS DynamoDB, specifically using the DynamoDB Document Client to perform the delete operation. + +The Lambda function should return an appropriate HTTP status code (204) with no content if the deletion is successful. If the task is not found, the Lambda function should return an appropriate HTTP status code (404) and the body should contain a meaningful message as JSON. If the request is invalid, the Lambda function should return an appropriate HTTP status code (400) and the body should contain validation error details as JSON. If an error occurs during processing, the Lambda function should return an appropriate HTTP status code (500) and the body should contain a meaningful message as JSON. + +Create appropriate AWS infrastructure for the new Lambda function using the AWS CDK, including updating the API Gateway to route delete requests to this Lambda function. + +Add unit tests for the newly created and updated source members, ensuring proper validation of the task identifier and handling of various response scenarios (success, not found, validation error, system error). + +Implement these requirements step by step. Follow all best practices and structure for this project. From 087347f773577bb774306f6c8768f23ec48c8756 Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Mon, 2 Jun 2025 15:57:11 -0400 Subject: [PATCH 08/26] list tasks --- infrastructure/stacks/ApiStack.ts | 29 ++++++- src/handlers/listTasks.test.ts | 131 ++++++++++++++++++++++++++++++ src/handlers/listTasks.ts | 32 ++++++++ src/services/taskService.test.ts | 85 ++++++++++++++++++- src/services/taskService.ts | 27 +++++- 5 files changed, 301 insertions(+), 3 deletions(-) create mode 100644 src/handlers/listTasks.test.ts create mode 100644 src/handlers/listTasks.ts diff --git a/infrastructure/stacks/ApiStack.ts b/infrastructure/stacks/ApiStack.ts index f831209..8039385 100644 --- a/infrastructure/stacks/ApiStack.ts +++ b/infrastructure/stacks/ApiStack.ts @@ -41,8 +41,32 @@ export class ApiStack extends cdk.Stack { timeout: cdk.Duration.seconds(6), }); - // Grant the Lambda function permissions to write to the DynamoDB table + // Create and configure CloudWatch Log Group for the list tasks Lambda function + const listTasksFunctionLogGroup = new logs.LogGroup(this, 'ListTasksFunctionLogGroup', { + retention: logs.RetentionDays.ONE_WEEK, + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Create Lambda function for listing tasks + const listTasksFunction = new lambdaNodejs.NodejsFunction(this, 'ListTasksFunction', { + runtime: lambda.Runtime.NODEJS_22_X, + entry: path.join(__dirname, '../../src/handlers/listTasks.ts'), + handler: 'listTasks', + environment: { + TASKS_TABLE: tasksTable.tableName, + }, + bundling: { + minify: true, + sourceMap: true, + }, + logGroup: listTasksFunctionLogGroup, + memorySize: 1024, + timeout: cdk.Duration.seconds(6), + }); + + // Grant the Lambda functions permissions to access the DynamoDB table tasksTable.grantWriteData(createTaskFunction); + tasksTable.grantReadData(listTasksFunction); // Create API Gateway const api = new apigateway.RestApi(this, 'TasksApi', { @@ -59,6 +83,9 @@ export class ApiStack extends cdk.Stack { // Add a POST method to create a new task tasksResource.addMethod('POST', new apigateway.LambdaIntegration(createTaskFunction)); + // Add a GET method to list all tasks + tasksResource.addMethod('GET', new apigateway.LambdaIntegration(listTasksFunction)); + // Output the API URL new cdk.CfnOutput(this, 'ApiUrl', { value: api.url, diff --git a/src/handlers/listTasks.test.ts b/src/handlers/listTasks.test.ts new file mode 100644 index 0000000..7cf05b3 --- /dev/null +++ b/src/handlers/listTasks.test.ts @@ -0,0 +1,131 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { APIGatewayProxyEvent } from 'aws-lambda'; +import { listTasks } from './listTasks'; + +// Mock dependencies +vi.mock('../services/taskService.js', () => ({ + TaskService: { + listTasks: vi.fn(), + }, +})); + +vi.mock('../utils/logger.js', () => ({ + logger: { + info: vi.fn(), + debug: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }, +})); + +describe('listTasks handler', () => { + const mockRequestId = 'test-request-id'; + + // Create a helper function to build mock events + const createMockEvent = (): APIGatewayProxyEvent => + ({ + requestContext: { + requestId: mockRequestId, + }, + }) as unknown as APIGatewayProxyEvent; + + beforeEach(async () => { + // Import the module fresh in each test to reset the mocks + vi.resetModules(); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('successful tasks retrieval', () => { + it('should return 200 with an array of tasks', async () => { + // Arrange + const mockEvent = createMockEvent(); + const mockTasks = [ + { + id: 'task1', + title: 'First Task', + detail: 'Task details', + isComplete: false, + dueAt: '2025-06-30', + }, + { + id: 'task2', + title: 'Second Task', + isComplete: true, + }, + ]; + + const { TaskService } = await import('../services/taskService.js'); + const { logger } = await import('../utils/logger.js'); + + // Mock the TaskService.listTasks to return tasks + vi.mocked(TaskService.listTasks).mockResolvedValue(mockTasks); + + // Act + const response = await listTasks(mockEvent); + + // Assert + expect(TaskService.listTasks).toHaveBeenCalledTimes(1); + expect(response.statusCode).toBe(200); + expect(JSON.parse(response.body)).toEqual(mockTasks); + expect(logger.info).toHaveBeenCalledWith( + 'Tasks retrieved successfully', + expect.objectContaining({ + requestId: mockRequestId, + count: mockTasks.length, + }), + ); + }); + + it('should return an empty array when there are no tasks', async () => { + // Arrange + const mockEvent = createMockEvent(); + const mockTasks: never[] = []; + + const { TaskService } = await import('../services/taskService.js'); + + // Mock the TaskService.listTasks to return an empty array + vi.mocked(TaskService.listTasks).mockResolvedValue(mockTasks); + + // Act + const response = await listTasks(mockEvent); + + // Assert + expect(TaskService.listTasks).toHaveBeenCalledTimes(1); + expect(response.statusCode).toBe(200); + expect(JSON.parse(response.body)).toEqual([]); + }); + }); + + describe('error handling', () => { + it('should return 500 when an unexpected error occurs', async () => { + // Arrange + const mockEvent = createMockEvent(); + + const { TaskService } = await import('../services/taskService.js'); + const { logger } = await import('../utils/logger.js'); + + // Mock the TaskService.listTasks to throw an error + const mockError = new Error('Test error'); + vi.mocked(TaskService.listTasks).mockRejectedValue(mockError); + + // Act + const response = await listTasks(mockEvent); + + // Assert + expect(response.statusCode).toBe(500); + expect(JSON.parse(response.body)).toEqual({ + message: 'An unexpected error occurred while retrieving tasks', + }); + expect(logger.error).toHaveBeenCalledWith( + 'Failed to list tasks', + mockError, + expect.objectContaining({ + requestId: mockRequestId, + }), + ); + }); + }); +}); diff --git a/src/handlers/listTasks.ts b/src/handlers/listTasks.ts new file mode 100644 index 0000000..e8513d5 --- /dev/null +++ b/src/handlers/listTasks.ts @@ -0,0 +1,32 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; +import { ok, internalServerError } from '@/utils/response'; +import { TaskService } from '@/services/taskService'; +import { logger } from '@/utils/logger'; + +/** + * Lambda Handler: listTasks + * + * Copilot Instructions: + * - Call taskService.listTasks() + * - Return 200 with array of task objects + * - Catch unexpected errors; log and return 500 + */ +export const listTasks = async (event: APIGatewayProxyEvent): Promise => { + const requestId = event.requestContext?.requestId; + logger.info('Processing list tasks request', { requestId }); + + try { + logger.debug('Retrieving all tasks', { requestId }); + + // Call the service to get all tasks + const tasks = await TaskService.listTasks(); + + logger.info('Tasks retrieved successfully', { requestId, count: tasks.length }); + + // Return successful response + return ok(tasks); + } catch (error) { + logger.error('Failed to list tasks', error as Error, { requestId }); + return internalServerError('An unexpected error occurred while retrieving tasks'); + } +}; diff --git a/src/services/taskService.test.ts b/src/services/taskService.test.ts index f7d5949..708f0c3 100644 --- a/src/services/taskService.test.ts +++ b/src/services/taskService.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { v4 as uuidv4 } from 'uuid'; -import { PutCommand } from '@aws-sdk/lib-dynamodb'; +import { PutCommand, ScanCommand } from '@aws-sdk/lib-dynamodb'; import { CreateTaskRequest } from '@/models/Task.js'; // Mock dependencies @@ -8,6 +8,9 @@ vi.mock('@aws-sdk/lib-dynamodb', () => ({ PutCommand: vi.fn().mockImplementation((params) => ({ ...params, })), + ScanCommand: vi.fn().mockImplementation((params) => ({ + ...params, + })), })); vi.mock('uuid', () => ({ @@ -153,4 +156,84 @@ describe('TaskService', () => { await expect(taskService.createTask(createTaskRequest)).rejects.toThrow('DynamoDB Error'); }); }); + + describe('listTasks', () => { + it('should retrieve all tasks from DynamoDB', async () => { + // Arrange + const { dynamoDocClient } = await import('@/utils/awsClients.js'); + const { logger } = await import('@/utils/logger.js'); + const { config } = await import('@/utils/config.js'); + + const mockTasks = [ + { + id: 'task1', + title: 'First Task', + detail: 'Task details', + isComplete: false, + dueAt: '2025-06-30', + }, + { + id: 'task2', + title: 'Second Task', + isComplete: true, + }, + ]; + + // Mock the DynamoDB scan response + // eslint-disable-next-line @typescript-eslint/no-explicit-any + vi.mocked(dynamoDocClient.send as any).mockResolvedValueOnce({ + Items: mockTasks, + }); + + // Act + const result = await taskService.listTasks(); + + // Assert + // Check that ScanCommand was called with the correct parameters + expect(ScanCommand).toHaveBeenCalledWith({ + TableName: config.TASKS_TABLE, + }); + + // Check that the document client's send method was called + expect(dynamoDocClient.send).toHaveBeenCalledTimes(1); + + // Check that the tasks were returned correctly + expect(result).toEqual(mockTasks); + + // Check logging + expect(logger.debug).toHaveBeenCalledWith('Listing all tasks from DynamoDB', { + tableName: 'tasks-table-test', + }); + expect(logger.info).toHaveBeenCalledWith('Retrieved all tasks successfully', { count: mockTasks.length }); + }); + + it('should return an empty array when no tasks exist', async () => { + // Arrange + const { dynamoDocClient } = await import('@/utils/awsClients.js'); + + // Mock the DynamoDB scan response with no items + // eslint-disable-next-line @typescript-eslint/no-explicit-any + vi.mocked(dynamoDocClient.send as any).mockResolvedValueOnce({ + Items: [], + }); + + // Act + const result = await taskService.listTasks(); + + // Assert + expect(result).toEqual([]); + }); + + it('should handle DynamoDB errors', async () => { + // Arrange + const { dynamoDocClient } = await import('@/utils/awsClients.js'); + const error = new Error('DynamoDB Error'); + + // Mock the DynamoDB send method to throw an error + vi.mocked(dynamoDocClient.send).mockRejectedValueOnce(error); + + // Act & Assert + await expect(taskService.listTasks()).rejects.toThrow('DynamoDB Error'); + }); + }); }); diff --git a/src/services/taskService.ts b/src/services/taskService.ts index 78662e1..3d09912 100644 --- a/src/services/taskService.ts +++ b/src/services/taskService.ts @@ -2,7 +2,7 @@ * The TaskService module provides functionality to operate on tasks in DynamoDB. * It includes methods to handle task creation, validation, and interaction with AWS services. */ -import { PutCommand } from '@aws-sdk/lib-dynamodb'; +import { PutCommand, ScanCommand } from '@aws-sdk/lib-dynamodb'; import { v4 as uuidv4 } from 'uuid'; import { Task, CreateTaskRequest } from '@/models/Task.js'; import { dynamoDocClient } from '@/utils/awsClients.js'; @@ -46,7 +46,32 @@ const createTask = async (createTaskRequest: CreateTaskRequest): Promise = return task; }; +/** + * Lists all tasks from DynamoDB + * + * @returns Array of Task objects + */ +const listTasks = async (): Promise => { + logger.debug('Listing all tasks from DynamoDB', { + tableName: config.TASKS_TABLE, + }); + + // Scan the DynamoDB table to get all tasks + const response = await dynamoDocClient.send( + new ScanCommand({ + TableName: config.TASKS_TABLE, + }), + ); + + // Convert the Items to Task objects + const tasks = response.Items as Task[]; + + logger.info('Retrieved all tasks successfully', { count: tasks.length }); + return tasks; +}; + // Define and export the TaskService with the methods to handle task operations export const TaskService = { createTask, + listTasks, }; From aefd90ad44f83f4fce09ee78ebc9016bf93aaa0c Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Tue, 3 Jun 2025 07:41:35 -0400 Subject: [PATCH 09/26] get task by ID --- infrastructure/stacks/ApiStack.ts | 30 ++ package-lock.json | 436 ++++++++++++++---------------- package.json | 10 +- src/handlers/getTask.test.ts | 138 ++++++++++ src/handlers/getTask.ts | 62 +++++ src/services/taskService.test.ts | 83 +++++- src/services/taskService.ts | 33 ++- 7 files changed, 549 insertions(+), 243 deletions(-) create mode 100644 src/handlers/getTask.test.ts create mode 100644 src/handlers/getTask.ts diff --git a/infrastructure/stacks/ApiStack.ts b/infrastructure/stacks/ApiStack.ts index 8039385..5a3be3c 100644 --- a/infrastructure/stacks/ApiStack.ts +++ b/infrastructure/stacks/ApiStack.ts @@ -64,9 +64,33 @@ export class ApiStack extends cdk.Stack { timeout: cdk.Duration.seconds(6), }); + // Create and configure CloudWatch Log Group for the get task Lambda function + const getTaskFunctionLogGroup = new logs.LogGroup(this, 'GetTaskFunctionLogGroup', { + retention: logs.RetentionDays.ONE_WEEK, + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Create Lambda function for getting a task by ID + const getTaskFunction = new lambdaNodejs.NodejsFunction(this, 'GetTaskFunction', { + runtime: lambda.Runtime.NODEJS_22_X, + entry: path.join(__dirname, '../../src/handlers/getTask.ts'), + handler: 'getTask', + environment: { + TASKS_TABLE: tasksTable.tableName, + }, + bundling: { + minify: true, + sourceMap: true, + }, + logGroup: getTaskFunctionLogGroup, + memorySize: 1024, + timeout: cdk.Duration.seconds(6), + }); + // Grant the Lambda functions permissions to access the DynamoDB table tasksTable.grantWriteData(createTaskFunction); tasksTable.grantReadData(listTasksFunction); + tasksTable.grantReadData(getTaskFunction); // Create API Gateway const api = new apigateway.RestApi(this, 'TasksApi', { @@ -86,6 +110,12 @@ export class ApiStack extends cdk.Stack { // Add a GET method to list all tasks tasksResource.addMethod('GET', new apigateway.LambdaIntegration(listTasksFunction)); + // Create a task resource + const taskResource = tasksResource.addResource('{taskId}'); + + // Add a GET method to get a task by ID + taskResource.addMethod('GET', new apigateway.LambdaIntegration(getTaskFunction)); + // Output the API URL new cdk.CfnOutput(this, 'ApiUrl', { value: api.url, diff --git a/package-lock.json b/package-lock.json index d242367..d3f01be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,17 +11,17 @@ "@aws-sdk/client-dynamodb": "3.821.0", "@aws-sdk/lib-dynamodb": "3.821.0", "@aws-sdk/util-dynamodb": "3.821.0", - "aws-cdk-lib": "2.199.0", + "aws-cdk-lib": "2.200.0", "constructs": "10.4.2", "uuid": "11.1.0", - "zod": "3.25.48" + "zod": "3.25.49" }, "devDependencies": { "@types/aws-lambda": "8.10.149", "@types/node": "22.15.29", "@types/uuid": "10.0.0", - "@typescript-eslint/eslint-plugin": "8.33.0", - "@typescript-eslint/parser": "8.33.0", + "@typescript-eslint/eslint-plugin": "8.33.1", + "@typescript-eslint/parser": "8.33.1", "@vitest/coverage-v8": "3.2.0", "aws-cdk": "2.1017.1", "eslint": "9.28.0", @@ -31,7 +31,7 @@ "rimraf": "6.0.1", "ts-node": "10.9.2", "typescript": "5.8.3", - "typescript-eslint": "8.33.0", + "typescript-eslint": "8.33.1", "vitest": "3.2.0" } }, @@ -49,17 +49,6 @@ "node": ">=6.0.0" } }, - "node_modules/@ampproject/remapping/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/@aws-cdk/asset-awscli-v1": { "version": "2.2.237", "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.237.tgz", @@ -73,9 +62,9 @@ "license": "Apache-2.0" }, "node_modules/@aws-cdk/cloud-assembly-schema": { - "version": "41.2.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-41.2.0.tgz", - "integrity": "sha512-JaulVS6z9y5+u4jNmoWbHZRs9uGOnmn/ktXygNWKNu1k6lF3ad4so3s18eRu15XCbUIomxN9WPYT6Ehh7hzONw==", + "version": "44.1.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-44.1.0.tgz", + "integrity": "sha512-WvesvSbBw5FrVbH8LZfjX5iDDRdixDkEnbsFGN8H2GNR9geBo4kIBI1nlOiqoGB6dwPwif8qDEM/4NOfuzIChQ==", "bundleDependencies": [ "jsonschema", "semver" @@ -83,7 +72,7 @@ "license": "Apache-2.0", "dependencies": { "jsonschema": "~1.4.1", - "semver": "^7.7.1" + "semver": "^7.7.2" }, "engines": { "node": ">= 14.15.0" @@ -98,7 +87,7 @@ } }, "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { - "version": "7.7.1", + "version": "7.7.2", "inBundle": true, "license": "ISC", "bin": { @@ -874,6 +863,17 @@ "node": ">=12" } }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", @@ -1607,17 +1607,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -1646,14 +1635,14 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@nodelib/fs.scandir": { @@ -2663,17 +2652,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.0.tgz", - "integrity": "sha512-CACyQuqSHt7ma3Ns601xykeBK/rDeZa3w6IS6UtMQbixO5DWy+8TilKkviGDH6jtWCo8FGRKEK5cLLkPvEammQ==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.1.tgz", + "integrity": "sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.33.0", - "@typescript-eslint/type-utils": "8.33.0", - "@typescript-eslint/utils": "8.33.0", - "@typescript-eslint/visitor-keys": "8.33.0", + "@typescript-eslint/scope-manager": "8.33.1", + "@typescript-eslint/type-utils": "8.33.1", + "@typescript-eslint/utils": "8.33.1", + "@typescript-eslint/visitor-keys": "8.33.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -2687,22 +2676,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.33.0", + "@typescript-eslint/parser": "^8.33.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.0.tgz", - "integrity": "sha512-JaehZvf6m0yqYp34+RVnihBAChkqeH+tqqhS0GuX1qgPpwLvmTPheKEs6OeCK6hVJgXZHJ2vbjnC9j119auStQ==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.1.tgz", + "integrity": "sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.33.0", - "@typescript-eslint/types": "8.33.0", - "@typescript-eslint/typescript-estree": "8.33.0", - "@typescript-eslint/visitor-keys": "8.33.0", + "@typescript-eslint/scope-manager": "8.33.1", + "@typescript-eslint/types": "8.33.1", + "@typescript-eslint/typescript-estree": "8.33.1", + "@typescript-eslint/visitor-keys": "8.33.1", "debug": "^4.3.4" }, "engines": { @@ -2718,14 +2707,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.0.tgz", - "integrity": "sha512-d1hz0u9l6N+u/gcrk6s6gYdl7/+pp8yHheRTqP6X5hVDKALEaTn8WfGiit7G511yueBEL3OpOEpD+3/MBdoN+A==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.1.tgz", + "integrity": "sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.33.0", - "@typescript-eslint/types": "^8.33.0", + "@typescript-eslint/tsconfig-utils": "^8.33.1", + "@typescript-eslint/types": "^8.33.1", "debug": "^4.3.4" }, "engines": { @@ -2734,17 +2723,20 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.0.tgz", - "integrity": "sha512-LMi/oqrzpqxyO72ltP+dBSP6V0xiUb4saY7WLtxSfiNEBI8m321LLVFU9/QDJxjDQG9/tjSqKz/E3380TEqSTw==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.1.tgz", + "integrity": "sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.33.0", - "@typescript-eslint/visitor-keys": "8.33.0" + "@typescript-eslint/types": "8.33.1", + "@typescript-eslint/visitor-keys": "8.33.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2755,9 +2747,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.0.tgz", - "integrity": "sha512-sTkETlbqhEoiFmGr1gsdq5HyVbSOF0145SYDJ/EQmXHtKViCaGvnyLqWFFHtEXoS0J1yU8Wyou2UGmgW88fEug==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.1.tgz", + "integrity": "sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==", "dev": true, "license": "MIT", "engines": { @@ -2772,14 +2764,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.0.tgz", - "integrity": "sha512-lScnHNCBqL1QayuSrWeqAL5GmqNdVUQAAMTaCwdYEdWfIrSrOGzyLGRCHXcCixa5NK6i5l0AfSO2oBSjCjf4XQ==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.1.tgz", + "integrity": "sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.33.0", - "@typescript-eslint/utils": "8.33.0", + "@typescript-eslint/typescript-estree": "8.33.1", + "@typescript-eslint/utils": "8.33.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -2796,9 +2788,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.0.tgz", - "integrity": "sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.1.tgz", + "integrity": "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==", "dev": true, "license": "MIT", "engines": { @@ -2810,16 +2802,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.0.tgz", - "integrity": "sha512-vegY4FQoB6jL97Tu/lWRsAiUUp8qJTqzAmENH2k59SJhw0Th1oszb9Idq/FyyONLuNqT1OADJPXfyUNOR8SzAQ==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.1.tgz", + "integrity": "sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.33.0", - "@typescript-eslint/tsconfig-utils": "8.33.0", - "@typescript-eslint/types": "8.33.0", - "@typescript-eslint/visitor-keys": "8.33.0", + "@typescript-eslint/project-service": "8.33.1", + "@typescript-eslint/tsconfig-utils": "8.33.1", + "@typescript-eslint/types": "8.33.1", + "@typescript-eslint/visitor-keys": "8.33.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2839,16 +2831,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.0.tgz", - "integrity": "sha512-lPFuQaLA9aSNa7D5u2EpRiqdAUhzShwGg/nhpBlc4GR6kcTABttCuyjFs8BcEZ8VWrjCBof/bePhP3Q3fS+Yrw==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.1.tgz", + "integrity": "sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.33.0", - "@typescript-eslint/types": "8.33.0", - "@typescript-eslint/typescript-estree": "8.33.0" + "@typescript-eslint/scope-manager": "8.33.1", + "@typescript-eslint/types": "8.33.1", + "@typescript-eslint/typescript-estree": "8.33.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2863,13 +2855,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.0.tgz", - "integrity": "sha512-7RW7CMYoskiz5OOGAWjJFxgb7c5UNjTG292gYhWeOAcFmYCtVCSqjqSBj5zMhxbXo2JOW95YYrUWJfU0zrpaGQ==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.1.tgz", + "integrity": "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.33.0", + "@typescript-eslint/types": "8.33.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -3159,17 +3151,6 @@ "js-tokens": "^9.0.1" } }, - "node_modules/ast-v8-to-istanbul/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/aws-cdk": { "version": "2.1017.1", "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1017.1.tgz", @@ -3187,9 +3168,9 @@ } }, "node_modules/aws-cdk-lib": { - "version": "2.199.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.199.0.tgz", - "integrity": "sha512-hAZHdb7bPHepIGpuyg0jS/F3toY7VRvJDqxo4+C2cYY5zvktGP3lgcC9ukE2ehxYU1Pa9YOAehEDIxrita0Hvw==", + "version": "2.200.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.200.0.tgz", + "integrity": "sha512-t4wGmFYuzlos7fFLFmv6ljtpMu+qYmPQnodfgUQ/BE0+y8S2MONQf9ihN+mZvsRqj96t6BwuVy3lR3UymtwGbw==", "bundleDependencies": [ "@balena/dockerignore", "case", @@ -3207,7 +3188,7 @@ "dependencies": { "@aws-cdk/asset-awscli-v1": "2.2.237", "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", - "@aws-cdk/cloud-assembly-schema": "^41.2.0", + "@aws-cdk/cloud-assembly-schema": "^44.1.0", "@balena/dockerignore": "^1.0.2", "case": "1.6.3", "fs-extra": "^11.3.0", @@ -3540,7 +3521,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/bowser": { @@ -3553,7 +3533,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -4242,22 +4221,25 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.2.tgz", + "integrity": "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==", "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" }, + "engines": { + "node": "20 || >=22" + }, "funding": { "url": "https://github.com/sponsors/isaacs" } @@ -4275,6 +4257,22 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/globals": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz", @@ -4316,7 +4314,6 @@ "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -4439,17 +4436,6 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-source-maps/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/istanbul-reports": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", @@ -4465,19 +4451,19 @@ } }, "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, + "engines": { + "node": "20 || >=22" + }, "funding": { "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/js-tokens": { @@ -4576,11 +4562,14 @@ "license": "MIT" }, "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", "dev": true, - "license": "ISC" + "license": "ISC", + "engines": { + "node": "20 || >=22" + } }, "node_modules/magic-string": { "version": "0.30.17", @@ -4655,7 +4644,6 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -4816,17 +4804,17 @@ } }, "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -4928,7 +4916,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4996,89 +4983,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/node_modules/glob": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.2.tgz", - "integrity": "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/jackspeak": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", - "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/lru-cache": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", - "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", - "dev": true, - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/rollup": { "version": "4.41.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz", @@ -5147,7 +5051,6 @@ "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -5380,6 +5283,67 @@ "node": ">=18" } }, + "node_modules/test-exclude/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/test-exclude/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/test-exclude/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -5573,15 +5537,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.33.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.33.0.tgz", - "integrity": "sha512-5YmNhF24ylCsvdNW2oJwMzTbaeO4bg90KeGtMjUw0AGtHksgEPLRTUil+coHwCfiu4QjVJFnjp94DmU6zV7DhQ==", + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.33.1.tgz", + "integrity": "sha512-AgRnV4sKkWOiZ0Kjbnf5ytTJXMUZQ0qhSVdQtDNYLPLnjsATEYhaO94GlRQwi4t4gO8FfjM6NnikHeKjUm8D7A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.33.0", - "@typescript-eslint/parser": "8.33.0", - "@typescript-eslint/utils": "8.33.0" + "@typescript-eslint/eslint-plugin": "8.33.1", + "@typescript-eslint/parser": "8.33.1", + "@typescript-eslint/utils": "8.33.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6021,9 +5985,9 @@ } }, "node_modules/zod": { - "version": "3.25.48", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.48.tgz", - "integrity": "sha512-0X1mz8FtgEIvaxGjdIImYpZEaZMrund9pGXm3M6vM7Reba0e2eI71KPjSCGXBfwKDPwPoywf6waUKc3/tFvX2Q==", + "version": "3.25.49", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.49.tgz", + "integrity": "sha512-JMMPMy9ZBk3XFEdbM3iL1brx4NUSejd6xr3ELrrGEfGb355gjhiAWtG3K5o+AViV/3ZfkIrCzXsZn6SbLwTR8Q==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 84e9ad7..2d9f127 100644 --- a/package.json +++ b/package.json @@ -18,17 +18,17 @@ "@aws-sdk/client-dynamodb": "3.821.0", "@aws-sdk/lib-dynamodb": "3.821.0", "@aws-sdk/util-dynamodb": "3.821.0", - "aws-cdk-lib": "2.199.0", + "aws-cdk-lib": "2.200.0", "constructs": "10.4.2", "uuid": "11.1.0", - "zod": "3.25.48" + "zod": "3.25.49" }, "devDependencies": { "@types/aws-lambda": "8.10.149", "@types/node": "22.15.29", "@types/uuid": "10.0.0", - "@typescript-eslint/eslint-plugin": "8.33.0", - "@typescript-eslint/parser": "8.33.0", + "@typescript-eslint/eslint-plugin": "8.33.1", + "@typescript-eslint/parser": "8.33.1", "@vitest/coverage-v8": "3.2.0", "aws-cdk": "2.1017.1", "eslint": "9.28.0", @@ -38,7 +38,7 @@ "rimraf": "6.0.1", "ts-node": "10.9.2", "typescript": "5.8.3", - "typescript-eslint": "8.33.0", + "typescript-eslint": "8.33.1", "vitest": "3.2.0" } } diff --git a/src/handlers/getTask.test.ts b/src/handlers/getTask.test.ts new file mode 100644 index 0000000..eea3582 --- /dev/null +++ b/src/handlers/getTask.test.ts @@ -0,0 +1,138 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { APIGatewayProxyEvent } from 'aws-lambda'; +import { getTask } from './getTask.js'; + +// Mock dependencies +vi.mock('@/services/taskService.js', () => ({ + TaskService: { + getTaskById: vi.fn(), + }, +})); + +vi.mock('@/utils/logger.js', () => ({ + logger: { + info: vi.fn(), + debug: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }, +})); + +vi.mock('@/utils/config.js', () => ({ + config: { + TASKS_TABLE: 'tasks-table-test', + }, +})); + +describe('getTask', () => { + // Setup data for tests + const mockTaskId = 'task-123'; + const mockTask = { + id: mockTaskId, + title: 'Test Task', + detail: 'Test task detail', + isComplete: false, + }; + + // Common test event + let event: Partial; + + beforeEach(() => { + // Reset mocks before each test + vi.resetAllMocks(); + + // Setup default event with path parameters + event = { + pathParameters: { + taskId: mockTaskId, + }, + } as unknown as APIGatewayProxyEvent; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + it('returns 200 with task when found', async () => { + // Arrange + const { TaskService } = await import('@/services/taskService.js'); + const { logger } = await import('@/utils/logger.js'); + + vi.mocked(TaskService.getTaskById).mockResolvedValue(mockTask); + + // Act + const response = await getTask(event as APIGatewayProxyEvent); + + // Assert + expect(response.statusCode).toBe(200); + expect(JSON.parse(response.body)).toEqual(mockTask); + expect(TaskService.getTaskById).toHaveBeenCalledWith(mockTaskId); + expect(logger.info).toHaveBeenCalledWith('Task retrieved successfully', { taskId: mockTaskId }); + }); + it('returns 404 when task is not found', async () => { + // Arrange + const { TaskService } = await import('@/services/taskService.js'); + const { logger } = await import('@/utils/logger.js'); + + vi.mocked(TaskService.getTaskById).mockResolvedValue(undefined); + + // Act + const response = await getTask(event as APIGatewayProxyEvent); + + // Assert + expect(response.statusCode).toBe(404); + expect(JSON.parse(response.body)).toEqual({ message: `Task with ID ${mockTaskId} not found` }); + expect(TaskService.getTaskById).toHaveBeenCalledWith(mockTaskId); + expect(logger.info).toHaveBeenCalledWith('Task not found', { taskId: mockTaskId }); + }); + it('returns 400 when taskId is missing', async () => { + // Arrange + const { TaskService } = await import('@/services/taskService.js'); + const { logger } = await import('@/utils/logger.js'); + + event = { + pathParameters: {}, + } as unknown as APIGatewayProxyEvent; + + // Act + const response = await getTask(event as APIGatewayProxyEvent); + + // Assert + expect(response.statusCode).toBe(400); + expect(JSON.parse(response.body)).toEqual({ message: 'Invalid request: taskId is required' }); + expect(TaskService.getTaskById).not.toHaveBeenCalled(); + expect(logger.warn).toHaveBeenCalled(); + }); + it('returns 400 when pathParameters is null', async () => { + // Arrange + const { TaskService } = await import('@/services/taskService.js'); + + event = { + pathParameters: null, + } as unknown as APIGatewayProxyEvent; + + // Act + const response = await getTask(event as APIGatewayProxyEvent); + + // Assert + expect(response.statusCode).toBe(400); + expect(JSON.parse(response.body)).toEqual({ message: 'Invalid request: taskId is required' }); + expect(TaskService.getTaskById).not.toHaveBeenCalled(); + }); + it('returns 500 when an unexpected error occurs', async () => { + // Arrange + const { TaskService } = await import('@/services/taskService.js'); + const { logger } = await import('@/utils/logger.js'); + + const error = new Error('Test error'); + vi.mocked(TaskService.getTaskById).mockRejectedValue(error); + + // Act + const response = await getTask(event as APIGatewayProxyEvent); + + // Assert + expect(response.statusCode).toBe(500); + expect(JSON.parse(response.body)).toEqual({ message: 'An unexpected error occurred while retrieving the task' }); + expect(TaskService.getTaskById).toHaveBeenCalledWith(mockTaskId); + expect(logger.error).toHaveBeenCalledWith('Failed to get task', error, { taskId: mockTaskId }); + }); +}); diff --git a/src/handlers/getTask.ts b/src/handlers/getTask.ts new file mode 100644 index 0000000..d22ef7b --- /dev/null +++ b/src/handlers/getTask.ts @@ -0,0 +1,62 @@ +/** + * Lambda Handler: getTask + * + * Retrieves a single task by its ID from DynamoDB. + * - Parses taskId from event.pathParameters + * - Calls taskService.getTaskById(taskId) + * - Returns 200 with task object or 404 if not found + * - Validates input; returns 400 if invalid + * - Catches unexpected errors; logs and returns 500 + */ +import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; +import { z } from 'zod'; +import { TaskService } from '@/services/taskService.js'; +import { badRequest, internalServerError, notFound, ok } from '@/utils/response.js'; +import { logger } from '@/utils/logger.js'; + +// Zod schema for request validation +const requestSchema = z.object({ + pathParameters: z.object({ + taskId: z.string().min(1, 'taskId path variable is required'), + }), +}); + +// Type definition derived from the schema +type Request = z.infer; + +/** + * Lambda handler function to get a task by ID + * + * @param event The API Gateway event containing the request + * @returns API Gateway response with task or error + */ +export const getTask = async (event: APIGatewayProxyEvent): Promise => { + logger.debug('Getting task', { event }); + + // Validate input + const result = requestSchema.safeParse(event); + if (!result.success) { + logger.warn('Invalid request', { errors: result.error.errors }); + return badRequest('Invalid request: taskId is required'); + } + + // Extract validated data + const request: Request = result.data; + const { taskId } = request.pathParameters; + + try { + // Call service to get task + const task = await TaskService.getTaskById(taskId); + + if (!task) { + logger.info('Task not found', { taskId }); + return notFound(`Task with ID ${taskId} not found`); + } + + logger.info('Task retrieved successfully', { taskId }); + return ok(task); + } catch (err) { + logger.error('Failed to get task', err as Error, { taskId }); + return internalServerError('An unexpected error occurred while retrieving the task'); + } +}; diff --git a/src/services/taskService.test.ts b/src/services/taskService.test.ts index 708f0c3..02899b9 100644 --- a/src/services/taskService.test.ts +++ b/src/services/taskService.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { v4 as uuidv4 } from 'uuid'; -import { PutCommand, ScanCommand } from '@aws-sdk/lib-dynamodb'; +import { PutCommand, ScanCommand, GetCommand } from '@aws-sdk/lib-dynamodb'; import { CreateTaskRequest } from '@/models/Task.js'; // Mock dependencies @@ -11,6 +11,9 @@ vi.mock('@aws-sdk/lib-dynamodb', () => ({ ScanCommand: vi.fn().mockImplementation((params) => ({ ...params, })), + GetCommand: vi.fn().mockImplementation((params) => ({ + ...params, + })), })); vi.mock('uuid', () => ({ @@ -236,4 +239,82 @@ describe('TaskService', () => { await expect(taskService.listTasks()).rejects.toThrow('DynamoDB Error'); }); }); + + describe('getTaskById', () => { + it('should retrieve a task by ID', async () => { + // Arrange + const { dynamoDocClient } = await import('@/utils/awsClients.js'); + const { logger } = await import('@/utils/logger.js'); + const { config } = await import('@/utils/config.js'); + + const taskId = 'task123'; + const mockTask = { + id: taskId, + title: 'Test Task', + detail: 'This is a test task', + isComplete: false, + }; + + // Mock the DynamoDB get response + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (dynamoDocClient.send as any).mockResolvedValueOnce({ + Item: mockTask, + }); + + // Act + const result = await taskService.getTaskById(taskId); + + // Assert + // Check that GetCommand was called with the correct parameters + expect(GetCommand).toHaveBeenCalledWith({ + TableName: config.TASKS_TABLE, + Key: { id: taskId }, + }); + + // Check that the document client's send method was called + expect(dynamoDocClient.send).toHaveBeenCalledTimes(1); + + // Check that the task was returned correctly + expect(result).toEqual(mockTask); + + // Check logging + expect(logger.debug).toHaveBeenCalledWith('Getting task by ID', { taskId }); + expect(logger.info).toHaveBeenCalledWith('Task retrieved successfully', { taskId }); + }); + + it('should return undefined when task does not exist', async () => { + // Arrange + const { dynamoDocClient } = await import('@/utils/awsClients.js'); + const { logger } = await import('@/utils/logger.js'); + + const taskId = 'nonexistent-task'; + + // Mock the DynamoDB get response with no item + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (dynamoDocClient.send as any).mockResolvedValueOnce({ + Item: undefined, + }); + + // Act + const result = await taskService.getTaskById(taskId); + + // Assert + expect(result).toBeUndefined(); + expect(logger.info).toHaveBeenCalledWith('Task not found', { taskId }); + }); + + it('should handle DynamoDB errors', async () => { + // Arrange + const { dynamoDocClient } = await import('@/utils/awsClients.js'); + const error = new Error('DynamoDB Error'); + + // Mock the DynamoDB send method to throw an error + vi.mocked(dynamoDocClient.send).mockRejectedValueOnce(error); + + const taskId = 'task123'; + + // Act & Assert + await expect(taskService.getTaskById(taskId)).rejects.toThrow('DynamoDB Error'); + }); + }); }); diff --git a/src/services/taskService.ts b/src/services/taskService.ts index 3d09912..9fcf0b2 100644 --- a/src/services/taskService.ts +++ b/src/services/taskService.ts @@ -2,7 +2,7 @@ * The TaskService module provides functionality to operate on tasks in DynamoDB. * It includes methods to handle task creation, validation, and interaction with AWS services. */ -import { PutCommand, ScanCommand } from '@aws-sdk/lib-dynamodb'; +import { PutCommand, ScanCommand, GetCommand } from '@aws-sdk/lib-dynamodb'; import { v4 as uuidv4 } from 'uuid'; import { Task, CreateTaskRequest } from '@/models/Task.js'; import { dynamoDocClient } from '@/utils/awsClients.js'; @@ -70,8 +70,39 @@ const listTasks = async (): Promise => { return tasks; }; +/** + * Retrieves a task by its ID from DynamoDB + * + * @param taskId The unique identifier of the task to retrieve + * @returns The task if found, undefined otherwise + */ +const getTaskById = async (taskId: string): Promise => { + logger.debug('Getting task by ID', { taskId }); + + // Get the task from DynamoDB + const response = await dynamoDocClient.send( + new GetCommand({ + TableName: config.TASKS_TABLE, + Key: { id: taskId }, + }), + ); + + // If the task wasn't found, return undefined + if (!response.Item) { + logger.info('Task not found', { taskId }); + return undefined; + } + + // Convert the Item to a Task object + const task = response.Item as Task; + + logger.info('Task retrieved successfully', { taskId }); + return task; +}; + // Define and export the TaskService with the methods to handle task operations export const TaskService = { createTask, listTasks, + getTaskById, }; From 6888c4cc8f747ed755e19129692966d14d1d6830 Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Tue, 3 Jun 2025 07:46:01 -0400 Subject: [PATCH 10/26] handler logging --- src/handlers/createTask.ts | 1 + src/handlers/getTask.ts | 12 +++++++----- src/handlers/listTasks.ts | 1 + 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/handlers/createTask.ts b/src/handlers/createTask.ts index 180f002..e888e7d 100644 --- a/src/handlers/createTask.ts +++ b/src/handlers/createTask.ts @@ -36,6 +36,7 @@ const requestSchema = z.object({ export const createTask = async (event: APIGatewayProxyEvent): Promise => { const requestId = event.requestContext?.requestId; logger.info('Processing create task request', { requestId }); + logger.debug('Received event', { requestId, event }); try { // Parse and validate request diff --git a/src/handlers/getTask.ts b/src/handlers/getTask.ts index d22ef7b..2df26fa 100644 --- a/src/handlers/getTask.ts +++ b/src/handlers/getTask.ts @@ -31,12 +31,14 @@ type Request = z.infer; * @returns API Gateway response with task or error */ export const getTask = async (event: APIGatewayProxyEvent): Promise => { - logger.debug('Getting task', { event }); + const requestId = event.requestContext?.requestId; + logger.info('Processing get task request', { requestId }); + logger.debug('Received event', { requestId, event }); // Validate input const result = requestSchema.safeParse(event); if (!result.success) { - logger.warn('Invalid request', { errors: result.error.errors }); + logger.warn('Invalid request', { requestId, errors: result.error.errors }); return badRequest('Invalid request: taskId is required'); } @@ -49,14 +51,14 @@ export const getTask = async (event: APIGatewayProxyEvent): Promise => { const requestId = event.requestContext?.requestId; logger.info('Processing list tasks request', { requestId }); + logger.debug('Received event', { requestId, event }); try { logger.debug('Retrieving all tasks', { requestId }); From 0e92a7eda398053e8af4d0546e6bb8a2a727df6c Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Tue, 3 Jun 2025 08:25:22 -0400 Subject: [PATCH 11/26] implement update task functionality with validation and logging --- infrastructure/stacks/ApiStack.ts | 27 ++++ src/handlers/updateTask.test.ts | 231 +++++++++++++++++++++++++++++ src/handlers/updateTask.ts | 105 ++++++++++++++ src/models/Task.ts | 17 +++ src/services/taskService.test.ts | 232 +++++++++++++++++++++++++++++- src/services/taskService.ts | 91 +++++++++++- 6 files changed, 699 insertions(+), 4 deletions(-) create mode 100644 src/handlers/updateTask.test.ts create mode 100644 src/handlers/updateTask.ts diff --git a/infrastructure/stacks/ApiStack.ts b/infrastructure/stacks/ApiStack.ts index 5a3be3c..ffb4056 100644 --- a/infrastructure/stacks/ApiStack.ts +++ b/infrastructure/stacks/ApiStack.ts @@ -87,10 +87,34 @@ export class ApiStack extends cdk.Stack { timeout: cdk.Duration.seconds(6), }); + // Create and configure CloudWatch Log Group for the update task Lambda function + const updateTaskFunctionLogGroup = new logs.LogGroup(this, 'UpdateTaskFunctionLogGroup', { + retention: logs.RetentionDays.ONE_WEEK, + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Create Lambda function for updating a task by ID + const updateTaskFunction = new lambdaNodejs.NodejsFunction(this, 'UpdateTaskFunction', { + runtime: lambda.Runtime.NODEJS_22_X, + entry: path.join(__dirname, '../../src/handlers/updateTask.ts'), + handler: 'updateTask', + environment: { + TASKS_TABLE: tasksTable.tableName, + }, + bundling: { + minify: true, + sourceMap: true, + }, + logGroup: updateTaskFunctionLogGroup, + memorySize: 1024, + timeout: cdk.Duration.seconds(6), + }); + // Grant the Lambda functions permissions to access the DynamoDB table tasksTable.grantWriteData(createTaskFunction); tasksTable.grantReadData(listTasksFunction); tasksTable.grantReadData(getTaskFunction); + tasksTable.grantReadWriteData(updateTaskFunction); // Create API Gateway const api = new apigateway.RestApi(this, 'TasksApi', { @@ -116,6 +140,9 @@ export class ApiStack extends cdk.Stack { // Add a GET method to get a task by ID taskResource.addMethod('GET', new apigateway.LambdaIntegration(getTaskFunction)); + // Add a PUT method to update a task by ID + taskResource.addMethod('PUT', new apigateway.LambdaIntegration(updateTaskFunction)); + // Output the API URL new cdk.CfnOutput(this, 'ApiUrl', { value: api.url, diff --git a/src/handlers/updateTask.test.ts b/src/handlers/updateTask.test.ts new file mode 100644 index 0000000..6fde357 --- /dev/null +++ b/src/handlers/updateTask.test.ts @@ -0,0 +1,231 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { APIGatewayProxyEvent } from 'aws-lambda'; +import { updateTask } from './updateTask.js'; + +// Mock dependencies +vi.mock('@/services/taskService.js', () => ({ + TaskService: { + updateTask: vi.fn(), + }, +})); + +vi.mock('@/utils/logger.js', () => ({ + logger: { + info: vi.fn(), + debug: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }, +})); + +vi.mock('@/utils/config.js', () => ({ + config: { + TASKS_TABLE: 'tasks-table-test', + }, +})); + +describe('updateTask', () => { + // Setup data for tests + const mockTaskId = 'task-123'; + const mockUpdateData = { + title: 'Updated Task', + isComplete: true, + }; + const mockUpdatedTask = { + id: mockTaskId, + title: 'Updated Task', + detail: 'Original detail', + isComplete: true, + }; + + // Common test event + let event: Partial; + + beforeEach(() => { + // Reset mocks before each test + vi.resetAllMocks(); + + // Setup default event with path parameters and body + event = { + pathParameters: { + taskId: mockTaskId, + }, + body: JSON.stringify(mockUpdateData), + } as unknown as APIGatewayProxyEvent; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('returns 200 with updated task when update is successful', async () => { + // Arrange + const { TaskService } = await import('@/services/taskService.js'); + const { logger } = await import('@/utils/logger.js'); + + vi.mocked(TaskService.updateTask).mockResolvedValue(mockUpdatedTask); + + // Act + const response = await updateTask(event as APIGatewayProxyEvent); + + // Assert + expect(response.statusCode).toBe(200); + expect(JSON.parse(response.body)).toEqual(mockUpdatedTask); + expect(TaskService.updateTask).toHaveBeenCalledWith(mockTaskId, mockUpdateData); + expect(logger.info).toHaveBeenCalledWith( + 'Task updated successfully', + expect.objectContaining({ taskId: mockTaskId }), + ); + }); + + it('returns 404 when task is not found', async () => { + // Arrange + const { TaskService } = await import('@/services/taskService.js'); + const { logger } = await import('@/utils/logger.js'); + + vi.mocked(TaskService.updateTask).mockResolvedValue(undefined); + + // Act + const response = await updateTask(event as APIGatewayProxyEvent); + + // Assert + expect(response.statusCode).toBe(404); + expect(JSON.parse(response.body)).toEqual({ message: `Task with ID ${mockTaskId} not found` }); + expect(TaskService.updateTask).toHaveBeenCalledWith(mockTaskId, mockUpdateData); + expect(logger.info).toHaveBeenCalledWith( + 'Task not found for update', + expect.objectContaining({ taskId: mockTaskId }), + ); + }); + + it('returns 400 when taskId is missing', async () => { + // Arrange + const { TaskService } = await import('@/services/taskService.js'); + const { logger } = await import('@/utils/logger.js'); + + event = { + pathParameters: {}, + body: JSON.stringify(mockUpdateData), + } as unknown as APIGatewayProxyEvent; + + // Act + const response = await updateTask(event as APIGatewayProxyEvent); + + // Assert + expect(response.statusCode).toBe(400); + expect(JSON.parse(response.body)).toEqual({ message: 'Invalid request: taskId path variable is required' }); + expect(TaskService.updateTask).not.toHaveBeenCalled(); + expect(logger.warn).toHaveBeenCalled(); + }); + + it('returns 400 when pathParameters is null', async () => { + // Arrange + const { TaskService } = await import('@/services/taskService.js'); + + event = { + pathParameters: null, + body: JSON.stringify(mockUpdateData), + } as unknown as APIGatewayProxyEvent; + + // Act + const response = await updateTask(event as APIGatewayProxyEvent); + + // Assert + expect(response.statusCode).toBe(400); + expect(JSON.parse(response.body)).toEqual({ message: 'Invalid request: taskId path variable is required' }); + expect(TaskService.updateTask).not.toHaveBeenCalled(); + }); + + it('returns 400 when request body is not valid JSON', async () => { + // Arrange + const { TaskService } = await import('@/services/taskService.js'); + const { logger } = await import('@/utils/logger.js'); + + event = { + pathParameters: { + taskId: mockTaskId, + }, + body: 'not valid json', + } as unknown as APIGatewayProxyEvent; + + // Act + const response = await updateTask(event as APIGatewayProxyEvent); + + // Assert + expect(response.statusCode).toBe(400); + expect(JSON.parse(response.body)).toEqual({ + message: 'Invalid request format: The request body must be valid JSON', + }); + expect(TaskService.updateTask).not.toHaveBeenCalled(); + expect(logger.warn).toHaveBeenCalled(); + }); + + it('returns 400 when update data does not match schema', async () => { + // Arrange + const { TaskService } = await import('@/services/taskService.js'); + const { logger } = await import('@/utils/logger.js'); + + event = { + pathParameters: { + taskId: mockTaskId, + }, + body: JSON.stringify({ + title: 'A'.repeat(101), // Exceeds max length + isComplete: 'not a boolean', // Not a boolean + }), + } as unknown as APIGatewayProxyEvent; + + // Act + const response = await updateTask(event as APIGatewayProxyEvent); + + // Assert + expect(response.statusCode).toBe(400); + expect(JSON.parse(response.body).message).toContain('Invalid update task data'); + expect(TaskService.updateTask).not.toHaveBeenCalled(); + expect(logger.warn).toHaveBeenCalled(); + }); + + it('returns 400 when no update fields are provided', async () => { + // Arrange + const { TaskService } = await import('@/services/taskService.js'); + const { logger } = await import('@/utils/logger.js'); + + event = { + pathParameters: { + taskId: mockTaskId, + }, + body: JSON.stringify({}), + } as unknown as APIGatewayProxyEvent; + + // Act + const response = await updateTask(event as APIGatewayProxyEvent); + + // Assert + expect(response.statusCode).toBe(400); + expect(JSON.parse(response.body)).toEqual({ message: 'At least one field must be provided for update' }); + expect(TaskService.updateTask).not.toHaveBeenCalled(); + expect(logger.warn).toHaveBeenCalled(); + }); + + it('returns 500 when an unexpected error occurs', async () => { + // Arrange + const { TaskService } = await import('@/services/taskService.js'); + const { logger } = await import('@/utils/logger.js'); + + const error = new Error('Test error'); + vi.mocked(TaskService.updateTask).mockRejectedValue(error); + + // Act + const response = await updateTask(event as APIGatewayProxyEvent); + + // Assert + expect(response.statusCode).toBe(500); + expect(JSON.parse(response.body)).toEqual({ message: 'An unexpected error occurred while updating the task' }); + expect(TaskService.updateTask).toHaveBeenCalledWith(mockTaskId, mockUpdateData); + expect(logger.error).toHaveBeenCalledWith( + 'Failed to update task', + error, + expect.objectContaining({ taskId: mockTaskId }), + ); + }); +}); diff --git a/src/handlers/updateTask.ts b/src/handlers/updateTask.ts new file mode 100644 index 0000000..3ecd0d0 --- /dev/null +++ b/src/handlers/updateTask.ts @@ -0,0 +1,105 @@ +/** + * Lambda Handler: updateTask + * + * Updates a single task by its ID in DynamoDB. + * - Parses taskId from event.pathParameters + * - Parses update data from event.body + * - Calls taskService.updateTask(taskId, updateData) + * - Returns 200 with updated task object or 404 if not found + * - Validates input; returns 400 if invalid + * - Catches unexpected errors; logs and returns 500 + */ +import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; +import { z } from 'zod'; +import { UpdateTaskSchema } from '@/models/Task.js'; +import { TaskService } from '@/services/taskService.js'; +import { badRequest, internalServerError, notFound, ok } from '@/utils/response.js'; +import { logger } from '@/utils/logger.js'; + +// Zod schema for request path validation +const requestPathSchema = z.object({ + pathParameters: z.object({ + taskId: z.string().min(1, 'taskId path variable is required'), + }), +}); + +// Zod schema for request body validation +const requestBodySchema = z.object({ + body: z.string().transform((body, ctx) => { + try { + // Parse body as JSON + return JSON.parse(body); + } catch (_error) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Invalid JSON in request body', + }); + return z.NEVER; + } + }), +}); + +// Type definition derived from the schema +type RequestPath = z.infer; + +/** + * Lambda handler function to update a task by ID + * + * @param event The API Gateway event containing the request + * @returns API Gateway response with updated task or error + */ +export const updateTask = async (event: APIGatewayProxyEvent): Promise => { + const requestId = event.requestContext?.requestId; + logger.info('Processing update task request', { requestId }); + logger.debug('Received event', { requestId, event }); + + // Validate path parameters + const pathResult = requestPathSchema.safeParse(event); + if (!pathResult.success) { + logger.warn('Invalid path parameters', { requestId, errors: pathResult.error.errors }); + return badRequest('Invalid request: taskId path variable is required'); + } + + // Extract validated path data + const request: RequestPath = pathResult.data; + const { taskId } = request.pathParameters; + + // Validate request body + const bodyResult = requestBodySchema.safeParse(event); + if (!bodyResult.success) { + logger.warn('Invalid request format', { requestId, errors: bodyResult.error.errors }); + return badRequest('Invalid request format: The request body must be valid JSON'); + } + + // Validate update task data against schema + const updateValidation = UpdateTaskSchema.safeParse(bodyResult.data.body); + if (!updateValidation.success) { + const errors = updateValidation.error.errors.map((e) => e.message).join(', '); + logger.warn('Invalid update task data', { requestId, errors }); + return badRequest(`Invalid update task data: ${errors}`); + } + + // If we get here and have no fields to update, return an error + if (Object.keys(updateValidation.data).length === 0) { + logger.warn('No update fields provided', { requestId }); + return badRequest('At least one field must be provided for update'); + } + + logger.debug('Updating task', { requestId, taskId, updateData: updateValidation.data }); + + try { + // Call service to update task + const updatedTask = await TaskService.updateTask(taskId, updateValidation.data); + + if (!updatedTask) { + logger.info('Task not found for update', { requestId, taskId }); + return notFound(`Task with ID ${taskId} not found`); + } + + logger.info('Task updated successfully', { requestId, taskId }); + return ok(updatedTask); + } catch (err) { + logger.error('Failed to update task', err as Error, { requestId, taskId }); + return internalServerError('An unexpected error occurred while updating the task'); + } +}; diff --git a/src/models/Task.ts b/src/models/Task.ts index 2953739..3541748 100644 --- a/src/models/Task.ts +++ b/src/models/Task.ts @@ -30,6 +30,23 @@ export const CreateTaskSchema = TaskSchema.omit({ id: true, isComplete: true }). isComplete: z.boolean().default(false).optional(), }); +/** + * Schema for updating an existing Task + * + * All fields are optional since this is for partial updates: + * - title: Optional string, max 100 characters + * - detail: Optional string, max 2000 characters + * - isComplete: Optional boolean + * - dueAt: Optional ISO-8601 date string + */ +export const UpdateTaskSchema = z.object({ + title: z.string().max(100, 'Title must be 100 characters or less').optional(), + detail: z.string().max(2000, 'Detail must be 2000 characters or less').optional(), + isComplete: z.boolean().optional(), + dueAt: z.string().date().optional(), +}); + // Type definitions derived from the schemas export type Task = z.infer; export type CreateTaskRequest = z.infer; +export type UpdateTaskRequest = z.infer; diff --git a/src/services/taskService.test.ts b/src/services/taskService.test.ts index 02899b9..fe7ad39 100644 --- a/src/services/taskService.test.ts +++ b/src/services/taskService.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { v4 as uuidv4 } from 'uuid'; -import { PutCommand, ScanCommand, GetCommand } from '@aws-sdk/lib-dynamodb'; -import { CreateTaskRequest } from '@/models/Task.js'; +import { PutCommand, ScanCommand, GetCommand, UpdateCommand } from '@aws-sdk/lib-dynamodb'; +import { CreateTaskRequest, UpdateTaskRequest } from '@/models/Task.js'; // Mock dependencies vi.mock('@aws-sdk/lib-dynamodb', () => ({ @@ -14,6 +14,9 @@ vi.mock('@aws-sdk/lib-dynamodb', () => ({ GetCommand: vi.fn().mockImplementation((params) => ({ ...params, })), + UpdateCommand: vi.fn().mockImplementation((params) => ({ + ...params, + })), })); vi.mock('uuid', () => ({ @@ -317,4 +320,229 @@ describe('TaskService', () => { await expect(taskService.getTaskById(taskId)).rejects.toThrow('DynamoDB Error'); }); }); + + describe('updateTask', () => { + it('should update a task with the provided data', async () => { + // Arrange + const { dynamoDocClient } = await import('@/utils/awsClients.js'); + const { logger } = await import('@/utils/logger.js'); + const { config } = await import('@/utils/config.js'); + + const taskId = 'task123'; + const existingTask = { + id: taskId, + title: 'Original Title', + detail: 'Original Detail', + isComplete: false, + }; + + const updateTaskRequest: UpdateTaskRequest = { + title: 'Updated Title', + isComplete: true, + }; + + const updatedTask = { + id: taskId, + title: 'Updated Title', + detail: 'Original Detail', + isComplete: true, + }; + + // Mock the DynamoDB get response for checking if task exists + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (dynamoDocClient.send as any).mockResolvedValueOnce({ + Item: existingTask, + }); + + // Mock the DynamoDB update response + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (dynamoDocClient.send as any).mockResolvedValueOnce({ + Attributes: updatedTask, + }); + + // Act + const result = await taskService.updateTask(taskId, updateTaskRequest); + + // Assert + // Check that GetCommand was called with the correct parameters + expect(GetCommand).toHaveBeenCalledWith({ + TableName: config.TASKS_TABLE, + Key: { id: taskId }, + }); + + // Check that UpdateCommand was called with the correct parameters + expect(UpdateCommand).toHaveBeenCalledWith({ + TableName: config.TASKS_TABLE, + Key: { id: taskId }, + UpdateExpression: 'SET #title = :title, #isComplete = :isComplete', + ExpressionAttributeNames: { + '#title': 'title', + '#isComplete': 'isComplete', + }, + ExpressionAttributeValues: { + ':title': 'Updated Title', + ':isComplete': true, + }, + ReturnValues: 'ALL_NEW', + }); + + // Check that the document client's send method was called twice (get + update) + expect(dynamoDocClient.send).toHaveBeenCalledTimes(2); + + // Check that the updated task was returned correctly + expect(result).toEqual(updatedTask); + + // Check logging + expect(logger.debug).toHaveBeenCalledWith('Updating task', { + taskId, + updateData: updateTaskRequest, + }); + expect(logger.info).toHaveBeenCalledWith('Task updated successfully', { taskId }); + }); + + it('should return undefined when task does not exist', async () => { + // Arrange + const { dynamoDocClient } = await import('@/utils/awsClients.js'); + const { logger } = await import('@/utils/logger.js'); + + const taskId = 'nonexistent-task'; + const updateTaskRequest: UpdateTaskRequest = { + title: 'Updated Title', + }; + + // Mock the DynamoDB get response with no item + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (dynamoDocClient.send as any).mockResolvedValueOnce({ + Item: undefined, + }); + + // Act + const result = await taskService.updateTask(taskId, updateTaskRequest); + + // Assert + expect(result).toBeUndefined(); + expect(dynamoDocClient.send).toHaveBeenCalledTimes(1); // Only getTaskById called, not update + expect(logger.info).toHaveBeenCalledWith('Task not found for update', { taskId }); + }); + + it('should return existing task when no fields to update', async () => { + // Arrange + const { dynamoDocClient } = await import('@/utils/awsClients.js'); + const { logger } = await import('@/utils/logger.js'); + + const taskId = 'task123'; + const existingTask = { + id: taskId, + title: 'Original Title', + detail: 'Original Detail', + isComplete: false, + }; + + // Empty update request + const updateTaskRequest: UpdateTaskRequest = {}; + + // Mock the DynamoDB get response + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (dynamoDocClient.send as any).mockResolvedValueOnce({ + Item: existingTask, + }); + + // Act + const result = await taskService.updateTask(taskId, updateTaskRequest); + + // Assert + expect(result).toEqual(existingTask); + expect(dynamoDocClient.send).toHaveBeenCalledTimes(1); // Only getTaskById called, not update + expect(logger.info).toHaveBeenCalledWith('No changes to update for task', { taskId }); + }); + + it('should update only specified fields', async () => { + // Arrange + const { dynamoDocClient } = await import('@/utils/awsClients.js'); + const { config } = await import('@/utils/config.js'); + + const taskId = 'task123'; + const existingTask = { + id: taskId, + title: 'Original Title', + detail: 'Original Detail', + isComplete: false, + dueAt: '2023-12-31', + }; + + // Update only detail field + const updateTaskRequest: UpdateTaskRequest = { + detail: 'Updated Detail', + }; + + const updatedTask = { + id: taskId, + title: 'Original Title', + detail: 'Updated Detail', + isComplete: false, + dueAt: '2023-12-31', + }; + + // Mock the DynamoDB get response + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (dynamoDocClient.send as any).mockResolvedValueOnce({ + Item: existingTask, + }); + + // Mock the DynamoDB update response + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (dynamoDocClient.send as any).mockResolvedValueOnce({ + Attributes: updatedTask, + }); + + // Act + const result = await taskService.updateTask(taskId, updateTaskRequest); + + // Assert + // Check that UpdateCommand was called with only the detail field + expect(UpdateCommand).toHaveBeenCalledWith({ + TableName: config.TASKS_TABLE, + Key: { id: taskId }, + UpdateExpression: 'SET #detail = :detail', + ExpressionAttributeNames: { + '#detail': 'detail', + }, + ExpressionAttributeValues: { + ':detail': 'Updated Detail', + }, + ReturnValues: 'ALL_NEW', + }); + + expect(result).toEqual(updatedTask); + }); + + it('should handle DynamoDB errors', async () => { + // Arrange + const { dynamoDocClient } = await import('@/utils/awsClients.js'); + const error = new Error('DynamoDB Error'); + + const taskId = 'task123'; + const existingTask = { + id: taskId, + title: 'Original Title', + isComplete: false, + }; + + const updateTaskRequest: UpdateTaskRequest = { + title: 'Updated Title', + }; + + // Mock the DynamoDB get response + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (dynamoDocClient.send as any).mockResolvedValueOnce({ + Item: existingTask, + }); + + // Mock the DynamoDB update method to throw an error + vi.mocked(dynamoDocClient.send).mockRejectedValueOnce(error); + + // Act & Assert + await expect(taskService.updateTask(taskId, updateTaskRequest)).rejects.toThrow('DynamoDB Error'); + }); + }); }); diff --git a/src/services/taskService.ts b/src/services/taskService.ts index 9fcf0b2..fcb2e83 100644 --- a/src/services/taskService.ts +++ b/src/services/taskService.ts @@ -2,9 +2,9 @@ * The TaskService module provides functionality to operate on tasks in DynamoDB. * It includes methods to handle task creation, validation, and interaction with AWS services. */ -import { PutCommand, ScanCommand, GetCommand } from '@aws-sdk/lib-dynamodb'; +import { PutCommand, ScanCommand, GetCommand, UpdateCommand } from '@aws-sdk/lib-dynamodb'; import { v4 as uuidv4 } from 'uuid'; -import { Task, CreateTaskRequest } from '@/models/Task.js'; +import { Task, CreateTaskRequest, UpdateTaskRequest } from '@/models/Task.js'; import { dynamoDocClient } from '@/utils/awsClients.js'; import { logger } from '@/utils/logger.js'; import { config } from '@/utils/config.js'; @@ -100,9 +100,96 @@ const getTaskById = async (taskId: string): Promise => { return task; }; +/** + * Updates an existing task in DynamoDB + * + * @param taskId The unique identifier of the task to update + * @param updateTaskRequest The task data to update + * @returns The updated task if found, undefined otherwise + */ +const updateTask = async (taskId: string, updateTaskRequest: UpdateTaskRequest): Promise => { + logger.debug('Updating task', { taskId, updateData: updateTaskRequest }); + + // First check if the task exists + const existingTask = await getTaskById(taskId); + if (!existingTask) { + logger.info('Task not found for update', { taskId }); + return undefined; + } + + // Build update expression and attribute values + const updateExpressions: string[] = []; + const expressionAttributeNames: Record = {}; + const expressionAttributeValues: Record = {}; + + // Add each provided field to the update expression + if (updateTaskRequest.title !== undefined) { + updateExpressions.push('#title = :title'); + expressionAttributeNames['#title'] = 'title'; + expressionAttributeValues[':title'] = updateTaskRequest.title; + } + + if (updateTaskRequest.detail !== undefined) { + updateExpressions.push('#detail = :detail'); + expressionAttributeNames['#detail'] = 'detail'; + expressionAttributeValues[':detail'] = updateTaskRequest.detail; + } + + if (updateTaskRequest.isComplete !== undefined) { + updateExpressions.push('#isComplete = :isComplete'); + expressionAttributeNames['#isComplete'] = 'isComplete'; + expressionAttributeValues[':isComplete'] = updateTaskRequest.isComplete; + } + + if (updateTaskRequest.dueAt !== undefined) { + updateExpressions.push('#dueAt = :dueAt'); + expressionAttributeNames['#dueAt'] = 'dueAt'; + expressionAttributeValues[':dueAt'] = updateTaskRequest.dueAt; + } + + // If no fields to update, return the existing task + if (updateExpressions.length === 0) { + logger.info('No changes to update for task', { taskId }); + return existingTask; + } + + // Create the update expression + const updateExpression = `SET ${updateExpressions.join(', ')}`; + + logger.debug('Updating task in DynamoDB', { + tableName: config.TASKS_TABLE, + taskId, + updateExpression, + expressionAttributeNames, + expressionAttributeValues, + }); + + // Update in DynamoDB + const response = await dynamoDocClient.send( + new UpdateCommand({ + TableName: config.TASKS_TABLE, + Key: { id: taskId }, + UpdateExpression: updateExpression, + ExpressionAttributeNames: expressionAttributeNames, + ExpressionAttributeValues: expressionAttributeValues, + ReturnValues: 'ALL_NEW', + }), + ); + + // Get the updated task + const updatedTask = response.Attributes as Task; + + logger.info('Task updated successfully', { taskId }); + logger.debug('Updated task details', { updatedTask }); + + // Return the updated task + return updatedTask; +}; + // Define and export the TaskService with the methods to handle task operations export const TaskService = { createTask, listTasks, getTaskById, + updateTask, }; From 50ad78e42827ff7777cb8d4c4c936c54154095ee Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Tue, 3 Jun 2025 08:41:32 -0400 Subject: [PATCH 12/26] refactor updateTask to handle removal of detail and dueAt fields when not provided --- src/services/taskService.test.ts | 102 +++++++++++++++++++++++++++++-- src/services/taskService.ts | 34 ++++++++--- 2 files changed, 123 insertions(+), 13 deletions(-) diff --git a/src/services/taskService.test.ts b/src/services/taskService.test.ts index fe7ad39..40cd833 100644 --- a/src/services/taskService.test.ts +++ b/src/services/taskService.test.ts @@ -374,10 +374,11 @@ describe('TaskService', () => { expect(UpdateCommand).toHaveBeenCalledWith({ TableName: config.TASKS_TABLE, Key: { id: taskId }, - UpdateExpression: 'SET #title = :title, #isComplete = :isComplete', + UpdateExpression: 'SET #title = :title, #isComplete = :isComplete REMOVE #detail', ExpressionAttributeNames: { '#title': 'title', '#isComplete': 'isComplete', + '#detail': 'detail', }, ExpressionAttributeValues: { ':title': 'Updated Title', @@ -429,6 +430,7 @@ describe('TaskService', () => { // Arrange const { dynamoDocClient } = await import('@/utils/awsClients.js'); const { logger } = await import('@/utils/logger.js'); + const { config } = await import('@/utils/config.js'); const taskId = 'task123'; const existingTask = { @@ -438,6 +440,13 @@ describe('TaskService', () => { isComplete: false, }; + // Result task after detail is removed + const updatedTask = { + id: taskId, + title: 'Original Title', + isComplete: false, + }; + // Empty update request const updateTaskRequest: UpdateTaskRequest = {}; @@ -447,13 +456,32 @@ describe('TaskService', () => { Item: existingTask, }); + // Mock the DynamoDB update response + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (dynamoDocClient.send as any).mockResolvedValueOnce({ + Attributes: updatedTask, + }); + // Act const result = await taskService.updateTask(taskId, updateTaskRequest); // Assert - expect(result).toEqual(existingTask); - expect(dynamoDocClient.send).toHaveBeenCalledTimes(1); // Only getTaskById called, not update - expect(logger.info).toHaveBeenCalledWith('No changes to update for task', { taskId }); + expect(result).toEqual(updatedTask); + expect(dynamoDocClient.send).toHaveBeenCalledTimes(2); // getTaskById and update are called to remove detail + + // Check that UpdateCommand was called with the correct parameters to remove detail + expect(UpdateCommand).toHaveBeenCalledWith({ + TableName: config.TASKS_TABLE, + Key: { id: taskId }, + UpdateExpression: 'REMOVE #detail', + ExpressionAttributeNames: { + '#detail': 'detail', + }, + ExpressionAttributeValues: {}, + ReturnValues: 'ALL_NEW', + }); + + expect(logger.info).toHaveBeenCalledWith('Task updated successfully', { taskId }); }); it('should update only specified fields', async () => { @@ -503,9 +531,10 @@ describe('TaskService', () => { expect(UpdateCommand).toHaveBeenCalledWith({ TableName: config.TASKS_TABLE, Key: { id: taskId }, - UpdateExpression: 'SET #detail = :detail', + UpdateExpression: 'SET #detail = :detail REMOVE #dueAt', ExpressionAttributeNames: { '#detail': 'detail', + '#dueAt': 'dueAt', }, ExpressionAttributeValues: { ':detail': 'Updated Detail', @@ -544,5 +573,68 @@ describe('TaskService', () => { // Act & Assert await expect(taskService.updateTask(taskId, updateTaskRequest)).rejects.toThrow('DynamoDB Error'); }); + + it('should remove detail and dueAt fields when not provided in the request', async () => { + // Arrange + const { dynamoDocClient } = await import('@/utils/awsClients.js'); + const { config } = await import('@/utils/config.js'); + const { logger } = await import('@/utils/logger.js'); + + const taskId = 'task123'; + const existingTask = { + id: taskId, + title: 'Original Title', + detail: 'Original Detail', + isComplete: false, + dueAt: '2023-12-31', + }; + + // Update request without detail or dueAt fields + const updateTaskRequest: UpdateTaskRequest = { + title: 'Updated Title', + }; + + // Mock result without detail and dueAt + const updatedTask = { + id: taskId, + title: 'Updated Title', + isComplete: false, + }; + + // Mock the DynamoDB get response + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (dynamoDocClient.send as any).mockResolvedValueOnce({ + Item: existingTask, + }); + + // Mock the DynamoDB update response + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (dynamoDocClient.send as any).mockResolvedValueOnce({ + Attributes: updatedTask, + }); + + // Act + const result = await taskService.updateTask(taskId, updateTaskRequest); + + // Assert + // Check that UpdateCommand was called with correct parameters + expect(UpdateCommand).toHaveBeenCalledWith({ + TableName: config.TASKS_TABLE, + Key: { id: taskId }, + UpdateExpression: 'SET #title = :title REMOVE #detail, #dueAt', + ExpressionAttributeNames: { + '#title': 'title', + '#detail': 'detail', + '#dueAt': 'dueAt', + }, + ExpressionAttributeValues: { + ':title': 'Updated Title', + }, + ReturnValues: 'ALL_NEW', + }); + + expect(result).toEqual(updatedTask); + expect(logger.info).toHaveBeenCalledWith('Task updated successfully', { taskId }); + }); }); }); diff --git a/src/services/taskService.ts b/src/services/taskService.ts index fcb2e83..230181e 100644 --- a/src/services/taskService.ts +++ b/src/services/taskService.ts @@ -118,43 +118,61 @@ const updateTask = async (taskId: string, updateTaskRequest: UpdateTaskRequest): } // Build update expression and attribute values - const updateExpressions: string[] = []; + const setExpressions: string[] = []; + const removeExpressions: string[] = []; const expressionAttributeNames: Record = {}; const expressionAttributeValues: Record = {}; // Add each provided field to the update expression if (updateTaskRequest.title !== undefined) { - updateExpressions.push('#title = :title'); + setExpressions.push('#title = :title'); expressionAttributeNames['#title'] = 'title'; expressionAttributeValues[':title'] = updateTaskRequest.title; } + // Handle detail attribute - remove if not provided if (updateTaskRequest.detail !== undefined) { - updateExpressions.push('#detail = :detail'); + setExpressions.push('#detail = :detail'); expressionAttributeNames['#detail'] = 'detail'; expressionAttributeValues[':detail'] = updateTaskRequest.detail; + } else if (existingTask.detail !== undefined) { + removeExpressions.push('#detail'); + expressionAttributeNames['#detail'] = 'detail'; } if (updateTaskRequest.isComplete !== undefined) { - updateExpressions.push('#isComplete = :isComplete'); + setExpressions.push('#isComplete = :isComplete'); expressionAttributeNames['#isComplete'] = 'isComplete'; expressionAttributeValues[':isComplete'] = updateTaskRequest.isComplete; } + // Handle dueAt attribute - remove if not provided if (updateTaskRequest.dueAt !== undefined) { - updateExpressions.push('#dueAt = :dueAt'); + setExpressions.push('#dueAt = :dueAt'); expressionAttributeNames['#dueAt'] = 'dueAt'; expressionAttributeValues[':dueAt'] = updateTaskRequest.dueAt; + } else if (existingTask.dueAt !== undefined) { + removeExpressions.push('#dueAt'); + expressionAttributeNames['#dueAt'] = 'dueAt'; } - // If no fields to update, return the existing task - if (updateExpressions.length === 0) { + // If no fields to update or remove, return the existing task + if (setExpressions.length === 0 && removeExpressions.length === 0) { logger.info('No changes to update for task', { taskId }); return existingTask; } // Create the update expression - const updateExpression = `SET ${updateExpressions.join(', ')}`; + let updateExpression = ''; + + if (setExpressions.length > 0) { + updateExpression += `SET ${setExpressions.join(', ')}`; + } + + if (removeExpressions.length > 0) { + updateExpression += updateExpression ? ' REMOVE ' : 'REMOVE '; + updateExpression += removeExpressions.join(', '); + } logger.debug('Updating task in DynamoDB', { tableName: config.TASKS_TABLE, From ac54b373eca0f9856cbf1c0866ddcfe241ea75da Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Tue, 3 Jun 2025 09:36:09 -0400 Subject: [PATCH 13/26] implement delete task functionality with Lambda handler, service method, and tests --- infrastructure/stacks/ApiStack.ts | 27 ++++++++ src/handlers/deleteTask.test.ts | 100 ++++++++++++++++++++++++++++++ src/handlers/deleteTask.ts | 64 +++++++++++++++++++ src/services/taskService.test.ts | 71 ++++++++++++++++++++- src/services/taskService.ts | 31 ++++++++- 5 files changed, 291 insertions(+), 2 deletions(-) create mode 100644 src/handlers/deleteTask.test.ts create mode 100644 src/handlers/deleteTask.ts diff --git a/infrastructure/stacks/ApiStack.ts b/infrastructure/stacks/ApiStack.ts index ffb4056..c1752c0 100644 --- a/infrastructure/stacks/ApiStack.ts +++ b/infrastructure/stacks/ApiStack.ts @@ -110,11 +110,35 @@ export class ApiStack extends cdk.Stack { timeout: cdk.Duration.seconds(6), }); + // Create and configure CloudWatch Log Group for the delete task Lambda function + const deleteTaskFunctionLogGroup = new logs.LogGroup(this, 'DeleteTaskFunctionLogGroup', { + retention: logs.RetentionDays.ONE_WEEK, + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Create Lambda function for deleting a task by ID + const deleteTaskFunction = new lambdaNodejs.NodejsFunction(this, 'DeleteTaskFunction', { + runtime: lambda.Runtime.NODEJS_22_X, + entry: path.join(__dirname, '../../src/handlers/deleteTask.ts'), + handler: 'deleteTask', + environment: { + TASKS_TABLE: tasksTable.tableName, + }, + bundling: { + minify: true, + sourceMap: true, + }, + logGroup: deleteTaskFunctionLogGroup, + memorySize: 1024, + timeout: cdk.Duration.seconds(6), + }); + // Grant the Lambda functions permissions to access the DynamoDB table tasksTable.grantWriteData(createTaskFunction); tasksTable.grantReadData(listTasksFunction); tasksTable.grantReadData(getTaskFunction); tasksTable.grantReadWriteData(updateTaskFunction); + tasksTable.grantReadWriteData(deleteTaskFunction); // Create API Gateway const api = new apigateway.RestApi(this, 'TasksApi', { @@ -143,6 +167,9 @@ export class ApiStack extends cdk.Stack { // Add a PUT method to update a task by ID taskResource.addMethod('PUT', new apigateway.LambdaIntegration(updateTaskFunction)); + // Add a DELETE method to delete a task by ID + taskResource.addMethod('DELETE', new apigateway.LambdaIntegration(deleteTaskFunction)); + // Output the API URL new cdk.CfnOutput(this, 'ApiUrl', { value: api.url, diff --git a/src/handlers/deleteTask.test.ts b/src/handlers/deleteTask.test.ts new file mode 100644 index 0000000..3e4c42f --- /dev/null +++ b/src/handlers/deleteTask.test.ts @@ -0,0 +1,100 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { APIGatewayProxyEvent } from 'aws-lambda'; +import { deleteTask } from './deleteTask.js'; +import { TaskService } from '@/services/taskService.js'; + +// Mock dependencies +vi.mock('@/services/taskService.js', () => ({ + TaskService: { + deleteTask: vi.fn(), + }, +})); + +vi.mock('@/utils/logger.js', () => ({ + logger: { + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }, +})); + +describe('deleteTask Lambda handler', () => { + // Valid mock API Gateway event + const validEvent = { + pathParameters: { + taskId: 'task123', + }, + requestContext: { + requestId: 'test-request-id', + }, + } as unknown as APIGatewayProxyEvent; + + // Invalid event (missing taskId) + const invalidEvent = { + pathParameters: {}, + requestContext: { + requestId: 'test-request-id', + }, + } as unknown as APIGatewayProxyEvent; + + // Reset mocks between tests + beforeEach(() => { + vi.resetAllMocks(); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should return 204 when task is deleted successfully', async () => { + // Arrange + vi.mocked(TaskService.deleteTask).mockResolvedValue(true); + + // Act + const response = await deleteTask(validEvent); + + // Assert + expect(response.statusCode).toBe(204); + expect(response.body).toBe('{}'); + expect(TaskService.deleteTask).toHaveBeenCalledWith('task123'); + }); + + it('should return 404 when task is not found', async () => { + // Arrange + vi.mocked(TaskService.deleteTask).mockResolvedValue(false); + + // Act + const response = await deleteTask(validEvent); + + // Assert + expect(response.statusCode).toBe(404); + expect(JSON.parse(response.body)).toEqual({ message: 'Task with ID task123 not found' }); + expect(TaskService.deleteTask).toHaveBeenCalledWith('task123'); + }); + + it('should return 400 when taskId is not provided', async () => { + // Act + const response = await deleteTask(invalidEvent); + + // Assert + expect(response.statusCode).toBe(400); + expect(JSON.parse(response.body)).toEqual({ message: 'Invalid request: taskId is required' }); + expect(TaskService.deleteTask).not.toHaveBeenCalled(); + }); + + it('should return 500 when an unexpected error occurs', async () => { + // Arrange + vi.mocked(TaskService.deleteTask).mockRejectedValue(new Error('Test error')); + + // Act + const response = await deleteTask(validEvent); + + // Assert + expect(response.statusCode).toBe(500); + expect(JSON.parse(response.body)).toEqual({ + message: 'An unexpected error occurred while deleting the task', + }); + expect(TaskService.deleteTask).toHaveBeenCalledWith('task123'); + }); +}); diff --git a/src/handlers/deleteTask.ts b/src/handlers/deleteTask.ts new file mode 100644 index 0000000..9eeadb9 --- /dev/null +++ b/src/handlers/deleteTask.ts @@ -0,0 +1,64 @@ +/** + * Lambda Handler: deleteTask + * + * Deletes a task by its ID from DynamoDB. + * - Parses taskId from event.pathParameters + * - Calls taskService.deleteTask(taskId) + * - Returns 204 No Content if successful, 404 if not found + * - Validates input; returns 400 if invalid + * - Catches unexpected errors; logs and returns 500 + */ +import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; +import { z } from 'zod'; +import { TaskService } from '@/services/taskService.js'; +import { badRequest, internalServerError, notFound, noContent } from '@/utils/response.js'; +import { logger } from '@/utils/logger.js'; + +// Zod schema for request validation +const requestSchema = z.object({ + pathParameters: z.object({ + taskId: z.string().min(1, 'taskId path variable is required'), + }), +}); + +// Type definition derived from the schema +type Request = z.infer; + +/** + * Lambda handler function to delete a task by ID + * + * @param event The API Gateway event containing the request + * @returns API Gateway response with 204 No Content, or error + */ +export const deleteTask = async (event: APIGatewayProxyEvent): Promise => { + const requestId = event.requestContext?.requestId; + logger.info('Processing delete task request', { requestId }); + logger.debug('Received event', { requestId, event }); + + // Validate input + const result = requestSchema.safeParse(event); + if (!result.success) { + logger.warn('Invalid request', { requestId, errors: result.error.errors }); + return badRequest('Invalid request: taskId is required'); + } + + // Extract validated data + const request: Request = result.data; + const { taskId } = request.pathParameters; + + try { + // Call service to delete task + const deleted = await TaskService.deleteTask(taskId); + + if (!deleted) { + logger.info('Task not found for deletion', { requestId, taskId }); + return notFound(`Task with ID ${taskId} not found`); + } + + logger.info('Task deleted successfully', { requestId, taskId }); + return noContent(); + } catch (err) { + logger.error('Failed to delete task', err as Error, { requestId, taskId }); + return internalServerError('An unexpected error occurred while deleting the task'); + } +}; diff --git a/src/services/taskService.test.ts b/src/services/taskService.test.ts index 40cd833..077892c 100644 --- a/src/services/taskService.test.ts +++ b/src/services/taskService.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { v4 as uuidv4 } from 'uuid'; -import { PutCommand, ScanCommand, GetCommand, UpdateCommand } from '@aws-sdk/lib-dynamodb'; +import { PutCommand, ScanCommand, GetCommand, UpdateCommand, DeleteCommand } from '@aws-sdk/lib-dynamodb'; import { CreateTaskRequest, UpdateTaskRequest } from '@/models/Task.js'; // Mock dependencies @@ -17,6 +17,9 @@ vi.mock('@aws-sdk/lib-dynamodb', () => ({ UpdateCommand: vi.fn().mockImplementation((params) => ({ ...params, })), + DeleteCommand: vi.fn().mockImplementation((params) => ({ + ...params, + })), })); vi.mock('uuid', () => ({ @@ -637,4 +640,70 @@ describe('TaskService', () => { expect(logger.info).toHaveBeenCalledWith('Task updated successfully', { taskId }); }); }); + + describe('deleteTask', () => { + it('should delete a task when it exists', async () => { + // Arrange + const { dynamoDocClient } = await import('@/utils/awsClients.js'); + const { logger } = await import('@/utils/logger.js'); + const { config } = await import('@/utils/config.js'); + + const taskId = 'test-task-id'; + const existingTask = { + id: taskId, + title: 'Test Task', + isComplete: false, + }; + + // Mock the task existence check + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (dynamoDocClient.send as any).mockResolvedValueOnce({ + Item: existingTask, + }); + + // Mock the delete operation + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (dynamoDocClient.send as any).mockResolvedValueOnce({}); + + // Act + const result = await taskService.deleteTask(taskId); + + // Assert + expect(GetCommand).toHaveBeenCalledWith({ + TableName: config.TASKS_TABLE, + Key: { id: taskId }, + }); + + expect(DeleteCommand).toHaveBeenCalledWith({ + TableName: config.TASKS_TABLE, + Key: { id: taskId }, + }); + + expect(result).toBe(true); + expect(logger.info).toHaveBeenCalledWith('Task deleted successfully', { taskId }); + }); + + it('should return false when the task does not exist', async () => { + // Arrange + const { dynamoDocClient } = await import('@/utils/awsClients.js'); + const { logger } = await import('@/utils/logger.js'); + + const taskId = 'non-existent-task-id'; + + // Mock the DynamoDB GetCommand to return undefined (task not found) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (dynamoDocClient.send as any).mockResolvedValueOnce({ + Item: undefined, + }); + + // Act + const result = await taskService.deleteTask(taskId); + + // Assert + expect(result).toBe(false); + expect(logger.info).toHaveBeenCalledWith('Task not found for deletion', { taskId }); + // Delete command should not be called if the task doesn't exist + expect(dynamoDocClient.send).toHaveBeenCalledTimes(1); // Only the GetCommand should be called + }); + }); }); diff --git a/src/services/taskService.ts b/src/services/taskService.ts index 230181e..9326630 100644 --- a/src/services/taskService.ts +++ b/src/services/taskService.ts @@ -2,7 +2,7 @@ * The TaskService module provides functionality to operate on tasks in DynamoDB. * It includes methods to handle task creation, validation, and interaction with AWS services. */ -import { PutCommand, ScanCommand, GetCommand, UpdateCommand } from '@aws-sdk/lib-dynamodb'; +import { PutCommand, ScanCommand, GetCommand, UpdateCommand, DeleteCommand } from '@aws-sdk/lib-dynamodb'; import { v4 as uuidv4 } from 'uuid'; import { Task, CreateTaskRequest, UpdateTaskRequest } from '@/models/Task.js'; import { dynamoDocClient } from '@/utils/awsClients.js'; @@ -204,10 +204,39 @@ const updateTask = async (taskId: string, updateTaskRequest: UpdateTaskRequest): return updatedTask; }; +/** + * Deletes a task by its ID from DynamoDB + * + * @param taskId The unique identifier of the task to delete + * @returns True if the task was deleted, false if not found + */ +const deleteTask = async (taskId: string): Promise => { + logger.debug('Deleting task', { taskId }); + + // First check if the task exists + const existingTask = await getTaskById(taskId); + if (!existingTask) { + logger.info('Task not found for deletion', { taskId }); + return false; + } + + // Delete from DynamoDB using the DeleteCommand + await dynamoDocClient.send( + new DeleteCommand({ + TableName: config.TASKS_TABLE, + Key: { id: taskId }, + }), + ); + + logger.info('Task deleted successfully', { taskId }); + return true; +}; + // Define and export the TaskService with the methods to handle task operations export const TaskService = { createTask, listTasks, getTaskById, updateTask, + deleteTask, }; From 3fd9908d9d85e6e4d5e6628d470e97ef0713bef4 Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Tue, 3 Jun 2025 09:41:11 -0400 Subject: [PATCH 14/26] add CI workflow requirements for GitHub Actions to automate testing and validation --- .../requirements/06-continuous-integration.md | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 docs/requirements/06-continuous-integration.md diff --git a/docs/requirements/06-continuous-integration.md b/docs/requirements/06-continuous-integration.md new file mode 100644 index 0000000..8c0e409 --- /dev/null +++ b/docs/requirements/06-continuous-integration.md @@ -0,0 +1,54 @@ +# Requirement: Implement Continuous Integration with GitHub Actions + +This document describes the requirements for implementing a Continuous Integration (CI) workflow using GitHub Actions for the AWS Lambda REST API project. + +--- + +## Description + +Create a GitHub Actions workflow to automate the testing, building, and validation of the AWS Lambda REST API project. The CI workflow should run automatically when code changes are pushed to the repository or when pull requests are created or updated. The workflow should ensure that all code meets the project's quality standards before it can be deployed to any environment. + +The CI workflow should include multiple jobs that run in parallel where possible, to validate different aspects of the codebase. The workflow should check out the source code, set up the necessary runtime environment (Node.js 22+), install dependencies, and execute various validation steps. + +The CI workflow should include steps to validate code formatting with Prettier, check code quality with ESLint, run unit tests with Vitest to ensure code functionality, and perform a CDK synthesis to validate that the infrastructure code can be properly synthesized without errors. + +The workflow should provide clear feedback on any failures, including detailed error messages and logs to help developers quickly identify and fix issues. All jobs in the workflow must pass successfully for the CI process to be considered successful. + +Create the workflow definition in the `.github/workflows` directory using YAML format, following GitHub Actions best practices and conventions. The workflow should be configured to run on the main branch and all feature branches, as well as on pull requests targeting the main branch. + +--- + +## CI Workflow Requirements + +The GitHub Actions workflow should include the following jobs: + +1. **Lint and Format Check**: + +- Verify code formatting using Prettier +- Run ESLint to check for code quality issues and potential bugs +- Fail the workflow if any formatting or linting errors are found + +2. **Unit Tests**: + +- Set up the Node.js environment +- Install all project dependencies +- Run all unit tests using Vitest +- Collect and report test coverage metrics +- Fail the workflow if any tests fail or if coverage falls below an acceptable threshold + +3. **Infrastructure Validation**: + +- Set up the Node.js environment +- Install all project dependencies +- Run CDK synthesis (`cdk synth`) to validate the infrastructure code +- Fail the workflow if the CDK synthesis produces any errors + +4. **Dependency Checks** (optional): + +- Check for outdated or vulnerable dependencies +- Generate a report of dependency issues +- Consider using tools like npm audit or Dependabot + +The workflow should be designed to be efficient, running jobs in parallel where possible to minimize the overall execution time. Each job should provide clear, actionable feedback to developers when issues are detected. + +Implement these requirements step by step, following all best practices for GitHub Actions workflows and ensuring proper configuration for this specific project. From 5810c0b63bd534d4a20523329c7f40387bea1a08 Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Tue, 3 Jun 2025 09:58:59 -0400 Subject: [PATCH 15/26] add CI workflow for AWS Lambda REST API with linting, testing, and dependency checks --- .github/workflows/ci.yml | 151 +++++++++++++++++++++++++++++++++++++++ vitest.config.ts | 6 ++ 2 files changed, 157 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..90e291f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,151 @@ +# AWS Lambda REST API CI Workflow +# This workflow runs the following checks: +# - Code formatting and linting +# - Unit tests with coverage +# - Infrastructure validation +# - Dependency checks +name: Continuous Integration + +on: + pull_request: + branches: + - main + +jobs: + lint-format: + name: Lint and Format Check + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install Dependencies + run: npm ci + + - name: Check Format with Prettier + run: npx prettier --check . + + - name: Lint with ESLint + run: npm run lint + + test: + name: Unit Tests + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install Dependencies + run: npm ci + + - name: Run Unit Tests with Coverage + run: npm run test:coverage + + - name: Upload Test Results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results + path: coverage/ + + cdk-validation: + name: Infrastructure Validation + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install Dependencies + run: npm ci + + - name: CDK Synthesis + run: npx cdk synth + + dependency-check: + name: Dependency Checks + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install Dependencies + run: npm ci + + - name: Audit Dependencies + run: npm audit --production + continue-on-error: true + + - name: Check Outdated Dependencies + run: npm outdated + continue-on-error: true + + - name: Generate Dependency Report + run: | + echo "## Dependency Audit Summary" > dependency-report.md + echo "Report generated on $(date)" >> dependency-report.md + echo "### Security Audit" >> dependency-report.md + npm audit --json | jq 'if has("vulnerabilities") then .vulnerabilities else {} end' | jq 'length' | xargs -I {} echo "* Found {} vulnerabilities" >> dependency-report.md + echo "### Outdated Packages" >> dependency-report.md + npm outdated --json | jq 'length' | xargs -I {} echo "* Found {} outdated packages" >> dependency-report.md + continue-on-error: true + + - name: Upload Dependency Report + uses: actions/upload-artifact@v4 + with: + name: dependency-report + path: dependency-report.md + continue-on-error: true + + workflow-summary: + name: Workflow Summary + needs: [lint-format, test, cdk-validation, dependency-check] + runs-on: ubuntu-latest + if: always() + steps: + - name: Create Summary + run: | + echo "## CI Workflow Summary" >> $GITHUB_STEP_SUMMARY + echo "| Job | Status |" >> $GITHUB_STEP_SUMMARY + echo "| --- | ------ |" >> $GITHUB_STEP_SUMMARY + echo "| Lint and Format | ${{ needs.lint-format.result == 'success' && '✅ Passed' || '❌ Failed' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Unit Tests | ${{ needs.test.result == 'success' && '✅ Passed' || '❌ Failed' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Infrastructure | ${{ needs.cdk-validation.result == 'success' && '✅ Passed' || '❌ Failed' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Dependencies | ${{ needs.dependency-check.result == 'success' && '✅ Passed' || '⚠️ Warnings' }} |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [[ "${{ needs.lint-format.result }}" == "success" && "${{ needs.test.result }}" == "success" && "${{ needs.cdk-validation.result }}" == "success" ]]; then + echo "Overall Status: ✅ All Required Checks Passed" >> $GITHUB_STEP_SUMMARY + else + echo "Overall Status: ❌ Some Checks Failed" >> $GITHUB_STEP_SUMMARY + fi + + - name: Fail Workflow if Required Jobs Failed + if: >- + needs.lint-format.result != 'success' || + needs.test.result != 'success' || + needs.cdk-validation.result != 'success' + run: exit 1 diff --git a/vitest.config.ts b/vitest.config.ts index 2440359..1c0c934 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -12,6 +12,12 @@ export default defineConfig({ reporter: ['text', 'html'], include: ['src/**/*.ts'], exclude: ['**/*.test.ts', 'src/models/**'], + thresholds: { + statements: 80, + branches: 80, + functions: 80, + lines: 80, + }, }, alias: { '@': path.resolve(__dirname, 'src'), From 514d38a1b6981713d4d940f02ca566f76b51fe7a Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Tue, 3 Jun 2025 10:36:09 -0400 Subject: [PATCH 16/26] add support for manual triggering of CI workflow --- .github/workflows/ci.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 90e291f..3d16cb1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,13 @@ name: Continuous Integration on: + workflow_dispatch: + push: + branches: + - main + - 'releases/*' + - 'hotfix/*' + - 'feature/*' pull_request: branches: - main From 08ef84488734adc3474902c76a346b0e10cc9c70 Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Tue, 3 Jun 2025 10:45:40 -0400 Subject: [PATCH 17/26] mark several packages as development dependencies in package-lock.json --- package-lock.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/package-lock.json b/package-lock.json index d3f01be..0899807 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3521,6 +3521,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, "license": "MIT" }, "node_modules/bowser": { @@ -3533,6 +3534,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -4314,6 +4316,7 @@ "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -4644,6 +4647,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -4916,6 +4920,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5051,6 +5056,7 @@ "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" From 6e23b895cc16a3136904546a8d5caacf63f7fd91 Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Fri, 6 Jun 2025 13:32:28 -0400 Subject: [PATCH 18/26] add CORS header integration requirements for Task Service API --- docs/requirements/07-cors-header.md | 82 +++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 docs/requirements/07-cors-header.md diff --git a/docs/requirements/07-cors-header.md b/docs/requirements/07-cors-header.md new file mode 100644 index 0000000..d1064f1 --- /dev/null +++ b/docs/requirements/07-cors-header.md @@ -0,0 +1,82 @@ +# CORS Origin Header Integration for Task Service + +## Overview + +This document outlines the requirements for implementing CORS (Cross-Origin Resource Sharing) headers in the Task Service API responses. The implementation will allow the API to be accessible from web applications hosted on different domains. + +## Requirements + +### 1. CORS Header Implementation + +The Task Service API must include the `Access-Control-Allow-Origin` header in all HTTP responses with the following specifications: + +- The header must be present in all API responses regardless of HTTP method +- The header value will be configurable via an environment variable +- If no value is provided, the default value will be `"*"` (allow all origins) + +### 2. Environment Variable Configuration + +An environment variable will be created to control the CORS origin header value: + +| Variable Name | Description | Default Value | Required | +| ------------------- | ------------------------------------------------ | ------------- | -------- | +| `CORS_ALLOW_ORIGIN` | Value for the Access-Control-Allow-Origin header | `"*"` | No | + +### 3. Implementation Details + +#### Response Utility Update + +The response utility module (`/src/utils/response.ts`) should be updated to include the CORS header in all response objects: + +```typescript +// Example implementation +export const ok = (body: any): APIGatewayProxyResult => { + const corsOrigin = process.env.CORS_ALLOW_ORIGIN || '*'; + + return { + statusCode: 200, + headers: { + 'Access-Control-Allow-Origin': corsOrigin, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }; +}; + +// Other response helpers (notFound, badRequest, etc.) should be updated similarly +``` + +#### CDK Infrastructure Update + +The CDK stack should be updated to include the environment variable in all Lambda function configurations: + +```typescript +// Example implementation in ApiStack.ts +const getTaskFunction = new NodejsFunction(this, 'GetTaskFunction', { + entry: '../src/handlers/getTask.ts', + handler: 'getTask', + environment: { + TASKS_TABLE: tasksTable.tableName, + CORS_ALLOW_ORIGIN: this.node.tryGetContext('corsAllowOrigin') || '*', + }, +}); +``` + +### 4. Testing Requirements + +- Unit tests should verify that all response helper functions include the CORS header +- Tests should verify that the environment variable is correctly used when set +- Tests should verify that the default value (`"*"`) is used when the environment variable is not set + +## Acceptance Criteria + +1. All API responses include the `Access-Control-Allow-Origin` header +2. The header value matches the `CORS_ALLOW_ORIGIN` environment variable when set +3. The header value defaults to `"*"` when the environment variable is not set +4. Unit tests verify the CORS header implementation +5. Documentation is updated to reflect the new environment variable + +## References + +- [MDN Web Docs: CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) +- [AWS Lambda Proxy Integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html) From 354cedff85b71c364af316ed8029fa4dd5e294ce Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Fri, 6 Jun 2025 14:24:50 -0400 Subject: [PATCH 19/26] add CORS support to API responses and configuration --- docs/configuration.md | 11 ++++++----- infrastructure/app.ts | 1 + infrastructure/stacks/ApiStack.ts | 12 +++++++++++- infrastructure/utils/config.ts | 1 + src/handlers/createTask.test.ts | 7 +++++++ src/handlers/deleteTask.test.ts | 7 +++++++ src/handlers/getTask.test.ts | 7 +++++++ src/handlers/listTasks.test.ts | 7 +++++++ src/handlers/updateTask.test.ts | 7 +++++++ src/utils/config.ts | 3 +++ src/utils/response.test.ts | 32 +++++++++++++++++++++++-------- src/utils/response.ts | 3 +++ 12 files changed, 84 insertions(+), 14 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index b2e7729..9d6ee17 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -27,11 +27,12 @@ The following environment variables are required: The following environment variables are optional and have default values: -| Variable | Default | Description | -| -------------- | --------- | ------------------------------------------------------------------------- | -| AWS_REGION | us-east-1 | The AWS region to use for AWS SDK clients | -| ENABLE_LOGGING | true | Indicates if logging is enabled | -| LOG_LEVEL | info | The level of logging statements emitted. One of: debug, info, warn, error | +| Variable | Default | Description | +| ----------------- | --------- | ------------------------------------------------------------------------- | +| AWS_REGION | us-east-1 | The AWS region to use for AWS SDK clients | +| ENABLE_LOGGING | true | Indicates if logging is enabled | +| LOG_LEVEL | info | The level of logging statements emitted. One of: debug, info, warn, error | +| CORS_ALLOW_ORIGIN | \* | Value for the Access-Control-Allow-Origin header in API responses | ## Usage diff --git a/infrastructure/app.ts b/infrastructure/app.ts index 2589d51..45b75b7 100644 --- a/infrastructure/app.ts +++ b/infrastructure/app.ts @@ -23,4 +23,5 @@ new ApiStack(app, 'TaskServiceApiStack', { account: cdkConfig.CDK_DEFAULT_ACCOUNT, region: cdkConfig.CDK_DEFAULT_REGION, }, + corsAllowOrigin: cdkConfig.CORS_ALLOW_ORIGIN, }); diff --git a/infrastructure/stacks/ApiStack.ts b/infrastructure/stacks/ApiStack.ts index c1752c0..0e80cd2 100644 --- a/infrastructure/stacks/ApiStack.ts +++ b/infrastructure/stacks/ApiStack.ts @@ -7,8 +7,13 @@ import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import * as logs from 'aws-cdk-lib/aws-logs'; import * as path from 'path'; +// Define custom stack props interface to include CORS configuration +export interface ApiStackProps extends cdk.StackProps { + corsAllowOrigin?: string; +} + export class ApiStack extends cdk.Stack { - constructor(scope: Construct, id: string, props?: cdk.StackProps) { + constructor(scope: Construct, id: string, props?: ApiStackProps) { super(scope, id, props); // Create DynamoDB table for tasks @@ -31,6 +36,7 @@ export class ApiStack extends cdk.Stack { handler: 'createTask', environment: { TASKS_TABLE: tasksTable.tableName, + CORS_ALLOW_ORIGIN: props?.corsAllowOrigin || '*', }, bundling: { minify: true, @@ -54,6 +60,7 @@ export class ApiStack extends cdk.Stack { handler: 'listTasks', environment: { TASKS_TABLE: tasksTable.tableName, + CORS_ALLOW_ORIGIN: props?.corsAllowOrigin || '*', }, bundling: { minify: true, @@ -77,6 +84,7 @@ export class ApiStack extends cdk.Stack { handler: 'getTask', environment: { TASKS_TABLE: tasksTable.tableName, + CORS_ALLOW_ORIGIN: props?.corsAllowOrigin || '*', }, bundling: { minify: true, @@ -100,6 +108,7 @@ export class ApiStack extends cdk.Stack { handler: 'updateTask', environment: { TASKS_TABLE: tasksTable.tableName, + CORS_ALLOW_ORIGIN: props?.corsAllowOrigin || '*', }, bundling: { minify: true, @@ -123,6 +132,7 @@ export class ApiStack extends cdk.Stack { handler: 'deleteTask', environment: { TASKS_TABLE: tasksTable.tableName, + CORS_ALLOW_ORIGIN: props?.corsAllowOrigin || '*', }, bundling: { minify: true, diff --git a/infrastructure/utils/config.ts b/infrastructure/utils/config.ts index 56eec61..60f36b6 100644 --- a/infrastructure/utils/config.ts +++ b/infrastructure/utils/config.ts @@ -10,6 +10,7 @@ const cdkEnvSchema = z.object({ // Optional variables with defaults CDK_DEFAULT_REGION: z.string().default('us-east-1'), ENV: z.enum(['dev', 'qa', 'prod']).default('dev'), + CORS_ALLOW_ORIGIN: z.string().default('*'), }); /** diff --git a/src/handlers/createTask.test.ts b/src/handlers/createTask.test.ts index 7c04076..550fe82 100644 --- a/src/handlers/createTask.test.ts +++ b/src/handlers/createTask.test.ts @@ -3,6 +3,13 @@ import { APIGatewayProxyEvent } from 'aws-lambda'; import { CreateTaskRequest } from '@/models/Task'; import { createTask } from './createTask'; +// Mock the config and logger +vi.mock('../utils/config.js', () => ({ + config: { + TASKS_TABLE: 'mock-tasks-table', + }, +})); + // Mock dependencies vi.mock('../services/taskService.js', () => ({ TaskService: { diff --git a/src/handlers/deleteTask.test.ts b/src/handlers/deleteTask.test.ts index 3e4c42f..18a10aa 100644 --- a/src/handlers/deleteTask.test.ts +++ b/src/handlers/deleteTask.test.ts @@ -3,6 +3,13 @@ import { APIGatewayProxyEvent } from 'aws-lambda'; import { deleteTask } from './deleteTask.js'; import { TaskService } from '@/services/taskService.js'; +// Mock the config and logger +vi.mock('../utils/config.js', () => ({ + config: { + TASKS_TABLE: 'mock-tasks-table', + }, +})); + // Mock dependencies vi.mock('@/services/taskService.js', () => ({ TaskService: { diff --git a/src/handlers/getTask.test.ts b/src/handlers/getTask.test.ts index eea3582..6494e7d 100644 --- a/src/handlers/getTask.test.ts +++ b/src/handlers/getTask.test.ts @@ -2,6 +2,13 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { APIGatewayProxyEvent } from 'aws-lambda'; import { getTask } from './getTask.js'; +// Mock the config and logger +vi.mock('../utils/config.js', () => ({ + config: { + TASKS_TABLE: 'mock-tasks-table', + }, +})); + // Mock dependencies vi.mock('@/services/taskService.js', () => ({ TaskService: { diff --git a/src/handlers/listTasks.test.ts b/src/handlers/listTasks.test.ts index 7cf05b3..7a589c8 100644 --- a/src/handlers/listTasks.test.ts +++ b/src/handlers/listTasks.test.ts @@ -2,6 +2,13 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { APIGatewayProxyEvent } from 'aws-lambda'; import { listTasks } from './listTasks'; +// Mock the config and logger +vi.mock('../utils/config.js', () => ({ + config: { + TASKS_TABLE: 'mock-tasks-table', + }, +})); + // Mock dependencies vi.mock('../services/taskService.js', () => ({ TaskService: { diff --git a/src/handlers/updateTask.test.ts b/src/handlers/updateTask.test.ts index 6fde357..52e6c60 100644 --- a/src/handlers/updateTask.test.ts +++ b/src/handlers/updateTask.test.ts @@ -2,6 +2,13 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { APIGatewayProxyEvent } from 'aws-lambda'; import { updateTask } from './updateTask.js'; +// Mock the config and logger +vi.mock('../utils/config.js', () => ({ + config: { + TASKS_TABLE: 'mock-tasks-table', + }, +})); + // Mock dependencies vi.mock('@/services/taskService.js', () => ({ TaskService: { diff --git a/src/utils/config.ts b/src/utils/config.ts index 97a063b..ca32e88 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -17,6 +17,9 @@ const envSchema = z.object({ .default('true'), LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('debug'), + // CORS configuration + CORS_ALLOW_ORIGIN: z.string().default('*'), + // Add more environment variables as needed }); diff --git a/src/utils/response.test.ts b/src/utils/response.test.ts index 391a89d..bb20649 100644 --- a/src/utils/response.test.ts +++ b/src/utils/response.test.ts @@ -1,6 +1,13 @@ -import { describe, it, expect } from 'vitest'; +import { describe, it, expect, vi } from 'vitest'; import { createResponse, ok, created, noContent, badRequest, notFound, internalServerError } from './response'; +// Mock the config module +vi.mock('./config', () => ({ + config: { + CORS_ALLOW_ORIGIN: '*', + }, +})); + describe('Response Utils', () => { describe('createResponse', () => { it('should create a response with the given status code and body', () => { @@ -14,12 +21,15 @@ describe('Response Utils', () => { // Assert expect(response.statusCode).toBe(200); expect(response.body).toBe(JSON.stringify(body)); - expect(response.headers).toEqual({ 'Content-Type': 'application/json' }); + expect(response.headers).toEqual({ + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }); }); }); describe('helper functions', () => { - it('should create a 200 OK response', () => { + it('should create a 200 OK response with CORS headers', () => { // Arrange const body = { data: 'test' }; @@ -29,9 +39,10 @@ describe('Response Utils', () => { // Assert expect(response.statusCode).toBe(200); expect(response.body).toBe(JSON.stringify(body)); + expect(response.headers!['Access-Control-Allow-Origin']).toBe('*'); }); - it('should create a 201 Created response', () => { + it('should create a 201 Created response with CORS headers', () => { // Arrange const body = { id: '123' }; @@ -41,18 +52,20 @@ describe('Response Utils', () => { // Assert expect(response.statusCode).toBe(201); expect(response.body).toBe(JSON.stringify(body)); + expect(response.headers!['Access-Control-Allow-Origin']).toBe('*'); }); - it('should create a 204 No Content response', () => { + it('should create a 204 No Content response with CORS headers', () => { // Act const response = noContent(); // Assert expect(response.statusCode).toBe(204); expect(response.body).toBe('{}'); + expect(response.headers!['Access-Control-Allow-Origin']).toBe('*'); }); - it('should create a 400 Bad Request response with custom message', () => { + it('should create a 400 Bad Request response with custom message and CORS headers', () => { // Arrange const message = 'Invalid input'; @@ -62,9 +75,10 @@ describe('Response Utils', () => { // Assert expect(response.statusCode).toBe(400); expect(response.body).toBe(JSON.stringify({ message })); + expect(response.headers!['Access-Control-Allow-Origin']).toBe('*'); }); - it('should create a 404 Not Found response with custom message', () => { + it('should create a 404 Not Found response with custom message and CORS headers', () => { // Arrange const message = 'Task not found'; @@ -74,9 +88,10 @@ describe('Response Utils', () => { // Assert expect(response.statusCode).toBe(404); expect(response.body).toBe(JSON.stringify({ message })); + expect(response.headers!['Access-Control-Allow-Origin']).toBe('*'); }); - it('should create a 500 Internal Server Error response with custom message', () => { + it('should create a 500 Internal Server Error response with custom message and CORS headers', () => { // Arrange const message = 'Something went wrong'; @@ -86,6 +101,7 @@ describe('Response Utils', () => { // Assert expect(response.statusCode).toBe(500); expect(response.body).toBe(JSON.stringify({ message })); + expect(response.headers!['Access-Control-Allow-Origin']).toBe('*'); }); it('should use default messages when none provided', () => { diff --git a/src/utils/response.ts b/src/utils/response.ts index 8e0e846..c43d174 100644 --- a/src/utils/response.ts +++ b/src/utils/response.ts @@ -1,13 +1,16 @@ 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), }; From 6a635405c740717dd3a959112581f9de04f86663 Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Sat, 7 Jun 2025 07:33:13 -0400 Subject: [PATCH 20/26] update project description in Copilot instructions for Task Service --- .github/copilot-instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index beb9b51..1481da4 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -13,7 +13,7 @@ You are a **Senior TypeScript developer** working on an AWS Lambda REST API proj ## Project Overview - **Component:** Task Service **task-service** -- **Description:** This service provides a REST API for managing tasks, including creating, retrieving, updating, and deleting tasks. It uses AWS Lambda functions triggered by API Gateway events, with business logic encapsulated in service classes. The project follows best practices for TypeScript development, AWS CDK infrastructure management, and unit testing with Vitest. +- **Description:** This service provides a REST API for managing tasks, including creating, retrieving, updating, and deleting tasks. It uses AWS Lambda functions triggered by API Gateway events, with business logic encapsulated in service modules. The project follows best practices for TypeScript development, AWS CDK infrastructure management, and unit testing with Vitest. --- From 26cf40f1d35420778856423d72895db6cae9d8e6 Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Sat, 7 Jun 2025 07:34:04 -0400 Subject: [PATCH 21/26] fix: correct typos and improve clarity in task creation requirements --- docs/requirements/01-create-task.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/requirements/01-create-task.md b/docs/requirements/01-create-task.md index 271e405..d04095e 100644 --- a/docs/requirements/01-create-task.md +++ b/docs/requirements/01-create-task.md @@ -6,7 +6,7 @@ This document describes the requirements for an AWS Lambda REST API that will cr ## Description -Create an AWS Lambda function which receives a REST API request containing the attributes of a new **Task** to be created. The handler should validate the create task object in the request. The task service should store the **Task** attributes in the task table in AWS DynamoDB. The task service should use the AWS SDK to interact with AWS DynamoDB. The task service should use the DynamoDB Document Client. The Lambda function should return the created **Task** in the response body if successful along with an appropriate HTTP status code. If an error occurs, the Lambda function should return an appropriate HTTP status code and the body should contain a meaningful message. +Create an AWS Lambda function which receives a REST API request containing the attributes of a new **Task** to be created. The handler should validate the create task object in the request. The task service should store the **Task** attributes in the task table in AWS DynamoDB. The task service should use the AWS SDK to interact with AWS DynamoDB. The task service should use the DynamoDB Document Client. The Lambda function should return the created **Task** in the response body if successful along with an appropriate HTTP status code (201). If an error occurs, the Lambda function should return an appropriate HTTP status code (500) and the body should contain a meaningful message. Initialize the AWS CDK for this project. Ensure that the CDK configuration is updated to use the project directory structure specified in the instructions. Create appropriate AWS infrastructure using the AWS CDK including an REST API Gateway, the Lambda function, the DynamoDB table, and all associated AWS resources. @@ -24,7 +24,7 @@ A **Task** object has the following attributes: - **title:** The task title. String. Required. Maximum of 100 characters. - **detail:** Additional information for the task. String. Optional. Maximum of 2000 characters. - **isComplete:** Indicates if the task is complete. Boolean. Optional. Defaults to **false** if not provided. -- **dueAt:** The task due date. String. Optioanl. Use ISO-8601 date format, YYYY-MM-DD. +- **dueAt:** The task due date. String. Optional. Use ISO-8601 date format, YYYY-MM-DD. ### Create Task Object From 103d00e24f8a0d144d302bbcbbcf3ecc36cae7c4 Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Mon, 9 Jun 2025 08:39:15 -0400 Subject: [PATCH 22/26] feat: implement OPTIONS method for CORS support in Tasks API --- docs/requirements/08-options-method.md | 89 ++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 docs/requirements/08-options-method.md diff --git a/docs/requirements/08-options-method.md b/docs/requirements/08-options-method.md new file mode 100644 index 0000000..a3200cd --- /dev/null +++ b/docs/requirements/08-options-method.md @@ -0,0 +1,89 @@ +# Adding OPTIONS Method to Tasks API Resources + +## Background + +The Tasks API currently supports standard REST operations (GET, POST, PUT, DELETE) for task resources. To support CORS (Cross-Origin Resource Sharing) and preflight requests, we need to implement the OPTIONS HTTP method for both the `/tasks` and `/tasks/{taskId}` resources. + +## Requirements + +### 1. Infrastructure Changes + +The AWS CDK infrastructure code needs to be updated to add support for the OPTIONS method on: + +- The collection resource `/tasks` +- The item resource `/tasks/{taskId}` + +### 2. Implementation Details + +- **Integration Type**: Use Mock Integration in API Gateway +- **Response Configuration**: + - Status code: 200 + - Response headers: + - `Access-Control-Allow-Origin`: Use the existing `corsAllowOrigin` stack property + - `Access-Control-Allow-Headers`: 'Content-Type,X-Amz-Date,Authorization,X-Api-Key' + - `Access-Control-Allow-Methods`: 'GET,POST,PUT,DELETE,OPTIONS' + - `Access-Control-Allow-Credentials`: 'true' + +### 3. Mock Integration Response + +The response body for the OPTIONS method should be empty (as per the HTTP specification). + +### 4. Code Changes + +Update the CDK stack in `infrastructure/stacks/ApiStack.ts` to add the OPTIONS method to both resources: + +```typescript +// For the /tasks resource +tasksResource.addMethod( + 'OPTIONS', + new MockIntegration({ + integrationResponses: [ + { + statusCode: '200', + responseParameters: { + 'method.response.header.Access-Control-Allow-Headers': "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'", + 'method.response.header.Access-Control-Allow-Methods': "'GET,POST,PUT,DELETE,OPTIONS'", + 'method.response.header.Access-Control-Allow-Origin': `'${this.corsAllowOrigin}'`, + 'method.response.header.Access-Control-Allow-Credentials': "'true'", + }, + }, + ], + passthroughBehavior: PassthroughBehavior.WHEN_NO_MATCH, + requestTemplates: { + 'application/json': '{"statusCode": 200}', + }, + }), + { + methodResponses: [ + { + statusCode: '200', + responseParameters: { + 'method.response.header.Access-Control-Allow-Headers': true, + 'method.response.header.Access-Control-Allow-Methods': true, + 'method.response.header.Access-Control-Allow-Origin': true, + 'method.response.header.Access-Control-Allow-Credentials': true, + }, + }, + ], + }, +); + +// Same implementation for the /tasks/{taskId} resource +``` + +### 5. Testing Requirements + +- Verify the OPTIONS method returns a 200 status code with the correct CORS headers +- Confirm that preflight requests from browsers work correctly +- Test cross-origin requests to ensure CORS is working as expected + +## Success Criteria + +- OPTIONS requests to `/tasks` and `/tasks/{taskId}` return a 200 status code with the appropriate CORS headers +- Client applications from different origins can make requests to the Tasks API +- Preflight requests are handled correctly + +## Out of Scope + +- Implementation of Lambda functions (not needed for OPTIONS method) +- Changes to existing API methods From 7070ca2ad96a5fff410ee1d6dd85b67df1d2d27b Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Mon, 9 Jun 2025 08:55:39 -0400 Subject: [PATCH 23/26] feat: add OPTIONS method for CORS preflight support in tasks API --- infrastructure/stacks/ApiStack.ts | 72 +++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/infrastructure/stacks/ApiStack.ts b/infrastructure/stacks/ApiStack.ts index 0e80cd2..7b4ab22 100644 --- a/infrastructure/stacks/ApiStack.ts +++ b/infrastructure/stacks/ApiStack.ts @@ -168,6 +168,42 @@ export class ApiStack extends cdk.Stack { // Add a GET method to list all tasks tasksResource.addMethod('GET', new apigateway.LambdaIntegration(listTasksFunction)); + // Add OPTIONS method to support CORS preflight requests for the /tasks resource + tasksResource.addMethod( + 'OPTIONS', + new apigateway.MockIntegration({ + integrationResponses: [ + { + statusCode: '200', + responseParameters: { + 'method.response.header.Access-Control-Allow-Headers': + "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'", + 'method.response.header.Access-Control-Allow-Methods': "'GET,POST,PUT,DELETE,OPTIONS'", + 'method.response.header.Access-Control-Allow-Origin': `'${props?.corsAllowOrigin || '*'}'`, + 'method.response.header.Access-Control-Allow-Credentials': "'true'", + }, + }, + ], + passthroughBehavior: apigateway.PassthroughBehavior.WHEN_NO_MATCH, + requestTemplates: { + 'application/json': '{"statusCode": 200}', + }, + }), + { + methodResponses: [ + { + statusCode: '200', + responseParameters: { + 'method.response.header.Access-Control-Allow-Headers': true, + 'method.response.header.Access-Control-Allow-Methods': true, + 'method.response.header.Access-Control-Allow-Origin': true, + 'method.response.header.Access-Control-Allow-Credentials': true, + }, + }, + ], + }, + ); + // Create a task resource const taskResource = tasksResource.addResource('{taskId}'); @@ -180,6 +216,42 @@ export class ApiStack extends cdk.Stack { // Add a DELETE method to delete a task by ID taskResource.addMethod('DELETE', new apigateway.LambdaIntegration(deleteTaskFunction)); + // Add OPTIONS method to support CORS preflight requests for the /tasks/{taskId} resource + taskResource.addMethod( + 'OPTIONS', + new apigateway.MockIntegration({ + integrationResponses: [ + { + statusCode: '200', + responseParameters: { + 'method.response.header.Access-Control-Allow-Headers': + "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'", + 'method.response.header.Access-Control-Allow-Methods': "'GET,POST,PUT,DELETE,OPTIONS'", + 'method.response.header.Access-Control-Allow-Origin': `'${props?.corsAllowOrigin || '*'}'`, + 'method.response.header.Access-Control-Allow-Credentials': "'true'", + }, + }, + ], + passthroughBehavior: apigateway.PassthroughBehavior.WHEN_NO_MATCH, + requestTemplates: { + 'application/json': '{"statusCode": 200}', + }, + }), + { + methodResponses: [ + { + statusCode: '200', + responseParameters: { + 'method.response.header.Access-Control-Allow-Headers': true, + 'method.response.header.Access-Control-Allow-Methods': true, + 'method.response.header.Access-Control-Allow-Origin': true, + 'method.response.header.Access-Control-Allow-Credentials': true, + }, + }, + ], + }, + ); + // Output the API URL new cdk.CfnOutput(this, 'ApiUrl', { value: api.url, From f74ad41726bb343b9286af2f056a3c92800e96ee Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Tue, 10 Jun 2025 06:16:46 -0400 Subject: [PATCH 24/26] fix: update due date format in task schemas to ISO-8601 specification --- docs/requirements/01-create-task.md | 10 +++++++--- docs/requirements/04-update-task.md | 2 +- src/models/Task.ts | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/requirements/01-create-task.md b/docs/requirements/01-create-task.md index d04095e..a4bd454 100644 --- a/docs/requirements/01-create-task.md +++ b/docs/requirements/01-create-task.md @@ -8,8 +8,6 @@ This document describes the requirements for an AWS Lambda REST API that will cr Create an AWS Lambda function which receives a REST API request containing the attributes of a new **Task** to be created. The handler should validate the create task object in the request. The task service should store the **Task** attributes in the task table in AWS DynamoDB. The task service should use the AWS SDK to interact with AWS DynamoDB. The task service should use the DynamoDB Document Client. The Lambda function should return the created **Task** in the response body if successful along with an appropriate HTTP status code (201). If an error occurs, the Lambda function should return an appropriate HTTP status code (500) and the body should contain a meaningful message. -Initialize the AWS CDK for this project. Ensure that the CDK configuration is updated to use the project directory structure specified in the instructions. Create appropriate AWS infrastructure using the AWS CDK including an REST API Gateway, the Lambda function, the DynamoDB table, and all associated AWS resources. - Initialize Vitest for this project. Add unit tests for the Lambda function source members. Implement these requirements step by step. Follow all best practices and structure for this project. @@ -24,7 +22,7 @@ A **Task** object has the following attributes: - **title:** The task title. String. Required. Maximum of 100 characters. - **detail:** Additional information for the task. String. Optional. Maximum of 2000 characters. - **isComplete:** Indicates if the task is complete. Boolean. Optional. Defaults to **false** if not provided. -- **dueAt:** The task due date. String. Optional. Use ISO-8601 date format, YYYY-MM-DD. +- **dueAt:** The task due date. String. Optional. Use ISO-8601 date format, e.g. 2025-06-01T04:00:00Z ### Create Task Object @@ -32,3 +30,9 @@ The request body to create a new task, the **CreateTaskRequest**, is slightly di - **id** The id is optional on the create task request. - **isComplete** Defaults to **false** if not defined. + +--- + +## CDK Infrastructure + +Initialize the AWS CDK for this project. Ensure that the CDK configuration is updated to use the project directory structure specified in the instructions. Create appropriate AWS infrastructure using the AWS CDK including an REST API Gateway, the Lambda function, the DynamoDB table, and all associated AWS resources. diff --git a/docs/requirements/04-update-task.md b/docs/requirements/04-update-task.md index 8e8c260..267f508 100644 --- a/docs/requirements/04-update-task.md +++ b/docs/requirements/04-update-task.md @@ -27,6 +27,6 @@ The request body to update an existing task, the **UpdateTaskRequest**, should i - **title:** The task title. String. Optional. Maximum of 100 characters. - **detail:** Additional information for the task. String. Optional. Maximum of 2000 characters. - **isComplete:** Indicates if the task is complete. Boolean. Optional. -- **dueAt:** The task due date. String. Optional. Use ISO-8601 date format, YYYY-MM-DD. +- **dueAt:** The task due date. String. Optional. Use ISO-8601 date format, e.g. 2025-06-01T04:00:00Z The update operation should only modify the attributes that are provided in the request. Attributes not included in the request should remain unchanged. diff --git a/src/models/Task.ts b/src/models/Task.ts index 3541748..2c76085 100644 --- a/src/models/Task.ts +++ b/src/models/Task.ts @@ -15,7 +15,7 @@ export const TaskSchema = z.object({ title: z.string().min(1, 'Title is required').max(100, 'Title must be 100 characters or less'), detail: z.string().max(2000, 'Detail must be 2000 characters or less').optional(), isComplete: z.boolean().default(false), - dueAt: z.string().date().optional(), + dueAt: z.string().datetime('Due date must be a valid ISO-8601 date string').optional(), }); /** @@ -43,7 +43,7 @@ export const UpdateTaskSchema = z.object({ title: z.string().max(100, 'Title must be 100 characters or less').optional(), detail: z.string().max(2000, 'Detail must be 2000 characters or less').optional(), isComplete: z.boolean().optional(), - dueAt: z.string().date().optional(), + dueAt: z.string().datetime('Due date must be a valid ISO-8601 date string').optional(), }); // Type definitions derived from the schemas From 7dd6e51b3556138e44532df9167b71ced6b0109c Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Tue, 10 Jun 2025 06:28:25 -0400 Subject: [PATCH 25/26] fix: update due date format in createTask test to ISO-8601 specification --- src/handlers/createTask.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/createTask.test.ts b/src/handlers/createTask.test.ts index 550fe82..7a4d4ee 100644 --- a/src/handlers/createTask.test.ts +++ b/src/handlers/createTask.test.ts @@ -96,7 +96,7 @@ describe('createTask handler', () => { const validTaskData: CreateTaskRequest = { title: 'Test Task', detail: 'This is a test task', - dueAt: '2025-06-30', + dueAt: '2025-06-30T12:00:00Z', // Example due date }; const mockEvent = createMockEvent(JSON.stringify(validTaskData)); From 7140d537b21376ddf2c9d535f985023c97d1a3c8 Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Tue, 10 Jun 2025 06:28:30 -0400 Subject: [PATCH 26/26] chore: update dependencies to latest versions - Bump AWS SDK packages to version 3.826.0 - Update aws-cdk-lib to version 2.200.1 - Upgrade zod to version 3.25.57 - Update @types/node to version 24.0.0 - Upgrade @typescript-eslint packages to version 8.34.0 - Update @vitest/coverage-v8 and vitest to version 3.2.3 --- package-lock.json | 832 ++++++++++++++++++++++++---------------------- package.json | 24 +- 2 files changed, 444 insertions(+), 412 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0899807..655e34c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,22 +8,22 @@ "name": "task-service", "version": "1.0.0", "dependencies": { - "@aws-sdk/client-dynamodb": "3.821.0", - "@aws-sdk/lib-dynamodb": "3.821.0", - "@aws-sdk/util-dynamodb": "3.821.0", - "aws-cdk-lib": "2.200.0", + "@aws-sdk/client-dynamodb": "3.826.0", + "@aws-sdk/lib-dynamodb": "3.826.0", + "@aws-sdk/util-dynamodb": "3.826.0", + "aws-cdk-lib": "2.200.1", "constructs": "10.4.2", "uuid": "11.1.0", - "zod": "3.25.49" + "zod": "3.25.57" }, "devDependencies": { "@types/aws-lambda": "8.10.149", - "@types/node": "22.15.29", + "@types/node": "24.0.0", "@types/uuid": "10.0.0", - "@typescript-eslint/eslint-plugin": "8.33.1", - "@typescript-eslint/parser": "8.33.1", - "@vitest/coverage-v8": "3.2.0", - "aws-cdk": "2.1017.1", + "@typescript-eslint/eslint-plugin": "8.34.0", + "@typescript-eslint/parser": "8.34.0", + "@vitest/coverage-v8": "3.2.3", + "aws-cdk": "2.1018.0", "eslint": "9.28.0", "eslint-config-prettier": "10.1.5", "globals": "16.2.0", @@ -31,8 +31,8 @@ "rimraf": "6.0.1", "ts-node": "10.9.2", "typescript": "5.8.3", - "typescript-eslint": "8.33.1", - "vitest": "3.2.0" + "typescript-eslint": "8.34.0", + "vitest": "3.2.3" } }, "node_modules/@ampproject/remapping": { @@ -62,9 +62,9 @@ "license": "Apache-2.0" }, "node_modules/@aws-cdk/cloud-assembly-schema": { - "version": "44.1.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-44.1.0.tgz", - "integrity": "sha512-WvesvSbBw5FrVbH8LZfjX5iDDRdixDkEnbsFGN8H2GNR9geBo4kIBI1nlOiqoGB6dwPwif8qDEM/4NOfuzIChQ==", + "version": "44.2.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-44.2.0.tgz", + "integrity": "sha512-oB3fq8yudGMHsSOyh2rFge5HVUMS6MOiAqK/6a9pyG4kHPkza0xCPlpvLFVCliD8y/VrsfxIcA584x9O0cxDiQ==", "bundleDependencies": [ "jsonschema", "semver" @@ -75,7 +75,7 @@ "semver": "^7.7.2" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.0.0" } }, "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { @@ -223,46 +223,46 @@ } }, "node_modules/@aws-sdk/client-dynamodb": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.821.0.tgz", - "integrity": "sha512-7GyFMN0B7NW6rv1pT0PSWcAFPsziEYnxdZUnF/sMsgXz5U0peviCnuPGUL5jqYL6sf6HgXLYYooHVjqLnVMVVQ==", + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.826.0.tgz", + "integrity": "sha512-EtgX1IqnrywGBREFuLTI6Va7I2SfL4RGxSONLi3NRHsmVh3/D5cV4imfrCDJ2I2vMOwk46/X6YDTS/Oz8uP/OQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.821.0", - "@aws-sdk/credential-provider-node": "3.821.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/credential-provider-node": "3.826.0", "@aws-sdk/middleware-endpoint-discovery": "3.821.0", "@aws-sdk/middleware-host-header": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.826.0", "@aws-sdk/region-config-resolver": "3.821.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.821.0", "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.826.0", "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.5.1", + "@smithy/core": "^3.5.3", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/hash-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.9", - "@smithy/middleware-retry": "^4.1.10", + "@smithy/middleware-endpoint": "^4.1.11", + "@smithy/middleware-retry": "^4.1.12", "@smithy/middleware-serde": "^4.0.8", "@smithy/middleware-stack": "^4.0.4", "@smithy/node-config-provider": "^4.1.3", "@smithy/node-http-handler": "^4.0.6", "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.1", + "@smithy/smithy-client": "^4.4.3", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.17", - "@smithy/util-defaults-mode-node": "^4.0.17", + "@smithy/util-defaults-mode-browser": "^4.0.19", + "@smithy/util-defaults-mode-node": "^4.0.19", "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.5", @@ -296,44 +296,44 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.821.0.tgz", - "integrity": "sha512-aDEBZUKUd/+Tvudi0d9KQlqt2OW2P27LATZX0jkNC8yVk4145bAPS04EYoqdKLuyUn/U33DibEOgKUpxZB12jQ==", + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.826.0.tgz", + "integrity": "sha512-/FEKnUC3xPkLL4RuRydwzx+y4b55HIX6qLPbGnyIs+sNmCUyc/62ijtV1Ml+b++YzEF6jWNBsJOxeyZdgrJ3Ig==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.821.0", + "@aws-sdk/core": "3.826.0", "@aws-sdk/middleware-host-header": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.826.0", "@aws-sdk/region-config-resolver": "3.821.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.821.0", "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.826.0", "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.5.1", + "@smithy/core": "^3.5.3", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/hash-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.9", - "@smithy/middleware-retry": "^4.1.10", + "@smithy/middleware-endpoint": "^4.1.11", + "@smithy/middleware-retry": "^4.1.12", "@smithy/middleware-serde": "^4.0.8", "@smithy/middleware-stack": "^4.0.4", "@smithy/node-config-provider": "^4.1.3", "@smithy/node-http-handler": "^4.0.6", "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.1", + "@smithy/smithy-client": "^4.4.3", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.17", - "@smithy/util-defaults-mode-node": "^4.0.17", + "@smithy/util-defaults-mode-browser": "^4.0.19", + "@smithy/util-defaults-mode-node": "^4.0.19", "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.5", @@ -345,20 +345,24 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.821.0.tgz", - "integrity": "sha512-8eB3wKbmfciQFmxFq7hAjy7mXdUs7vBOR5SwT0ZtQBg0Txc18Lc9tMViqqdO6/KU7OukA6ib2IAVSjIJJEN7FQ==", + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.826.0.tgz", + "integrity": "sha512-BGbQYzWj3ps+dblq33FY5tz/SsgJCcXX0zjQlSC07tYvU1jHTUvsefphyig+fY38xZ4wdKjbTop+KUmXUYrOXw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.821.0", - "@smithy/core": "^3.5.1", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/core": "^3.5.3", "@smithy/node-config-provider": "^4.1.3", "@smithy/property-provider": "^4.0.4", "@smithy/protocol-http": "^5.1.2", "@smithy/signature-v4": "^5.1.2", - "@smithy/smithy-client": "^4.4.1", + "@smithy/smithy-client": "^4.4.3", "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-middleware": "^4.0.4", + "@smithy/util-utf8": "^4.0.0", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, @@ -367,12 +371,12 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.821.0.tgz", - "integrity": "sha512-C+s/A72pd7CXwEsJj9+Uq9T726iIfIF18hGRY8o82xcIEfOyakiPnlisku8zZOaAu+jm0CihbbYN4NyYNQ+HZQ==", + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.826.0.tgz", + "integrity": "sha512-DK3pQY8+iKK3MGDdC3uOZQ2psU01obaKlTYhEwNu4VWzgwQL4Vi3sWj4xSWGEK41vqZxiRLq6fOq7ysRI+qEZA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.821.0", + "@aws-sdk/core": "3.826.0", "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/types": "^4.3.1", @@ -383,18 +387,18 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.821.0.tgz", - "integrity": "sha512-gIRzTLnAsRfRSNarCag7G7rhcHagz4x5nNTWRihQs5cwTOghEExDy7Tj5m4TEkv3dcTAsNn+l4tnR4nZXo6R+Q==", + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.826.0.tgz", + "integrity": "sha512-N+IVZBh+yx/9GbMZTKO/gErBi/FYZQtcFRItoLbY+6WU+0cSWyZYfkoeOxHmQV3iX9k65oljERIWUmL9x6OSQg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.821.0", + "@aws-sdk/core": "3.826.0", "@aws-sdk/types": "3.821.0", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/node-http-handler": "^4.0.6", "@smithy/property-provider": "^4.0.4", "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.1", + "@smithy/smithy-client": "^4.4.3", "@smithy/types": "^4.3.1", "@smithy/util-stream": "^4.2.2", "tslib": "^2.6.2" @@ -404,18 +408,18 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.821.0.tgz", - "integrity": "sha512-VRTrmsca8kBHtY1tTek1ce+XkK/H0fzodBKcilM/qXjTyumMHPAzVAxKZfSvGC+28/pXyQzhOEyxZfw7giCiWA==", + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.826.0.tgz", + "integrity": "sha512-g7n+qSklq/Lzjxe2Ke5QFNCgYn26a3ydZnbFIk8QqYin4pzG+qiunaqJjpV3c/EeHMlfK8bBc7MXAylKzGRccQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.821.0", - "@aws-sdk/credential-provider-env": "3.821.0", - "@aws-sdk/credential-provider-http": "3.821.0", - "@aws-sdk/credential-provider-process": "3.821.0", - "@aws-sdk/credential-provider-sso": "3.821.0", - "@aws-sdk/credential-provider-web-identity": "3.821.0", - "@aws-sdk/nested-clients": "3.821.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/credential-provider-env": "3.826.0", + "@aws-sdk/credential-provider-http": "3.826.0", + "@aws-sdk/credential-provider-process": "3.826.0", + "@aws-sdk/credential-provider-sso": "3.826.0", + "@aws-sdk/credential-provider-web-identity": "3.826.0", + "@aws-sdk/nested-clients": "3.826.0", "@aws-sdk/types": "3.821.0", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/property-provider": "^4.0.4", @@ -428,17 +432,17 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.821.0.tgz", - "integrity": "sha512-oBgbcgOXWMgknAfhIdTeHSSVIv+k2LXN9oTbxu1r++o4WWBWrEQ8mHU0Zo9dfr7Uaoqi3pezYZznsBkXnMLEOg==", + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.826.0.tgz", + "integrity": "sha512-UfIJXxHjmSxH6bea00HBPLkjNI2D04enQA/xNLZvB+4xtzt1/gYdCis1P4/73f5aGVVVB4/zQMobBbnjkrmbQw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.821.0", - "@aws-sdk/credential-provider-http": "3.821.0", - "@aws-sdk/credential-provider-ini": "3.821.0", - "@aws-sdk/credential-provider-process": "3.821.0", - "@aws-sdk/credential-provider-sso": "3.821.0", - "@aws-sdk/credential-provider-web-identity": "3.821.0", + "@aws-sdk/credential-provider-env": "3.826.0", + "@aws-sdk/credential-provider-http": "3.826.0", + "@aws-sdk/credential-provider-ini": "3.826.0", + "@aws-sdk/credential-provider-process": "3.826.0", + "@aws-sdk/credential-provider-sso": "3.826.0", + "@aws-sdk/credential-provider-web-identity": "3.826.0", "@aws-sdk/types": "3.821.0", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/property-provider": "^4.0.4", @@ -451,12 +455,12 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.821.0.tgz", - "integrity": "sha512-e18ucfqKB3ICNj5RP/FEdvUfhVK6E9MALOsl8pKP13mwegug46p/1BsZWACD5n+Zf9ViiiHxIO7td03zQixfwA==", + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.826.0.tgz", + "integrity": "sha512-kURrc4amu3NLtw1yZw7EoLNEVhmOMRUTs+chaNcmS+ERm3yK0nKjaJzmKahmwlTQTSl3wJ8jjK7x962VPo+zWw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.821.0", + "@aws-sdk/core": "3.826.0", "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", @@ -468,14 +472,14 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.821.0.tgz", - "integrity": "sha512-Dt+pheBLom4O/egO4L75/72k9C1qtUOLl0F0h6lmqZe4Mvhz+wDtjoO/MdGC/P1q0kcIX/bBKr0NQ3cIvAH8pA==", + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.826.0.tgz", + "integrity": "sha512-F19J3zcfoom6OnQ0MyAtvduVKQXPgkz9i5ExSO01J2CzjbyMhCDA99qAjHYe+LwhW+W7P/jzBPd0+uOQ2Nhh9Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.821.0", - "@aws-sdk/core": "3.821.0", - "@aws-sdk/token-providers": "3.821.0", + "@aws-sdk/client-sso": "3.826.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/token-providers": "3.826.0", "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", @@ -487,13 +491,13 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.821.0.tgz", - "integrity": "sha512-FF5wnRJkxSQaCVVvWNv53K1MhTMgH8d+O+MHTbkv51gVIgVATrtfFQMKBLcEAxzXrgAliIO3LiNv+1TqqBZ+BA==", + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.826.0.tgz", + "integrity": "sha512-o27GZ6Hy7qhuvMFVUL2eFEpBzf33Jaa/x3u3SHwU0nL7ko7jmbpeF0x4+wmagpI9X2IvVlUxIs0VaQ3YayPLEA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.821.0", - "@aws-sdk/nested-clients": "3.821.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/nested-clients": "3.826.0", "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/types": "^4.3.1", @@ -517,15 +521,15 @@ } }, "node_modules/@aws-sdk/lib-dynamodb": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.821.0.tgz", - "integrity": "sha512-o87vY8SqlQtaI7WookHiWeTnPMzWrpv6H/IXuihj04AnNSwhTej2w4uL0kPTmWaA9DIRzmD8n2M9cjFnz6Pj6Q==", + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.826.0.tgz", + "integrity": "sha512-KFHccreS1XY8geyz4plsH+DR5SXEh2jVe3+bguHPjE1XIyz6k02IUXjlpWlaL14kkAVmQ27XNrGHnnpshrbIag==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.821.0", - "@aws-sdk/util-dynamodb": "3.821.0", - "@smithy/core": "^3.5.1", - "@smithy/smithy-client": "^4.4.1", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/util-dynamodb": "3.826.0", + "@smithy/core": "^3.5.3", + "@smithy/smithy-client": "^4.4.3", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, @@ -533,7 +537,7 @@ "node": ">=18.0.0" }, "peerDependencies": { - "@aws-sdk/client-dynamodb": "^3.821.0" + "@aws-sdk/client-dynamodb": "^3.826.0" } }, "node_modules/@aws-sdk/middleware-endpoint-discovery": { @@ -598,15 +602,15 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.821.0.tgz", - "integrity": "sha512-rw8q3TxygMg3VrofN04QyWVCCyGwz3bVthYmBZZseENPWG3Krz1OCKcyqjkTcAxMQlEywOske+GIiOasGKnJ3w==", + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.826.0.tgz", + "integrity": "sha512-j404+EcfBbtTlAhyObjXbdKwwDXO1pCxHvR5Fw8FXNvp/H330j6YnXgs3SJ6d3bZUwUJ/ztPx2S5AlBbLVLDFw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.821.0", + "@aws-sdk/core": "3.826.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.821.0", - "@smithy/core": "^3.5.1", + "@smithy/core": "^3.5.3", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" @@ -616,44 +620,44 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.821.0.tgz", - "integrity": "sha512-2IuHcUsWw44ftSEDYU4dvktTEqgyDvkOcfpoGC/UmT4Qo6TVCP3U5tWEGpNK9nN+7nLvekruxxG/jaMt5/oWVw==", + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.826.0.tgz", + "integrity": "sha512-p7olPq0uTtHqGuXI1GSc/gzKDvV55PMbLtnmupEDfnY9SoRu+QatbWQ6da9sI1lhOcNmRMgiNQBXFzaUFrG+SQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.821.0", + "@aws-sdk/core": "3.826.0", "@aws-sdk/middleware-host-header": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.826.0", "@aws-sdk/region-config-resolver": "3.821.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.821.0", "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.826.0", "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.5.1", + "@smithy/core": "^3.5.3", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/hash-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.9", - "@smithy/middleware-retry": "^4.1.10", + "@smithy/middleware-endpoint": "^4.1.11", + "@smithy/middleware-retry": "^4.1.12", "@smithy/middleware-serde": "^4.0.8", "@smithy/middleware-stack": "^4.0.4", "@smithy/node-config-provider": "^4.1.3", "@smithy/node-http-handler": "^4.0.6", "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.1", + "@smithy/smithy-client": "^4.4.3", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.17", - "@smithy/util-defaults-mode-node": "^4.0.17", + "@smithy/util-defaults-mode-browser": "^4.0.19", + "@smithy/util-defaults-mode-node": "^4.0.19", "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.5", @@ -682,13 +686,13 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.821.0.tgz", - "integrity": "sha512-qJ7wgKhdxGbPg718zWXbCYKDuSWZNU3TSw64hPRW6FtbZrIyZxObpiTKC6DKwfsVoZZhHEoP/imGykN1OdOTJA==", + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.826.0.tgz", + "integrity": "sha512-iCOcVAqGPSHtQL8ZBXifZMEcHyUl9wJ8HvLZ5l1ohA/3ZNP+dqEPGi7jfhR5jZKs+xyp2jxByFqfil9PjI9c5A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.821.0", - "@aws-sdk/nested-clients": "3.821.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/nested-clients": "3.826.0", "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", @@ -713,9 +717,9 @@ } }, "node_modules/@aws-sdk/util-dynamodb": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.821.0.tgz", - "integrity": "sha512-ziVGOwqsqiZzTuSEKTxbGA6NH0MvSivmH0k6WRqoVhgl1uu65BZKI/z7musNDuNruwMzvL5ChlCq+npOo6E0kA==", + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.826.0.tgz", + "integrity": "sha512-4zsbpJ9wEnDDvCTrhPCc/iLkn8Bf0+ux4iK39b4cX11sSPVtwTUnpnGHyilApKWaxBKwNIJHicwskwORxqR2mw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -724,7 +728,7 @@ "node": ">=18.0.0" }, "peerDependencies": { - "@aws-sdk/client-dynamodb": "^3.821.0" + "@aws-sdk/client-dynamodb": "^3.826.0" } }, "node_modules/@aws-sdk/util-endpoints": { @@ -767,12 +771,12 @@ } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.821.0.tgz", - "integrity": "sha512-YwMXc9EvuzJgnLBTyiQly2juPujXwDgcMHB0iSN92tHe7Dd1jJ1feBmTgdClaaqCeHFUaFpw+3JU/ZUJ6LjR+A==", + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.826.0.tgz", + "integrity": "sha512-wHw6bZQWIMcFF/8r03aY9Itp6JLBYY4absGGhCDK1dc3tPEfi8NVSdb05a/Oz+g4TVaDdxLo0OQ/OKMS1DFRHQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.826.0", "@aws-sdk/types": "3.821.0", "@smithy/node-config-provider": "^4.1.3", "@smithy/types": "^4.3.1", @@ -790,6 +794,19 @@ } } }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.821.0.tgz", + "integrity": "sha512-DIIotRnefVL6DiaHtO6/21DhJ4JZnnIwdNbpwiAhdt/AVbttcE4yw925gsjur0OGv5BTYXQXU3YnANBYnZjuQA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -811,9 +828,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.4.tgz", - "integrity": "sha512-BRmLHGwpUqLFR2jzx9orBuX/ABDkj2jLKOXrHDTN2aOKL+jFDDKaRNo9nyYsIl9h/UE/7lMKdDjKQQyxKKDZ7g==", + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", "dev": true, "license": "MIT", "dependencies": { @@ -827,9 +844,9 @@ } }, "node_modules/@babel/types": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz", - "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", + "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1695,9 +1712,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz", - "integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.42.0.tgz", + "integrity": "sha512-gldmAyS9hpj+H6LpRNlcjQWbuKUtb94lodB9uCz71Jm+7BxK1VIOo7y62tZZwxhA7j1ylv/yQz080L5WkS+LoQ==", "cpu": [ "arm" ], @@ -1709,9 +1726,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz", - "integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.42.0.tgz", + "integrity": "sha512-bpRipfTgmGFdCZDFLRvIkSNO1/3RGS74aWkJJTFJBH7h3MRV4UijkaEUeOMbi9wxtxYmtAbVcnMtHTPBhLEkaw==", "cpu": [ "arm64" ], @@ -1723,9 +1740,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz", - "integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.42.0.tgz", + "integrity": "sha512-JxHtA081izPBVCHLKnl6GEA0w3920mlJPLh89NojpU2GsBSB6ypu4erFg/Wx1qbpUbepn0jY4dVWMGZM8gplgA==", "cpu": [ "arm64" ], @@ -1737,9 +1754,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz", - "integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.42.0.tgz", + "integrity": "sha512-rv5UZaWVIJTDMyQ3dCEK+m0SAn6G7H3PRc2AZmExvbDvtaDc+qXkei0knQWcI3+c9tEs7iL/4I4pTQoPbNL2SA==", "cpu": [ "x64" ], @@ -1751,9 +1768,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz", - "integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.42.0.tgz", + "integrity": "sha512-fJcN4uSGPWdpVmvLuMtALUFwCHgb2XiQjuECkHT3lWLZhSQ3MBQ9pq+WoWeJq2PrNxr9rPM1Qx+IjyGj8/c6zQ==", "cpu": [ "arm64" ], @@ -1765,9 +1782,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz", - "integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.42.0.tgz", + "integrity": "sha512-CziHfyzpp8hJpCVE/ZdTizw58gr+m7Y2Xq5VOuCSrZR++th2xWAz4Nqk52MoIIrV3JHtVBhbBsJcAxs6NammOQ==", "cpu": [ "x64" ], @@ -1779,9 +1796,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz", - "integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.42.0.tgz", + "integrity": "sha512-UsQD5fyLWm2Fe5CDM7VPYAo+UC7+2Px4Y+N3AcPh/LdZu23YcuGPegQly++XEVaC8XUTFVPscl5y5Cl1twEI4A==", "cpu": [ "arm" ], @@ -1793,9 +1810,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz", - "integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.42.0.tgz", + "integrity": "sha512-/i8NIrlgc/+4n1lnoWl1zgH7Uo0XK5xK3EDqVTf38KvyYgCU/Rm04+o1VvvzJZnVS5/cWSd07owkzcVasgfIkQ==", "cpu": [ "arm" ], @@ -1807,9 +1824,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz", - "integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.42.0.tgz", + "integrity": "sha512-eoujJFOvoIBjZEi9hJnXAbWg+Vo1Ov8n/0IKZZcPZ7JhBzxh2A+2NFyeMZIRkY9iwBvSjloKgcvnjTbGKHE44Q==", "cpu": [ "arm64" ], @@ -1821,9 +1838,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz", - "integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.42.0.tgz", + "integrity": "sha512-/3NrcOWFSR7RQUQIuZQChLND36aTU9IYE4j+TB40VU78S+RA0IiqHR30oSh6P1S9f9/wVOenHQnacs/Byb824g==", "cpu": [ "arm64" ], @@ -1835,9 +1852,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz", - "integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.42.0.tgz", + "integrity": "sha512-O8AplvIeavK5ABmZlKBq9/STdZlnQo7Sle0LLhVA7QT+CiGpNVe197/t8Aph9bhJqbDVGCHpY2i7QyfEDDStDg==", "cpu": [ "loong64" ], @@ -1849,9 +1866,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz", - "integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.42.0.tgz", + "integrity": "sha512-6Qb66tbKVN7VyQrekhEzbHRxXXFFD8QKiFAwX5v9Xt6FiJ3BnCVBuyBxa2fkFGqxOCSGGYNejxd8ht+q5SnmtA==", "cpu": [ "ppc64" ], @@ -1863,9 +1880,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz", - "integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.42.0.tgz", + "integrity": "sha512-KQETDSEBamQFvg/d8jajtRwLNBlGc3aKpaGiP/LvEbnmVUKlFta1vqJqTrvPtsYsfbE/DLg5CC9zyXRX3fnBiA==", "cpu": [ "riscv64" ], @@ -1877,9 +1894,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz", - "integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.42.0.tgz", + "integrity": "sha512-qMvnyjcU37sCo/tuC+JqeDKSuukGAd+pVlRl/oyDbkvPJ3awk6G6ua7tyum02O3lI+fio+eM5wsVd66X0jQtxw==", "cpu": [ "riscv64" ], @@ -1891,9 +1908,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz", - "integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.42.0.tgz", + "integrity": "sha512-I2Y1ZUgTgU2RLddUHXTIgyrdOwljjkmcZ/VilvaEumtS3Fkuhbw4p4hgHc39Ypwvo2o7sBFNl2MquNvGCa55Iw==", "cpu": [ "s390x" ], @@ -1905,9 +1922,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz", - "integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.42.0.tgz", + "integrity": "sha512-Gfm6cV6mj3hCUY8TqWa63DB8Mx3NADoFwiJrMpoZ1uESbK8FQV3LXkhfry+8bOniq9pqY1OdsjFWNsSbfjPugw==", "cpu": [ "x64" ], @@ -1919,9 +1936,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz", - "integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.42.0.tgz", + "integrity": "sha512-g86PF8YZ9GRqkdi0VoGlcDUb4rYtQKyTD1IVtxxN4Hpe7YqLBShA7oHMKU6oKTCi3uxwW4VkIGnOaH/El8de3w==", "cpu": [ "x64" ], @@ -1933,9 +1950,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz", - "integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.42.0.tgz", + "integrity": "sha512-+axkdyDGSp6hjyzQ5m1pgcvQScfHnMCcsXkx8pTgy/6qBmWVhtRVlgxjWwDp67wEXXUr0x+vD6tp5W4x6V7u1A==", "cpu": [ "arm64" ], @@ -1947,9 +1964,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz", - "integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.42.0.tgz", + "integrity": "sha512-F+5J9pelstXKwRSDq92J0TEBXn2nfUrQGg+HK1+Tk7VOL09e0gBqUHugZv7SW4MGrYj41oNCUe3IKCDGVlis2g==", "cpu": [ "ia32" ], @@ -1961,9 +1978,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz", - "integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.42.0.tgz", + "integrity": "sha512-LpHiJRwkaVz/LqjHjK8LCi8osq7elmpwujwbXKNW88bM8eeGxavJIKKjkjpMHAh/2xfnrt1ZSnhTv41WYUHYmA==", "cpu": [ "x64" ], @@ -2004,9 +2021,9 @@ } }, "node_modules/@smithy/core": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.5.1.tgz", - "integrity": "sha512-xSw7bZEFKwOKrm/iv8e2BLt2ur98YZdrRD6nII8ditQeUsY2Q1JmIQ0rpILOhaLKYxxG2ivnoOpokzr9qLyDWA==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.5.3.tgz", + "integrity": "sha512-xa5byV9fEguZNofCclv6v9ra0FYh5FATQW/da7FQUVTic94DfrN/NvmKZjrMyzbpqfot9ZjBaO8U1UeTbmSLuA==", "license": "Apache-2.0", "dependencies": { "@smithy/middleware-serde": "^4.0.8", @@ -2110,12 +2127,12 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.9.tgz", - "integrity": "sha512-AjDgX4UjORLltD/LZCBQTwjQqEfyrx/GeDTHcYLzIgf87pIT70tMWnN87NQpJru1K4ITirY2htSOxNECZJCBOg==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.11.tgz", + "integrity": "sha512-zDogwtRLzKl58lVS8wPcARevFZNBOOqnmzWWxVe9XiaXU2CADFjvJ9XfNibgkOWs08sxLuSr81NrpY4mgp9OwQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.5.1", + "@smithy/core": "^3.5.3", "@smithy/middleware-serde": "^4.0.8", "@smithy/node-config-provider": "^4.1.3", "@smithy/shared-ini-file-loader": "^4.0.4", @@ -2129,15 +2146,15 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.10.tgz", - "integrity": "sha512-RyhcA3sZIIvAo6r48b2Nx2qfg0OnyohlaV0fw415xrQyx5HQ2bvHl9vs/WBiDXIP49mCfws5wX4308c9Pi/isw==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.12.tgz", + "integrity": "sha512-wvIH70c4e91NtRxdaLZF+mbLZ/HcC6yg7ySKUiufL6ESp6zJUSnJucZ309AvG9nqCFHSRB5I6T3Ez1Q9wCh0Ww==", "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^4.1.3", "@smithy/protocol-http": "^5.1.2", "@smithy/service-error-classification": "^4.0.5", - "@smithy/smithy-client": "^4.4.1", + "@smithy/smithy-client": "^4.4.3", "@smithy/types": "^4.3.1", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.5", @@ -2317,13 +2334,13 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.1.tgz", - "integrity": "sha512-XPbcHRfd0iwx8dY5XCBCGyI7uweMW0oezYezxXcG8ANgvZ5YPuC6Ylh+n0bTHpdU3SCMZOnhzgVklYz+p3fIhw==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.3.tgz", + "integrity": "sha512-xxzNYgA0HD6ETCe5QJubsxP0hQH3QK3kbpJz3QrosBCuIWyEXLR/CO5hFb2OeawEKUxMNhz3a1nuJNN2np2RMA==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.5.1", - "@smithy/middleware-endpoint": "^4.1.9", + "@smithy/core": "^3.5.3", + "@smithy/middleware-endpoint": "^4.1.11", "@smithy/middleware-stack": "^4.0.4", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", @@ -2424,13 +2441,13 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.17.tgz", - "integrity": "sha512-HXq5181qnXmIwB7VrwqwP8rsJybHMoYuJnNoXy4PROs2pfSI4sWDMASF2i+7Lo+u64Y6xowhegcdxczowgJtZg==", + "version": "4.0.19", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.19.tgz", + "integrity": "sha512-mvLMh87xSmQrV5XqnUYEPoiFFeEGYeAKIDDKdhE2ahqitm8OHM3aSvhqL6rrK6wm1brIk90JhxDf5lf2hbrLbQ==", "license": "Apache-2.0", "dependencies": { "@smithy/property-provider": "^4.0.4", - "@smithy/smithy-client": "^4.4.1", + "@smithy/smithy-client": "^4.4.3", "@smithy/types": "^4.3.1", "bowser": "^2.11.0", "tslib": "^2.6.2" @@ -2440,16 +2457,16 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.17.tgz", - "integrity": "sha512-RfU2A5LjFhEHw4Nwl1GZNitK4AUWu5jGtigAUDoQtfDUvYHpQxcuLw2QGAdKDtKRflIiHSZ8wXBDR36H9R2Ang==", + "version": "4.0.19", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.19.tgz", + "integrity": "sha512-8tYnx+LUfj6m+zkUUIrIQJxPM1xVxfRBvoGHua7R/i6qAxOMjqR6CpEpDwKoIs1o0+hOjGvkKE23CafKL0vJ9w==", "license": "Apache-2.0", "dependencies": { "@smithy/config-resolver": "^4.1.4", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/node-config-provider": "^4.1.3", "@smithy/property-provider": "^4.0.4", - "@smithy/smithy-client": "^4.4.1", + "@smithy/smithy-client": "^4.4.3", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, @@ -2621,9 +2638,9 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, @@ -2635,13 +2652,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.15.29", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz", - "integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.0.tgz", + "integrity": "sha512-yZQa2zm87aRVcqDyH5+4Hv9KYgSdgwX1rFnGvpbzMaC7YAljmhBET93TPiTd3ObwTL+gSpIzPKg5BqVxdCvxKg==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.8.0" } }, "node_modules/@types/uuid": { @@ -2652,17 +2669,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.1.tgz", - "integrity": "sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz", + "integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/type-utils": "8.33.1", - "@typescript-eslint/utils": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/scope-manager": "8.34.0", + "@typescript-eslint/type-utils": "8.34.0", + "@typescript-eslint/utils": "8.34.0", + "@typescript-eslint/visitor-keys": "8.34.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -2676,22 +2693,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.33.1", + "@typescript-eslint/parser": "^8.34.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.1.tgz", - "integrity": "sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.0.tgz", + "integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/typescript-estree": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/scope-manager": "8.34.0", + "@typescript-eslint/types": "8.34.0", + "@typescript-eslint/typescript-estree": "8.34.0", + "@typescript-eslint/visitor-keys": "8.34.0", "debug": "^4.3.4" }, "engines": { @@ -2707,14 +2724,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.1.tgz", - "integrity": "sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.0.tgz", + "integrity": "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.33.1", - "@typescript-eslint/types": "^8.33.1", + "@typescript-eslint/tsconfig-utils": "^8.34.0", + "@typescript-eslint/types": "^8.34.0", "debug": "^4.3.4" }, "engines": { @@ -2729,14 +2746,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.1.tgz", - "integrity": "sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz", + "integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1" + "@typescript-eslint/types": "8.34.0", + "@typescript-eslint/visitor-keys": "8.34.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2747,9 +2764,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.1.tgz", - "integrity": "sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz", + "integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==", "dev": true, "license": "MIT", "engines": { @@ -2764,14 +2781,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.1.tgz", - "integrity": "sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.0.tgz", + "integrity": "sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.33.1", - "@typescript-eslint/utils": "8.33.1", + "@typescript-eslint/typescript-estree": "8.34.0", + "@typescript-eslint/utils": "8.34.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -2788,9 +2805,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.1.tgz", - "integrity": "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz", + "integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==", "dev": true, "license": "MIT", "engines": { @@ -2802,16 +2819,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.1.tgz", - "integrity": "sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz", + "integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.33.1", - "@typescript-eslint/tsconfig-utils": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/project-service": "8.34.0", + "@typescript-eslint/tsconfig-utils": "8.34.0", + "@typescript-eslint/types": "8.34.0", + "@typescript-eslint/visitor-keys": "8.34.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2831,16 +2848,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.1.tgz", - "integrity": "sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz", + "integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/typescript-estree": "8.33.1" + "@typescript-eslint/scope-manager": "8.34.0", + "@typescript-eslint/types": "8.34.0", + "@typescript-eslint/typescript-estree": "8.34.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2855,13 +2872,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.1.tgz", - "integrity": "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz", + "integrity": "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.33.1", + "@typescript-eslint/types": "8.34.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -2873,9 +2890,9 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2886,9 +2903,9 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.0.tgz", - "integrity": "sha512-HjgvaokAiHxRMI5ioXl4WmgAi4zQtKtnltOOlmpzUqApdcTTZrZJAastbbRGydtiqwtYLFaIb6Jpo3PzowZ0cg==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.3.tgz", + "integrity": "sha512-D1QKzngg8PcDoCE8FHSZhREDuEy+zcKmMiMafYse41RZpBE5EDJyKOTdqK3RQfsV2S2nyKor5KCs8PyPRFqKPg==", "dev": true, "license": "MIT", "dependencies": { @@ -2910,8 +2927,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "3.2.0", - "vitest": "3.2.0" + "@vitest/browser": "3.2.3", + "vitest": "3.2.3" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -2920,15 +2937,15 @@ } }, "node_modules/@vitest/expect": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.0.tgz", - "integrity": "sha512-0v4YVbhDKX3SKoy0PHWXpKhj44w+3zZkIoVES9Ex2pq+u6+Bijijbi2ua5kE+h3qT6LBWFTNZSCOEU37H8Y5sA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.3.tgz", + "integrity": "sha512-W2RH2TPWVHA1o7UmaFKISPvdicFJH+mjykctJFoAkUw+SPTJTGjUNdKscFBrqM7IPnCVu6zihtKYa7TkZS1dkQ==", "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.0", - "@vitest/utils": "3.2.0", + "@vitest/spy": "3.2.3", + "@vitest/utils": "3.2.3", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" }, @@ -2937,13 +2954,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.0.tgz", - "integrity": "sha512-HFcW0lAMx3eN9vQqis63H0Pscv0QcVMo1Kv8BNysZbxcmHu3ZUYv59DS6BGYiGQ8F5lUkmsfMMlPm4DJFJdf/A==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.3.tgz", + "integrity": "sha512-cP6fIun+Zx8he4rbWvi+Oya6goKQDZK+Yq4hhlggwQBbrlOQ4qtZ+G4nxB6ZnzI9lyIb+JnvyiJnPC2AGbKSPA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.2.0", + "@vitest/spy": "3.2.3", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -2964,9 +2981,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.0.tgz", - "integrity": "sha512-gUUhaUmPBHFkrqnOokmfMGRBMHhgpICud9nrz/xpNV3/4OXCn35oG+Pl8rYYsKaTNd/FAIrqRHnwpDpmYxCYZw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.3.tgz", + "integrity": "sha512-yFglXGkr9hW/yEXngO+IKMhP0jxyFw2/qys/CK4fFUZnSltD+MU7dVYGrH8rvPcK/O6feXQA+EU33gjaBBbAng==", "dev": true, "license": "MIT", "dependencies": { @@ -2977,27 +2994,28 @@ } }, "node_modules/@vitest/runner": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.0.tgz", - "integrity": "sha512-bXdmnHxuB7fXJdh+8vvnlwi/m1zvu+I06i1dICVcDQFhyV4iKw2RExC/acavtDn93m/dRuawUObKsrNE1gJacA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.3.tgz", + "integrity": "sha512-83HWYisT3IpMaU9LN+VN+/nLHVBCSIUKJzGxC5RWUOsK1h3USg7ojL+UXQR3b4o4UBIWCYdD2fxuzM7PQQ1u8w==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.2.0", - "pathe": "^2.0.3" + "@vitest/utils": "3.2.3", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.0.tgz", - "integrity": "sha512-z7P/EneBRMe7hdvWhcHoXjhA6at0Q4ipcoZo6SqgxLyQQ8KSMMCmvw1cSt7FHib3ozt0wnRHc37ivuUMbxzG/A==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.3.tgz", + "integrity": "sha512-9gIVWx2+tysDqUmmM1L0hwadyumqssOL1r8KJipwLx5JVYyxvVRfxvMq7DaWbZZsCqZnu/dZedaZQh4iYTtneA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.0", + "@vitest/pretty-format": "3.2.3", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -3006,9 +3024,9 @@ } }, "node_modules/@vitest/spy": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.0.tgz", - "integrity": "sha512-s3+TkCNUIEOX99S0JwNDfsHRaZDDZZR/n8F0mop0PmsEbQGKZikCGpTGZ6JRiHuONKew3Fb5//EPwCP+pUX9cw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.3.tgz", + "integrity": "sha512-JHu9Wl+7bf6FEejTCREy+DmgWe+rQKbK+y32C/k5f4TBIAlijhJbRBIRIOCEpVevgRsCQR2iHRUH2/qKVM/plw==", "dev": true, "license": "MIT", "dependencies": { @@ -3019,13 +3037,13 @@ } }, "node_modules/@vitest/utils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.0.tgz", - "integrity": "sha512-gXXOe7Fj6toCsZKVQouTRLJftJwmvbhH5lKOBR6rlP950zUq9AitTUjnFoXS/CqjBC2aoejAztLPzzuva++XBw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.3.tgz", + "integrity": "sha512-4zFBCU5Pf+4Z6v+rwnZ1HU1yzOKKvDkMXZrymE2PBlbjKJRlrOxbvpfPSvJTGRIwGoahaOGvp+kbCoxifhzJ1Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.0", + "@vitest/pretty-format": "3.2.3", "loupe": "^3.1.3", "tinyrainbow": "^2.0.0" }, @@ -3034,9 +3052,9 @@ } }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -3152,25 +3170,25 @@ } }, "node_modules/aws-cdk": { - "version": "2.1017.1", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1017.1.tgz", - "integrity": "sha512-KtDdkMhfVjDeexjpMrVoSlz2mTYI5BE/KotvJ7iFbZy1G0nkpW1ImZ54TdBefeeFmZ+8DAjU3I6nUFtymyOI1A==", + "version": "2.1018.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1018.0.tgz", + "integrity": "sha512-sppVsNtFJTW4wawS/PBudHCSNHb8xwaZ2WX1mpsfwaPNyTWm0eSUVJsRbRiRBu9O/Us8pgrd4woUjfM1lgD7Kw==", "dev": true, "license": "Apache-2.0", "bin": { "cdk": "bin/cdk" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.0.0" }, "optionalDependencies": { "fsevents": "2.3.2" } }, "node_modules/aws-cdk-lib": { - "version": "2.200.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.200.0.tgz", - "integrity": "sha512-t4wGmFYuzlos7fFLFmv6ljtpMu+qYmPQnodfgUQ/BE0+y8S2MONQf9ihN+mZvsRqj96t6BwuVy3lR3UymtwGbw==", + "version": "2.200.1", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.200.1.tgz", + "integrity": "sha512-kLeDtMJPYX3qSAGPONNa3XZk8Z/K3d0As8ui10/Hbv0ohsEsphxSy0xRoxdyj58/hGxOwj1TZsBezMp+TuPPrg==", "bundleDependencies": [ "@balena/dockerignore", "case", @@ -3521,7 +3539,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/bowser": { @@ -3534,7 +3551,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -3870,9 +3886,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -3911,9 +3927,9 @@ } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3947,15 +3963,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3965,9 +3981,9 @@ } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4316,7 +4332,6 @@ "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -4647,7 +4662,6 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -4920,7 +4934,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4989,9 +5002,9 @@ } }, "node_modules/rollup": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz", - "integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.42.0.tgz", + "integrity": "sha512-LW+Vse3BJPyGJGAJt1j8pWDKPd73QM8cRXYK1IxOBgL2AGLu7Xd2YOW0M2sLUBCkF5MshXXtMApyEAEzMVMsnw==", "dev": true, "license": "MIT", "dependencies": { @@ -5005,29 +5018,36 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.41.1", - "@rollup/rollup-android-arm64": "4.41.1", - "@rollup/rollup-darwin-arm64": "4.41.1", - "@rollup/rollup-darwin-x64": "4.41.1", - "@rollup/rollup-freebsd-arm64": "4.41.1", - "@rollup/rollup-freebsd-x64": "4.41.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", - "@rollup/rollup-linux-arm-musleabihf": "4.41.1", - "@rollup/rollup-linux-arm64-gnu": "4.41.1", - "@rollup/rollup-linux-arm64-musl": "4.41.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", - "@rollup/rollup-linux-riscv64-gnu": "4.41.1", - "@rollup/rollup-linux-riscv64-musl": "4.41.1", - "@rollup/rollup-linux-s390x-gnu": "4.41.1", - "@rollup/rollup-linux-x64-gnu": "4.41.1", - "@rollup/rollup-linux-x64-musl": "4.41.1", - "@rollup/rollup-win32-arm64-msvc": "4.41.1", - "@rollup/rollup-win32-ia32-msvc": "4.41.1", - "@rollup/rollup-win32-x64-msvc": "4.41.1", + "@rollup/rollup-android-arm-eabi": "4.42.0", + "@rollup/rollup-android-arm64": "4.42.0", + "@rollup/rollup-darwin-arm64": "4.42.0", + "@rollup/rollup-darwin-x64": "4.42.0", + "@rollup/rollup-freebsd-arm64": "4.42.0", + "@rollup/rollup-freebsd-x64": "4.42.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.42.0", + "@rollup/rollup-linux-arm-musleabihf": "4.42.0", + "@rollup/rollup-linux-arm64-gnu": "4.42.0", + "@rollup/rollup-linux-arm64-musl": "4.42.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.42.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.42.0", + "@rollup/rollup-linux-riscv64-gnu": "4.42.0", + "@rollup/rollup-linux-riscv64-musl": "4.42.0", + "@rollup/rollup-linux-s390x-gnu": "4.42.0", + "@rollup/rollup-linux-x64-gnu": "4.42.0", + "@rollup/rollup-linux-x64-musl": "4.42.0", + "@rollup/rollup-win32-arm64-msvc": "4.42.0", + "@rollup/rollup-win32-ia32-msvc": "4.42.0", + "@rollup/rollup-win32-x64-msvc": "4.42.0", "fsevents": "~2.3.2" } }, + "node_modules/rollup/node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5056,7 +5076,6 @@ "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -5249,6 +5268,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-literal": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/strnum": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", @@ -5382,9 +5414,9 @@ } }, "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", - "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "dev": true, "license": "MIT", "peerDependencies": { @@ -5543,15 +5575,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.33.1.tgz", - "integrity": "sha512-AgRnV4sKkWOiZ0Kjbnf5ytTJXMUZQ0qhSVdQtDNYLPLnjsATEYhaO94GlRQwi4t4gO8FfjM6NnikHeKjUm8D7A==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.0.tgz", + "integrity": "sha512-MRpfN7uYjTrTGigFCt8sRyNqJFhjN0WwZecldaqhWm+wy0gaRt8Edb/3cuUy0zdq2opJWT6iXINKAtewnDOltQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.33.1", - "@typescript-eslint/parser": "8.33.1", - "@typescript-eslint/utils": "8.33.1" + "@typescript-eslint/eslint-plugin": "8.34.0", + "@typescript-eslint/parser": "8.34.0", + "@typescript-eslint/utils": "8.34.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5566,9 +5598,9 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", "dev": true, "license": "MIT" }, @@ -5678,9 +5710,9 @@ } }, "node_modules/vite-node": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.0.tgz", - "integrity": "sha512-8Fc5Ko5Y4URIJkmMF/iFP1C0/OJyY+VGVe9Nw6WAdZyw4bTO+eVg9mwxWkQp/y8NnAoQY3o9KAvE1ZdA2v+Vmg==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.3.tgz", + "integrity": "sha512-gc8aAifGuDIpZHrPjuHyP4dpQmYXqWw7D1GmDnWeNWP654UEXzVfQ5IHPSK5HaHkwB/+p1atpYpSdw/2kOv8iQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5701,9 +5733,9 @@ } }, "node_modules/vite/node_modules/fdir": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", - "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "dev": true, "license": "MIT", "peerDependencies": { @@ -5744,20 +5776,20 @@ } }, "node_modules/vitest": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.0.tgz", - "integrity": "sha512-P7Nvwuli8WBNmeMHHek7PnGW4oAZl9za1fddfRVidZar8wDZRi7hpznLKQePQ8JPLwSBEYDK11g+++j7uFJV8Q==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.3.tgz", + "integrity": "sha512-E6U2ZFXe3N/t4f5BwUaVCKRLHqUpk1CBWeMh78UT4VaTPH/2dyvH6ALl29JTovEPu9dVKr/K/J4PkXgrMbw4Ww==", "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.0", - "@vitest/mocker": "3.2.0", - "@vitest/pretty-format": "^3.2.0", - "@vitest/runner": "3.2.0", - "@vitest/snapshot": "3.2.0", - "@vitest/spy": "3.2.0", - "@vitest/utils": "3.2.0", + "@vitest/expect": "3.2.3", + "@vitest/mocker": "3.2.3", + "@vitest/pretty-format": "^3.2.3", + "@vitest/runner": "3.2.3", + "@vitest/snapshot": "3.2.3", + "@vitest/spy": "3.2.3", + "@vitest/utils": "3.2.3", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", @@ -5771,7 +5803,7 @@ "tinypool": "^1.1.0", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", - "vite-node": "3.2.0", + "vite-node": "3.2.3", "why-is-node-running": "^2.3.0" }, "bin": { @@ -5787,8 +5819,8 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.2.0", - "@vitest/ui": "3.2.0", + "@vitest/browser": "3.2.3", + "@vitest/ui": "3.2.3", "happy-dom": "*", "jsdom": "*" }, @@ -5991,9 +6023,9 @@ } }, "node_modules/zod": { - "version": "3.25.49", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.49.tgz", - "integrity": "sha512-JMMPMy9ZBk3XFEdbM3iL1brx4NUSejd6xr3ELrrGEfGb355gjhiAWtG3K5o+AViV/3ZfkIrCzXsZn6SbLwTR8Q==", + "version": "3.25.57", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.57.tgz", + "integrity": "sha512-6tgzLuwVST5oLUxXTmBqoinKMd3JeesgbgseXeFasKKj8Q1FCZrHnbqJOyiEvr4cVAlbug+CgIsmJ8cl/pU5FA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 2d9f127..fac59c7 100644 --- a/package.json +++ b/package.json @@ -15,22 +15,22 @@ "cdk": "cdk" }, "dependencies": { - "@aws-sdk/client-dynamodb": "3.821.0", - "@aws-sdk/lib-dynamodb": "3.821.0", - "@aws-sdk/util-dynamodb": "3.821.0", - "aws-cdk-lib": "2.200.0", + "@aws-sdk/client-dynamodb": "3.826.0", + "@aws-sdk/lib-dynamodb": "3.826.0", + "@aws-sdk/util-dynamodb": "3.826.0", + "aws-cdk-lib": "2.200.1", "constructs": "10.4.2", "uuid": "11.1.0", - "zod": "3.25.49" + "zod": "3.25.57" }, "devDependencies": { "@types/aws-lambda": "8.10.149", - "@types/node": "22.15.29", + "@types/node": "24.0.0", "@types/uuid": "10.0.0", - "@typescript-eslint/eslint-plugin": "8.33.1", - "@typescript-eslint/parser": "8.33.1", - "@vitest/coverage-v8": "3.2.0", - "aws-cdk": "2.1017.1", + "@typescript-eslint/eslint-plugin": "8.34.0", + "@typescript-eslint/parser": "8.34.0", + "@vitest/coverage-v8": "3.2.3", + "aws-cdk": "2.1018.0", "eslint": "9.28.0", "eslint-config-prettier": "10.1.5", "globals": "16.2.0", @@ -38,7 +38,7 @@ "rimraf": "6.0.1", "ts-node": "10.9.2", "typescript": "5.8.3", - "typescript-eslint": "8.33.1", - "vitest": "3.2.0" + "typescript-eslint": "8.34.0", + "vitest": "3.2.3" } }