diff --git a/.env.aws.example b/.env.aws.example new file mode 100644 index 0000000..ad12386 --- /dev/null +++ b/.env.aws.example @@ -0,0 +1,38 @@ +# AWS Environment Configuration +# Copy this file to .env and fill in your values + +# Environment +NODE_ENV=development + +# AWS Configuration +AWS_REGION=us-west-2 +AWS_BUCKET_NAME=your-uhrp-bucket-name + +# Server Configuration +HTTP_PORT=3104 +SERVER_URL=https://your-domain.com +CORS_ORIGIN=* + +# BSV Configuration +SERVER_PRIVATE_KEY=your-32-byte-hex-private-key +BSV_NETWORK=testnet +WALLET_STORAGE_URL=https://staging-storage.babbage.systems + +# Pricing Configuration (in satoshis) +PER_BYTE_PRICE=0.00001 +BASE_PRICE=1000 +MIN_HOSTING_MINUTES=15 + +# Admin Configuration +ADMIN_TOKEN=your-super-secret-admin-token + +# Error Tracking (optional) +BUGSNAG_API_KEY=your-bugsnag-api-key + +# Notifier Configuration (optional) +NOTIFIER_URL=https://your-notifier-endpoint.com + +# AWS Credentials (for local development only) +# In production, use IAM roles instead +# AWS_ACCESS_KEY_ID=your-access-key +# AWS_SECRET_ACCESS_KEY=your-secret-key \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ee17331 --- /dev/null +++ b/.env.example @@ -0,0 +1,50 @@ +# UHRP Storage Server Configuration + +# Server Configuration +NODE_ENV=production +PORT=3000 +SERVER_URL=https://your-domain.com +HOSTING_DOMAIN=https://your-domain.com + +# Storage Provider Configuration +# Choose between 'aws' or 'gcs' +STORAGE_PROVIDER=aws + +# Common storage configuration (works for both providers) +STORAGE_BUCKET_NAME=your-bucket-name + +# AWS-specific configuration (required when STORAGE_PROVIDER=aws) +AWS_REGION=us-west-2 +# AWS credentials are automatically loaded from IAM roles in ECS/EKS +# For local development, use AWS CLI configuration or environment variables: +# AWS_ACCESS_KEY_ID=your-access-key +# AWS_SECRET_ACCESS_KEY=your-secret-key + +# GCS-specific configuration (required when STORAGE_PROVIDER=gcs) +GCP_PROJECT_ID=your-project-id +GCP_BUCKET_NAME=your-gcs-bucket-name +# GCS credentials file should be placed at ./storage-creds.json +# Or set GCS_KEY_FILE=/path/to/credentials.json + +# Authentication and Security +SERVER_PRIVATE_KEY=your-server-private-key +ADMIN_TOKEN=your-secure-admin-token + +# BSV Network Configuration +BSV_NETWORK=mainnet +BSV_WALLET_DIR=./wallet + +# Optional: Bugsnag error tracking +BUGSNAG_API_KEY=your-bugsnag-api-key + +# Optional: Payment configuration +PAYMENT_KEY=your-payment-key +PAYMENT_URL=https://payment-service.com + +# Optional: Minimum hosting time in minutes +MIN_HOSTING_MINUTES=60 + +# Legacy environment variables (kept for backward compatibility) +# These will be used if STORAGE_BUCKET_NAME is not set +# GCP_BUCKET_NAME=your-gcs-bucket-name # Used by GCS provider +# AWS_BUCKET_NAME=your-s3-bucket-name # Used by AWS provider \ No newline at end of file diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 455bf1d..da72f98 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,66 +1,71 @@ -name: Build and push OCI image to Docker Hub +name: Build and Push to GHCR on: push: tags: - - 'v*' + - "v*" + branches: + - master + pull_request: + branches: + - master + workflow_dispatch: jobs: - check-current-branch: + get_tag: runs-on: ubuntu-latest - outputs: - branch: ${{ steps.check_step.outputs.branch }} steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Get current branch - id: check_step - # 1. Get the list of branches ref where this tag exists - # 2. Remove 'origin/' from that result - # 3. Put that string in output + - name: Determine deployment tag + id: deployment_tag run: | - raw=$(git branch -r --contains ${{ github.ref }}) - branch="$(echo ${raw//origin\//} | tr -d '\n')" - echo "{name}=branch" >> $GITHUB_OUTPUT - echo "Branches where this tag exists : $branch." + if [[ '${{ github.ref_type }}' == 'tag' ]]; then + export tag=${{ github.ref_name }} + echo "version tag is $tag" + echo "id=$tag" >> $GITHUB_OUTPUT + else + export tag=latest + echo "version tag is $tag" + echo "id=$tag" >> $GITHUB_OUTPUT + fi + outputs: + deployment_tag: ${{ steps.deployment_tag.outputs.id }} - image: + build-and-push: + needs: [ get_tag ] runs-on: ubuntu-latest - needs: check-current-branch - if: contains(${{ needs.check.outputs.branch }}, 'main')` + permissions: + contents: read + packages: write steps: - - name: Check out the repo + - name: Checkout code uses: actions/checkout@v4 - - name: Get build args - id: build_args - run: | - echo "APP_COMMIT=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" - echo "APP_VERSION=$(git describe --tags --always --abbrev=0 --match='v[0-9]*.[0-9]*.[0-9]*' 2> /dev/null | sed 's/^.//')" >> "$GITHUB_OUTPUT" - - - name: Log in to Docker Hub + - name: Log in to GitHub Container Registry uses: docker/login-action@v3 with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata (tags, labels) - id: meta - uses: docker/metadata-action@v5 + - name: Build and push frontend Docker image + uses: docker/build-push-action@v5 with: - images: bsvb/uhrp-storage-server + context: . # Build context (root directory, adjust if Dockerfile is elsewhere) + file: ./Dockerfile # Path to Dockerfile + # push: ${{ github.event_name != 'pull_request' }} # Only push on push events, not PRs + push: true + tags: | + ghcr.io/${{ github.repository }}:${{ github.sha }} + ghcr.io/${{ github.repository }}:${{ needs.get_tag.outputs.deployment_tag }} - - name: Build and push image + - name: Build and push frontend Docker image uses: docker/build-push-action@v5 with: - context: . - file: ./Dockerfile + context: ./k8s/s3-event-handler # Build context (root directory, adjust if Dockerfile is elsewhere) + file: ./k8s/s3-event-handler/Dockerfile # Path to Dockerfile + # push: ${{ github.event_name != 'pull_request' }} # Only push on push events, not PRs push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - build-args: | - APP_COMMIT=${{ steps.build_args.outputs.APP_COMMIT }} - APP_VERSION=${{ steps.build_args.outputs.APP_VERSION }} + tags: | + ghcr.io/${{ github.repository }}-s3-notifier:${{ github.sha }} + ghcr.io/${{ github.repository }}-s3-notifier:${{ needs.get_tag.outputs.deployment_tag }} + diff --git a/.github/workflows/deploy-aws.yaml b/.github/workflows/deploy-aws.yaml new file mode 100644 index 0000000..de8a4ef --- /dev/null +++ b/.github/workflows/deploy-aws.yaml @@ -0,0 +1,139 @@ +name: Deploy to AWS + +on: + push: + branches: + - master + - production + +env: + AWS_REGION: ${{ secrets.AWS_REGION }} + ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY }} + ECS_SERVICE: ${{ github.ref_name == 'production' && 'prod-uhrp-storage-service' || 'staging-uhrp-storage-service' }} + ECS_CLUSTER: ${{ github.ref_name == 'production' && 'prod-uhrp-cluster' || 'staging-uhrp-cluster' }} + TASK_DEFINITION_FAMILY: uhrp-storage-server + LAMBDA_FUNCTION: ${{ github.ref_name == 'production' && 'prod-uhrp-notifier' || 'staging-uhrp-notifier' }} + +jobs: + deploy: + name: Deploy to AWS + runs-on: ubuntu-latest + environment: ${{ github.ref_name }} + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + id: build-image + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + IMAGE_TAG: ${{ github.ref_name }}-${{ github.sha }} + run: | + # Build the Docker image + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . + docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest-${{ github.ref_name }} + + # Push both tags to ECR + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest-${{ github.ref_name }} + + # Output the image URI + echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT + + - name: Download current task definition + run: | + aws ecs describe-task-definition \ + --task-definition ${{ env.TASK_DEFINITION_FAMILY }} \ + --query taskDefinition > task-definition.json + + # Remove fields that shouldn't be in the new definition + jq 'del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)' task-definition.json > task-definition-clean.json + mv task-definition-clean.json task-definition.json + + - name: Update task definition with new image + id: task-def + uses: aws-actions/amazon-ecs-render-task-definition@v1 + with: + task-definition: task-definition.json + container-name: uhrp-storage + image: ${{ steps.build-image.outputs.image }} + environment-variables: | + NODE_ENV=${{ github.ref_name == 'production' && 'production' || 'staging' }} + AWS_BUCKET_NAME=${{ github.ref_name == 'production' && secrets.PROD_AWS_BUCKET_NAME || secrets.STAGING_AWS_BUCKET_NAME }} + SERVER_URL=${{ github.ref_name == 'production' && secrets.PROD_SERVER_URL || secrets.STAGING_SERVER_URL }} + CORS_ORIGIN=${{ github.ref_name == 'production' && secrets.PROD_CORS_ORIGIN || secrets.STAGING_CORS_ORIGIN }} + PER_BYTE_PRICE=${{ github.ref_name == 'production' && secrets.PROD_PER_BYTE_PRICE || secrets.STAGING_PER_BYTE_PRICE }} + BASE_PRICE=${{ github.ref_name == 'production' && secrets.PROD_BASE_PRICE || secrets.STAGING_BASE_PRICE }} + BSV_NETWORK=${{ github.ref_name == 'production' && 'mainnet' || 'testnet' }} + MIN_HOSTING_MINUTES=${{ github.ref_name == 'production' && secrets.PROD_MIN_HOSTING_MINUTES || secrets.STAGING_MIN_HOSTING_MINUTES }} + WALLET_STORAGE_URL=${{ github.ref_name == 'production' && secrets.PROD_WALLET_STORAGE_URL || secrets.STAGING_WALLET_STORAGE_URL }} + + - name: Deploy Amazon ECS task definition + uses: aws-actions/amazon-ecs-deploy-task-definition@v1 + with: + task-definition: ${{ steps.task-def.outputs.task-definition }} + service: ${{ env.ECS_SERVICE }} + cluster: ${{ env.ECS_CLUSTER }} + wait-for-service-stability: true + + - name: Package and deploy Lambda function + run: | + # Package the notifier + cd notifier + npm ci --production + zip -r ../notifier.zip . + cd .. + + # Update Lambda function code + aws lambda update-function-code \ + --function-name ${{ env.LAMBDA_FUNCTION }} \ + --zip-file fileb://notifier.zip + + # Update Lambda environment variables + aws lambda update-function-configuration \ + --function-name ${{ env.LAMBDA_FUNCTION }} \ + --environment Variables="{ + NODE_ENV=${{ github.ref_name == 'production' && 'production' || 'staging' }}, + SERVER_PRIVATE_KEY=${{ github.ref_name == 'production' && secrets.PROD_SERVER_PRIVATE_KEY || secrets.STAGING_SERVER_PRIVATE_KEY }}, + BSV_NETWORK=${{ github.ref_name == 'production' && 'mainnet' || 'testnet' }}, + AWS_BUCKET_NAME=${{ github.ref_name == 'production' && secrets.PROD_AWS_BUCKET_NAME || secrets.STAGING_AWS_BUCKET_NAME }} + }" + + # Wait for configuration update to complete + aws lambda wait function-updated \ + --function-name ${{ env.LAMBDA_FUNCTION }} + + - name: Verify deployment + run: | + echo "🚀 Deployment completed!" + echo "ECS Service: ${{ env.ECS_SERVICE }}" + echo "Lambda Function: ${{ env.LAMBDA_FUNCTION }}" + echo "Image: ${{ steps.build-image.outputs.image }}" + + # Get service info + aws ecs describe-services \ + --cluster ${{ env.ECS_CLUSTER }} \ + --services ${{ env.ECS_SERVICE }} \ + --query 'services[0].{desiredCount:desiredCount,runningCount:runningCount,pendingCount:pendingCount}' \ + --output table + + - name: Send deployment notification + if: always() + run: | + if [ "${{ job.status }}" == "success" ]; then + echo "✅ Deployment to ${{ github.ref_name }} succeeded" + else + echo "❌ Deployment to ${{ github.ref_name }} failed" + fi \ No newline at end of file diff --git a/.github/workflows/deploy-eks.yaml b/.github/workflows/deploy-eks.yaml new file mode 100644 index 0000000..7c943c5 --- /dev/null +++ b/.github/workflows/deploy-eks.yaml @@ -0,0 +1,181 @@ +name: Deploy to EKS + +on: + push: + branches: + - master + - production + +env: + AWS_REGION: ${{ secrets.AWS_REGION }} + ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY }} + EKS_CLUSTER_NAME: ${{ secrets.EKS_CLUSTER_NAME }} + NAMESPACE: uhrp-storage + +jobs: + deploy: + name: Deploy to EKS + runs-on: ubuntu-latest + environment: ${{ github.ref_name }} + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build and push storage server image + id: build-storage-server + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + IMAGE_TAG: ${{ github.ref_name }}-${{ github.sha }} + run: | + # Build and push storage server + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . + docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest-${{ github.ref_name }} + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest-${{ github.ref_name }} + echo "storage_image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT + + - name: Build and push event handler image + id: build-event-handler + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + IMAGE_TAG: ${{ github.ref_name }}-${{ github.sha }} + run: | + # Build and push event handler + cd k8s/s3-event-handler + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY-event-handler:$IMAGE_TAG . + docker tag $ECR_REGISTRY/$ECR_REPOSITORY-event-handler:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY-event-handler:latest-${{ github.ref_name }} + docker push $ECR_REGISTRY/$ECR_REPOSITORY-event-handler:$IMAGE_TAG + docker push $ECR_REGISTRY/$ECR_REPOSITORY-event-handler:latest-${{ github.ref_name }} + echo "event_handler_image=$ECR_REGISTRY/$ECR_REPOSITORY-event-handler:$IMAGE_TAG" >> $GITHUB_OUTPUT + + - name: Setup kubectl + uses: aws-actions/setup-kubectl@v3 + with: + version: 'v1.28.0' + + - name: Update kubeconfig + run: | + aws eks update-kubeconfig --region ${{ env.AWS_REGION }} --name ${{ env.EKS_CLUSTER_NAME }} + + - name: Create namespace if not exists + run: | + kubectl create namespace ${{ env.NAMESPACE }} --dry-run=client -o yaml | kubectl apply -f - + + - name: Update ConfigMap + run: | + # Update configmap with environment-specific values + kubectl apply -f k8s/configmap.yaml + + # Patch configmap with dynamic values + kubectl patch configmap storage-server-config -n ${{ env.NAMESPACE }} --type merge -p ' + { + "data": { + "NODE_ENV": "${{ github.ref_name == 'production' && 'production' || 'staging' }}", + "AWS_BUCKET_NAME": "${{ github.ref_name == 'production' && secrets.PROD_AWS_BUCKET_NAME || secrets.STAGING_AWS_BUCKET_NAME }}", + "SERVER_URL": "${{ github.ref_name == 'production' && secrets.PROD_SERVER_URL || secrets.STAGING_SERVER_URL }}", + "CORS_ORIGIN": "${{ github.ref_name == 'production' && secrets.PROD_CORS_ORIGIN || secrets.STAGING_CORS_ORIGIN }}", + "BSV_NETWORK": "${{ github.ref_name == 'production' && 'mainnet' || 'testnet' }}", + "SQS_QUEUE_URL": "${{ github.ref_name == 'production' && secrets.PROD_SQS_QUEUE_URL || secrets.STAGING_SQS_QUEUE_URL }}" + } + }' + + - name: Update Secrets + run: | + # Check if secret exists, update or create + if kubectl get secret uhrp-secrets -n ${{ env.NAMESPACE }} >/dev/null 2>&1; then + echo "Updating existing secret" + else + echo "Creating new secret" + kubectl create secret generic uhrp-secrets \ + --namespace=${{ env.NAMESPACE }} \ + --from-literal=server-private-key="${{ github.ref_name == 'production' && secrets.PROD_SERVER_PRIVATE_KEY || secrets.STAGING_SERVER_PRIVATE_KEY }}" \ + --from-literal=admin-token="${{ github.ref_name == 'production' && secrets.PROD_ADMIN_TOKEN || secrets.STAGING_ADMIN_TOKEN }}" \ + --from-literal=bugsnag-api-key="${{ github.ref_name == 'production' && secrets.PROD_BUGSNAG_API_KEY || secrets.STAGING_BUGSNAG_API_KEY }}" + fi + + - name: Deploy storage server + run: | + # Update deployment with new image + kubectl set image deployment/storage-server \ + storage-server=${{ steps.build-storage-server.outputs.storage_image }} \ + -n ${{ env.NAMESPACE }} + + # Apply other manifests + kubectl apply -f k8s/service.yaml + kubectl apply -f k8s/ingress.yaml + kubectl apply -f k8s/hpa.yaml + + - name: Deploy event handler + run: | + # Update deployment with new image + if kubectl get deployment event-handler -n ${{ env.NAMESPACE }} >/dev/null 2>&1; then + kubectl set image deployment/event-handler \ + event-handler=${{ steps.build-event-handler.outputs.event_handler_image }} \ + -n ${{ env.NAMESPACE }} + else + # First deployment - apply the manifest then update image + kubectl apply -f k8s/s3-event-handler/deployment.yaml + kubectl set image deployment/event-handler \ + event-handler=${{ steps.build-event-handler.outputs.event_handler_image }} \ + -n ${{ env.NAMESPACE }} + fi + + - name: Wait for rollout + run: | + kubectl rollout status deployment/storage-server -n ${{ env.NAMESPACE }} --timeout=300s + kubectl rollout status deployment/event-handler -n ${{ env.NAMESPACE }} --timeout=300s + + - name: Verify deployment + run: | + echo "🚀 Deployment completed!" + echo "Storage Server Image: ${{ steps.build-storage-server.outputs.storage_image }}" + echo "Event Handler Image: ${{ steps.build-event-handler.outputs.event_handler_image }}" + + # Get deployment status + kubectl get deployments -n ${{ env.NAMESPACE }} + kubectl get pods -n ${{ env.NAMESPACE }} + kubectl get ingress -n ${{ env.NAMESPACE }} + + # Get ingress URL + INGRESS_URL=$(kubectl get ingress storage-server-ingress -n ${{ env.NAMESPACE }} -o jsonpath='{.status.loadBalancer.ingress[0].hostname}' || echo "Pending...") + echo "Ingress URL: $INGRESS_URL" + + - name: Run smoke tests + run: | + # Wait for ingress to be ready + echo "Waiting for ingress to be ready..." + for i in {1..30}; do + INGRESS_URL=$(kubectl get ingress storage-server-ingress -n ${{ env.NAMESPACE }} -o jsonpath='{.status.loadBalancer.ingress[0].hostname}' 2>/dev/null || echo "") + if [ -n "$INGRESS_URL" ] && [ "$INGRESS_URL" != "Pending..." ]; then + echo "Ingress ready at: $INGRESS_URL" + break + fi + echo "Waiting for ingress... ($i/30)" + sleep 10 + done + + # Basic health check + if [ -n "$INGRESS_URL" ]; then + curl -f -s -o /dev/null -w "%{http_code}" "http://$INGRESS_URL/" || echo "Health check pending..." + fi + + - name: Notify deployment status + if: always() + run: | + if [ "${{ job.status }}" == "success" ]; then + echo "✅ Successfully deployed to ${{ github.ref_name }} environment" + else + echo "❌ Deployment to ${{ github.ref_name }} failed" + fi \ No newline at end of file diff --git a/AWS-MIGRATION-GUIDE.md b/AWS-MIGRATION-GUIDE.md new file mode 100644 index 0000000..4a60c7f --- /dev/null +++ b/AWS-MIGRATION-GUIDE.md @@ -0,0 +1,486 @@ +# AWS Migration Guide - Code Modifications + +This guide details the code changes required to migrate the UHRP Storage Server from Google Cloud Platform (GCP) to Amazon Web Services (AWS). + +## Overview + +The main changes involve: +1. Replacing Google Cloud Storage with AWS S3 +2. Updating environment variables +3. Modifying the Lambda function handler +4. Adjusting authentication and configuration + +## Required Dependencies + +First, update your `package.json` to include AWS SDK v3: + +```json +{ + "dependencies": { + "@aws-sdk/client-s3": "^3.0.0", + "@aws-sdk/s3-request-presigner": "^3.0.0", + "@aws-sdk/lib-storage": "^3.0.0" + } +} +``` + +Remove the GCP dependency: +```bash +npm uninstall @google-cloud/storage +``` + +## Environment Variable Mapping + +| GCP Variable | AWS Variable | Notes | +|--------------|--------------|-------| +| `GCS_BUCKET_NAME` | `AWS_BUCKET_NAME` | S3 bucket name | +| `GCS_BUCKET_EXTRA_TIME` | `S3_RETENTION_DAYS` | Convert to days | +| `GCS_KEY_FILE` | Not needed | Use IAM roles | +| `GCS_KEY_VALUE` | Not needed | Use IAM roles | +| `GCP_PROJECT_ID` | `AWS_REGION` | AWS region | +| `GCP_STORAGE_CREDS` | Not needed | Use IAM roles | + +## Code Modifications + +### 1. Storage Client Initialization + +**Original (GCP):** +```javascript +// src/utilities/storage.js +const { Storage } = require('@google-cloud/storage'); + +const storage = new Storage({ + projectId: process.env.GCP_PROJECT_ID, + keyFilename: process.env.GCS_KEY_FILE, + credentials: process.env.GCS_KEY_VALUE ? JSON.parse(process.env.GCS_KEY_VALUE) : undefined +}); + +const bucket = storage.bucket(process.env.GCS_BUCKET_NAME); +``` + +**New (AWS):** +```javascript +// src/utilities/storage.js +const { S3Client } = require('@aws-sdk/client-s3'); + +const s3Client = new S3Client({ + region: process.env.AWS_REGION || 'us-west-2', + // Credentials are automatically loaded from IAM role in ECS/Lambda +}); + +const bucketName = process.env.AWS_BUCKET_NAME; +``` + +### 2. File Upload + +**Original (GCP):** +```javascript +// src/controllers/upload.js +async function uploadFile(file, fileName) { + const blob = bucket.file(fileName); + const stream = blob.createWriteStream({ + metadata: { + contentType: file.mimetype, + }, + }); + + return new Promise((resolve, reject) => { + stream.on('error', reject); + stream.on('finish', () => { + blob.makePublic().then(() => { + resolve(`https://storage.googleapis.com/${bucket.name}/${fileName}`); + }); + }); + stream.end(file.buffer); + }); +} +``` + +**New (AWS):** +```javascript +// src/controllers/upload.js +const { PutObjectCommand } = require('@aws-sdk/client-s3'); +const { Upload } = require('@aws-sdk/lib-storage'); + +async function uploadFile(file, fileName) { + // For small files + if (file.size < 5 * 1024 * 1024) { // 5MB + const command = new PutObjectCommand({ + Bucket: bucketName, + Key: fileName, + Body: file.buffer, + ContentType: file.mimetype, + // S3 doesn't have makePublic, use bucket policy or presigned URLs + }); + + await s3Client.send(command); + return `https://${bucketName}.s3.${process.env.AWS_REGION}.amazonaws.com/${fileName}`; + } else { + // For large files, use multipart upload + const upload = new Upload({ + client: s3Client, + params: { + Bucket: bucketName, + Key: fileName, + Body: file.stream, + ContentType: file.mimetype, + }, + }); + + await upload.done(); + return `https://${bucketName}.s3.${process.env.AWS_REGION}.amazonaws.com/${fileName}`; + } +} +``` + +### 3. File Download + +**Original (GCP):** +```javascript +// src/controllers/download.js +async function downloadFile(fileName) { + const file = bucket.file(fileName); + const [exists] = await file.exists(); + + if (!exists) { + throw new Error('File not found'); + } + + const [metadata] = await file.getMetadata(); + const stream = file.createReadStream(); + + return { + stream, + contentType: metadata.contentType, + size: metadata.size + }; +} +``` + +**New (AWS):** +```javascript +// src/controllers/download.js +const { GetObjectCommand, HeadObjectCommand } = require('@aws-sdk/client-s3'); + +async function downloadFile(fileName) { + try { + // Check if file exists and get metadata + const headCommand = new HeadObjectCommand({ + Bucket: bucketName, + Key: fileName + }); + + const metadata = await s3Client.send(headCommand); + + // Get the file + const getCommand = new GetObjectCommand({ + Bucket: bucketName, + Key: fileName + }); + + const response = await s3Client.send(getCommand); + + return { + stream: response.Body, + contentType: response.ContentType, + size: response.ContentLength + }; + } catch (error) { + if (error.name === 'NoSuchKey') { + throw new Error('File not found'); + } + throw error; + } +} +``` + +### 4. File Deletion + +**Original (GCP):** +```javascript +// src/controllers/delete.js +async function deleteFile(fileName) { + const file = bucket.file(fileName); + await file.delete(); +} +``` + +**New (AWS):** +```javascript +// src/controllers/delete.js +const { DeleteObjectCommand } = require('@aws-sdk/client-s3'); + +async function deleteFile(fileName) { + const command = new DeleteObjectCommand({ + Bucket: bucketName, + Key: fileName + }); + + await s3Client.send(command); +} +``` + +### 5. Generate Signed URLs + +**Original (GCP):** +```javascript +async function getSignedUrl(fileName, expiresIn = 3600) { + const options = { + version: 'v4', + action: 'read', + expires: Date.now() + expiresIn * 1000, + }; + + const [url] = await bucket.file(fileName).getSignedUrl(options); + return url; +} +``` + +**New (AWS):** +```javascript +const { getSignedUrl } = require('@aws-sdk/s3-request-presigner'); +const { GetObjectCommand } = require('@aws-sdk/client-s3'); + +async function getSignedUrl(fileName, expiresIn = 3600) { + const command = new GetObjectCommand({ + Bucket: bucketName, + Key: fileName + }); + + const url = await getSignedUrl(s3Client, command, { expiresIn }); + return url; +} +``` + +### 6. Lambda Function Handler + +**Original (GCP Cloud Function):** +```javascript +// notifier/index.js +exports.notifier = async (file, context) => { + const bucketName = file.bucket; + const fileName = file.name; + + console.log(`File ${fileName} uploaded to ${bucketName}`); + + // Process the file + await processFile(bucketName, fileName); +}; +``` + +**New (AWS Lambda):** +```javascript +// notifier/index.js +exports.notifier = async (event, context) => { + // Lambda receives S3 events in a different format + for (const record of event.Records) { + const bucketName = record.s3.bucket.name; + const fileName = decodeURIComponent(record.s3.object.key.replace(/\+/g, ' ')); + const eventName = record.eventName; + + console.log(`Event ${eventName} for file ${fileName} in bucket ${bucketName}`); + + if (eventName.startsWith('ObjectCreated:')) { + await processFile(bucketName, fileName); + } + } + + return { + statusCode: 200, + body: JSON.stringify({ message: 'Success' }) + }; +}; +``` + +### 7. Stream Handling + +**Original (GCP):** +```javascript +// Streaming uploads +const stream = bucket.file(fileName).createWriteStream(); +fileStream.pipe(stream); +``` + +**New (AWS):** +```javascript +// For streaming uploads, use the Upload class +const { Upload } = require('@aws-sdk/lib-storage'); + +const upload = new Upload({ + client: s3Client, + params: { + Bucket: bucketName, + Key: fileName, + Body: fileStream, + ContentType: 'application/octet-stream' + } +}); + +await upload.done(); +``` + +### 8. Bucket Policy (Replace makePublic) + +Since S3 doesn't have a `makePublic()` method, you need to set a bucket policy for public access: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "PublicReadGetObject", + "Effect": "Allow", + "Principal": "*", + "Action": "s3:GetObject", + "Resource": "arn:aws:s3:::your-bucket-name/public/*" + } + ] +} +``` + +Or use CloudFront for better performance and security. + +### 9. Error Handling + +**Original (GCP):** +```javascript +try { + await file.save(data); +} catch (error) { + if (error.code === 404) { + // File not found + } +} +``` + +**New (AWS):** +```javascript +try { + await s3Client.send(command); +} catch (error) { + if (error.name === 'NoSuchKey') { + // File not found + } else if (error.name === 'NoSuchBucket') { + // Bucket not found + } + // AWS errors have different structure + console.error('AWS Error:', error.$metadata?.httpStatusCode, error.name); +} +``` + +### 10. Configuration Updates + +Update your server initialization to remove GCS-specific configurations: + +```javascript +// src/server.js +// Remove GCS initialization +// Add AWS SDK v3 doesn't require explicit initialization + +// Update health check endpoint +app.get('/health', async (req, res) => { + try { + // Check S3 access + const { HeadBucketCommand } = require('@aws-sdk/client-s3'); + await s3Client.send(new HeadBucketCommand({ Bucket: bucketName })); + res.json({ status: 'healthy', storage: 'aws-s3' }); + } catch (error) { + res.status(500).json({ status: 'unhealthy', error: error.message }); + } +}); +``` + +## Testing the Migration + +1. **Unit Tests**: Update your tests to mock AWS SDK instead of GCP: +```javascript +// Mock AWS SDK +jest.mock('@aws-sdk/client-s3', () => ({ + S3Client: jest.fn(() => ({ + send: jest.fn() + })), + PutObjectCommand: jest.fn(), + GetObjectCommand: jest.fn() +})); +``` + +2. **Integration Tests**: Use LocalStack for testing S3 locally: +```bash +docker run -d \ + --name localstack \ + -p 4566:4566 \ + -e SERVICES=s3 \ + localstack/localstack +``` + +3. **Environment Setup for Local Development**: +```javascript +// For local development with LocalStack +const s3Client = new S3Client({ + region: process.env.AWS_REGION || 'us-west-2', + endpoint: process.env.S3_ENDPOINT || undefined, // http://localhost:4566 for LocalStack + forcePathStyle: true // Required for LocalStack +}); +``` + +## Performance Optimizations + +1. **Use S3 Transfer Acceleration** for better upload speeds: +```javascript +const s3Client = new S3Client({ + region: process.env.AWS_REGION, + useAccelerateEndpoint: true +}); +``` + +2. **Implement S3 Multipart Upload** for large files (shown above) + +3. **Use CloudFront** for content delivery: +```javascript +const cloudfrontUrl = `https://${process.env.CLOUDFRONT_DOMAIN}/${fileName}`; +``` + +## Security Considerations + +1. **Use IAM Roles** instead of access keys in production +2. **Enable S3 Bucket Encryption**: +```javascript +const command = new PutObjectCommand({ + Bucket: bucketName, + Key: fileName, + Body: file.buffer, + ServerSideEncryption: 'AES256' +}); +``` + +3. **Implement bucket policies** for access control +4. **Use VPC Endpoints** for private S3 access + +## Rollback Plan + +If you need to rollback to GCP: +1. Keep the original GCS code in a separate branch +2. Use environment variables to switch between AWS and GCP +3. Implement a storage abstraction layer + +## Common Issues and Solutions + +1. **CORS Issues**: Ensure S3 CORS configuration matches your needs +2. **Permission Errors**: Check IAM roles and bucket policies +3. **Region Mismatches**: Ensure all services are in the same region +4. **Large File Uploads**: Use multipart upload for files > 5MB +5. **Public Access**: S3 blocks public access by default - update bucket policy + +## Monitoring and Debugging + +1. **CloudWatch Logs**: All S3 operations are logged +2. **X-Ray Tracing**: Add AWS X-Ray for distributed tracing +3. **S3 Access Logs**: Enable S3 access logging for audit trails + +```javascript +// Add X-Ray tracing +const AWSXRay = require('aws-xray-sdk-core'); +const AWS = AWSXRay.captureAWS(require('aws-sdk')); +``` + +This completes the code migration from GCP to AWS. Test thoroughly in a staging environment before deploying to production. \ No newline at end of file diff --git a/README-AWS.md b/README-AWS.md new file mode 100644 index 0000000..eaae2aa --- /dev/null +++ b/README-AWS.md @@ -0,0 +1,941 @@ +# UHRP Storage Server – AWS Deployment Guide + +This guide walks you through deploying the **UHRP Storage Server** on Amazon Web Services (AWS) with continuous delivery via GitHub Actions. This is an alternative to the GCP deployment covered in the main README. + +## Overview + +When complete, you'll have: + +- An **S3 bucket** for storing UHRP data +- An **ECS Fargate** service running the containerized application +- A **Lambda function** for handling file upload notifications +- An **Application Load Balancer (ALB)** providing HTTPS access +- **GitHub Actions** for automated deployments + +## GCP to AWS Service Mapping + +| GCP Service | AWS Equivalent | Purpose | +|------------|----------------|---------| +| Cloud Storage | S3 | Object storage | +| Cloud Run | ECS Fargate | Container hosting | +| Cloud Functions | Lambda | Event processing | +| Artifact Registry | ECR | Container registry | +| Load Balancer | ALB | HTTPS routing | +| Cloud Build | CodeBuild | Build automation | +| Pub/Sub | EventBridge | Event routing | +| IAM | IAM | Access control | + +## Prerequisites + +- **AWS Account** with billing enabled +- **GitHub repository** containing your code +- **AWS CLI** installed and configured +- **Docker** installed locally +- **Domain name** (recommended for HTTPS) + +## Phase 1: AWS Infrastructure Setup + +### 1.1 Create S3 Bucket + +```bash +# Set your AWS region and unique bucket name +export AWS_REGION=us-west-2 +export BUCKET_NAME=my-uhrp-storage-$(date +%s) + +# Create the bucket +aws s3api create-bucket \ + --bucket $BUCKET_NAME \ + --region $AWS_REGION \ + --create-bucket-configuration LocationConstraint=$AWS_REGION + +# Enable bucket versioning (optional) +aws s3api put-bucket-versioning \ + --bucket $BUCKET_NAME \ + --versioning-configuration Status=Enabled + +# Configure lifecycle rules for automatic deletion (optional) +cat > lifecycle.json << EOF +{ + "Rules": [ + { + "ID": "DeleteOldFiles", + "Status": "Enabled", + "Expiration": { + "Days": 30 + } + } + ] +} +EOF + +aws s3api put-bucket-lifecycle-configuration \ + --bucket $BUCKET_NAME \ + --lifecycle-configuration file://lifecycle.json +``` + +### 1.2 Configure S3 CORS + +```bash +# Create CORS configuration +cat > s3-cors.json << EOF +{ + "CORSRules": [ + { + "AllowedHeaders": ["*"], + "AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"], + "AllowedOrigins": ["*"], + "ExposeHeaders": ["ETag"], + "MaxAgeSeconds": 3000 + } + ] +} +EOF + +# Apply CORS configuration +aws s3api put-bucket-cors \ + --bucket $BUCKET_NAME \ + --cors-configuration file://s3-cors.json +``` + +### 1.3 Create ECR Repository + +```bash +# Create repository for Docker images +aws ecr create-repository \ + --repository-name uhrp-storage-server \ + --region $AWS_REGION + +# Get the repository URI +export ECR_URI=$(aws ecr describe-repositories \ + --repository-names uhrp-storage-server \ + --region $AWS_REGION \ + --query 'repositories[0].repositoryUri' \ + --output text) +``` + +### 1.4 Create VPC and Networking (if needed) + +```bash +# Create VPC +aws ec2 create-vpc --cidr-block 10.0.0.0/16 --query 'Vpc.VpcId' --output text +export VPC_ID= + +# Create subnets in different AZs +aws ec2 create-subnet \ + --vpc-id $VPC_ID \ + --cidr-block 10.0.1.0/24 \ + --availability-zone ${AWS_REGION}a \ + --query 'Subnet.SubnetId' --output text +export SUBNET_1= + +aws ec2 create-subnet \ + --vpc-id $VPC_ID \ + --cidr-block 10.0.2.0/24 \ + --availability-zone ${AWS_REGION}b \ + --query 'Subnet.SubnetId' --output text +export SUBNET_2= + +# Create Internet Gateway +aws ec2 create-internet-gateway --query 'InternetGateway.InternetGatewayId' --output text +export IGW_ID= + +aws ec2 attach-internet-gateway --vpc-id $VPC_ID --internet-gateway-id $IGW_ID + +# Create route table +aws ec2 create-route-table --vpc-id $VPC_ID --query 'RouteTable.RouteTableId' --output text +export ROUTE_TABLE_ID= + +aws ec2 create-route \ + --route-table-id $ROUTE_TABLE_ID \ + --destination-cidr-block 0.0.0.0/0 \ + --gateway-id $IGW_ID + +# Associate subnets with route table +aws ec2 associate-route-table --subnet-id $SUBNET_1 --route-table-id $ROUTE_TABLE_ID +aws ec2 associate-route-table --subnet-id $SUBNET_2 --route-table-id $ROUTE_TABLE_ID +``` + +## Phase 2: IAM Roles and Policies + +### 2.1 Create ECS Task Execution Role + +```bash +# Create trust policy +cat > ecs-trust-policy.json << EOF +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] +} +EOF + +# Create the role +aws iam create-role \ + --role-name uhrp-ecs-task-execution-role \ + --assume-role-policy-document file://ecs-trust-policy.json + +# Attach AWS managed policy +aws iam attach-role-policy \ + --role-name uhrp-ecs-task-execution-role \ + --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy +``` + +### 2.2 Create ECS Task Role + +```bash +# Create task role for application permissions +aws iam create-role \ + --role-name uhrp-ecs-task-role \ + --assume-role-policy-document file://ecs-trust-policy.json + +# Create custom policy for S3 access +cat > s3-policy.json << EOF +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject", + "s3:ListBucket" + ], + "Resource": [ + "arn:aws:s3:::${BUCKET_NAME}", + "arn:aws:s3:::${BUCKET_NAME}/*" + ] + } + ] +} +EOF + +# Create and attach the policy +aws iam create-policy \ + --policy-name uhrp-s3-access \ + --policy-document file://s3-policy.json + +aws iam attach-role-policy \ + --role-name uhrp-ecs-task-role \ + --policy-arn arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):policy/uhrp-s3-access +``` + +### 2.3 Create Lambda Execution Role + +```bash +# Create Lambda trust policy +cat > lambda-trust-policy.json << EOF +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] +} +EOF + +# Create Lambda role +aws iam create-role \ + --role-name uhrp-lambda-execution-role \ + --assume-role-policy-document file://lambda-trust-policy.json + +# Attach basic Lambda execution policy +aws iam attach-role-policy \ + --role-name uhrp-lambda-execution-role \ + --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + +# Attach S3 read policy +aws iam attach-role-policy \ + --role-name uhrp-lambda-execution-role \ + --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess +``` + +### 2.4 Create GitHub Actions User + +```bash +# Create policy for GitHub Actions +cat > github-actions-policy.json << EOF +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + "ecr:PutImage", + "ecr:InitiateLayerUpload", + "ecr:UploadLayerPart", + "ecr:CompleteLayerUpload" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "ecs:UpdateService", + "ecs:DescribeServices", + "ecs:RegisterTaskDefinition", + "ecs:DescribeTaskDefinition" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "iam:PassRole" + ], + "Resource": [ + "arn:aws:iam::*:role/uhrp-ecs-task-execution-role", + "arn:aws:iam::*:role/uhrp-ecs-task-role" + ] + }, + { + "Effect": "Allow", + "Action": [ + "lambda:UpdateFunctionCode", + "lambda:UpdateFunctionConfiguration" + ], + "Resource": "*" + } + ] +} +EOF + +# Create IAM user for GitHub Actions +aws iam create-user --user-name github-actions-uhrp + +# Create and attach policy +aws iam create-policy \ + --policy-name github-actions-uhrp-policy \ + --policy-document file://github-actions-policy.json + +aws iam attach-user-policy \ + --user-name github-actions-uhrp \ + --policy-arn arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):policy/github-actions-uhrp-policy + +# Create access key +aws iam create-access-key --user-name github-actions-uhrp +# Save the AccessKeyId and SecretAccessKey for GitHub secrets +``` + +## Phase 3: ECS Setup + +### 3.1 Create ECS Cluster + +```bash +aws ecs create-cluster --cluster-name uhrp-cluster +``` + +### 3.2 Create CloudWatch Log Group + +```bash +aws logs create-log-group --log-group-name /ecs/uhrp-storage-server +``` + +### 3.3 Create Task Definition + +```bash +cat > task-definition.json << EOF +{ + "family": "uhrp-storage-server", + "networkMode": "awsvpc", + "requiresCompatibilities": ["FARGATE"], + "cpu": "1024", + "memory": "2048", + "taskRoleArn": "arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):role/uhrp-ecs-task-role", + "executionRoleArn": "arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):role/uhrp-ecs-task-execution-role", + "containerDefinitions": [ + { + "name": "uhrp-storage", + "image": "${ECR_URI}:latest", + "portMappings": [ + { + "containerPort": 8080, + "protocol": "tcp" + } + ], + "essential": true, + "environment": [ + {"name": "NODE_ENV", "value": "production"}, + {"name": "AWS_BUCKET_NAME", "value": "${BUCKET_NAME}"}, + {"name": "AWS_REGION", "value": "${AWS_REGION}"}, + {"name": "HTTP_PORT", "value": "3104"}, + {"name": "CORS_ORIGIN", "value": "*"}, + {"name": "SERVER_URL", "value": "https://your-domain.com"}, + {"name": "PER_BYTE_PRICE", "value": "0.00001"}, + {"name": "BASE_PRICE", "value": "1000"}, + {"name": "SERVER_PRIVATE_KEY", "value": "YOUR_PRIVATE_KEY"}, + {"name": "BSV_NETWORK", "value": "mainnet"}, + {"name": "BUGSNAG_API_KEY", "value": "YOUR_BUGSNAG_KEY"} + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "/ecs/uhrp-storage-server", + "awslogs-region": "${AWS_REGION}", + "awslogs-stream-prefix": "ecs" + } + }, + "healthCheck": { + "command": ["CMD-SHELL", "curl -f http://localhost:8080/ || exit 1"], + "interval": 30, + "timeout": 5, + "retries": 3, + "startPeriod": 60 + } + } + ] +} +EOF + +# Register task definition +aws ecs register-task-definition --cli-input-json file://task-definition.json +``` + +### 3.4 Create Security Group + +```bash +# Create security group for ECS tasks +aws ec2 create-security-group \ + --group-name uhrp-ecs-sg \ + --description "Security group for UHRP ECS tasks" \ + --vpc-id $VPC_ID \ + --query 'GroupId' --output text +export SG_ID= + +# Allow inbound HTTP traffic from ALB +aws ec2 authorize-security-group-ingress \ + --group-id $SG_ID \ + --protocol tcp \ + --port 8080 \ + --source-group $ALB_SG_ID # Create ALB security group first + +# Allow all outbound traffic +aws ec2 authorize-security-group-egress \ + --group-id $SG_ID \ + --protocol -1 \ + --cidr 0.0.0.0/0 +``` + +## Phase 4: Load Balancer Setup + +### 4.1 Create ALB Security Group + +```bash +# Create security group for ALB +aws ec2 create-security-group \ + --group-name uhrp-alb-sg \ + --description "Security group for UHRP ALB" \ + --vpc-id $VPC_ID \ + --query 'GroupId' --output text +export ALB_SG_ID= + +# Allow HTTPS inbound +aws ec2 authorize-security-group-ingress \ + --group-id $ALB_SG_ID \ + --protocol tcp \ + --port 443 \ + --cidr 0.0.0.0/0 + +# Allow HTTP inbound (for redirect) +aws ec2 authorize-security-group-ingress \ + --group-id $ALB_SG_ID \ + --protocol tcp \ + --port 80 \ + --cidr 0.0.0.0/0 +``` + +### 4.2 Request SSL Certificate + +```bash +# Request certificate from ACM +aws acm request-certificate \ + --domain-name your-domain.com \ + --validation-method DNS \ + --region $AWS_REGION +# Follow the DNS validation process +``` + +### 4.3 Create Application Load Balancer + +```bash +# Create ALB +aws elbv2 create-load-balancer \ + --name uhrp-alb \ + --subnets $SUBNET_1 $SUBNET_2 \ + --security-groups $ALB_SG_ID \ + --scheme internet-facing \ + --type application \ + --ip-address-type ipv4 +export ALB_ARN= + +# Create target group +aws elbv2 create-target-group \ + --name uhrp-targets \ + --protocol HTTP \ + --port 8080 \ + --vpc-id $VPC_ID \ + --target-type ip \ + --health-check-path / \ + --health-check-interval-seconds 30 \ + --health-check-timeout-seconds 5 \ + --healthy-threshold-count 2 \ + --unhealthy-threshold-count 3 +export TG_ARN= + +# Create HTTPS listener +aws elbv2 create-listener \ + --load-balancer-arn $ALB_ARN \ + --protocol HTTPS \ + --port 443 \ + --certificates CertificateArn= \ + --default-actions Type=forward,TargetGroupArn=$TG_ARN + +# Create HTTP to HTTPS redirect +aws elbv2 create-listener \ + --load-balancer-arn $ALB_ARN \ + --protocol HTTP \ + --port 80 \ + --default-actions Type=redirect,RedirectConfig='{Protocol=HTTPS,Port=443,StatusCode=HTTP_301}' +``` + +### 4.4 Create ECS Service + +```bash +# Create service +aws ecs create-service \ + --cluster uhrp-cluster \ + --service-name uhrp-storage-service \ + --task-definition uhrp-storage-server:1 \ + --desired-count 2 \ + --launch-type FARGATE \ + --network-configuration "awsvpcConfiguration={subnets=[$SUBNET_1,$SUBNET_2],securityGroups=[$SG_ID],assignPublicIp=ENABLED}" \ + --load-balancers targetGroupArn=$TG_ARN,containerName=uhrp-storage,containerPort=8080 +``` + +## Phase 5: Lambda Function Setup + +### 5.1 Package Lambda Function + +```bash +cd notifier +npm install +zip -r ../notifier.zip . +cd .. +``` + +### 5.2 Create Lambda Function + +```bash +# Create the function +aws lambda create-function \ + --function-name uhrp-notifier \ + --runtime nodejs18.x \ + --role arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):role/uhrp-lambda-execution-role \ + --handler index.notifier \ + --zip-file fileb://notifier.zip \ + --timeout 540 \ + --memory-size 4096 \ + --environment Variables='{ + NODE_ENV=production, + SERVER_PRIVATE_KEY=YOUR_PRIVATE_KEY, + BSV_NETWORK=mainnet + }' + +# Add S3 trigger permission +aws lambda add-permission \ + --function-name uhrp-notifier \ + --statement-id s3-trigger \ + --action lambda:InvokeFunction \ + --principal s3.amazonaws.com \ + --source-arn arn:aws:s3:::${BUCKET_NAME} +``` + +### 5.3 Configure S3 Event Notification + +```bash +# Create notification configuration +cat > notification.json << EOF +{ + "LambdaFunctionConfigurations": [ + { + "LambdaFunctionArn": "arn:aws:lambda:${AWS_REGION}:$(aws sts get-caller-identity --query Account --output text):function:uhrp-notifier", + "Events": ["s3:ObjectCreated:*"] + } + ] +} +EOF + +# Apply to bucket +aws s3api put-bucket-notification-configuration \ + --bucket $BUCKET_NAME \ + --notification-configuration file://notification.json +``` + +## Phase 6: GitHub Actions Configuration + +### 6.1 Add GitHub Secrets + +Add these secrets to your GitHub repository (Settings > Secrets and variables > Actions): + +- `AWS_ACCESS_KEY_ID` - From IAM user creation +- `AWS_SECRET_ACCESS_KEY` - From IAM user creation +- `AWS_REGION` - Your chosen region +- `ECR_REPOSITORY` - uhrp-storage-server +- `ECS_CLUSTER` - uhrp-cluster +- `ECS_SERVICE` - uhrp-storage-service +- `TASK_DEFINITION_FAMILY` - uhrp-storage-server + +### 6.2 Create GitHub Actions Workflow + +Create `.github/workflows/deploy-aws.yaml`: + +```yaml +name: Deploy to AWS +on: + push: + branches: [master, production] + +env: + AWS_REGION: ${{ secrets.AWS_REGION }} + ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY }} + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + environment: ${{ github.ref_name }} + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + id: build-image + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + IMAGE_TAG: ${{ github.sha }} + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT + + - name: Download task definition + run: | + aws ecs describe-task-definition \ + --task-definition ${{ secrets.TASK_DEFINITION_FAMILY }} \ + --query taskDefinition > task-definition.json + + - name: Fill in the new image ID in the Amazon ECS task definition + id: task-def + uses: aws-actions/amazon-ecs-render-task-definition@v1 + with: + task-definition: task-definition.json + container-name: uhrp-storage + image: ${{ steps.build-image.outputs.image }} + + - name: Deploy Amazon ECS task definition + uses: aws-actions/amazon-ecs-deploy-task-definition@v1 + with: + task-definition: ${{ steps.task-def.outputs.task-definition }} + service: ${{ secrets.ECS_SERVICE }} + cluster: ${{ secrets.ECS_CLUSTER }} + wait-for-service-stability: true + + - name: Update Lambda function + run: | + cd notifier + npm install --production + zip -r ../notifier.zip . + cd .. + aws lambda update-function-code \ + --function-name uhrp-notifier \ + --zip-file fileb://notifier.zip +``` + +## Phase 7: Code Modifications Required + +### 7.1 Environment Variables + +Update your application to use AWS-specific environment variables: + +```javascript +// Old (GCP) +const bucket = storage.bucket(process.env.GCS_BUCKET_NAME); + +// New (AWS) +const bucketName = process.env.AWS_BUCKET_NAME; +``` + +### 7.2 Storage SDK + +Replace Google Cloud Storage with AWS S3: + +```javascript +// Install AWS SDK +// npm install @aws-sdk/client-s3 + +// Old (GCP) +const { Storage } = require('@google-cloud/storage'); +const storage = new Storage(); + +// New (AWS) +const { S3Client, PutObjectCommand, GetObjectCommand } = require('@aws-sdk/client-s3'); +const s3Client = new S3Client({ region: process.env.AWS_REGION }); +``` + +### 7.3 Update File Operations + +Example file upload modification: + +```javascript +// Old (GCP) +async function uploadFile(file) { + const bucket = storage.bucket(process.env.GCS_BUCKET_NAME); + const blob = bucket.file(fileName); + await blob.save(fileBuffer); +} + +// New (AWS) +async function uploadFile(file) { + const command = new PutObjectCommand({ + Bucket: process.env.AWS_BUCKET_NAME, + Key: fileName, + Body: fileBuffer, + ContentType: file.mimetype + }); + await s3Client.send(command); +} +``` + +### 7.4 Lambda Handler + +Modify the notifier for Lambda: + +```javascript +// Old (GCP Cloud Function) +exports.notifier = async (file, context) => { + // Process file +}; + +// New (AWS Lambda) +exports.notifier = async (event, context) => { + // S3 event structure is different + const bucket = event.Records[0].s3.bucket.name; + const key = decodeURIComponent(event.Records[0].s3.object.key); + // Process file +}; +``` + +## Phase 8: Testing and Validation + +### 8.1 Test S3 Operations + +```bash +# Upload test file +aws s3 cp test.txt s3://$BUCKET_NAME/test.txt + +# Verify Lambda triggered +aws logs tail /aws/lambda/uhrp-notifier --follow + +# Download test file +aws s3 cp s3://$BUCKET_NAME/test.txt downloaded.txt +``` + +### 8.2 Test ECS Service + +```bash +# Check service status +aws ecs describe-services \ + --cluster uhrp-cluster \ + --services uhrp-storage-service + +# View logs +aws logs tail /ecs/uhrp-storage-server --follow + +# Test endpoint +curl https://your-domain.com/ +``` + +### 8.3 Load Testing + +```bash +# Install artillery +npm install -g artillery + +# Create test script +cat > load-test.yml << EOF +config: + target: "https://your-domain.com" + phases: + - duration: 60 + arrivalRate: 10 +scenarios: + - name: "Upload Test" + flow: + - post: + url: "/upload" + headers: + Authorization: "Bearer YOUR_TOKEN" + formData: + file: "./test-file.pdf" +EOF + +# Run load test +artillery run load-test.yml +``` + +## Phase 9: Monitoring and Maintenance + +### 9.1 CloudWatch Alarms + +```bash +# Create CPU alarm +aws cloudwatch put-metric-alarm \ + --alarm-name uhrp-high-cpu \ + --alarm-description "Alert when CPU exceeds 80%" \ + --metric-name CPUUtilization \ + --namespace AWS/ECS \ + --statistic Average \ + --period 300 \ + --threshold 80 \ + --comparison-operator GreaterThanThreshold \ + --evaluation-periods 2 + +# Create error rate alarm +aws cloudwatch put-metric-alarm \ + --alarm-name uhrp-high-error-rate \ + --alarm-description "Alert when 5xx errors exceed 10%" \ + --metric-name HTTPCode_Target_5XX_Count \ + --namespace AWS/ApplicationELB \ + --statistic Sum \ + --period 300 \ + --threshold 10 \ + --comparison-operator GreaterThanThreshold \ + --evaluation-periods 1 +``` + +### 9.2 Auto Scaling + +```bash +# Register scalable target +aws application-autoscaling register-scalable-target \ + --service-namespace ecs \ + --resource-id service/uhrp-cluster/uhrp-storage-service \ + --scalable-dimension ecs:service:DesiredCount \ + --min-capacity 2 \ + --max-capacity 10 + +# Create scaling policy +aws application-autoscaling put-scaling-policy \ + --policy-name uhrp-cpu-scaling \ + --service-namespace ecs \ + --resource-id service/uhrp-cluster/uhrp-storage-service \ + --scalable-dimension ecs:service:DesiredCount \ + --policy-type TargetTrackingScaling \ + --target-tracking-scaling-policy-configuration '{ + "TargetValue": 70.0, + "PredefinedMetricSpecification": { + "PredefinedMetricType": "ECSServiceAverageCPUUtilization" + } + }' +``` + +## Troubleshooting + +### Common Issues + +1. **ECS tasks failing to start** + - Check CloudWatch logs + - Verify IAM roles have correct permissions + - Ensure security groups allow traffic + +2. **Lambda not triggering** + - Verify S3 event configuration + - Check Lambda permissions + - Review CloudWatch logs + +3. **ALB health checks failing** + - Verify container health check + - Check security group rules + - Ensure application responds on health check path + +4. **S3 access denied** + - Review IAM policies + - Check bucket policies + - Verify CORS configuration + +### Useful Commands + +```bash +# View ECS task logs +aws logs get-log-events \ + --log-group-name /ecs/uhrp-storage-server \ + --log-stream-name ecs/uhrp-storage/ + +# Force new deployment +aws ecs update-service \ + --cluster uhrp-cluster \ + --service uhrp-storage-service \ + --force-new-deployment + +# Check ALB target health +aws elbv2 describe-target-health \ + --target-group-arn $TG_ARN +``` + +## Cost Optimization + +1. **Use S3 Intelligent-Tiering** for automatic storage class transitions +2. **Enable ECS Fargate Spot** for non-critical workloads +3. **Set up S3 lifecycle policies** to delete old files +4. **Use CloudFront** for static content delivery +5. **Right-size ECS tasks** based on actual usage + +## Security Best Practices + +1. **Use AWS Secrets Manager** for sensitive environment variables +2. **Enable VPC endpoints** for S3 access +3. **Implement AWS WAF** on the ALB +4. **Use private subnets** for ECS tasks +5. **Enable S3 bucket encryption** +6. **Set up AWS GuardDuty** for threat detection +7. **Use least-privilege IAM policies** + +## Next Steps + +1. Set up CloudFront distribution for CDN +2. Implement AWS Backup for S3 data +3. Configure AWS X-Ray for distributed tracing +4. Set up AWS Cost Explorer budgets +5. Implement multi-region deployment for high availability + +--- + +© 2025 - UHRP Storage Server AWS Deployment Guide \ No newline at end of file diff --git a/README-EKS.md b/README-EKS.md new file mode 100644 index 0000000..1e1551e --- /dev/null +++ b/README-EKS.md @@ -0,0 +1,513 @@ +# UHRP Storage Server – EKS Deployment Guide + +This guide walks you through deploying the **UHRP Storage Server** on Amazon Elastic Kubernetes Service (EKS). This approach consolidates all components into a single Kubernetes cluster, eliminating the need for separate ECS and Lambda services. + +## Architecture Overview + +In this EKS deployment: +- **Storage Server** runs as a Kubernetes Deployment +- **S3 Event Handler** replaces Lambda, running as a pod in the cluster +- **AWS ALB Ingress Controller** handles HTTPS termination +- **S3 Bucket** remains the only external AWS service +- **Everything else** runs inside Kubernetes + +## Benefits Over ECS/Lambda + +1. **Unified Platform** - All components in one cluster +2. **Simplified Deployment** - Single kubectl/helm command +3. **Better Resource Utilization** - Pod packing and node sharing +4. **Native Scaling** - Kubernetes HPA instead of AWS-specific scaling +5. **Portable** - Can run on any Kubernetes cluster (not just EKS) + +## Prerequisites + +- AWS Account with appropriate permissions +- `kubectl` CLI installed +- `eksctl` CLI installed +- `helm` v3 installed +- AWS CLI configured +- Docker installed (for local builds) + +## Phase 1: EKS Cluster Setup + +### 1.1 Create EKS Cluster + +```bash +# Set your configuration +export AWS_REGION=us-west-2 +export CLUSTER_NAME=uhrp-cluster +export NODE_GROUP_NAME=uhrp-nodes + +# Create cluster with eksctl +eksctl create cluster \ + --name $CLUSTER_NAME \ + --region $AWS_REGION \ + --nodegroup-name $NODE_GROUP_NAME \ + --node-type t3.medium \ + --nodes 3 \ + --nodes-min 2 \ + --nodes-max 5 \ + --managed \ + --with-oidc \ + --ssh-access \ + --ssh-public-key ~/.ssh/id_rsa.pub + +# Verify cluster is ready +kubectl get nodes +``` + +### 1.2 Install AWS Load Balancer Controller + +The AWS Load Balancer Controller manages ALBs for Kubernetes Ingress resources. + +```bash +# Download IAM policy +curl -o iam_policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.6.2/docs/install/iam_policy.json + +# Create IAM policy +aws iam create-policy \ + --policy-name AWSLoadBalancerControllerIAMPolicy \ + --policy-document file://iam_policy.json + +# Create service account +eksctl create iamserviceaccount \ + --cluster=$CLUSTER_NAME \ + --namespace=kube-system \ + --name=aws-load-balancer-controller \ + --attach-policy-arn=arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):policy/AWSLoadBalancerControllerIAMPolicy \ + --override-existing-serviceaccounts \ + --approve + +# Install using Helm +helm repo add eks https://aws.github.io/eks-charts +helm repo update + +helm install aws-load-balancer-controller eks/aws-load-balancer-controller \ + -n kube-system \ + --set clusterName=$CLUSTER_NAME \ + --set serviceAccount.create=false \ + --set serviceAccount.name=aws-load-balancer-controller +``` + +### 1.3 Install EBS CSI Driver (for persistent volumes) + +```bash +# Create IAM role for EBS CSI driver +eksctl create iamserviceaccount \ + --cluster=$CLUSTER_NAME \ + --namespace=kube-system \ + --name=ebs-csi-controller-sa \ + --attach-policy-arn=arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy \ + --override-existing-serviceaccounts \ + --approve + +# Install EBS CSI driver +kubectl apply -k "github.com/kubernetes-sigs/aws-ebs-csi-driver/deploy/kubernetes/overlays/stable/ecr/?ref=release-1.24" +``` + +## Phase 2: S3 Setup and Event Configuration + +### 2.1 Create S3 Bucket + +```bash +export BUCKET_NAME=uhrp-storage-$(date +%s) + +# Create bucket +aws s3api create-bucket \ + --bucket $BUCKET_NAME \ + --region $AWS_REGION \ + --create-bucket-configuration LocationConstraint=$AWS_REGION + +# Apply CORS configuration +aws s3api put-bucket-cors \ + --bucket $BUCKET_NAME \ + --cors-configuration file://aws/s3-cors.json +``` + +### 2.2 Create SQS Queue for S3 Events + +Since Lambda is being replaced, we'll use SQS to queue S3 events for processing by our Kubernetes pods. + +```bash +# Create SQS queue +aws sqs create-queue \ + --queue-name uhrp-s3-events \ + --region $AWS_REGION + +# Get queue URL and ARN +QUEUE_URL=$(aws sqs get-queue-url --queue-name uhrp-s3-events --query QueueUrl --output text --region $AWS_REGION) +QUEUE_ARN=$(aws sqs get-queue-attributes --queue-url $QUEUE_URL --attribute-names QueueArn --query Attributes.QueueArn --output text --region $AWS_REGION) + +# Create SQS policy to allow S3 to send messages +cat > sqs-policy.json << EOF +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "s3.amazonaws.com" + }, + "Action": "sqs:SendMessage", + "Resource": "${QUEUE_ARN}", + "Condition": { + "ArnLike": { + "aws:SourceArn": "arn:aws:s3:::${BUCKET_NAME}" + } + } + } + ] +} +EOF + +# Set the SQS queue policy +aws sqs set-queue-attributes \ + --queue-url $QUEUE_URL \ + --region $AWS_REGION \ + --attributes Policy="$(cat sqs-policy.json | jq -c . | sed 's/"/\\"/g')" + +# Create S3 event notification configuration +cat > s3-notification.json << EOF +{ + "QueueConfigurations": [ + { + "QueueArn": "${QUEUE_ARN}", + "Events": ["s3:ObjectCreated:*"], + "Filter": { + "Key": { + "FilterRules": [ + { + "Name": "prefix", + "Value": "cdn/" + } + ] + } + } + } + ] +} +EOF + +# Configure S3 to send events to SQS +aws s3api put-bucket-notification-configuration \ + --bucket $BUCKET_NAME \ + --notification-configuration file://s3-notification.json +``` + +## Phase 3: IAM Roles for Service Accounts (IRSA) + +### 3.1 Create IAM Policy for Storage Server + +```bash +cat > storage-server-policy.json << EOF +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject", + "s3:ListBucket", + "s3:GetObjectMetadata", + "s3:PutObjectMetadata" + ], + "Resource": [ + "arn:aws:s3:::${BUCKET_NAME}", + "arn:aws:s3:::${BUCKET_NAME}/*" + ] + } + ] +} +EOF + +aws iam create-policy \ + --policy-name uhrp-storage-server-policy \ + --policy-document file://storage-server-policy.json +``` + +### 3.2 Create IAM Policy for S3 Event Handler + +```bash +cat > event-handler-policy.json << EOF +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "sqs:ReceiveMessage", + "sqs:DeleteMessage", + "sqs:GetQueueAttributes" + ], + "Resource": "${QUEUE_ARN}" + }, + { + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:GetObjectMetadata" + ], + "Resource": "arn:aws:s3:::${BUCKET_NAME}/*" + } + ] +} +EOF + +aws iam create-policy \ + --policy-name uhrp-event-handler-policy \ + --policy-document file://event-handler-policy.json +``` + +### 3.3 Create Kubernetes Service Accounts with IRSA + +```bash +# Create namespace +kubectl create namespace uhrp-storage + +# Create service account for storage server +eksctl create iamserviceaccount \ + --cluster=$CLUSTER_NAME \ + --namespace=uhrp-storage \ + --name=storage-server \ + --attach-policy-arn=arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):policy/uhrp-storage-server-policy \ + --override-existing-serviceaccounts \ + --approve + +# Create service account for event handler +eksctl create iamserviceaccount \ + --cluster=$CLUSTER_NAME \ + --namespace=uhrp-storage \ + --name=event-handler \ + --attach-policy-arn=arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):policy/uhrp-event-handler-policy \ + --override-existing-serviceaccounts \ + --approve +``` + +## Phase 4: Deploy Application + +### 4.1 Create ConfigMap + +```bash +kubectl apply -f k8s/configmap.yaml +``` + +### 4.2 Create Secrets + +```bash +# Create secret from environment variables +kubectl create secret generic uhrp-secrets \ + --namespace=uhrp-storage \ + --from-literal=server-private-key=$SERVER_PRIVATE_KEY \ + --from-literal=admin-token=$ADMIN_TOKEN \ + --from-literal=bugsnag-api-key=$BUGSNAG_API_KEY +``` + +### 4.3 Deploy Storage Server + +```bash +# Apply all manifests +kubectl apply -f k8s/deployment.yaml +kubectl apply -f k8s/service.yaml +kubectl apply -f k8s/ingress.yaml +kubectl apply -f k8s/hpa.yaml +``` + +### 4.4 Deploy S3 Event Handler + +```bash +kubectl apply -f k8s/s3-event-handler/deployment.yaml +``` + +### 4.5 Verify Deployment + +```bash +# Check pods are running +kubectl get pods -n uhrp-storage + +# Check services +kubectl get svc -n uhrp-storage + +# Check ingress (wait for ADDRESS to be populated) +kubectl get ingress -n uhrp-storage + +# Get logs +kubectl logs -n uhrp-storage -l app=storage-server --tail=100 +``` + +## Phase 5: DNS Configuration + +Once the Ingress has an ADDRESS: + +```bash +# Get the ALB DNS name +ALB_DNS=$(kubectl get ingress -n uhrp-storage storage-server-ingress -o jsonpath='{.status.loadBalancer.ingress[0].hostname}') + +echo "Create a CNAME record pointing your domain to: $ALB_DNS" +``` + +## Phase 6: Monitoring and Observability + +### 6.1 Install Prometheus and Grafana + +```bash +# Add Prometheus Helm repo +helm repo add prometheus-community https://prometheus-community.github.io/helm-charts +helm repo update + +# Install kube-prometheus-stack +helm install monitoring prometheus-community/kube-prometheus-stack \ + --namespace monitoring \ + --create-namespace \ + --set prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false \ + --set grafana.adminPassword=admin +``` + +### 6.2 Install Fluentd for Log Aggregation + +```bash +kubectl apply -f k8s/monitoring/fluentd-daemonset.yaml +``` + +### 6.3 Access Grafana + +```bash +# Port forward to access Grafana +kubectl port-forward -n monitoring svc/monitoring-grafana 3000:80 + +# Access at http://localhost:3000 +# Username: admin +# Password: admin +``` + +## Phase 7: CI/CD with GitHub Actions + +Configure GitHub secrets: +- `AWS_REGION` +- `EKS_CLUSTER_NAME` +- `ECR_REPOSITORY` +- `AWS_ACCESS_KEY_ID` +- `AWS_SECRET_ACCESS_KEY` + +Then push to trigger deployment: + +```bash +git add . +git commit -m "Deploy to EKS" +git push origin master +``` + +## Operational Commands + +### Scaling + +```bash +# Manual scaling +kubectl scale deployment storage-server -n uhrp-storage --replicas=5 + +# Check HPA status +kubectl get hpa -n uhrp-storage +``` + +### Updates + +```bash +# Update deployment +kubectl set image deployment/storage-server storage-server=your-ecr-url:new-tag -n uhrp-storage + +# Rolling restart +kubectl rollout restart deployment/storage-server -n uhrp-storage + +# Check rollout status +kubectl rollout status deployment/storage-server -n uhrp-storage +``` + +### Debugging + +```bash +# Get pod logs +kubectl logs -n uhrp-storage -l app=storage-server -f + +# Exec into pod +kubectl exec -it -n uhrp-storage deployment/storage-server -- /bin/sh + +# Describe pod for events +kubectl describe pod -n uhrp-storage + +# Get events +kubectl get events -n uhrp-storage --sort-by='.lastTimestamp' +``` + +## Cost Optimization + +1. **Use Spot Instances** for worker nodes: +```bash +eksctl create nodegroup \ + --cluster=$CLUSTER_NAME \ + --name=spot-nodes \ + --spot \ + --instance-types=t3.medium,t3a.medium \ + --nodes-min=2 \ + --nodes-max=10 +``` + +2. **Enable Cluster Autoscaler**: +```bash +kubectl apply -f k8s/cluster-autoscaler.yaml +``` + +3. **Use Karpenter** for more efficient node provisioning +4. **Set resource requests/limits** appropriately +5. **Use S3 Lifecycle policies** for old files + +## Security Best Practices + +1. **Network Policies** - Restrict pod-to-pod communication +2. **Pod Security Standards** - Enforce security policies +3. **Secrets Management** - Use AWS Secrets Manager with Secrets Store CSI Driver +4. **Image Scanning** - Enable ECR image scanning +5. **RBAC** - Implement least-privilege access +6. **Audit Logging** - Enable EKS audit logs + +## Troubleshooting + +### Common Issues + +1. **Ingress not getting ADDRESS** + - Check AWS Load Balancer Controller logs + - Verify IAM permissions + +2. **Pods can't access S3** + - Check IRSA configuration + - Verify service account annotations + +3. **S3 events not processing** + - Check SQS queue for messages + - Verify event handler logs + +4. **High memory usage** + - Adjust resource limits + - Check for memory leaks + +## Migration from ECS/Lambda + +To migrate existing data: + +1. Stop ECS services +2. Deploy to EKS +3. Update DNS to point to new ALB +4. Monitor for issues +5. Decommission old infrastructure + +## Next Steps + +1. Implement GitOps with ArgoCD +2. Add service mesh (Istio/Linkerd) +3. Implement progressive delivery +4. Add chaos engineering tests +5. Implement multi-region deployment + +--- + +© 2025 - UHRP Storage Server EKS Deployment Guide diff --git a/aws/s3-cors.json b/aws/s3-cors.json new file mode 100644 index 0000000..601a321 --- /dev/null +++ b/aws/s3-cors.json @@ -0,0 +1,26 @@ +{ + "CORSRules": [ + { + "AllowedHeaders": [ + "*" + ], + "AllowedMethods": [ + "GET", + "PUT", + "POST", + "DELETE", + "HEAD" + ], + "AllowedOrigins": [ + "*" + ], + "ExposeHeaders": [ + "ETag", + "x-amz-server-side-encryption", + "x-amz-request-id", + "x-amz-id-2" + ], + "MaxAgeSeconds": 3000 + } + ] +} \ No newline at end of file diff --git a/aws/task-definition.json b/aws/task-definition.json new file mode 100644 index 0000000..2e69445 --- /dev/null +++ b/aws/task-definition.json @@ -0,0 +1,97 @@ +{ + "family": "uhrp-storage-server", + "networkMode": "awsvpc", + "requiresCompatibilities": ["FARGATE"], + "cpu": "1024", + "memory": "2048", + "taskRoleArn": "arn:aws:iam::ACCOUNT_ID:role/uhrp-ecs-task-role", + "executionRoleArn": "arn:aws:iam::ACCOUNT_ID:role/uhrp-ecs-task-execution-role", + "containerDefinitions": [ + { + "name": "uhrp-storage", + "image": "ACCOUNT_ID.dkr.ecr.REGION.amazonaws.com/uhrp-storage-server:latest", + "portMappings": [ + { + "containerPort": 8080, + "protocol": "tcp" + } + ], + "essential": true, + "environment": [ + { + "name": "NODE_ENV", + "value": "production" + }, + { + "name": "AWS_BUCKET_NAME", + "value": "YOUR_BUCKET_NAME" + }, + { + "name": "AWS_REGION", + "value": "us-west-2" + }, + { + "name": "HTTP_PORT", + "value": "3104" + }, + { + "name": "CORS_ORIGIN", + "value": "*" + }, + { + "name": "SERVER_URL", + "value": "https://your-domain.com" + }, + { + "name": "PER_BYTE_PRICE", + "value": "0.00001" + }, + { + "name": "BASE_PRICE", + "value": "1000" + }, + { + "name": "BSV_NETWORK", + "value": "mainnet" + }, + { + "name": "MIN_HOSTING_MINUTES", + "value": "15" + }, + { + "name": "WALLET_STORAGE_URL", + "value": "https://storage.babbage.systems" + } + ], + "secrets": [ + { + "name": "SERVER_PRIVATE_KEY", + "valueFrom": "arn:aws:secretsmanager:REGION:ACCOUNT_ID:secret:uhrp/server-private-key:key::" + }, + { + "name": "BUGSNAG_API_KEY", + "valueFrom": "arn:aws:secretsmanager:REGION:ACCOUNT_ID:secret:uhrp/bugsnag-api-key:key::" + }, + { + "name": "ADMIN_TOKEN", + "valueFrom": "arn:aws:secretsmanager:REGION:ACCOUNT_ID:secret:uhrp/admin-token:key::" + } + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "/ecs/uhrp-storage-server", + "awslogs-region": "us-west-2", + "awslogs-stream-prefix": "ecs" + } + }, + "healthCheck": { + "command": ["CMD-SHELL", "curl -f http://localhost:8080/ || exit 1"], + "interval": 30, + "timeout": 5, + "retries": 3, + "startPeriod": 60 + } + } + ] +} \ No newline at end of file diff --git a/deploy/uhrp-server-deployment.yaml b/deploy/uhrp-server-deployment.yaml new file mode 100644 index 0000000..4d94589 --- /dev/null +++ b/deploy/uhrp-server-deployment.yaml @@ -0,0 +1,59 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: uhrp-server + name: uhrp-server +spec: + replicas: 1 + selector: + matchLabels: + app: uhrp-server + template: + metadata: + labels: + app: uhrp-server + spec: + containers: + - args: + - npm + - run + - dev + env: + - name: ADMIN_TOKEN + value: admin-token + - name: GCP_BUCKET_NAME + value: staging-uhrp + - name: GCP_PROJECT_ID + value: babbage-uhrp + - name: GOOGLE_APPLICATION_CREDENTIALS + value: /app/service-account.json + - name: HOSTING_DOMAIN + value: http://localhost:8080 + - name: HTTP_PORT + value: "8080" + - name: MIGRATE_KEY + value: my-great-key + - name: MIN_HOSTING_MINUTES + value: "15" + - name: NODE_ENV + value: development + - name: PRICE_PER_GB_MO + value: "0.03" + - name: SERVER_PRIVATE_KEY + value: 53a6156d87fd51d00095e82b59d20354b31786206462dbc8bbf5aeaa3a2a9309 + - name: SERVER_XPRIV + value: xprv9s21ZrQH143K3GN3VANh6bt72CgqhysD35WrbZWq7zJMg9Jegv7qPhgCy1To8PHW8fJoa4EDLCx73FK8Ljfg64mW9XK9g13xkMwZKQPWZ9Y + - name: UHRP_HOST_PRIVATE_KEY + value: 5KKGHf1dZpJ1L4aQTGCrVL4q3y5iwbcf9YcWS3oXynrWjaxS2LD + - name: WALLET_STORAGE_URL + value: https://staging-storage.babbage.systems + image: uhrp-server + name: uhrp-server + ports: + - containerPort: 8080 + protocol: TCP + - containerPort: 9229 + protocol: TCP + hostname: uhrp-server + restartPolicy: Always diff --git a/deploy/uhrp-server-service.yaml b/deploy/uhrp-server-service.yaml new file mode 100644 index 0000000..78d7fd4 --- /dev/null +++ b/deploy/uhrp-server-service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: uhrp-server + name: uhrp-server +spec: + ports: + - name: "8080" + port: 8080 + targetPort: 8080 + - name: "9229" + port: 9229 + targetPort: 9229 + selector: + app: uhrp-server diff --git a/k8s/cluster-autoscaler.yaml b/k8s/cluster-autoscaler.yaml new file mode 100644 index 0000000..4e3bce9 --- /dev/null +++ b/k8s/cluster-autoscaler.yaml @@ -0,0 +1,143 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cluster-autoscaler + namespace: kube-system + labels: + app: cluster-autoscaler +spec: + selector: + matchLabels: + app: cluster-autoscaler + template: + metadata: + labels: + app: cluster-autoscaler + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "8085" + spec: + priorityClassName: system-cluster-critical + securityContext: + runAsNonRoot: true + runAsUser: 65534 + fsGroup: 65534 + serviceAccountName: cluster-autoscaler + containers: + - image: k8s.gcr.io/autoscaling/cluster-autoscaler:v1.28.0 + name: cluster-autoscaler + resources: + limits: + cpu: 100m + memory: 300Mi + requests: + cpu: 100m + memory: 300Mi + command: + - ./cluster-autoscaler + - --v=4 + - --stderrthreshold=info + - --cloud-provider=aws + - --skip-nodes-with-local-storage=false + - --expander=least-waste + - --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/CLUSTER_NAME + - --balance-similar-node-groups + - --skip-nodes-with-system-pods=false + volumeMounts: + - name: ssl-certs + mountPath: /etc/ssl/certs/ca-certificates.crt + readOnly: true + imagePullPolicy: "Always" + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + volumes: + - name: ssl-certs + hostPath: + path: "/etc/ssl/certs/ca-bundle.crt" +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: cluster-autoscaler + namespace: kube-system + labels: + k8s-addon: cluster-autoscaler.addons.k8s.io + k8s-app: cluster-autoscaler + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT_ID:role/cluster-autoscaler +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cluster-autoscaler + labels: + k8s-addon: cluster-autoscaler.addons.k8s.io + k8s-app: cluster-autoscaler +rules: + - apiGroups: [""] + resources: ["events", "endpoints"] + verbs: ["create", "patch"] + - apiGroups: [""] + resources: ["pods/eviction"] + verbs: ["create"] + - apiGroups: [""] + resources: ["pods/status"] + verbs: ["update"] + - apiGroups: [""] + resources: ["endpoints"] + resourceNames: ["cluster-autoscaler"] + verbs: ["get", "update"] + - apiGroups: [""] + resources: ["nodes"] + verbs: ["watch", "list", "get", "update"] + - apiGroups: [""] + resources: + - "namespaces" + - "pods" + - "services" + - "replicationcontrollers" + - "persistentvolumeclaims" + - "persistentvolumes" + verbs: ["watch", "list", "get"] + - apiGroups: ["extensions"] + resources: ["replicasets", "daemonsets"] + verbs: ["watch", "list", "get"] + - apiGroups: ["policy"] + resources: ["poddisruptionbudgets"] + verbs: ["watch", "list"] + - apiGroups: ["apps"] + resources: ["statefulsets", "replicasets", "daemonsets"] + verbs: ["watch", "list", "get"] + - apiGroups: ["storage.k8s.io"] + resources: ["storageclasses", "csinodes", "csidrivers", "csistoragecapacities"] + verbs: ["watch", "list", "get"] + - apiGroups: ["batch", "extensions"] + resources: ["jobs"] + verbs: ["get", "list", "watch", "patch"] + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["create"] + - apiGroups: ["coordination.k8s.io"] + resourceNames: ["cluster-autoscaler"] + resources: ["leases"] + verbs: ["get", "update"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cluster-autoscaler + labels: + k8s-addon: cluster-autoscaler.addons.k8s.io + k8s-app: cluster-autoscaler +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-autoscaler +subjects: + - kind: ServiceAccount + name: cluster-autoscaler + namespace: kube-system \ No newline at end of file diff --git a/k8s/configmap.yaml b/k8s/configmap.yaml new file mode 100644 index 0000000..9622ade --- /dev/null +++ b/k8s/configmap.yaml @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: storage-server-config + namespace: uhrp-storage + labels: + app: storage-server + app.kubernetes.io/name: storage-server + app.kubernetes.io/component: configuration +data: + NODE_ENV: "production" + HTTP_PORT: "8080" + AWS_REGION: "us-west-2" + AWS_BUCKET_NAME: "your-uhrp-bucket" # Update this + SERVER_URL: "https://storage.example.com" # Update this + CORS_ORIGIN: "*" + BSV_NETWORK: "mainnet" + WALLET_STORAGE_URL: "https://storage.babbage.systems" + PER_BYTE_PRICE: "0.00001" + BASE_PRICE: "1000" + MIN_HOSTING_MINUTES: "15" + # S3 Event Handler Config + SQS_QUEUE_URL: "https://sqs.us-west-2.amazonaws.com/YOUR_ACCOUNT/uhrp-s3-events" # Update this + SQS_REGION: "us-west-2" + SQS_MAX_MESSAGES: "10" + SQS_WAIT_TIME_SECONDS: "20" \ No newline at end of file diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml new file mode 100644 index 0000000..35b5ba0 --- /dev/null +++ b/k8s/deployment.yaml @@ -0,0 +1,157 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: storage-server + namespace: uhrp-storage + labels: + app: storage-server + app.kubernetes.io/name: storage-server + app.kubernetes.io/component: api + app.kubernetes.io/part-of: uhrp-storage +spec: + replicas: 3 + selector: + matchLabels: + app: storage-server + template: + metadata: + labels: + app: storage-server + app.kubernetes.io/name: storage-server + app.kubernetes.io/component: api + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "8080" + prometheus.io/path: "/metrics" + spec: + serviceAccountName: storage-server + securityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + containers: + - name: storage-server + image: YOUR_ECR_URL/uhrp-storage-server:latest # Will be replaced by CI/CD + imagePullPolicy: Always + ports: + - name: http + containerPort: 8080 + protocol: TCP + env: + - name: NODE_ENV + valueFrom: + configMapKeyRef: + name: storage-server-config + key: NODE_ENV + - name: HTTP_PORT + valueFrom: + configMapKeyRef: + name: storage-server-config + key: HTTP_PORT + - name: AWS_REGION + valueFrom: + configMapKeyRef: + name: storage-server-config + key: AWS_REGION + - name: AWS_BUCKET_NAME + valueFrom: + configMapKeyRef: + name: storage-server-config + key: AWS_BUCKET_NAME + - name: SERVER_URL + valueFrom: + configMapKeyRef: + name: storage-server-config + key: SERVER_URL + - name: CORS_ORIGIN + valueFrom: + configMapKeyRef: + name: storage-server-config + key: CORS_ORIGIN + - name: BSV_NETWORK + valueFrom: + configMapKeyRef: + name: storage-server-config + key: BSV_NETWORK + - name: WALLET_STORAGE_URL + valueFrom: + configMapKeyRef: + name: storage-server-config + key: WALLET_STORAGE_URL + - name: PER_BYTE_PRICE + valueFrom: + configMapKeyRef: + name: storage-server-config + key: PER_BYTE_PRICE + - name: BASE_PRICE + valueFrom: + configMapKeyRef: + name: storage-server-config + key: BASE_PRICE + - name: MIN_HOSTING_MINUTES + valueFrom: + configMapKeyRef: + name: storage-server-config + key: MIN_HOSTING_MINUTES + - name: SERVER_PRIVATE_KEY + valueFrom: + secretKeyRef: + name: uhrp-secrets + key: server-private-key + - name: ADMIN_TOKEN + valueFrom: + secretKeyRef: + name: uhrp-secrets + key: admin-token + - name: BUGSNAG_API_KEY + valueFrom: + secretKeyRef: + name: uhrp-secrets + key: bugsnag-api-key + optional: true + - name: NOTIFIER_URL + value: "http://event-handler.uhrp-storage.svc.cluster.local:8081/notify" + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1000m" + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + successThreshold: 1 + failureThreshold: 3 + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + nodeSelector: + kubernetes.io/os: linux + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - storage-server + topologyKey: kubernetes.io/hostname \ No newline at end of file diff --git a/k8s/hpa.yaml b/k8s/hpa.yaml new file mode 100644 index 0000000..fe45e24 --- /dev/null +++ b/k8s/hpa.yaml @@ -0,0 +1,99 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: storage-server-hpa + namespace: uhrp-storage + labels: + app: storage-server + app.kubernetes.io/name: storage-server +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: storage-server + minReplicas: 3 + maxReplicas: 20 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 + behavior: + scaleDown: + stabilizationWindowSeconds: 300 + policies: + - type: Percent + value: 10 + periodSeconds: 60 + - type: Pods + value: 2 + periodSeconds: 60 + selectPolicy: Min + scaleUp: + stabilizationWindowSeconds: 60 + policies: + - type: Percent + value: 100 + periodSeconds: 30 + - type: Pods + value: 4 + periodSeconds: 30 + selectPolicy: Max +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: event-handler-hpa + namespace: uhrp-storage + labels: + app: event-handler + app.kubernetes.io/name: event-handler +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: event-handler + minReplicas: 2 + maxReplicas: 10 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 + behavior: + scaleDown: + stabilizationWindowSeconds: 300 + policies: + - type: Percent + value: 10 + periodSeconds: 60 + - type: Pods + value: 1 + periodSeconds: 60 + selectPolicy: Min + scaleUp: + stabilizationWindowSeconds: 30 + policies: + - type: Percent + value: 50 + periodSeconds: 30 + - type: Pods + value: 2 + periodSeconds: 30 + selectPolicy: Max \ No newline at end of file diff --git a/k8s/ingress.yaml b/k8s/ingress.yaml new file mode 100644 index 0000000..f1366bd --- /dev/null +++ b/k8s/ingress.yaml @@ -0,0 +1,45 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: storage-server-ingress + namespace: uhrp-storage + labels: + app: storage-server + app.kubernetes.io/name: storage-server + annotations: + # AWS Load Balancer Controller annotations + kubernetes.io/ingress.class: alb + alb.ingress.kubernetes.io/scheme: internet-facing + alb.ingress.kubernetes.io/target-type: ip + alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]' + alb.ingress.kubernetes.io/ssl-redirect: '443' + alb.ingress.kubernetes.io/healthcheck-path: / + alb.ingress.kubernetes.io/healthcheck-interval-seconds: '30' + alb.ingress.kubernetes.io/healthcheck-timeout-seconds: '5' + alb.ingress.kubernetes.io/healthy-threshold-count: '2' + alb.ingress.kubernetes.io/unhealthy-threshold-count: '3' + alb.ingress.kubernetes.io/success-codes: '200,404' + alb.ingress.kubernetes.io/tags: Environment=production,Application=uhrp-storage + # Certificate - replace with your ACM certificate ARN + alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:REGION:ACCOUNT:certificate/CERT_ID + # Optional: WAF + # alb.ingress.kubernetes.io/wafv2-acl-arn: arn:aws:wafv2:REGION:ACCOUNT:regional/webacl/NAME/ID +spec: + ingressClassName: alb + rules: + - host: storage.example.com # Replace with your domain + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: storage-server + port: + number: 80 + # Optional: default backend for health checks + defaultBackend: + service: + name: storage-server + port: + number: 80 \ No newline at end of file diff --git a/k8s/monitoring/service-monitor.yaml b/k8s/monitoring/service-monitor.yaml new file mode 100644 index 0000000..8f36f13 --- /dev/null +++ b/k8s/monitoring/service-monitor.yaml @@ -0,0 +1,35 @@ +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: storage-server + namespace: uhrp-storage + labels: + app: storage-server + prometheus: kube-prometheus +spec: + selector: + matchLabels: + app: storage-server + endpoints: + - port: http + path: /metrics + interval: 30s + scrapeTimeout: 10s +--- +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: event-handler + namespace: uhrp-storage + labels: + app: event-handler + prometheus: kube-prometheus +spec: + selector: + matchLabels: + app: event-handler + endpoints: + - port: http + path: /metrics + interval: 30s + scrapeTimeout: 10s \ No newline at end of file diff --git a/k8s/namespace.yaml b/k8s/namespace.yaml new file mode 100644 index 0000000..a866018 --- /dev/null +++ b/k8s/namespace.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: uhrp-storage + labels: + name: uhrp-storage + app.kubernetes.io/name: uhrp-storage + app.kubernetes.io/instance: production \ No newline at end of file diff --git a/k8s/s3-event-handler/Dockerfile b/k8s/s3-event-handler/Dockerfile new file mode 100644 index 0000000..d957a82 --- /dev/null +++ b/k8s/s3-event-handler/Dockerfile @@ -0,0 +1,22 @@ +FROM node:23-alpine + +# Create app directory +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies as root (for proper permissions) +RUN npm install --omit=dev + +# Copy application code with correct ownership +COPY --chown=node:node . . + +# Switch to non-root user +USER node + +# Expose port +EXPOSE 8081 + +# Start the event handler +CMD ["node", "src/event-handler.js"] \ No newline at end of file diff --git a/k8s/s3-event-handler/deployment.yaml b/k8s/s3-event-handler/deployment.yaml new file mode 100644 index 0000000..23258cf --- /dev/null +++ b/k8s/s3-event-handler/deployment.yaml @@ -0,0 +1,145 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: event-handler + namespace: uhrp-server + labels: + app: event-handler + app.kubernetes.io/name: event-handler + app.kubernetes.io/component: event-processor + app.kubernetes.io/part-of: uhrp-server +spec: + replicas: 2 # Multiple replicas for high availability + selector: + matchLabels: + app: event-handler + template: + metadata: + labels: + app: event-handler + app.kubernetes.io/name: event-handler + app.kubernetes.io/component: event-processor + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "8081" + prometheus.io/path: "/metrics" + spec: + serviceAccountName: event-handler + securityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + containers: + - name: event-handler + image: ghcr.io/bsv-blockchain/storage-server-s3-notifier:latest # Will be replaced by CI/CD + imagePullPolicy: Always + ports: + - name: http + containerPort: 8081 + protocol: TCP + env: + - name: NODE_ENV + valueFrom: + configMapKeyRef: + name: storage-server-config + key: NODE_ENV + - name: AWS_REGION + valueFrom: + configMapKeyRef: + name: storage-server-config + key: AWS_REGION + - name: AWS_BUCKET_NAME + valueFrom: + configMapKeyRef: + name: storage-server-config + key: AWS_BUCKET_NAME + - name: SQS_QUEUE_URL + valueFrom: + configMapKeyRef: + name: storage-server-config + key: SQS_QUEUE_URL + - name: SQS_REGION + valueFrom: + configMapKeyRef: + name: storage-server-config + key: SQS_REGION + - name: SQS_MAX_MESSAGES + valueFrom: + configMapKeyRef: + name: storage-server-config + key: SQS_MAX_MESSAGES + - name: SQS_WAIT_TIME_SECONDS + valueFrom: + configMapKeyRef: + name: storage-server-config + key: SQS_WAIT_TIME_SECONDS + - name: HOSTING_DOMAIN + valueFrom: + configMapKeyRef: + name: storage-server-config + key: SERVER_URL + - name: SERVER_PRIVATE_KEY + valueFrom: + secretKeyRef: + name: uhrp-secrets + key: SERVER_PRIVATE_KEY + - name: ADMIN_TOKEN + valueFrom: + secretKeyRef: + name: uhrp-secrets + key: ADMIN_TOKEN + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + nodeSelector: + kubernetes.io/os: linux + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - event-handler + topologyKey: kubernetes.io/hostname +--- +apiVersion: v1 +kind: Service +metadata: + name: event-handler + namespace: uhrp-server + labels: + app: event-handler + app.kubernetes.io/name: event-handler + app.kubernetes.io/component: event-processor +spec: + type: ClusterIP + selector: + app: event-handler + ports: + - name: http + port: 8081 + targetPort: http + protocol: TCP + sessionAffinity: None diff --git a/k8s/s3-event-handler/package.json b/k8s/s3-event-handler/package.json new file mode 100644 index 0000000..c574152 --- /dev/null +++ b/k8s/s3-event-handler/package.json @@ -0,0 +1,31 @@ +{ + "name": "uhrp-s3-event-handler", + "version": "1.0.0", + "description": "S3 event handler for UHRP Storage Server running in Kubernetes", + "main": "src/event-handler.js", + "scripts": { + "start": "node src/event-handler.js", + "dev": "nodemon src/event-handler.js" + }, + "keywords": [ + "uhrp", + "s3", + "kubernetes", + "event-handler" + ], + "author": "UHRP Team", + "license": "MIT", + "dependencies": { + "@aws-sdk/client-s3": "^3.0.0", + "@aws-sdk/client-sqs": "^3.0.0", + "@bsv/sdk": "^1.6.20", + "axios": "^1.6.0", + "express": "^4.18.2" + }, + "devDependencies": { + "nodemon": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0" + } +} \ No newline at end of file diff --git a/k8s/s3-event-handler/src/event-handler.js b/k8s/s3-event-handler/src/event-handler.js new file mode 100644 index 0000000..cea456d --- /dev/null +++ b/k8s/s3-event-handler/src/event-handler.js @@ -0,0 +1,269 @@ +const express = require('express'); +const crypto = require('crypto'); +const axios = require('axios'); +const { StorageUtils } = require('@bsv/sdk'); +const { + S3Client, + GetObjectCommand, + HeadObjectCommand +} = require('@aws-sdk/client-s3'); +const { + SQSClient, + ReceiveMessageCommand, + DeleteMessageCommand +} = require('@aws-sdk/client-sqs'); + +// Environment variables +const { + NODE_ENV, + AWS_REGION, + AWS_BUCKET_NAME, + SQS_QUEUE_URL, + SQS_MAX_MESSAGES = '10', + SQS_WAIT_TIME_SECONDS = '20', + HOSTING_DOMAIN, + ADMIN_TOKEN, + SERVER_PRIVATE_KEY +} = process.env; + +// Initialize AWS clients +const s3Client = new S3Client({ region: AWS_REGION }); +const sqsClient = new SQSClient({ region: AWS_REGION }); + +// Express app for health checks and metrics +const app = express(); +app.use(express.json()); + +// Health check endpoint +app.get('/health', (req, res) => { + res.json({ status: 'healthy', service: 'event-handler' }); +}); + +// Readiness check endpoint +app.get('/ready', (req, res) => { + res.json({ status: 'ready', service: 'event-handler' }); +}); + +// Metrics endpoint (could be expanded with Prometheus metrics) +app.get('/metrics', (req, res) => { + res.type('text/plain'); + res.send(`# HELP processed_events_total Total number of processed events +# TYPE processed_events_total counter +processed_events_total ${processedEvents} + +# HELP processing_errors_total Total number of processing errors +# TYPE processing_errors_total counter +processing_errors_total ${processingErrors} +`); +}); + +// Manual notification endpoint (for testing or manual triggers) +app.post('/notify', async (req, res) => { + try { + const { bucket, key } = req.body; + await processS3Object(bucket, key); + res.json({ success: true, message: 'Notification processed' }); + } catch (error) { + console.error('Manual notification error:', error); + res.status(500).json({ success: false, error: error.message }); + } +}); + +// Metrics +let processedEvents = 0; +let processingErrors = 0; + +// Start HTTP server for health checks +const PORT = process.env.PORT || 8081; +app.listen(PORT, () => { + console.log(`Event handler HTTP server listening on port ${PORT}`); +}); + +// Process S3 object +async function processS3Object(bucketName, objectKey) { + console.log(`Processing S3 object: ${bucketName}/${objectKey}`); + + // Only process files in cdn/ folder + if (!objectKey.startsWith('cdn/')) { + console.log('Skipping non-CDN file:', objectKey); + return; + } + + try { + // Get object metadata + const headCommand = new HeadObjectCommand({ + Bucket: bucketName, + Key: objectKey + }); + const metadata = await s3Client.send(headCommand); + + console.log('S3 Object Metadata:', { + ContentLength: metadata.ContentLength, + ContentType: metadata.ContentType, + LastModified: metadata.LastModified, + Metadata: metadata.Metadata + }); + + // Extract uploader identity key from metadata + let uploaderIdentityKey = ''; + if (metadata.Metadata && metadata.Metadata.uploaderidentitykey) { + uploaderIdentityKey = metadata.Metadata.uploaderidentitykey; + console.log('Found uploaderIdentityKey in metadata:', uploaderIdentityKey); + } else { + console.warn('WARNING: uploaderidentitykey not found in S3 metadata for object:', objectKey); + console.warn('Available metadata keys:', Object.keys(metadata.Metadata || {})); + // Skip processing if no uploader identity key + console.error('Cannot process file without uploaderIdentityKey. File needs to be uploaded with proper metadata.'); + throw new Error('Missing uploaderIdentityKey in S3 metadata'); + } + + // Get expiry time from custom metadata + const expiryTime = metadata.Metadata?.customtime + ? Math.round(new Date(metadata.Metadata.customtime).getTime() / 1000) + : Math.round(Date.now() / 1000) + (30 * 24 * 60 * 60); // Default 30 days + + if (!metadata.Metadata?.customtime) { + console.warn('No customtime found in metadata, using default expiry of 30 days'); + } + + // Get the object to calculate hash + const getCommand = new GetObjectCommand({ + Bucket: bucketName, + Key: objectKey + }); + const response = await s3Client.send(getCommand); + + // Calculate SHA256 hash + const hash = crypto.createHash('sha256'); + const stream = response.Body; + + // Convert stream to buffer and calculate hash + const chunks = []; + for await (const chunk of stream) { + chunks.push(chunk); + hash.update(chunk); + } + + const fileHash = hash.digest(); + const uhrpUrl = StorageUtils.getURLForHash(fileHash); + + console.log('Generated UHRP URL:', uhrpUrl); + + // Extract object identifier from path + const objectIdentifier = objectKey.split('/').pop(); + + // Send notification to advertise endpoint + const notificationData = { + adminToken: ADMIN_TOKEN, + uhrpUrl, + uploaderIdentityKey, + objectIdentifier, + expiryTime, + fileSize: metadata.ContentLength + }; + + console.log('Sending notification:', notificationData); + + await axios.post( + `${HOSTING_DOMAIN}/advertise`, + notificationData, + { + timeout: 30000, + headers: { + 'Content-Type': 'application/json' + } + } + ); + + console.log('Successfully advertised file:', objectKey); + processedEvents++; + + } catch (error) { + console.error('Error processing S3 object:', error); + processingErrors++; + throw error; + } +} + +// Process SQS message +async function processMessage(message) { + try { + const body = JSON.parse(message.Body); + + // Handle S3 event notification + if (body.Records) { + for (const record of body.Records) { + if (record.eventName.startsWith('ObjectCreated:')) { + const bucketName = record.s3.bucket.name; + const objectKey = decodeURIComponent(record.s3.object.key.replace(/\+/g, ' ')); + + await processS3Object(bucketName, objectKey); + } + } + } + + // Delete message from queue after successful processing + const deleteCommand = new DeleteMessageCommand({ + QueueUrl: SQS_QUEUE_URL, + ReceiptHandle: message.ReceiptHandle + }); + await sqsClient.send(deleteCommand); + + } catch (error) { + console.error('Error processing message:', error); + processingErrors++; + // Don't delete message on error - let it retry + } +} + +// Main polling loop +async function pollSQS() { + while (true) { + try { + // Receive messages from SQS + const receiveCommand = new ReceiveMessageCommand({ + QueueUrl: SQS_QUEUE_URL, + MaxNumberOfMessages: parseInt(SQS_MAX_MESSAGES), + WaitTimeSeconds: parseInt(SQS_WAIT_TIME_SECONDS), + MessageAttributeNames: ['All'] + }); + + const response = await sqsClient.send(receiveCommand); + + if (response.Messages && response.Messages.length > 0) { + console.log(`Received ${response.Messages.length} messages from SQS`); + + // Process messages in parallel + await Promise.all( + response.Messages.map(message => processMessage(message)) + ); + } + + } catch (error) { + console.error('Error polling SQS:', error); + processingErrors++; + // Wait before retrying + await new Promise(resolve => setTimeout(resolve, 5000)); + } + } +} + +// Graceful shutdown +process.on('SIGTERM', () => { + console.log('Received SIGTERM, shutting down gracefully...'); + process.exit(0); +}); + +process.on('SIGINT', () => { + console.log('Received SIGINT, shutting down gracefully...'); + process.exit(0); +}); + +// Start polling SQS +console.log('Starting S3 event handler...'); +console.log('SQS Queue URL:', SQS_QUEUE_URL); +console.log('S3 Bucket:', AWS_BUCKET_NAME); +pollSQS().catch(error => { + console.error('Fatal error in polling loop:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/k8s/service.yaml b/k8s/service.yaml new file mode 100644 index 0000000..02d57ab --- /dev/null +++ b/k8s/service.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: Service +metadata: + name: storage-server + namespace: uhrp-storage + labels: + app: storage-server + app.kubernetes.io/name: storage-server + app.kubernetes.io/component: api + annotations: + service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http + service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "https" +spec: + type: ClusterIP + selector: + app: storage-server + ports: + - name: http + port: 80 + targetPort: http + protocol: TCP + sessionAffinity: None \ No newline at end of file diff --git a/k8s/serviceaccount.yaml b/k8s/serviceaccount.yaml new file mode 100644 index 0000000..3c90c4f --- /dev/null +++ b/k8s/serviceaccount.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: storage-server + namespace: uhrp-storage + labels: + app: storage-server + app.kubernetes.io/name: storage-server + annotations: + # This annotation will be added by eksctl when creating IRSA + # eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: event-handler + namespace: uhrp-storage + labels: + app: event-handler + app.kubernetes.io/name: event-handler + annotations: + # This annotation will be added by eksctl when creating IRSA + # eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6f1f87e..efbae49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,10 @@ "version": "0.2.0", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { + "@aws-sdk/client-s3": "^3.0.0", + "@aws-sdk/lib-storage": "^3.0.0", + "@aws-sdk/s3-presigned-post": "^3.850.0", + "@aws-sdk/s3-request-presigner": "^3.0.0", "@bsv/auth-express-middleware": "^1.2.2", "@bsv/payment-express-middleware": "^1.2.3", "@bsv/sdk": "^1.6.20", @@ -54,657 +58,1023 @@ "node": ">=6.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==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/compat-data": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", - "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" } }, - "node_modules/@babel/core": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", - "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "license": "Apache-2.0", "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.6", - "@babel/parser": "^7.28.0", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.0", - "@babel/types": "^7.28.0", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" + "@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/sha1-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" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" + "node": ">=14.0.0" } }, - "node_modules/@babel/core/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/sha1-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": { - "ms": "^2.1.3" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.0" + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-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" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@babel/core/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@babel/generator": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", - "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", - "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": { - "@babel/parser": "^7.28.0", - "@babel/types": "^7.28.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, - "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": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", + "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": { - "yallist": "^3.0.2" + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" + "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/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", + "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/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "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": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", - "dev": true, - "license": "MIT", + "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": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, + "node_modules/@aws-sdk/client-s3": { + "version": "3.850.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.850.0.tgz", + "integrity": "sha512-tX5bUfqiLOh6jtAlaiAuOUKFYh8KDG9k9zFLUdgGplC5TP47AYTreUEg+deCTHo4DD3YCvrLuyZ8tIDgKu7neQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.846.0", + "@aws-sdk/credential-provider-node": "3.848.0", + "@aws-sdk/middleware-bucket-endpoint": "3.840.0", + "@aws-sdk/middleware-expect-continue": "3.840.0", + "@aws-sdk/middleware-flexible-checksums": "3.846.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-location-constraint": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-sdk-s3": "3.846.0", + "@aws-sdk/middleware-ssec": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.848.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/signature-v4-multi-region": "3.846.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.848.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.848.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.7.0", + "@smithy/eventstream-serde-browser": "^4.0.4", + "@smithy/eventstream-serde-config-resolver": "^4.1.2", + "@smithy/eventstream-serde-node": "^4.0.4", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/hash-blob-browser": "^4.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/hash-stream-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/md5-js": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.15", + "@smithy/middleware-retry": "^4.1.16", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.7", + "@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.23", + "@smithy/util-defaults-mode-node": "^4.0.23", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-stream": "^4.2.3", + "@smithy/util-utf8": "^4.0.0", + "@smithy/util-waiter": "^4.0.6", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/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.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.848.0.tgz", + "integrity": "sha512-mD+gOwoeZQvbecVLGoCmY6pS7kg02BHesbtIxUj+PeBqYoZV5uLvjUOmuGfw1SfoSobKvS11urxC9S7zxU/Maw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.846.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.848.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.848.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.848.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.7.0", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.15", + "@smithy/middleware-retry": "^4.1.16", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.7", + "@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.23", + "@smithy/util-defaults-mode-node": "^4.0.23", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.846.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.846.0.tgz", + "integrity": "sha512-7CX0pM906r4WSS68fCTNMTtBCSkTtf3Wggssmx13gD40gcWEZXsU00KzPp1bYheNRyPlAq3rE22xt4wLPXbuxA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/core": "^3.7.0", + "@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.7", + "@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": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.846.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.846.0.tgz", + "integrity": "sha512-QuCQZET9enja7AWVISY+mpFrEIeHzvkx/JEEbHYzHhUkxcnC2Kq2c0bB7hDihGD0AZd3Xsm653hk1O97qu69zg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.846.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.846.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.846.0.tgz", + "integrity": "sha512-Jh1iKUuepdmtreMYozV2ePsPcOF5W9p3U4tWhi3v6nDvz0GsBjzjAROW+BW8XMz9vAD3I9R+8VC3/aq63p5nlw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.846.0", + "@aws-sdk/types": "3.840.0", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.7", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.3", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.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==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.848.0.tgz", + "integrity": "sha512-r6KWOG+En2xujuMhgZu7dzOZV3/M5U/5+PXrG8dLQ3rdPRB3vgp5tc56KMqLwm/EXKRzAOSuw/UE4HfNOAB8Hw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.846.0", + "@aws-sdk/credential-provider-env": "3.846.0", + "@aws-sdk/credential-provider-http": "3.846.0", + "@aws-sdk/credential-provider-process": "3.846.0", + "@aws-sdk/credential-provider-sso": "3.848.0", + "@aws-sdk/credential-provider-web-identity": "3.848.0", + "@aws-sdk/nested-clients": "3.848.0", + "@aws-sdk/types": "3.840.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": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.848.0.tgz", + "integrity": "sha512-AblNesOqdzrfyASBCo1xW3uweiSro4Kft9/htdxLeCVU1KVOnFWA5P937MNahViRmIQm2sPBCqL8ZG0u9lnh5g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.846.0", + "@aws-sdk/credential-provider-http": "3.846.0", + "@aws-sdk/credential-provider-ini": "3.848.0", + "@aws-sdk/credential-provider-process": "3.846.0", + "@aws-sdk/credential-provider-sso": "3.848.0", + "@aws-sdk/credential-provider-web-identity": "3.848.0", + "@aws-sdk/types": "3.840.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": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helpers": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz", - "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.846.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.846.0.tgz", + "integrity": "sha512-mEpwDYarJSH+CIXnnHN0QOe0MXI+HuPStD6gsv3z/7Q6ESl8KRWon3weFZCDnqpiJMUVavlDR0PPlAFg2MQoPg==", + "license": "Apache-2.0", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.2" + "@aws-sdk/core": "3.846.0", + "@aws-sdk/types": "3.840.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": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/parser": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", - "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.848.0.tgz", + "integrity": "sha512-pozlDXOwJZL0e7w+dqXLgzVDB7oCx4WvtY0sk6l4i07uFliWF/exupb6pIehFWvTUcOvn5aFTTqcQaEzAD5Wsg==", + "license": "Apache-2.0", "dependencies": { - "@babel/types": "^7.28.0" - }, - "bin": { - "parser": "bin/babel-parser.js" + "@aws-sdk/client-sso": "3.848.0", + "@aws-sdk/core": "3.846.0", + "@aws-sdk/token-providers": "3.848.0", + "@aws-sdk/types": "3.840.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": ">=6.0.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.848.0.tgz", + "integrity": "sha512-D1fRpwPxtVDhcSc/D71exa2gYweV+ocp4D3brF0PgFd//JR3XahZ9W24rVnTQwYEcK9auiBZB89Ltv+WbWN8qw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/core": "3.846.0", + "@aws-sdk/nested-clients": "3.848.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/lib-storage": { + "version": "3.850.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.850.0.tgz", + "integrity": "sha512-DKG8mKeUMLRboyqwhKiV9QOiKXN00OYLnGsT21mhlaF1Uc7OZ6Vm+Olw4YrbYSBuDup0rMWtVaWudJ49I+ZCHA==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@smithy/abort-controller": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.15", + "@smithy/smithy-client": "^4.4.7", + "buffer": "5.6.0", + "events": "3.3.0", + "stream-browserify": "3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@aws-sdk/client-s3": "^3.850.0" } }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, + "node_modules/@aws-sdk/lib-storage/node_modules/buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" } }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.840.0.tgz", + "integrity": "sha512-+gkQNtPwcSMmlwBHFd4saVVS11In6ID1HczNzpM3MXKXRBfSlbZJbCt6wN//AZ8HMklZEik4tcEOG0qa9UY8SQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-arn-parser": "3.804.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.840.0.tgz", + "integrity": "sha512-iJg2r6FKsKKvdiU4oCOuCf7Ro/YE0Q2BT/QyEZN3/Rt8Nr4SAZiQOlcBXOCpGvuIKOEAhvDOUnW3aDHL01PdVw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@aws-sdk/types": "3.840.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.846.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.846.0.tgz", + "integrity": "sha512-CdkeVfkwt3+bDLhmOwBxvkUf6oY9iUhvosaUnqkoPsOqIiUEN54yTGOnO8A0wLz6mMsZ6aBlfFrQhFnxt3c+yw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.846.0", + "@aws-sdk/types": "3.840.0", + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.3", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.840.0.tgz", + "integrity": "sha512-ub+hXJAbAje94+Ya6c6eL7sYujoE8D4Bumu1NUI8TXjUhVVn0HzVWQjpRLshdLsUp1AW7XyeJaxyajRaJQ8+Xg==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/types": "3.840.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.840.0.tgz", + "integrity": "sha512-KVLD0u0YMF3aQkVF8bdyHAGWSUY6N1Du89htTLgqCcIhSxxAJ9qifrosVZ9jkAzqRW99hcufyt2LylcVU2yoKQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.840.0.tgz", + "integrity": "sha512-lSV8FvjpdllpGaRspywss4CtXV8M7NNNH+2/j86vMH+YCOZ6fu2T/TyFd/tHwZ92vDfHctWkRbQxg0bagqwovA==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.840.0.tgz", + "integrity": "sha512-Gu7lGDyfddyhIkj1Z1JtrY5NHb5+x/CRiB87GjaSrKxkDaydtX2CU977JIABtt69l9wLbcGDIQ+W0uJ5xPof7g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/types": "3.840.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.846.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.846.0.tgz", + "integrity": "sha512-jP9x+2Q87J5l8FOP+jlAd7vGLn0cC6G9QGmf386e5OslBPqxXKcl3RjqGLIOKKos2mVItY3ApP5xdXQx7jGTVA==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@aws-sdk/core": "3.846.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-arn-parser": "3.804.0", + "@smithy/core": "^3.7.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.7", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.3", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.840.0.tgz", + "integrity": "sha512-CBZP9t1QbjDFGOrtnUEHL1oAvmnCUUm7p0aPNbIdSzNtH42TNKjPRN3TuEIJDGjkrqpL3MXyDSmNayDcw/XW7Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.848.0.tgz", + "integrity": "sha512-rjMuqSWJEf169/ByxvBqfdei1iaduAnfolTshsZxwcmLIUtbYrFUmts0HrLQqsAG8feGPpDLHA272oPl+NTCCA==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/core": "3.846.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.848.0", + "@smithy/core": "^3.7.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/nested-clients": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.848.0.tgz", + "integrity": "sha512-joLsyyo9u61jnZuyYzo1z7kmS7VgWRAkzSGESVzQHfOA1H2PYeUFek6vLT4+c9xMGrX/Z6B0tkRdzfdOPiatLg==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.846.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.848.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.848.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.848.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.7.0", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.15", + "@smithy/middleware-retry": "^4.1.16", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.7", + "@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.23", + "@smithy/util-defaults-mode-node": "^4.0.23", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.840.0.tgz", + "integrity": "sha512-Qjnxd/yDv9KpIMWr90ZDPtRj0v75AqGC92Lm9+oHXZ8p1MjG5JE2CW0HL8JRgK9iKzgKBL7pPQRXI8FkvEVfrA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.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" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/s3-presigned-post": { + "version": "3.850.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-presigned-post/-/s3-presigned-post-3.850.0.tgz", + "integrity": "sha512-SNIvs1nB4ZAOGXtzDEEKaSZYy8SxRYR5u6Its+gU6s2HL1rv2UnHSe5gnQLWmGMyDnU8/vuXKqYhQst8AUo13A==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/client-s3": "3.850.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-format-url": "3.840.0", + "@smithy/middleware-endpoint": "^4.1.15", + "@smithy/signature-v4": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/s3-request-presigner": { + "version": "3.850.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.850.0.tgz", + "integrity": "sha512-eFvMUCJXoVTkAxkqHKn125mLMGtNa76+oD3wV97ScXUZuL5liaj+kAN9nSqRiQ5vaCz5gsOeB9t/ba/cTGATjg==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@aws-sdk/signature-v4-multi-region": "3.846.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-format-url": "3.840.0", + "@smithy/middleware-endpoint": "^4.1.15", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.7", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.846.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.846.0.tgz", + "integrity": "sha512-ZMfIMxUljqZzPJGOcraC6erwq/z1puNMU35cO1a/WdhB+LdYknMn1lr7SJuH754QwNzzIlZbEgg4hoHw50+DpQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@aws-sdk/middleware-sdk-s3": "3.846.0", + "@aws-sdk/types": "3.840.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/token-providers": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.848.0.tgz", + "integrity": "sha512-oNPyM4+Di2Umu0JJRFSxDcKQ35+Chl/rAwD47/bS0cDPI8yrao83mLXLeDqpRPHyQW4sXlP763FZcuAibC0+mg==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@aws-sdk/core": "3.846.0", + "@aws-sdk/nested-clients": "3.848.0", + "@aws-sdk/types": "3.840.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": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/types": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.840.0.tgz", + "integrity": "sha512-xliuHaUFZxEx1NSXeLLZ9Dyu6+EJVQKEoD+yM+zqUo3YDZ7medKJWY6fIOKiPX/N7XbLdBYwajb15Q7IL8KkeA==", + "license": "Apache-2.0", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", - "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.804.0.tgz", + "integrity": "sha512-wmBJqn1DRXnZu3b4EkE6CWnoWMo1ZMvlfkqU5zPz67xx1GMaXlDCchFvKAXMjk4jn/L1O3tKnoFDNsoLV1kgNQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.0", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.0", - "debug": "^4.3.1" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/traverse/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.848.0.tgz", + "integrity": "sha512-fY/NuFFCq/78liHvRyFKr+aqq1aA/uuVSANjzr5Ym8c+9Z3HRPE9OrExAHoMrZ6zC8tHerQwlsXYYH5XZ7H+ww==", + "license": "Apache-2.0", "dependencies": { - "ms": "^2.1.3" + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-endpoints": "^3.0.6", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.0" + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-format-url": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.840.0.tgz", + "integrity": "sha512-VB1PWyI1TQPiPvg4w7tgUGGQER1xxXPNUqfh3baxUSFi1Oh8wHrDnFywkxLm3NMmgDmnLnSZ5Q326qAoyqKLSg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/traverse/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/types": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", - "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", - "dev": true, - "license": "MIT", + "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": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@bsv/auth-express-middleware": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@bsv/auth-express-middleware/-/auth-express-middleware-1.2.2.tgz", - "integrity": "sha512-Fpr4dd2pZ8rMAgnZrTRRHczJVi1NFi8Hhq4z98DqXVPJNOXF25c3gBd+ssRiB92E4udRT67k7zxDMm5yrkc7lw==", - "license": "SEE LICENSE IN LICENSE.txt", + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.840.0.tgz", + "integrity": "sha512-JdyZM3EhhL4PqwFpttZu1afDpPJCCc3eyZOLi+srpX11LsGj6sThf47TYQN75HT1CarZ7cCdQHGzP2uy3/xHfQ==", + "license": "Apache-2.0", "dependencies": { - "@bsv/sdk": "^1.6.17", - "express": "^5.1.0" + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" } }, - "node_modules/@bsv/auth-express-middleware/node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.848.0.tgz", + "integrity": "sha512-Zz1ft9NiLqbzNj/M0jVNxaoxI2F4tGXN0ZbZIj+KJ+PbJo+w5+Jo6d0UDAtbj3AEd79pjcCaP4OA9NTVzItUdw==", + "license": "Apache-2.0", "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" + "@aws-sdk/middleware-user-agent": "3.848.0", + "@aws-sdk/types": "3.840.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 0.6" + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/@bsv/auth-express-middleware/node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", - "license": "MIT", + "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": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@bsv/auth-express-middleware/node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "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==", + "dev": true, "license": "MIT", "dependencies": { - "safe-buffer": "5.2.1" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { - "node": ">= 0.6" + "node": ">=6.9.0" } }, - "node_modules/@bsv/auth-express-middleware/node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "node_modules/@babel/compat-data": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "dev": true, "license": "MIT", "engines": { - "node": ">=6.6.0" + "node": ">=6.9.0" } }, - "node_modules/@bsv/auth-express-middleware/node_modules/debug": { + "node_modules/@babel/core": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", + "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -718,1506 +1088,2828 @@ } } }, - "node_modules/@bsv/auth-express-middleware/node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.0", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@bsv/auth-express-middleware/node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "node_modules/@babel/generator": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", + "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", + "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" + "@babel/parser": "^7.28.0", + "@babel/types": "^7.28.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { - "node": ">= 0.8" + "node": ">=6.9.0" } }, - "node_modules/@bsv/auth-express-middleware/node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, "engines": { - "node": ">= 0.8" + "node": ">=6.9.0" } }, - "node_modules/@bsv/auth-express-middleware/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" + "yallist": "^3.0.2" } }, - "node_modules/@bsv/auth-express-middleware/node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@bsv/auth-express-middleware/node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6.9.0" } }, - "node_modules/@bsv/auth-express-middleware/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, "license": "MIT", "dependencies": { - "mime-db": "^1.54.0" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { - "node": ">= 0.6" + "node": ">=6.9.0" } }, - "node_modules/@bsv/auth-express-middleware/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/@bsv/auth-express-middleware/node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@bsv/auth-express-middleware/node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" }, "engines": { - "node": ">=0.6" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@bsv/auth-express-middleware/node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" - }, "engines": { - "node": ">= 0.8" + "node": ">=6.9.0" } }, - "node_modules/@bsv/auth-express-middleware/node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "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", - "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" - }, "engines": { - "node": ">= 18" + "node": ">=6.9.0" } }, - "node_modules/@bsv/auth-express-middleware/node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, "engines": { - "node": ">= 18" + "node": ">=6.9.0" } }, - "node_modules/@bsv/auth-express-middleware/node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, "engines": { - "node": ">= 0.6" + "node": ">=6.9.0" } }, - "node_modules/@bsv/payment-express-middleware": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@bsv/payment-express-middleware/-/payment-express-middleware-1.2.3.tgz", - "integrity": "sha512-yseq4qiM1d30E0gfO41ewk5kmmpXr8nRY5sdAwoaIEBc5TrpUxKK1L4ZEk5vIf3ylh5Yr2P2T+k2YgA3CcHfAw==", - "license": "SEE LICENSE IN LICENSE.txt", + "node_modules/@babel/helpers": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz", + "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==", + "dev": true, + "license": "MIT", "dependencies": { - "@bsv/sdk": "^1.6.17", - "express": "^5.1.0" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@bsv/payment-express-middleware/node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "node_modules/@babel/parser": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "dev": true, "license": "MIT", "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" + "@babel/types": "^7.28.0" + }, + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">= 0.6" + "node": ">=6.0.0" } }, - "node_modules/@bsv/payment-express-middleware/node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, "license": "MIT", "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=18" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@bsv/payment-express-middleware/node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, "license": "MIT", "dependencies": { - "safe-buffer": "5.2.1" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">= 0.6" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@bsv/payment-express-middleware/node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=6.6.0" + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@bsv/payment-express-middleware/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">=6.0" + "node": ">=6.9.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@bsv/payment-express-middleware/node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.0", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { - "node": ">= 18" + "node": ">=6.9.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@bsv/payment-express-middleware/node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" + "@babel/helper-plugin-utils": "^7.10.4" }, - "engines": { - "node": ">= 0.8" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@bsv/payment-express-middleware/node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.8" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@bsv/payment-express-middleware/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@bsv/payment-express-middleware/node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.8" + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@bsv/payment-express-middleware/node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@bsv/payment-express-middleware/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, "license": "MIT", "dependencies": { - "mime-db": "^1.54.0" + "@babel/helper-plugin-utils": "^7.10.4" }, - "engines": { - "node": ">= 0.6" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@bsv/payment-express-middleware/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/@bsv/payment-express-middleware/node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@bsv/payment-express-middleware/node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" + "@babel/helper-plugin-utils": "^7.8.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@bsv/payment-express-middleware/node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">= 0.8" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@bsv/payment-express-middleware/node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">= 18" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@bsv/payment-express-middleware/node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, "license": "MIT", "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">= 18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@bsv/payment-express-middleware/node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, "license": "MIT", "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">= 0.6" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@bsv/sdk": { - "version": "1.6.20", - "resolved": "https://registry.npmjs.org/@bsv/sdk/-/sdk-1.6.20.tgz", - "integrity": "sha512-EiUKTzIlBqYRW135skDtMV0i99D5VhiNPxWzP+F0WpwzuTdDx4jWrOfxLtsfgq8Rp8dQdk9ouqpavBUSSbhdRQ==", - "license": "SEE LICENSE IN LICENSE.txt" + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/@bsv/wallet-toolbox": { - "version": "1.5.19", - "resolved": "https://registry.npmjs.org/@bsv/wallet-toolbox/-/wallet-toolbox-1.5.19.tgz", - "integrity": "sha512-NVUZl2y3FU1kAZcJ9nOgA3ZLTAmW0yb58+rpLd6tzKBvTpsIpXKCMJmpOd+b7gC5UShEaCglxD6UlWxGhHohVQ==", - "license": "SEE LICENSE IN license.md", + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", "dependencies": { - "@bsv/auth-express-middleware": "^1.2.2", - "@bsv/payment-express-middleware": "^1.2.3", - "@bsv/sdk": "^1.6.20", - "express": "^4.21.2", - "idb": "^8.0.2", - "knex": "^3.1.0", - "mysql2": "^3.12.0", - "sqlite3": "^5.1.7" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@bsv/wallet-toolbox-client": { - "version": "1.5.19", - "resolved": "https://registry.npmjs.org/@bsv/wallet-toolbox-client/-/wallet-toolbox-client-1.5.19.tgz", - "integrity": "sha512-8hDh7oBCjs0UVsMTWZT/kt3bEW2ehblJi1bE5UGW3KEqlJz00g542KNOzwRRkS/0v3L24wd8Gizo4LW6xIZKFg==", - "license": "SEE LICENSE IN license.md", + "node_modules/@babel/traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", + "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", + "dev": true, + "license": "MIT", "dependencies": { - "@bsv/sdk": "^1.6.20", - "idb": "^8.0.2" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@bugsnag/browser": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@bugsnag/browser/-/browser-7.25.0.tgz", - "integrity": "sha512-PzzWy5d9Ly1CU1KkxTB6ZaOw/dO+CYSfVtqxVJccy832e6+7rW/dvSw5Jy7rsNhgcKSKjZq86LtNkPSvritOLA==", + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, "license": "MIT", "dependencies": { - "@bugsnag/core": "^7.25.0" + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@bugsnag/core": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@bugsnag/core/-/core-7.25.0.tgz", - "integrity": "sha512-JZLak1b5BVzy77CPcklViZrppac/pE07L3uSDmfSvFYSCGReXkik2txOgV05VlF9EDe36dtUAIIV7iAPDfFpQQ==", + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/types": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "dev": true, "license": "MIT", "dependencies": { - "@bugsnag/cuid": "^3.0.0", - "@bugsnag/safe-json-stringify": "^6.0.0", - "error-stack-parser": "^2.0.3", - "iserror": "0.0.2", - "stack-generator": "^2.0.3" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@bugsnag/cuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@bugsnag/cuid/-/cuid-3.2.1.tgz", - "integrity": "sha512-zpvN8xQ5rdRWakMd/BcVkdn2F8HKlDSbM3l7duueK590WmI1T0ObTLc1V/1e55r14WNjPd5AJTYX4yPEAFVi+Q==", + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, "license": "MIT" }, - "node_modules/@bugsnag/js": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@bugsnag/js/-/js-7.25.0.tgz", - "integrity": "sha512-d8n8SyKdRUz8jMacRW1j/Sj/ckhKbIEp49+Dacp3CS8afRgfMZ//NXhUFFXITsDP5cXouaejR9fx4XVapYXNgg==", + "node_modules/@bsv/auth-express-middleware": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@bsv/auth-express-middleware/-/auth-express-middleware-1.2.2.tgz", + "integrity": "sha512-Fpr4dd2pZ8rMAgnZrTRRHczJVi1NFi8Hhq4z98DqXVPJNOXF25c3gBd+ssRiB92E4udRT67k7zxDMm5yrkc7lw==", + "license": "SEE LICENSE IN LICENSE.txt", + "dependencies": { + "@bsv/sdk": "^1.6.17", + "express": "^5.1.0" + } + }, + "node_modules/@bsv/auth-express-middleware/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@bsv/auth-express-middleware/node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@bsv/auth-express-middleware/node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@bsv/auth-express-middleware/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/@bsv/auth-express-middleware/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@bsv/auth-express-middleware/node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@bsv/auth-express-middleware/node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@bsv/auth-express-middleware/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@bsv/auth-express-middleware/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@bsv/auth-express-middleware/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@bsv/auth-express-middleware/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@bsv/auth-express-middleware/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@bsv/auth-express-middleware/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/@bsv/auth-express-middleware/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@bsv/auth-express-middleware/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@bsv/auth-express-middleware/node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@bsv/auth-express-middleware/node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@bsv/auth-express-middleware/node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@bsv/auth-express-middleware/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@bsv/payment-express-middleware": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@bsv/payment-express-middleware/-/payment-express-middleware-1.2.3.tgz", + "integrity": "sha512-yseq4qiM1d30E0gfO41ewk5kmmpXr8nRY5sdAwoaIEBc5TrpUxKK1L4ZEk5vIf3ylh5Yr2P2T+k2YgA3CcHfAw==", + "license": "SEE LICENSE IN LICENSE.txt", + "dependencies": { + "@bsv/sdk": "^1.6.17", + "express": "^5.1.0" + } + }, + "node_modules/@bsv/payment-express-middleware/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@bsv/payment-express-middleware/node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@bsv/payment-express-middleware/node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@bsv/payment-express-middleware/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/@bsv/payment-express-middleware/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@bsv/payment-express-middleware/node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@bsv/payment-express-middleware/node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@bsv/payment-express-middleware/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@bsv/payment-express-middleware/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@bsv/payment-express-middleware/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@bsv/payment-express-middleware/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@bsv/payment-express-middleware/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@bsv/payment-express-middleware/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/@bsv/payment-express-middleware/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@bsv/payment-express-middleware/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@bsv/payment-express-middleware/node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@bsv/payment-express-middleware/node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@bsv/payment-express-middleware/node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@bsv/payment-express-middleware/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@bsv/sdk": { + "version": "1.6.20", + "resolved": "https://registry.npmjs.org/@bsv/sdk/-/sdk-1.6.20.tgz", + "integrity": "sha512-EiUKTzIlBqYRW135skDtMV0i99D5VhiNPxWzP+F0WpwzuTdDx4jWrOfxLtsfgq8Rp8dQdk9ouqpavBUSSbhdRQ==", + "license": "SEE LICENSE IN LICENSE.txt" + }, + "node_modules/@bsv/wallet-toolbox": { + "version": "1.5.19", + "resolved": "https://registry.npmjs.org/@bsv/wallet-toolbox/-/wallet-toolbox-1.5.19.tgz", + "integrity": "sha512-NVUZl2y3FU1kAZcJ9nOgA3ZLTAmW0yb58+rpLd6tzKBvTpsIpXKCMJmpOd+b7gC5UShEaCglxD6UlWxGhHohVQ==", + "license": "SEE LICENSE IN license.md", + "dependencies": { + "@bsv/auth-express-middleware": "^1.2.2", + "@bsv/payment-express-middleware": "^1.2.3", + "@bsv/sdk": "^1.6.20", + "express": "^4.21.2", + "idb": "^8.0.2", + "knex": "^3.1.0", + "mysql2": "^3.12.0", + "sqlite3": "^5.1.7" + } + }, + "node_modules/@bsv/wallet-toolbox-client": { + "version": "1.5.19", + "resolved": "https://registry.npmjs.org/@bsv/wallet-toolbox-client/-/wallet-toolbox-client-1.5.19.tgz", + "integrity": "sha512-8hDh7oBCjs0UVsMTWZT/kt3bEW2ehblJi1bE5UGW3KEqlJz00g542KNOzwRRkS/0v3L24wd8Gizo4LW6xIZKFg==", + "license": "SEE LICENSE IN license.md", + "dependencies": { + "@bsv/sdk": "^1.6.20", + "idb": "^8.0.2" + } + }, + "node_modules/@bugsnag/browser": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@bugsnag/browser/-/browser-7.25.0.tgz", + "integrity": "sha512-PzzWy5d9Ly1CU1KkxTB6ZaOw/dO+CYSfVtqxVJccy832e6+7rW/dvSw5Jy7rsNhgcKSKjZq86LtNkPSvritOLA==", + "license": "MIT", + "dependencies": { + "@bugsnag/core": "^7.25.0" + } + }, + "node_modules/@bugsnag/core": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@bugsnag/core/-/core-7.25.0.tgz", + "integrity": "sha512-JZLak1b5BVzy77CPcklViZrppac/pE07L3uSDmfSvFYSCGReXkik2txOgV05VlF9EDe36dtUAIIV7iAPDfFpQQ==", + "license": "MIT", + "dependencies": { + "@bugsnag/cuid": "^3.0.0", + "@bugsnag/safe-json-stringify": "^6.0.0", + "error-stack-parser": "^2.0.3", + "iserror": "0.0.2", + "stack-generator": "^2.0.3" + } + }, + "node_modules/@bugsnag/cuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@bugsnag/cuid/-/cuid-3.2.1.tgz", + "integrity": "sha512-zpvN8xQ5rdRWakMd/BcVkdn2F8HKlDSbM3l7duueK590WmI1T0ObTLc1V/1e55r14WNjPd5AJTYX4yPEAFVi+Q==", + "license": "MIT" + }, + "node_modules/@bugsnag/js": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@bugsnag/js/-/js-7.25.0.tgz", + "integrity": "sha512-d8n8SyKdRUz8jMacRW1j/Sj/ckhKbIEp49+Dacp3CS8afRgfMZ//NXhUFFXITsDP5cXouaejR9fx4XVapYXNgg==", + "license": "MIT", + "dependencies": { + "@bugsnag/browser": "^7.25.0", + "@bugsnag/node": "^7.25.0" + } + }, + "node_modules/@bugsnag/node": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@bugsnag/node/-/node-7.25.0.tgz", + "integrity": "sha512-KlxBaJ8EREEsfKInybAjTO9LmdDXV3cUH5+XNXyqUZrcRVuPOu4j4xvljh+n24ifok/wbFZTKVXUzrN4iKIeIA==", + "license": "MIT", + "dependencies": { + "@bugsnag/core": "^7.25.0", + "byline": "^5.0.0", + "error-stack-parser": "^2.0.2", + "iserror": "^0.0.2", + "pump": "^3.0.0", + "stack-generator": "^2.0.3" + } + }, + "node_modules/@bugsnag/plugin-express": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@bugsnag/plugin-express/-/plugin-express-7.25.0.tgz", + "integrity": "sha512-X3JhhfYet7MhOGmW59p4+R2N122VQruljysAwl1p9J0jp1eWmslKtsuPcVTjI08+SSSn1iC67kJ3F6MhDMt7/A==", + "license": "MIT", + "dependencies": { + "iserror": "^0.0.2" + }, + "peerDependencies": { + "@bugsnag/core": "^7.0.0" + } + }, + "node_modules/@bugsnag/safe-json-stringify": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@bugsnag/safe-json-stringify/-/safe-json-stringify-6.0.0.tgz", + "integrity": "sha512-htzFO1Zc57S8kgdRK9mLcPVTW1BY2ijfH7Dk2CeZmspTWKdKqSo1iwmqrq2WtRjFlo8aRZYgLX0wFrDXF/9DLA==", + "license": "MIT" + }, + "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/@emnapi/core": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", + "integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.0.4", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", + "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz", + "integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", + "integrity": "sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "lodash": "^4.17.20", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@eslint/eslintrc/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "license": "MIT", + "optional": true + }, + "node_modules/@google-cloud/paginator": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.7.tgz", + "integrity": "sha512-jJNutk0arIQhmpUUQJPJErsojqo834KcyB6X7a1mxuic8i1tKXxde8E69IZxNZawRIlZdIK2QY4WALvlK5MzYQ==", + "license": "Apache-2.0", + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.1.1.tgz", + "integrity": "sha512-+rssMZHnlh0twl122gXY4/aCrk0G1acBqkHFfYddtsqpYXGxA29nj9V5V9SfC+GyOG00l650f6lG9KL+EpFEWQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.4.tgz", + "integrity": "sha512-j8yRSSqswWi1QqUGKVEKOG03Q7qOoZP6/h2zN2YO+F5h2+DHU0bSrHCK9Y7lo2DI9fBd8qGAw795sf+3Jva4yA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/storage": { + "version": "5.20.5", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.20.5.tgz", + "integrity": "sha512-lOs/dCyveVF8TkVFnFSF7IGd0CJrTm91qiK6JLu+Z8qiT+7Ag0RyVhxZIWkhiACqwABo7kSHDm8FdH8p2wxSSw==", + "license": "Apache-2.0", + "dependencies": { + "@google-cloud/paginator": "^3.0.7", + "@google-cloud/projectify": "^2.0.0", + "@google-cloud/promisify": "^2.0.0", + "abort-controller": "^3.0.0", + "arrify": "^2.0.0", + "async-retry": "^1.3.3", + "compressible": "^2.0.12", + "configstore": "^5.0.0", + "duplexify": "^4.0.0", + "ent": "^2.2.0", + "extend": "^3.0.2", + "gaxios": "^4.0.0", + "google-auth-library": "^7.14.1", + "hash-stream-validation": "^0.2.2", + "mime": "^3.0.0", + "mime-types": "^2.0.8", + "p-limit": "^3.0.1", + "pumpify": "^2.0.0", + "retry-request": "^4.2.2", + "stream-events": "^1.0.4", + "teeny-request": "^7.1.3", + "uuid": "^8.0.0", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.0.5.tgz", + "integrity": "sha512-xY6b0XiL0Nav3ReresUarwl2oIz1gTnxGbGpho9/rbUWsLH0f1OD/VT84xs8c7VmH7MChnLb0pag6PhZhAdDiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.5", + "@types/node": "*", + "chalk": "^4.1.2", + "jest-message-util": "30.0.5", + "jest-util": "30.0.5", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.0.5.tgz", + "integrity": "sha512-fKD0OulvRsXF1hmaFgHhVJzczWzA1RXMMo9LTPuFXo9q/alDbME3JIyWYqovWsUBWSoBcsHaGPSLF9rz4l9Qeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.0.5", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.0.5", + "jest-config": "30.0.5", + "jest-haste-map": "30.0.5", + "jest-message-util": "30.0.5", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.0.5", + "jest-resolve-dependencies": "30.0.5", + "jest-runner": "30.0.5", + "jest-runtime": "30.0.5", + "jest-snapshot": "30.0.5", + "jest-util": "30.0.5", + "jest-validate": "30.0.5", + "jest-watcher": "30.0.5", + "micromatch": "^4.0.8", + "pretty-format": "30.0.5", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.0.5.tgz", + "integrity": "sha512-aRX7WoaWx1oaOkDQvCWImVQ8XNtdv5sEWgk4gxR6NXb7WBUnL5sRak4WRzIQRZ1VTWPvV4VI4mgGjNL9TeKMYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "30.0.5", + "@jest/types": "30.0.5", + "@types/node": "*", + "jest-mock": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.0.5.tgz", + "integrity": "sha512-6udac8KKrtTtC+AXZ2iUN/R7dp7Ydry+Fo6FPFnDG54wjVMnb6vW/XNlf7Xj8UDjAE3aAVAsR4KFyKk3TCXmTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "30.0.5", + "jest-snapshot": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.5.tgz", + "integrity": "sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.0.5.tgz", + "integrity": "sha512-ZO5DHfNV+kgEAeP3gK3XlpJLL4U3Sz6ebl/n68Uwt64qFFs5bv4bfEEjyRGK5uM0C90ewooNgFuKMdkbEoMEXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.5", + "@sinonjs/fake-timers": "^13.0.0", + "@types/node": "*", + "jest-message-util": "30.0.5", + "jest-mock": "30.0.5", + "jest-util": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.0.1.tgz", + "integrity": "sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.0.5.tgz", + "integrity": "sha512-7oEJT19WW4oe6HR7oLRvHxwlJk2gev0U9px3ufs8sX9PoD1Eza68KF0/tlN7X0dq/WVsBScXQGgCldA1V9Y/jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.0.5", + "@jest/expect": "30.0.5", + "@jest/types": "30.0.5", + "jest-mock": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.0.5.tgz", + "integrity": "sha512-mafft7VBX4jzED1FwGC1o/9QUM2xebzavImZMeqnsklgcyxBto8mV4HzNSzUrryJ+8R9MFOM3HgYuDradWR+4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", + "@jridgewell/trace-mapping": "^0.3.25", + "@types/node": "*", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^5.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "30.0.5", + "jest-util": "30.0.5", + "jest-worker": "30.0.5", + "slash": "^3.0.0", + "string-length": "^4.0.2", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/reporters/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/reporters/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.0.5.tgz", + "integrity": "sha512-XcCQ5qWHLvi29UUrowgDFvV4t7ETxX91CbDczMnoqXPOIcZOxyNdSjm6kV5XMc8+HkxfRegU/MUmnTbJRzGrUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.5", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.0.5.tgz", + "integrity": "sha512-wPyztnK0gbDMQAJZ43tdMro+qblDHH1Ru/ylzUo21TBKqt88ZqnKKK2m30LKmLLoKtR2lxdpCC/P3g1vfKcawQ==", + "dev": true, "license": "MIT", "dependencies": { - "@bugsnag/browser": "^7.25.0", - "@bugsnag/node": "^7.25.0" + "@jest/console": "30.0.5", + "@jest/types": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@bugsnag/node": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@bugsnag/node/-/node-7.25.0.tgz", - "integrity": "sha512-KlxBaJ8EREEsfKInybAjTO9LmdDXV3cUH5+XNXyqUZrcRVuPOu4j4xvljh+n24ifok/wbFZTKVXUzrN4iKIeIA==", + "node_modules/@jest/test-sequencer": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.0.5.tgz", + "integrity": "sha512-Aea/G1egWoIIozmDD7PBXUOxkekXl7ueGzrsGGi1SbeKgQqCYCIf+wfbflEbf2LiPxL8j2JZGLyrzZagjvW4YQ==", + "dev": true, "license": "MIT", "dependencies": { - "@bugsnag/core": "^7.25.0", - "byline": "^5.0.0", - "error-stack-parser": "^2.0.2", - "iserror": "^0.0.2", - "pump": "^3.0.0", - "stack-generator": "^2.0.3" + "@jest/test-result": "30.0.5", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.0.5", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@bugsnag/plugin-express": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@bugsnag/plugin-express/-/plugin-express-7.25.0.tgz", - "integrity": "sha512-X3JhhfYet7MhOGmW59p4+R2N122VQruljysAwl1p9J0jp1eWmslKtsuPcVTjI08+SSSn1iC67kJ3F6MhDMt7/A==", + "node_modules/@jest/transform": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.0.5.tgz", + "integrity": "sha512-Vk8amLQCmuZyy6GbBht1Jfo9RSdBtg7Lks+B0PecnjI8J+PCLQPGh7uI8Q/2wwpW2gLdiAfiHNsmekKlywULqg==", + "dev": true, "license": "MIT", "dependencies": { - "iserror": "^0.0.2" + "@babel/core": "^7.27.4", + "@jest/types": "30.0.5", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.0", + "chalk": "^4.1.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.0.5", + "jest-regex-util": "30.0.1", + "jest-util": "30.0.5", + "micromatch": "^4.0.8", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" }, - "peerDependencies": { - "@bugsnag/core": "^7.0.0" + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@bugsnag/safe-json-stringify": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@bugsnag/safe-json-stringify/-/safe-json-stringify-6.0.0.tgz", - "integrity": "sha512-htzFO1Zc57S8kgdRK9mLcPVTW1BY2ijfH7Dk2CeZmspTWKdKqSo1iwmqrq2WtRjFlo8aRZYgLX0wFrDXF/9DLA==", - "license": "MIT" + "node_modules/@jest/transform/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "node_modules/@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==", + "node_modules/@jest/transform/node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" }, "engines": { - "node": ">=12" + "node": "^14.17.0 || ^16.13.0 || >=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==", + "node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@emnapi/core": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", - "integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.0.4", - "tslib": "^2.4.0" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@emnapi/runtime": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", - "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "tslib": "^2.4.0" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz", - "integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==", + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "tslib": "^2.4.0" + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", - "integrity": "sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==", - "dev": true, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", "license": "MIT", + "optional": true, "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "lodash": "^4.17.20", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=10" } }, - "node_modules/@eslint/eslintrc/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, + "optional": true, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=14" } }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "dev": true, "license": "MIT", - "dependencies": { - "type-fest": "^0.8.1" - }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/pkgr" } }, - "node_modules/@eslint/eslintrc/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "node_modules/@sinclair/typebox": { + "version": "0.34.38", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.38.tgz", + "integrity": "sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==", "dev": true, "license": "MIT" }, - "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" } }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" } }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "license": "MIT", - "optional": true - }, - "node_modules/@google-cloud/paginator": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.7.tgz", - "integrity": "sha512-jJNutk0arIQhmpUUQJPJErsojqo834KcyB6X7a1mxuic8i1tKXxde8E69IZxNZawRIlZdIK2QY4WALvlK5MzYQ==", + "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": { - "arrify": "^2.0.0", - "extend": "^3.0.2" + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=10" + "node": ">=18.0.0" } }, - "node_modules/@google-cloud/projectify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.1.1.tgz", - "integrity": "sha512-+rssMZHnlh0twl122gXY4/aCrk0G1acBqkHFfYddtsqpYXGxA29nj9V5V9SfC+GyOG00l650f6lG9KL+EpFEWQ==", + "node_modules/@smithy/chunked-blob-reader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.0.0.tgz", + "integrity": "sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==", "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=10" + "node": ">=18.0.0" } }, - "node_modules/@google-cloud/promisify": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.4.tgz", - "integrity": "sha512-j8yRSSqswWi1QqUGKVEKOG03Q7qOoZP6/h2zN2YO+F5h2+DHU0bSrHCK9Y7lo2DI9fBd8qGAw795sf+3Jva4yA==", + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.0.0.tgz", + "integrity": "sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==", "license": "Apache-2.0", + "dependencies": { + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=10" + "node": ">=18.0.0" } }, - "node_modules/@google-cloud/storage": { - "version": "5.20.5", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.20.5.tgz", - "integrity": "sha512-lOs/dCyveVF8TkVFnFSF7IGd0CJrTm91qiK6JLu+Z8qiT+7Ag0RyVhxZIWkhiACqwABo7kSHDm8FdH8p2wxSSw==", + "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": { - "@google-cloud/paginator": "^3.0.7", - "@google-cloud/projectify": "^2.0.0", - "@google-cloud/promisify": "^2.0.0", - "abort-controller": "^3.0.0", - "arrify": "^2.0.0", - "async-retry": "^1.3.3", - "compressible": "^2.0.12", - "configstore": "^5.0.0", - "duplexify": "^4.0.0", - "ent": "^2.2.0", - "extend": "^3.0.2", - "gaxios": "^4.0.0", - "google-auth-library": "^7.14.1", - "hash-stream-validation": "^0.2.2", - "mime": "^3.0.0", - "mime-types": "^2.0.8", - "p-limit": "^3.0.1", - "pumpify": "^2.0.0", - "retry-request": "^4.2.2", - "stream-events": "^1.0.4", - "teeny-request": "^7.1.3", - "uuid": "^8.0.0", - "xdg-basedir": "^4.0.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": ">=10" + "node": ">=18.0.0" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", + "node_modules/@smithy/core": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.2.tgz", + "integrity": "sha512-JoLw59sT5Bm8SAjFCYZyuCGxK8y3vovmoVbZWLDPTH5XpPEIwpFd9m90jjVMwoypDuB/SdVgje5Y4T7w50lJaw==", + "license": "Apache-2.0", "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" + "@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.3", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=12" + "node": ">=18.0.0" } }, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" + "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" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "engines": { + "node": ">=18.0.0" } }, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" + "node_modules/@smithy/eventstream-codec": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.0.4.tgz", + "integrity": "sha512-7XoWfZqWb/QoR/rAU4VSi0mWnO2vu9/ltS6JZ5ZSZv0eovLVfDfu0/AX4ub33RsJTOth3TiFWSHS5YdztvFnig==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.3.1", + "@smithy/util-hex-encoding": "^4.0.0", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.0.4.tgz", + "integrity": "sha512-3fb/9SYaYqbpy/z/H3yIi0bYKyAa89y6xPmIqwr2vQiUT2St+avRt8UKwsWt9fEdEasc5d/V+QjrviRaX1JRFA==", + "license": "Apache-2.0", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "@smithy/eventstream-serde-universal": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18.0.0" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.1.2.tgz", + "integrity": "sha512-JGtambizrWP50xHgbzZI04IWU7LdI0nh/wGbqH3sJesYToMi2j/DcoElqyOcqEIG/D4tNyxgRuaqBXWE3zOFhQ==", + "license": "Apache-2.0", "dependencies": { - "ansi-regex": "^6.0.1" + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">=18.0.0" } }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.0.4.tgz", + "integrity": "sha512-RD6UwNZ5zISpOWPuhVgRz60GkSIp0dy1fuZmj4RYmqLVRtejFqQ16WmfYDdoSoAjlp1LX+FnZo+/hkdmyyGZ1w==", + "license": "Apache-2.0", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "@smithy/eventstream-serde-universal": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=18.0.0" } }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "license": "ISC", + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.0.4.tgz", + "integrity": "sha512-UeJpOmLGhq1SLox79QWw/0n2PFX+oPRE1ZyRMxPIaFEfCqWaqpB7BU9C8kpPOGEhLF7AwEqfFbtwNxGy4ReENA==", + "license": "Apache-2.0", "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" + "@smithy/eventstream-codec": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=8" + "node": ">=18.0.0" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/fetch-http-handler": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.1.0.tgz", + "integrity": "sha512-mADw7MS0bYe2OGKkHYMaqarOXuDwRbO6ArD91XhHcl2ynjGCFF+hvqf0LyQcYxkA1zaWjefSkU7Ne9mqgApSgQ==", + "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": ">=8" + "node": ">=18.0.0" } }, - "node_modules/@jest/console": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.0.5.tgz", - "integrity": "sha512-xY6b0XiL0Nav3ReresUarwl2oIz1gTnxGbGpho9/rbUWsLH0f1OD/VT84xs8c7VmH7MChnLb0pag6PhZhAdDiA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/hash-blob-browser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.0.4.tgz", + "integrity": "sha512-WszRiACJiQV3QG6XMV44i5YWlkrlsM5Yxgz4jvsksuu7LDXA6wAtypfPajtNTadzpJy3KyJPoWehYpmZGKUFIQ==", + "license": "Apache-2.0", "dependencies": { - "@jest/types": "30.0.5", - "@types/node": "*", - "chalk": "^4.1.2", - "jest-message-util": "30.0.5", - "jest-util": "30.0.5", - "slash": "^3.0.0" + "@smithy/chunked-blob-reader": "^5.0.0", + "@smithy/chunked-blob-reader-native": "^4.0.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/core": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.0.5.tgz", - "integrity": "sha512-fKD0OulvRsXF1hmaFgHhVJzczWzA1RXMMo9LTPuFXo9q/alDbME3JIyWYqovWsUBWSoBcsHaGPSLF9rz4l9Qeg==", - "dev": true, - "license": "MIT", + "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": { - "@jest/console": "30.0.5", - "@jest/pattern": "30.0.1", - "@jest/reporters": "30.0.5", - "@jest/test-result": "30.0.5", - "@jest/transform": "30.0.5", - "@jest/types": "30.0.5", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-changed-files": "30.0.5", - "jest-config": "30.0.5", - "jest-haste-map": "30.0.5", - "jest-message-util": "30.0.5", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.0.5", - "jest-resolve-dependencies": "30.0.5", - "jest-runner": "30.0.5", - "jest-runtime": "30.0.5", - "jest-snapshot": "30.0.5", - "jest-util": "30.0.5", - "jest-validate": "30.0.5", - "jest-watcher": "30.0.5", - "micromatch": "^4.0.8", - "pretty-format": "30.0.5", - "slash": "^3.0.0" + "@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/hash-stream-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.0.4.tgz", + "integrity": "sha512-wHo0d8GXyVmpmMh/qOR0R7Y46/G1y6OR8U+bSTB4ppEzRxd1xVAQ9xOE9hOc0bSjhz0ujCPAbfNLkLrpa6cevg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": ">=18.0.0" } }, - "node_modules/@jest/diff-sequences": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", - "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", - "dev": true, - "license": "MIT", + "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.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/environment": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.0.5.tgz", - "integrity": "sha512-aRX7WoaWx1oaOkDQvCWImVQ8XNtdv5sEWgk4gxR6NXb7WBUnL5sRak4WRzIQRZ1VTWPvV4VI4mgGjNL9TeKMYA==", - "dev": true, - "license": "MIT", + "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": { - "@jest/fake-timers": "30.0.5", - "@jest/types": "30.0.5", - "@types/node": "*", - "jest-mock": "30.0.5" + "tslib": "^2.6.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/expect": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.0.5.tgz", - "integrity": "sha512-6udac8KKrtTtC+AXZ2iUN/R7dp7Ydry+Fo6FPFnDG54wjVMnb6vW/XNlf7Xj8UDjAE3aAVAsR4KFyKk3TCXmTA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/md5-js": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.0.4.tgz", + "integrity": "sha512-uGLBVqcOwrLvGh/v/jw423yWHq/ofUGK1W31M2TNspLQbUV1Va0F5kTxtirkoHawODAZcjXTSGi7JwbnPcDPJg==", + "license": "Apache-2.0", "dependencies": { - "expect": "30.0.5", - "jest-snapshot": "30.0.5" + "@smithy/types": "^4.3.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/expect-utils": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.5.tgz", - "integrity": "sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew==", - "dev": true, - "license": "MIT", + "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": { - "@jest/get-type": "30.0.1" + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/fake-timers": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.0.5.tgz", - "integrity": "sha512-ZO5DHfNV+kgEAeP3gK3XlpJLL4U3Sz6ebl/n68Uwt64qFFs5bv4bfEEjyRGK5uM0C90ewooNgFuKMdkbEoMEXw==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/middleware-endpoint": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.17.tgz", + "integrity": "sha512-S3hSGLKmHG1m35p/MObQCBCdRsrpbPU8B129BVzRqRfDvQqPMQ14iO4LyRw+7LNizYc605COYAcjqgawqi+6jA==", + "license": "Apache-2.0", "dependencies": { - "@jest/types": "30.0.5", - "@sinonjs/fake-timers": "^13.0.0", - "@types/node": "*", - "jest-message-util": "30.0.5", - "jest-mock": "30.0.5", - "jest-util": "30.0.5" + "@smithy/core": "^3.7.2", + "@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.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/get-type": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.0.1.tgz", - "integrity": "sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/middleware-retry": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.18.tgz", + "integrity": "sha512-bYLZ4DkoxSsPxpdmeapvAKy7rM5+25gR7PGxq2iMiecmbrRGBHj9s75N74Ylg+aBiw9i5jIowC/cLU2NR0qH8w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/service-error-classification": "^4.0.6", + "@smithy/smithy-client": "^4.4.9", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/globals": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.0.5.tgz", - "integrity": "sha512-7oEJT19WW4oe6HR7oLRvHxwlJk2gev0U9px3ufs8sX9PoD1Eza68KF0/tlN7X0dq/WVsBScXQGgCldA1V9Y/jA==", - "dev": true, + "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": { - "@jest/environment": "30.0.5", - "@jest/expect": "30.0.5", - "@jest/types": "30.0.5", - "jest-mock": "30.0.5" + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/pattern": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", - "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", - "dev": true, - "license": "MIT", + "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": { - "@types/node": "*", - "jest-regex-util": "30.0.1" + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/reporters": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.0.5.tgz", - "integrity": "sha512-mafft7VBX4jzED1FwGC1o/9QUM2xebzavImZMeqnsklgcyxBto8mV4HzNSzUrryJ+8R9MFOM3HgYuDradWR+4g==", - "dev": true, - "license": "MIT", + "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": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.0.5", - "@jest/test-result": "30.0.5", - "@jest/transform": "30.0.5", - "@jest/types": "30.0.5", - "@jridgewell/trace-mapping": "^0.3.25", - "@types/node": "*", - "chalk": "^4.1.2", - "collect-v8-coverage": "^1.0.2", - "exit-x": "^0.2.2", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^5.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "30.0.5", - "jest-util": "30.0.5", - "jest-worker": "30.0.5", - "slash": "^3.0.0", - "string-length": "^4.0.2", - "v8-to-istanbul": "^9.0.1" + "@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.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.1.0.tgz", + "integrity": "sha512-vqfSiHz2v8b3TTTrdXi03vNz1KLYYS3bhHCDv36FYDqxT7jvTll1mMnCrkD+gOvgwybuunh/2VmvOMqwBegxEg==", + "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" }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "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" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@jest/reporters/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", + "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": { - "balanced-match": "^1.0.0" + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@jest/reporters/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", + "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": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "@smithy/types": "^4.3.1", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "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" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@jest/reporters/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", + "node_modules/@smithy/service-error-classification": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.6.tgz", + "integrity": "sha512-RRoTDL//7xi4tn5FrN2NzH17jbgmnKidUqd4KvquT0954/i6CXXkh1884jBiunq24g9cGtPBEXlU40W6EpNOOg==", + "license": "Apache-2.0", "dependencies": { - "brace-expansion": "^2.0.1" + "@smithy/types": "^4.3.1" }, "engines": { - "node": ">=16 || 14 >=14.17" + "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" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@jest/reporters/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", + "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": ">=16 || 14 >=14.17" + "node": ">=18.0.0" } }, - "node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/smithy-client": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.9.tgz", + "integrity": "sha512-mbMg8mIUAWwMmb74LoYiArP04zWElPzDoA1jVOp3or0cjlDMgoS6WTC3QXK0Vxoc9I4zdrX0tq6qsOmaIoTWEQ==", + "license": "Apache-2.0", "dependencies": { - "@sinclair/typebox": "^0.34.0" + "@smithy/core": "^3.7.2", + "@smithy/middleware-endpoint": "^4.1.17", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.3", + "tslib": "^2.6.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/snapshot-utils": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.0.5.tgz", - "integrity": "sha512-XcCQ5qWHLvi29UUrowgDFvV4t7ETxX91CbDczMnoqXPOIcZOxyNdSjm6kV5XMc8+HkxfRegU/MUmnTbJRzGrUQ==", - "dev": true, - "license": "MIT", + "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": { - "@jest/types": "30.0.5", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "natural-compare": "^1.4.0" + "tslib": "^2.6.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/source-map": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", - "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", - "dev": true, - "license": "MIT", + "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": { - "@jridgewell/trace-mapping": "^0.3.25", - "callsites": "^3.1.0", - "graceful-fs": "^4.2.11" + "@smithy/querystring-parser": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/test-result": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.0.5.tgz", - "integrity": "sha512-wPyztnK0gbDMQAJZ43tdMro+qblDHH1Ru/ylzUo21TBKqt88ZqnKKK2m30LKmLLoKtR2lxdpCC/P3g1vfKcawQ==", - "dev": true, - "license": "MIT", + "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": { - "@jest/console": "30.0.5", - "@jest/types": "30.0.5", - "@types/istanbul-lib-coverage": "^2.0.6", - "collect-v8-coverage": "^1.0.2" + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/test-sequencer": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.0.5.tgz", - "integrity": "sha512-Aea/G1egWoIIozmDD7PBXUOxkekXl7ueGzrsGGi1SbeKgQqCYCIf+wfbflEbf2LiPxL8j2JZGLyrzZagjvW4YQ==", - "dev": true, - "license": "MIT", + "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": { - "@jest/test-result": "30.0.5", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.5", - "slash": "^3.0.0" + "tslib": "^2.6.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/transform": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.0.5.tgz", - "integrity": "sha512-Vk8amLQCmuZyy6GbBht1Jfo9RSdBtg7Lks+B0PecnjI8J+PCLQPGh7uI8Q/2wwpW2gLdiAfiHNsmekKlywULqg==", - "dev": true, - "license": "MIT", + "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": { - "@babel/core": "^7.27.4", - "@jest/types": "30.0.5", - "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.0", - "chalk": "^4.1.2", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.5", - "jest-regex-util": "30.0.1", - "jest-util": "30.0.5", - "micromatch": "^4.0.8", - "pirates": "^4.0.7", - "slash": "^3.0.0", - "write-file-atomic": "^5.0.1" + "tslib": "^2.6.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/transform/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" + "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" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@jest/transform/node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "dev": true, - "license": "ISC", + "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": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" + "tslib": "^2.6.2" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/types": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", - "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.25", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.25.tgz", + "integrity": "sha512-pxEWsxIsOPLfKNXvpgFHBGFC3pKYKUFhrud1kyooO9CJai6aaKDHfT10Mi5iiipPXN/JhKAu3qX9o75+X85OdQ==", + "license": "Apache-2.0", "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.5", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.9", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.25", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.25.tgz", + "integrity": "sha512-+w4n4hKFayeCyELZLfsSQG5mCC3TwSkmRHv4+el5CzFU8ToQpYGhpV7mrRzqlwKkntlPilT1HJy1TVeEvEjWOQ==", + "license": "Apache-2.0", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", + "@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.9", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "dev": true, - "license": "MIT", + "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": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", - "dev": true, - "license": "MIT", - "optional": true, + "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": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@npmcli/fs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", - "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", - "license": "ISC", - "optional": true, + "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": { - "@gar/promisify": "^1.0.1", - "semver": "^7.3.5" + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "license": "MIT", - "optional": true, + "node_modules/@smithy/util-retry": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.6.tgz", + "integrity": "sha512-+YekoF2CaSMv6zKrA6iI/N9yva3Gzn4L6n35Luydweu5MMPYpiGZlWqehPHDHyNbnyaYlz/WJyYAZnC+loBDZg==", + "license": "Apache-2.0", "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" + "@smithy/service-error-classification": "^4.0.6", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=10" + "node": ">=18.0.0" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, + "node_modules/@smithy/util-stream": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.3.tgz", + "integrity": "sha512-cQn412DWHHFNKrQfbHY8vSFI3nTROY1aIKji9N0tpp8gUABRilr7wdf8fqBbSlXresobM+tQFNk6I+0LXK/YZg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/node-http-handler": "^4.1.0", + "@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" + "node": ">=18.0.0" } }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.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://opencollective.com/pkgr" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@sinclair/typebox": { - "version": "0.34.38", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.38.tgz", - "integrity": "sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", + "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": { - "type-detect": "4.0.8" + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", - "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/@smithy/util-waiter": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.0.6.tgz", + "integrity": "sha512-slcr1wdRbX7NFphXZOxtxRNA7hXAAtJAXJDE/wdoMAos27SIquVCKiSqfB6/28YzQ8FCsB5NKkhdM5gMADbqxg==", + "license": "Apache-2.0", "dependencies": { - "@sinonjs/commons": "^3.0.1" + "@smithy/abort-controller": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@tootallnate/once": { @@ -2487,6 +4179,12 @@ "dev": true, "license": "MIT" }, + "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/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -3417,6 +5115,12 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -5349,6 +7053,15 @@ "node": ">=6" } }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -5506,6 +7219,24 @@ ], "license": "BSD-3-Clause" }, + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -10863,6 +12594,16 @@ "node": ">= 0.8" } }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, "node_modules/stream-events": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", @@ -11075,6 +12816,18 @@ "node": ">=0.10.0" } }, + "node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/stubs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", @@ -11527,9 +13280,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true + "license": "0BSD" }, "node_modules/tunnel-agent": { "version": "0.6.0", diff --git a/package.json b/package.json index 56b8905..c514ffb 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,10 @@ "author": "BSV Association", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { + "@aws-sdk/client-s3": "^3.0.0", + "@aws-sdk/lib-storage": "^3.0.0", + "@aws-sdk/s3-presigned-post": "^3.850.0", + "@aws-sdk/s3-request-presigner": "^3.0.0", "@bsv/auth-express-middleware": "^1.2.2", "@bsv/payment-express-middleware": "^1.2.3", "@bsv/sdk": "^1.6.20", diff --git a/src/routes/advertise.ts b/src/routes/advertise.ts index bb3b88f..946541b 100644 --- a/src/routes/advertise.ts +++ b/src/routes/advertise.ts @@ -1,16 +1,13 @@ -import { Storage } from '@google-cloud/storage'; import createUHRPAdvertisement from '../utils/createUHRPAdvertisement'; import { Request, Response } from 'express'; import { StorageUtils } from '@bsv/sdk'; +import { getStorageProvider } from '../utils/storage'; const { ADMIN_TOKEN, - HOSTING_DOMAIN, - GCP_BUCKET_NAME + HOSTING_DOMAIN } = process.env -const storage = new Storage() - interface AdvertiseRequest extends Request { body: { adminToken: string @@ -49,12 +46,12 @@ const advertiseHandler = async (req: AdvertiseRequest, res: Response) => await broadcaster.broadcast(Transaction.fromAtomicBEEF(tx)) - // Setting the new expiry time in the actual database - await storage.bucket(GCP_BUCKET_NAME).file(`cdn/${objectIdentifier}`) - .setMetadata({ customTime: newCustomTimeIso }) + // Setting the new expiry time in the storage provider + const storageProvider = getStorageProvider() + const objectKey = `cdn/${objectIdentifier}` + await storageProvider.updateObjectMetadata(objectKey, { customTime: newCustomTimeIso }) return res.status(200).json({ status: 'success', diff --git a/src/routes/upload.ts b/src/routes/upload.ts index a454298..e9b6d2b 100644 --- a/src/routes/upload.ts +++ b/src/routes/upload.ts @@ -21,6 +21,7 @@ interface UploadResponse { uploadURL?: string publicURL?: string requiredHeaders?: Record + formFields?: Record amount?: number code?: string description?: string @@ -71,7 +72,7 @@ export async function uploadHandler(req: UploadRequest, res: Response -} +const { NODE_ENV } = process.env const devUploadFunction = (): Promise => { console.log('[DEV] Returning pretend upload URL http://localhost:8080/upload') - return Promise.resolve({ uploadURL: 'http://localhost:8080/upload', requiredHeaders: {} }) + return Promise.resolve({ + uploadURL: 'http://localhost:8080/upload', + requiredHeaders: {}, + formFields: undefined + }) } /** - * Creates a V4 signed URL for uploading an object to Google Cloud Storage. + * Creates a signed URL for uploading an object to the configured storage provider. * The signed URL includes metadata headers that must be provided by the client. * * @param {UploadParams} params - Parameters for file upload. * @returns {Promise} - The signed upload URL. * - * Note: Although we include the metadata (uploaderIdentityKey and custom time) in the signed URL, - * the client must include these headers in the PUT request. GCS requires that signed headers - * be present on the request. There is no way to force these headers solely via the URL. + * Note: The client must include the required headers in the PUT request. + * Different storage providers have different header requirements. */ -const prodUploadFunction = async ({ - size, - expiryTime, - objectIdentifier, - uploaderIdentityKey -}: UploadParams): Promise => { - if (!GCP_BUCKET_NAME || !GCP_PROJECT_ID) { - throw new Error('Missing required Google Cloud Storage environment variables.') - } - const serviceKey = path.join(__dirname, '../../storage-creds.json') - const storage = new Storage({ - keyFilename: serviceKey, - projectId: GCP_PROJECT_ID - }) - - const bucket = storage.bucket(GCP_BUCKET_NAME) - const bucketFile = bucket.file(`cdn/${objectIdentifier}`) - - // Calculate the custom time (e.g., expiry time plus 5 minutes in this example) - const customTime = new Date((expiryTime + 300) * 1000).toISOString() - - // Generate the signed URL including the metadata headers. - // The extensionHeaders are part of the signature and must be included by the client in the PUT request. - const [uploadURL] = await bucketFile.getSignedUrl({ - version: 'v4', - action: 'write', - expires: Date.now() + 604000 * 1000, // 1 week - extensionHeaders: { - 'content-length': size.toString(), - 'x-goog-meta-uploaderidentitykey': uploaderIdentityKey, - 'x-goog-custom-time': customTime - } - }) - - return { - uploadURL, - requiredHeaders: { - 'content-length': size.toString(), - 'x-goog-meta-uploaderidentitykey': uploaderIdentityKey, - 'x-goog-custom-time': customTime - } - } +const prodUploadFunction = async (params: UploadParams): Promise => { + const storage = getStorageProvider() + return storage.generateUploadURL(params) } export default NODE_ENV === 'development' ? devUploadFunction : prodUploadFunction \ No newline at end of file diff --git a/src/utils/storage/README.md b/src/utils/storage/README.md new file mode 100644 index 0000000..0c09f51 --- /dev/null +++ b/src/utils/storage/README.md @@ -0,0 +1,96 @@ +# Storage Abstraction Layer + +This storage abstraction layer provides a unified interface for working with multiple cloud storage providers (currently AWS S3 and Google Cloud Storage). + +## Configuration + +Set the `STORAGE_PROVIDER` environment variable to choose your storage backend: +- `aws` - Amazon S3 +- `gcs` - Google Cloud Storage + +## Usage + +```typescript +import { getStorageProvider } from './utils/storage' + +// Get the configured storage provider +const storage = getStorageProvider() + +// Generate upload URL +const { uploadURL, requiredHeaders } = await storage.generateUploadURL({ + size: 1024, + expiryTime: Math.floor(Date.now() / 1000) + 3600, + objectIdentifier: 'unique-id', + uploaderIdentityKey: 'user-key' +}) + +// Get object metadata +const metadata = await storage.getObjectMetadata('cdn/object-id') + +// Update object metadata +await storage.updateObjectMetadata('cdn/object-id', { + customTime: new Date().toISOString() +}) + +// Check if object exists +const exists = await storage.objectExists('cdn/object-id') + +// Generate download URL +const downloadUrl = await storage.generateDownloadURL('cdn/object-id', 3600) +``` + +## Environment Variables + +### Common Variables +- `STORAGE_PROVIDER` - Choose between 'aws' or 'gcs' +- `STORAGE_BUCKET_NAME` - Bucket name (works for both providers) + +### AWS S3 Configuration +- `AWS_REGION` - AWS region (default: us-west-2) +- `AWS_BUCKET_NAME` - S3 bucket name (fallback if STORAGE_BUCKET_NAME not set) +- AWS credentials are automatically loaded from IAM roles or AWS CLI configuration + +### Google Cloud Storage Configuration +- `GCP_PROJECT_ID` - Google Cloud project ID +- `GCP_BUCKET_NAME` - GCS bucket name (fallback if STORAGE_BUCKET_NAME not set) +- Credentials file should be at `./storage-creds.json` + +## Adding New Storage Providers + +To add a new storage provider: + +1. Create a new provider class in `providers/` that implements the `StorageProvider` interface +2. Add the provider type to `StorageProviderType` in `types.ts` +3. Update the factory in `index.ts` to instantiate your provider + +Example: +```typescript +export class AzureStorageProvider implements StorageProvider { + // Implement all required methods +} +``` + +## Migration Guide + +### From Direct GCS Usage + +Before: +```typescript +const storage = new Storage() +const bucket = storage.bucket(GCP_BUCKET_NAME) +const file = bucket.file('cdn/file-id') +``` + +After: +```typescript +const storage = getStorageProvider() +const metadata = await storage.getObjectMetadata('cdn/file-id') +``` + +### Environment Variable Changes + +The system supports both old and new environment variable names for backward compatibility: +- `GCP_BUCKET_NAME` → `STORAGE_BUCKET_NAME` +- `AWS_BUCKET_NAME` → `STORAGE_BUCKET_NAME` + +Set `STORAGE_PROVIDER` to choose which backend to use. \ No newline at end of file diff --git a/src/utils/storage/S3-METADATA-HANDLING.md b/src/utils/storage/S3-METADATA-HANDLING.md new file mode 100644 index 0000000..4715fe6 --- /dev/null +++ b/src/utils/storage/S3-METADATA-HANDLING.md @@ -0,0 +1,117 @@ +# S3 Metadata Handling + +## The Solution + +S3 presigned URLs handle metadata differently than Google Cloud Storage, but we can achieve the same result using **POST presigned URLs** instead of PUT presigned URLs. + +1. **GCS**: Can include custom headers in signed URLs that are automatically applied +2. **S3 PUT**: Requires exact headers to be sent by the client if they're part of the signature (causes 403 errors) +3. **S3 POST**: Supports form fields including metadata without signature issues + +## Previous Challenge (Now Resolved) + +The old approach using PUT presigned URLs caused this error: +``` + + AccessDenied + There were headers present in the request which were not signed + x-amz-meta-customtime, x-amz-meta-uploaderidentitykey + +``` + +## Our Current Solution + +We use **POST presigned URLs with form fields** for S3: + +### Single-Phase Upload (via POST presigned URL) +- Generate a POST presigned URL with form fields including metadata +- Client uploads the file using `multipart/form-data` POST request +- Metadata is included directly in the form fields: + - `x-amz-meta-uploaderidentitykey`: Identity of uploader + - `x-amz-meta-customtime`: Expiry time + 5 minutes +- S3 event handler can process file immediately (like GCS) + +## Code Flow + +1. **Client requests upload URL**: + ```javascript + POST /upload + { + "fileSize": 1024, + "retentionPeriod": 60 + } + ``` + +2. **Server returns POST presigned URL** (S3 version): + ```javascript + { + "uploadURL": "https://bucket.s3.amazonaws.com/", + "requiredHeaders": { + "content-type": "multipart/form-data" + }, + "formFields": { + "key": "cdn/object-identifier", + "x-amz-meta-uploaderidentitykey": "uploader-key", + "x-amz-meta-customtime": "2024-01-01T00:00:00.000Z", + "policy": "...", + "x-amz-algorithm": "AWS4-HMAC-SHA256", + "x-amz-credential": "...", + "x-amz-date": "...", + "x-amz-signature": "..." + } + } + ``` + +3. **Client uploads file using POST**: + ```javascript + POST [uploadURL] + Content-Type: multipart/form-data + + FormData: { + ...formFields, + "file": [file data] + } + ``` + +4. **S3 event handler processes file automatically**: + - File uploaded with metadata already present + - Event handler finds `uploaderidentitykey` in metadata + - Calls `/advertise` endpoint automatically + - Creates UHRP advertisement + +## Comparison with GCS + +### GCS Approach: +- Metadata included in signed PUT URL headers +- Single-step process +- Client must send exact headers + +### S3 Approach (Current): +- Metadata included in POST form fields +- Single-step process (like GCS!) +- Client uploads using multipart/form-data +- S3 event handler processes automatically + +## Benefits + +1. **GCS-like Behavior**: Metadata available immediately after upload +2. **Automatic Processing**: S3 event handler can process files without manual `/advertise` calls +3. **No 403 Errors**: POST presigned URLs avoid signature mismatch issues +4. **Consistent Workflow**: Both GCS and S3 now follow the same pattern + +## Technical Details + +### POST vs PUT Presigned URLs + +- **PUT Presigned URLs**: Require exact header matching, causing 403 errors with metadata +- **POST Presigned URLs**: Use form fields, avoiding signature validation issues + +### Client Implementation + +Clients now need to: +1. Use POST instead of PUT +2. Set `Content-Type: multipart/form-data` +3. Include all form fields from the response +4. Add the file as a form field named "file" + +This approach successfully replicates GCS behavior while working within S3's constraints. \ No newline at end of file diff --git a/src/utils/storage/index.ts b/src/utils/storage/index.ts new file mode 100644 index 0000000..60c8cad --- /dev/null +++ b/src/utils/storage/index.ts @@ -0,0 +1,40 @@ +import { StorageProvider, StorageProviderType } from './types' +import { GCSStorageProvider } from './providers/gcs' +import { S3StorageProvider } from './providers/s3' + +let storageProvider: StorageProvider | null = null + +/** + * Get the configured storage provider instance + * Uses singleton pattern to avoid re-initializing the provider + */ +export function getStorageProvider(): StorageProvider { + if (!storageProvider) { + const providerType = (process.env.STORAGE_PROVIDER || 'gcs').toLowerCase() as StorageProviderType + + console.log(`Initializing storage provider: ${providerType}`) + + switch (providerType) { + case 'gcs': + storageProvider = new GCSStorageProvider() + break + case 'aws': + storageProvider = new S3StorageProvider() + break + default: + throw new Error(`Unsupported storage provider: ${providerType}. Supported values: 'gcs', 'aws'`) + } + } + + return storageProvider +} + +/** + * Reset the storage provider instance (useful for testing) + */ +export function resetStorageProvider(): void { + storageProvider = null +} + +// Re-export types for convenience +export * from './types' \ No newline at end of file diff --git a/src/utils/storage/providers/gcs.ts b/src/utils/storage/providers/gcs.ts new file mode 100644 index 0000000..6146dda --- /dev/null +++ b/src/utils/storage/providers/gcs.ts @@ -0,0 +1,158 @@ +import { Storage } from '@google-cloud/storage' +import path from 'path' +import { + StorageProvider, + UploadParams, + UploadResponse, + ObjectMetadata, + ListObjectsParams, + ListObjectsResponse, + ObjectInfo +} from '../types' + +export class GCSStorageProvider implements StorageProvider { + private storage: Storage + private bucket: any + private bucketName: string + + constructor() { + const { GCP_PROJECT_ID, GCP_BUCKET_NAME, STORAGE_BUCKET_NAME } = process.env + + // Support both old and new env var names + this.bucketName = STORAGE_BUCKET_NAME || GCP_BUCKET_NAME || '' + + if (!this.bucketName || !GCP_PROJECT_ID) { + throw new Error('Missing required Google Cloud Storage environment variables.') + } + + const serviceKey = path.join(__dirname, '../../../../storage-creds.json') + this.storage = new Storage({ + keyFilename: serviceKey, + projectId: GCP_PROJECT_ID + }) + + this.bucket = this.storage.bucket(this.bucketName) + } + + async generateUploadURL({ + size, + expiryTime, + objectIdentifier, + uploaderIdentityKey + }: UploadParams): Promise { + const bucketFile = this.bucket.file(`cdn/${objectIdentifier}`) + + // Calculate the custom time (e.g., expiry time plus 5 minutes) + const customTime = new Date((expiryTime + 300) * 1000).toISOString() + + // Generate the signed URL including the metadata headers. + // The extensionHeaders are part of the signature and must be included by the client in the PUT request. + const [uploadURL] = await bucketFile.getSignedUrl({ + version: 'v4', + action: 'write', + expires: Date.now() + 604000 * 1000, // 1 week + extensionHeaders: { + 'content-length': size.toString(), + 'x-goog-meta-uploaderidentitykey': uploaderIdentityKey, + 'x-goog-custom-time': customTime + } + }) + + return { + uploadURL, + requiredHeaders: { + 'content-length': size.toString(), + 'x-goog-meta-uploaderidentitykey': uploaderIdentityKey, + 'x-goog-custom-time': customTime + } + } + } + + async getObjectMetadata(objectKey: string): Promise { + const file = this.bucket.file(objectKey) + const [metadata] = await file.getMetadata() + + return { + size: parseInt(metadata.size, 10), + contentType: metadata.contentType || 'application/octet-stream', + lastModified: new Date(metadata.updated || metadata.timeCreated), + customMetadata: metadata.metadata || {} + } + } + + async objectExists(objectKey: string): Promise { + const file = this.bucket.file(objectKey) + const [exists] = await file.exists() + return exists + } + + async deleteObject(objectKey: string): Promise { + const file = this.bucket.file(objectKey) + await file.delete() + } + + async listObjects({ prefix, maxKeys = 1000, continuationToken }: ListObjectsParams): Promise { + const options: any = { + prefix, + maxResults: maxKeys + } + + if (continuationToken) { + options.pageToken = continuationToken + } + + const [files, nextQuery] = await this.bucket.getFiles(options) + + const objects: ObjectInfo[] = files.map((file: any) => ({ + key: file.name, + size: parseInt(file.metadata.size, 10), + lastModified: new Date(file.metadata.updated || file.metadata.timeCreated) + })) + + return { + objects, + continuationToken: nextQuery?.pageToken, + isTruncated: !!nextQuery?.pageToken + } + } + + async generateDownloadURL(objectKey: string, expiresIn: number): Promise { + const file = this.bucket.file(objectKey) + const [url] = await file.getSignedUrl({ + version: 'v4', + action: 'read', + expires: Date.now() + expiresIn * 1000 + }) + return url + } + + async downloadObject(objectKey: string): Promise { + const file = this.bucket.file(objectKey) + const [buffer] = await file.download() + return buffer + } + + async updateObjectMetadata(objectKey: string, metadata: { customTime?: string; [key: string]: any }): Promise { + const file = this.bucket.file(objectKey) + + // GCS uses setMetadata to update metadata + const gcsMetadata: any = {} + + if (metadata.customTime) { + gcsMetadata.customTime = metadata.customTime + } + + // Map other metadata fields if needed + Object.keys(metadata).forEach(key => { + if (key !== 'customTime') { + gcsMetadata[key] = metadata[key] + } + }) + + await file.setMetadata(gcsMetadata) + } + + getProviderName(): string { + return 'gcs' + } +} \ No newline at end of file diff --git a/src/utils/storage/providers/s3.ts b/src/utils/storage/providers/s3.ts new file mode 100644 index 0000000..e5ff7c2 --- /dev/null +++ b/src/utils/storage/providers/s3.ts @@ -0,0 +1,213 @@ +import { + S3Client, + PutObjectCommand, + GetObjectCommand, + HeadObjectCommand, + DeleteObjectCommand, + ListObjectsV2Command, + CopyObjectCommand +} from '@aws-sdk/client-s3' +import { getSignedUrl } from '@aws-sdk/s3-request-presigner' +import { createPresignedPost } from '@aws-sdk/s3-presigned-post' +import { + StorageProvider, + UploadParams, + UploadResponse, + ObjectMetadata, + ListObjectsParams, + ListObjectsResponse, + ObjectInfo +} from '../types' + +export class S3StorageProvider implements StorageProvider { + private s3Client: S3Client + private bucketName: string + + constructor() { + const { AWS_REGION, AWS_BUCKET_NAME, STORAGE_BUCKET_NAME } = process.env + + // Support both old and new env var names + this.bucketName = STORAGE_BUCKET_NAME || AWS_BUCKET_NAME || '' + + if (!this.bucketName) { + throw new Error('Missing required AWS S3 bucket name environment variable.') + } + + this.s3Client = new S3Client({ + region: AWS_REGION || 'us-west-2', + // Credentials are automatically loaded from IAM role in ECS/EKS + }) + } + + async generateUploadURL({ + size, + expiryTime, + objectIdentifier, + uploaderIdentityKey + }: UploadParams): Promise { + const objectKey = `cdn/${objectIdentifier}` + + // Calculate the custom time (e.g., expiry time plus 5 minutes) + const customTime = new Date((expiryTime + 300) * 1000).toISOString() + + // Use S3 POST presigned URL with metadata (matches GCS behavior) + // This allows metadata to be included without signature issues + const { url: uploadURL, fields } = await createPresignedPost(this.s3Client, { + Bucket: this.bucketName, + Key: objectKey, + Conditions: [ + ['content-length-range', size, size], // Exact size match + ['starts-with', '$x-amz-meta-uploaderidentitykey', ''], // Allow metadata + ['starts-with', '$x-amz-meta-customtime', ''] // Allow metadata + ], + Fields: { + 'x-amz-meta-uploaderidentitykey': uploaderIdentityKey, + 'x-amz-meta-customtime': customTime + }, + Expires: 604800 // 1 week in seconds + }) + + // Return POST presigned URL with form fields + // Client must use multipart/form-data POST request + return { + uploadURL, + requiredHeaders: { + 'content-type': 'multipart/form-data' + }, + formFields: fields + } + } + + async getObjectMetadata(objectKey: string): Promise { + const command = new HeadObjectCommand({ + Bucket: this.bucketName, + Key: objectKey + }) + + const response = await this.s3Client.send(command) + + return { + size: response.ContentLength || 0, + contentType: response.ContentType || 'application/octet-stream', + lastModified: response.LastModified || new Date(), + customMetadata: response.Metadata || {} + } + } + + async objectExists(objectKey: string): Promise { + try { + await this.getObjectMetadata(objectKey) + return true + } catch (error: any) { + if (error.name === 'NotFound' || error.$metadata?.httpStatusCode === 404) { + return false + } + throw error + } + } + + async deleteObject(objectKey: string): Promise { + const command = new DeleteObjectCommand({ + Bucket: this.bucketName, + Key: objectKey + }) + + await this.s3Client.send(command) + } + + async listObjects({ prefix, maxKeys = 1000, continuationToken }: ListObjectsParams): Promise { + const command = new ListObjectsV2Command({ + Bucket: this.bucketName, + Prefix: prefix, + MaxKeys: maxKeys, + ContinuationToken: continuationToken + }) + + const response = await this.s3Client.send(command) + + const objects: ObjectInfo[] = (response.Contents || []).map(obj => ({ + key: obj.Key || '', + size: obj.Size || 0, + lastModified: obj.LastModified || new Date() + })) + + return { + objects, + continuationToken: response.NextContinuationToken, + isTruncated: response.IsTruncated || false + } + } + + async generateDownloadURL(objectKey: string, expiresIn: number): Promise { + const command = new GetObjectCommand({ + Bucket: this.bucketName, + Key: objectKey + }) + + return await getSignedUrl(this.s3Client, command, { expiresIn }) + } + + async downloadObject(objectKey: string): Promise { + const command = new GetObjectCommand({ + Bucket: this.bucketName, + Key: objectKey + }) + + const response = await this.s3Client.send(command) + + // Convert stream to buffer + const chunks: Uint8Array[] = [] + const stream = response.Body as any + + for await (const chunk of stream) { + chunks.push(chunk) + } + + return Buffer.concat(chunks) + } + + async updateObjectMetadata(objectKey: string, metadata: { customTime?: string; [key: string]: any }): Promise { + // S3 doesn't support updating metadata without copying the object + // We need to copy the object to itself with new metadata + + // First, get current object metadata + const headCommand = new HeadObjectCommand({ + Bucket: this.bucketName, + Key: objectKey + }) + + const currentObject = await this.s3Client.send(headCommand) + + // Prepare new metadata + const newMetadata: { [key: string]: string } = { + ...(currentObject.Metadata || {}) + } + + if (metadata.customTime) { + newMetadata.customtime = metadata.customTime + } + + // Add other metadata fields + Object.keys(metadata).forEach(key => { + if (key !== 'customTime') { + newMetadata[key.toLowerCase()] = String(metadata[key]) + } + }) + + // Copy object to itself with new metadata + const copyCommand = new CopyObjectCommand({ + Bucket: this.bucketName, + Key: objectKey, + CopySource: `${this.bucketName}/${objectKey}`, + Metadata: newMetadata, + MetadataDirective: 'REPLACE', + ContentType: currentObject.ContentType + }) + + await this.s3Client.send(copyCommand) + } + + getProviderName(): string { + return 'aws' + } +} \ No newline at end of file diff --git a/src/utils/storage/types.ts b/src/utils/storage/types.ts new file mode 100644 index 0000000..67329a1 --- /dev/null +++ b/src/utils/storage/types.ts @@ -0,0 +1,93 @@ +/** + * Storage provider abstraction layer for multi-cloud support + */ + +export interface UploadParams { + size: number + expiryTime: number + objectIdentifier: string + uploaderIdentityKey: string +} + +export interface UploadResponse { + uploadURL: string + requiredHeaders: Record + formFields?: Record // For S3 POST presigned URLs +} + +export interface ObjectMetadata { + size: number + contentType: string + lastModified: Date + customMetadata: Record +} + +export interface ObjectInfo { + key: string + size: number + lastModified: Date +} + +export interface ListObjectsParams { + prefix?: string + maxKeys?: number + continuationToken?: string +} + +export interface ListObjectsResponse { + objects: ObjectInfo[] + continuationToken?: string + isTruncated: boolean +} + +/** + * Abstract interface for storage providers (GCS, S3, etc.) + */ +export interface StorageProvider { + /** + * Generate a signed URL for uploading an object + */ + generateUploadURL(params: UploadParams): Promise + + /** + * Get metadata for an object + */ + getObjectMetadata(objectKey: string): Promise + + /** + * Check if an object exists + */ + objectExists(objectKey: string): Promise + + /** + * Delete an object + */ + deleteObject(objectKey: string): Promise + + /** + * List objects with optional prefix + */ + listObjects(params: ListObjectsParams): Promise + + /** + * Get a signed URL for downloading an object + */ + generateDownloadURL(objectKey: string, expiresIn: number): Promise + + /** + * Download an object and return its content + */ + downloadObject(objectKey: string): Promise + + /** + * Update metadata for an object + */ + updateObjectMetadata(objectKey: string, metadata: { customTime?: string; [key: string]: any }): Promise + + /** + * Get the provider name + */ + getProviderName(): string +} + +export type StorageProviderType = 'gcs' | 'aws' \ No newline at end of file