diff --git a/.github/scripts/pause.sh b/.github/scripts/pause.sh index a0fb5f0..4ce185e 100644 --- a/.github/scripts/pause.sh +++ b/.github/scripts/pause.sh @@ -1,5 +1,6 @@ #!/bin/bash -# This script pauses AWS resources (ECS service and RDS Aurora cluster) in the current AWS account. +# This script pauses AWS resources (ECS service) in the current AWS account. +# Note: DynamoDB doesn't require pausing like RDS as it's pay-per-request set -e # Exit on error @@ -28,29 +29,7 @@ function validate_args() { fi } -# Check if Aurora DB cluster exists and get its status -function check_aurora_cluster() { - local cluster_id="${STACK_PREFIX}-aurora-${ENVIRONMENT}" - local status=$(aws rds describe-db-clusters --db-cluster-identifier "$cluster_id" \ - --query 'DBClusters[0].Status' --output text 2>/dev/null || echo "false") - echo "$status" -} -# Pause Aurora DB cluster if available -function pause_aurora_cluster() { - local cluster_id="${STACK_PREFIX}-aurora-${ENVIRONMENT}" - local status=$1 - - if [ "$status" = "false" ]; then - echo "Skipping Aurora pause operation: DB cluster does not exist" - return - elif [ "$status" = "available" ]; then - echo "Pausing Aurora cluster: $cluster_id" - aws rds stop-db-cluster --db-cluster-identifier "$cluster_id" --no-cli-pager --output json - else - echo "DB cluster is not in an available state. Current state: $status" - fi -} # Check if ECS cluster exists function check_ecs_cluster() { @@ -92,16 +71,13 @@ function pause_ecs_service() { # Main execution validate_args -# Check and pause Aurora cluster -aurora_status=$(check_aurora_cluster) -[ "$aurora_status" = "false" ] || echo "Aurora cluster status: $aurora_status" - # Check and pause ECS service ecs_status=$(check_ecs_cluster) [ "$ecs_status" = "INACTIVE" ] || echo "ECS cluster status: $ecs_status" # Perform pause operations pause_ecs_service "$ecs_status" -pause_aurora_cluster "$aurora_status" + +echo "Pause completed. Note: DynamoDB doesn't require pausing as it uses pay-per-request billing." echo "Pause operations completed" \ No newline at end of file diff --git a/.github/scripts/resume.sh b/.github/scripts/resume.sh index 98dd0e7..f765ea5 100644 --- a/.github/scripts/resume.sh +++ b/.github/scripts/resume.sh @@ -1,5 +1,6 @@ #!/bin/bash -# This script resumes AWS resources (ECS service and RDS Aurora cluster) in the specified AWS account. +# This script resumes AWS resources (ECS service) in the specified AWS account. +# Note: DynamoDB doesn't require resuming like RDS as it's always available set -e # Exit on error @@ -25,34 +26,14 @@ check_parameters() { exit 1 fi } - -# Function to check if DB cluster exists and get its status -check_db_cluster() { - local prefix=$1 - local env=$2 - local cluster_id="${prefix}-aurora-${env}" - local status=$(aws rds describe-db-clusters --db-cluster-identifier ${cluster_id} --query 'DBClusters[0].Status' --output text 2>/dev/null || echo "not-found") +# Check if ECS cluster exists +function check_ecs_cluster() { + local cluster_name="ecs-cluster-${STACK_PREFIX}-node-api-${ENVIRONMENT}" + local status=$(aws ecs describe-clusters --clusters "$cluster_name" \ + --query 'clusters[0].status' --output text 2>/dev/null || echo "INACTIVE") echo "$status" } -# Function to start DB cluster -start_db_cluster() { - local prefix=$1 - local env=$2 - local cluster_id="${prefix}-aurora-${env}" - - echo "Starting DB cluster ${cluster_id}..." - aws rds start-db-cluster --db-cluster-identifier ${cluster_id} --no-cli-pager --output json - - echo "Waiting for DB cluster to be available..." - if ! aws rds wait db-cluster-available --db-cluster-identifier ${cluster_id}; then - echo "Timeout waiting for DB cluster to become available" - return 1 - fi - - echo "DB cluster is now available" - return 0 -} # Function to resume ECS service resume_ecs_service() { @@ -60,9 +41,13 @@ resume_ecs_service() { local env=$2 local cluster="ecs-cluster-${prefix}-node-api-${env}" local service="${prefix}-node-api-${env}-service" + local cluster_status=$3 + if [ "$cluster_status" != "ACTIVE" ]; then + echo "Skipping ECS resume operation: Cluster $cluster does not exist" + return + fi echo "Resuming ECS service ${service} on cluster ${cluster}..." - # Update scaling policy aws application-autoscaling register-scalable-target \ --service-namespace ecs \ @@ -90,21 +75,11 @@ main() { local prefix=$2 echo "Starting to resume resources for environment: ${env} with stack prefix: ${prefix}" - - # Check DB cluster status - local db_status=$(check_db_cluster "$prefix" "$env") - - if [ "$db_status" == "not-found" ]; then - echo "Skipping resume operation, DB cluster does not exist" - return 0 - elif [ "$db_status" == "stopped" ]; then - start_db_cluster "$prefix" "$env" || return 1 - else - echo "DB cluster is not in a stopped state. Current state: $db_status" - fi - - # Resume ECS service - resume_ecs_service "$prefix" "$env" + # Check and pause ECS service + ecs_status=$(check_ecs_cluster) + [ "$ecs_status" = "INACTIVE" ] || echo "ECS cluster status: $ecs_status" + # Resume ECS service (DynamoDB is always available) + resume_ecs_service "$prefix" "$env" "$ecs_status" echo "Resources have been resumed successfully" } diff --git a/.github/workflows/.builds.yml b/.github/workflows/.builds.yml index 2d04aa3..6f5b4c6 100644 --- a/.github/workflows/.builds.yml +++ b/.github/workflows/.builds.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: # Only building frontend containers to run PR based e2e tests - package: [backend, migrations, frontend] + package: [backend, frontend] timeout-minutes: 10 steps: - uses: bcgov/action-builder-ghcr@v4.0.0 diff --git a/.github/workflows/.deployer.yml b/.github/workflows/.deployer.yml index 83b4084..e71807c 100644 --- a/.github/workflows/.deployer.yml +++ b/.github/workflows/.deployer.yml @@ -84,7 +84,6 @@ jobs: env: target_env: ${{ inputs.environment_name }} aws_license_plate: ${{ secrets.AWS_LICENSE_PLATE }} - flyway_image: ghcr.io/${{github.repository}}/migrations:${{inputs.tag}} api_image: ghcr.io/${{github.repository}}/backend:${{inputs.tag}} app_env: ${{inputs.app_env}} stack_prefix: ${{ inputs.stack_prefix }} @@ -98,7 +97,6 @@ jobs: env: target_env: ${{ inputs.environment_name }} aws_license_plate: ${{ secrets.AWS_LICENSE_PLATE }} - flyway_image: ghcr.io/${{github.repository}}/migrations:${{inputs.tag}} api_image: ghcr.io/${{github.repository}}/backend:${{inputs.tag}} app_env: ${{inputs.app_env}} stack_prefix: ${{ inputs.stack_prefix }} @@ -115,7 +113,6 @@ jobs: env: target_env: ${{ inputs.environment_name }} aws_license_plate: ${{ secrets.AWS_LICENSE_PLATE }} - flyway_image: ghcr.io/${{github.repository}}/migrations:${{inputs.tag}} api_image: ghcr.io/${{github.repository}}/backend:${{inputs.tag}} app_env: ${{inputs.app_env}} stack_prefix: ${{ inputs.stack_prefix }} diff --git a/.github/workflows/.e2e.yml b/.github/workflows/.e2e.yml index c69c2a5..240aa19 100644 --- a/.github/workflows/.e2e.yml +++ b/.github/workflows/.e2e.yml @@ -26,6 +26,7 @@ jobs: BACKEND_IMAGE: ghcr.io/${{ github.repository }}/backend:${{ inputs.tag }} FLYWAY_IMAGE: ghcr.io/${{ github.repository }}/migrations:${{ inputs.tag }} FRONTEND_IMAGE: ghcr.io/${{ github.repository }}/frontend:${{ inputs.tag }} + IS_OFFLINE: 'true' # this is for backend to run in offline mode run: docker compose up -d --wait continue-on-error: true - name: Docker Compose Logs diff --git a/.github/workflows/.tests.yml b/.github/workflows/.tests.yml index 889b7f0..5f27b72 100644 --- a/.github/workflows/.tests.yml +++ b/.github/workflows/.tests.yml @@ -16,18 +16,6 @@ jobs: if: ${{ ! github.event.pull_request.draft }} runs-on: ubuntu-24.04 timeout-minutes: 5 - services: - postgres: - image: postgis/postgis:17-3.5 # Updated to PostgreSQL 17 with PostGIS 3.5 - env: - POSTGRES_PASSWORD: default - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 5432:5432 steps: - uses: bcgov-nr/action-test-and-analyse@v1.3.0 env: diff --git a/AWS-DEPLOY.md b/AWS-DEPLOY.md new file mode 100644 index 0000000..4b4f744 --- /dev/null +++ b/AWS-DEPLOY.md @@ -0,0 +1,148 @@ +# How To Deploy to AWS using Terraform + +## Prerequisites + +1. BCGov AWS account/namespace. + +## Steps to be taken in the console(UI) to setup the secret in github for terraform deployment + +1. [Login to console via IDIR MFA](https://login.nimbus.cloud.gov.bc.ca/) +2. Navigate to IAM, click on policies on left hand menu. +3. Click on `Create policy` button and switch from visual to JSON then paste the below snippet + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "IAM", + "Effect": "Allow", + "Action": ["iam:*"], + "Resource": ["*"] + }, + { + "Sid": "S3", + "Effect": "Allow", + "Action": ["s3:*"], + "Resource": ["*"] + }, + { + "Sid": "Cloudfront", + "Effect": "Allow", + "Action": ["cloudfront:*"], + "Resource": ["*"] + }, + { + "Sid": "ecs", + "Effect": "Allow", + "Action": ["ecs:*"], + "Resource": "*" + }, + { + "Sid": "ecr", + "Effect": "Allow", + "Action": ["ecr:*"], + "Resource": "*" + }, + { + "Sid": "Dynamodb", + "Effect": "Allow", + "Action": ["dynamodb:*"], + "Resource": ["*"] + }, + { + "Sid": "APIgateway", + "Effect": "Allow", + "Action": ["apigateway:*"], + "Resource": ["*"] + }, + { + "Sid": "Cloudwatch", + "Effect": "Allow", + "Action": ["cloudwatch:*"], + "Resource": "*" + }, + { + "Sid": "EC2", + "Effect": "Allow", + "Action": ["ec2:*"], + "Resource": "*" + }, + { + "Sid": "Autoscaling", + "Effect": "Allow", + "Action": ["autoscaling:*"], + "Resource": "*" + }, + { + "Sid": "KMS", + "Effect": "Allow", + "Action": ["kms:*"], + "Resource": "*" + }, + { + "Sid": "SecretsManager", + "Effect": "Allow", + "Action": ["secretsmanager:*"], + "Resource": "*" + }, + { + "Sid": "CloudWatchLogs", + "Effect": "Allow", + "Action": ["logs:*"], + "Resource": "*" + }, + { + "Sid": "WAF", + "Effect": "Allow", + "Action": ["wafv2:*"], + "Resource": "*" + }, + { + "Sid": "ELB", + "Effect": "Allow", + "Action": ["elasticloadbalancing:*"], + "Resource": "*" + }, + { + "Sid": "AppAutoScaling", + "Effect": "Allow", + "Action": ["application-autoscaling:*"], + "Resource": "*" + } + + ] +} +``` +4. Then create a role by clicking `create role` button and then selecting (custom trust policy radio button). +5. Paste the below JSON after making modifications to set trust relationships of the role with your github repo( ex: bcgov/quickstart-aws-containers) . + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Federated": "arn:aws:iam:::oidc-provider/token.actions.githubusercontent.com" + }, + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringLike": { + "token.actions.githubusercontent.com:sub": "repo::*" + }, + "ForAllValues:StringEquals": { + "token.actions.githubusercontent.com:aud": "sts.amazonaws.com", + "token.actions.githubusercontent.com:iss": "https://token.actions.githubusercontent.com" + } + } + } + ] +} +``` +6. Click on Next button, then add the policies after searching for it and then enabling it by checking the checkbox. +7. Finally give a role name for ex: `GHA_CI_CD` and then click on `create role` button. +7. After the role is created copy the ARN, it would be like `arn:aws:iam:::role/` , `role_name` is what was created on step 4. +8. Paste this value into github secrets, repository secret or environment secret based on your needs. The key to use is `AWS_DEPLOY_ROLE_ARN` +9. Paste the license plate value( 6 alphanumeric characters ex: `ab9okj`) without the env as a repository secret. The Key to use is `AWS_LICENSE_PLATE` +10. After this the github action workflows would be able to deploy the stack to AWS. \ No newline at end of file diff --git a/GHA.md b/GHA.md index 32ef1d4..0ed349d 100644 --- a/GHA.md +++ b/GHA.md @@ -198,15 +198,15 @@ The workflows in this repository are organized into three main categories: - Manual workflow dispatch with environment selection - Workflow call from other workflows -**Purpose**: Cost optimization by pausing resources outside of working hours. +**Purpose**: Cost optimization by pausing ECS services outside of working hours. Note: DynamoDB doesn't require pausing as it uses pay-per-request billing. **Inputs**: - `app_env`: Environment to pause resources for (dev, test, prod, or all) **Details**: -- Identifies resources that can be safely paused in specified environment(s) +- Identifies ECS services that can be safely paused in specified environment(s) - Scales down ECS services to zero -- Stops RDS clusters +- Note: DynamoDB tables remain available as they use pay-per-request billing with no idle costs - Uses AWS CLI commands to pause specific services - Runs on a schedule to automatically pause resources - Can be targeted to specific environments (dev, test, prod) @@ -218,15 +218,15 @@ The workflows in this repository are organized into three main categories: - Manual workflow dispatch with environment selection - Workflow call from other workflows (like PR deployment) -**Purpose**: Resume paused resources at the start of the working day or on-demand. +**Purpose**: Resume paused ECS services at the start of the working day or on-demand. Note: DynamoDB is always available. **Inputs**: - `app_env`: Environment to resume resources for (dev, test, prod, or all) **Details**: -- Starts RDS clusters in specified environment(s) - Scales ECS services back to their configured capacity - Ensures all services are in a ready state +- Note: DynamoDB tables are always available and don't require resuming - Can be targeted to specific environments (dev, test, prod) ### `prune-env.yml` @@ -257,7 +257,7 @@ The workflows use the following environment configurations: - Can be paused/resumed independently from other environments 3. **Production (prod)**: Used for live production deployments via the release workflow - Uses a mix of FARGATE (base=1, 20%) and FARGATE_SPOT (80%) for reliability and cost-effectiveness - - Database credentials stored and retrieved securely from AWS Secrets Manager + - DynamoDB tables with deletion protection enabled for production environments - API Gateway with VPC Link for secure backend access - Requires strict environment approval for resource management operations - Can be excluded from automatic pause/resume schedules if needed for 24/7 availability diff --git a/README.md b/README.md index 7bddc16..a96a9bd 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,21 @@ -[![Merge](https://github.com/bcgov/quickstart-aws-containers/actions/workflows/merge.yml/badge.svg)](https://github.com/bcgov/quickstart-aws-containers/actions/workflows/merge.yml) -[![PR](https://github.com/bcgov/quickstart-aws-containers/actions/workflows/pr-open.yml/badge.svg)](https://github.com/bcgov/quickstart-aws-containers/actions/workflows/pr-open.yml) -[![PR Validate](https://github.com/bcgov/quickstart-aws-containers/actions/workflows/pr-validate.yml/badge.svg)](https://github.com/bcgov/quickstart-aws-containers/actions/workflows/pr-validate.yml) -[![CodeQL](https://github.com/bcgov/quickstart-aws-containers/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/bcgov/quickstart-aws-containers/actions/workflows/github-code-scanning/codeql) -[![Pause AWS Resources](https://github.com/bcgov/quickstart-aws-containers/actions/workflows/pause-resources.yml/badge.svg)](https://github.com/bcgov/quickstart-aws-containers/actions/workflows/pause-resources.yml) -[![Pause AWS Resources](https://github.com/bcgov/quickstart-aws-containers/actions/workflows/pause-resources.yml/badge.svg)](https://github.com/bcgov/quickstart-aws-containers/actions/workflows/pause-resources.yml) -# Quickstart for AWS using Aurora Serverless v2, ECS Fargate, and CloudFront +[![Merge](https://github.com/bcgov/quickstart-aws-serverless/actions/workflows/merge.yml/badge.svg)](https://github.com/bcgov/quickstart-aws-serverless/actions/workflows/merge.yml) +[![PR](https://github.com/bcgov/quickstart-aws-serverless/actions/workflows/pr-open.yml/badge.svg)](https://github.com/bcgov/quickstart-aws-serverless/actions/workflows/pr-open.yml) +[![PR Validate](https://github.com/bcgov/quickstart-aws-serverless/actions/workflows/pr-validate.yml/badge.svg)](https://github.com/bcgov/quickstart-aws-serverless/actions/workflows/pr-validate.yml) +[![CodeQL](https://github.com/bcgov/quickstart-aws-serverless/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/bcgov/quickstart-aws-serverless/actions/workflows/github-code-scanning/codeql) +[![Pause AWS Resources](https://github.com/bcgov/quickstart-aws-serverless/actions/workflows/pause-resources.yml/badge.svg)](https://github.com/bcgov/quickstart-aws-serverless/actions/workflows/pause-resources.yml) +[![Pause AWS Resources](https://github.com/bcgov/quickstart-aws-serverless/actions/workflows/pause-resources.yml/badge.svg)](https://github.com/bcgov/quickstart-aws-serverless/actions/workflows/pause-resources.yml) +# Quickstart for AWS using DynamoDB, ECS Fargate, and CloudFront This template repository provides a ready-to-deploy containerized application stack for AWS, developed by BC Government. It includes a complete application architecture with: -- **Aurora Serverless v2** PostgreSQL database with PostGIS extension +- **DynamoDB** NoSQL database with pay-per-request billing - **ECS Fargate** with mixed FARGATE/FARGATE_SPOT capacity providers for cost-optimized backend services -- **Flyway Migrations** automated through ECS tasks for database schema management - **API Gateway** with VPC link integration for secure backend access - **CloudFront** for frontend content delivery with WAF protection -- **NestJS** TypeScript backend API with Prisma ORM +- **NestJS** TypeScript backend API with AWS SDK for DynamoDB - **React** with Vite for the frontend application - **Terragrunt/Terraform** for infrastructure-as-code deployment - **GitHub Actions** for CI/CD pipeline automation -- **AWS Secrets Manager** integration for secure credential management Use this repository as a starting point to quickly deploy a modern, scalable web application on AWS infrastructure. @@ -32,7 +30,7 @@ Use this repository as a starting point to quickly deploy a modern, scalable web # Folder Structure ``` -/quickstart-aws-containers +/quickstart-aws-serverless ├── .github/ # GitHub workflows and actions for CI/CD │ └── workflows/ # GitHub Actions workflow definitions ├── terraform/ # Terragrunt configuration files for environment management @@ -42,17 +40,14 @@ Use this repository as a starting point to quickly deploy a modern, scalable web ├── infrastructure/ # Terraform code for each AWS infrastructure component │ ├── api/ # ECS Fargate API configuration (ALB, API Gateway, autoscaling) │ ├── frontend/ # CloudFront with WAF configuration -│ └── database/ # Aurora Serverless v2 PostgreSQL configuration +│ └── database/ # DynamoDB table configuration ├── backend/ # NestJS backend API code │ ├── src/ # Source code with controllers, services, and modules -│ ├── prisma/ # Prisma ORM schema and migrations │ └── Dockerfile # Container definition for backend service ├── frontend/ # Vite + React SPA │ ├── src/ # React components, routes, and services │ ├── e2e/ # End-to-end tests using Playwright │ └── Dockerfile # Container definition for frontend service -├── migrations/ # Flyway migrations for database schema management -│ └── sql/ # SQL migration scripts ├── tests/ # Test suites beyond component-level tests │ ├── integration/ # Integration tests across services │ └── load/ # Load testing scripts for performance testing @@ -79,22 +74,18 @@ Use this repository as a starting point to quickly deploy a modern, scalable web - Flyway migration task execution for database schema management - IAM roles and security group configurations - AWS Secrets Manager integration for database credentials - - **frontend/**: Sets up CloudFront distribution with WAF rules for content delivery. - - **database/**: Configures Aurora Serverless v2 PostgreSQL database with networking. + - **frontend/**: Sets up CloudFront distribution with WAF rules for content delivery. + - **database/**: Configures DynamoDB table with proper indexes and settings. - **backend/**: NestJS backend application with TypeScript. - **src/**: Application code organized by feature modules. - - **prisma/**: Database ORM schema definitions and connection handling. - - Includes testing infrastructure and containerization setup. + - Includes AWS SDK integration for DynamoDB operations. - **frontend/**: React-based single-page application built with Vite. - **src/**: React components and application logic. - **e2e/**: End-to-end tests with Playwright for UI validation. - Includes deployment configuration for AWS. -- **migrations/**: Flyway database migration scripts and configuration. - - **sql/**: SQL scripts for schema evolution that Flyway executes in order. - - **tests/**: Cross-component test suites to validate the application at a higher level. - **integration/**: Tests validating interactions between services. - **load/**: Performance testing scripts to ensure scalability. @@ -126,23 +117,48 @@ docker-compose down ## Running Locally without Docker (Complex) Prerequisites: - 1. Install JDK 17 and above. - 2. Install Node.js 22 and above. - 3. Install Postgres 17.4 with Postgis extension. - 4. Download flyway.jar file -Once all the softwares are installed follow below steps. + 1. Install Node.js 22 and above. + 2. Install AWS CLI and configure with credentials for DynamoDB Local. + 3. Install DynamoDB Local for development. + +Once all the software is installed follow below steps. -1. Run Postgres DB (better as a service on OS). -2. Run flyway migrations (this needs to be run everytime changes to migrations folder happen) +1. Start DynamoDB Local: ```sh -java -jar flyway.jar -url=jdbc:postgresql://$posgtres_host:5432/$postgres_db -user=$POSTGRES_USER -password=$POSTGRES_PASSWORD -baselineOnMigrate=true -schemas=$FLYWAY_DEFAULT_SCHEMA migrate +# Download and run DynamoDB Local +java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar -sharedDb -inMemory ``` -3. Run backend from root of folder. + +2. Create the local table and add sample data: +```sh +# Create table +aws dynamodb create-table \ + --endpoint-url http://localhost:8000 \ + --table-name users-local \ + --attribute-definitions AttributeName=id,AttributeType=S AttributeName=email,AttributeType=S \ + --key-schema AttributeName=id,KeyType=HASH \ + --global-secondary-indexes IndexName=EmailIndex,KeySchema=[{AttributeName=email,KeyType=HASH}],Projection={ProjectionType=ALL} \ + --billing-mode PAY_PER_REQUEST + +# Add sample data +aws dynamodb put-item \ + --endpoint-url http://localhost:8000 \ + --table-name users-local \ + --item '{"id":{"S":"1"}, "name":{"S":"John"}, "email":{"S":"John.ipsum@test.com"}}' +``` + +3. Run backend from root of folder: ```sh cd backend -npm run start:dev or npm run start:debug +export DYNAMODB_TABLE_NAME=users-local +export DYNAMODB_ENDPOINT=http://localhost:8000 +export AWS_REGION=ca-central-1 +export AWS_ACCESS_KEY_ID=dummy +export AWS_SECRET_ACCESS_KEY=dummy +npm run start:dev ``` -4. Run Frontend from root of folder. + +4. Run Frontend from root of folder: ```sh cd frontend npm run dev @@ -188,7 +204,7 @@ terragrunt plan terragrunt apply ``` -For detailed deployment instructions, refer to the [AWS deployment setup guide](https://github.com/bcgov/quickstart-aws-containers/wiki/Deploy-To-AWS-Using-Terraform). +For detailed deployment instructions, refer to the [AWS deployment setup guide](./AWS-DEPLOY.md). # CI/CD Workflows @@ -237,8 +253,8 @@ The repository includes a comprehensive set of GitHub Actions workflows that aut ### Resource Management - **Cost Optimization**: - - `pause-resources.yml`: Pauses resources in specified environments (dev/test/prod) either on schedule, manually, or automatically after deployment - - `resume-resources.yml`: Resumes resources in specified environments either on schedule, manually, or automatically before deployment + - `pause-resources.yml`: Pauses ECS services in specified environments (dev/test/prod) either on schedule, manually, or automatically after deployment. Note: DynamoDB doesn't require pausing as it uses pay-per-request billing. + - `resume-resources.yml`: Resumes ECS services in specified environments either on schedule, manually, or automatically before deployment - **Workflow Integration**: - Resources are automatically resumed before deployments in the merge workflow - Resources are automatically paused after successful deployments to save costs @@ -268,10 +284,10 @@ For detailed documentation on all GitHub Actions workflows, including their trig - Supports ANY method with proxy path integration #### Database Integration -- Automatically connects to Aurora PostgreSQL using endpoint discovery -- Uses master credentials stored in Secrets Manager -- Applies schema migrations using Flyway in a dedicated ECS task -- Supports read/write splitting with separate endpoints for read-only operations +- Automatically connects to DynamoDB using the AWS SDK +- Uses pay-per-request billing model for cost efficiency +- Supports both local development with DynamoDB Local and production deployment +- No migration scripts needed - DynamoDB tables are created via Terraform # Customizing the Template diff --git a/backend/Dockerfile b/backend/Dockerfile index 3764e21..f0aced8 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -4,7 +4,6 @@ FROM node:22.14.0-slim AS build # Copy, build static files; see .dockerignore for exclusions WORKDIR /app COPY . ./ -ENV PRISMA_CLI_BINARY_TARGETS=debian-openssl-3.0.x RUN npm run deploy # Dependencies @@ -13,7 +12,6 @@ FROM node:22.14.0-slim AS dependencies # Copy, build static files; see .dockerignore for exclusions WORKDIR /app COPY . ./ -ENV PRISMA_CLI_BINARY_TARGETS=debian-openssl-3.0.x RUN npm ci --ignore-scripts --no-update-notifier --omit=dev # Deploy using minimal Distroless image @@ -23,9 +21,6 @@ ENV NODE_ENV=production # Copy app and dependencies WORKDIR /app COPY --from=dependencies /app/node_modules ./node_modules -COPY --from=build /app/node_modules/@prisma ./node_modules/@prisma -COPY --from=build /app/node_modules/.prisma ./node_modules/.prisma -COPY --from=build /app/node_modules/prisma ./node_modules/prisma COPY --from=build /app/dist ./dist # Boilerplate, not used in OpenShift/Kubernetes diff --git a/backend/package-lock.json b/backend/package-lock.json index d8c6564..26e2c3c 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -6,6 +6,8 @@ "": { "license": "Apache-2.0", "dependencies": { + "@aws-sdk/client-dynamodb": "^3.698.0", + "@aws-sdk/lib-dynamodb": "^3.698.0", "@nestjs/cli": "^11.0.0", "@nestjs/common": "^11.0.0", "@nestjs/config": "^4.0.0", @@ -15,17 +17,15 @@ "@nestjs/swagger": "^11.0.0", "@nestjs/terminus": "^11.0.0", "@nestjs/testing": "^11.0.0", - "@prisma/client": "^6.1.0", "dotenv": "^16.4.7", "express-prom-bundle": "^8.0.0", "helmet": "^8.0.0", "nest-winston": "^1.9.7", - "pg": "^8.13.1", - "prisma": "^6.1.0", "prom-client": "^15.1.3", "reflect-metadata": "^0.2.2", "rimraf": "^6.0.1", "swagger-ui-express": "^5.0.1", + "uuid": "^11.0.3", "winston": "^3.17.0" }, "devDependencies": { @@ -34,6 +34,7 @@ "@types/express": "^5.0.0", "@types/node": "^22.0.0", "@types/supertest": "^6.0.0", + "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", "@vitest/coverage-v8": "^3.0.0", @@ -187,508 +188,878 @@ "tslib": "^2.1.0" } }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "license": "MIT", + "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": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" + "@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": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/code-frame/node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "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", + "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": ">=6.9.0" + "node": ">=14.0.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==", - "license": "MIT", + "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": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", - "dev": true, - "license": "MIT", + "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": { - "@babel/types": "^7.27.3" - }, - "bin": { - "parser": "bin/babel-parser.js" + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/types": { - "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", + "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": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "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": ">=6.9.0" + "node": ">=14.0.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", + "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": ">=18" + "node": ">=14.0.0" } }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "license": "MIT", - "optional": true, + "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": ">=0.1.90" + "node": ">=14.0.0" } }, - "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", + "node_modules/@aws-sdk/client-dynamodb": { + "version": "3.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.828.0.tgz", + "integrity": "sha512-UU8k3wXE/E8PiQlxSFc/7GQC65cuuF27UFRjAxQ8gFHy6WRsO7SsTRoJekbV8W6dBEbiYP1dpUCCccoDF8Owgw==", + "license": "Apache-2.0", "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/credential-provider-node": "3.828.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.828.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.828.0", + "@smithy/config-resolver": "^4.1.4", + "@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.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.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.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", + "@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": ">=12" + "node": ">=18.0.0" } }, - "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, + "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.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.828.0.tgz", + "integrity": "sha512-qxw8JcPTaFaBwTBUr4YmLajaMh3En65SuBWAKEtjctbITRRekzR7tvr/TkwoyVOh+XoAtkwOn+BQeQbX+/wgHw==", + "license": "Apache-2.0", "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.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.828.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.828.0", + "@smithy/config-resolver": "^4.1.4", + "@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.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.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.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", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@dabh/diagnostics": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", - "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", - "license": "MIT", + "node_modules/@aws-sdk/core": { + "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": { - "colorspace": "1.1.x", - "enabled": "2.0.x", - "kuler": "^2.0.0" + "@aws-sdk/types": "3.821.0", + "@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.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" + }, + "engines": { + "node": ">=18.0.0" } }, - "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" - ], + "node_modules/@aws-sdk/credential-provider-env": { + "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.826.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" + "node": ">=18.0.0" } }, - "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" - ], + "node_modules/@aws-sdk/credential-provider-http": { + "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.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.3", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "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" - ], + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.828.0.tgz", + "integrity": "sha512-T3DJMo2/j7gCPpFg2+xEHWgua05t8WP89ye7PaZxA2Fc6CgScHkZsJZTri1QQIU2h+eOZ75EZWkeFLIPgN0kRQ==", + "license": "Apache-2.0", + "dependencies": { + "@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.828.0", + "@aws-sdk/credential-provider-web-identity": "3.828.0", + "@aws-sdk/nested-clients": "3.828.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" + "node": ">=18.0.0" } }, - "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" - ], + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.828.0.tgz", + "integrity": "sha512-9z3iPwVYOQYNzVZj8qycZaS/BOSKRXWA+QVNQlfEnQ4sA4sOcKR4kmV2h+rJcuBsSFfmOF62ZDxyIBGvvM4t/w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.826.0", + "@aws-sdk/credential-provider-http": "3.826.0", + "@aws-sdk/credential-provider-ini": "3.828.0", + "@aws-sdk/credential-provider-process": "3.826.0", + "@aws-sdk/credential-provider-sso": "3.828.0", + "@aws-sdk/credential-provider-web-identity": "3.828.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" + "node": ">=18.0.0" } }, - "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" - ], + "node_modules/@aws-sdk/credential-provider-process": { + "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.826.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" + "node": ">=18.0.0" } }, - "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" - ], + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.828.0.tgz", + "integrity": "sha512-9CEAXzUDSzOjOCb3XfM15TZhTaM+l07kumZyx2z8NC6T2U4qbCJqn4h8mFlRvYrs6cBj2SN40sD3r5Wp0Cq2Kw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.828.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/token-providers": "3.828.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" + "node": ">=18.0.0" } }, - "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" - ], + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.828.0.tgz", + "integrity": "sha512-MguDhGHlQBeK9CQ/P4NOY0whAJ4HJU4x+f1dphg3I1sGlccFqfB8Moor2vXNKu0Th2kvAwkn9pr7gGb/+NGR9g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/nested-clients": "3.828.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" + "node": ">=18.0.0" } }, - "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" - ], + "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" + "node": ">=18.0.0" } }, - "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" - ], + "node_modules/@aws-sdk/lib-dynamodb": { + "version": "3.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.828.0.tgz", + "integrity": "sha512-EQppnIYHUyRxNcHnzVcQhPRi6KvtzsVkjQcsiNYuQjVPiI/shiQRHXJyRES74nFESYgh1AXTBaZUyO1bgy1TDQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/util-dynamodb": "3.828.0", + "@smithy/core": "^3.5.3", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.828.0" } }, - "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" - ], + "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" + "node": ">=18.0.0" } }, - "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" - ], + "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" + "node": ">=18.0.0" } }, - "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" - ], + "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" + "node": ">=18.0.0" } }, - "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" - ], + "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" + "node": ">=18.0.0" } }, - "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" - ], + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.828.0.tgz", + "integrity": "sha512-nixvI/SETXRdmrVab4D9LvXT3lrXkwAWGWk2GVvQvzlqN1/M/RfClj+o37Sn4FqRkGH9o9g7Fqb1YqZ4mqDAtA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@smithy/core": "^3.5.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "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" - ], + "node_modules/@aws-sdk/nested-clients": { + "version": "3.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.828.0.tgz", + "integrity": "sha512-xmeOILiR9LvfC8MctgeRXXN8nQTwbOvO4wHvgE8tDRsjnBpyyO0j50R4+viHXdMUGtgGkHEXRv8fFNBq54RgnA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.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.828.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.828.0", + "@smithy/config-resolver": "^4.1.4", + "@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.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.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.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", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "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" - ], + "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" + "node": ">=18.0.0" } }, - "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" - ], + "node_modules/@aws-sdk/token-providers": { + "version": "3.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.828.0.tgz", + "integrity": "sha512-JdOjI/TxkfQpY/bWbdGMdCiePESXTbtl6MfnJxz35zZ3tfHvBnxAWCoYJirdmjzY/j/dFo5oEyS6mQuXAG9w2w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/nested-clients": "3.828.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" + "node": ">=18.0.0" } }, - "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" - ], + "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" + "node": ">=18.0.0" } }, - "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" - ], + "node_modules/@aws-sdk/util-dynamodb": { + "version": "3.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.828.0.tgz", + "integrity": "sha512-TcNPaacxqZsFxObjuZPV5j7Mh7bdVPoFxdwO6utfg8FFdSsbg7h2jF26FiBF0sVfTvGvItJc9tUjE1jO1auADA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.828.0" } }, - "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" - ], + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.828.0.tgz", + "integrity": "sha512-RvKch111SblqdkPzg3oCIdlGxlQs+k+P7Etory9FmxPHyPDvsP1j1c74PmgYqtzzMWmoXTjd+c9naUHh9xG8xg==", + "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" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/openbsd-x64": { + "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.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.828.0.tgz", + "integrity": "sha512-LdN6fTBzTlQmc8O8f1wiZN0qF3yBWVGis7NwpWK7FUEzP9bEZRxYfIkV9oV9zpt6iNRze1SedK3JQVB/udxBoA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.828.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/@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/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "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": { + "@babel/types": "^7.27.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "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": { + "@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/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "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/@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/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "license": "MIT", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { "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==", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", "cpu": [ - "x64" + "ppc64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "openbsd" + "aix" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/sunos-x64": { + "node_modules/@esbuild/android-arm": { "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==", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", "cpu": [ - "x64" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "sunos" + "android" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/win32-arm64": { + "node_modules/@esbuild/android-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==", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", "cpu": [ "arm64" ], @@ -696,468 +1067,569 @@ "license": "MIT", "optional": true, "os": [ - "win32" + "android" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/win32-ia32": { + "node_modules/@esbuild/android-x64": { "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", - "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", "cpu": [ - "ia32" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" + "android" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/win32-x64": { + "node_modules/@esbuild/darwin-arm64": { "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", - "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" + "darwin" ], "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==", + "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", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, + "optional": true, + "os": [ + "darwin" + ], "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": ">=18" } }, - "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==", + "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": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=18" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "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", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.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" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=18" } }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "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", - "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" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "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==", + "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", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "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" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "*" + "node": ">=18" } }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "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": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=18" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", + "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": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10.10.0" + "node": ">=18" } }, - "node_modules/@humanwhocodes/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==", + "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", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@humanwhocodes/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==", + "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": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "*" + "node": ">=18" } }, - "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==", + "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": "Apache-2.0", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=18" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", + "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": "BSD-3-Clause" - }, - "node_modules/@inquirer/checkbox": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.8.tgz", - "integrity": "sha512-d/QAsnwuHX2OPolxvYcgSj7A9DO9H6gVOy2DvBTx+P2LH2iRTo/RSGV3iwCzW024nP9hw98KIuDmdyhZQj1UQg==", "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.1.13", - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", - "ansi-escapes": "^4.3.2", - "yoctocolors-cjs": "^2.1.2" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } } }, - "node_modules/@inquirer/confirm": { - "version": "5.1.12", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.12.tgz", - "integrity": "sha512-dpq+ielV9/bqgXRUbNH//KsY6WEw9DrGPmipkpmgC1Y46cwuBTNx7PXFWTjc3MQ+urcc0QxoVHcMI0FW4Ok0hg==", + "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", - "dependencies": { - "@inquirer/core": "^10.1.13", - "@inquirer/type": "^3.0.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } } }, - "node_modules/@inquirer/core": { - "version": "10.1.13", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.13.tgz", - "integrity": "sha512-1viSxebkYN2nJULlzCxES6G9/stgHSepZ9LqqfdIGPHj5OHhiBUXVS0a6R0bEC2A+VL4D9w6QB66ebCr6HGllA==", + "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", - "dependencies": { - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", - "ansi-escapes": "^4.3.2", - "cli-width": "^4.1.0", - "mute-stream": "^2.0.0", - "signal-exit": "^4.1.0", - "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.2" - }, + "optional": true, + "os": [ + "netbsd" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } } }, - "node_modules/@inquirer/editor": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.13.tgz", - "integrity": "sha512-WbicD9SUQt/K8O5Vyk9iC2ojq5RHoCLK6itpp2fHsWe44VxxcA9z3GTWlvjSTGmMQpZr+lbVmrxdHcumJoLbMA==", + "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", - "dependencies": { - "@inquirer/core": "^10.1.13", - "@inquirer/type": "^3.0.7", - "external-editor": "^3.1.0" - }, + "optional": true, + "os": [ + "netbsd" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } } }, - "node_modules/@inquirer/expand": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.15.tgz", - "integrity": "sha512-4Y+pbr/U9Qcvf+N/goHzPEXiHH8680lM3Dr3Y9h9FFw4gHS+zVpbj8LfbKWIb/jayIB4aSO4pWiBTrBYWkvi5A==", + "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", - "dependencies": { - "@inquirer/core": "^10.1.13", - "@inquirer/type": "^3.0.7", - "yoctocolors-cjs": "^2.1.2" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } } }, - "node_modules/@inquirer/figures": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.12.tgz", - "integrity": "sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==", + "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/@inquirer/input": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.1.12.tgz", - "integrity": "sha512-xJ6PFZpDjC+tC1P8ImGprgcsrzQRsUh9aH3IZixm1lAZFK49UGHxM3ltFfuInN2kPYNfyoPRh+tU4ftsjPLKqQ==", + "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", - "dependencies": { - "@inquirer/core": "^10.1.13", - "@inquirer/type": "^3.0.7" - }, + "optional": true, + "os": [ + "sunos" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } } }, - "node_modules/@inquirer/number": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.15.tgz", - "integrity": "sha512-xWg+iYfqdhRiM55MvqiTCleHzszpoigUpN5+t1OMcRkJrUrw7va3AzXaxvS+Ak7Gny0j2mFSTv2JJj8sMtbV2g==", + "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", - "dependencies": { - "@inquirer/core": "^10.1.13", - "@inquirer/type": "^3.0.7" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } } }, - "node_modules/@inquirer/password": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.15.tgz", - "integrity": "sha512-75CT2p43DGEnfGTaqFpbDC2p2EEMrq0S+IRrf9iJvYreMy5mAWj087+mdKyLHapUEPLjN10mNvABpGbk8Wdraw==", + "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", - "dependencies": { - "@inquirer/core": "^10.1.13", - "@inquirer/type": "^3.0.7", - "ansi-escapes": "^4.3.2" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } } }, - "node_modules/@inquirer/prompts": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.4.1.tgz", - "integrity": "sha512-UlmM5FVOZF0gpoe1PT/jN4vk8JmpIWBlMvTL8M+hlvPmzN89K6z03+IFmyeu/oFCenwdwHDr2gky7nIGSEVvlA==", + "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", - "dependencies": { - "@inquirer/checkbox": "^4.1.5", - "@inquirer/confirm": "^5.1.9", - "@inquirer/editor": "^4.2.10", - "@inquirer/expand": "^4.0.12", - "@inquirer/input": "^4.1.9", - "@inquirer/number": "^3.0.12", - "@inquirer/password": "^4.0.12", - "@inquirer/rawlist": "^4.0.12", - "@inquirer/search": "^3.0.12", - "@inquirer/select": "^4.1.1" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } } }, - "node_modules/@inquirer/rawlist": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.3.tgz", - "integrity": "sha512-7XrV//6kwYumNDSsvJIPeAqa8+p7GJh7H5kRuxirct2cgOcSWwwNGoXDRgpNFbY/MG2vQ4ccIWCi8+IXXyFMZA==", + "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": { - "@inquirer/core": "^10.1.13", - "@inquirer/type": "^3.0.7", - "yoctocolors-cjs": "^2.1.2" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">=18" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "peerDependencies": { - "@types/node": ">=18" + "funding": { + "url": "https://opencollective.com/eslint" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@inquirer/search": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.15.tgz", - "integrity": "sha512-YBMwPxYBrADqyvP4nNItpwkBnGGglAvCLVW8u4pRmmvOsHUtCAUIMbUrLX5B3tFL1/WsLGdQ2HNzkqswMs5Uaw==", + "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/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.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": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "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/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/@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": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/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/@humanwhocodes/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/@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/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@inquirer/checkbox": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.8.tgz", + "integrity": "sha512-d/QAsnwuHX2OPolxvYcgSj7A9DO9H6gVOy2DvBTx+P2LH2iRTo/RSGV3iwCzW024nP9hw98KIuDmdyhZQj1UQg==", "license": "MIT", "dependencies": { "@inquirer/core": "^10.1.13", "@inquirer/figures": "^1.0.12", "@inquirer/type": "^3.0.7", + "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, "engines": { @@ -1172,16 +1644,40 @@ } } }, - "node_modules/@inquirer/select": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.2.3.tgz", - "integrity": "sha512-OAGhXU0Cvh0PhLz9xTF/kx6g6x+sP+PcyTiLvCrewI99P3BBeexD+VbuwkNDvqGkk3y2h5ZiWLeRP7BFlhkUDg==", + "node_modules/@inquirer/confirm": { + "version": "5.1.12", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.12.tgz", + "integrity": "sha512-dpq+ielV9/bqgXRUbNH//KsY6WEw9DrGPmipkpmgC1Y46cwuBTNx7PXFWTjc3MQ+urcc0QxoVHcMI0FW4Ok0hg==", "license": "MIT", "dependencies": { "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.1.13", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.13.tgz", + "integrity": "sha512-1viSxebkYN2nJULlzCxES6G9/stgHSepZ9LqqfdIGPHj5OHhiBUXVS0a6R0bEC2A+VL4D9w6QB66ebCr6HGllA==", + "license": "MIT", + "dependencies": { "@inquirer/figures": "^1.0.12", "@inquirer/type": "^3.0.7", "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", "yoctocolors-cjs": "^2.1.2" }, "engines": { @@ -1196,11 +1692,16 @@ } } }, - "node_modules/@inquirer/type": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", - "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", + "node_modules/@inquirer/editor": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.13.tgz", + "integrity": "sha512-WbicD9SUQt/K8O5Vyk9iC2ojq5RHoCLK6itpp2fHsWe44VxxcA9z3GTWlvjSTGmMQpZr+lbVmrxdHcumJoLbMA==", "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7", + "external-editor": "^3.1.0" + }, "engines": { "node": ">=18" }, @@ -1213,39 +1714,249 @@ } } }, - "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==", - "license": "ISC", + "node_modules/@inquirer/expand": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.15.tgz", + "integrity": "sha512-4Y+pbr/U9Qcvf+N/goHzPEXiHH8680lM3Dr3Y9h9FFw4gHS+zVpbj8LfbKWIb/jayIB4aSO4pWiBTrBYWkvi5A==", + "license": "MIT", "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" + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7", + "yoctocolors-cjs": "^2.1.2" }, "engines": { - "node": ">=12" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "node_modules/@inquirer/figures": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.12.tgz", + "integrity": "sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==", "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": ">=18" } }, - "node_modules/@isaacs/cliui/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==", + "node_modules/@inquirer/input": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.1.12.tgz", + "integrity": "sha512-xJ6PFZpDjC+tC1P8ImGprgcsrzQRsUh9aH3IZixm1lAZFK49UGHxM3ltFfuInN2kPYNfyoPRh+tU4ftsjPLKqQ==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.15.tgz", + "integrity": "sha512-xWg+iYfqdhRiM55MvqiTCleHzszpoigUpN5+t1OMcRkJrUrw7va3AzXaxvS+Ak7Gny0j2mFSTv2JJj8sMtbV2g==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.15.tgz", + "integrity": "sha512-75CT2p43DGEnfGTaqFpbDC2p2EEMrq0S+IRrf9iJvYreMy5mAWj087+mdKyLHapUEPLjN10mNvABpGbk8Wdraw==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7", + "ansi-escapes": "^4.3.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.4.1.tgz", + "integrity": "sha512-UlmM5FVOZF0gpoe1PT/jN4vk8JmpIWBlMvTL8M+hlvPmzN89K6z03+IFmyeu/oFCenwdwHDr2gky7nIGSEVvlA==", + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.1.5", + "@inquirer/confirm": "^5.1.9", + "@inquirer/editor": "^4.2.10", + "@inquirer/expand": "^4.0.12", + "@inquirer/input": "^4.1.9", + "@inquirer/number": "^3.0.12", + "@inquirer/password": "^4.0.12", + "@inquirer/rawlist": "^4.0.12", + "@inquirer/search": "^3.0.12", + "@inquirer/select": "^4.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.3.tgz", + "integrity": "sha512-7XrV//6kwYumNDSsvJIPeAqa8+p7GJh7H5kRuxirct2cgOcSWwwNGoXDRgpNFbY/MG2vQ4ccIWCi8+IXXyFMZA==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.15.tgz", + "integrity": "sha512-YBMwPxYBrADqyvP4nNItpwkBnGGglAvCLVW8u4pRmmvOsHUtCAUIMbUrLX5B3tFL1/WsLGdQ2HNzkqswMs5Uaw==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/figures": "^1.0.12", + "@inquirer/type": "^3.0.7", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.2.3.tgz", + "integrity": "sha512-OAGhXU0Cvh0PhLz9xTF/kx6g6x+sP+PcyTiLvCrewI99P3BBeexD+VbuwkNDvqGkk3y2h5ZiWLeRP7BFlhkUDg==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/figures": "^1.0.12", + "@inquirer/type": "^3.0.7", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", + "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "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==", + "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/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/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==", "license": "MIT", "engines": { "node": ">=12" @@ -2323,6 +3034,8 @@ "integrity": "sha512-Gg7j1hwy3SgF1KHrh0PZsYvAaykeR0PaxusnLXydehS96voYCGt1U5zVR31NIouYc63hWzidcrir1a7AIyCsNQ==", "hasInstallScript": true, "license": "Apache-2.0", + "optional": true, + "peer": true, "engines": { "node": ">=18.18" }, @@ -2344,6 +3057,8 @@ "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.9.0.tgz", "integrity": "sha512-Wcfk8/lN3WRJd5w4jmNQkUwhUw0eksaU/+BlAJwPQKW10k0h0LC9PD/6TQFmqKVbHQL0vG2z266r0S1MPzzhbA==", "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "jiti": "2.4.2" } @@ -2352,7 +3067,9 @@ "version": "6.9.0", "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.9.0.tgz", "integrity": "sha512-bFeur/qi/Q+Mqk4JdQ3R38upSYPebv5aOyD1RKywVD+rAMLtRkmTFn28ZuTtVOnZHEdtxnNOCH+bPIeSGz1+Fg==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "optional": true, + "peer": true }, "node_modules/@prisma/engines": { "version": "6.9.0", @@ -2360,6 +3077,8 @@ "integrity": "sha512-im0X0bwDLA0244CDf8fuvnLuCQcBBdAGgr+ByvGfQY9wWl6EA+kRGwVk8ZIpG65rnlOwtaWIr/ZcEU5pNVvq9g==", "hasInstallScript": true, "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "@prisma/debug": "6.9.0", "@prisma/engines-version": "6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e", @@ -2371,13 +3090,17 @@ "version": "6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e", "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e.tgz", "integrity": "sha512-Qp9gMoBHgqhKlrvumZWujmuD7q4DV/gooEyPCLtbkc13EZdSz2RsGUJ5mHb3RJgAbk+dm6XenqG7obJEhXcJ6Q==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "optional": true, + "peer": true }, "node_modules/@prisma/fetch-engine": { "version": "6.9.0", "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.9.0.tgz", "integrity": "sha512-PMKhJdl4fOdeE3J3NkcWZ+tf3W6rx3ht/rLU8w4SXFRcLhd5+3VcqY4Kslpdm8osca4ej3gTfB3+cSk5pGxgFg==", "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "@prisma/debug": "6.9.0", "@prisma/engines-version": "6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e", @@ -2389,6 +3112,8 @@ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.9.0.tgz", "integrity": "sha512-/B4n+5V1LI/1JQcHp+sUpyRT1bBgZVPHbsC4lt4/19Xp4jvNIVcq5KYNtQDk5e/ukTSjo9PZVAxxy9ieFtlpTQ==", "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "@prisma/debug": "6.9.0" } @@ -2661,80 +3386,674 @@ "linux" ] }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "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.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", + "@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.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.3", + "@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.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.3", + "@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.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.3", + "@smithy/middleware-endpoint": "^4.1.11", + "@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.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.3", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@smithy/util-defaults-mode-node": { + "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.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "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/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true, - "license": "MIT" + "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/@scarf/scarf": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", - "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", - "hasInstallScript": true, - "license": "Apache-2.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/@sec-ant/readable-stream": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", - "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", - "devOptional": true, - "license": "MIT" + "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/@sindresorhus/is": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", - "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", - "devOptional": true, - "license": "MIT", + "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": ">=14.16" + "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" }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "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/@swc/cli": { @@ -3276,6 +4595,13 @@ "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "license": "MIT" }, + "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": "7.18.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", @@ -4860,6 +6186,12 @@ "node": ">=18" } }, + "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/boxen": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", @@ -7130,6 +8462,28 @@ ], "license": "BSD-3-Clause" }, + "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", @@ -8811,6 +10165,8 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", "license": "MIT", + "optional": true, + "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -9649,6 +11005,15 @@ "mkdirp": "bin/cmd.js" } }, + "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", @@ -9960,6 +11325,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obliterator": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-1.6.1.tgz", + "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==", + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -10299,95 +11670,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/pg": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.0.tgz", - "integrity": "sha512-7SKfdvP8CTNXjMUzfcVTaI+TDzBEeaUnVwiVGZQD1Hh33Kpev7liQba9uLd4CfN8r9mCVsD0JIpq03+Unpz+kg==", - "license": "MIT", - "dependencies": { - "pg-connection-string": "^2.9.0", - "pg-pool": "^3.10.0", - "pg-protocol": "^1.10.0", - "pg-types": "2.2.0", - "pgpass": "1.0.5" - }, - "engines": { - "node": ">= 8.0.0" - }, - "optionalDependencies": { - "pg-cloudflare": "^1.2.5" - }, - "peerDependencies": { - "pg-native": ">=3.0.1" - }, - "peerDependenciesMeta": { - "pg-native": { - "optional": true - } - } - }, - "node_modules/pg-cloudflare": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.5.tgz", - "integrity": "sha512-OOX22Vt0vOSRrdoUPKJ8Wi2OpE/o/h9T8X1s4qSkCedbNah9ei2W2765be8iMVxQUsvgT7zIAT2eIa9fs5+vtg==", - "license": "MIT", - "optional": true - }, - "node_modules/pg-connection-string": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.0.tgz", - "integrity": "sha512-P2DEBKuvh5RClafLngkAuGe9OUlFV7ebu8w1kmaaOgPcpJd1RIFh7otETfI6hAR8YupOLFTY7nuvvIn7PLciUQ==", - "license": "MIT" - }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "license": "ISC", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/pg-pool": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.0.tgz", - "integrity": "sha512-DzZ26On4sQ0KmqnO34muPcmKbhrjmyiO4lCCR0VwEd7MjmiKf5NTg/6+apUEu0NF7ESa37CGzFxH513CoUmWnA==", - "license": "MIT", - "peerDependencies": { - "pg": ">=8.0" - } - }, - "node_modules/pg-protocol": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.0.tgz", - "integrity": "sha512-IpdytjudNuLv8nhlHs/UrVBhU0e78J0oIS/0AVdTbWxSOkFUVdsHC/NrorO6nXsQNDTT1kzDSOMJubBQviX18Q==", - "license": "MIT" - }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "license": "MIT", - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pgpass": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", - "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", - "license": "MIT", - "dependencies": { - "split2": "^4.1.0" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -10477,45 +11759,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/postgres-bytea": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "license": "MIT", - "dependencies": { - "xtend": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -10561,6 +11804,8 @@ "integrity": "sha512-resJAwMyZREC/I40LF6FZ6rZTnlrlrYrb63oW37Gq+U+9xHwbyMSPJjKtM7VZf3gTO86t/Oyz+YeSXr3CmAY1Q==", "hasInstallScript": true, "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "@prisma/config": "6.9.0", "@prisma/engines": "6.9.0" @@ -11558,15 +12803,6 @@ "node": ">=0.10.0" } }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -11828,6 +13064,18 @@ "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/strtok3": { "version": "10.3.1", "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.1.tgz", @@ -12724,6 +13972,19 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "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", diff --git a/backend/package.json b/backend/package.json index f358749..669000f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,11 +1,10 @@ { "author": "BC Government Forestry Suite of Applications Starter Template", "license": "Apache-2.0", - "main": "index.js", + "main": "index.js", "scripts": { - "prisma-generate": "prisma generate", "prebuild": "rimraf dist", - "build": "prisma generate && nest build", + "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "deploy": "npm ci --ignore-scripts --no-update-notifier && npm run build", "start": "nest start", @@ -17,10 +16,12 @@ "make-badges": "istanbul-badges-readme --logo=vitest --exitCode=1", "make-badges:ci": "npm run make-badges -- --ci", "test": "vitest --dir src", - "test:cov": "prisma generate && vitest run --coverage", + "test:cov": "vitest run --coverage", "test:e2e": "vitest --dir test" - }, + }, "dependencies": { + "@aws-sdk/client-dynamodb": "^3.698.0", + "@aws-sdk/lib-dynamodb": "^3.698.0", "@nestjs/cli": "^11.0.0", "@nestjs/common": "^11.0.0", "@nestjs/config": "^4.0.0", @@ -30,24 +31,23 @@ "@nestjs/swagger": "^11.0.0", "@nestjs/terminus": "^11.0.0", "@nestjs/testing": "^11.0.0", - "@prisma/client": "^6.1.0", - "prisma": "^6.1.0", "dotenv": "^16.4.7", "express-prom-bundle": "^8.0.0", "helmet": "^8.0.0", "nest-winston": "^1.9.7", - "pg": "^8.13.1", "prom-client": "^15.1.3", "reflect-metadata": "^0.2.2", "rimraf": "^6.0.1", "swagger-ui-express": "^5.0.1", + "uuid": "^11.0.3", "winston": "^3.17.0" }, "devDependencies": { "@swc/cli": "^0.6.0", - "@swc/core": "^1.9.1", + "@swc/core": "^1.9.1", "@types/express": "^5.0.0", "@types/node": "^22.0.0", + "@types/uuid": "^10.0.0", "@types/supertest": "^6.0.0", "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma deleted file mode 100644 index 313b037..0000000 --- a/backend/prisma/schema.prisma +++ /dev/null @@ -1,16 +0,0 @@ -generator client { - provider = "prisma-client-js" - previewFeatures = ["metrics"] - binaryTargets = ["native", "debian-openssl-3.0.x"] -} - -datasource db { - provider = "postgresql" - url = env("DATABASE_URL") -} - -model users { - id Decimal @id(map: "USER_PK") @default(dbgenerated("nextval('\"USER_SEQ\"'::regclass)")) @db.Decimal - name String @db.VarChar(200) - email String @db.VarChar(200) -} diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index 4f3d553..61e3527 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -1,7 +1,7 @@ import "dotenv/config"; import { MiddlewareConsumer, Module, RequestMethod } from "@nestjs/common"; import { HTTPLoggerMiddleware } from "./middleware/req.res.logger"; -import { PrismaService } from "src/prisma.service"; +import { DynamoDBService } from "src/dynamodb.service"; import { ConfigModule } from "@nestjs/config"; import { UsersModule } from "./users/users.module"; import { AppService } from "./app.service"; @@ -19,7 +19,7 @@ import { HealthController } from "./health.controller"; UsersModule ], controllers: [AppController,MetricsController, HealthController], - providers: [AppService, PrismaService] + providers: [AppService, DynamoDBService] }) export class AppModule { // let's add a middleware on all routes configure(consumer: MiddlewareConsumer) { diff --git a/backend/src/dynamodb.module.ts b/backend/src/dynamodb.module.ts new file mode 100644 index 0000000..3e341e4 --- /dev/null +++ b/backend/src/dynamodb.module.ts @@ -0,0 +1,8 @@ +import { Module } from "@nestjs/common"; +import { DynamoDBService } from "src/dynamodb.service"; + +@Module({ + providers: [DynamoDBService], + exports: [DynamoDBService], +}) +export class DynamoDBModule {} diff --git a/backend/src/dynamodb.service.ts b/backend/src/dynamodb.service.ts new file mode 100644 index 0000000..d792892 --- /dev/null +++ b/backend/src/dynamodb.service.ts @@ -0,0 +1,101 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { DynamoDBClient, DynamoDBClientConfig } from "@aws-sdk/client-dynamodb"; +import { + DynamoDBDocumentClient, + GetCommand, + PutCommand, + ScanCommand, + UpdateCommand, + DeleteCommand, + QueryCommand, +} from "@aws-sdk/lib-dynamodb"; + +@Injectable() +export class DynamoDBService { + private dynamoClient: DynamoDBDocumentClient; + private tableName: string; + constructor() { + const clientConfig: DynamoDBClientConfig = { + region: process.env.AWS_REGION || "ca-central-1", // Default to Canada Central if not set + endpoint: process.env.DYNAMODB_ENDPOINT || "http://localhost:8000", // Default to local DynamoDB endpoint + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID || "dummy", + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || "dummy", + }, + }; + + const client = new DynamoDBClient( + process.env.IS_OFFLINE ? clientConfig : {}, + ); + this.dynamoClient = DynamoDBDocumentClient.from(client); + this.tableName = process.env.DYNAMODB_TABLE_NAME || "users"; + } + + getClient(): DynamoDBDocumentClient { + return this.dynamoClient; + } + + getTableName(): string { + return this.tableName; + } + + // Helper methods for common operations + async get(key: any) { + const command = new GetCommand({ + TableName: this.tableName, + Key: key, + }); + return this.dynamoClient.send(command); + } + + async put(item: any) { + const command = new PutCommand({ + TableName: this.tableName, + Item: item, + }); + return this.dynamoClient.send(command); + } + + async scan(options: any = {}) { + const command = new ScanCommand({ + TableName: this.tableName, + ...options, + }); + return this.dynamoClient.send(command); + } + + async query(options: any) { + const command = new QueryCommand({ + TableName: this.tableName, + ...options, + }); + return this.dynamoClient.send(command); + } + + async update( + key: any, + updateExpression: string, + expressionAttributeValues: any, + expressionAttributeNames?: any, + ) { + const command = new UpdateCommand({ + TableName: this.tableName, + Key: key, + UpdateExpression: updateExpression, + ExpressionAttributeValues: expressionAttributeValues, + ...(expressionAttributeNames && { + ExpressionAttributeNames: expressionAttributeNames, + }), + ReturnValues: "ALL_NEW", + }); + return this.dynamoClient.send(command); + } + + async delete(key: any) { + const command = new DeleteCommand({ + TableName: this.tableName, + Key: key, + }); + return this.dynamoClient.send(command); + } +} diff --git a/backend/src/health.controller.ts b/backend/src/health.controller.ts index 3a8776c..1045f01 100644 --- a/backend/src/health.controller.ts +++ b/backend/src/health.controller.ts @@ -1,19 +1,16 @@ import { Controller, Get } from "@nestjs/common"; -import { HealthCheckService, HealthCheck, PrismaHealthIndicator } from "@nestjs/terminus"; -import { PrismaService } from "src/prisma.service"; +import { HealthCheckService, HealthCheck } from "@nestjs/terminus"; @Controller("health") export class HealthController { constructor( private health: HealthCheckService, - private prisma: PrismaHealthIndicator, - private readonly prismaService: PrismaService, ) {} @Get() @HealthCheck() check() { return this.health.check([ - () => this.prisma.pingCheck('prisma', this.prismaService), + () => ({ status: { status: 'up' } }), ]); } } diff --git a/backend/src/metrics.controller.ts b/backend/src/metrics.controller.ts index 377d68c..b54d806 100644 --- a/backend/src/metrics.controller.ts +++ b/backend/src/metrics.controller.ts @@ -1,15 +1,12 @@ import { Controller, Get, Res } from "@nestjs/common"; import { Response } from "express"; import { register } from "src/middleware/prom"; -import { PrismaService } from "src/prisma.service"; @Controller("metrics") export class MetricsController { - constructor(private prisma: PrismaService) {} @Get() async getMetrics(@Res() res: Response) { - const prismaMetrics = await this.prisma.$metrics.prometheus(); const appMetrics = await register.metrics(); - res.end(prismaMetrics + appMetrics); + res.end(appMetrics); } } diff --git a/backend/src/prisma.module.ts b/backend/src/prisma.module.ts deleted file mode 100644 index f3dce19..0000000 --- a/backend/src/prisma.module.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Module } from "@nestjs/common"; -import { PrismaService } from "src/prisma.service"; - -@Module({ - providers: [PrismaService], - exports: [PrismaService], -}) -export class PrismaModule {} \ No newline at end of file diff --git a/backend/src/prisma.service.ts b/backend/src/prisma.service.ts deleted file mode 100644 index c94decb..0000000 --- a/backend/src/prisma.service.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Injectable, OnModuleDestroy, OnModuleInit, Logger, Scope } from "@nestjs/common"; -import { PrismaClient, Prisma } from "@prisma/client"; - -const DB_HOST = process.env.POSTGRES_HOST || "localhost"; -const DB_USER = process.env.POSTGRES_USER || "postgres"; -const DB_PWD = encodeURIComponent(process.env.POSTGRES_PASSWORD || "default"); // this needs to be encoded, if the password contains special characters it will break connection string. -const DB_PORT = process.env.POSTGRES_PORT || 5432; -const DB_NAME = process.env.POSTGRES_DATABASE || "postgres"; -const DB_SCHEMA = process.env.POSTGRES_SCHEMA || "app"; -// SSL settings for PostgreSQL 17+ which requires SSL by default -const SSL_MODE = (process.env.NODE_ENV === 'local' || 'unittest') ? 'prefer' : 'require'; // 'require' for aws deployments, 'prefer' for local development or ut in gha -const dataSourceURL = `postgresql://${DB_USER}:${DB_PWD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?schema=${DB_SCHEMA}&connection_limit=5&sslmode=${SSL_MODE}`; - -@Injectable({ scope: Scope.DEFAULT}) -class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { - private static instance: PrismaService; - private logger = new Logger("PRISMA"); - - constructor() { - if (PrismaService.instance) { - console.log('Returning existing PrismaService instance'); - return PrismaService.instance; - } - super({ - errorFormat: 'pretty', - datasources: { - db: { - url: dataSourceURL, - }, - }, - log: [ - { emit: 'event', level: 'query' }, - { emit: 'stdout', level: 'info' }, - { emit: 'stdout', level: 'warn' }, - { emit: 'stdout', level: 'error' }, - ] - }); - PrismaService.instance = this; - } - - - async onModuleInit() { - await this.$connect(); - this.$on('query', (e: Prisma.QueryEvent) => { - // dont print the health check queries - if(e?.query?.includes("SELECT 1")) return; - this.logger.log( - `Query: ${e.query} - Params: ${e.params} - Duration: ${e.duration}ms`, - ); - }); - } - - async onModuleDestroy() { - await this.$disconnect(); - } -} - -export { PrismaService }; diff --git a/backend/src/users/dto/user.dto.ts b/backend/src/users/dto/user.dto.ts index 89eb7f0..9a1be8c 100644 --- a/backend/src/users/dto/user.dto.ts +++ b/backend/src/users/dto/user.dto.ts @@ -3,9 +3,9 @@ import { ApiProperty } from '@nestjs/swagger'; export class UserDto { @ApiProperty({ description: 'The ID of the user', - // default: '9999', + example: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', }) - id: number; + id: string; @ApiProperty({ description: 'The name of the user', diff --git a/backend/src/users/users.controller.spec.ts b/backend/src/users/users.controller.spec.ts index 4807d51..9e99f01 100644 --- a/backend/src/users/users.controller.spec.ts +++ b/backend/src/users/users.controller.spec.ts @@ -6,7 +6,8 @@ import { HttpException, INestApplication } from "@nestjs/common"; import { CreateUserDto } from "./dto/create-user.dto"; import { UpdateUserDto } from "./dto/update-user.dto"; import { UserDto } from "./dto/user.dto"; -import { PrismaService } from "src/prisma.service"; +import { DynamoDBService } from "src/dynamodb.service"; + describe("UserController", () => { let controller: UsersController; let usersService: UsersService; @@ -18,8 +19,14 @@ describe("UserController", () => { providers: [ UsersService, { - provide: PrismaService, - useValue: {}, + provide: DynamoDBService, + useValue: { + put: vi.fn(), + get: vi.fn(), + scan: vi.fn(), + update: vi.fn(), + delete: vi.fn(), + }, }, ], }).compile(); @@ -28,6 +35,7 @@ describe("UserController", () => { app = module.createNestApplication(); await app.init(); }); + // Close the app after each test afterEach(async () => { await app.close(); @@ -45,7 +53,7 @@ describe("UserController", () => { name: "Test User", }; const expectedResult = { - id: 1, + id: "test-uuid-123", ...createUserDto, }; vi.spyOn(usersService, "create").mockResolvedValue(expectedResult); @@ -57,78 +65,76 @@ describe("UserController", () => { expect(usersService.create).toHaveBeenCalledWith(createUserDto); expect(result).toEqual(expectedResult); }); - }); - describe("findAll", () => { + }); describe("findAll", () => { it("should return an array of users", async () => { const result = []; - result.push({ id: 1, name: "Alice", email: "test@gmail.com" }); + result.push({ id: "test-uuid-1", name: "Alice", email: "test@gmail.com" }); vi.spyOn(usersService, "findAll").mockResolvedValue(result); expect(await controller.findAll()).toBe(result); }); }); + describe("findOne", () => { it("should return a user object", async () => { const result: UserDto = { - id: 1, + id: "test-uuid-1", name: "john", email: "John_Doe@gmail.com", }; vi.spyOn(usersService, "findOne").mockResolvedValue(result); - expect(await controller.findOne("1")).toBe(result); + expect(await controller.findOne("test-uuid-1")).toBe(result); }); + it("should throw error if user not found", async () => { vi.spyOn(usersService, "findOne").mockResolvedValue(undefined); try { - await controller.findOne("1"); + await controller.findOne("test-uuid-1"); } catch (e) { expect(e).toBeInstanceOf(HttpException); expect(e.message).toBe("User not found."); } }); }); + describe("update", () => { it("should update and return a user object", async () => { - const id = "1"; + const id = "test-uuid-1"; const updateUserDto: UpdateUserDto = { email: "johndoe@example.com", name: "John Doe", }; const userDto: UserDto = { - id: 1, + id: "test-uuid-1", name: "John Doe", email: "johndoe@example.com", - }; - vi.spyOn(usersService, "update").mockResolvedValue(userDto); + }; vi.spyOn(usersService, "update").mockResolvedValue(userDto); expect(await controller.update(id, updateUserDto)).toBe(userDto); - expect(usersService.update).toHaveBeenCalledWith(+id, updateUserDto); + expect(usersService.update).toHaveBeenCalledWith(id, updateUserDto); }); }); + describe("remove", () => { it("should remove a user", async () => { - const id = "1"; + const id = "test-uuid-1"; vi.spyOn(usersService, "remove").mockResolvedValue(undefined); expect(await controller.remove(id)).toBeUndefined(); - expect(usersService.remove).toHaveBeenCalledWith(+id); + expect(usersService.remove).toHaveBeenCalledWith(id); }); }); // Test the GET /users/search endpoint describe("GET /users/search", () => { // Test with valid query parameters - it("given valid query parameters_should return an array of users with pagination metadata", async () => { - // Mock the usersService.searchUsers method to return a sample result + it("given valid query parameters_should return an array of users with pagination metadata", async () => { // Mock the usersService.searchUsers method to return a sample result const result = { users: [ - { id: 1, name: "Alice", email: "alice@example.com" }, - { id: 2, name: "Adam", email: "Adam@example.com" }, + { id: "test-uuid-1", name: "Alice", email: "alice@example.com" }, + { id: "test-uuid-2", name: "Adam", email: "Adam@example.com" }, ], + totalCount: 2, page: 1, limit: 10, - sort: '{"name":"ASC"}', - filter: '[{"key":"name","operation":"like","value":"A"}]', - total: 2, - totalPages: 1, }; vi.spyOn(usersService, "searchUsers").mockImplementation( async () => result, @@ -136,12 +142,11 @@ describe("UserController", () => { // Make a GET request with query parameters and expect a 200 status code and the result object return request(app.getHttpServer()) - .get("/users/search") - .query({ + .get("/users/search") .query({ page: 1, limit: 10, - sort: '{"name":"ASC"}', - filter: '[{"key":"name","operation":"like","value":"A"}]', + sort: '{}', + filter: '{}', }) .expect(200) .expect(result); diff --git a/backend/src/users/users.controller.ts b/backend/src/users/users.controller.ts index 36fd258..43d74ec 100644 --- a/backend/src/users/users.controller.ts +++ b/backend/src/users/users.controller.ts @@ -41,10 +41,9 @@ export class UsersController { } return this.usersService.searchUsers(page, limit, sort, filter); } - @Get(":id") async findOne(@Param("id") id: string) { - const user = await this.usersService.findOne(+id); + const user = await this.usersService.findOne(id); if (!user) { throw new HttpException("User not found.", 404); } @@ -53,12 +52,12 @@ export class UsersController { @Put(":id") update(@Param("id") id: string, @Body() updateUserDto: UpdateUserDto) { - return this.usersService.update(+id, updateUserDto); + return this.usersService.update(id, updateUserDto); } @Delete(":id") remove(@Param("id") id: string) { - return this.usersService.remove(+id); + return this.usersService.remove(id); } diff --git a/backend/src/users/users.module.ts b/backend/src/users/users.module.ts index ec1f8e7..ee8826f 100644 --- a/backend/src/users/users.module.ts +++ b/backend/src/users/users.module.ts @@ -1,12 +1,12 @@ import { Module } from "@nestjs/common"; import { UsersService } from "./users.service"; import { UsersController } from "./users.controller"; -import { PrismaModule } from "src/prisma.module"; +import { DynamoDBModule } from "src/dynamodb.module"; @Module({ controllers: [UsersController], providers: [UsersService], - imports: [PrismaModule] + imports: [DynamoDBModule] }) export class UsersModule { } diff --git a/backend/src/users/users.service.spec.ts b/backend/src/users/users.service.spec.ts index 7c8bd13..7198757 100644 --- a/backend/src/users/users.service.spec.ts +++ b/backend/src/users/users.service.spec.ts @@ -1,241 +1,246 @@ import type { TestingModule } from "@nestjs/testing"; import { Test } from "@nestjs/testing"; import { UsersService } from "./users.service"; -import { PrismaService } from "src/prisma.service"; -import { Prisma } from "@prisma/client"; +import { DynamoDBService } from "src/dynamodb.service"; + +// Mock UUID to return predictable values for testing +vi.mock("uuid", () => ({ + v4: vi.fn(() => "test-uuid-123"), +})); describe("UserService", () => { let service: UsersService; - let prisma: PrismaService; + let dynamoDBService: DynamoDBService; - const savedUser1 = { - id: new Prisma.Decimal(1), + const mockUser1 = { + id: "test-uuid-123", name: "Test Numone", email: "numone@test.com", + createdAt: "2024-01-01T00:00:00.000Z", + updatedAt: "2024-01-01T00:00:00.000Z", }; - const savedUser2 = { - id: new Prisma.Decimal(2), + + const mockUser2 = { + id: "test-uuid-456", name: "Test Numtwo", email: "numtwo@test.com", + createdAt: "2024-01-01T00:00:00.000Z", + updatedAt: "2024-01-01T00:00:00.000Z", }; - const oneUser = { - id: 1, + + const createUserDto = { name: "Test Numone", email: "numone@test.com", }; - const updateUser = { - id: 1, - name: "Test Numone update", - email: "numoneupdate@test.com", - }; - const updatedUser = { - id: new Prisma.Decimal(1), + + const updateUserDto = { name: "Test Numone update", email: "numoneupdate@test.com", }; - const twoUser = { - id: 2, - name: "Test Numtwo", - email: "numtwo@test.com", + const userDtoResponse = { + id: "test-uuid-123", + name: "Test Numone", + email: "numone@test.com", }; - const userArray = [oneUser, twoUser]; - const savedUserArray = [savedUser1, savedUser2]; + const updatedUserResponse = { + id: "test-uuid-123", + name: "Test Numone update", + email: "numoneupdate@test.com", + }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ - UsersService, - { - provide: PrismaService, + UsersService, { + provide: DynamoDBService, useValue: { - users: { - findMany: vi.fn().mockResolvedValue(savedUserArray), - findUnique: vi.fn().mockResolvedValue(savedUser1), - create: vi.fn().mockResolvedValue(savedUser1), - update: vi.fn().mockResolvedValue(updatedUser), - delete: vi.fn().mockResolvedValue(true), - count: vi.fn(), - }, + put: vi.fn().mockResolvedValue({ $metadata: {} }), + get: vi.fn().mockResolvedValue({ Item: mockUser1, $metadata: {} }), + scan: vi.fn().mockResolvedValue({ Items: [mockUser1, mockUser2], Count: 2, $metadata: {} }), + update: vi.fn().mockResolvedValue({ + Attributes: { + id: "test-uuid-123", + name: "Test Numone update", + email: "numoneupdate@test.com", + updatedAt: "2024-01-01T00:00:00.000Z" + }, + $metadata: {} + }), + delete: vi.fn().mockResolvedValue({ $metadata: {} }), }, }, ], }).compile(); service = module.get(UsersService); - prisma = module.get(PrismaService); + dynamoDBService = module.get(DynamoDBService); }); it("should be defined", () => { expect(service).toBeDefined(); }); - describe("createOne", () => { + describe("create", () => { it("should successfully add a user", async () => { - await expect(service.create(oneUser)).resolves.toEqual(oneUser); - expect(prisma.users.create).toBeCalledTimes(1); + const result = await service.create(createUserDto); + + expect(result).toEqual(userDtoResponse); + expect(dynamoDBService.put).toHaveBeenCalledWith({ + id: "test-uuid-123", + name: "Test Numone", + email: "numone@test.com", + createdAt: expect.any(String), + updatedAt: expect.any(String), + }); + expect(dynamoDBService.put).toHaveBeenCalledTimes(1); }); }); describe("findAll", () => { it("should return an array of users", async () => { const users = await service.findAll(); - expect(users).toEqual(userArray); + + expect(users).toEqual([ + { id: "test-uuid-123", name: "Test Numone", email: "numone@test.com" }, + { id: "test-uuid-456", name: "Test Numtwo", email: "numtwo@test.com" }, + ]); + expect(dynamoDBService.scan).toHaveBeenCalledTimes(1); }); }); describe("findOne", () => { it("should get a single user", async () => { - await expect(service.findOne(1)).resolves.toEqual(oneUser); + const user = await service.findOne("test-uuid-123"); + + expect(user).toEqual(userDtoResponse); + expect(dynamoDBService.get).toHaveBeenCalledWith({ id: "test-uuid-123" }); + }); it("should throw error when user not found", async () => { + vi.spyOn(dynamoDBService, "get").mockResolvedValueOnce({ Item: undefined, $metadata: {} }); + + await expect(service.findOne("non-existent-id")).rejects.toThrow("User with id non-existent-id not found"); }); }); describe("update", () => { it("should call the update method", async () => { - const user = await service.update(1, updateUser); - expect(user).toEqual(updateUser); - expect(prisma.users.update).toBeCalledTimes(1); + const user = await service.update("test-uuid-123", updateUserDto); + + expect(user).toEqual(updatedUserResponse); + expect(dynamoDBService.update).toHaveBeenCalledWith( + { id: "test-uuid-123" }, + "SET #name = :name, #email = :email, updatedAt = :updatedAt", + { + ":name": "Test Numone update", + ":email": "numoneupdate@test.com", + ":updatedAt": expect.any(String), + }, + { + "#name": "name", + "#email": "email", + } + ); + expect(dynamoDBService.update).toHaveBeenCalledTimes(1); }); }); describe("remove", () => { it("should return {deleted: true}", async () => { - await expect(service.remove(2)).resolves.toEqual({ deleted: true }); + const result = await service.remove("test-uuid-123"); + + expect(result).toEqual({ deleted: true }); + expect(dynamoDBService.delete).toHaveBeenCalledWith({ id: "test-uuid-123" }); }); + it("should return {deleted: false, message: err.message}", async () => { - const repoSpy = vi - .spyOn(prisma.users, "delete") - .mockRejectedValueOnce(new Error("Bad Delete Method.")); - await expect(service.remove(-1)).resolves.toEqual({ + const mockError = new Error("Bad Delete Method."); + vi.spyOn(dynamoDBService, "delete").mockRejectedValueOnce(mockError); + + const result = await service.remove("test-uuid-123"); + + expect(result).toEqual({ deleted: false, message: "Bad Delete Method.", }); - expect(repoSpy).toBeCalledTimes(1); + expect(dynamoDBService.delete).toHaveBeenCalledTimes(1); }); }); describe("searchUsers", () => { - it("should return a list of users with pagination and filtering", async () => { + it("should return a list of users with pagination", async () => { const page = 1; const limit = 10; - const sortObject: Prisma.SortOrder = "asc"; - const sort: any = `[{ "name": "${sortObject}" }]`; - const filter: any = '[{ "name": { "equals": "Peter" } }]'; + const sort = "{}"; + const filter = "{}"; - vi.spyOn(prisma.users, "findMany").mockResolvedValue([]); - vi.spyOn(prisma.users, "count").mockResolvedValue(0); const result = await service.searchUsers(page, limit, sort, filter); expect(result).toEqual({ - users: [], - page, - limit, - total: 0, - totalPages: 0, + users: [ + { id: "test-uuid-123", name: "Test Numone", email: "numone@test.com" }, + { id: "test-uuid-456", name: "Test Numtwo", email: "numtwo@test.com" }, + ], + totalCount: 2, + page: 1, + limit: 10, }); + expect(dynamoDBService.scan).toHaveBeenCalledWith({ Limit: 10 }); }); - it("given no page should return a list of users with pagination and filtering with default page 1", async () => { + it("given no page should return a list of users with default page 1", async () => { const limit = 10; - const sortObject: Prisma.SortOrder = "asc"; - const sort: any = `[{ "name": "${sortObject}" }]`; - const filter: any = '[{ "name": { "equals": "Peter" } }]'; + const sort = "{}"; + const filter = "{}"; - vi.spyOn(prisma.users, "findMany").mockResolvedValue([]); - vi.spyOn(prisma.users, "count").mockResolvedValue(0); const result = await service.searchUsers(null, limit, sort, filter); expect(result).toEqual({ - users: [], + users: [ + { id: "test-uuid-123", name: "Test Numone", email: "numone@test.com" }, + { id: "test-uuid-456", name: "Test Numtwo", email: "numtwo@test.com" }, + ], + totalCount: 2, page: 1, - limit, - total: 0, - totalPages: 0, + limit: 10, }); }); - it("given no limit should return a list of users with pagination and filtering with default limit 10", async () => { + + it("given no limit should return a list of users with default limit 10", async () => { const page = 1; - const sortObject: Prisma.SortOrder = "asc"; - const sort: any = `[{ "name": "${sortObject}" }]`; - const filter: any = '[{ "name": { "equals": "Peter" } }]'; + const sort = "{}"; + const filter = "{}"; - vi.spyOn(prisma.users, "findMany").mockResolvedValue([]); - vi.spyOn(prisma.users, "count").mockResolvedValue(0); const result = await service.searchUsers(page, null, sort, filter); expect(result).toEqual({ - users: [], + users: [ + { id: "test-uuid-123", name: "Test Numone", email: "numone@test.com" }, + { id: "test-uuid-456", name: "Test Numtwo", email: "numtwo@test.com" }, + ], + totalCount: 2, page: 1, limit: 10, - total: 0, - totalPages: 0, }); }); - it("given limit greater than 200 should return a list of users with pagination and filtering with default limit 10", async () => { + it("given limit greater than 200 should return a list of users with default limit 10", async () => { const page = 1; const limit = 201; - const sortObject: Prisma.SortOrder = "asc"; - const sort: any = `[{ "name": "${sortObject}" }]`; - const filter: any = '[{ "name": { "equals": "Peter" } }]'; + const sort = "{}"; + const filter = "{}"; - vi.spyOn(prisma.users, "findMany").mockResolvedValue([]); - vi.spyOn(prisma.users, "count").mockResolvedValue(0); const result = await service.searchUsers(page, limit, sort, filter); expect(result).toEqual({ - users: [], + users: [ + { id: "test-uuid-123", name: "Test Numone", email: "numone@test.com" }, + { id: "test-uuid-456", name: "Test Numtwo", email: "numtwo@test.com" }, + ], + totalCount: 2, page: 1, limit: 10, - total: 0, - totalPages: 0, }); }); - it("given invalid JSON should throw error", async () => { - const page = 1; - const limit = 201; - const sortObject: Prisma.SortOrder = "asc"; - const sort: any = `[{ "name" "${sortObject}" }]`; - const filter: any = '[{ "name": { "equals": "Peter" } }]'; - try { - await service.searchUsers(page, limit, sort, filter); - } catch (e) { - expect(e).toEqual(new Error("Invalid query parameters")); - } - }); - }); - describe("convertFiltersToPrismaFormat", () => { - it("should convert input filters to prisma's filter format", () => { - const inputFilter = [ - { key: "a", operation: "like", value: "1" }, - { key: "b", operation: "eq", value: "2" }, - { key: "c", operation: "neq", value: "3" }, - { key: "d", operation: "gt", value: "4" }, - { key: "e", operation: "gte", value: "5" }, - { key: "f", operation: "lt", value: "6" }, - { key: "g", operation: "lte", value: "7" }, - { key: "h", operation: "in", value: ["8"] }, - { key: "i", operation: "notin", value: ["9"] }, - { key: "j", operation: "isnull", value: "10" }, - ]; - - const expectedOutput = { - a: { contains: "1" }, - b: { equals: "2" }, - c: { not: { equals: "3" } }, - d: { gt: "4" }, - e: { gte: "5" }, - f: { lt: "6" }, - g: { lte: "7" }, - h: { in: ["8"] }, - i: { not: { in: ["9"] } }, - j: { equals: null }, - }; - - expect(service.convertFiltersToPrismaFormat(inputFilter)).toStrictEqual( - expectedOutput, - ); - }); }); }); diff --git a/backend/src/users/users.service.ts b/backend/src/users/users.service.ts index 33c0bf0..d069f25 100644 --- a/backend/src/users/users.service.ts +++ b/backend/src/users/users.service.ts @@ -1,82 +1,92 @@ import { Injectable } from "@nestjs/common"; -import { PrismaService } from "src/prisma.service"; +import { DynamoDBService } from "src/dynamodb.service"; +import { v4 as uuidv4 } from "uuid"; import { CreateUserDto } from "./dto/create-user.dto"; import { UpdateUserDto } from "./dto/update-user.dto"; import { UserDto } from "./dto/user.dto"; -import { Prisma } from "@prisma/client"; @Injectable() export class UsersService { constructor( - private prisma: PrismaService + private dynamoDBService: DynamoDBService ) { } async create(user: CreateUserDto): Promise { - const savedUser = await this.prisma.users.create({ - data: { - name: user.name, - email: user.email - } - }); + const id = uuidv4(); + const newUser = { + id, + name: user.name, + email: user.email, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + }; + + await this.dynamoDBService.put(newUser); return { - id: savedUser.id.toNumber(), - name: savedUser.name, - email: savedUser.email + id, + name: user.name, + email: user.email }; } async findAll(): Promise { - const users = await this.prisma.users.findMany(); - return users.flatMap(user => { - const userDto: UserDto = { - id: user.id.toNumber(), - name: user.name, - email: user.email - }; - return userDto; - }); + const result = await this.dynamoDBService.scan(); + const users = result.Items || []; + + return users.map(user => ({ + id: user.id, + name: user.name, + email: user.email + })); } - async findOne(id: number): Promise { - const user = await this.prisma.users.findUnique({ - where: { - id: new Prisma.Decimal(id) - } - }); + async findOne(id: string): Promise { + const result = await this.dynamoDBService.get({ id }); + const user = result.Item; + + if (!user) { + throw new Error(`User with id ${id} not found`); + } + return { - id: user.id.toNumber(), + id: user.id, name: user.name, email: user.email }; } - async update(id: number, updateUserDto: UpdateUserDto): Promise { - const user = await this.prisma.users.update({ - where: { - id: new Prisma.Decimal(id) - }, - data: { - name: updateUserDto.name, - email: updateUserDto.email - } - }); + async update(id: string, updateUserDto: UpdateUserDto): Promise { + const updateExpression = "SET #name = :name, #email = :email, updatedAt = :updatedAt"; + const expressionAttributeNames = { + "#name": "name", + "#email": "email" + }; + const expressionAttributeValues = { + ":name": updateUserDto.name, + ":email": updateUserDto.email, + ":updatedAt": new Date().toISOString() + }; + + const result = await this.dynamoDBService.update( + { id }, + updateExpression, + expressionAttributeValues, + expressionAttributeNames + ); + return { - id: user.id.toNumber(), - name: user.name, - email: user.email + id: result.Attributes.id, + name: result.Attributes.name, + email: result.Attributes.email }; } - async remove(id: number): Promise<{ deleted: boolean; message?: string }> { + async remove(id: string): Promise<{ deleted: boolean; message?: string }> { try { - await this.prisma.users.delete({ - where: { - id: new Prisma.Decimal(id) - } - }); + await this.dynamoDBService.delete({ id }); return { deleted: true }; } catch (err) { return { deleted: false, message: err.message }; @@ -85,8 +95,8 @@ export class UsersService { async searchUsers(page: number, limit: number, - sort: string, // JSON string to store sort key and sort value, ex: [{"name":"desc"},{"email":"asc"}] - filter: string // JSON array for key, operation and value, ex: [{"key": "name", "operation": "like", "value": "Jo"}] + sort: string, // JSON string for sort configuration + filter: string // JSON string for filter configuration ): Promise { page = page || 1; @@ -94,63 +104,23 @@ export class UsersService { limit = 10; } - let sortObj=[]; - let filterObj = {}; - try { - sortObj = JSON.parse(sort); - filterObj = JSON.parse(filter); - } catch (e) { - throw new Error("Invalid query parameters"); - } - const users = await this.prisma.users.findMany({ - skip: (page - 1) * limit, - take: parseInt(String(limit)), - orderBy: sortObj, - where: this.convertFiltersToPrismaFormat(filterObj) - }); - - const count = await this.prisma.users.count({ - orderBy: sortObj, - where: this.convertFiltersToPrismaFormat(filterObj) + // For simplicity, implementing basic scan with pagination + // In production, you might want to use DynamoDB's pagination features + const result = await this.dynamoDBService.scan({ + Limit: limit, + // Note: DynamoDB pagination is different from SQL OFFSET + // You would typically use LastEvaluatedKey for pagination }); - return { + const users = (result.Items || []).map(user => ({ + id: user.id, + name: user.name, + email: user.email + })); return { users, + totalCount: result.Count || 0, page, - limit, - total: count, - totalPages: Math.ceil(count / limit) + limit }; } - - public convertFiltersToPrismaFormat(filterObj): any { - - let prismaFilterObj = {}; - - for (const item of filterObj) { - - if (item.operation === "like") { - prismaFilterObj[item.key] = { contains: item.value }; - } else if (item.operation === "eq") { - prismaFilterObj[item.key] = { equals: item.value }; - } else if (item.operation === "neq") { - prismaFilterObj[item.key] = { not: { equals: item.value } }; - } else if (item.operation === "gt") { - prismaFilterObj[item.key] = { gt: item.value }; - } else if (item.operation === "gte") { - prismaFilterObj[item.key] = { gte: item.value }; - } else if (item.operation === "lt") { - prismaFilterObj[item.key] = { lt: item.value }; - } else if (item.operation === "lte") { - prismaFilterObj[item.key] = { lte: item.value }; - } else if (item.operation === "in") { - prismaFilterObj[item.key] = { in: item.value }; - } else if (item.operation === "notin") { - prismaFilterObj[item.key] = { not: { in: item.value } }; - } else if (item.operation === "isnull") { - prismaFilterObj[item.key] = { equals: null }; - } - } - return prismaFilterObj; - } } diff --git a/docker-compose.yml b/docker-compose.yml index 4e5d455..d3b86ce 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,68 +1,59 @@ -# Reusable vars +# Reusable vars for DynamoDB x-var: - - &POSTGRES_USER - postgres - - &POSTGRES_PASSWORD - default - - &POSTGRES_DATABASE - postgres + - &DYNAMODB_TABLE_NAME + users + - &AWS_REGION + ca-central-1 -# Reusable envars for postgres -x-postgres-vars: &postgres-vars - POSTGRES_HOST: database - POSTGRES_USER: *POSTGRES_USER - POSTGRES_PASSWORD: *POSTGRES_PASSWORD - POSTGRES_DATABASE: *POSTGRES_DATABASE +# Reusable envars for DynamoDB +x-dynamodb-vars: &dynamodb-vars + DYNAMODB_TABLE_NAME: *DYNAMODB_TABLE_NAME + AWS_REGION: *AWS_REGION + AWS_ACCESS_KEY_ID: dummy + AWS_SECRET_ACCESS_KEY: dummy + DYNAMODB_ENDPOINT: http://dynamodb-local:8000 services: - database: - image: postgis/postgis:17-3.5 # Updated to PostgreSQL 17 with PostGIS 3.4 - container_name: database + dynamodb-local: + image: amazon/dynamodb-local:latest + container_name: dynamodb-local environment: - <<: *postgres-vars + <<: *dynamodb-vars + command: ["-jar", "DynamoDBLocal.jar", "-sharedDb", "-inMemory"] + ports: ["8000:8000"] healthcheck: - test: ["CMD", "pg_isready", "-U", *POSTGRES_USER] - ports: ["5432:5432"] + test: ["CMD-SHELL", '[ "$(curl -s -o /dev/null -I -w ''%{http_code}'' http://localhost:8000)" == "400" ]'] + interval: 10s + timeout: 10s + retries: 10 - migrations: - image: ${FLYWAY_IMAGE:-flyway/flyway:11-alpine} - container_name: migrations - command: info migrate info - volumes: ["./migrations/sql:/flyway/sql:ro"] + # DynamoDB table initialization service + dynamodb-init: + image: amazon/aws-cli:latest + container_name: dynamodb-init environment: - FLYWAY_URL: jdbc:postgresql://database:5432/postgres - FLYWAY_USER: *POSTGRES_USER - FLYWAY_PASSWORD: *POSTGRES_PASSWORD - FLYWAY_BASELINE_ON_MIGRATE: true - FLYWAY_DEFAULT_SCHEMA: app + <<: *dynamodb-vars + volumes: + - ./dynamo-migrations-local-env.sh:/dynamo-migrations-local-env.sh:ro + entrypoint: ["/bin/sh", "/dynamo-migrations-local-env.sh"] depends_on: - database: + dynamodb-local: condition: service_healthy - schemaspy: - image: schemaspy/schemaspy:6.2.4 - profiles: ["schemaspy"] - container_name: schemaspy - command: -t pgsql11 -db postgres -host database -port 5432 -u postgres -p default -schemas app - depends_on: - migrations: - condition: service_completed_successfully - volumes: ["./output:/output"] - backend: container_name: backend depends_on: - migrations: + dynamodb-init: condition: service_completed_successfully environment: - <<: *postgres-vars + <<: *dynamodb-vars NODE_ENV: development PORT: 3001 + IS_OFFLINE: 'true' + LOG_LEVEL: debug image: ${BACKEND_IMAGE:-backend} build: context: ./backend ports: ["3001:3001"] - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3001/api"] working_dir: "/app" frontend: @@ -76,8 +67,6 @@ services: LOG_LEVEL: debug image: ${FRONTEND_IMAGE:-frontend} ports: ["3000:3000"] - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3000"] working_dir: "/app" depends_on: backend: diff --git a/dynamo-migrations-local-env.sh b/dynamo-migrations-local-env.sh new file mode 100644 index 0000000..a42eeef --- /dev/null +++ b/dynamo-migrations-local-env.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# This script initializes a DynamoDB table for local development. +# It creates a table named 'users' with a primary key 'id' and a GSI on 'email'. +if ! aws dynamodb describe-table --endpoint-url http://dynamodb-local:8000 --table-name users 2>/dev/null; then + echo 'Creating users table...' + aws dynamodb create-table \ + --endpoint-url http://dynamodb-local:8000 \ + --table-name users \ + --attribute-definitions \ + AttributeName=id,AttributeType=S \ + AttributeName=email,AttributeType=S \ + --key-schema AttributeName=id,KeyType=HASH \ + --global-secondary-indexes \ + '[{"IndexName":"EmailIndex","KeySchema":[{"AttributeName":"email","KeyType":"HASH"}],"Projection":{"ProjectionType":"ALL"}}]' \ + --billing-mode PAY_PER_REQUEST + + echo 'Waiting for table to be active...' + aws dynamodb wait table-exists --endpoint-url http://dynamodb-local:8000 --table-name users +else + echo 'Table users already exists' +fi + +# Add sample users +echo 'Adding sample users...' + +# User 1 +aws dynamodb put-item \ + --endpoint-url http://dynamodb-local:8000 \ + --table-name users \ + --item '{"id":{"S":"1"},"name":{"S":"John"},"email":{"S":"John.ipsum@test.com"}}' \ + --condition-expression "attribute_not_exists(id)" 2>/dev/null || echo "User 1 already exists" + +# User 2 +aws dynamodb put-item \ + --endpoint-url http://dynamodb-local:8000 \ + --table-name users \ + --item '{"id":{"S":"2"},"name":{"S":"Jane"},"email":{"S":"Jane.ipsum@test.com"}}' \ + --condition-expression "attribute_not_exists(id)" 2>/dev/null || echo "User 2 already exists" + +# User 3 +aws dynamodb put-item \ + --endpoint-url http://dynamodb-local:8000 \ + --table-name users \ + --item '{"id":{"S":"3"},"name":{"S":"Jack"},"email":{"S":"Jack.ipsum@test.com"}}' \ + --condition-expression "attribute_not_exists(id)" 2>/dev/null || echo "User 3 already exists" +# User 4 +aws dynamodb put-item \ + --endpoint-url http://dynamodb-local:8000 \ + --table-name users \ + --item '{"id":{"S":"4"},"name":{"S":"Jill"},"email":{"S":"Jill.ipsum@test.com"}}' \ + --condition-expression "attribute_not_exists(id)" 2>/dev/null || echo "User 4 already exists" +# User 5 +aws dynamodb put-item \ + --endpoint-url http://dynamodb-local:8000 \ + --table-name users \ + --item '{"id":{"S":"5"},"name":{"S":"Joe"},"email":{"S":"Joe.ipsum@test.com"}}' \ + --condition-expression "attribute_not_exists(id)" 2>/dev/null || echo "User 5 already exists" + +echo 'Listing tables...' +aws dynamodb list-tables --endpoint-url http://dynamodb-local:8000 + +echo 'DynamoDB initialization completed successfully' \ No newline at end of file diff --git a/infrastructure/api/ecs.tf b/infrastructure/api/ecs.tf index ab2d82a..a6b681c 100644 --- a/infrastructure/api/ecs.tf +++ b/infrastructure/api/ecs.tf @@ -1,21 +1,5 @@ locals { - container_name = "${var.app_name}" - -} -data "aws_secretsmanager_secret" "db_master_creds" { - name = "${var.db_cluster_name}" -} - -data "aws_rds_cluster" "rds_cluster" { - cluster_identifier = "${var.db_cluster_name}" -} - -data "aws_secretsmanager_secret_version" "db_master_creds_version" { - secret_id = data.aws_secretsmanager_secret.db_master_creds.id -} - -locals { - db_master_creds = jsondecode(data.aws_secretsmanager_secret_version.db_master_creds_version.secret_string) + container_name = var.app_name } @@ -35,133 +19,6 @@ resource "aws_ecs_cluster_capacity_providers" "ecs_cluster_capacity_providers" { } } -resource "terraform_data" "trigger_flyway" { - input = "${timestamp()}" -} - -resource "aws_ecs_task_definition" "flyway_task" { - family = "${var.app_name}-flyway-task" - network_mode = "awsvpc" - requires_compatibilities = ["FARGATE"] - cpu = "512" - memory = "1024" - execution_role_arn = aws_iam_role.ecs_task_execution_role.arn - task_role_arn = aws_iam_role.app_container_role.arn - container_definitions = jsonencode([ - { - name = "${var.app_name}-flyway" - image = "${var.flyway_image}" - essential = true - environment = [ - { - name = "FLYWAY_URL" - value = "jdbc:postgresql://${data.aws_rds_cluster.rds_cluster.endpoint}/${var.db_name}?sslmode=require" - }, - { - name = "FLYWAY_USER" - value = local.db_master_creds.username - }, - { - name = "FLYWAY_PASSWORD" - value = local.db_master_creds.password - }, - { - name = "FLYWAY_DEFAULT_SCHEMA" - value = "${var.db_schema}" - }, - { - name = "FLYWAY_CONNECT_RETRIES" - value = "2" - }, - { - name = "FLYWAY_GROUP" - value = "true" - } - ] - - logConfiguration = { - logDriver = "awslogs" - options = { - awslogs-create-group = "true" - awslogs-group = "/ecs/${var.app_name}/flyway" - awslogs-region = var.aws_region - awslogs-stream-prefix = "ecs" - } - } - mountPoints = [] - volumesFrom = [] - - } - ]) - lifecycle { - replace_triggered_by = [terraform_data.trigger_flyway] - } - provisioner "local-exec" { - interpreter = ["bash", "-c"] - command = <<-EOF - set -euo pipefail - - max_attempts=5 - attempt=1 - task_arn="" - while [[ $attempt -le $max_attempts ]]; do - echo "Starting Flyway task (attempt $attempt)..." - task_arn=$(aws ecs run-task \ - --task-definition ${var.app_name}-flyway-task \ - --cluster ${aws_ecs_cluster.ecs_cluster.id} \ - --count 1 \ - --network-configuration "{\"awsvpcConfiguration\":{\"subnets\":[\"${data.aws_subnets.app.ids[0]}\"],\"securityGroups\":[\"${data.aws_security_group.app.id}\"],\"assignPublicIp\":\"DISABLED\"}}" \ - --query 'tasks[0].taskArn' \ - --output text) - - if [[ -n "$task_arn" && "$task_arn" != "None" ]]; then - echo "Flyway task started with ARN: $task_arn at $(date)." - break - fi - echo "No task ARN returned. Retrying in 5 seconds..." - sleep 5 - ((attempt++)) - done - if [[ -z "$task_arn" || "$task_arn" == "None" ]]; then - echo "ERROR: Failed to start ECS task after $max_attempts attempts." - exit 1 - fi - echo "Waiting for Flyway task to complete..." - aws ecs wait tasks-stopped --cluster ${aws_ecs_cluster.ecs_cluster.id} --tasks $task_arn - - echo "Flyway task completed, at $(date)." - - task_status=$(aws ecs describe-tasks --cluster ${aws_ecs_cluster.ecs_cluster.id} --tasks $task_arn --query 'tasks[0].lastStatus' --output text) - echo "Flyway task status: $task_status at $(date)." - log_stream_name=$(aws logs describe-log-streams \ - --log-group-name "/ecs/${var.app_name}/flyway" \ - --order-by "LastEventTime" \ - --descending \ - --limit 1 \ - --query 'logStreams[0].logStreamName' \ - --output text) - - echo "Fetching logs from log stream: $log_stream_name" - - aws logs get-log-events \ - --log-group-name "/ecs/${var.app_name}/flyway" \ - --log-stream-name $log_stream_name \ - --limit 1000 \ - --no-cli-pager - task_exit_code=$(aws ecs describe-tasks \ - --cluster ${aws_ecs_cluster.ecs_cluster.id} \ - --tasks $task_arn \ - --query 'tasks[0].containers[0].exitCode' \ - --output text) - - if [ "$task_exit_code" != "0" ]; then - echo "Flyway task failed with exit code: $task_exit_code" - exit 1 - fi - EOF - } -} - resource "aws_ecs_task_definition" "node_api_task" { family = "${var.app_name}-task" network_mode = "awsvpc" @@ -177,28 +34,16 @@ resource "aws_ecs_task_definition" "node_api_task" { essential = true environment = [ { - name = "POSTGRES_HOST" - value = data.aws_rds_cluster.rds_cluster.endpoint + name = "DYNAMODB_TABLE_NAME" + value = "${var.dynamodb_table_name}" }, { - name = "POSTGRES_READ_ONLY_HOST" - value = data.aws_rds_cluster.rds_cluster.reader_endpoint + name = "AWS_REGION" + value = var.aws_region }, { - name = "POSTGRES_USER" - value = local.db_master_creds.username - }, - { - name = "POSTGRES_PASSWORD" - value = local.db_master_creds.password - }, - { - name = "POSTGRES_DATABASE" - value = var.db_name - }, - { - name = "POSTGRES_SCHEMA" - value = "${var.db_schema}" + name = "NODE_ENV" + value = "production" }, { name = "PORT" @@ -232,10 +77,10 @@ resource "aws_ecs_task_definition" "node_api_task" { resource "aws_ecs_service" "node_api_service" { - name = "${var.app_name}-service" - cluster = aws_ecs_cluster.ecs_cluster.id - task_definition = aws_ecs_task_definition.node_api_task.arn - desired_count = 1 + name = "${var.app_name}-service" + cluster = aws_ecs_cluster.ecs_cluster.id + task_definition = aws_ecs_task_definition.node_api_task.arn + desired_count = 1 health_check_grace_period_seconds = 60 capacity_provider_strategy { @@ -256,10 +101,10 @@ resource "aws_ecs_service" "node_api_service" { load_balancer { target_group_arn = aws_alb_target_group.app.id - container_name = "${local.container_name}" + container_name = local.container_name container_port = var.app_port } wait_for_steady_state = true - depends_on = [aws_iam_role_policy_attachment.ecs_task_execution_role] - tags = local.common_tags -} \ No newline at end of file + depends_on = [aws_iam_role_policy_attachment.ecs_task_execution_role] + tags = local.common_tags +} diff --git a/infrastructure/api/iam.tf b/infrastructure/api/iam.tf index 7895a57..8b75e2e 100644 --- a/infrastructure/api/iam.tf +++ b/infrastructure/api/iam.tf @@ -14,8 +14,8 @@ data "aws_iam_policy_document" "ecs_task_execution_role" { } } } -data "aws_iam_policy" "appRDS" { - name = "AmazonRDSDataFullAccess" +data "aws_iam_policy" "appDynamoDB" { + name = "AmazonDynamoDBFullAccess" } # ECS task execution role @@ -100,7 +100,7 @@ resource "aws_iam_role_policy" "app_container_cwlogs" { } EOF } -resource "aws_iam_role_policy_attachment" "rdsAttach" { +resource "aws_iam_role_policy_attachment" "dynamodbAttach" { role = aws_iam_role.app_container_role.name - policy_arn = data.aws_iam_policy.appRDS.arn + policy_arn = data.aws_iam_policy.appDynamoDB.arn } \ No newline at end of file diff --git a/infrastructure/api/vars.tf b/infrastructure/api/vars.tf index a7afd20..8e03138 100644 --- a/infrastructure/api/vars.tf +++ b/infrastructure/api/vars.tf @@ -7,18 +7,6 @@ variable "app_env" { type = string } -variable "db_name" { - description = "The default schema for Flyway" - type = string - default = "app" -} - -variable "db_schema" { - description = "The default schema for Flyway" - type = string - default = "app" -} - variable "subnet_app_a" { description = "Value of the name tag for a subnet in the APP security group" type = string @@ -70,10 +58,7 @@ variable "common_tags" { type = map(string) default = {} } -variable "flyway_image" { - description = "The image for the Flyway container" - type = string -} +# Note: Flyway variables removed as DynamoDB doesn't require migrations variable "api_image" { description = "The image for the API container" type = string @@ -104,63 +89,19 @@ variable "min_capacity" { variable "max_capacity" { type = number default = 5 - description = < 0 + error_message = "The DynamoDB table name must not be empty." + } } -variable "is_public_api" { - description = "Flag to indicate if the API is public or private" - type = bool - default = true -} \ No newline at end of file diff --git a/infrastructure/api/waf.tf b/infrastructure/api/waf.tf index 3ba8b8d..f677380 100644 --- a/infrastructure/api/waf.tf +++ b/infrastructure/api/waf.tf @@ -67,9 +67,9 @@ resource "aws_cloudfront_distribution" "api" { cached_methods = ["GET", "HEAD"] target_origin_id = "http-api-origin" viewer_protocol_policy = "https-only" - default_ttl = 900 # 15 minutes + default_ttl = 60 # 1 minute min_ttl = 0 - max_ttl = 900 + max_ttl = 60 forwarded_values { query_string = true diff --git a/infrastructure/database/aurora-v2.tf b/infrastructure/database/aurora-v2.tf deleted file mode 100644 index 5c6272e..0000000 --- a/infrastructure/database/aurora-v2.tf +++ /dev/null @@ -1,100 +0,0 @@ -data "aws_kms_alias" "rds_key" { - name = "alias/aws/rds" -} -data "aws_caller_identity" "current" {} - -resource "random_password" "db_master_password" { - length = 12 - special = true - override_special = "!#$%&*()-_=+[]{}<>:?" -} - -resource "aws_db_subnet_group" "db_subnet_group" { - description = "For Aurora cluster ${var.db_cluster_name}" - name = "${var.db_cluster_name}-subnet-group" - subnet_ids = [ for s in data.aws_subnet.data : s.id ] - - tags = { - managed-by = "terraform" - } - - tags_all = { - managed-by = "terraform" - } -} - -data "aws_rds_engine_version" "postgresql" { - engine = "aurora-postgresql" - version = "17.4" -} - - -resource "aws_secretsmanager_secret" "db_mastercreds_secret" { - name = "${var.db_cluster_name}" - - tags = { - managed-by = "terraform" - } -} - -resource "aws_secretsmanager_secret_version" "db_mastercreds_secret_version" { - secret_id = aws_secretsmanager_secret.db_mastercreds_secret.id - secret_string = <